using System.Globalization;
using System.Text;
using Cysharp.Text;
using X10D.Linq;
using X10D.Text;
///
/// Represents a set of coordinates.
///
public readonly struct Coordinates
{
///
/// Initializes a new instance of the struct.
///
/// The X coordinate.
/// The Y coordinate.
/// The Z coordinate.
/// The yaw.
///
/// if these coordinates represent relative coordinates; otherwise.
///
public Coordinates(double x, double y, double z, double yaw, bool isRelative = false)
: this(null, x, y, z, yaw, isRelative)
{
}
///
/// Initializes a new instance of the struct.
///
/// The world name.
/// The X coordinate.
/// The Y coordinate.
/// The Z coordinate.
/// The yaw.
///
/// if these coordinates represent relative coordinates; otherwise.
///
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;
}
///
/// Gets or initializes a value indicating whether this instance represents relative coordinates.
///
///
/// if this instance represents relative coordinates; otherwise, .
///
public bool IsRelative { get; init; }
///
/// Gets or initializes the world.
///
/// The world.
public string? World { get; init; }
///
/// Gets or initializes the X coordinate.
///
/// The X coordinate.
public double X { get; init; }
///
/// Gets or initializes the Y coordinate.
///
/// The Y coordinate.
public double Y { get; init; }
///
/// Gets or initializes the yaw.
///
/// The yaw.
public double Yaw { get; init; }
///
/// Gets or initializes the Z coordinate.
///
/// The Z coordinate.
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);
///
/// Parses a coordinate string.
///
/// The coordinates to parse.
/// An instance of .
public static Coordinates Parse(string coordinates)
{
return Serializer.Deserialize(coordinates);
}
///
/// Returns a value indicating whether this instance of and another instance of
/// are equal.
///
/// The instance against which to compare.
///
/// if this instance is equal to ; otherwise, .
///
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);
}
///
public override bool Equals(object? obj)
{
return obj is Coordinates other && Equals(other);
}
///
public override int GetHashCode()
{
return HashCode.Combine(World, X, Y, Z, Yaw);
}
///
/// Returns the string representation of these coordinates.
///
/// A representation of these coordinates.
public override string ToString()
{
return ToString("{0}");
}
///
/// Returns the string representation of these coordinates.
///
/// The format to apply to each component.
/// A representation of these coordinates.
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.Empty);
Span chars = stackalloc char[count];
Serialize(coordinates, format, chars);
return chars.ToString();
}
public static int Serialize(in Coordinates coordinates, string format, Span 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 bytes = builder.AsSpan();
Span 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 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 bytes = builder.AsSpan();
Span 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;
}
}
}
}
///
/// Returns a value indicating whether the specified span of characters represents a relative unit string.
///
/// The span of characters to validate.
///
/// if represents a valid relative unit string; otherwise,
/// .
///
public static bool IsAbsoluteUnit(ReadOnlySpan bytes)
{
Span chars = stackalloc char[bytes.Length];
Encoding.UTF8.GetChars(bytes, chars);
return IsRelativeUnit(chars);
}
///
/// Returns a value indicating whether the specified span of characters represents a relative unit string.
///
/// The span of characters to validate.
///
/// if represents a valid relative unit string; otherwise,
/// .
///
public static bool IsAbsoluteUnit(ReadOnlySpan chars)
{
ReadOnlySpan validChars = "nNeEwWsSaA";
return double.TryParse(chars, out _) ||
(validChars.Contains(chars[^1]) &&
double.TryParse(chars[..^1], NumberStyles.Float, CultureInfo.InvariantCulture, out _));
}
///
/// Returns a value indicating whether the specified span of characters represents a relative unit string.
///
/// The span of characters to validate.
///
/// if represents a valid relative unit string; otherwise,
/// .
///
public static bool IsRelativeUnit(ReadOnlySpan bytes)
{
Span chars = stackalloc char[bytes.Length];
Encoding.UTF8.GetChars(bytes, chars);
return IsRelativeUnit(chars);
}
///
/// Returns a value indicating whether the specified span of characters represents a relative unit string.
///
/// The span of characters to validate.
///
/// if represents a valid relative unit string; otherwise,
/// .
///
public static bool IsRelativeUnit(ReadOnlySpan chars)
{
return (chars[0] == '+' || chars[0] == '-') &&
double.TryParse(chars, NumberStyles.Float, CultureInfo.InvariantCulture, out _);
}
///
/// Returns a value indicating whether the specified span of characters represents a valid coordinate unit string.
///
/// The span of characters to validate.
///
/// if represents a valid coordinate unit string; otherwise,
/// .
///
public static bool IsUnitString(ReadOnlySpan bytes)
{
Span chars = stackalloc char[bytes.Length];
Encoding.UTF8.GetChars(bytes, chars);
return IsUnitString(chars);
}
///
/// Returns a value indicating whether the specified span of characters represents a valid coordinate unit string.
///
/// The span of characters to validate.
///
/// if represents a valid coordinate unit string; otherwise,
/// .
///
public static bool IsUnitString(ReadOnlySpan 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);
}
}
}