From a9fc8192681f5c6f60ad354ffc9f000229fb2779 Mon Sep 17 00:00:00 2001 From: Syntriax Date: Sun, 19 Oct 2025 18:34:48 +0300 Subject: [PATCH] feat: added 3D AABB primitive --- Engine.Core/Primitives/AABB3D.cs | 111 ++++++++++++++++++ .../Packers/AABB3DNetPacker.cs | 22 ++++ .../Converters/Primitives/AABB3DConverter.cs | 41 +++++++ .../EngineExtensions/TweenAABB3DExtensions.cs | 24 ++++ 4 files changed, 198 insertions(+) create mode 100644 Engine.Core/Primitives/AABB3D.cs create mode 100644 Engine.Integration/Engine.Integration.LiteNetLib/Packers/AABB3DNetPacker.cs create mode 100644 Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/AABB3DConverter.cs create mode 100644 Engine.Systems/Tween/EngineExtensions/TweenAABB3DExtensions.cs diff --git a/Engine.Core/Primitives/AABB3D.cs b/Engine.Core/Primitives/AABB3D.cs new file mode 100644 index 0000000..70f331a --- /dev/null +++ b/Engine.Core/Primitives/AABB3D.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections.Generic; + +namespace Engine.Core; + +/// +/// Represents an Axis-Aligned Bounding Box (AABB) in 3D space. +/// +/// The lower boundary of the . +/// The upper boundary of the . +/// +/// Initializes a new instance of the struct with the specified lower and upper boundaries. +/// +[System.Diagnostics.DebuggerDisplay("LowerBoundary: {LowerBoundary.ToString(), nq}, UpperBoundary: {UpperBoundary.ToString(), nq}")] +public readonly struct AABB3D(Vector3D lowerBoundary, Vector3D upperBoundary) : IEquatable +{ + /// + /// The lower boundary of the . + /// + public readonly Vector3D LowerBoundary = lowerBoundary; + + /// + /// The upper boundary of the . + /// + public readonly Vector3D UpperBoundary = upperBoundary; + + /// + /// Gets the center point of the . + /// + public readonly Vector3D Center => (LowerBoundary + UpperBoundary) * .5f; + + /// + /// Gets the size of the . + /// + public readonly Vector3D Size => LowerBoundary.FromTo(UpperBoundary).Abs(); + + /// + /// Gets half the size of the . + /// + public readonly Vector3D SizeHalf => Size * .5f; + + public static bool operator ==(AABB3D left, AABB3D right) => left.UpperBoundary == right.UpperBoundary && left.LowerBoundary == right.LowerBoundary; + public static bool operator !=(AABB3D left, AABB3D right) => left.UpperBoundary != right.UpperBoundary || left.LowerBoundary != right.LowerBoundary; + + /// + /// Creates an from a collection of s. + /// + /// The collection of s. + /// An that bounds all the s. + public static AABB3D FromVectors(IEnumerable vectors) + { + int counter = 0; + + Vector3D lowerBoundary = new(float.MaxValue, float.MaxValue, float.MaxValue); + Vector3D upperBoundary = new(float.MinValue, float.MinValue, float.MinValue); + + foreach (Vector3D vector in vectors) + { + lowerBoundary = Vector3D.Min(lowerBoundary, vector); + upperBoundary = Vector3D.Max(upperBoundary, vector); + counter++; + } + + if (counter < 2) + throw new ArgumentException($"Parameter {nameof(vectors)} must have at least 2 items."); + + return new(lowerBoundary, upperBoundary); + } + + /// + /// Checks if two s are approximately equal. + /// + /// The first . + /// The second . + /// The epsilon range. + /// if the s are approximately equal; otherwise, . + public static bool ApproximatelyEquals(AABB3D left, AABB3D right, float epsilon = float.Epsilon) + => left.LowerBoundary.ApproximatelyEquals(right.LowerBoundary, epsilon) && left.UpperBoundary.ApproximatelyEquals(right.UpperBoundary, 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 AABB3D aabb && this == aabb; + public bool Equals(AABB3D other) => this == other; + + /// + /// Generates a hash code for the . + /// + /// A hash code for the . + public override int GetHashCode() => System.HashCode.Combine(LowerBoundary, UpperBoundary); + + /// + /// Converts the to its string representation. + /// + /// A string representation of the . + public override string ToString() => $"{nameof(AABB3D)}({LowerBoundary}, {UpperBoundary})"; +} + +/// +/// Provides extension methods for the struct. +/// +public static class AABB3DExtensions +{ + /// + public static AABB3D ToAABB3D(this IEnumerable vectors) => AABB3D.FromVectors(vectors); + + /// + public static bool ApproximatelyEquals(this AABB3D left, AABB3D right, float epsilon = float.Epsilon) => AABB3D.ApproximatelyEquals(left, right, epsilon); +} diff --git a/Engine.Integration/Engine.Integration.LiteNetLib/Packers/AABB3DNetPacker.cs b/Engine.Integration/Engine.Integration.LiteNetLib/Packers/AABB3DNetPacker.cs new file mode 100644 index 0000000..3bd4545 --- /dev/null +++ b/Engine.Integration/Engine.Integration.LiteNetLib/Packers/AABB3DNetPacker.cs @@ -0,0 +1,22 @@ +using LiteNetLib.Utils; + +using Engine.Core; + +namespace Engine.Systems.Network; + +internal static class AABB3DNetPacker +{ + internal static void Write(NetDataWriter writer, AABB3D data) + { + Vector3DNetPacker.Write(writer, data.LowerBoundary); + Vector3DNetPacker.Write(writer, data.UpperBoundary); + } + + internal static AABB3D Read(NetDataReader reader) + { + Vector3D lowerBoundary = Vector3DNetPacker.Read(reader); + Vector3D upperBoundary = Vector3DNetPacker.Read(reader); + + return new AABB3D(lowerBoundary, upperBoundary); + } +} diff --git a/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/AABB3DConverter.cs b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/AABB3DConverter.cs new file mode 100644 index 0000000..83558e8 --- /dev/null +++ b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/AABB3DConverter.cs @@ -0,0 +1,41 @@ +using System; + +using Engine.Core; + +using YamlDotNet.Core; +using YamlDotNet.Core.Events; +using YamlDotNet.Serialization; + +namespace Engine.Serializers.Yaml; + +public class AABB3DConverter : EngineTypeYamlSerializerBase +{ + public override AABB3D Read(IParser parser, Type type, ObjectDeserializer rootDeserializer) + { + parser.Consume(); + + if (parser.Consume().Value.CompareTo(nameof(AABB3D.LowerBoundary)) != 0) + throw new ArgumentException($"{nameof(AABB3D)} mapping must start with {nameof(AABB3D.LowerBoundary)}"); + Vector3D lowerBoundary = (Vector3D)rootDeserializer(typeof(Vector3D))!; + + if (parser.Consume().Value.CompareTo(nameof(AABB3D.UpperBoundary)) != 0) + throw new ArgumentException($"{nameof(AABB3D)} mapping must end with {nameof(AABB3D.UpperBoundary)}"); + Vector3D upperBoundary = (Vector3D)rootDeserializer(typeof(Vector3D))!; + + parser.Consume(); + + return new AABB3D(lowerBoundary, upperBoundary); + } + + public override void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer) + { + AABB3D aabb = (AABB3D)value!; + + emitter.Emit(new MappingStart()); + emitter.Emit(new Scalar(nameof(AABB3D.LowerBoundary))); + serializer(aabb.LowerBoundary, typeof(Vector3D)); + emitter.Emit(new Scalar(nameof(AABB3D.UpperBoundary))); + serializer(aabb.UpperBoundary, typeof(Vector3D)); + emitter.Emit(new MappingEnd()); + } +} diff --git a/Engine.Systems/Tween/EngineExtensions/TweenAABB3DExtensions.cs b/Engine.Systems/Tween/EngineExtensions/TweenAABB3DExtensions.cs new file mode 100644 index 0000000..f65daf0 --- /dev/null +++ b/Engine.Systems/Tween/EngineExtensions/TweenAABB3DExtensions.cs @@ -0,0 +1,24 @@ +using Engine.Core; + +namespace Engine.Systems.Tween; + +public static class TweenAABB3DExtensions +{ + private static readonly BoxedPool boxedAABBPool = new(2); + + public static ITween TweenAABB(this AABB3D initialAABB, ITweenManager tweenManager, float duration, AABB3D targetAABB, System.Action setMethod) + { + Boxed boxedInitial = boxedAABBPool.Get(initialAABB); + Boxed boxedTarget = boxedAABBPool.Get(targetAABB); + + ITween tween = tweenManager.StartTween(duration, t => setMethod?.Invoke(new AABB3D(boxedInitial.Value.LowerBoundary.Lerp(boxedTarget.Value.LowerBoundary, t), boxedInitial.Value.UpperBoundary.Lerp(boxedTarget.Value.UpperBoundary, t)))); + + tween.OnComplete(() => + { + boxedAABBPool.Return(boxedInitial); + boxedAABBPool.Return(boxedTarget); + }); + + return tween; + } +}