diff --git a/OpenRA.Game/Exts.cs b/OpenRA.Game/Exts.cs index 64fad44cc3..07021dc184 100644 --- a/OpenRA.Game/Exts.cs +++ b/OpenRA.Game/Exts.cs @@ -87,6 +87,29 @@ namespace OpenRA return r.Contains(p.ToPointF()); } + static int WindingDirectionTest(int2 v0, int2 v1, int2 p) + { + return (v1.X - v0.X) * (p.Y - v0.Y) - (p.X - v0.X) * (v1.Y - v0.Y); + } + + public static bool PolygonContains(this int2[] polygon, int2 p) + { + var windingNumber = 0; + + for (var i = 0; i < polygon.Length; i++) + { + var tv = polygon[i]; + var nv = polygon[(i + 1) % polygon.Length]; + + if (tv.Y <= p.Y && nv.Y > p.Y && WindingDirectionTest(tv, nv, p) > 0) + windingNumber++; + else if (tv.Y > p.Y && nv.Y <= p.Y && WindingDirectionTest(tv, nv, p) < 0) + windingNumber--; + } + + return windingNumber != 0; + } + public static bool HasModifier(this Modifiers k, Modifiers mod) { return (k & mod) == mod; diff --git a/OpenRA.Game/Graphics/Viewport.cs b/OpenRA.Game/Graphics/Viewport.cs index c09fc5bb94..03c5e87f05 100644 --- a/OpenRA.Game/Graphics/Viewport.cs +++ b/OpenRA.Game/Graphics/Viewport.cs @@ -105,9 +105,66 @@ namespace OpenRA.Graphics public CPos ViewToWorld(int2 view) { + var world = worldRenderer.Viewport.ViewToWorldPx(view); + var map = worldRenderer.World.Map; + var ts = Game.ModData.Manifest.TileSize; + var candidates = CandidateMouseoverCells(world); + var tileSet = worldRenderer.World.TileSet; + + foreach (var uv in candidates) + { + // Coarse filter to nearby cells + var p = map.CenterOfCell(uv.ToCPos(map.TileShape)); + var s = worldRenderer.ScreenPxPosition(p); + if (Math.Abs(s.X - world.X) <= ts.Width && Math.Abs(s.Y - world.Y) <= ts.Height) + { + var tile = map.MapTiles.Value[uv]; + var ti = tileSet.GetTileInfo(tile); + var ramp = ti != null ? ti.RampType : 0; + + var corners = map.CellCorners[ramp]; + var pos = map.CenterOfCell(uv.ToCPos(map)); + var screen = corners.Select(c => worldRenderer.ScreenPxPosition(pos + c)).ToArray(); + + if (screen.PolygonContains(world)) + return uv.ToCPos(map); + } + } + + // Mouse is not directly over a cell (perhaps on a cliff) + // Try and find the closest cell + if (candidates.Any()) + { + return candidates.OrderBy(uv => + { + var p = map.CenterOfCell(uv.ToCPos(map.TileShape)); + var s = worldRenderer.ScreenPxPosition(p); + var dx = Math.Abs(s.X - world.X); + var dy = Math.Abs(s.Y - world.Y); + + return dx * dx + dy * dy; + }).First().ToCPos(map); + } + + // Something is very wrong, but lets return something that isn't completely bogus and hope the caller can recover return worldRenderer.World.Map.CellContaining(worldRenderer.Position(ViewToWorldPx(view))); } + /// Returns an unfiltered list of all cells that could potentially contain the mouse cursor + IEnumerable CandidateMouseoverCells(int2 world) + { + var map = worldRenderer.World.Map; + var minPos = worldRenderer.Position(world); + + // Find all the cells that could potentially have been clicked + var a = map.CellContaining(minPos - new WVec(1024, 0, 0)).ToMPos(map.TileShape); + var b = map.CellContaining(minPos + new WVec(512, 512 * maxGroundHeight, 0)).ToMPos(map.TileShape); + + for (var v = b.V; v >= a.V; v--) + for (var u = b.U; u >= a.U; u--) + yield return new MPos(u, v); + } + public int2 ViewToWorldPx(int2 view) { return (1f / Zoom * view.ToFloat2()).ToInt2() + TopLeft; } public int2 WorldToViewPx(int2 world) { return (Zoom * (world - TopLeft).ToFloat2()).ToInt2(); } diff --git a/OpenRA.Mods.Common/Traits/World/TerrainGeometryOverlay.cs b/OpenRA.Mods.Common/Traits/World/TerrainGeometryOverlay.cs index 5a7b5eff86..6e0ab280db 100644 --- a/OpenRA.Mods.Common/Traits/World/TerrainGeometryOverlay.cs +++ b/OpenRA.Mods.Common/Traits/World/TerrainGeometryOverlay.cs @@ -37,19 +37,21 @@ namespace OpenRA.Mods.Common.Traits if (devMode.Value == null || !devMode.Value.ShowTerrainGeometry) return; + var map = wr.World.Map; + var tileSet = wr.World.TileSet; var lr = Game.Renderer.WorldLineRenderer; var colors = wr.World.TileSet.HeightDebugColors; foreach (var uv in wr.Viewport.VisibleCells.MapCoords) { - var height = (int)wr.World.Map.MapHeight.Value[uv]; - var tile = wr.World.Map.MapTiles.Value[uv]; - var ti = wr.World.TileSet.GetTileInfo(tile); + var height = (int)map.MapHeight.Value[uv]; + var tile = map.MapTiles.Value[uv]; + var ti = tileSet.GetTileInfo(tile); var ramp = ti != null ? ti.RampType : 0; - var corners = wr.World.Map.CellCorners[ramp]; + var corners = map.CellCorners[ramp]; var color = corners.Select(c => colors[height + c.Z / 512]).ToArray(); - var pos = wr.World.Map.CenterOfCell(uv.ToCPos(wr.World.Map)); + var pos = map.CenterOfCell(uv.ToCPos(map)); var screen = corners.Select(c => wr.ScreenPxPosition(pos + c).ToFloat2()).ToArray(); for (var i = 0; i < 4; i++)