From 00cd43819095fd0a16efc31eaf64cbd2f0d51d8e Mon Sep 17 00:00:00 2001 From: Ashley Newson Date: Sat, 8 Nov 2025 20:43:20 +0000 Subject: [PATCH] Create coordinate system conversion wrapper around Mirror The Symmetry.Mirror enum was previously documented as defined in terms of the WPos system, which happens to align with CPos for Rectangular grid types. However, the existing code gets confused when attempting to support RectangularIsometric, due to a lack of appropriate conversion logic. This change makes Mirror agnostic to specific coordinate systems and introduces a WMirror wrapper that provides getters for the correct Mirror configuration given the coordinate system being used. --- OpenRA.Mods.Common/MapGenerator/NoiseUtils.cs | 4 +- OpenRA.Mods.Common/MapGenerator/Symmetry.cs | 89 ++++++++++++++----- .../MapGenerator/Terraformer.cs | 34 +++---- 3 files changed, 84 insertions(+), 43 deletions(-) diff --git a/OpenRA.Mods.Common/MapGenerator/NoiseUtils.cs b/OpenRA.Mods.Common/MapGenerator/NoiseUtils.cs index b0b02edcf2..c02faeaa73 100644 --- a/OpenRA.Mods.Common/MapGenerator/NoiseUtils.cs +++ b/OpenRA.Mods.Common/MapGenerator/NoiseUtils.cs @@ -189,7 +189,7 @@ namespace OpenRA.Mods.Common.MapGenerator MersenneTwister random, CellLayer cellLayer, int rotations, - Symmetry.Mirror mirror, + Symmetry.WMirror wmirror, int featureSize, Func ampFunc) { @@ -199,7 +199,7 @@ namespace OpenRA.Mods.Common.MapGenerator random, size, rotations, - mirror, + wmirror.ForCPos(), featureSize, ampFunc); CellLayerUtils.FromMatrix(cellLayer, noise); diff --git a/OpenRA.Mods.Common/MapGenerator/Symmetry.cs b/OpenRA.Mods.Common/MapGenerator/Symmetry.cs index b69ecc82af..dc8c029c47 100644 --- a/OpenRA.Mods.Common/MapGenerator/Symmetry.cs +++ b/OpenRA.Mods.Common/MapGenerator/Symmetry.cs @@ -18,25 +18,66 @@ namespace OpenRA.Mods.Common.MapGenerator { public static class Symmetry { - /// Trivial mirroring configurations defined in world space. + /// + /// Trivial mirroring configurations. These are not tied to a specific coordinate system. + /// public enum Mirror { /// No mirror. None = 0, - /// Match low X with high X in WPos space. + /// Match low X with high X. LeftMatchesRight = 1, - /// Match low X, low Y with high X, high Y in WPos space. + /// Match low X, low Y with high X, high Y. TopLeftMatchesBottomRight = 2, - /// Match low Y with high Y in WPos space. + /// Match low Y with high Y. TopMatchesBottom = 3, - /// Match low X, high Y with high X, low Y in WPos space. + /// Match low X, high Y with high X, low Y. TopRightMatchesBottomLeft = 4, } + /// + /// Wraps a mirror defined in terms of WPos space and provides conversions to appropriate + /// coordinate systems. + /// + public readonly struct WMirror + { + readonly Mirror mirror; + readonly MapGridType gridType; + + /// Create a WMirror. + /// Mirror relative to WPos space. + /// Map grid type used for conversions. + public WMirror(Mirror mirror, MapGridType gridType) + { + this.mirror = mirror; + this.gridType = gridType; + } + + public bool HasMirror => mirror != Mirror.None; + + /// Return a Mirror relative to the WPos coordinate system. + public Mirror ForWPos() + { + return mirror; + } + + /// Return a Mirror relative to the CPos coordinate system. + public Mirror ForCPos() + { + if (mirror == Mirror.None) + return Mirror.None; + + if (gridType == MapGridType.RectangularIsometric) + return (Mirror)((((int)mirror + 2) & 0b11) + 1); + + return mirror; + } + } + public static bool TryParseMirror(string s, out Mirror mirror) => Enum.TryParse(s, out mirror); /// @@ -71,10 +112,10 @@ namespace OpenRA.Mods.Common.MapGenerator } } - public static WPos MirrorWPosAround(Mirror mirror, WPos original, WPos center) + public static WPos MirrorWPosAround(WMirror wmirror, WPos original, WPos center) { var result = MirrorPointAround( - mirror, + wmirror.ForWPos(), new int2(original.X, original.Y), new int2(center.X, center.Y)); return new WPos(result.X, result.Y, original.Z); @@ -91,9 +132,9 @@ namespace OpenRA.Mods.Common.MapGenerator WPos original, WPos center, int rotations, - Mirror mirror) + WMirror wmirror) { - var projections = new WPos[RotateAndMirrorProjectionCount(rotations, mirror)]; + var projections = new WPos[RotateAndMirrorProjectionCount(rotations, wmirror.ForWPos())]; var projectionIndex = 0; for (var rotation = 0; rotation < rotations; rotation++) @@ -109,8 +150,8 @@ namespace OpenRA.Mods.Common.MapGenerator var projection = new WPos(projX, projY, original.Z); projections[projectionIndex++] = projection; - if (mirror != Mirror.None) - projections[projectionIndex++] = MirrorWPosAround(mirror, projection, center); + if (wmirror.HasMirror) + projections[projectionIndex++] = MirrorWPosAround(wmirror, projection, center); } return projections; @@ -120,24 +161,24 @@ namespace OpenRA.Mods.Common.MapGenerator WPos original, CellLayer cellLayer, int rotations, - Mirror mirror) + WMirror wmirror) { return RotateAndMirrorWPosAround( original, CellLayerUtils.Center(cellLayer), rotations, - mirror); + wmirror); } public static CPos[] RotateAndMirrorCPos( CPos original, CellLayer cellLayer, int rotations, - Mirror mirror) + WMirror wmirror) { - var cposProjections = new CPos[RotateAndMirrorProjectionCount(rotations, mirror)]; + var cposProjections = new CPos[RotateAndMirrorProjectionCount(rotations, wmirror.ForWPos())]; var wpos = CellLayerUtils.CPosToWPos(original, cellLayer.GridType); - var wposProjections = RotateAndMirrorWPos(wpos, cellLayer, rotations, mirror); + var wposProjections = RotateAndMirrorWPos(wpos, cellLayer, rotations, wmirror); for (var i = 0; i < wposProjections.Length; i++) cposProjections[i] = CellLayerUtils.WPosToCPos(wposProjections[i], cellLayer.GridType); return cposProjections; @@ -222,12 +263,12 @@ namespace OpenRA.Mods.Common.MapGenerator public static ImmutableArray RotateAndMirrorActorPlans( IReadOnlyList originals, int rotations, - Mirror mirror) + WMirror wmirror) { var projections = new List( - originals.Count * RotateAndMirrorProjectionCount(rotations, mirror)); + originals.Count * RotateAndMirrorProjectionCount(rotations, wmirror.ForWPos())); foreach (var original in originals) - projections.AddRange(RotateAndMirrorActorPlan(original, rotations, mirror)); + projections.AddRange(RotateAndMirrorActorPlan(original, rotations, wmirror)); return projections.ToImmutableArray(); } @@ -239,14 +280,14 @@ namespace OpenRA.Mods.Common.MapGenerator public static ImmutableArray RotateAndMirrorActorPlan( ActorPlan original, int rotations, - Mirror mirror) + WMirror wmirror) { - var projections = new List(RotateAndMirrorProjectionCount(rotations, mirror)); + var projections = new List(RotateAndMirrorProjectionCount(rotations, wmirror.ForWPos())); var points = RotateAndMirrorWPos( original.WPosCenterLocation, original.Map.Tiles, rotations, - mirror); + wmirror); foreach (var point in points) { var plan = original.Clone(); @@ -266,7 +307,7 @@ namespace OpenRA.Mods.Common.MapGenerator public static void RotateAndMirrorOverCPos( CellLayer cellLayer, int rotations, - Mirror mirror, + WMirror wmirror, Action action) { var size = cellLayer.Size; @@ -274,7 +315,7 @@ namespace OpenRA.Mods.Common.MapGenerator for (var u = 0; u < size.Width; u++) { var original = new MPos(u, v).ToCPos(cellLayer.GridType); - var projections = RotateAndMirrorCPos(original, cellLayer, rotations, mirror); + var projections = RotateAndMirrorCPos(original, cellLayer, rotations, wmirror); action(projections, original); } } diff --git a/OpenRA.Mods.Common/MapGenerator/Terraformer.cs b/OpenRA.Mods.Common/MapGenerator/Terraformer.cs index 75ca8e2ffe..2d49309c3c 100644 --- a/OpenRA.Mods.Common/MapGenerator/Terraformer.cs +++ b/OpenRA.Mods.Common/MapGenerator/Terraformer.cs @@ -106,7 +106,7 @@ namespace OpenRA.Mods.Common.MapGenerator public readonly Map Map; public readonly ModData ModData; public readonly List ActorPlans; - public readonly Symmetry.Mirror Mirror; + public readonly Symmetry.WMirror WMirror; public readonly int Rotations; readonly ITerrainInfo terrainInfo; @@ -128,7 +128,7 @@ namespace OpenRA.Mods.Common.MapGenerator Map = map; ModData = modData; ActorPlans = actorPlans; - Mirror = mirror; + WMirror = new Symmetry.WMirror(mirror, map.Grid.Type); Rotations = rotations; terrainInfo = modData.DefaultTerrainInfo[map.Tileset]; @@ -222,7 +222,7 @@ namespace OpenRA.Mods.Common.MapGenerator Symmetry.RotateAndMirrorOverCPos( layer, Rotations, - Mirror, + WMirror, (sources, destination) => newLayer[destination] = sources .Select(source => layer.TryGetValue(source, out var value) ? value : outsideValue) @@ -371,7 +371,7 @@ namespace OpenRA.Mods.Common.MapGenerator if (mask != null) zoneable = CellLayerUtils.Intersect([zoneable, mask]); - if (Rotations > 1 || Mirror != Symmetry.Mirror.None) + if (Rotations > 1 || WMirror.HasMirror) { // Reserve the center of the map - otherwise it will mess with symmetries CellLayerUtils.OverCircle( @@ -411,7 +411,7 @@ namespace OpenRA.Mods.Common.MapGenerator Symmetry.RotateAndMirrorOverCPos( projectionSpacing, Rotations, - Mirror, + WMirror, (projections, cpos) => projectionSpacing[cpos] = Symmetry.ProjectionProximity(projections) / 2); return projectionSpacing; @@ -443,7 +443,7 @@ namespace OpenRA.Mods.Common.MapGenerator Symmetry.RotateAndMirrorOverCPos( incompatibilities, Rotations, - Mirror, + WMirror, (sources, destination) => { if (!dominant[destination]) @@ -545,7 +545,7 @@ namespace OpenRA.Mods.Common.MapGenerator Symmetry.RotateAndMirrorOverCPos( regionMask, Rotations, - Mirror, + WMirror, TestSymmetry); for (var id = 0; id < symmetryScore.Length; id++) @@ -726,7 +726,7 @@ namespace OpenRA.Mods.Common.MapGenerator { CheckHasMapShapeOrNull(zoneable); var projections = Symmetry.RotateAndMirrorActorPlan( - actorPlan, Rotations, Mirror); + actorPlan, Rotations, WMirror); ActorPlans.AddRange(projections); if (zoneable != null) foreach (var projection in projections) @@ -998,7 +998,7 @@ namespace OpenRA.Mods.Common.MapGenerator random, noise, Rotations, - Mirror, + WMirror, noiseFeatureSize, wavelength => NoiseUtils.ClumpinessAmplitude(wavelength, clumpiness)); @@ -1021,7 +1021,7 @@ namespace OpenRA.Mods.Common.MapGenerator random, CellLayerUtils.CellBounds(Map).Size.ToInt2(), Rotations, - Mirror, + WMirror.ForCPos(), noiseFeatureSize, NoiseUtils.PinkAmplitude); MatrixUtils.NormalizeRangeInPlace(elevation, 1024); @@ -1050,7 +1050,7 @@ namespace OpenRA.Mods.Common.MapGenerator random, pattern, Rotations, - Mirror, + WMirror, noiseFeatureSize, wavelength => NoiseUtils.ClumpinessAmplitude(wavelength, clumpiness)); { @@ -1764,7 +1764,7 @@ namespace OpenRA.Mods.Common.MapGenerator chosenMPos.ToCPos(Map), space, Rotations, - Mirror); + WMirror); foreach (var projection in projections) { if (space.Contains(projection)) @@ -1811,7 +1811,7 @@ namespace OpenRA.Mods.Common.MapGenerator // For awkward symmetries, we try harder to make sure roads are fairer. // This can degrade the quantity of roads, though. var imperfectSymmetry = - Mirror != Symmetry.Mirror.None || + WMirror.HasMirror || Rotations == 3 || Rotations >= 5; var gridType = Map.Grid.Type; @@ -1890,7 +1890,7 @@ namespace OpenRA.Mods.Common.MapGenerator { var cposPath = CellLayerUtils.FromMatrixPoints([path], space)[0]; var projectedPoints = cposPath - .SelectMany(p => Symmetry.RotateAndMirrorCPos(p, space, Rotations, Mirror)) + .SelectMany(p => Symmetry.RotateAndMirrorCPos(p, space, Rotations, WMirror)) .ToArray(); var matrixPoints = CellLayerUtils.ToMatrixPoints([projectedPoints], space)[0]; if (!matrixPoints.All(p => !nearPath.ContainsXY(p) || nearPath[p])) @@ -2140,7 +2140,7 @@ namespace OpenRA.Mods.Common.MapGenerator var chosenMPos = PriorityMPos(n); var chosenCPos = chosenMPos.ToCPos(gridType); - foreach (var cpos in Symmetry.RotateAndMirrorCPos(chosenCPos, plan, Rotations, Mirror)) + foreach (var cpos in Symmetry.RotateAndMirrorCPos(chosenCPos, plan, Rotations, WMirror)) if (Map.Resources.Contains(cpos)) remaining -= AddResource(cpos); } @@ -2186,7 +2186,7 @@ namespace OpenRA.Mods.Common.MapGenerator random, decorationNoise, Rotations, - Mirror, + WMirror, featureSize, NoiseUtils.WhiteAmplitude); @@ -2195,7 +2195,7 @@ namespace OpenRA.Mods.Common.MapGenerator random, densityNoise, Rotations, - Mirror, + WMirror, 1024, NoiseUtils.PinkAmplitude); var densityMask = CellLayerUtils.CalibratedBooleanThreshold(