using System; using System.Text.Json.Serialization; using MessagePack; namespace GameCult.Geometry { /// /// Deterministic two-dimensional float vector for cross-runtime geometry and query contracts. /// [MessagePackObject] public readonly struct CultVec2 : IEquatable { /// Creates a two-dimensional vector. public CultVec2(float x, float y) { X = x; Y = y; } /// Gets the x component. [Key(0)] public float X { get; } /// Gets the y component. [Key(1)] public float Y { get; } /// Gets the zero vector. [IgnoreMember] [JsonIgnore] public static CultVec2 Zero => new(0f, 0f); /// Gets the squared vector length. [IgnoreMember] [JsonIgnore] public float LengthSquared => (X * X) + (Y * Y); /// Returns whether this vector is equal to another vector. public bool Equals(CultVec2 other) => X.Equals(other.X) && Y.Equals(other.Y); /// public override bool Equals(object? obj) => obj is CultVec2 other && Equals(other); /// public override int GetHashCode() => HashCode.Combine(X, Y); /// public override string ToString() => $"({X}, {Y})"; public static CultVec2 operator +(CultVec2 left, CultVec2 right) => new(left.X + right.X, left.Y + right.Y); public static CultVec2 operator -(CultVec2 left, CultVec2 right) => new(left.X - right.X, left.Y - right.Y); public static CultVec2 operator *(CultVec2 value, float scalar) => new(value.X * scalar, value.Y * scalar); public static CultVec2 operator /(CultVec2 value, float scalar) => new(value.X / scalar, value.Y / scalar); public static bool operator ==(CultVec2 left, CultVec2 right) => left.Equals(right); public static bool operator !=(CultVec2 left, CultVec2 right) => !left.Equals(right); } /// /// Deterministic three-dimensional float vector for cross-runtime geometry and query contracts. /// [MessagePackObject] public readonly struct CultVec3 : IEquatable { /// Creates a three-dimensional vector. public CultVec3(float x, float y, float z) { X = x; Y = y; Z = z; } /// Gets the x component. [Key(0)] public float X { get; } /// Gets the y component. [Key(1)] public float Y { get; } /// Gets the z component. [Key(2)] public float Z { get; } /// Gets the zero vector. [IgnoreMember] [JsonIgnore] public static CultVec3 Zero => new(0f, 0f, 0f); /// Gets the XY plane projection. [IgnoreMember] [JsonIgnore] public CultVec2 Xy => new(X, Y); /// Gets the XZ plane projection for Unity compatibility adapters. [IgnoreMember] [JsonIgnore] public CultVec2 Xz => new(X, Z); /// Gets the squared vector length. [IgnoreMember] [JsonIgnore] public float LengthSquared => (X * X) + (Y * Y) + (Z * Z); /// Returns whether this vector is equal to another vector. public bool Equals(CultVec3 other) => X.Equals(other.X) && Y.Equals(other.Y) && Z.Equals(other.Z); /// public override bool Equals(object? obj) => obj is CultVec3 other && Equals(other); /// public override int GetHashCode() => HashCode.Combine(X, Y, Z); /// public override string ToString() => $"({X}, {Y}, {Z})"; public static CultVec3 operator +(CultVec3 left, CultVec3 right) => new(left.X + right.X, left.Y + right.Y, left.Z + right.Z); public static CultVec3 operator -(CultVec3 left, CultVec3 right) => new(left.X - right.X, left.Y - right.Y, left.Z - right.Z); public static CultVec3 operator *(CultVec3 value, float scalar) => new(value.X * scalar, value.Y * scalar, value.Z * scalar); public static CultVec3 operator /(CultVec3 value, float scalar) => new(value.X / scalar, value.Y / scalar, value.Z / scalar); public static bool operator ==(CultVec3 left, CultVec3 right) => left.Equals(right); public static bool operator !=(CultVec3 left, CultVec3 right) => !left.Equals(right); } /// /// Axis-aligned two-dimensional rectangle represented by canonical min and max corners. /// [MessagePackObject] public readonly struct CultRect : IEquatable { /// Creates a rectangle and canonicalizes min/max corners. public CultRect(CultVec2 min, CultVec2 max) { Min = new CultVec2(Math.Min(min.X, max.X), Math.Min(min.Y, max.Y)); Max = new CultVec2(Math.Max(min.X, max.X), Math.Max(min.Y, max.Y)); } /// Creates a rectangle and canonicalizes min/max corners. public CultRect(float minX, float minY, float maxX, float maxY) : this(new CultVec2(minX, minY), new CultVec2(maxX, maxY)) { } /// Gets the canonical minimum corner. [Key(0)] public CultVec2 Min { get; } /// Gets the canonical maximum corner. [Key(1)] public CultVec2 Max { get; } /// Gets the rectangle size. [IgnoreMember] [JsonIgnore] public CultVec2 Size => Max - Min; /// Gets the rectangle center. [IgnoreMember] [JsonIgnore] public CultVec2 Center => (Min + Max) * 0.5f; /// Returns whether the point lies inside or on the rectangle edge. public bool Contains(CultVec2 point) { return point.X >= Min.X && point.X <= Max.X && point.Y >= Min.Y && point.Y <= Max.Y; } /// Returns whether this rectangle intersects another rectangle. public bool Intersects(CultRect other) { return Min.X <= other.Max.X && Max.X >= other.Min.X && Min.Y <= other.Max.Y && Max.Y >= other.Min.Y; } /// Returns whether this rectangle is equal to another rectangle. public bool Equals(CultRect other) => Min == other.Min && Max == other.Max; /// public override bool Equals(object? obj) => obj is CultRect other && Equals(other); /// public override int GetHashCode() => HashCode.Combine(Min, Max); /// public override string ToString() => $"[{Min}..{Max}]"; public static bool operator ==(CultRect left, CultRect right) => left.Equals(right); public static bool operator !=(CultRect left, CultRect right) => !left.Equals(right); } /// /// Two-dimensional circle for spatial queries. /// [MessagePackObject] public readonly struct CultCircle : IEquatable { /// Creates a circle. public CultCircle(CultVec2 center, float radius) { if (radius < 0f) { throw new ArgumentOutOfRangeException(nameof(radius), "Radius cannot be negative."); } Center = center; Radius = radius; } /// Gets the circle center. [Key(0)] public CultVec2 Center { get; } /// Gets the circle radius. [Key(1)] public float Radius { get; } /// Gets the circle bounds. [IgnoreMember] [JsonIgnore] public CultRect Bounds => new( Center.X - Radius, Center.Y - Radius, Center.X + Radius, Center.Y + Radius); /// Returns whether the point lies inside or on the circle edge. public bool Contains(CultVec2 point) { var delta = point - Center; return delta.LengthSquared <= Radius * Radius; } /// Returns whether this circle intersects a rectangle. public bool Intersects(CultRect rect) { var clampedX = Math.Max(rect.Min.X, Math.Min(Center.X, rect.Max.X)); var clampedY = Math.Max(rect.Min.Y, Math.Min(Center.Y, rect.Max.Y)); return Contains(new CultVec2(clampedX, clampedY)); } /// Returns whether this circle is equal to another circle. public bool Equals(CultCircle other) => Center == other.Center && Radius.Equals(other.Radius); /// public override bool Equals(object? obj) => obj is CultCircle other && Equals(other); /// public override int GetHashCode() => HashCode.Combine(Center, Radius); public static bool operator ==(CultCircle left, CultCircle right) => left.Equals(right); public static bool operator !=(CultCircle left, CultCircle right) => !left.Equals(right); } /// /// Three-dimensional sphere for physics and spatial queries. /// [MessagePackObject] public readonly struct CultSphere : IEquatable { /// Creates a sphere. public CultSphere(CultVec3 center, float radius) { if (radius < 0f) { throw new ArgumentOutOfRangeException(nameof(radius), "Radius cannot be negative."); } Center = center; Radius = radius; } /// Gets the sphere center. [Key(0)] public CultVec3 Center { get; } /// Gets the sphere radius. [Key(1)] public float Radius { get; } /// Gets the XY circle projection. [IgnoreMember] [JsonIgnore] public CultCircle XyCircle => new(Center.Xy, Radius); /// Returns whether the point lies inside or on the sphere surface. public bool Contains(CultVec3 point) { var delta = point - Center; return delta.LengthSquared <= Radius * Radius; } /// Returns whether this sphere is equal to another sphere. public bool Equals(CultSphere other) => Center == other.Center && Radius.Equals(other.Radius); /// public override bool Equals(object? obj) => obj is CultSphere other && Equals(other); /// public override int GetHashCode() => HashCode.Combine(Center, Radius); public static bool operator ==(CultSphere left, CultSphere right) => left.Equals(right); public static bool operator !=(CultSphere left, CultSphere right) => !left.Equals(right); } }