446 lines
17 KiB
C#
446 lines
17 KiB
C#
using System.Globalization;
|
|
using System.Text;
|
|
using Cysharp.Text;
|
|
using X10D.Linq;
|
|
using X10D.Text;
|
|
|
|
/// <summary>
|
|
/// Represents a set of coordinates.
|
|
/// </summary>
|
|
public readonly struct Coordinates
|
|
{
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="Coordinates" /> struct.
|
|
/// </summary>
|
|
/// <param name="x">The X coordinate.</param>
|
|
/// <param name="y">The Y coordinate.</param>
|
|
/// <param name="z">The Z coordinate.</param>
|
|
/// <param name="yaw">The yaw.</param>
|
|
/// <param name="isRelative">
|
|
/// <see langword="true" /> if these coordinates represent relative coordinates; <see langword="false" /> otherwise.
|
|
/// </param>
|
|
public Coordinates(double x, double y, double z, double yaw, bool isRelative = false)
|
|
: this(null, x, y, z, yaw, isRelative)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="Coordinates" /> struct.
|
|
/// </summary>
|
|
/// <param name="world">The world name.</param>
|
|
/// <param name="x">The X coordinate.</param>
|
|
/// <param name="y">The Y coordinate.</param>
|
|
/// <param name="z">The Z coordinate.</param>
|
|
/// <param name="yaw">The yaw.</param>
|
|
/// <param name="isRelative">
|
|
/// <see langword="true" /> if these coordinates represent relative coordinates; <see langword="false" /> otherwise.
|
|
/// </param>
|
|
public Coordinates(string? world, double x, double y, double z, double yaw, bool isRelative = false)
|
|
{
|
|
World = world;
|
|
X = x;
|
|
Y = y;
|
|
Z = z;
|
|
Yaw = yaw;
|
|
IsRelative = isRelative;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or initializes a value indicating whether this instance represents relative coordinates.
|
|
/// </summary>
|
|
/// <value>
|
|
/// <see langword="true" /> if this instance represents relative coordinates; otherwise, <see langword="false" />.
|
|
/// </value>
|
|
public bool IsRelative { get; init; }
|
|
|
|
/// <summary>
|
|
/// Gets or initializes the world.
|
|
/// </summary>
|
|
/// <value>The world.</value>
|
|
public string? World { get; init; }
|
|
|
|
/// <summary>
|
|
/// Gets or initializes the X coordinate.
|
|
/// </summary>
|
|
/// <value>The X coordinate.</value>
|
|
public double X { get; init; }
|
|
|
|
/// <summary>
|
|
/// Gets or initializes the Y coordinate.
|
|
/// </summary>
|
|
/// <value>The Y coordinate.</value>
|
|
public double Y { get; init; }
|
|
|
|
/// <summary>
|
|
/// Gets or initializes the yaw.
|
|
/// </summary>
|
|
/// <value>The yaw.</value>
|
|
public double Yaw { get; init; }
|
|
|
|
/// <summary>
|
|
/// Gets or initializes the Z coordinate.
|
|
/// </summary>
|
|
/// <value>The Z coordinate.</value>
|
|
public double Z { get; init; }
|
|
|
|
public static bool operator ==(Coordinates left, Coordinates right) =>
|
|
left.Equals(right);
|
|
|
|
public static bool operator !=(Coordinates left, Coordinates right) =>
|
|
!(left == right);
|
|
|
|
/// <summary>
|
|
/// Parses a coordinate string.
|
|
/// </summary>
|
|
/// <param name="coordinates">The coordinates to parse.</param>
|
|
/// <returns>An instance of <see cref="Coordinates" />.</returns>
|
|
public static Coordinates Parse(string coordinates)
|
|
{
|
|
return Serializer.Deserialize(coordinates);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a value indicating whether this instance of <see cref="Coordinates" /> and another instance of
|
|
/// <see cref="Coordinates" /> are equal.
|
|
/// </summary>
|
|
/// <param name="other">The instance against which to compare.</param>
|
|
/// <returns>
|
|
/// <see langword="true" /> if this instance is equal to <paramref name="other" />; otherwise, <see langword="false" />.
|
|
/// </returns>
|
|
public bool Equals(Coordinates other)
|
|
{
|
|
return X.Equals(other.X) &&
|
|
Y.Equals(other.Y) &&
|
|
Z.Equals(other.Z) &&
|
|
Yaw.Equals(other.Yaw) &&
|
|
IsRelative.Equals(other.IsRelative) &&
|
|
string.Equals(World, other.World);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override bool Equals(object? obj)
|
|
{
|
|
return obj is Coordinates other && Equals(other);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override int GetHashCode()
|
|
{
|
|
return HashCode.Combine(World, X, Y, Z, Yaw);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the string representation of these coordinates.
|
|
/// </summary>
|
|
/// <returns>A <see cref="string" /> representation of these coordinates.</returns>
|
|
public override string ToString()
|
|
{
|
|
return ToString("{0}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the string representation of these coordinates.
|
|
/// </summary>
|
|
/// <param name="format">The format to apply to each component.</param>
|
|
/// <returns>A <see cref="string" /> representation of these coordinates.</returns>
|
|
public string ToString(string format)
|
|
{
|
|
return Serializer.Serialize(this, format);
|
|
}
|
|
|
|
internal static class Serializer
|
|
{
|
|
public static string Serialize(in Coordinates coordinates, string format)
|
|
{
|
|
int count = Serialize(coordinates, format, Span<char>.Empty);
|
|
Span<char> chars = stackalloc char[count];
|
|
Serialize(coordinates, format, chars);
|
|
return chars.ToString();
|
|
}
|
|
|
|
public static int Serialize(in Coordinates coordinates, string format, Span<char> destination)
|
|
{
|
|
using Utf8ValueStringBuilder builder = ZString.CreateUtf8StringBuilder();
|
|
|
|
if (!string.IsNullOrWhiteSpace(coordinates.World))
|
|
{
|
|
builder.Append(coordinates.World);
|
|
builder.Append(' ');
|
|
}
|
|
|
|
bool north = coordinates.Z >= 0.0;
|
|
bool west = coordinates.X >= 0.0;
|
|
bool up = coordinates.Y >= 0.0;
|
|
bool dir = coordinates.Yaw >= 0.0;
|
|
|
|
if (coordinates.IsRelative)
|
|
{
|
|
if (north)
|
|
{
|
|
builder.Append('+');
|
|
}
|
|
|
|
builder.Append(string.Format(CultureInfo.InvariantCulture, format, coordinates.Z));
|
|
builder.Append(' ');
|
|
|
|
if (west)
|
|
{
|
|
builder.Append('+');
|
|
}
|
|
|
|
builder.Append(string.Format(CultureInfo.InvariantCulture, format, coordinates.X));
|
|
builder.Append(' ');
|
|
|
|
if (up)
|
|
{
|
|
builder.Append('+');
|
|
}
|
|
|
|
builder.Append(string.Format(CultureInfo.InvariantCulture, format, coordinates.Y));
|
|
builder.Append("a ");
|
|
|
|
if (up)
|
|
{
|
|
builder.Append('+');
|
|
}
|
|
|
|
builder.Append(string.Format(CultureInfo.InvariantCulture, format, coordinates.Yaw));
|
|
}
|
|
else
|
|
{
|
|
char zChar = north ? 'n' : 's';
|
|
char xChar = west ? 'w' : 'e';
|
|
|
|
builder.Append(string.Format(CultureInfo.InvariantCulture, format, Math.Abs(coordinates.Z)));
|
|
builder.Append(zChar);
|
|
builder.Append(' ');
|
|
|
|
builder.Append(string.Format(CultureInfo.InvariantCulture, format, Math.Abs(coordinates.X)));
|
|
builder.Append(xChar);
|
|
builder.Append(' ');
|
|
|
|
builder.Append(string.Format(CultureInfo.InvariantCulture, format, coordinates.Y));
|
|
builder.Append("a ");
|
|
|
|
builder.Append(string.Format(CultureInfo.InvariantCulture, format, coordinates.Yaw));
|
|
}
|
|
|
|
ReadOnlySpan<byte> bytes = builder.AsSpan();
|
|
Span<char> chars = stackalloc char[bytes.Length];
|
|
Encoding.UTF8.GetChars(bytes, chars);
|
|
|
|
for (var index = 0; index < destination.Length; index++)
|
|
{
|
|
destination[index] = chars[index];
|
|
}
|
|
|
|
return builder.Length;
|
|
}
|
|
|
|
public static Coordinates Deserialize(ReadOnlySpan<char> value)
|
|
{
|
|
using Utf8ValueStringBuilder builder = ZString.CreateUtf8StringBuilder();
|
|
string? world = null;
|
|
var isRelative = false;
|
|
double x = 0.0, y = 0.0, z = 0.0, yaw = 0.0;
|
|
|
|
var word = 0;
|
|
for (var index = 0; index < value.Length; index++)
|
|
{
|
|
char current = value[index];
|
|
bool atEnd = index == value.Length - 1;
|
|
|
|
if (atEnd || char.IsWhiteSpace(current))
|
|
{
|
|
if (!builder.AsSpan().All(b => char.IsWhiteSpace((char)b)))
|
|
{
|
|
if (atEnd)
|
|
{
|
|
builder.Append(current);
|
|
}
|
|
|
|
ProcessBuffer();
|
|
word++;
|
|
}
|
|
|
|
builder.Clear();
|
|
}
|
|
else
|
|
{
|
|
builder.Append(current);
|
|
}
|
|
}
|
|
|
|
return new Coordinates(world, x, y, z, yaw, isRelative);
|
|
|
|
void ProcessBuffer()
|
|
{
|
|
ReadOnlySpan<byte> bytes = builder.AsSpan();
|
|
Span<char> chars = stackalloc char[bytes.Length];
|
|
Encoding.UTF8.GetChars(bytes, chars);
|
|
bool hasWorld = !string.IsNullOrWhiteSpace(world);
|
|
|
|
if (word == 0 && !IsUnitString(bytes))
|
|
{
|
|
world = chars.ToString().AsNullIfWhiteSpace();
|
|
}
|
|
else if (IsRelativeUnit(bytes))
|
|
{
|
|
isRelative = true;
|
|
|
|
switch (word)
|
|
{
|
|
case 0 when !hasWorld:
|
|
case 1 when hasWorld:
|
|
double.TryParse(chars, NumberStyles.Float, CultureInfo.InvariantCulture, out z);
|
|
break;
|
|
case 1 when !hasWorld:
|
|
case 2 when hasWorld:
|
|
double.TryParse(chars, NumberStyles.Float, CultureInfo.InvariantCulture, out x);
|
|
break;
|
|
case 2 when !hasWorld:
|
|
case 3 when hasWorld:
|
|
double.TryParse(chars, NumberStyles.Float, CultureInfo.InvariantCulture, out y);
|
|
break;
|
|
case 3 when !hasWorld:
|
|
case 4 when hasWorld:
|
|
double.TryParse(chars, NumberStyles.Float, CultureInfo.InvariantCulture, out yaw);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (((!hasWorld && word == 1) || (hasWorld && word == 2)) && chars[^1] is 'x' or 'X' or 'w' or 'W')
|
|
{
|
|
_ = double.TryParse(chars[..^1], NumberStyles.Float, CultureInfo.InvariantCulture, out x);
|
|
}
|
|
else if (((!hasWorld && word == 0) || (hasWorld && word == 1)) && chars[^1] is 'z' or 'Z' or 'n' or 'N')
|
|
{
|
|
_ = double.TryParse(chars[..^1], NumberStyles.Float, CultureInfo.InvariantCulture, out z);
|
|
}
|
|
else if (((!hasWorld && word == 1) || (hasWorld && word == 2)) && chars[^1] is 'e' or 'E')
|
|
{
|
|
_ = double.TryParse(chars[..^1], NumberStyles.Float, CultureInfo.InvariantCulture, out x);
|
|
x = -x;
|
|
}
|
|
else if (((!hasWorld && word == 0) || (hasWorld && word == 1)) && chars[^1] is 's' or 'S')
|
|
{
|
|
_ = double.TryParse(chars[..^1], NumberStyles.Float, CultureInfo.InvariantCulture, out z);
|
|
z = -z;
|
|
}
|
|
else if (((!hasWorld && word == 2) || (hasWorld && word == 3)) && chars[^1] is 'a' or 'A')
|
|
{
|
|
_ = double.TryParse(chars[..^1], NumberStyles.Float, CultureInfo.InvariantCulture, out y);
|
|
}
|
|
else if (((!hasWorld && word == 3) || (hasWorld && word == 4)) && double.TryParse(chars,
|
|
NumberStyles.Float, CultureInfo.InvariantCulture, out double temp))
|
|
{
|
|
yaw = temp;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a value indicating whether the specified span of characters represents a relative unit string.
|
|
/// </summary>
|
|
/// <param name="bytes">The span of characters to validate.</param>
|
|
/// <returns>
|
|
/// <see langword="true" /> if <paramref name="bytes" /> represents a valid relative unit string; otherwise,
|
|
/// <see langword="false" />.
|
|
/// </returns>
|
|
public static bool IsAbsoluteUnit(ReadOnlySpan<byte> bytes)
|
|
{
|
|
Span<char> chars = stackalloc char[bytes.Length];
|
|
Encoding.UTF8.GetChars(bytes, chars);
|
|
return IsRelativeUnit(chars);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a value indicating whether the specified span of characters represents a relative unit string.
|
|
/// </summary>
|
|
/// <param name="chars">The span of characters to validate.</param>
|
|
/// <returns>
|
|
/// <see langword="true" /> if <paramref name="chars" /> represents a valid relative unit string; otherwise,
|
|
/// <see langword="false" />.
|
|
/// </returns>
|
|
public static bool IsAbsoluteUnit(ReadOnlySpan<char> chars)
|
|
{
|
|
ReadOnlySpan<char> validChars = "nNeEwWsSaA";
|
|
return double.TryParse(chars, out _) ||
|
|
(validChars.Contains(chars[^1]) &&
|
|
double.TryParse(chars[..^1], NumberStyles.Float, CultureInfo.InvariantCulture, out _));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a value indicating whether the specified span of characters represents a relative unit string.
|
|
/// </summary>
|
|
/// <param name="bytes">The span of characters to validate.</param>
|
|
/// <returns>
|
|
/// <see langword="true" /> if <paramref name="bytes" /> represents a valid relative unit string; otherwise,
|
|
/// <see langword="false" />.
|
|
/// </returns>
|
|
public static bool IsRelativeUnit(ReadOnlySpan<byte> bytes)
|
|
{
|
|
Span<char> chars = stackalloc char[bytes.Length];
|
|
Encoding.UTF8.GetChars(bytes, chars);
|
|
return IsRelativeUnit(chars);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a value indicating whether the specified span of characters represents a relative unit string.
|
|
/// </summary>
|
|
/// <param name="chars">The span of characters to validate.</param>
|
|
/// <returns>
|
|
/// <see langword="true" /> if <paramref name="chars" /> represents a valid relative unit string; otherwise,
|
|
/// <see langword="false" />.
|
|
/// </returns>
|
|
public static bool IsRelativeUnit(ReadOnlySpan<char> chars)
|
|
{
|
|
return (chars[0] == '+' || chars[0] == '-') &&
|
|
double.TryParse(chars, NumberStyles.Float, CultureInfo.InvariantCulture, out _);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a value indicating whether the specified span of characters represents a valid coordinate unit string.
|
|
/// </summary>
|
|
/// <param name="bytes">The span of characters to validate.</param>
|
|
/// <returns>
|
|
/// <see langword="true" /> if <paramref name="bytes" /> represents a valid coordinate unit string; otherwise,
|
|
/// <see langword="false" />.
|
|
/// </returns>
|
|
public static bool IsUnitString(ReadOnlySpan<byte> bytes)
|
|
{
|
|
Span<char> chars = stackalloc char[bytes.Length];
|
|
Encoding.UTF8.GetChars(bytes, chars);
|
|
return IsUnitString(chars);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a value indicating whether the specified span of characters represents a valid coordinate unit string.
|
|
/// </summary>
|
|
/// <param name="chars">The span of characters to validate.</param>
|
|
/// <returns>
|
|
/// <see langword="true" /> if <paramref name="chars" /> represents a valid coordinate unit string; otherwise,
|
|
/// <see langword="false" />.
|
|
/// </returns>
|
|
public static bool IsUnitString(ReadOnlySpan<char> chars)
|
|
{
|
|
chars = chars.Trim();
|
|
|
|
if (chars.Length == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!char.IsDigit(chars[0]) && chars[0] != '+' && chars[0] != '-')
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// thicc char span
|
|
return IsRelativeUnit(chars) || IsAbsoluteUnit(chars);
|
|
}
|
|
}
|
|
}
|