From fb5bcd3889c32bc08a6c13dd35d2891d5143edd0 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sun, 26 Jul 2015 15:55:49 +0100 Subject: [PATCH 1/4] Extract MapCoordsRegion from CellRegion. --- OpenRA.Game/Map/CellRegion.cs | 72 +----------------------- OpenRA.Game/Map/MapCoordsRegion.cs | 89 ++++++++++++++++++++++++++++++ OpenRA.Game/OpenRA.Game.csproj | 1 + 3 files changed, 91 insertions(+), 71 deletions(-) create mode 100644 OpenRA.Game/Map/MapCoordsRegion.cs 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/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/OpenRA.Game.csproj b/OpenRA.Game/OpenRA.Game.csproj index 035ddf3832..d5daabd551 100644 --- a/OpenRA.Game/OpenRA.Game.csproj +++ b/OpenRA.Game/OpenRA.Game.csproj @@ -248,6 +248,7 @@ + From e8794032e0bd1e9c3961548462b1d80871fc434b Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Tue, 23 Jun 2015 19:41:34 +0100 Subject: [PATCH 2/4] Introduce initial PPos plumbing. PPos is best thought of as a cell grid applied in screen space. Multiple cells with different terrain heights may be projected to the same PPos, or to multiple PPos if they do not align with the screen grid. PPos coordinates are used primarily for map edge checks and shroud / visibility queries. --- OpenRA.Game/Graphics/TerrainSpriteLayer.cs | 4 +- OpenRA.Game/Graphics/Viewport.cs | 64 +++---- OpenRA.Game/MPos.cs | 30 ++++ OpenRA.Game/Map/Map.cs | 165 ++++++++++++++++-- OpenRA.Game/Map/ProjectedCellRegion.cs | 125 +++++++++++++ OpenRA.Game/OpenRA.Game.csproj | 1 + OpenRA.Game/Traits/World/Shroud.cs | 6 +- .../Traits/World/PathfinderDebugOverlay.cs | 11 +- .../Traits/World/ShroudRenderer.cs | 4 +- .../Traits/World/TerrainGeometryOverlay.cs | 18 +- .../UtilityCommands/LegacyMapImporter.cs | 4 +- .../Widgets/Logic/Editor/NewMapLogic.cs | 4 +- OpenRA.Mods.Common/Widgets/RadarWidget.cs | 4 +- .../UtilityCommands/D2kMapImporter.cs | 4 +- 14 files changed, 381 insertions(+), 63 deletions(-) create mode 100644 OpenRA.Game/Map/ProjectedCellRegion.cs 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/Map.cs b/OpenRA.Game/Map/Map.cs index a61b209892..d620aa3e7f 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); + // The first check ensures that the cell is within the valid map region, avoiding + // potential crashes in deeper code. All CellLayers have the same geometry, and + // CustomTerrain is convenient (cellProjection may be null and others are Lazy). + return CustomTerrain.Contains(uv) && 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/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 d5daabd551..dbfd7cee98 100644 --- a/OpenRA.Game/OpenRA.Game.csproj +++ b/OpenRA.Game/OpenRA.Game.csproj @@ -248,6 +248,7 @@ + diff --git a/OpenRA.Game/Traits/World/Shroud.cs b/OpenRA.Game/Traits/World/Shroud.cs index e54a8d6685..dec63f0282 100644 --- a/OpenRA.Game/Traits/World/Shroud.cs +++ b/OpenRA.Game/Traits/World/Shroud.cs @@ -204,7 +204,7 @@ namespace OpenRA.Traits throw new ArgumentException("The map bounds of these shrouds do not match.", "s"); var changed = new List(); - foreach (var uv in map.CellsInsideBounds.MapCoords) + foreach (var uv in map.ProjectedCellBounds.CandidateMapCoords) { if (!explored[uv] && s.explored[uv]) { @@ -219,7 +219,7 @@ namespace OpenRA.Traits public void ExploreAll(World world) { var changed = new List(); - foreach (var uv in map.CellsInsideBounds.MapCoords) + foreach (var uv in map.ProjectedCellBounds.CandidateMapCoords) { if (!explored[uv]) { @@ -234,7 +234,7 @@ namespace OpenRA.Traits public void ResetExploration() { var changed = new List(); - foreach (var uv in map.CellsInsideBounds.MapCoords) + foreach (var uv in map.ProjectedCellBounds.CandidateMapCoords) { var visible = visibleCount[uv] > 0; if (explored[uv] != visible) 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..05f4e608c8 100644 --- a/OpenRA.Mods.Common/Traits/World/ShroudRenderer.cs +++ b/OpenRA.Mods.Common/Traits/World/ShroudRenderer.cs @@ -249,7 +249,9 @@ namespace OpenRA.Mods.Common.Traits } currentShroud = shroud; - DirtyCells(map.CellsInsideBounds); + var dirty = map.ProjectedCellBounds + .SelectMany(puv => map.Unproject(puv).Select(uv => uv.ToCPos(map))); + DirtyCells(dirty); } // We need to update newly dirtied areas of the shroud. diff --git a/OpenRA.Mods.Common/Traits/World/TerrainGeometryOverlay.cs b/OpenRA.Mods.Common/Traits/World/TerrainGeometryOverlay.cs index 189293d1f8..b2eb65b127 100644 --- a/OpenRA.Mods.Common/Traits/World/TerrainGeometryOverlay.cs +++ b/OpenRA.Mods.Common/Traits/World/TerrainGeometryOverlay.cs @@ -57,7 +57,7 @@ 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) { var height = (int)map.MapHeight.Value[uv]; var tile = map.MapTiles.Value[uv]; @@ -80,6 +80,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..742e02b0a5 100644 --- a/OpenRA.Mods.Common/Widgets/RadarWidget.cs +++ b/OpenRA.Mods.Common/Widgets/RadarWidget.cs @@ -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; @@ -290,7 +290,7 @@ namespace OpenRA.Mods.Common.Widgets if (newRenderShroud != null) { // Redraw the full shroud sprite - MarkShroudDirty(world.Map.CellsInsideBounds); + MarkShroudDirty(world.Map.AllCells); // 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 From 86ba26e013db660905e9ce2c13316a724538d6da Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Wed, 1 Apr 2015 20:03:51 +0100 Subject: [PATCH 3/4] Convert shroud calculations and rendering to PPos. --- OpenRA.Game/Traits/Player/FrozenActorLayer.cs | 4 +- OpenRA.Game/Traits/World/Shroud.cs | 156 ++++++++++-------- OpenRA.Game/World.cs | 29 +--- OpenRA.Mods.Common/Traits/CreatesShroud.cs | 2 +- .../Traits/Modifiers/FrozenUnderFog.cs | 6 +- OpenRA.Mods.Common/Traits/RevealsShroud.cs | 32 ++-- .../Traits/World/MPStartLocations.cs | 4 +- .../Traits/World/ShroudRenderer.cs | 63 +++---- .../Traits/World/TerrainGeometryOverlay.cs | 3 + OpenRA.Mods.Common/Widgets/RadarWidget.cs | 27 +-- 10 files changed, 161 insertions(+), 165 deletions(-) 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 dec63f0282..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.ProjectedCellBounds.CandidateMapCoords) + 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.ProjectedCellBounds.CandidateMapCoords) + 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.ProjectedCellBounds.CandidateMapCoords) + 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/ShroudRenderer.cs b/OpenRA.Mods.Common/Traits/World/ShroudRenderer.cs index 05f4e608c8..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,38 +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; - var dirty = map.ProjectedCellBounds - .SelectMany(puv => map.Unproject(puv).Select(uv => uv.ToCPos(map))); - DirtyCells(dirty); + 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 b2eb65b127..d801f44fb6 100644 --- a/OpenRA.Mods.Common/Traits/World/TerrainGeometryOverlay.cs +++ b/OpenRA.Mods.Common/Traits/World/TerrainGeometryOverlay.cs @@ -59,6 +59,9 @@ namespace OpenRA.Mods.Common.Traits 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); diff --git a/OpenRA.Mods.Common/Widgets/RadarWidget.cs b/OpenRA.Mods.Common/Widgets/RadarWidget.cs index 742e02b0a5..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; @@ -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.AllCells); + MarkShroudDirty(world.Map.AllCells.MapCoords.Select(uv => (PPos)uv)); // Update the notification binding newRenderShroud.CellsChanged += MarkShroudDirty; From 4bc75f1ed68cc00ced92dc306512335d7da6b872 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Mon, 27 Jul 2015 20:19:52 +0100 Subject: [PATCH 4/4] Temporarily work around RadarWidget corruption. --- OpenRA.Game/Map/Map.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/OpenRA.Game/Map/Map.cs b/OpenRA.Game/Map/Map.cs index d620aa3e7f..a26d6fa4ee 100644 --- a/OpenRA.Game/Map/Map.cs +++ b/OpenRA.Game/Map/Map.cs @@ -739,10 +739,10 @@ namespace OpenRA public bool Contains(MPos uv) { - // The first check ensures that the cell is within the valid map region, avoiding - // potential crashes in deeper code. All CellLayers have the same geometry, and - // CustomTerrain is convenient (cellProjection may be null and others are Lazy). - return CustomTerrain.Contains(uv) && ProjectedCellsCovering(uv).All(containsTest); + // 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)