Merge pull request #8781 from pchote/heightmap-shroud

Add plumbing for heighmap-aware shroud and map bounds checks.
This commit is contained in:
Oliver Brakmann
2015-07-28 15:35:51 +02:00
22 changed files with 626 additions and 292 deletions

View File

@@ -98,8 +98,8 @@ namespace OpenRA.Graphics
var cells = restrictToBounds ? viewport.VisibleCellsInsideBounds : viewport.AllVisibleCells; var cells = restrictToBounds ? viewport.VisibleCellsInsideBounds : viewport.AllVisibleCells;
// Only draw the rows that are visible. // Only draw the rows that are visible.
var firstRow = cells.MapCoords.TopLeft.V; var firstRow = cells.CandidateMapCoords.TopLeft.V.Clamp(0, map.MapSize.Y);
var lastRow = Math.Min(cells.MapCoords.BottomRight.V + 1, map.MapSize.Y); var lastRow = (cells.CandidateMapCoords.BottomRight.V + 1).Clamp(firstRow, map.MapSize.Y);
Game.Renderer.Flush(); Game.Renderer.Flush();

View File

@@ -48,10 +48,10 @@ namespace OpenRA.Graphics
public int2 TopLeft { get { return CenterLocation - viewportSize / 2; } } public int2 TopLeft { get { return CenterLocation - viewportSize / 2; } }
public int2 BottomRight { get { return CenterLocation + viewportSize / 2; } } public int2 BottomRight { get { return CenterLocation + viewportSize / 2; } }
int2 viewportSize; int2 viewportSize;
CellRegion cells; ProjectedCellRegion cells;
bool cellsDirty = true; bool cellsDirty = true;
CellRegion allCells; ProjectedCellRegion allCells;
bool allCellsDirty = true; bool allCellsDirty = true;
float zoom = 1f; float zoom = 1f;
@@ -93,15 +93,27 @@ namespace OpenRA.Graphics
{ {
worldRenderer = wr; worldRenderer = wr;
var cells = wr.World.Type == WorldType.Editor ?
map.AllCells : map.CellsInsideBounds;
// Calculate map bounds in world-px // Calculate map bounds in world-px
var tl = wr.ScreenPxPosition(map.CenterOfCell(cells.TopLeft) - new WVec(512, 512, 0)); if (wr.World.Type == WorldType.Editor)
var br = wr.ScreenPxPosition(map.CenterOfCell(cells.BottomRight) + new WVec(511, 511, 0)); {
mapBounds = Rectangle.FromLTRB(tl.X, tl.Y, br.X, br.Y); // The full map is visible in the editor
var ts = Game.ModData.Manifest.TileSize;
var width = map.MapSize.X * ts.Width;
var height = map.MapSize.Y * ts.Height;
if (wr.World.Map.TileShape == TileShape.Diamond)
height /= 2;
mapBounds = new Rectangle(0, 0, width, height);
CenterLocation = new int2(width / 2, height / 2);
}
else
{
var tl = wr.ScreenPxPosition(map.ProjectedTopLeft);
var br = wr.ScreenPxPosition(map.ProjectedBottomRight);
mapBounds = Rectangle.FromLTRB(tl.X, tl.Y, br.X, br.Y);
CenterLocation = (tl + br) / 2;
}
CenterLocation = (tl + br) / 2;
Zoom = Game.Settings.Graphics.PixelDouble ? 2 : 1; Zoom = Game.Settings.Graphics.PixelDouble ? 2 : 1;
tileSize = Game.ModData.Manifest.TileSize; tileSize = Game.ModData.Manifest.TileSize;
} }
@@ -209,8 +221,8 @@ namespace OpenRA.Graphics
// Visible rectangle in world coordinates (expanded to the corners of the cells) // Visible rectangle in world coordinates (expanded to the corners of the cells)
var bounds = insideBounds ? VisibleCellsInsideBounds : AllVisibleCells; var bounds = insideBounds ? VisibleCellsInsideBounds : AllVisibleCells;
var map = worldRenderer.World.Map; var map = worldRenderer.World.Map;
var ctl = map.CenterOfCell(bounds.TopLeft) - new WVec(512, 512, 0); var ctl = map.CenterOfCell(((MPos)bounds.TopLeft).ToCPos(map)) - new WVec(512, 512, 0);
var cbr = map.CenterOfCell(bounds.BottomRight) + new WVec(512, 512, 0); var cbr = map.CenterOfCell(((MPos)bounds.BottomRight).ToCPos(map)) + new WVec(512, 512, 0);
// Convert to screen coordinates // Convert to screen coordinates
var tl = WorldToViewPx(worldRenderer.ScreenPxPosition(ctl - new WVec(0, 0, ctl.Z))).Clamp(ScreenClip); var tl = WorldToViewPx(worldRenderer.ScreenPxPosition(ctl - new WVec(0, 0, ctl.Z))).Clamp(ScreenClip);
@@ -221,22 +233,21 @@ namespace OpenRA.Graphics
br.X + tileSize.Width, br.Y + tileSize.Height); br.X + tileSize.Width, br.Y + tileSize.Height);
} }
CellRegion CalculateVisibleCells(bool insideBounds) ProjectedCellRegion CalculateVisibleCells(bool insideBounds)
{ {
var map = worldRenderer.World.Map; var map = worldRenderer.World.Map;
// Calculate the viewport corners in "projected wpos" (at ground level), and // Calculate the projected cell position at the corners of the visible area
// this to an equivalent projected cell for the two corners var tl = (PPos)map.CellContaining(worldRenderer.ProjectedPosition(TopLeft)).ToMPos(map);
var tl = map.CellContaining(worldRenderer.ProjectedPosition(TopLeft)).ToMPos(map); var br = (PPos)map.CellContaining(worldRenderer.ProjectedPosition(BottomRight)).ToMPos(map);
var br = map.CellContaining(worldRenderer.ProjectedPosition(BottomRight)).ToMPos(map);
// Diamond tile shapes don't have straight edges, and so we need // Diamond tile shapes don't have straight edges, and so we need
// an additional cell margin to include the cells that are half // an additional cell margin to include the cells that are half
// visible on each edge. // visible on each edge.
if (map.TileShape == TileShape.Diamond) if (map.TileShape == TileShape.Diamond)
{ {
tl = new MPos(tl.U - 1, tl.V - 1); tl = new PPos(tl.U - 1, tl.V - 1);
br = new MPos(br.U + 1, br.V + 1); br = new PPos(br.U + 1, br.V + 1);
} }
// Clamp to the visible map bounds, if requested // Clamp to the visible map bounds, if requested
@@ -246,21 +257,10 @@ namespace OpenRA.Graphics
br = map.Clamp(br); br = map.Clamp(br);
} }
// Cells can be pushed up from below if they have non-zero height. return new ProjectedCellRegion(map, tl, br);
// Each height step is equivalent to 512 WDist units, which is
// one MPos step for diamond cells, but only half a MPos step
// for classic cells. Doh!
var heightOffset = map.TileShape == TileShape.Diamond ? map.MaximumTerrainHeight : map.MaximumTerrainHeight / 2;
br = new MPos(br.U, br.V + heightOffset);
// Finally, make sure that this region doesn't extend outside the map area.
tl = map.MapHeight.Value.Clamp(tl);
br = map.MapHeight.Value.Clamp(br);
return new CellRegion(map.TileShape, tl.ToCPos(map), br.ToCPos(map));
} }
public CellRegion VisibleCellsInsideBounds public ProjectedCellRegion VisibleCellsInsideBounds
{ {
get get
{ {
@@ -274,7 +274,7 @@ namespace OpenRA.Graphics
} }
} }
public CellRegion AllVisibleCells public ProjectedCellRegion AllVisibleCells
{ {
get get
{ {

View File

@@ -61,4 +61,34 @@ namespace OpenRA
return new CPos(x, y); return new CPos(x, y);
} }
} }
/// <summary>
/// Projected map position
/// </summary>
public struct PPos : IEquatable<PPos>
{
public readonly int U, V;
public PPos(int u, int v) { U = u; V = v; }
public static readonly PPos Zero = new PPos(0, 0);
public static bool operator ==(PPos me, PPos other) { return me.U == other.U && me.V == other.V; }
public static bool operator !=(PPos me, PPos other) { return !(me == other); }
public static explicit operator MPos(PPos puv) { return new MPos(puv.U, puv.V); }
public static explicit operator PPos(MPos uv) { return new PPos(uv.U, uv.V); }
public PPos Clamp(Rectangle r)
{
return new PPos(Math.Min(r.Right, Math.Max(U, r.Left)),
Math.Min(r.Bottom, Math.Max(V, r.Top)));
}
public override int GetHashCode() { return U.GetHashCode() ^ V.GetHashCode(); }
public bool Equals(PPos other) { return other == this; }
public override bool Equals(object obj) { return obj is PPos && Equals((PPos)obj); }
public override string ToString() { return U + "," + V; }
}
} }

View File

@@ -87,7 +87,7 @@ namespace OpenRA
public MapCoordsRegion MapCoords public MapCoordsRegion MapCoords
{ {
get { return new MapCoordsRegion(this); } get { return new MapCoordsRegion(mapTopLeft, mapBottomRight); }
} }
public CellRegionEnumerator GetEnumerator() public CellRegionEnumerator GetEnumerator()
@@ -151,75 +151,5 @@ namespace OpenRA
object IEnumerator.Current { get { return Current; } } object IEnumerator.Current { get { return Current; } }
public void Dispose() { } public void Dispose() { }
} }
public struct MapCoordsRegion : IEnumerable<MPos>
{
public struct MapCoordsEnumerator : IEnumerator<MPos>
{
readonly CellRegion r;
MPos current;
public MapCoordsEnumerator(CellRegion region)
: this()
{
r = region;
Reset();
}
public bool MoveNext()
{
var u = current.U + 1;
var v = current.V;
// Check for column overflow
if (u > r.mapBottomRight.U)
{
v += 1;
u = r.mapTopLeft.U;
// Check for row overflow
if (v > r.mapBottomRight.V)
return false;
}
current = new MPos(u, v);
return true;
}
public void Reset()
{
current = new MPos(r.mapTopLeft.U - 1, r.mapTopLeft.V);
}
public MPos Current { get { return current; } }
object IEnumerator.Current { get { return Current; } }
public void Dispose() { }
}
readonly CellRegion r;
public MapCoordsRegion(CellRegion region)
{
r = region;
}
public MapCoordsEnumerator GetEnumerator()
{
return new MapCoordsEnumerator(r);
}
IEnumerator<MPos> IEnumerable<MPos>.GetEnumerator()
{
return GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public MPos TopLeft { get { return r.mapTopLeft; } }
public MPos BottomRight { get { return r.mapBottomRight; } }
}
} }
} }

View File

@@ -9,6 +9,7 @@
#endregion #endregion
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using System.Drawing.Imaging; using System.Drawing.Imaging;
@@ -245,6 +246,8 @@ namespace OpenRA
[FieldLoader.Ignore] public Lazy<CellLayer<byte>> MapHeight; [FieldLoader.Ignore] public Lazy<CellLayer<byte>> MapHeight;
[FieldLoader.Ignore] public CellLayer<byte> CustomTerrain; [FieldLoader.Ignore] public CellLayer<byte> CustomTerrain;
[FieldLoader.Ignore] CellLayer<PPos[]> cellProjection;
[FieldLoader.Ignore] CellLayer<List<MPos>> inverseCellProjection;
[FieldLoader.Ignore] Lazy<TileSet> cachedTileSet; [FieldLoader.Ignore] Lazy<TileSet> cachedTileSet;
[FieldLoader.Ignore] Lazy<Ruleset> rules; [FieldLoader.Ignore] Lazy<Ruleset> rules;
@@ -252,9 +255,11 @@ namespace OpenRA
public SequenceProvider SequenceProvider { get { return Rules.Sequences[Tileset]; } } public SequenceProvider SequenceProvider { get { return Rules.Sequences[Tileset]; } }
public WVec[][] CellCorners { get; private set; } public WVec[][] CellCorners { get; private set; }
[FieldLoader.Ignore] public CellRegion CellsInsideBounds; [FieldLoader.Ignore] public ProjectedCellRegion ProjectedCellBounds;
[FieldLoader.Ignore] public CellRegion AllCells; [FieldLoader.Ignore] public CellRegion AllCells;
readonly Func<PPos, bool> containsTest;
void AssertExists(string filename) void AssertExists(string filename)
{ {
using (var s = Container.GetContent(filename)) using (var s = Container.GetContent(filename))
@@ -268,6 +273,8 @@ namespace OpenRA
/// </summary> /// </summary>
public Map(TileSet tileset, int width, int height) public Map(TileSet tileset, int width, int height)
{ {
containsTest = Contains;
var size = new Size(width, height); var size = new Size(width, height);
var tileShape = Game.ModData.Manifest.TileShape; var tileShape = Game.ModData.Manifest.TileShape;
var tileRef = new TerrainTile(tileset.Templates.First().Key, (byte)0); var tileRef = new TerrainTile(tileset.Templates.First().Key, (byte)0);
@@ -307,6 +314,8 @@ namespace OpenRA
/// <summary>Initializes a map loaded from disk.</summary> /// <summary>Initializes a map loaded from disk.</summary>
public Map(string path) public Map(string path)
{ {
containsTest = Contains;
Path = path; Path = path;
Container = GlobalFileSystem.OpenPackage(path, null, int.MaxValue); Container = GlobalFileSystem.OpenPackage(path, null, int.MaxValue);
@@ -409,8 +418,8 @@ namespace OpenRA
var br = new MPos(MapSize.X - 1, MapSize.Y - 1).ToCPos(this); var br = new MPos(MapSize.X - 1, MapSize.Y - 1).ToCPos(this);
AllCells = new CellRegion(TileShape, tl, br); AllCells = new CellRegion(TileShape, tl, br);
var btl = new MPos(Bounds.Left, Bounds.Top); var btl = new PPos(Bounds.Left, Bounds.Top);
var bbr = new MPos(Bounds.Right - 1, Bounds.Bottom - 1); var bbr = new PPos(Bounds.Right - 1, Bounds.Bottom - 1);
SetBounds(btl, bbr); SetBounds(btl, bbr);
CustomTerrain = new CellLayer<byte>(this); CustomTerrain = new CellLayer<byte>(this);
@@ -428,6 +437,80 @@ namespace OpenRA
rightDelta + new WVec(0, 0, 512 * ramp[2]), rightDelta + new WVec(0, 0, 512 * ramp[2]),
bottomDelta + new WVec(0, 0, 512 * ramp[3]) bottomDelta + new WVec(0, 0, 512 * ramp[3])
}).ToArray(); }).ToArray();
if (MaximumTerrainHeight != 0)
{
cellProjection = new CellLayer<PPos[]>(this);
inverseCellProjection = new CellLayer<List<MPos>>(this);
// Initialize collections
foreach (var cell in AllCells)
{
var uv = cell.ToMPos(TileShape);
cellProjection[uv] = new PPos[0];
inverseCellProjection[uv] = new List<MPos>();
}
// Initialize projections
foreach (var cell in AllCells)
UpdateProjection(cell);
}
}
void UpdateProjection(CPos cell)
{
if (MaximumTerrainHeight == 0)
return;
var uv = cell.ToMPos(TileShape);
// Remove old reverse projection
foreach (var puv in cellProjection[uv])
inverseCellProjection[(MPos)puv].Remove(uv);
var projected = ProjectCellInner(uv);
cellProjection[uv] = projected;
foreach (var puv in projected)
inverseCellProjection[(MPos)puv].Add(uv);
}
PPos[] ProjectCellInner(MPos uv)
{
var mapHeight = MapHeight.Value;
if (!mapHeight.Contains(uv))
return NoProjectedCells;
var height = mapHeight[uv];
if (height == 0)
return new[] { (PPos)uv };
// Odd-height ramps get bumped up a level to the next even height layer
if ((height & 1) == 1)
{
var ti = cachedTileSet.Value.GetTileInfo(MapTiles.Value[uv]);
if (ti != null && ti.RampType != 0)
height += 1;
}
var candidates = new List<PPos>();
// Odd-height level tiles are equally covered by four projected tiles
if ((height & 1) == 1)
{
if ((uv.V & 1) == 1)
candidates.Add(new PPos(uv.U + 1, uv.V - height));
else
candidates.Add(new PPos(uv.U - 1, uv.V - height));
candidates.Add(new PPos(uv.U, uv.V - height));
candidates.Add(new PPos(uv.U, uv.V - height + 1));
candidates.Add(new PPos(uv.U, uv.V - height - 1));
}
else
candidates.Add(new PPos(uv.U, uv.V - height));
return candidates.Where(c => mapHeight.Contains((MPos)c)).ToArray();
} }
public Ruleset PreloadRules() public Ruleset PreloadRules()
@@ -534,6 +617,8 @@ namespace OpenRA
} }
} }
tiles.CellEntryChanged += UpdateProjection;
return tiles; return tiles;
} }
@@ -552,6 +637,8 @@ namespace OpenRA
} }
} }
tiles.CellEntryChanged += UpdateProjection;
return tiles; return tiles;
} }
@@ -652,7 +739,15 @@ namespace OpenRA
public bool Contains(MPos uv) public bool Contains(MPos uv)
{ {
return Bounds.Contains(uv.U, uv.V); // TODO: Checking against the bounds excludes valid parts of the map if MaxTerrainHeight > 0.
// Unfortunatley, doing this properly leads to memory corruption issues in the (unsafe) radar
// rendering code.
return Bounds.Contains(uv.U, uv.V) && ProjectedCellsCovering(uv).All(containsTest);
}
public bool Contains(PPos puv)
{
return Bounds.Contains(puv.U, puv.V);
} }
public WPos CenterOfCell(CPos cell) public WPos CenterOfCell(CPos cell)
@@ -695,6 +790,39 @@ namespace OpenRA
return new CPos(u, v); return new CPos(u, v);
} }
public PPos ProjectedCellCovering(WPos pos)
{
var projectedPos = pos - new WVec(0, pos.Z, pos.Z);
return (PPos)CellContaining(projectedPos).ToMPos(TileShape);
}
static readonly PPos[] NoProjectedCells = { };
public PPos[] ProjectedCellsCovering(MPos uv)
{
// Shortcut for mods that don't use heightmaps
if (MaximumTerrainHeight == 0)
return new[] { (PPos)uv };
if (!cellProjection.Contains(uv))
return NoProjectedCells;
return cellProjection[uv];
}
public MPos[] Unproject(PPos puv)
{
var uv = (MPos)puv;
// Shortcut for mods that don't use heightmaps
if (MaximumTerrainHeight == 0)
return new[] { uv };
if (!inverseCellProjection.Contains(uv))
return new MPos[0];
return inverseCellProjection[uv].ToArray();
}
public int FacingBetween(CPos cell, CPos towards, int fallbackfacing) public int FacingBetween(CPos cell, CPos towards, int fallbackfacing)
{ {
return Traits.Util.GetFacing(CenterOfCell(towards) - CenterOfCell(cell), fallbackfacing); return Traits.Util.GetFacing(CenterOfCell(towards) - CenterOfCell(cell), fallbackfacing);
@@ -717,12 +845,11 @@ namespace OpenRA
AllCells = new CellRegion(TileShape, tl, br); AllCells = new CellRegion(TileShape, tl, br);
} }
public void SetBounds(MPos tl, MPos br) public void SetBounds(PPos tl, PPos br)
{ {
// The tl and br coordinates are inclusive, but the Rectangle // The tl and br coordinates are inclusive, but the Rectangle
// is exclusive. Pad the right and bottom edges to match. // is exclusive. Pad the right and bottom edges to match.
Bounds = Rectangle.FromLTRB(tl.U, tl.V, br.U + 1, br.V + 1); Bounds = Rectangle.FromLTRB(tl.U, tl.V, br.U + 1, br.V + 1);
CellsInsideBounds = new CellRegion(TileShape, tl.ToCPos(this), br.ToCPos(this));
// Directly calculate the projected map corners in world units avoiding unnecessary // Directly calculate the projected map corners in world units avoiding unnecessary
// conversions. This abuses the definition that the width of the cell is always // conversions. This abuses the definition that the width of the cell is always
@@ -738,6 +865,8 @@ namespace OpenRA
ProjectedTopLeft = new WPos(tl.U * 1024, wtop, 0); ProjectedTopLeft = new WPos(tl.U * 1024, wtop, 0);
ProjectedBottomRight = new WPos(br.U * 1024 - 1, wbottom - 1, 0); ProjectedBottomRight = new WPos(br.U * 1024 - 1, wbottom - 1, 0);
ProjectedCellBounds = new ProjectedCellRegion(this, tl, br);
} }
string ComputeHash() string ComputeHash()
@@ -799,18 +928,28 @@ namespace OpenRA
public CPos Clamp(CPos cell) public CPos Clamp(CPos cell)
{ {
var bounds = new Rectangle(Bounds.X, Bounds.Y, Bounds.Width - 1, Bounds.Height - 1); return Clamp(cell.ToMPos(this)).ToCPos(this);
return cell.ToMPos(this).Clamp(bounds).ToCPos(this);
} }
public MPos Clamp(MPos uv) public MPos Clamp(MPos uv)
{
// Already in bounds, so don't need to do anything.
if (Contains(uv))
return uv;
// TODO: Account for terrain height
return (MPos)Clamp((PPos)uv);
}
public PPos Clamp(PPos puv)
{ {
var bounds = new Rectangle(Bounds.X, Bounds.Y, Bounds.Width - 1, Bounds.Height - 1); var bounds = new Rectangle(Bounds.X, Bounds.Y, Bounds.Width - 1, Bounds.Height - 1);
return uv.Clamp(bounds); return puv.Clamp(bounds);
} }
public CPos ChooseRandomCell(MersenneTwister rand) public CPos ChooseRandomCell(MersenneTwister rand)
{ {
// TODO: Account for terrain height
var x = rand.Next(Bounds.Left, Bounds.Right); var x = rand.Next(Bounds.Left, Bounds.Right);
var y = rand.Next(Bounds.Top, Bounds.Bottom); var y = rand.Next(Bounds.Top, Bounds.Bottom);
@@ -819,6 +958,7 @@ namespace OpenRA
public CPos ChooseClosestEdgeCell(CPos pos) public CPos ChooseClosestEdgeCell(CPos pos)
{ {
// TODO: Account for terrain height
var mpos = pos.ToMPos(this); var mpos = pos.ToMPos(this);
var horizontalBound = ((mpos.U - Bounds.Left) < Bounds.Width / 2) ? Bounds.Left : Bounds.Right; var horizontalBound = ((mpos.U - Bounds.Left) < Bounds.Width / 2) ? Bounds.Left : Bounds.Right;
@@ -832,6 +972,7 @@ namespace OpenRA
public CPos ChooseRandomEdgeCell(MersenneTwister rand) public CPos ChooseRandomEdgeCell(MersenneTwister rand)
{ {
// TODO: Account for terrain height
var isX = rand.Next(2) == 0; var isX = rand.Next(2) == 0;
var edge = rand.Next(2) == 0; var edge = rand.Next(2) == 0;
@@ -843,8 +984,10 @@ namespace OpenRA
public WDist DistanceToEdge(WPos pos, WVec dir) public WDist DistanceToEdge(WPos pos, WVec dir)
{ {
var tl = CenterOfCell(CellsInsideBounds.TopLeft) - new WVec(512, 512, 0); // TODO: Account for terrain height
var br = CenterOfCell(CellsInsideBounds.BottomRight) + new WVec(511, 511, 0); // Project into the screen plane and then compare against ProjectedWorldBounds.
var tl = CenterOfCell(((MPos)ProjectedCellBounds.TopLeft).ToCPos(this)) - new WVec(512, 512, 0);
var br = CenterOfCell(((MPos)ProjectedCellBounds.BottomRight).ToCPos(this)) + new WVec(511, 511, 0);
var x = dir.X == 0 ? int.MaxValue : ((dir.X < 0 ? tl.X : br.X) - pos.X) / dir.X; var x = dir.X == 0 ? int.MaxValue : ((dir.X < 0 ? tl.X : br.X) - pos.X) / dir.X;
var y = dir.Y == 0 ? int.MaxValue : ((dir.Y < 0 ? tl.Y : br.Y) - pos.Y) / dir.Y; var y = dir.Y == 0 ? int.MaxValue : ((dir.Y < 0 ? tl.Y : br.Y) - pos.Y) / dir.Y;
return new WDist(Math.Min(x, y) * dir.Length); return new WDist(Math.Min(x, y) * dir.Length);

View File

@@ -0,0 +1,89 @@
#region Copyright & License Information
/*
* Copyright 2007-2015 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation. For more information,
* see COPYING.
*/
#endregion
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace OpenRA
{
public struct MapCoordsRegion : IEnumerable<MPos>
{
public struct MapCoordsEnumerator : IEnumerator<MPos>
{
readonly MapCoordsRegion r;
MPos current;
public MapCoordsEnumerator(MapCoordsRegion region)
: this()
{
r = region;
Reset();
}
public bool MoveNext()
{
var u = current.U + 1;
var v = current.V;
// Check for column overflow
if (u > r.bottomRight.U)
{
v += 1;
u = r.topLeft.U;
// Check for row overflow
if (v > r.bottomRight.V)
return false;
}
current = new MPos(u, v);
return true;
}
public void Reset()
{
current = new MPos(r.topLeft.U - 1, r.topLeft.V);
}
public MPos Current { get { return current; } }
object IEnumerator.Current { get { return Current; } }
public void Dispose() { }
}
readonly MPos topLeft;
readonly MPos bottomRight;
public MapCoordsRegion(MPos mapTopLeft, MPos mapBottomRight)
{
this.topLeft = mapTopLeft;
this.bottomRight = mapBottomRight;
}
public MapCoordsEnumerator GetEnumerator()
{
return new MapCoordsEnumerator(this);
}
IEnumerator<MPos> IEnumerable<MPos>.GetEnumerator()
{
return GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public MPos TopLeft { get { return topLeft; } }
public MPos BottomRight { get { return bottomRight; } }
}
}

View File

@@ -0,0 +1,125 @@
#region Copyright & License Information
/*
* Copyright 2007-2015 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation. For more information,
* see COPYING.
*/
#endregion
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace OpenRA
{
// Represents a (on-screen) rectangular collection of tiles.
// TopLeft and BottomRight are inclusive
public class ProjectedCellRegion : IEnumerable<PPos>
{
// Corners of the region
public readonly PPos TopLeft;
public readonly PPos BottomRight;
// Corners of the bounding map region that contains all the cells that
// may be projected within this region.
readonly MPos mapTopLeft;
readonly MPos mapBottomRight;
public ProjectedCellRegion(Map map, PPos topLeft, PPos bottomRight)
{
TopLeft = topLeft;
BottomRight = bottomRight;
// The projection from MPos -> PPos cannot produce a larger V coordinate
// so the top edge of the MPos region is the same as the PPos region.
// (in fact the cells are identical if height == 0)
mapTopLeft = (MPos)topLeft;
// The bottom edge is trickier: cells at MPos.V > bottomRight.V may have
// been projected into this region if they have height > 0.
// Each height step is equivalent to 512 WRange units, which is one MPos
// step for diamond cells, but only half a MPos step for classic cells. Doh!
var heightOffset = map.TileShape == TileShape.Diamond ? map.MaximumTerrainHeight : map.MaximumTerrainHeight / 2;
// Use the MapHeight data array to clamp the bottom coordinate so it doesn't overflow the map
mapBottomRight = map.MapHeight.Value.Clamp(new MPos(bottomRight.U, bottomRight.V + heightOffset));
}
public bool Contains(PPos puv)
{
return puv.U >= TopLeft.U && puv.U <= BottomRight.U && puv.V >= TopLeft.V && puv.V <= BottomRight.V;
}
/// <summary>
/// The region in map coordinates that contains all the cells that
/// may be projected inside this region. For increased performance,
/// this does not validate whether individual map cells are actually
/// projected inside the region.
/// </summary>
public MapCoordsRegion CandidateMapCoords { get { return new MapCoordsRegion(mapTopLeft, mapBottomRight); } }
public ProjectedCellRegionEnumerator GetEnumerator()
{
return new ProjectedCellRegionEnumerator(this);
}
IEnumerator<PPos> IEnumerable<PPos>.GetEnumerator()
{
return GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public sealed class ProjectedCellRegionEnumerator : IEnumerator<PPos>
{
readonly ProjectedCellRegion r;
// Current position, in projected map coordinates
int u, v;
PPos current;
public ProjectedCellRegionEnumerator(ProjectedCellRegion region)
{
r = region;
Reset();
}
public bool MoveNext()
{
u += 1;
// Check for column overflow
if (u > r.BottomRight.U)
{
v += 1;
u = r.TopLeft.U;
// Check for row overflow
if (v > r.BottomRight.V)
return false;
}
current = new PPos(u, v);
return true;
}
public void Reset()
{
// Enumerator starts *before* the first element in the sequence.
u = r.TopLeft.U - 1;
v = r.TopLeft.V;
}
public PPos Current { get { return current; } }
object IEnumerator.Current { get { return Current; } }
public void Dispose() { }
}
}
}

View File

@@ -247,6 +247,8 @@
<Compile Include="Widgets\WorldInteractionControllerWidget.cs" /> <Compile Include="Widgets\WorldInteractionControllerWidget.cs" />
<Compile Include="Graphics\PaletteReference.cs" /> <Compile Include="Graphics\PaletteReference.cs" />
<Compile Include="Graphics\TerrainSpriteLayer.cs" /> <Compile Include="Graphics\TerrainSpriteLayer.cs" />
<Compile Include="Map\ProjectedCellRegion.cs" />
<Compile Include="Map\MapCoordsRegion.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="FileSystem\D2kSoundResources.cs" /> <Compile Include="FileSystem\D2kSoundResources.cs" />

View File

@@ -24,7 +24,7 @@ namespace OpenRA.Traits
public class FrozenActor public class FrozenActor
{ {
public readonly MPos[] Footprint; public readonly PPos[] Footprint;
public readonly WPos CenterPosition; public readonly WPos CenterPosition;
public readonly Rectangle Bounds; public readonly Rectangle Bounds;
readonly Actor actor; readonly Actor actor;
@@ -42,7 +42,7 @@ namespace OpenRA.Traits
public bool NeedRenderables; public bool NeedRenderables;
public bool IsRendering { get; private set; } public bool IsRendering { get; private set; }
public FrozenActor(Actor self, MPos[] footprint, Shroud shroud) public FrozenActor(Actor self, PPos[] footprint, Shroud shroud)
{ {
actor = self; actor = self;
this.shroud = shroud; this.shroud = shroud;

View File

@@ -24,7 +24,7 @@ namespace OpenRA.Traits
{ {
[Sync] public bool Disabled; [Sync] public bool Disabled;
public event Action<IEnumerable<CPos>> CellsChanged; public event Action<IEnumerable<PPos>> CellsChanged;
readonly Actor self; readonly Actor self;
readonly Map map; readonly Map map;
@@ -35,15 +35,15 @@ namespace OpenRA.Traits
// Cache of visibility that was added, so no matter what crazy trait code does, it // Cache of visibility that was added, so no matter what crazy trait code does, it
// can't make us invalid. // can't make us invalid.
readonly Dictionary<Actor, CPos[]> visibility = new Dictionary<Actor, CPos[]>(); readonly Dictionary<Actor, PPos[]> visibility = new Dictionary<Actor, PPos[]>();
readonly Dictionary<Actor, CPos[]> generation = new Dictionary<Actor, CPos[]>(); readonly Dictionary<Actor, PPos[]> generation = new Dictionary<Actor, PPos[]>();
public int Hash { get; private set; } public int Hash { get; private set; }
static readonly Func<MPos, bool> TruthPredicate = _ => true; static readonly Func<PPos, bool> TruthPredicate = _ => true;
readonly Func<MPos, bool> shroudEdgeTest; readonly Func<PPos, bool> shroudEdgeTest;
readonly Func<MPos, bool> isExploredTest; readonly Func<PPos, bool> isExploredTest;
readonly Func<MPos, bool> isVisibleTest; readonly Func<PPos, bool> isVisibleTest;
public Shroud(Actor self) public Shroud(Actor self)
{ {
@@ -55,11 +55,12 @@ namespace OpenRA.Traits
explored = new CellLayer<bool>(map); explored = new CellLayer<bool>(map);
shroudEdgeTest = map.Contains; shroudEdgeTest = map.Contains;
isExploredTest = IsExploredCore;
isVisibleTest = IsVisibleCore; isExploredTest = IsExplored;
isVisibleTest = IsVisible;
} }
void Invalidate(IEnumerable<CPos> changed) void Invalidate(IEnumerable<PPos> changed)
{ {
if (CellsChanged != null) if (CellsChanged != null)
CellsChanged(changed); CellsChanged(changed);
@@ -72,35 +73,38 @@ namespace OpenRA.Traits
Hash += 1; Hash += 1;
} }
public static IEnumerable<CPos> CellsInRange(Map map, WPos pos, WDist range) public static IEnumerable<PPos> ProjectedCellsInRange(Map map, WPos pos, WDist range)
{ {
var r = (range.Length + 1023) / 1024; // Account for potential extra half-cell from odd-height terrain
var r = (range.Length + 1023 + 512) / 1024;
var limit = range.LengthSquared; var limit = range.LengthSquared;
var cell = map.CellContaining(pos);
foreach (var c in map.FindTilesInCircle(cell, r, true)) // Project actor position into the shroud plane
if ((map.CenterOfCell(c) - pos).HorizontalLengthSquared <= limit) var projectedPos = pos - new WVec(0, pos.Z, pos.Z);
yield return c; var projectedCell = map.CellContaining(projectedPos);
foreach (var c in map.FindTilesInCircle(projectedCell, r, true))
if ((map.CenterOfCell(c) - projectedPos).HorizontalLengthSquared <= limit)
yield return (PPos)c.ToMPos(map);
} }
public static IEnumerable<CPos> CellsInRange(Map map, CPos cell, WDist range) public static IEnumerable<PPos> ProjectedCellsInRange(Map map, CPos cell, WDist range)
{ {
return CellsInRange(map, map.CenterOfCell(cell), range); return ProjectedCellsInRange(map, map.CenterOfCell(cell), range);
} }
public void AddVisibility(Actor a, CPos[] visible) public void AddProjectedVisibility(Actor a, PPos[] visible)
{ {
if (!a.Owner.IsAlliedWith(self.Owner)) if (!a.Owner.IsAlliedWith(self.Owner))
return; return;
foreach (var c in visible) foreach (var puv in visible)
{ {
var uv = c.ToMPos(map);
// Force cells outside the visible bounds invisible // Force cells outside the visible bounds invisible
if (!map.Contains(uv)) if (!map.Contains(puv))
continue; continue;
var uv = (MPos)puv;
visibleCount[uv]++; visibleCount[uv]++;
explored[uv] = true; explored[uv] = true;
} }
@@ -114,28 +118,28 @@ namespace OpenRA.Traits
public void RemoveVisibility(Actor a) public void RemoveVisibility(Actor a)
{ {
CPos[] visible; PPos[] visible;
if (!visibility.TryGetValue(a, out visible)) if (!visibility.TryGetValue(a, out visible))
return; return;
foreach (var c in visible) foreach (var puv in visible)
{ {
// Cells outside the visible bounds don't increment visibleCount // Cells outside the visible bounds don't increment visibleCount
if (map.Contains(c)) if (map.Contains(puv))
visibleCount[c.ToMPos(map)]--; visibleCount[(MPos)puv]--;
} }
visibility.Remove(a); visibility.Remove(a);
Invalidate(visible); Invalidate(visible);
} }
public void AddShroudGeneration(Actor a, CPos[] shrouded) public void AddProjectedShroudGeneration(Actor a, PPos[] shrouded)
{ {
if (a.Owner.IsAlliedWith(self.Owner)) if (a.Owner.IsAlliedWith(self.Owner))
return; return;
foreach (var c in shrouded) foreach (var uv in shrouded)
generatedShroudCount[c]++; generatedShroudCount[(MPos)uv]++;
if (generation.ContainsKey(a)) if (generation.ContainsKey(a))
throw new InvalidOperationException("Attempting to add duplicate shroud generation"); throw new InvalidOperationException("Attempting to add duplicate shroud generation");
@@ -146,12 +150,12 @@ namespace OpenRA.Traits
public void RemoveShroudGeneration(Actor a) public void RemoveShroudGeneration(Actor a)
{ {
CPos[] shrouded; PPos[] shrouded;
if (!generation.TryGetValue(a, out shrouded)) if (!generation.TryGetValue(a, out shrouded))
return; return;
foreach (var c in shrouded) foreach (var uv in shrouded)
generatedShroudCount[c]--; generatedShroudCount[(MPos)uv]--;
generation.Remove(a); generation.Remove(a);
Invalidate(shrouded); Invalidate(shrouded);
@@ -164,34 +168,35 @@ namespace OpenRA.Traits
foreach (var a in w.Actors.Where(a => a.Owner == player)) foreach (var a in w.Actors.Where(a => a.Owner == player))
{ {
CPos[] visible = null; PPos[] visible = null;
CPos[] shrouded = null; PPos[] shrouded = null;
foreach (var p in self.World.Players) foreach (var p in self.World.Players)
{ {
if (p.Shroud.visibility.TryGetValue(self, out visible)) if (p.Shroud.visibility.TryGetValue(self, out visible))
{ {
p.Shroud.RemoveVisibility(self); p.Shroud.RemoveVisibility(self);
p.Shroud.AddVisibility(self, visible); p.Shroud.AddProjectedVisibility(self, visible);
} }
if (p.Shroud.generation.TryGetValue(self, out shrouded)) if (p.Shroud.generation.TryGetValue(self, out shrouded))
{ {
p.Shroud.RemoveShroudGeneration(self); p.Shroud.RemoveShroudGeneration(self);
p.Shroud.AddShroudGeneration(self, shrouded); p.Shroud.AddProjectedShroudGeneration(self, shrouded);
} }
} }
} }
} }
public void Explore(World world, IEnumerable<CPos> cells) public void ExploreProjectedCells(World world, IEnumerable<PPos> cells)
{ {
var changed = new HashSet<CPos>(); var changed = new HashSet<PPos>();
foreach (var c in cells) foreach (var puv in cells)
{ {
if (!explored[c]) var uv = (MPos)puv;
if (!explored[uv])
{ {
explored[c] = true; explored[uv] = true;
changed.Add(c); changed.Add(puv);
} }
} }
@@ -203,13 +208,14 @@ namespace OpenRA.Traits
if (map.Bounds != s.map.Bounds) if (map.Bounds != s.map.Bounds)
throw new ArgumentException("The map bounds of these shrouds do not match.", "s"); throw new ArgumentException("The map bounds of these shrouds do not match.", "s");
var changed = new List<CPos>(); var changed = new List<PPos>();
foreach (var uv in map.CellsInsideBounds.MapCoords) foreach (var puv in map.ProjectedCellBounds)
{ {
var uv = (MPos)puv;
if (!explored[uv] && s.explored[uv]) if (!explored[uv] && s.explored[uv])
{ {
explored[uv] = true; explored[uv] = true;
changed.Add(uv.ToCPos(map)); changed.Add(puv);
} }
} }
@@ -218,13 +224,14 @@ namespace OpenRA.Traits
public void ExploreAll(World world) public void ExploreAll(World world)
{ {
var changed = new List<CPos>(); var changed = new List<PPos>();
foreach (var uv in map.CellsInsideBounds.MapCoords) foreach (var puv in map.ProjectedCellBounds)
{ {
var uv = (MPos)puv;
if (!explored[uv]) if (!explored[uv])
{ {
explored[uv] = true; explored[uv] = true;
changed.Add(uv.ToCPos(map)); changed.Add(puv);
} }
} }
@@ -233,14 +240,15 @@ namespace OpenRA.Traits
public void ResetExploration() public void ResetExploration()
{ {
var changed = new List<CPos>(); var changed = new List<PPos>();
foreach (var uv in map.CellsInsideBounds.MapCoords) foreach (var puv in map.ProjectedCellBounds)
{ {
var uv = (MPos)puv;
var visible = visibleCount[uv] > 0; var visible = visibleCount[uv] > 0;
if (explored[uv] != visible) if (explored[uv] != visible)
{ {
explored[uv] = visible; explored[uv] = visible;
changed.Add(uv.ToCPos(map)); changed.Add(puv);
} }
} }
@@ -249,7 +257,7 @@ namespace OpenRA.Traits
public bool IsExplored(WPos pos) public bool IsExplored(WPos pos)
{ {
return IsExplored(map.CellContaining(pos)); return IsExplored(map.ProjectedCellCovering(pos));
} }
public bool IsExplored(CPos cell) public bool IsExplored(CPos cell)
@@ -262,25 +270,26 @@ namespace OpenRA.Traits
if (!map.Contains(uv)) if (!map.Contains(uv))
return false; return false;
return map.ProjectedCellsCovering(uv).Any(isExploredTest);
}
public bool IsExplored(PPos puv)
{
if (!ShroudEnabled) if (!ShroudEnabled)
return true; return true;
return IsExploredCore(uv); var uv = (MPos)puv;
return explored.Contains(uv) && explored[uv] && (generatedShroudCount[uv] == 0 || visibleCount[uv] > 0);
} }
bool ShroudEnabled { get { return !Disabled && self.World.LobbyInfo.GlobalSettings.Shroud; } } bool ShroudEnabled { get { return !Disabled && self.World.LobbyInfo.GlobalSettings.Shroud; } }
bool IsExploredCore(MPos uv)
{
return explored[uv] && (generatedShroudCount[uv] == 0 || visibleCount[uv] > 0);
}
/// <summary> /// <summary>
/// Returns a fast exploration lookup that skips the usual validation. /// Returns a fast exploration lookup that skips the usual validation.
/// The return value should not be cached across ticks, and should not /// The return value should not be cached across ticks, and should not
/// be called with cells outside the map bounds. /// be called with cells outside the map bounds.
/// </summary> /// </summary>
public Func<MPos, bool> IsExploredTest public Func<PPos, bool> IsExploredTest
{ {
get get
{ {
@@ -294,39 +303,40 @@ namespace OpenRA.Traits
public bool IsVisible(WPos pos) public bool IsVisible(WPos pos)
{ {
return IsVisible(map.CellContaining(pos)); return IsVisible(map.ProjectedCellCovering(pos));
} }
public bool IsVisible(CPos cell) public bool IsVisible(CPos cell)
{ {
var uv = cell.ToMPos(map); return IsVisible(cell.ToMPos(map));
return IsVisible(uv);
} }
public bool IsVisible(MPos uv) public bool IsVisible(MPos uv)
{ {
if (!map.Contains(uv)) if (!visibleCount.Contains(uv))
return false; return false;
return map.ProjectedCellsCovering(uv).Any(isVisibleTest);
}
// In internal shroud coords
public bool IsVisible(PPos puv)
{
if (!FogEnabled) if (!FogEnabled)
return true; return true;
return IsVisibleCore(uv); var uv = (MPos)puv;
return visibleCount.Contains(uv) && visibleCount[uv] > 0;
} }
bool FogEnabled { get { return !Disabled && self.World.LobbyInfo.GlobalSettings.Fog; } } bool FogEnabled { get { return !Disabled && self.World.LobbyInfo.GlobalSettings.Fog; } }
bool IsVisibleCore(MPos uv)
{
return visibleCount[uv] > 0;
}
/// <summary> /// <summary>
/// Returns a fast visibility lookup that skips the usual validation. /// Returns a fast visibility lookup that skips the usual validation.
/// The return value should not be cached across ticks, and should not /// The return value should not be cached across ticks, and should not
/// be called with cells outside the map bounds. /// be called with cells outside the map bounds.
/// </summary> /// </summary>
public Func<MPos, bool> IsVisibleTest public Func<PPos, bool> IsVisibleTest
{ {
get get
{ {
@@ -339,11 +349,11 @@ namespace OpenRA.Traits
} }
} }
public bool Contains(MPos uv) public bool Contains(PPos uv)
{ {
// Check that uv is inside the map area. There is nothing special // Check that uv is inside the map area. There is nothing special
// about explored here: any of the CellLayers would have been suitable. // about explored here: any of the CellLayers would have been suitable.
return explored.Contains(uv); return explored.Contains((MPos)uv);
} }
} }
} }

View File

@@ -33,7 +33,6 @@ namespace OpenRA
public int Compare(Actor x, Actor y) { return x.ActorID.CompareTo(y.ActorID); } public int Compare(Actor x, Actor y) { return x.ActorID.CompareTo(y.ActorID); }
} }
static readonly Func<MPos, bool> FalsePredicate = _ => false;
internal readonly TraitDictionary TraitDict = new TraitDictionary(); internal readonly TraitDictionary TraitDict = new TraitDictionary();
readonly SortedSet<Actor> actors = new SortedSet<Actor>(ActorIDComparer.Instance); readonly SortedSet<Actor> actors = new SortedSet<Actor>(ActorIDComparer.Instance);
readonly List<IEffect> effects = new List<IEffect>(); readonly List<IEffect> effects = new List<IEffect>();
@@ -78,33 +77,7 @@ namespace OpenRA
public bool FogObscures(WPos pos) { return RenderPlayer != null && !RenderPlayer.Shroud.IsVisible(pos); } public bool FogObscures(WPos pos) { return RenderPlayer != null && !RenderPlayer.Shroud.IsVisible(pos); }
public bool ShroudObscures(CPos p) { return RenderPlayer != null && !RenderPlayer.Shroud.IsExplored(p); } public bool ShroudObscures(CPos p) { return RenderPlayer != null && !RenderPlayer.Shroud.IsExplored(p); }
public bool ShroudObscures(WPos pos) { return RenderPlayer != null && !RenderPlayer.Shroud.IsExplored(pos); } public bool ShroudObscures(WPos pos) { return RenderPlayer != null && !RenderPlayer.Shroud.IsExplored(pos); }
public bool ShroudObscures(MPos uv) { return RenderPlayer != null && !RenderPlayer.Shroud.IsExplored(uv); } public bool ShroudObscures(PPos uv) { return RenderPlayer != null && !RenderPlayer.Shroud.IsExplored(uv); }
public Func<MPos, bool> FogObscuresTest
{
get
{
var rp = RenderPlayer;
if (rp == null)
return FalsePredicate;
var predicate = rp.Shroud.IsVisibleTest;
return uv => !predicate(uv);
}
}
public Func<MPos, bool> ShroudObscuresTest
{
get
{
var rp = RenderPlayer;
if (rp == null)
return FalsePredicate;
var predicate = rp.Shroud.IsExploredTest;
return uv => !predicate(uv);
}
}
public bool IsReplay public bool IsReplay
{ {

View File

@@ -20,7 +20,7 @@ namespace OpenRA.Mods.Common.Traits
public CreatesShroud(Actor self, CreatesShroudInfo info) public CreatesShroud(Actor self, CreatesShroudInfo info)
: base(self, info) : base(self, info)
{ {
addCellsToPlayerShroud = (p, c) => p.Shroud.AddShroudGeneration(self, c); addCellsToPlayerShroud = (p, uv) => p.Shroud.AddProjectedShroudGeneration(self, uv);
removeCellsFromPlayerShroud = p => p.Shroud.RemoveShroudGeneration(self); removeCellsFromPlayerShroud = p => p.Shroud.RemoveShroudGeneration(self);
isDisabled = () => self.IsDisabled(); isDisabled = () => self.IsDisabled();
} }

View File

@@ -33,7 +33,7 @@ namespace OpenRA.Mods.Common.Traits
readonly FrozenUnderFogInfo info; readonly FrozenUnderFogInfo info;
readonly bool startsRevealed; readonly bool startsRevealed;
readonly MPos[] footprint; readonly PPos[] footprint;
readonly Lazy<IToolTip> tooltip; readonly Lazy<IToolTip> tooltip;
readonly Lazy<Health> health; readonly Lazy<Health> health;
@@ -47,10 +47,12 @@ namespace OpenRA.Mods.Common.Traits
{ {
this.info = info; this.info = info;
var map = init.World.Map;
// Spawned actors (e.g. building husks) shouldn't be revealed // Spawned actors (e.g. building husks) shouldn't be revealed
startsRevealed = info.StartsRevealed && !init.Contains<ParentActorInit>(); startsRevealed = info.StartsRevealed && !init.Contains<ParentActorInit>();
var footprintCells = FootprintUtils.Tiles(init.Self).ToList(); var footprintCells = FootprintUtils.Tiles(init.Self).ToList();
footprint = footprintCells.Select(cell => cell.ToMPos(init.World.Map)).ToArray(); footprint = footprintCells.SelectMany(c => map.ProjectedCellsCovering(c.ToMPos(map))).ToArray();
tooltip = Exts.Lazy(() => init.Self.TraitsImplementing<IToolTip>().FirstOrDefault()); tooltip = Exts.Lazy(() => init.Self.TraitsImplementing<IToolTip>().FirstOrDefault());
health = Exts.Lazy(() => init.Self.TraitOrDefault<Health>()); health = Exts.Lazy(() => init.Self.TraitOrDefault<Health>());

View File

@@ -27,14 +27,14 @@ namespace OpenRA.Mods.Common.Traits
public class RevealsShroud : ITick, ISync, INotifyAddedToWorld, INotifyRemovedFromWorld public class RevealsShroud : ITick, ISync, INotifyAddedToWorld, INotifyRemovedFromWorld
{ {
static readonly CPos[] NoCells = { }; static readonly PPos[] NoCells = { };
readonly RevealsShroudInfo info; readonly RevealsShroudInfo info;
readonly bool lobbyShroudFogDisabled; readonly bool lobbyShroudFogDisabled;
[Sync] CPos cachedLocation; [Sync] CPos cachedLocation;
[Sync] bool cachedDisabled; [Sync] bool cachedDisabled;
protected Action<Player, CPos[]> addCellsToPlayerShroud; protected Action<Player, PPos[]> addCellsToPlayerShroud;
protected Action<Player> removeCellsFromPlayerShroud; protected Action<Player> removeCellsFromPlayerShroud;
protected Func<bool> isDisabled; protected Func<bool> isDisabled;
@@ -43,12 +43,12 @@ namespace OpenRA.Mods.Common.Traits
this.info = info; this.info = info;
lobbyShroudFogDisabled = !self.World.LobbyInfo.GlobalSettings.Shroud && !self.World.LobbyInfo.GlobalSettings.Fog; lobbyShroudFogDisabled = !self.World.LobbyInfo.GlobalSettings.Shroud && !self.World.LobbyInfo.GlobalSettings.Fog;
addCellsToPlayerShroud = (p, c) => p.Shroud.AddVisibility(self, c); addCellsToPlayerShroud = (p, uv) => p.Shroud.AddProjectedVisibility(self, uv);
removeCellsFromPlayerShroud = p => p.Shroud.RemoveVisibility(self); removeCellsFromPlayerShroud = p => p.Shroud.RemoveVisibility(self);
isDisabled = () => false; isDisabled = () => false;
} }
CPos[] Cells(Actor self) PPos[] ProjectedCells(Actor self)
{ {
var map = self.World.Map; var map = self.World.Map;
var range = Range; var range = Range;
@@ -57,10 +57,10 @@ namespace OpenRA.Mods.Common.Traits
if (info.Type == VisibilityType.Footprint) if (info.Type == VisibilityType.Footprint)
return self.OccupiesSpace.OccupiedCells() return self.OccupiesSpace.OccupiedCells()
.SelectMany(kv => Shroud.CellsInRange(map, kv.First, range)) .SelectMany(kv => Shroud.ProjectedCellsInRange(map, kv.First, range))
.Distinct().ToArray(); .Distinct().ToArray();
return Shroud.CellsInRange(map, self.CenterPosition, range) return Shroud.ProjectedCellsInRange(map, self.CenterPosition, range)
.ToArray(); .ToArray();
} }
@@ -69,15 +69,18 @@ namespace OpenRA.Mods.Common.Traits
if (lobbyShroudFogDisabled || !self.IsInWorld) if (lobbyShroudFogDisabled || !self.IsInWorld)
return; return;
var location = self.Location; var centerPosition = self.CenterPosition;
var projectedPos = centerPosition - new WVec(0, centerPosition.Z, centerPosition.Z);
var projectedLocation = self.World.Map.CellContaining(projectedPos);
var disabled = isDisabled(); var disabled = isDisabled();
if (cachedLocation == location && cachedDisabled == disabled)
if (cachedLocation == projectedLocation && cachedDisabled == disabled)
return; return;
cachedLocation = location; cachedLocation = projectedLocation;
cachedDisabled = disabled; cachedDisabled = disabled;
var cells = Cells(self); var cells = ProjectedCells(self);
foreach (var p in self.World.Players) foreach (var p in self.World.Players)
{ {
removeCellsFromPlayerShroud(p); removeCellsFromPlayerShroud(p);
@@ -87,10 +90,11 @@ namespace OpenRA.Mods.Common.Traits
public void AddedToWorld(Actor self) public void AddedToWorld(Actor self)
{ {
cachedLocation = self.Location; var centerPosition = self.CenterPosition;
var projectedPos = centerPosition - new WVec(0, centerPosition.Z, centerPosition.Z);
cachedLocation = self.World.Map.CellContaining(projectedPos);
cachedDisabled = isDisabled(); cachedDisabled = isDisabled();
var cells = ProjectedCells(self);
var cells = Cells(self);
foreach (var p in self.World.Players) foreach (var p in self.World.Players)
addCellsToPlayerShroud(p, cells); addCellsToPlayerShroud(p, cells);
} }

View File

@@ -64,10 +64,10 @@ namespace OpenRA.Mods.Common.Traits
var map = world.Map; var map = world.Map;
foreach (var p in Start.Keys) foreach (var p in Start.Keys)
{ {
var cells = Shroud.CellsInRange(map, Start[p], info.InitialExploreRange); var cells = Shroud.ProjectedCellsInRange(map, Start[p], info.InitialExploreRange);
foreach (var q in world.Players) foreach (var q in world.Players)
if (p.IsAlliedWith(q)) if (p.IsAlliedWith(q))
q.Shroud.Explore(world, cells); q.Shroud.ExploreProjectedCells(world, cells);
} }
// Set viewport // Set viewport

View File

@@ -60,23 +60,24 @@ namespace OpenRA.Mods.Common.Traits
var doDim = refreshTick - world.WorldTick <= 0; var doDim = refreshTick - world.WorldTick <= 0;
if (doDim) refreshTick = world.WorldTick + 20; if (doDim) refreshTick = world.WorldTick + 20;
var map = wr.World.Map;
foreach (var pair in layers) foreach (var pair in layers)
{ {
var c = (pair.Key != null) ? pair.Key.Color.RGB : Color.PaleTurquoise; var c = (pair.Key != null) ? pair.Key.Color.RGB : Color.PaleTurquoise;
var layer = pair.Value; var layer = pair.Value;
// Only render quads in viewing range: // Only render quads in viewing range:
foreach (var cell in wr.Viewport.VisibleCellsInsideBounds) foreach (var uv in wr.Viewport.VisibleCellsInsideBounds.CandidateMapCoords)
{ {
if (layer[cell] <= 0) if (layer[uv] <= 0)
continue; continue;
var w = Math.Max(0, Math.Min(layer[cell], 128)); var w = Math.Max(0, Math.Min(layer[uv], 128));
if (doDim) if (doDim)
layer[cell] = layer[cell] * 5 / 6; layer[uv] = layer[uv] * 5 / 6;
// TODO: This doesn't make sense for isometric terrain // TODO: This doesn't make sense for isometric terrain
var pos = wr.World.Map.CenterOfCell(cell); var pos = wr.World.Map.CenterOfCell(uv.ToCPos(map));
var tl = wr.ScreenPxPosition(pos - new WVec(512, 512, 0)); var tl = wr.ScreenPxPosition(pos - new WVec(512, 512, 0));
var br = wr.ScreenPxPosition(pos + new WVec(511, 511, 0)); var br = wr.ScreenPxPosition(pos + new WVec(511, 511, 0));
qr.FillRect(RectangleF.FromLTRB(tl.X, tl.Y, br.X, br.Y), Color.FromArgb(w, c)); qr.FillRect(RectangleF.FromLTRB(tl.X, tl.Y, br.X, br.Y), Color.FromArgb(w, c));

View File

@@ -91,11 +91,11 @@ namespace OpenRA.Mods.Common.Traits
readonly CellLayer<TileInfo> tileInfos; readonly CellLayer<TileInfo> tileInfos;
readonly Sprite[] fogSprites, shroudSprites; readonly Sprite[] fogSprites, shroudSprites;
readonly HashSet<CPos> cellsDirty = new HashSet<CPos>(); readonly HashSet<PPos> cellsDirty = new HashSet<PPos>();
readonly HashSet<CPos> cellsAndNeighborsDirty = new HashSet<CPos>(); readonly HashSet<PPos> cellsAndNeighborsDirty = new HashSet<PPos>();
Shroud currentShroud; Shroud currentShroud;
Func<MPos, bool> visibleUnderShroud, visibleUnderFog; Func<PPos, bool> visibleUnderShroud, visibleUnderFog;
TerrainSpriteLayer shroudLayer, fogLayer; TerrainSpriteLayer shroudLayer, fogLayer;
public ShroudRenderer(World world, ShroudRendererInfo info) public ShroudRenderer(World world, ShroudRendererInfo info)
@@ -158,20 +158,22 @@ namespace OpenRA.Mods.Common.Traits
// This includes the region outside the visible area to cover any sprites peeking outside the map // This includes the region outside the visible area to cover any sprites peeking outside the map
foreach (var uv in w.Map.AllCells.MapCoords) foreach (var uv in w.Map.AllCells.MapCoords)
{ {
var screen = wr.ScreenPosition(w.Map.CenterOfCell(uv.ToCPos(map))); var pos = w.Map.CenterOfCell(uv.ToCPos(map));
var screen = wr.ScreenPosition(pos - new WVec(0, 0, pos.Z));
var variant = (byte)Game.CosmeticRandom.Next(info.ShroudVariants.Length); var variant = (byte)Game.CosmeticRandom.Next(info.ShroudVariants.Length);
tileInfos[uv] = new TileInfo(screen, variant); tileInfos[uv] = new TileInfo(screen, variant);
} }
DirtyCells(map.AllCells); // Dirty the whole projected space
DirtyCells(map.AllCells.MapCoords.Select(uv => (PPos)uv));
// All tiles are visible in the editor // All tiles are visible in the editor
if (w.Type == WorldType.Editor) if (w.Type == WorldType.Editor)
visibleUnderShroud = _ => true; visibleUnderShroud = _ => true;
else else
visibleUnderShroud = map.Contains; visibleUnderShroud = puv => map.Contains(puv);
visibleUnderFog = map.Contains; visibleUnderFog = puv => map.Contains(puv);
var shroudSheet = shroudSprites[0].Sheet; var shroudSheet = shroudSprites[0].Sheet;
if (shroudSprites.Any(s => s.Sheet != shroudSheet)) if (shroudSprites.Any(s => s.Sheet != shroudSheet))
@@ -193,25 +195,25 @@ namespace OpenRA.Mods.Common.Traits
fogLayer = new TerrainSpriteLayer(w, wr, fogSheet, fogBlend, wr.Palette(info.FogPalette), false); fogLayer = new TerrainSpriteLayer(w, wr, fogSheet, fogBlend, wr.Palette(info.FogPalette), false);
} }
Edges GetEdges(MPos uv, Func<MPos, bool> isVisible) Edges GetEdges(PPos puv, Func<PPos, bool> isVisible)
{ {
if (!isVisible(uv)) if (!isVisible(puv))
return notVisibleEdges; return notVisibleEdges;
var cell = uv.ToCPos(map); var cell = ((MPos)puv).ToCPos(map);
// If a side is shrouded then we also count the corners. // If a side is shrouded then we also count the corners.
var edge = Edges.None; var edge = Edges.None;
if (!isVisible((cell + new CVec(0, -1)).ToMPos(map))) edge |= Edges.Top; if (!isVisible((PPos)(cell + new CVec(0, -1)).ToMPos(map))) edge |= Edges.Top;
if (!isVisible((cell + new CVec(1, 0)).ToMPos(map))) edge |= Edges.Right; if (!isVisible((PPos)(cell + new CVec(1, 0)).ToMPos(map))) edge |= Edges.Right;
if (!isVisible((cell + new CVec(0, 1)).ToMPos(map))) edge |= Edges.Bottom; if (!isVisible((PPos)(cell + new CVec(0, 1)).ToMPos(map))) edge |= Edges.Bottom;
if (!isVisible((cell + new CVec(-1, 0)).ToMPos(map))) edge |= Edges.Left; if (!isVisible((PPos)(cell + new CVec(-1, 0)).ToMPos(map))) edge |= Edges.Left;
var ucorner = edge & Edges.AllCorners; var ucorner = edge & Edges.AllCorners;
if (!isVisible((cell + new CVec(-1, -1)).ToMPos(map))) edge |= Edges.TopLeft; if (!isVisible((PPos)(cell + new CVec(-1, -1)).ToMPos(map))) edge |= Edges.TopLeft;
if (!isVisible((cell + new CVec(1, -1)).ToMPos(map))) edge |= Edges.TopRight; if (!isVisible((PPos)(cell + new CVec(1, -1)).ToMPos(map))) edge |= Edges.TopRight;
if (!isVisible((cell + new CVec(1, 1)).ToMPos(map))) edge |= Edges.BottomRight; if (!isVisible((PPos)(cell + new CVec(1, 1)).ToMPos(map))) edge |= Edges.BottomRight;
if (!isVisible((cell + new CVec(-1, 1)).ToMPos(map))) edge |= Edges.BottomLeft; if (!isVisible((PPos)(cell + new CVec(-1, 1)).ToMPos(map))) edge |= Edges.BottomLeft;
// RA provides a set of frames for tiles with shrouded // RA provides a set of frames for tiles with shrouded
// corners but unshrouded edges. We want to detect this // corners but unshrouded edges. We want to detect this
@@ -222,7 +224,7 @@ namespace OpenRA.Mods.Common.Traits
return info.UseExtendedIndex ? edge ^ ucorner : edge & Edges.AllCorners; return info.UseExtendedIndex ? edge ^ ucorner : edge & Edges.AllCorners;
} }
void DirtyCells(IEnumerable<CPos> cells) void DirtyCells(IEnumerable<PPos> cells)
{ {
cellsDirty.UnionWith(cells); cellsDirty.UnionWith(cells);
} }
@@ -244,36 +246,37 @@ namespace OpenRA.Mods.Common.Traits
} }
else else
{ {
visibleUnderShroud = map.Contains; visibleUnderShroud = puv => map.Contains(puv);
visibleUnderFog = map.Contains; visibleUnderFog = puv => map.Contains(puv);
} }
currentShroud = shroud; currentShroud = shroud;
DirtyCells(map.CellsInsideBounds); DirtyCells(map.ProjectedCellBounds);
} }
// We need to update newly dirtied areas of the shroud. // We need to update newly dirtied areas of the shroud.
// Expand the dirty area to cover the neighboring cells, since shroud is affected by neighboring cells. // Expand the dirty area to cover the neighboring cells, since shroud is affected by neighboring cells.
foreach (var cell in cellsDirty) foreach (var uv in cellsDirty)
{ {
cellsAndNeighborsDirty.Add(cell); cellsAndNeighborsDirty.Add(uv);
var cell = ((MPos)uv).ToCPos(map);
foreach (var direction in CVec.Directions) foreach (var direction in CVec.Directions)
cellsAndNeighborsDirty.Add(cell + direction); cellsAndNeighborsDirty.Add((PPos)(cell + direction).ToMPos(map));
} }
foreach (var cell in cellsAndNeighborsDirty) foreach (var puv in cellsAndNeighborsDirty)
{ {
var uv = cell.ToMPos(map.TileShape); var uv = (MPos)puv;
if (!tileInfos.Contains(uv)) if (!tileInfos.Contains(uv))
continue; continue;
var tileInfo = tileInfos[uv]; var tileInfo = tileInfos[uv];
var shroudSprite = GetSprite(shroudSprites, GetEdges(uv, visibleUnderShroud), tileInfo.Variant); var shroudSprite = GetSprite(shroudSprites, GetEdges(puv, visibleUnderShroud), tileInfo.Variant);
var shroudPos = tileInfo.ScreenPosition; var shroudPos = tileInfo.ScreenPosition;
if (shroudSprite != null) if (shroudSprite != null)
shroudPos += shroudSprite.Offset - 0.5f * shroudSprite.Size; shroudPos += shroudSprite.Offset - 0.5f * shroudSprite.Size;
var fogSprite = GetSprite(fogSprites, GetEdges(uv, visibleUnderFog), tileInfo.Variant); var fogSprite = GetSprite(fogSprites, GetEdges(puv, visibleUnderFog), tileInfo.Variant);
var fogPos = tileInfo.ScreenPosition; var fogPos = tileInfo.ScreenPosition;
if (fogSprite != null) if (fogSprite != null)
fogPos += fogSprite.Offset - 0.5f * fogSprite.Size; fogPos += fogSprite.Offset - 0.5f * fogSprite.Size;

View File

@@ -57,8 +57,11 @@ namespace OpenRA.Mods.Common.Traits
var colors = wr.World.TileSet.HeightDebugColors; var colors = wr.World.TileSet.HeightDebugColors;
var mouseCell = wr.Viewport.ViewToWorld(Viewport.LastMousePos).ToMPos(wr.World.Map); var mouseCell = wr.Viewport.ViewToWorld(Viewport.LastMousePos).ToMPos(wr.World.Map);
foreach (var uv in wr.Viewport.AllVisibleCells.MapCoords) foreach (var uv in wr.Viewport.AllVisibleCells.CandidateMapCoords)
{ {
if (!map.MapHeight.Value.Contains(uv))
continue;
var height = (int)map.MapHeight.Value[uv]; var height = (int)map.MapHeight.Value[uv];
var tile = map.MapTiles.Value[uv]; var tile = map.MapTiles.Value[uv];
var ti = tileSet.GetTileInfo(tile); var ti = tileSet.GetTileInfo(tile);
@@ -80,6 +83,22 @@ namespace OpenRA.Mods.Common.Traits
lr.LineWidth = 1; lr.LineWidth = 1;
} }
// Projected cell coordinates for the current cell
var projectedCorners = map.CellCorners[0];
lr.LineWidth = 3;
foreach (var puv in map.ProjectedCellsCovering(mouseCell))
{
var pos = map.CenterOfCell(((MPos)puv).ToCPos(map));
var screen = projectedCorners.Select(c => wr.ScreenPxPosition(pos + c - new WVec(0, 0, pos.Z)).ToFloat2()).ToArray();
for (var i = 0; i < 4; i++)
{
var j = (i + 1) % 4;
lr.DrawLine(screen[i], screen[j], Color.Navy);
}
}
lr.LineWidth = 1;
} }
} }
} }

View File

@@ -151,8 +151,8 @@ namespace OpenRA.Mods.Common.UtilityCommands
Author = "Westwood Studios" Author = "Westwood Studios"
}; };
var tl = new MPos(offsetX, offsetY); var tl = new PPos(offsetX, offsetY);
var br = new MPos(offsetX + width - 1, offsetY + height - 1); var br = new PPos(offsetX + width - 1, offsetY + height - 1);
map.SetBounds(tl, br); map.SetBounds(tl, br);
if (legacyMapFormat == IniMapFormat.RedAlert) if (legacyMapFormat == IniMapFormat.RedAlert)

View File

@@ -64,8 +64,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
var tileset = modRules.TileSets[tilesetDropDown.Text]; var tileset = modRules.TileSets[tilesetDropDown.Text];
var map = new Map(tileset, width + 2, height + maxTerrainHeight + 2); var map = new Map(tileset, width + 2, height + maxTerrainHeight + 2);
var tl = new MPos(1, 1); var tl = new PPos(1, 1);
var br = new MPos(width, height + maxTerrainHeight); var br = new PPos(width, height + maxTerrainHeight);
map.SetBounds(tl, br); map.SetBounds(tl, br);
map.PlayerDefinitions = new MapPlayers(map.Rules, map.SpawnPoints.Value.Length).ToMiniYaml(); map.PlayerDefinitions = new MapPlayers(map.Rules, map.SpawnPoints.Value.Length).ToMiniYaml();

View File

@@ -34,7 +34,7 @@ namespace OpenRA.Mods.Common.Widgets
readonly WorldRenderer worldRenderer; readonly WorldRenderer worldRenderer;
readonly RadarPings radarPings; readonly RadarPings radarPings;
readonly HashSet<CPos> dirtyShroudCells = new HashSet<CPos>(); readonly HashSet<PPos> dirtyShroudCells = new HashSet<PPos>();
float radarMinimapHeight; float radarMinimapHeight;
int frame; int frame;
@@ -82,7 +82,7 @@ namespace OpenRA.Mods.Common.Widgets
actorSprite = new Sprite(radarSheet, new Rectangle(0, height, width, height), TextureChannel.Alpha); actorSprite = new Sprite(radarSheet, new Rectangle(0, height, width, height), TextureChannel.Alpha);
// Set initial terrain data // Set initial terrain data
foreach (var cell in world.Map.CellsInsideBounds) foreach (var cell in world.Map.AllCells)
UpdateTerrainCell(cell); UpdateTerrainCell(cell);
world.Map.MapTiles.Value.CellEntryChanged += UpdateTerrainCell; world.Map.MapTiles.Value.CellEntryChanged += UpdateTerrainCell;
@@ -120,35 +120,38 @@ namespace OpenRA.Mods.Common.Widgets
} }
} }
void UpdateShroudCell(CPos cell) void UpdateShroudCell(PPos projectedCell)
{ {
if (!world.Map.Contains(cell)) if (!world.Map.Bounds.Contains(projectedCell.U, projectedCell.V))
return; return;
var stride = radarSheet.Size.Width; var stride = radarSheet.Size.Width;
var uv = cell.ToMPos(world.Map);
var dx = shroudSprite.Bounds.Left - world.Map.Bounds.Left; var dx = shroudSprite.Bounds.Left - world.Map.Bounds.Left;
var dy = shroudSprite.Bounds.Top - world.Map.Bounds.Top; var dy = shroudSprite.Bounds.Top - world.Map.Bounds.Top;
var color = 0; var color = 0;
if (world.ShroudObscures(cell)) var rp = world.RenderPlayer;
color = Color.Black.ToArgb(); if (rp != null)
else if (world.FogObscures(cell)) {
color = Color.FromArgb(128, Color.Black).ToArgb(); if (!rp.Shroud.IsExplored(projectedCell))
color = Color.Black.ToArgb();
else if (!rp.Shroud.IsVisible(projectedCell))
color = Color.FromArgb(128, Color.Black).ToArgb();
}
unsafe unsafe
{ {
fixed (byte* colorBytes = &radarData[0]) fixed (byte* colorBytes = &radarData[0])
{ {
var colors = (int*)colorBytes; var colors = (int*)colorBytes;
colors[(uv.V + dy) * stride + uv.U + dx] = color; colors[(projectedCell.V + dy) * stride + projectedCell.U + dx] = color;
} }
} }
} }
void MarkShroudDirty(IEnumerable<CPos> cellsChanged) void MarkShroudDirty(IEnumerable<PPos> projectedCellsChanged)
{ {
dirtyShroudCells.UnionWith(cellsChanged); dirtyShroudCells.UnionWith(projectedCellsChanged);
} }
public override string GetCursor(int2 pos) public override string GetCursor(int2 pos)
@@ -290,7 +293,7 @@ namespace OpenRA.Mods.Common.Widgets
if (newRenderShroud != null) if (newRenderShroud != null)
{ {
// Redraw the full shroud sprite // Redraw the full shroud sprite
MarkShroudDirty(world.Map.CellsInsideBounds); MarkShroudDirty(world.Map.AllCells.MapCoords.Select(uv => (PPos)uv));
// Update the notification binding // Update the notification binding
newRenderShroud.CellsChanged += MarkShroudDirty; newRenderShroud.CellsChanged += MarkShroudDirty;

View File

@@ -315,8 +315,8 @@ namespace OpenRA.Mods.D2k.UtilityCommands
Author = "Westwood Studios" Author = "Westwood Studios"
}; };
var tl = new MPos(MapCordonWidth, MapCordonWidth); var tl = new PPos(MapCordonWidth, MapCordonWidth);
var br = new MPos(MapCordonWidth + mapSize.Width - 1, MapCordonWidth + mapSize.Height - 1); var br = new PPos(MapCordonWidth + mapSize.Width - 1, MapCordonWidth + mapSize.Height - 1);
map.SetBounds(tl, br); map.SetBounds(tl, br);
// Get all templates from the tileset YAML file that have at least one frame and an Image property corresponding to the requested tileset // Get all templates from the tileset YAML file that have at least one frame and an Image property corresponding to the requested tileset