Files
let5sne.win10 9cf6ebb986
Some checks failed
Continuous Integration / Linux (.NET 8.0) (push) Has been cancelled
Continuous Integration / Windows (.NET 8.0) (push) Has been cancelled
Initial commit: OpenRA game engine
Fork from OpenRA/OpenRA with one-click launch script (start-ra.cmd)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-10 21:46:54 +08:00

324 lines
9.9 KiB
C#

#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* 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, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
namespace OpenRA.Mods.Common.MapGenerator
{
public static class Symmetry
{
/// <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.</summary>
LeftMatchesRight = 1,
/// <summary>Match low X, low Y with high X, high Y.</summary>
TopLeftMatchesBottomRight = 2,
/// <summary>Match low Y with high Y.</summary>
TopMatchesBottom = 3,
/// <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>
/// <para>
/// Mirrors a (zero-area) point around a given center.
/// </para>
/// <para>
/// For example, if using a center of (40, 40) a point at (1, 1) could be projected
/// to (1, 1), (1, 79), (79, 1), or (79, 79).
/// </para>
/// </summary>
public static int2 MirrorPointAround(Mirror mirror, int2 original, int2 center)
{
switch (mirror)
{
case Mirror.None:
throw new ArgumentException("Mirror.None has no transformed point");
case Mirror.LeftMatchesRight:
return new int2(2 * center.X - original.X, original.Y);
case Mirror.TopLeftMatchesBottomRight:
return new int2(
center.Y - original.Y + center.X,
center.X - original.X + center.Y);
case Mirror.TopMatchesBottom:
return new int2(original.X, 2 * center.Y - original.Y);
case Mirror.TopRightMatchesBottomLeft:
return new int2(
center.X + original.Y - center.Y,
center.Y + original.X - center.X);
default:
throw new ArgumentException("Bad mirror");
}
}
public static WPos MirrorWPosAround(WMirror wmirror, WPos original, WPos center)
{
var result = MirrorPointAround(
wmirror.ForWPos(),
new int2(original.X, original.Y),
new int2(center.X, center.Y));
return new WPos(result.X, result.Y, original.Z);
}
/// <summary>
/// Given rotation and mirror parameters, return the total number of projected points this
/// would result in (including the original point).
/// </summary>
public static int RotateAndMirrorProjectionCount(int rotations, Mirror mirror)
=> mirror == Mirror.None ? rotations : rotations * 2;
public static WPos[] RotateAndMirrorWPosAround(
WPos original,
WPos center,
int rotations,
WMirror wmirror)
{
var projections = new WPos[RotateAndMirrorProjectionCount(rotations, wmirror.ForWPos())];
var projectionIndex = 0;
for (var rotation = 0; rotation < rotations; rotation++)
{
// This could be made more accurate using dedicated, higher precision
// rotation count to cos and sin lookup tables.
var wangle = new WAngle(rotation * 1024 / rotations);
var cos1024 = wangle.Cos();
var sin1024 = wangle.Sin();
var relOrig = original - center;
var projX = (relOrig.X * cos1024 - relOrig.Y * sin1024) / 1024 + center.X;
var projY = (relOrig.X * sin1024 + relOrig.Y * cos1024) / 1024 + center.Y;
var projection = new WPos(projX, projY, original.Z);
projections[projectionIndex++] = projection;
if (wmirror.HasMirror)
projections[projectionIndex++] = MirrorWPosAround(wmirror, projection, center);
}
return projections;
}
public static WPos[] RotateAndMirrorWPos<T>(
WPos original,
CellLayer<T> cellLayer,
int rotations,
WMirror wmirror)
{
return RotateAndMirrorWPosAround(
original,
CellLayerUtils.Center(cellLayer),
rotations,
wmirror);
}
public static CPos[] RotateAndMirrorCPos<T>(
CPos original,
CellLayer<T> cellLayer,
int rotations,
WMirror wmirror)
{
var cposProjections = new CPos[RotateAndMirrorProjectionCount(rotations, wmirror.ForWPos())];
var wpos = CellLayerUtils.CPosToWPos(original, cellLayer.GridType);
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;
}
/// <summary>
/// Determine the shortest distance between projected positions.
/// </summary>
public static int ProjectionProximity(int2[] projections)
{
if (projections.Length == 1)
return int.MaxValue;
var worstSpacingSq = long.MaxValue;
for (var i1 = 0; i1 < projections.Length; i1++)
for (var i2 = 0; i2 < projections.Length; i2++)
{
if (i1 == i2)
continue;
var spacingSq = (projections[i1] - projections[i2]).LengthSquared;
if (spacingSq < worstSpacingSq)
worstSpacingSq = spacingSq;
}
return (int)Math.Sqrt(worstSpacingSq);
}
/// <summary>
/// Determine the shortest distance between projected positions.
/// </summary>
public static int ProjectionProximity(CPos[] projections)
{
return ProjectionProximity(
projections.Select(cpos => new int2(cpos.X, cpos.Y)).ToArray());
}
/// <summary>
/// <para>
/// Duplicate an original point into an array of projected points according to a rotation
/// and mirror specification.
/// </para>
/// <para>
/// Rotations use WAngel-based trigonometric math for consistency with other Symmetry
/// functions. This may be slightly imprecise for non-trivial rotations.
/// </para>
/// <para>
/// For example, if using a center of (40, 40) a point at (1, 1) could be projected
/// to (1, 1), (1, 79), (79, 1), and (79, 79).
/// </para>
/// </summary>
public static int2[] RotateAndMirrorPointAround(
int2 original,
int2 center,
int rotations,
Mirror mirror)
{
var projections = new int2[RotateAndMirrorProjectionCount(rotations, mirror)];
var projectionIndex = 0;
for (var rotation = 0; rotation < rotations; rotation++)
{
// This could be made more accurate using dedicated, higher precision
// rotation count to cos and sin lookup tables.
var wangle = new WAngle(rotation * 1024 / rotations);
long cos = wangle.Cos();
long sin = wangle.Sin();
var relOrig = original - center;
var projX = (relOrig.X * cos - relOrig.Y * sin) / 1024 + center.X;
var projY = (relOrig.X * sin + relOrig.Y * cos) / 1024 + center.Y;
var projection = new int2((int)projX, (int)projY);
projections[projectionIndex++] = projection;
if (mirror != Mirror.None)
projections[projectionIndex++] = MirrorPointAround(mirror, projection, center);
}
return projections;
}
/// <summary>
/// Rotate and mirror multiple actor plans. See RotateAndMirrorActorPlan.
/// </summary>
public static ImmutableArray<ActorPlan> RotateAndMirrorActorPlans(
IReadOnlyList<ActorPlan> originals,
int rotations,
WMirror wmirror)
{
var projections = new List<ActorPlan>(
originals.Count * RotateAndMirrorProjectionCount(rotations, wmirror.ForWPos()));
foreach (var original in originals)
projections.AddRange(RotateAndMirrorActorPlan(original, rotations, wmirror));
return projections.ToImmutableArray();
}
/// <summary>
/// Rotate and mirror a single actor plan, adding to an accumulator list.
/// Locations (CPos) are necessarily snapped to grid.
/// </summary>
public static ImmutableArray<ActorPlan> RotateAndMirrorActorPlan(
ActorPlan original,
int rotations,
WMirror wmirror)
{
var projections = new List<ActorPlan>(RotateAndMirrorProjectionCount(rotations, wmirror.ForWPos()));
var points = RotateAndMirrorWPos(
original.WPosCenterLocation,
original.Map.Tiles,
rotations,
wmirror);
foreach (var point in points)
{
var plan = original.Clone();
plan.WPosCenterLocation = point;
projections.Add(plan);
}
return projections.ToImmutableArray();
}
/// <summary>
/// Calls action(projections, original) over all possible original
/// CPos positions, where each projection in projections is a
/// mirrored/rotated point. For non-trivial symmetries, projections may
/// be outside the bounds defined by cellLayer.
/// </summary>
public static void RotateAndMirrorOverCPos<T>(
CellLayer<T> cellLayer,
int rotations,
WMirror wmirror,
Action<CPos[], CPos> action)
{
var size = cellLayer.Size;
for (var v = 0; v < size.Height; v++)
for (var u = 0; u < size.Width; u++)
{
var original = new MPos(u, v).ToCPos(cellLayer.GridType);
var projections = RotateAndMirrorCPos(original, cellLayer, rotations, wmirror);
action(projections, original);
}
}
}
}