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.
This commit is contained in:
Ashley Newson
2025-11-08 20:43:20 +00:00
committed by Gustas Kažukauskas
parent 91ddfd6fc1
commit 00cd438190
3 changed files with 84 additions and 43 deletions

View File

@@ -189,7 +189,7 @@ namespace OpenRA.Mods.Common.MapGenerator
MersenneTwister random,
CellLayer<int> cellLayer,
int rotations,
Symmetry.Mirror mirror,
Symmetry.WMirror wmirror,
int featureSize,
Func<int, int> ampFunc)
{
@@ -199,7 +199,7 @@ namespace OpenRA.Mods.Common.MapGenerator
random,
size,
rotations,
mirror,
wmirror.ForCPos(),
featureSize,
ampFunc);
CellLayerUtils.FromMatrix(cellLayer, noise);

View File

@@ -18,25 +18,66 @@ namespace OpenRA.Mods.Common.MapGenerator
{
public static class Symmetry
{
/// <summary>Trivial mirroring configurations defined in world space.</summary>
/// <summary>
/// Trivial mirroring configurations. These are not tied to a specific coordinate system.
/// </summary>
public enum Mirror
{
/// <summary>No mirror.</summary>
None = 0,
/// <summary>Match low X with high X in WPos space.</summary>
/// <summary>Match low X with high X.</summary>
LeftMatchesRight = 1,
/// <summary>Match low X, low Y with high X, high Y in WPos space.</summary>
/// <summary>Match low X, low Y with high X, high Y.</summary>
TopLeftMatchesBottomRight = 2,
/// <summary>Match low Y with high Y in WPos space.</summary>
/// <summary>Match low Y with high Y.</summary>
TopMatchesBottom = 3,
/// <summary>Match low X, high Y with high X, low Y in WPos space.</summary>
/// <summary>Match low X, high Y with high X, low Y.</summary>
TopRightMatchesBottomLeft = 4,
}
/// <summary>
/// Wraps a mirror defined in terms of WPos space and provides conversions to appropriate
/// coordinate systems.
/// </summary>
public readonly struct WMirror
{
readonly Mirror mirror;
readonly MapGridType gridType;
/// <summary>Create a WMirror.</summary>
/// <param name="mirror">Mirror relative to WPos space.</param>
/// <param name="gridType">Map grid type used for conversions.</param>
public WMirror(Mirror mirror, MapGridType gridType)
{
this.mirror = mirror;
this.gridType = gridType;
}
public bool HasMirror => mirror != Mirror.None;
/// <summary>Return a Mirror relative to the WPos coordinate system.</summary>
public Mirror ForWPos()
{
return mirror;
}
/// <summary>Return a Mirror relative to the CPos coordinate system.</summary>
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);
/// <summary>
@@ -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<T> cellLayer,
int rotations,
Mirror mirror)
WMirror wmirror)
{
return RotateAndMirrorWPosAround(
original,
CellLayerUtils.Center(cellLayer),
rotations,
mirror);
wmirror);
}
public static CPos[] RotateAndMirrorCPos<T>(
CPos original,
CellLayer<T> 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<ActorPlan> RotateAndMirrorActorPlans(
IReadOnlyList<ActorPlan> originals,
int rotations,
Mirror mirror)
WMirror wmirror)
{
var projections = new List<ActorPlan>(
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<ActorPlan> RotateAndMirrorActorPlan(
ActorPlan original,
int rotations,
Mirror mirror)
WMirror wmirror)
{
var projections = new List<ActorPlan>(RotateAndMirrorProjectionCount(rotations, mirror));
var projections = new List<ActorPlan>(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<T>(
CellLayer<T> cellLayer,
int rotations,
Mirror mirror,
WMirror wmirror,
Action<CPos[], CPos> 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);
}
}

View File

@@ -106,7 +106,7 @@ namespace OpenRA.Mods.Common.MapGenerator
public readonly Map Map;
public readonly ModData ModData;
public readonly List<ActorPlan> 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(