diff --git a/Engine.Core/Primitives/Line3D.cs b/Engine.Core/Primitives/Line3D.cs new file mode 100644 index 0000000..b56890d --- /dev/null +++ b/Engine.Core/Primitives/Line3D.cs @@ -0,0 +1,114 @@ +using System; + +namespace Engine.Core; + +/// +/// Represents a 3D line segment defined by two endpoints. +/// +/// The starting point of the segment. +/// The ending point of the segment. +/// +/// Initializes a new instance of the struct with the specified endpoints. +/// +[System.Diagnostics.DebuggerDisplay("From: {From.ToString(),nq}, To: {To.ToString(),nq}, Direction: {Direction.ToString(),nq}, Length: {Length}")] +public readonly struct Line3D(Vector3D from, Vector3D to) : IEquatable +{ + /// + /// The starting point of the segment. + /// + public readonly Vector3D From = from; + + /// + /// The ending point of the segment. + /// + public readonly Vector3D To = to; + + /// + /// The reversed segment. + /// + public readonly Line3D Reversed => new(To, From); + + /// + /// The normalized direction of the segment. + /// + public readonly Vector3D Direction => From.FromTo(To).Normalize(); + + /// + /// The length of the segment. + /// + public readonly float Length => From.FromTo(To).Length(); + + /// + /// The squared length of the segment. + /// + public readonly float LengthSquared => From.FromTo(To).LengthSquared(); + + public static bool operator ==(Line3D left, Line3D right) => left.From == right.From && left.To == right.To; + public static bool operator !=(Line3D left, Line3D right) => left.From != right.From || left.To != right.To; + + /// + /// Linearly interpolates between the two endpoints of the segment using parameter 't'. + /// + public static Vector3D Lerp(Line3D line, float t) + => Vector3D.Lerp(line.From, line.To, t); + + /// + /// Calculates the closest point on the segment to the specified point. + /// + public static Vector3D ClosestPointTo(Line3D line, Vector3D point) + { + Vector3D lineRelativeVector = line.From.FromTo(line.To); + + Vector3D lineDirection = lineRelativeVector.Normalized; + Vector3D pointVector = line.From.FromTo(point); + + float dot = lineDirection.Dot(pointVector).Clamp(0f, lineRelativeVector.Magnitude); + + return lineDirection * dot; + } + + /// + /// Checks if two segments are approximately equal. + /// + /// The first . + /// The second . + /// The epsilon range. + /// if the s are approximately equal; otherwise, . + public static bool ApproximatelyEquals(Line3D left, Line3D right, float epsilon = float.Epsilon) + => left.From.ApproximatelyEquals(right.From, epsilon) && left.To.ApproximatelyEquals(right.To, epsilon); + + /// + /// Determines whether the specified object is equal to the current . + /// + /// The object to compare with the current . + /// if the specified object is equal to the current ; otherwise, . + public override bool Equals(object? obj) => obj is Line3D line3D && this == line3D; + public bool Equals(Line3D other) => this == other; + + /// + /// Generates a hash code for the . + /// + /// A hash code for the . + public override int GetHashCode() => System.HashCode.Combine(From, To); + + /// + /// Converts the to its string representation. + /// + /// A string representation of the . + public override string ToString() => $"{nameof(Line3D)}({From}, {To})"; +} + +/// +/// Provides extension methods for the struct. +/// +public static class Line3DExtensions +{ + /// + public static Vector3D Lerp(this Line3D line, float t) => Line3D.Lerp(line, t); + + /// + public static Vector3D ClosestPointTo(this Line3D line, Vector3D point) => Line3D.ClosestPointTo(line, point); + + /// + public static bool ApproximatelyEquals(this Line3D left, Line3D right, float epsilon = float.Epsilon) => Line3D.ApproximatelyEquals(left, right, epsilon); +} diff --git a/Engine.Core/Primitives/Ray3D.cs b/Engine.Core/Primitives/Ray3D.cs new file mode 100644 index 0000000..042db08 --- /dev/null +++ b/Engine.Core/Primitives/Ray3D.cs @@ -0,0 +1,109 @@ +using System; + +namespace Engine.Core; + +/// +/// Represents an infinite ray in 3D space. +/// +/// The in 3D space where the ray starts from. +/// Normalized indicating the ray's is direction. +public readonly struct Ray3D(Vector3D Origin, Vector3D Direction) : IEquatable +{ + /// + /// The starting point of the . + /// + public readonly Vector3D Origin = Origin; + + /// + /// The direction in which the points. Should be a normalized vector. + /// + public readonly Vector3D Direction = Direction; + + /// + /// Gets a with the same origin but with the direction reversed. + /// + public readonly Ray3D Reversed => new(Origin, -Direction); + + public static bool operator ==(Ray3D left, Ray3D right) => left.Origin == right.Origin && left.Direction == right.Direction; + public static bool operator !=(Ray3D left, Ray3D right) => left.Origin != right.Origin || left.Direction != right.Direction; + + public static implicit operator Ray3D(Line3D line) => new(line.From, line.From.FromTo(line.To).Normalized); + + /// + /// Constructs a from a , extending from its origin in the 's direction for a given distance. + /// + /// The source . + /// The length of the line segment to create from the . + /// A representing the segment of the . + public static Line3D GetLine(Ray3D ray, float distance) + => new(ray.Origin, ray.Origin + ray.Direction * distance); + + /// + /// Evaluates the point on the at a specified distance from its origin. + /// + /// The to evaluate. + /// The distance from the origin along the 's direction. + /// A representing the point at the given distance on the . + public static Vector3D Evaluate(Ray3D ray, float distanceFromOrigin) + => ray.Origin + ray.Direction * distanceFromOrigin; + + /// + /// Calculates the closest point on the to the specified point. + /// + public static Vector3D ClosestPointTo(Ray3D ray, Vector3D point) + { + Vector3D originToPoint = ray.Origin.FromTo(point); + + float dot = ray.Direction.Dot(originToPoint); + + return ray.Origin + ray.Direction * dot; + } + + /// + /// Checks if two s are approximately equal within a specified epsilon range. + /// + /// The first . + /// The second . + /// The epsilon range. + /// if the s are approximately equal; otherwise, . + public static bool ApproximatelyEquals(Ray3D left, Ray3D right, float epsilon = float.Epsilon) + => left.Origin.ApproximatelyEquals(right.Origin, epsilon) && left.Direction.ApproximatelyEquals(right.Direction, epsilon); + + /// + /// Determines whether the specified object is equal to the current . + /// + /// The object to compare with the current . + /// if the specified object is equal to the current ; otherwise, . + public override bool Equals(object? obj) => obj is Ray3D ray3D && this == ray3D; + public bool Equals(Ray3D other) => this == other; + + /// + /// Generates a hash code for the . + /// + /// A hash code for the . + public override int GetHashCode() => System.HashCode.Combine(Origin, Direction); + + /// + /// Converts the to its string representation. + /// + /// A string representation of the . + public override string ToString() => $"{nameof(Ray3D)}({Origin}, {Direction})"; +} + +/// +/// Provides extension methods for the struct. +/// +public static class Ray3DExtensions +{ + /// + public static Vector3D Evaluate(this Ray3D ray, float distanceFromOrigin) => Ray3D.Evaluate(ray, distanceFromOrigin); + + /// + public static bool ApproximatelyEquals(this Ray3D left, Ray3D right, float epsilon = float.Epsilon) => Ray3D.ApproximatelyEquals(left, right, epsilon); +}