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/Game.cs b/OpenRA.Game/Game.cs index b0e86e3d11..1c6a68c82e 100644 --- a/OpenRA.Game/Game.cs +++ b/OpenRA.Game/Game.cs @@ -494,7 +494,7 @@ namespace OpenRA if (worldRenderer != null) { Renderer.BeginFrame(worldRenderer.Viewport.TopLeft, worldRenderer.Viewport.Zoom); - Sound.SetListenerPosition(worldRenderer.Position(worldRenderer.Viewport.CenterLocation)); + Sound.SetListenerPosition(worldRenderer.Viewport.CenterPosition); worldRenderer.Draw(); } else diff --git a/OpenRA.Game/Graphics/Viewport.cs b/OpenRA.Game/Graphics/Viewport.cs index c09fc5bb94..3ab14e4bbe 100644 --- a/OpenRA.Game/Graphics/Viewport.cs +++ b/OpenRA.Game/Graphics/Viewport.cs @@ -105,9 +105,71 @@ 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 ramp = 0; + if (map.Contains(uv)) + { + var tile = map.MapTiles.Value[uv]; + var ti = tileSet.GetTileInfo(tile); + if (ti != null) + ramp = ti.RampType; + } + + 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.Game/Map/Map.cs b/OpenRA.Game/Map/Map.cs index fc479373e5..782c7fdbf8 100644 --- a/OpenRA.Game/Map/Map.cs +++ b/OpenRA.Game/Map/Map.cs @@ -125,6 +125,42 @@ namespace OpenRA public class Map : IMap { + static readonly int[][] CellCornerHalfHeights = new int[][] + { + // Flat + new[] { 0, 0, 0, 0 }, + + // Slopes (two corners high) + new[] { 0, 0, 1, 1 }, + new[] { 1, 0, 0, 1 }, + new[] { 1, 1, 0, 0 }, + new[] { 0, 1, 1, 0 }, + + // Slopes (one corner high) + new[] { 0, 0, 0, 1 }, + new[] { 1, 0, 0, 0 }, + new[] { 0, 1, 0, 0 }, + new[] { 0, 0, 1, 0 }, + + // Slopes (three corners high) + new[] { 1, 0, 1, 1 }, + new[] { 1, 1, 0, 1 }, + new[] { 1, 1, 1, 0 }, + new[] { 0, 1, 1, 1 }, + + // Slopes (two corners high, one corner double high) + new[] { 1, 0, 1, 2 }, + new[] { 2, 1, 0, 1 }, + new[] { 1, 2, 1, 0 }, + new[] { 0, 1, 2, 1 }, + + // Slopes (two corners high, alternating) + new[] { 1, 0, 1, 0 }, + new[] { 0, 1, 0, 1 }, + new[] { 1, 0, 1, 0 }, + new[] { 0, 1, 0, 1 } + }; + public const int MaxTilesInCircleRange = 50; public readonly TileShape TileShape; TileShape IMap.TileShape @@ -219,6 +255,7 @@ namespace OpenRA public Ruleset Rules { get { return rules != null ? rules.Value : null; } } public SequenceProvider SequenceProvider { get { return Rules.Sequences[Tileset]; } } + public WVec[][] CellCorners { get; private set; } [FieldLoader.Ignore] public CellRegion Cells; public static Map FromTileset(TileSet tileset) @@ -394,6 +431,18 @@ namespace OpenRA CustomTerrain = new CellLayer(this); foreach (var uv in Cells.MapCoords) CustomTerrain[uv] = byte.MaxValue; + + var leftDelta = TileShape == TileShape.Diamond ? new WVec(-512, 0, 0) : new WVec(-512, -512, 0); + var topDelta = TileShape == TileShape.Diamond ? new WVec(0, -512, 0) : new WVec(512, -512, 0); + var rightDelta = TileShape == TileShape.Diamond ? new WVec(512, 0, 0) : new WVec(512, 512, 0); + var bottomDelta = TileShape == TileShape.Diamond ? new WVec(0, 512, 0) : new WVec(-512, 512, 0); + CellCorners = CellCornerHalfHeights.Select(ramp => new WVec[] + { + leftDelta + new WVec(0, 0, 512 * ramp[0]), + topDelta + new WVec(0, 0, 512 * ramp[1]), + rightDelta + new WVec(0, 0, 512 * ramp[2]), + bottomDelta + new WVec(0, 0, 512 * ramp[3]) + }).ToArray(); } public Ruleset PreloadRules() @@ -641,7 +690,7 @@ namespace OpenRA // - ax + by adds (a - b) * 512 + 512 to u // - ax + by adds (a + b) * 512 + 512 to v var z = Contains(cell) ? 512 * MapHeight.Value[cell] : 0; - return new WPos(512 * (cell.X - cell.Y + 1), 512 * (cell.X + cell.Y + 1), z); + return new WPos(512 * (cell.X - cell.Y), 512 * (cell.X + cell.Y + 1), z); } public WPos CenterOfSubCell(CPos cell, SubCell subCell) @@ -660,10 +709,10 @@ namespace OpenRA // Convert from world position to diamond cell position: // (a) Subtract (512, 512) to move the rotation center to the middle of the corner cell // (b) Rotate axes by -pi/4 - // (c) Add 512 to x (but not y) to realign the cell + // (c) Add (512, 512) to move back to the center of the cell // (d) Divide by 1024 to find final cell coords var u = (pos.Y + pos.X - 512) / 1024; - var v = (pos.Y - pos.X) / 1024; + var v = (pos.Y - pos.X - 512) / 1024; return new CPos(u, v); } diff --git a/OpenRA.Game/Traits/World/ScreenShaker.cs b/OpenRA.Game/Traits/World/ScreenShaker.cs index 06a82efe0f..ffce699341 100644 --- a/OpenRA.Game/Traits/World/ScreenShaker.cs +++ b/OpenRA.Game/Traits/World/ScreenShaker.cs @@ -73,7 +73,7 @@ namespace OpenRA.Traits float GetIntensity() { - var cp = worldRenderer.Position(worldRenderer.Viewport.CenterLocation); + var cp = worldRenderer.Viewport.CenterPosition; var intensity = 100 * 1024 * 1024 * shakeEffects.Sum( e => (float)e.Intensity / (e.Position - cp).LengthSquared); diff --git a/OpenRA.Game/Widgets/WorldInteractionControllerWidget.cs b/OpenRA.Game/Widgets/WorldInteractionControllerWidget.cs index c16d57a615..07dbabeab8 100644 --- a/OpenRA.Game/Widgets/WorldInteractionControllerWidget.cs +++ b/OpenRA.Game/Widgets/WorldInteractionControllerWidget.cs @@ -71,7 +71,7 @@ namespace OpenRA.Widgets // Place buildings, use support powers, and other non-unit things if (!(World.OrderGenerator is UnitOrderGenerator)) { - ApplyOrders(World, xy, mi); + ApplyOrders(World, mi); dragStart = dragEnd = null; YieldMouseFocus(mi); lastMousePosition = xy; @@ -95,7 +95,7 @@ namespace OpenRA.Widgets !mi.Modifiers.HasModifier(Modifiers.Alt) && UnitOrderGenerator.InputOverridesSelection(World, xy, mi))) { // Order units instead of selecting - ApplyOrders(World, xy, mi); + ApplyOrders(World, mi); dragStart = dragEnd = null; YieldMouseFocus(mi); lastMousePosition = xy; @@ -133,7 +133,7 @@ namespace OpenRA.Widgets if (useClassicMouseStyle) World.Selection.Clear(); - ApplyOrders(World, xy, mi); + ApplyOrders(World, mi); } } @@ -159,13 +159,13 @@ namespace OpenRA.Widgets } } - void ApplyOrders(World world, int2 xy, MouseInput mi) + void ApplyOrders(World world, MouseInput mi) { if (world.OrderGenerator == null) return; - var pos = worldRenderer.Position(xy); - var orders = world.OrderGenerator.Order(world, world.Map.CellContaining(pos), mi).ToArray(); + var cell = worldRenderer.Viewport.ViewToWorld(mi.Location); + var orders = world.OrderGenerator.Order(world, cell, mi).ToArray(); world.PlayVoiceForOrders(orders); var flashed = false; @@ -184,8 +184,8 @@ namespace OpenRA.Widgets } else if (o.TargetLocation != CPos.Zero) { - world.AddFrameEndTask(w => w.Add( - new SpriteEffect(worldRenderer.Position(worldRenderer.Viewport.ViewToWorldPx(mi.Location)), world, "moveflsh", "moveflash"))); + var pos = world.Map.CenterOfCell(cell); + world.AddFrameEndTask(w => w.Add(new SpriteEffect(pos, world, "moveflsh", "moveflash"))); flashed = true; } } @@ -202,9 +202,7 @@ namespace OpenRA.Widgets if (SelectionBox != null) return null; - var xy = worldRenderer.Viewport.ViewToWorldPx(screenPos); - var pos = worldRenderer.Position(xy); - var cell = World.Map.CellContaining(pos); + var cell = worldRenderer.Viewport.ViewToWorld(screenPos); var mi = new MouseInput { diff --git a/OpenRA.Mods.Common/Traits/World/TerrainGeometryOverlay.cs b/OpenRA.Mods.Common/Traits/World/TerrainGeometryOverlay.cs index 8666710aff..5b18ea44f1 100644 --- a/OpenRA.Mods.Common/Traits/World/TerrainGeometryOverlay.cs +++ b/OpenRA.Mods.Common/Traits/World/TerrainGeometryOverlay.cs @@ -25,42 +25,6 @@ namespace OpenRA.Mods.Common.Traits public class TerrainGeometryOverlay : IRenderOverlay { - readonly int[][] vertices = new int[][] - { - // Flat - new[] { 0, 0, 0, 0 }, - - // Slopes (two corners high) - new[] { 0, 0, 1, 1 }, - new[] { 1, 0, 0, 1 }, - new[] { 1, 1, 0, 0 }, - new[] { 0, 1, 1, 0 }, - - // Slopes (one corner high) - new[] { 0, 0, 0, 1 }, - new[] { 1, 0, 0, 0 }, - new[] { 0, 1, 0, 0 }, - new[] { 0, 0, 1, 0 }, - - // Slopes (three corners high) - new[] { 1, 0, 1, 1 }, - new[] { 1, 1, 0, 1 }, - new[] { 1, 1, 1, 0 }, - new[] { 0, 1, 1, 1 }, - - // Slopes (two corners high, one corner double high) - new[] { 1, 0, 1, 2 }, - new[] { 2, 1, 0, 1 }, - new[] { 1, 2, 1, 0 }, - new[] { 0, 1, 2, 1 }, - - // Slopes (two corners high, alternating) - new[] { 1, 0, 1, 0 }, - new[] { 0, 1, 0, 1 }, - new[] { 1, 0, 1, 0 }, - new[] { 0, 1, 0, 1 } - }; - readonly Lazy devMode; public TerrainGeometryOverlay(Actor self) @@ -73,54 +37,34 @@ namespace OpenRA.Mods.Common.Traits if (devMode.Value == null || !devMode.Value.ShowTerrainGeometry) return; - var ts = wr.World.Map.TileShape; + var map = wr.World.Map; + var tileSet = wr.World.TileSet; + var lr = Game.Renderer.WorldLineRenderer; var colors = wr.World.TileSet.HeightDebugColors; - - var leftDelta = ts == TileShape.Diamond ? new WVec(-512, 0, 0) : new WVec(-512, -512, 0); - var topDelta = ts == TileShape.Diamond ? new WVec(0, -512, 0) : new WVec(512, -512, 0); - var rightDelta = ts == TileShape.Diamond ? new WVec(512, 0, 0) : new WVec(512, 512, 0); - var bottomDelta = ts == TileShape.Diamond ? new WVec(0, 512, 0) : new WVec(-512, 512, 0); + var mouseCell = wr.Viewport.ViewToWorld(Viewport.LastMousePos).ToMPos(wr.World.Map); foreach (var uv in wr.Viewport.VisibleCells.MapCoords) { - var lr = Game.Renderer.WorldLineRenderer; - var pos = wr.World.Map.CenterOfCell(uv.ToCPos(wr.World.Map)); + 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 height = (int)wr.World.Map.MapHeight.Value[uv]; - var tile = wr.World.Map.MapTiles.Value[uv]; + var corners = map.CellCorners[ramp]; + var color = corners.Select(c => colors[height + c.Z / 512]).ToArray(); + var pos = map.CenterOfCell(uv.ToCPos(map)); + var screen = corners.Select(c => wr.ScreenPxPosition(pos + c).ToFloat2()).ToArray(); - TerrainTileInfo tileInfo = null; + if (uv == mouseCell) + lr.LineWidth = 3; - // TODO: This is a temporary workaround for our sloppy tileset definitions - // (ra/td templates omit Clear tiles from templates) - try + for (var i = 0; i < 4; i++) { - tileInfo = wr.World.TileSet.Templates[tile.Type][tile.Index]; + var j = (i + 1) % 4; + lr.DrawLine(screen[i], screen[j], color[i], color[j]); } - catch (Exception) { } - if (tileInfo == null) - continue; - - var leftHeight = vertices[tileInfo.RampType][0]; - var topHeight = vertices[tileInfo.RampType][1]; - var rightHeight = vertices[tileInfo.RampType][2]; - var bottomHeight = vertices[tileInfo.RampType][3]; - - var leftColor = colors[height + leftHeight]; - var topColor = colors[height + topHeight]; - var rightColor = colors[height + rightHeight]; - var bottomColor = colors[height + bottomHeight]; - - var left = wr.ScreenPxPosition(pos + leftDelta + new WVec(0, 0, 512 * leftHeight)).ToFloat2(); - var top = wr.ScreenPxPosition(pos + topDelta + new WVec(0, 0, 512 * topHeight)).ToFloat2(); - var right = wr.ScreenPxPosition(pos + rightDelta + new WVec(0, 0, 512 * rightHeight)).ToFloat2(); - var bottom = wr.ScreenPxPosition(pos + bottomDelta + new WVec(0, 0, 512 * bottomHeight)).ToFloat2(); - - lr.DrawLine(left, top, leftColor, topColor); - lr.DrawLine(top, right, topColor, rightColor); - lr.DrawLine(right, bottom, rightColor, bottomColor); - lr.DrawLine(bottom, left, bottomColor, leftColor); + lr.LineWidth = 1; } } }