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