2026-06-15 16:51:17 +01:00
|
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
|
|
|
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
2026-06-21 09:37:54 +01:00
|
|
|
using cfloat2 = CultMath.float2;
|
|
|
|
|
using cfloat3 = CultMath.float3;
|
2026-06-15 16:51:17 +01:00
|
|
|
using int2 = Unity.Mathematics.int2;
|
2026-06-21 09:37:54 +01:00
|
|
|
using unityfloat2 = Unity.Mathematics.float2;
|
2026-06-15 16:51:17 +01:00
|
|
|
|
|
|
|
|
public class Shape
|
|
|
|
|
{
|
|
|
|
|
public bool[,] Cells;
|
|
|
|
|
|
|
|
|
|
private bool _dirty = true;
|
|
|
|
|
|
|
|
|
|
public Shape()
|
|
|
|
|
{
|
|
|
|
|
Cells = new bool[1, 1];
|
|
|
|
|
Cells[0, 0] = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Shape(int width, int height)
|
|
|
|
|
{
|
|
|
|
|
Cells = new bool[width, height];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int Width
|
|
|
|
|
{
|
|
|
|
|
get { return Cells.GetLength(0); }
|
|
|
|
|
set { Resize(value, Height); }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int Height
|
|
|
|
|
{
|
|
|
|
|
get { return Cells.GetLength(1); }
|
|
|
|
|
set { Resize(Width, value); }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Resize(int width, int height)
|
|
|
|
|
{
|
|
|
|
|
var newCells = new bool[width, height];
|
|
|
|
|
|
|
|
|
|
for (var x = 0; x < width; x++)
|
|
|
|
|
{
|
|
|
|
|
for (var y = 0; y < height; y++)
|
|
|
|
|
{
|
|
|
|
|
newCells[x, y] = x >= Width || y >= Height || Cells[x, y];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Cells = newCells;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int2 Rotate(int2 position, ItemRotation rotation)
|
|
|
|
|
{
|
|
|
|
|
return rotation switch
|
|
|
|
|
{
|
2026-06-21 09:37:54 +01:00
|
|
|
ItemRotation.Clockwise => new int2(position.y, Width - 1 - position.x),
|
|
|
|
|
ItemRotation.Reversed => new int2(Width - 1 - position.x, Height - 1 - position.y),
|
|
|
|
|
ItemRotation.CounterClockwise => new int2(Height - 1 - position.y, position.x),
|
|
|
|
|
_ => new int2(position.x, position.y)
|
2026-06-15 16:51:17 +01:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private int2[] _cachedShapeCoordinates;
|
|
|
|
|
|
|
|
|
|
public int2[] Coordinates
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
if (_dirty)
|
|
|
|
|
{
|
|
|
|
|
_cachedShapeCoordinates = EnumerateShapeCoordinates().ToArray();
|
|
|
|
|
_dirty = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return _cachedShapeCoordinates;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private IEnumerable<int2> EnumerateShapeCoordinates()
|
|
|
|
|
{
|
|
|
|
|
for (var y = 0; y < Height; y++)
|
|
|
|
|
{
|
|
|
|
|
for (var x = 0; x < Width; x++)
|
|
|
|
|
{
|
|
|
|
|
if (Cells[x, y])
|
2026-06-21 09:37:54 +01:00
|
|
|
yield return new int2(x, y);
|
2026-06-15 16:51:17 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private int2[] _cachedAllShapeCoordinates;
|
|
|
|
|
|
|
|
|
|
public int2[] AllCoordinates => _cachedAllShapeCoordinates ??= EnumerateAllShapeCoordinates().ToArray();
|
|
|
|
|
|
|
|
|
|
private IEnumerable<int2> EnumerateAllShapeCoordinates()
|
|
|
|
|
{
|
|
|
|
|
for (var y = 0; y < Height; y++)
|
|
|
|
|
{
|
|
|
|
|
for (var x = 0; x < Width; x++)
|
|
|
|
|
{
|
2026-06-21 09:37:54 +01:00
|
|
|
yield return new int2(x, y);
|
2026-06-15 16:51:17 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-21 09:37:54 +01:00
|
|
|
private cfloat2? _centerOfMass;
|
2026-06-15 16:51:17 +01:00
|
|
|
|
2026-06-21 09:37:54 +01:00
|
|
|
public cfloat2 CenterOfMass => _centerOfMass ??= Coordinates
|
|
|
|
|
.Aggregate(new cfloat2(0, 0), (total, coord) => total + new cfloat2(coord.x, coord.y)) / Coordinates.Length;
|
2026-06-15 16:51:17 +01:00
|
|
|
|
2026-06-21 09:37:54 +01:00
|
|
|
public bool GetCell(int x, int y)
|
2026-06-15 16:51:17 +01:00
|
|
|
{
|
2026-06-21 09:37:54 +01:00
|
|
|
return x >= 0 && y >= 0 && x < Width && y < Height && Cells[x, y];
|
|
|
|
|
}
|
2026-06-15 16:51:17 +01:00
|
|
|
|
2026-06-21 09:37:54 +01:00
|
|
|
public void SetCell(int x, int y, bool value)
|
|
|
|
|
{
|
|
|
|
|
if (x < 0 || y < 0 || x >= Width || y >= Height)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
_dirty = true;
|
|
|
|
|
_centerOfMass = null;
|
|
|
|
|
Cells[x, y] = value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool this[int2 pos]
|
|
|
|
|
{
|
|
|
|
|
get { return GetCell(pos.x, pos.y); }
|
|
|
|
|
set { SetCell(pos.x, pos.y, value); }
|
2026-06-15 16:51:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Shape Shrink()
|
|
|
|
|
{
|
|
|
|
|
var shape = new Shape(Width, Height);
|
|
|
|
|
foreach (var shapeCoord in Coordinates)
|
|
|
|
|
{
|
|
|
|
|
shape[shapeCoord] = (
|
2026-06-21 09:37:54 +01:00
|
|
|
this[shapeCoord + new int2(-1, -1)] && this[shapeCoord + new int2(0, -1)] && this[shapeCoord + new int2(1, -1)] &&
|
|
|
|
|
this[shapeCoord + new int2(-1, 0)] && this[shapeCoord + new int2(1, 0)] &&
|
|
|
|
|
this[shapeCoord + new int2(-1, 1)] && this[shapeCoord + new int2(0, 1)] && this[shapeCoord + new int2(1, 1)]
|
2026-06-15 16:51:17 +01:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return shape;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Shape Inset(Shape inset, int2 insetPosition, ItemRotation rotation = ItemRotation.None)
|
|
|
|
|
{
|
2026-06-21 09:37:54 +01:00
|
|
|
var shape = new Shape(Math.Max(Width, insetPosition.x + inset.Width - 1), Math.Max(Height, insetPosition.y + inset.Height - 1));
|
2026-06-15 16:51:17 +01:00
|
|
|
foreach (var v in inset.Coordinates)
|
|
|
|
|
{
|
|
|
|
|
var insetCoord = inset.Rotate(v, rotation) + insetPosition;
|
|
|
|
|
shape[insetCoord] = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return shape;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Shape Expand()
|
|
|
|
|
{
|
|
|
|
|
var shape = new Shape(Width, Height);
|
|
|
|
|
foreach (var shapeCoord in AllCoordinates)
|
|
|
|
|
{
|
|
|
|
|
shape[shapeCoord] = (
|
2026-06-21 09:37:54 +01:00
|
|
|
this[shapeCoord + new int2(-1, -1)] || this[shapeCoord + new int2(0, -1)] || this[shapeCoord + new int2(1, -1)] ||
|
|
|
|
|
this[shapeCoord + new int2(-1, 0)] || this[shapeCoord] || this[shapeCoord + new int2(1, 0)] ||
|
|
|
|
|
this[shapeCoord + new int2(-1, 1)] || this[shapeCoord + new int2(0, 1)] || this[shapeCoord + new int2(1, 1)]
|
2026-06-15 16:51:17 +01:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return shape;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool FitsWithin(Shape other, out ItemRotation rotation, out int2 position)
|
|
|
|
|
{
|
|
|
|
|
foreach (var rot in (ItemRotation[])Enum.GetValues(typeof(ItemRotation)))
|
|
|
|
|
{
|
|
|
|
|
rotation = rot;
|
|
|
|
|
if (FitsWithin(other, rot, out var pos))
|
|
|
|
|
{
|
|
|
|
|
position = pos;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rotation = ItemRotation.None;
|
|
|
|
|
position = int2.zero;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool FitsWithin(Shape other, ItemRotation rotation, out int2 position)
|
|
|
|
|
{
|
|
|
|
|
var width = rotation == ItemRotation.Clockwise || rotation == ItemRotation.CounterClockwise ? Height : Width;
|
|
|
|
|
var height = rotation == ItemRotation.Clockwise || rotation == ItemRotation.CounterClockwise ? Width : Height;
|
|
|
|
|
|
|
|
|
|
for (var x = 0; x < other.Width - width + 1; x++)
|
|
|
|
|
{
|
|
|
|
|
for (var y = 0; y < other.Height - height + 1; y++)
|
|
|
|
|
{
|
2026-06-21 09:37:54 +01:00
|
|
|
position = new int2(x, y);
|
2026-06-15 16:51:17 +01:00
|
|
|
var fits = true;
|
|
|
|
|
foreach (var v in Coordinates)
|
|
|
|
|
{
|
|
|
|
|
fits = fits && other[Rotate(v, rotation) + position];
|
|
|
|
|
if (!fits)
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (fits)
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
position = int2.zero;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-21 09:37:54 +01:00
|
|
|
public void SetLine(unityfloat2 a, unityfloat2 b) =>
|
|
|
|
|
SetLine(AetheriaMath.ToCult(a), AetheriaMath.ToCult(b));
|
|
|
|
|
|
|
|
|
|
public void SetLine(cfloat2 a, cfloat2 b)
|
2026-06-15 16:51:17 +01:00
|
|
|
{
|
|
|
|
|
if (a.Equals(b))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
var steep = Math.Abs(b.y - a.y) > Math.Abs(b.x - a.x);
|
|
|
|
|
if (steep)
|
|
|
|
|
{
|
2026-06-21 09:37:54 +01:00
|
|
|
a = new cfloat2(a.y, a.x);
|
|
|
|
|
b = new cfloat2(b.y, b.x);
|
2026-06-15 16:51:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (a.x > b.x)
|
|
|
|
|
{
|
|
|
|
|
var temp = a;
|
|
|
|
|
a = b;
|
|
|
|
|
b = temp;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var dx = b.x - a.x;
|
|
|
|
|
var dy = b.y - a.y;
|
|
|
|
|
var derr = Math.Abs(dy / dx);
|
|
|
|
|
|
|
|
|
|
var y = (int)Math.Round(a.y);
|
|
|
|
|
var xStart = (int)Math.Round(a.x);
|
|
|
|
|
var error = (a.y - y) + (xStart - a.x) * derr;
|
|
|
|
|
|
|
|
|
|
for (var x = xStart; x <= (int)Math.Round(b.x); x++)
|
|
|
|
|
{
|
|
|
|
|
if (x >= 0 && x < Width && y >= 0 && y < Height)
|
|
|
|
|
{
|
|
|
|
|
Cells[steep ? y : x, steep ? x : y] = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
error += derr;
|
|
|
|
|
if (error >= 0.5f)
|
|
|
|
|
{
|
|
|
|
|
y += Math.Sign(dy);
|
|
|
|
|
error -= 1f;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class HardpointData : ITintInspector
|
|
|
|
|
{
|
2026-06-16 19:38:02 +01:00
|
|
|
public HardpointType Type;
|
|
|
|
|
public int2 Position;
|
|
|
|
|
public Shape Shape = new Shape();
|
|
|
|
|
public string Transform;
|
|
|
|
|
public ItemRotation Rotation;
|
|
|
|
|
public float Armor;
|
2026-06-15 16:51:17 +01:00
|
|
|
|
|
|
|
|
public override string ToString()
|
|
|
|
|
{
|
|
|
|
|
return $"{Enum.GetName(typeof(HardpointType), Type)} Hardpoint {Rotation.Arrow()}";
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-21 09:37:54 +01:00
|
|
|
public cfloat3 TintColor
|
2026-06-15 16:51:17 +01:00
|
|
|
{
|
|
|
|
|
get { return GetColor(Type); }
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-21 09:37:54 +01:00
|
|
|
public static cfloat3 GetColor(HardpointType type)
|
2026-06-15 16:51:17 +01:00
|
|
|
{
|
|
|
|
|
if (_tintColors == null)
|
|
|
|
|
{
|
|
|
|
|
var hardpointTypes = (HardpointType[])Enum.GetValues(typeof(HardpointType));
|
|
|
|
|
_tintColors = hardpointTypes.ToDictionary(
|
|
|
|
|
x => x,
|
2026-06-21 09:37:54 +01:00
|
|
|
x => ColorMath.HsvToRgb(new cfloat3(Fraction((float)(int)x / hardpointTypes.Length + .25f), 1, 1)));
|
2026-06-15 16:51:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return _tintColors.TryGetValue(type, out var color) ? color : _tintColors[HardpointType.Hull];
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-21 09:37:54 +01:00
|
|
|
private static float Fraction(float value)
|
2026-06-15 16:51:17 +01:00
|
|
|
{
|
2026-06-21 09:37:54 +01:00
|
|
|
return value - MathF.Floor(value);
|
2026-06-15 16:51:17 +01:00
|
|
|
}
|
|
|
|
|
|
2026-06-21 09:37:54 +01:00
|
|
|
private static Dictionary<HardpointType, cfloat3> _tintColors;
|
2026-06-15 16:51:17 +01:00
|
|
|
}
|