diff --git a/OpenRA.Game/Graphics/TerrainSpriteLayer.cs b/OpenRA.Game/Graphics/TerrainSpriteLayer.cs index 7471b5d04f..08cecb0660 100644 --- a/OpenRA.Game/Graphics/TerrainSpriteLayer.cs +++ b/OpenRA.Game/Graphics/TerrainSpriteLayer.cs @@ -98,8 +98,8 @@ namespace OpenRA.Graphics var cells = restrictToBounds ? viewport.VisibleCellsInsideBounds : viewport.AllVisibleCells; // Only draw the rows that are visible. - var firstRow = cells.MapCoords.TopLeft.V; - var lastRow = Math.Min(cells.MapCoords.BottomRight.V + 1, map.MapSize.Y); + var firstRow = cells.CandidateMapCoords.TopLeft.V.Clamp(0, map.MapSize.Y); + var lastRow = (cells.CandidateMapCoords.BottomRight.V + 1).Clamp(firstRow, map.MapSize.Y); Game.Renderer.Flush(); diff --git a/OpenRA.Game/Graphics/Viewport.cs b/OpenRA.Game/Graphics/Viewport.cs index d67e9d37c1..d8a69b94a1 100644 --- a/OpenRA.Game/Graphics/Viewport.cs +++ b/OpenRA.Game/Graphics/Viewport.cs @@ -48,10 +48,10 @@ namespace OpenRA.Graphics public int2 TopLeft { get { return CenterLocation - viewportSize / 2; } } public int2 BottomRight { get { return CenterLocation + viewportSize / 2; } } int2 viewportSize; - CellRegion cells; + ProjectedCellRegion cells; bool cellsDirty = true; - CellRegion allCells; + ProjectedCellRegion allCells; bool allCellsDirty = true; float zoom = 1f; @@ -93,15 +93,27 @@ namespace OpenRA.Graphics { worldRenderer = wr; - var cells = wr.World.Type == WorldType.Editor ? - map.AllCells : map.CellsInsideBounds; - // Calculate map bounds in world-px - var tl = wr.ScreenPxPosition(map.CenterOfCell(cells.TopLeft) - new WVec(512, 512, 0)); - var br = wr.ScreenPxPosition(map.CenterOfCell(cells.BottomRight) + new WVec(511, 511, 0)); - mapBounds = Rectangle.FromLTRB(tl.X, tl.Y, br.X, br.Y); + if (wr.World.Type == WorldType.Editor) + { + // 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; tileSize = Game.ModData.Manifest.TileSize; } @@ -209,8 +221,8 @@ namespace OpenRA.Graphics // Visible rectangle in world coordinates (expanded to the corners of the cells) var bounds = insideBounds ? VisibleCellsInsideBounds : AllVisibleCells; var map = worldRenderer.World.Map; - var ctl = map.CenterOfCell(bounds.TopLeft) - new WVec(512, 512, 0); - var cbr = map.CenterOfCell(bounds.BottomRight) + new WVec(512, 512, 0); + var ctl = map.CenterOfCell(((MPos)bounds.TopLeft).ToCPos(map)) - new WVec(512, 512, 0); + var cbr = map.CenterOfCell(((MPos)bounds.BottomRight).ToCPos(map)) + new WVec(512, 512, 0); // Convert to screen coordinates 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); } - CellRegion CalculateVisibleCells(bool insideBounds) + ProjectedCellRegion CalculateVisibleCells(bool insideBounds) { var map = worldRenderer.World.Map; - // Calculate the viewport corners in "projected wpos" (at ground level), and - // this to an equivalent projected cell for the two corners - var tl = map.CellContaining(worldRenderer.ProjectedPosition(TopLeft)).ToMPos(map); - var br = map.CellContaining(worldRenderer.ProjectedPosition(BottomRight)).ToMPos(map); + // Calculate the projected cell position at the corners of the visible area + var tl = (PPos)map.CellContaining(worldRenderer.ProjectedPosition(TopLeft)).ToMPos(map); + var br = (PPos)map.CellContaining(worldRenderer.ProjectedPosition(BottomRight)).ToMPos(map); // Diamond tile shapes don't have straight edges, and so we need // an additional cell margin to include the cells that are half // visible on each edge. if (map.TileShape == TileShape.Diamond) { - tl = new MPos(tl.U - 1, tl.V - 1); - br = new MPos(br.U + 1, br.V + 1); + tl = new PPos(tl.U - 1, tl.V - 1); + br = new PPos(br.U + 1, br.V + 1); } // Clamp to the visible map bounds, if requested @@ -246,21 +257,10 @@ namespace OpenRA.Graphics br = map.Clamp(br); } - // Cells can be pushed up from below if they have non-zero height. - // 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)); + return new ProjectedCellRegion(map, tl, br); } - public CellRegion VisibleCellsInsideBounds + public ProjectedCellRegion VisibleCellsInsideBounds { get { @@ -274,7 +274,7 @@ namespace OpenRA.Graphics } } - public CellRegion AllVisibleCells + public ProjectedCellRegion AllVisibleCells { get { diff --git a/OpenRA.Game/MPos.cs b/OpenRA.Game/MPos.cs index 5dd6b862e6..32f97077a4 100644 --- a/OpenRA.Game/MPos.cs +++ b/OpenRA.Game/MPos.cs @@ -61,4 +61,34 @@ namespace OpenRA return new CPos(x, y); } } + + /// + /// Projected map position + /// + public struct PPos : IEquatable + { + 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; } + } } \ No newline at end of file diff --git a/OpenRA.Game/Map/CellRegion.cs b/OpenRA.Game/Map/CellRegion.cs index 7920498a0a..fb41ed60a9 100644 --- a/OpenRA.Game/Map/CellRegion.cs +++ b/OpenRA.Game/Map/CellRegion.cs @@ -87,7 +87,7 @@ namespace OpenRA public MapCoordsRegion MapCoords { - get { return new MapCoordsRegion(this); } + get { return new MapCoordsRegion(mapTopLeft, mapBottomRight); } } public CellRegionEnumerator GetEnumerator() @@ -151,75 +151,5 @@ namespace OpenRA object IEnumerator.Current { get { return Current; } } public void Dispose() { } } - - public struct MapCoordsRegion : IEnumerable - { - public struct MapCoordsEnumerator : IEnumerator - { - 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 IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - public MPos TopLeft { get { return r.mapTopLeft; } } - public MPos BottomRight { get { return r.mapBottomRight; } } - } } } diff --git a/OpenRA.Game/Map/Map.cs b/OpenRA.Game/Map/Map.cs index a61b209892..a26d6fa4ee 100644 --- a/OpenRA.Game/Map/Map.cs +++ b/OpenRA.Game/Map/Map.cs @@ -9,6 +9,7 @@ #endregion using System; +using System.Collections; using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; @@ -245,6 +246,8 @@ namespace OpenRA [FieldLoader.Ignore] public Lazy> MapHeight; [FieldLoader.Ignore] public CellLayer CustomTerrain; + [FieldLoader.Ignore] CellLayer cellProjection; + [FieldLoader.Ignore] CellLayer> inverseCellProjection; [FieldLoader.Ignore] Lazy cachedTileSet; [FieldLoader.Ignore] Lazy rules; @@ -252,9 +255,11 @@ namespace OpenRA public SequenceProvider SequenceProvider { get { return Rules.Sequences[Tileset]; } } public WVec[][] CellCorners { get; private set; } - [FieldLoader.Ignore] public CellRegion CellsInsideBounds; + [FieldLoader.Ignore] public ProjectedCellRegion ProjectedCellBounds; [FieldLoader.Ignore] public CellRegion AllCells; + readonly Func containsTest; + void AssertExists(string filename) { using (var s = Container.GetContent(filename)) @@ -268,6 +273,8 @@ namespace OpenRA /// public Map(TileSet tileset, int width, int height) { + containsTest = Contains; + var size = new Size(width, height); var tileShape = Game.ModData.Manifest.TileShape; var tileRef = new TerrainTile(tileset.Templates.First().Key, (byte)0); @@ -307,6 +314,8 @@ namespace OpenRA /// Initializes a map loaded from disk. public Map(string path) { + containsTest = Contains; + Path = path; 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); AllCells = new CellRegion(TileShape, tl, br); - var btl = new MPos(Bounds.Left, Bounds.Top); - var bbr = new MPos(Bounds.Right - 1, Bounds.Bottom - 1); + var btl = new PPos(Bounds.Left, Bounds.Top); + var bbr = new PPos(Bounds.Right - 1, Bounds.Bottom - 1); SetBounds(btl, bbr); CustomTerrain = new CellLayer(this); @@ -428,6 +437,80 @@ namespace OpenRA rightDelta + new WVec(0, 0, 512 * ramp[2]), bottomDelta + new WVec(0, 0, 512 * ramp[3]) }).ToArray(); + + if (MaximumTerrainHeight != 0) + { + cellProjection = new CellLayer(this); + inverseCellProjection = new CellLayer>(this); + + // Initialize collections + foreach (var cell in AllCells) + { + var uv = cell.ToMPos(TileShape); + cellProjection[uv] = new PPos[0]; + inverseCellProjection[uv] = new List(); + } + + // 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(); + + // 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() @@ -534,6 +617,8 @@ namespace OpenRA } } + tiles.CellEntryChanged += UpdateProjection; + return tiles; } @@ -552,6 +637,8 @@ namespace OpenRA } } + tiles.CellEntryChanged += UpdateProjection; + return tiles; } @@ -652,7 +739,15 @@ namespace OpenRA 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) @@ -695,6 +790,39 @@ namespace OpenRA 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) { return Traits.Util.GetFacing(CenterOfCell(towards) - CenterOfCell(cell), fallbackfacing); @@ -717,12 +845,11 @@ namespace OpenRA 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 // is exclusive. Pad the right and bottom edges to match. 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 // 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); ProjectedBottomRight = new WPos(br.U * 1024 - 1, wbottom - 1, 0); + + ProjectedCellBounds = new ProjectedCellRegion(this, tl, br); } string ComputeHash() @@ -799,18 +928,28 @@ namespace OpenRA public CPos Clamp(CPos cell) { - var bounds = new Rectangle(Bounds.X, Bounds.Y, Bounds.Width - 1, Bounds.Height - 1); - return cell.ToMPos(this).Clamp(bounds).ToCPos(this); + return Clamp(cell.ToMPos(this)).ToCPos(this); } 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); - return uv.Clamp(bounds); + return puv.Clamp(bounds); } public CPos ChooseRandomCell(MersenneTwister rand) { + // TODO: Account for terrain height var x = rand.Next(Bounds.Left, Bounds.Right); var y = rand.Next(Bounds.Top, Bounds.Bottom); @@ -819,6 +958,7 @@ namespace OpenRA public CPos ChooseClosestEdgeCell(CPos pos) { + // TODO: Account for terrain height var mpos = pos.ToMPos(this); var horizontalBound = ((mpos.U - Bounds.Left) < Bounds.Width / 2) ? Bounds.Left : Bounds.Right; @@ -832,6 +972,7 @@ namespace OpenRA public CPos ChooseRandomEdgeCell(MersenneTwister rand) { + // TODO: Account for terrain height var isX = rand.Next(2) == 0; var edge = rand.Next(2) == 0; @@ -843,8 +984,10 @@ namespace OpenRA public WDist DistanceToEdge(WPos pos, WVec dir) { - var tl = CenterOfCell(CellsInsideBounds.TopLeft) - new WVec(512, 512, 0); - var br = CenterOfCell(CellsInsideBounds.BottomRight) + new WVec(511, 511, 0); + // TODO: Account for terrain height + // 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 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); diff --git a/OpenRA.Game/Map/MapCoordsRegion.cs b/OpenRA.Game/Map/MapCoordsRegion.cs new file mode 100644 index 0000000000..a7540b4e01 --- /dev/null +++ b/OpenRA.Game/Map/MapCoordsRegion.cs @@ -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 + { + public struct MapCoordsEnumerator : IEnumerator + { + 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 IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public MPos TopLeft { get { return topLeft; } } + public MPos BottomRight { get { return bottomRight; } } + } +} diff --git a/OpenRA.Game/Map/ProjectedCellRegion.cs b/OpenRA.Game/Map/ProjectedCellRegion.cs new file mode 100644 index 0000000000..1e7b719142 --- /dev/null +++ b/OpenRA.Game/Map/ProjectedCellRegion.cs @@ -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 + { + // 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; + } + + /// + /// 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. + /// + public MapCoordsRegion CandidateMapCoords { get { return new MapCoordsRegion(mapTopLeft, mapBottomRight); } } + + public ProjectedCellRegionEnumerator GetEnumerator() + { + return new ProjectedCellRegionEnumerator(this); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public sealed class ProjectedCellRegionEnumerator : IEnumerator + { + 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() { } + } + } +} diff --git a/OpenRA.Game/OpenRA.Game.csproj b/OpenRA.Game/OpenRA.Game.csproj index e2fad0b42f..477deb934d 100644 --- a/OpenRA.Game/OpenRA.Game.csproj +++ b/OpenRA.Game/OpenRA.Game.csproj @@ -247,6 +247,8 @@ + + diff --git a/OpenRA.Game/Traits/Player/FrozenActorLayer.cs b/OpenRA.Game/Traits/Player/FrozenActorLayer.cs index b1330efcae..d68bcb0d33 100644 --- a/OpenRA.Game/Traits/Player/FrozenActorLayer.cs +++ b/OpenRA.Game/Traits/Player/FrozenActorLayer.cs @@ -24,7 +24,7 @@ namespace OpenRA.Traits public class FrozenActor { - public readonly MPos[] Footprint; + public readonly PPos[] Footprint; public readonly WPos CenterPosition; public readonly Rectangle Bounds; readonly Actor actor; @@ -42,7 +42,7 @@ namespace OpenRA.Traits public bool NeedRenderables; public bool IsRendering { get; private set; } - public FrozenActor(Actor self, MPos[] footprint, Shroud shroud) + public FrozenActor(Actor self, PPos[] footprint, Shroud shroud) { actor = self; this.shroud = shroud; diff --git a/OpenRA.Game/Traits/World/Shroud.cs b/OpenRA.Game/Traits/World/Shroud.cs index e54a8d6685..de47db980d 100644 --- a/OpenRA.Game/Traits/World/Shroud.cs +++ b/OpenRA.Game/Traits/World/Shroud.cs @@ -24,7 +24,7 @@ namespace OpenRA.Traits { [Sync] public bool Disabled; - public event Action> CellsChanged; + public event Action> CellsChanged; readonly Actor self; 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 // can't make us invalid. - readonly Dictionary visibility = new Dictionary(); - readonly Dictionary generation = new Dictionary(); + readonly Dictionary visibility = new Dictionary(); + readonly Dictionary generation = new Dictionary(); public int Hash { get; private set; } - static readonly Func TruthPredicate = _ => true; - readonly Func shroudEdgeTest; - readonly Func isExploredTest; - readonly Func isVisibleTest; + static readonly Func TruthPredicate = _ => true; + readonly Func shroudEdgeTest; + readonly Func isExploredTest; + readonly Func isVisibleTest; public Shroud(Actor self) { @@ -55,11 +55,12 @@ namespace OpenRA.Traits explored = new CellLayer(map); shroudEdgeTest = map.Contains; - isExploredTest = IsExploredCore; - isVisibleTest = IsVisibleCore; + + isExploredTest = IsExplored; + isVisibleTest = IsVisible; } - void Invalidate(IEnumerable changed) + void Invalidate(IEnumerable changed) { if (CellsChanged != null) CellsChanged(changed); @@ -72,35 +73,38 @@ namespace OpenRA.Traits Hash += 1; } - public static IEnumerable CellsInRange(Map map, WPos pos, WDist range) + public static IEnumerable 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 cell = map.CellContaining(pos); - foreach (var c in map.FindTilesInCircle(cell, r, true)) - if ((map.CenterOfCell(c) - pos).HorizontalLengthSquared <= limit) - yield return c; + // Project actor position into the shroud plane + var projectedPos = pos - new WVec(0, pos.Z, pos.Z); + 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 CellsInRange(Map map, CPos cell, WDist range) + public static IEnumerable 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)) return; - foreach (var c in visible) + foreach (var puv in visible) { - var uv = c.ToMPos(map); - // Force cells outside the visible bounds invisible - if (!map.Contains(uv)) + if (!map.Contains(puv)) continue; + var uv = (MPos)puv; visibleCount[uv]++; explored[uv] = true; } @@ -114,28 +118,28 @@ namespace OpenRA.Traits public void RemoveVisibility(Actor a) { - CPos[] visible; + PPos[] visible; if (!visibility.TryGetValue(a, out visible)) return; - foreach (var c in visible) + foreach (var puv in visible) { // Cells outside the visible bounds don't increment visibleCount - if (map.Contains(c)) - visibleCount[c.ToMPos(map)]--; + if (map.Contains(puv)) + visibleCount[(MPos)puv]--; } visibility.Remove(a); Invalidate(visible); } - public void AddShroudGeneration(Actor a, CPos[] shrouded) + public void AddProjectedShroudGeneration(Actor a, PPos[] shrouded) { if (a.Owner.IsAlliedWith(self.Owner)) return; - foreach (var c in shrouded) - generatedShroudCount[c]++; + foreach (var uv in shrouded) + generatedShroudCount[(MPos)uv]++; if (generation.ContainsKey(a)) throw new InvalidOperationException("Attempting to add duplicate shroud generation"); @@ -146,12 +150,12 @@ namespace OpenRA.Traits public void RemoveShroudGeneration(Actor a) { - CPos[] shrouded; + PPos[] shrouded; if (!generation.TryGetValue(a, out shrouded)) return; - foreach (var c in shrouded) - generatedShroudCount[c]--; + foreach (var uv in shrouded) + generatedShroudCount[(MPos)uv]--; generation.Remove(a); Invalidate(shrouded); @@ -164,34 +168,35 @@ namespace OpenRA.Traits foreach (var a in w.Actors.Where(a => a.Owner == player)) { - CPos[] visible = null; - CPos[] shrouded = null; + PPos[] visible = null; + PPos[] shrouded = null; foreach (var p in self.World.Players) { if (p.Shroud.visibility.TryGetValue(self, out visible)) { p.Shroud.RemoveVisibility(self); - p.Shroud.AddVisibility(self, visible); + p.Shroud.AddProjectedVisibility(self, visible); } if (p.Shroud.generation.TryGetValue(self, out shrouded)) { p.Shroud.RemoveShroudGeneration(self); - p.Shroud.AddShroudGeneration(self, shrouded); + p.Shroud.AddProjectedShroudGeneration(self, shrouded); } } } } - public void Explore(World world, IEnumerable cells) + public void ExploreProjectedCells(World world, IEnumerable cells) { - var changed = new HashSet(); - foreach (var c in cells) + var changed = new HashSet(); + foreach (var puv in cells) { - if (!explored[c]) + var uv = (MPos)puv; + if (!explored[uv]) { - explored[c] = true; - changed.Add(c); + explored[uv] = true; + changed.Add(puv); } } @@ -203,13 +208,14 @@ namespace OpenRA.Traits if (map.Bounds != s.map.Bounds) throw new ArgumentException("The map bounds of these shrouds do not match.", "s"); - var changed = new List(); - foreach (var uv in map.CellsInsideBounds.MapCoords) + var changed = new List(); + foreach (var puv in map.ProjectedCellBounds) { + var uv = (MPos)puv; if (!explored[uv] && s.explored[uv]) { explored[uv] = true; - changed.Add(uv.ToCPos(map)); + changed.Add(puv); } } @@ -218,13 +224,14 @@ namespace OpenRA.Traits public void ExploreAll(World world) { - var changed = new List(); - foreach (var uv in map.CellsInsideBounds.MapCoords) + var changed = new List(); + foreach (var puv in map.ProjectedCellBounds) { + var uv = (MPos)puv; if (!explored[uv]) { explored[uv] = true; - changed.Add(uv.ToCPos(map)); + changed.Add(puv); } } @@ -233,14 +240,15 @@ namespace OpenRA.Traits public void ResetExploration() { - var changed = new List(); - foreach (var uv in map.CellsInsideBounds.MapCoords) + var changed = new List(); + foreach (var puv in map.ProjectedCellBounds) { + var uv = (MPos)puv; var visible = visibleCount[uv] > 0; if (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) { - return IsExplored(map.CellContaining(pos)); + return IsExplored(map.ProjectedCellCovering(pos)); } public bool IsExplored(CPos cell) @@ -262,25 +270,26 @@ namespace OpenRA.Traits if (!map.Contains(uv)) return false; + return map.ProjectedCellsCovering(uv).Any(isExploredTest); + } + + public bool IsExplored(PPos puv) + { if (!ShroudEnabled) 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 IsExploredCore(MPos uv) - { - return explored[uv] && (generatedShroudCount[uv] == 0 || visibleCount[uv] > 0); - } - /// /// Returns a fast exploration lookup that skips the usual validation. /// The return value should not be cached across ticks, and should not /// be called with cells outside the map bounds. /// - public Func IsExploredTest + public Func IsExploredTest { get { @@ -294,39 +303,40 @@ namespace OpenRA.Traits public bool IsVisible(WPos pos) { - return IsVisible(map.CellContaining(pos)); + return IsVisible(map.ProjectedCellCovering(pos)); } public bool IsVisible(CPos cell) { - var uv = cell.ToMPos(map); - return IsVisible(uv); + return IsVisible(cell.ToMPos(map)); } public bool IsVisible(MPos uv) { - if (!map.Contains(uv)) + if (!visibleCount.Contains(uv)) return false; + return map.ProjectedCellsCovering(uv).Any(isVisibleTest); + } + + // In internal shroud coords + public bool IsVisible(PPos puv) + { if (!FogEnabled) 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 IsVisibleCore(MPos uv) - { - return visibleCount[uv] > 0; - } - /// /// Returns a fast visibility lookup that skips the usual validation. /// The return value should not be cached across ticks, and should not /// be called with cells outside the map bounds. /// - public Func IsVisibleTest + public Func IsVisibleTest { 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 // about explored here: any of the CellLayers would have been suitable. - return explored.Contains(uv); + return explored.Contains((MPos)uv); } } } diff --git a/OpenRA.Game/World.cs b/OpenRA.Game/World.cs index 35aa9974bf..94ce263507 100644 --- a/OpenRA.Game/World.cs +++ b/OpenRA.Game/World.cs @@ -33,7 +33,6 @@ namespace OpenRA public int Compare(Actor x, Actor y) { return x.ActorID.CompareTo(y.ActorID); } } - static readonly Func FalsePredicate = _ => false; internal readonly TraitDictionary TraitDict = new TraitDictionary(); readonly SortedSet actors = new SortedSet(ActorIDComparer.Instance); readonly List effects = new List(); @@ -78,33 +77,7 @@ namespace OpenRA 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(WPos pos) { return RenderPlayer != null && !RenderPlayer.Shroud.IsExplored(pos); } - public bool ShroudObscures(MPos uv) { return RenderPlayer != null && !RenderPlayer.Shroud.IsExplored(uv); } - - public Func FogObscuresTest - { - get - { - var rp = RenderPlayer; - if (rp == null) - return FalsePredicate; - - var predicate = rp.Shroud.IsVisibleTest; - return uv => !predicate(uv); - } - } - - public Func ShroudObscuresTest - { - get - { - var rp = RenderPlayer; - if (rp == null) - return FalsePredicate; - - var predicate = rp.Shroud.IsExploredTest; - return uv => !predicate(uv); - } - } + public bool ShroudObscures(PPos uv) { return RenderPlayer != null && !RenderPlayer.Shroud.IsExplored(uv); } public bool IsReplay { diff --git a/OpenRA.Mods.Common/Traits/CreatesShroud.cs b/OpenRA.Mods.Common/Traits/CreatesShroud.cs index 862a599175..f00d0143b3 100644 --- a/OpenRA.Mods.Common/Traits/CreatesShroud.cs +++ b/OpenRA.Mods.Common/Traits/CreatesShroud.cs @@ -20,7 +20,7 @@ namespace OpenRA.Mods.Common.Traits public CreatesShroud(Actor self, CreatesShroudInfo 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); isDisabled = () => self.IsDisabled(); } diff --git a/OpenRA.Mods.Common/Traits/Modifiers/FrozenUnderFog.cs b/OpenRA.Mods.Common/Traits/Modifiers/FrozenUnderFog.cs index e911a02c7e..60728770d9 100644 --- a/OpenRA.Mods.Common/Traits/Modifiers/FrozenUnderFog.cs +++ b/OpenRA.Mods.Common/Traits/Modifiers/FrozenUnderFog.cs @@ -33,7 +33,7 @@ namespace OpenRA.Mods.Common.Traits readonly FrozenUnderFogInfo info; readonly bool startsRevealed; - readonly MPos[] footprint; + readonly PPos[] footprint; readonly Lazy tooltip; readonly Lazy health; @@ -47,10 +47,12 @@ namespace OpenRA.Mods.Common.Traits { this.info = info; + var map = init.World.Map; + // Spawned actors (e.g. building husks) shouldn't be revealed startsRevealed = info.StartsRevealed && !init.Contains(); 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().FirstOrDefault()); health = Exts.Lazy(() => init.Self.TraitOrDefault()); diff --git a/OpenRA.Mods.Common/Traits/RevealsShroud.cs b/OpenRA.Mods.Common/Traits/RevealsShroud.cs index 1ae7491b6b..535e710ff2 100644 --- a/OpenRA.Mods.Common/Traits/RevealsShroud.cs +++ b/OpenRA.Mods.Common/Traits/RevealsShroud.cs @@ -27,14 +27,14 @@ namespace OpenRA.Mods.Common.Traits public class RevealsShroud : ITick, ISync, INotifyAddedToWorld, INotifyRemovedFromWorld { - static readonly CPos[] NoCells = { }; + static readonly PPos[] NoCells = { }; readonly RevealsShroudInfo info; readonly bool lobbyShroudFogDisabled; [Sync] CPos cachedLocation; [Sync] bool cachedDisabled; - protected Action addCellsToPlayerShroud; + protected Action addCellsToPlayerShroud; protected Action removeCellsFromPlayerShroud; protected Func isDisabled; @@ -43,12 +43,12 @@ namespace OpenRA.Mods.Common.Traits this.info = info; 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); isDisabled = () => false; } - CPos[] Cells(Actor self) + PPos[] ProjectedCells(Actor self) { var map = self.World.Map; var range = Range; @@ -57,10 +57,10 @@ namespace OpenRA.Mods.Common.Traits if (info.Type == VisibilityType.Footprint) return self.OccupiesSpace.OccupiedCells() - .SelectMany(kv => Shroud.CellsInRange(map, kv.First, range)) - .Distinct().ToArray(); + .SelectMany(kv => Shroud.ProjectedCellsInRange(map, kv.First, range)) + .Distinct().ToArray(); - return Shroud.CellsInRange(map, self.CenterPosition, range) + return Shroud.ProjectedCellsInRange(map, self.CenterPosition, range) .ToArray(); } @@ -69,15 +69,18 @@ namespace OpenRA.Mods.Common.Traits if (lobbyShroudFogDisabled || !self.IsInWorld) 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(); - if (cachedLocation == location && cachedDisabled == disabled) + + if (cachedLocation == projectedLocation && cachedDisabled == disabled) return; - cachedLocation = location; + cachedLocation = projectedLocation; cachedDisabled = disabled; - var cells = Cells(self); + var cells = ProjectedCells(self); foreach (var p in self.World.Players) { removeCellsFromPlayerShroud(p); @@ -87,10 +90,11 @@ namespace OpenRA.Mods.Common.Traits 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(); - - var cells = Cells(self); + var cells = ProjectedCells(self); foreach (var p in self.World.Players) addCellsToPlayerShroud(p, cells); } diff --git a/OpenRA.Mods.Common/Traits/World/MPStartLocations.cs b/OpenRA.Mods.Common/Traits/World/MPStartLocations.cs index 08d940b4ec..4779337a7a 100644 --- a/OpenRA.Mods.Common/Traits/World/MPStartLocations.cs +++ b/OpenRA.Mods.Common/Traits/World/MPStartLocations.cs @@ -64,10 +64,10 @@ namespace OpenRA.Mods.Common.Traits var map = world.Map; 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) if (p.IsAlliedWith(q)) - q.Shroud.Explore(world, cells); + q.Shroud.ExploreProjectedCells(world, cells); } // Set viewport diff --git a/OpenRA.Mods.Common/Traits/World/PathfinderDebugOverlay.cs b/OpenRA.Mods.Common/Traits/World/PathfinderDebugOverlay.cs index 102cc53000..3f2de61ebe 100644 --- a/OpenRA.Mods.Common/Traits/World/PathfinderDebugOverlay.cs +++ b/OpenRA.Mods.Common/Traits/World/PathfinderDebugOverlay.cs @@ -60,23 +60,24 @@ namespace OpenRA.Mods.Common.Traits var doDim = refreshTick - world.WorldTick <= 0; if (doDim) refreshTick = world.WorldTick + 20; + var map = wr.World.Map; foreach (var pair in layers) { var c = (pair.Key != null) ? pair.Key.Color.RGB : Color.PaleTurquoise; var layer = pair.Value; // 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; - var w = Math.Max(0, Math.Min(layer[cell], 128)); + var w = Math.Max(0, Math.Min(layer[uv], 128)); if (doDim) - layer[cell] = layer[cell] * 5 / 6; + layer[uv] = layer[uv] * 5 / 6; // 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 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)); diff --git a/OpenRA.Mods.Common/Traits/World/ShroudRenderer.cs b/OpenRA.Mods.Common/Traits/World/ShroudRenderer.cs index 6a32bfdad9..e948a68034 100644 --- a/OpenRA.Mods.Common/Traits/World/ShroudRenderer.cs +++ b/OpenRA.Mods.Common/Traits/World/ShroudRenderer.cs @@ -91,11 +91,11 @@ namespace OpenRA.Mods.Common.Traits readonly CellLayer tileInfos; readonly Sprite[] fogSprites, shroudSprites; - readonly HashSet cellsDirty = new HashSet(); - readonly HashSet cellsAndNeighborsDirty = new HashSet(); + readonly HashSet cellsDirty = new HashSet(); + readonly HashSet cellsAndNeighborsDirty = new HashSet(); Shroud currentShroud; - Func visibleUnderShroud, visibleUnderFog; + Func visibleUnderShroud, visibleUnderFog; TerrainSpriteLayer shroudLayer, fogLayer; 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 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); 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 if (w.Type == WorldType.Editor) visibleUnderShroud = _ => true; else - visibleUnderShroud = map.Contains; + visibleUnderShroud = puv => map.Contains(puv); - visibleUnderFog = map.Contains; + visibleUnderFog = puv => map.Contains(puv); var shroudSheet = shroudSprites[0].Sheet; 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); } - Edges GetEdges(MPos uv, Func isVisible) + Edges GetEdges(PPos puv, Func isVisible) { - if (!isVisible(uv)) + if (!isVisible(puv)) return notVisibleEdges; - var cell = uv.ToCPos(map); + var cell = ((MPos)puv).ToCPos(map); // If a side is shrouded then we also count the corners. var edge = Edges.None; - if (!isVisible((cell + new CVec(0, -1)).ToMPos(map))) edge |= Edges.Top; - if (!isVisible((cell + new CVec(1, 0)).ToMPos(map))) edge |= Edges.Right; - if (!isVisible((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(0, -1)).ToMPos(map))) edge |= Edges.Top; + if (!isVisible((PPos)(cell + new CVec(1, 0)).ToMPos(map))) edge |= Edges.Right; + if (!isVisible((PPos)(cell + new CVec(0, 1)).ToMPos(map))) edge |= Edges.Bottom; + if (!isVisible((PPos)(cell + new CVec(-1, 0)).ToMPos(map))) edge |= Edges.Left; var ucorner = edge & Edges.AllCorners; - if (!isVisible((cell + new CVec(-1, -1)).ToMPos(map))) edge |= Edges.TopLeft; - if (!isVisible((cell + new CVec(1, -1)).ToMPos(map))) edge |= Edges.TopRight; - if (!isVisible((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.TopLeft; + if (!isVisible((PPos)(cell + new CVec(1, -1)).ToMPos(map))) edge |= Edges.TopRight; + if (!isVisible((PPos)(cell + new CVec(1, 1)).ToMPos(map))) edge |= Edges.BottomRight; + if (!isVisible((PPos)(cell + new CVec(-1, 1)).ToMPos(map))) edge |= Edges.BottomLeft; // RA provides a set of frames for tiles with shrouded // 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; } - void DirtyCells(IEnumerable cells) + void DirtyCells(IEnumerable cells) { cellsDirty.UnionWith(cells); } @@ -244,36 +246,37 @@ namespace OpenRA.Mods.Common.Traits } else { - visibleUnderShroud = map.Contains; - visibleUnderFog = map.Contains; + visibleUnderShroud = puv => map.Contains(puv); + visibleUnderFog = puv => map.Contains(puv); } currentShroud = shroud; - DirtyCells(map.CellsInsideBounds); + DirtyCells(map.ProjectedCellBounds); } // 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. - 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) - 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)) continue; 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; if (shroudSprite != null) 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; if (fogSprite != null) fogPos += fogSprite.Offset - 0.5f * fogSprite.Size; diff --git a/OpenRA.Mods.Common/Traits/World/TerrainGeometryOverlay.cs b/OpenRA.Mods.Common/Traits/World/TerrainGeometryOverlay.cs index 189293d1f8..d801f44fb6 100644 --- a/OpenRA.Mods.Common/Traits/World/TerrainGeometryOverlay.cs +++ b/OpenRA.Mods.Common/Traits/World/TerrainGeometryOverlay.cs @@ -57,8 +57,11 @@ namespace OpenRA.Mods.Common.Traits var colors = wr.World.TileSet.HeightDebugColors; 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 tile = map.MapTiles.Value[uv]; var ti = tileSet.GetTileInfo(tile); @@ -80,6 +83,22 @@ namespace OpenRA.Mods.Common.Traits 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; } } } diff --git a/OpenRA.Mods.Common/UtilityCommands/LegacyMapImporter.cs b/OpenRA.Mods.Common/UtilityCommands/LegacyMapImporter.cs index 9cf4f76163..56f175f42a 100644 --- a/OpenRA.Mods.Common/UtilityCommands/LegacyMapImporter.cs +++ b/OpenRA.Mods.Common/UtilityCommands/LegacyMapImporter.cs @@ -151,8 +151,8 @@ namespace OpenRA.Mods.Common.UtilityCommands Author = "Westwood Studios" }; - var tl = new MPos(offsetX, offsetY); - var br = new MPos(offsetX + width - 1, offsetY + height - 1); + var tl = new PPos(offsetX, offsetY); + var br = new PPos(offsetX + width - 1, offsetY + height - 1); map.SetBounds(tl, br); if (legacyMapFormat == IniMapFormat.RedAlert) diff --git a/OpenRA.Mods.Common/Widgets/Logic/Editor/NewMapLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Editor/NewMapLogic.cs index a3f11d14e5..185d39c274 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Editor/NewMapLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Editor/NewMapLogic.cs @@ -64,8 +64,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic var tileset = modRules.TileSets[tilesetDropDown.Text]; var map = new Map(tileset, width + 2, height + maxTerrainHeight + 2); - var tl = new MPos(1, 1); - var br = new MPos(width, height + maxTerrainHeight); + var tl = new PPos(1, 1); + var br = new PPos(width, height + maxTerrainHeight); map.SetBounds(tl, br); map.PlayerDefinitions = new MapPlayers(map.Rules, map.SpawnPoints.Value.Length).ToMiniYaml(); diff --git a/OpenRA.Mods.Common/Widgets/RadarWidget.cs b/OpenRA.Mods.Common/Widgets/RadarWidget.cs index 615eda1977..02c27ac013 100644 --- a/OpenRA.Mods.Common/Widgets/RadarWidget.cs +++ b/OpenRA.Mods.Common/Widgets/RadarWidget.cs @@ -34,7 +34,7 @@ namespace OpenRA.Mods.Common.Widgets readonly WorldRenderer worldRenderer; readonly RadarPings radarPings; - readonly HashSet dirtyShroudCells = new HashSet(); + readonly HashSet dirtyShroudCells = new HashSet(); float radarMinimapHeight; int frame; @@ -82,7 +82,7 @@ namespace OpenRA.Mods.Common.Widgets actorSprite = new Sprite(radarSheet, new Rectangle(0, height, width, height), TextureChannel.Alpha); // Set initial terrain data - foreach (var cell in world.Map.CellsInsideBounds) + foreach (var cell in world.Map.AllCells) UpdateTerrainCell(cell); 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; var stride = radarSheet.Size.Width; - var uv = cell.ToMPos(world.Map); var dx = shroudSprite.Bounds.Left - world.Map.Bounds.Left; var dy = shroudSprite.Bounds.Top - world.Map.Bounds.Top; var color = 0; - if (world.ShroudObscures(cell)) - color = Color.Black.ToArgb(); - else if (world.FogObscures(cell)) - color = Color.FromArgb(128, Color.Black).ToArgb(); + var rp = world.RenderPlayer; + if (rp != null) + { + if (!rp.Shroud.IsExplored(projectedCell)) + color = Color.Black.ToArgb(); + else if (!rp.Shroud.IsVisible(projectedCell)) + color = Color.FromArgb(128, Color.Black).ToArgb(); + } unsafe { fixed (byte* colorBytes = &radarData[0]) { 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 cellsChanged) + void MarkShroudDirty(IEnumerable projectedCellsChanged) { - dirtyShroudCells.UnionWith(cellsChanged); + dirtyShroudCells.UnionWith(projectedCellsChanged); } public override string GetCursor(int2 pos) @@ -290,7 +293,7 @@ namespace OpenRA.Mods.Common.Widgets if (newRenderShroud != null) { // Redraw the full shroud sprite - MarkShroudDirty(world.Map.CellsInsideBounds); + MarkShroudDirty(world.Map.AllCells.MapCoords.Select(uv => (PPos)uv)); // Update the notification binding newRenderShroud.CellsChanged += MarkShroudDirty; diff --git a/OpenRA.Mods.D2k/UtilityCommands/D2kMapImporter.cs b/OpenRA.Mods.D2k/UtilityCommands/D2kMapImporter.cs index 2ca753a292..46a1bb7db7 100644 --- a/OpenRA.Mods.D2k/UtilityCommands/D2kMapImporter.cs +++ b/OpenRA.Mods.D2k/UtilityCommands/D2kMapImporter.cs @@ -315,8 +315,8 @@ namespace OpenRA.Mods.D2k.UtilityCommands Author = "Westwood Studios" }; - var tl = new MPos(MapCordonWidth, MapCordonWidth); - var br = new MPos(MapCordonWidth + mapSize.Width - 1, MapCordonWidth + mapSize.Height - 1); + var tl = new PPos(MapCordonWidth, MapCordonWidth); + var br = new PPos(MapCordonWidth + mapSize.Width - 1, MapCordonWidth + mapSize.Height - 1); 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