From 0db2cae1bb792e177e451fff96109373152f8720 Mon Sep 17 00:00:00 2001 From: Syntriax Date: Tue, 21 Oct 2025 19:06:58 +0300 Subject: [PATCH] feat: added sphere primitive --- Engine.Core/Primitives/AABB3D.cs | 2 + Engine.Core/Primitives/Circle.cs | 2 + Engine.Core/Primitives/Sphere3D.cs | 131 ++++++++++++++++++ .../LiteNetLibCommunicatorBase.cs | 1 + .../Packers/Sphere3DNetPacker.cs | 22 +++ .../Primitives/Sphere3DConverter.cs | 41 ++++++ .../TweenSphere3DExtensions.cs | 31 +++++ 7 files changed, 230 insertions(+) create mode 100644 Engine.Core/Primitives/Sphere3D.cs create mode 100644 Engine.Integration/Engine.Integration.LiteNetLib/Packers/Sphere3DNetPacker.cs create mode 100644 Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Sphere3DConverter.cs create mode 100644 Engine.Systems/Tween/EngineExtensions/TweenSphere3DExtensions.cs diff --git a/Engine.Core/Primitives/AABB3D.cs b/Engine.Core/Primitives/AABB3D.cs index 70f331a..29996b2 100644 --- a/Engine.Core/Primitives/AABB3D.cs +++ b/Engine.Core/Primitives/AABB3D.cs @@ -42,6 +42,8 @@ public readonly struct AABB3D(Vector3D lowerBoundary, Vector3D upperBoundary) : 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; + public static implicit operator AABB3D(Sphere3D sphere) => new(sphere.Center - new Vector3D(sphere.Radius, sphere.Radius, sphere.Radius), sphere.Center + new Vector3D(sphere.Radius, sphere.Radius, sphere.Radius)); + /// /// Creates an from a collection of s. /// diff --git a/Engine.Core/Primitives/Circle.cs b/Engine.Core/Primitives/Circle.cs index e8ea634..4c1c20e 100644 --- a/Engine.Core/Primitives/Circle.cs +++ b/Engine.Core/Primitives/Circle.cs @@ -42,6 +42,8 @@ public readonly struct Circle(Vector2D center, float radius) : IEquatable left.Center == right.Center && left.Radius == right.Radius; public static bool operator !=(Circle left, Circle right) => left.Center != right.Center || left.Radius != right.Radius; + public static implicit operator Circle(Sphere3D sphere) => new(sphere.Center, sphere.Radius); + /// /// Sets the center of the . /// diff --git a/Engine.Core/Primitives/Sphere3D.cs b/Engine.Core/Primitives/Sphere3D.cs new file mode 100644 index 0000000..6a93893 --- /dev/null +++ b/Engine.Core/Primitives/Sphere3D.cs @@ -0,0 +1,131 @@ +using System; +using System.Diagnostics; + +namespace Engine.Core; + +/// +/// Represents a 3D sphere. +/// +/// The center of the sphere. +/// The radius of the sphere. +/// +/// Initializes a new instance of the struct with the specified center and radius. +/// +[DebuggerDisplay("Center: {Center.ToString(),nq}, Radius: {Radius}")] +public readonly struct Sphere3D(Vector3D center, float radius) : IEquatable +{ + /// + /// The center of the . + /// + public readonly Vector3D Center = center; + + /// + /// The radius of the . + /// + public readonly float Radius = radius; + + /// + /// Gets the squared radius of the . + /// + public readonly float RadiusSquared => Radius * Radius; + + /// + /// Gets the diameter of the . + /// + public readonly float Diameter => 2f * Radius; + + /// + /// A predefined unit with a center at the origin and a radius of 1. + /// + public static readonly Sphere3D UnitSphere = new(Vector3D.Zero, 1f); + + public static bool operator ==(Sphere3D left, Sphere3D right) => left.Center == right.Center && left.Radius == right.Radius; + public static bool operator !=(Sphere3D left, Sphere3D right) => left.Center != right.Center || left.Radius != right.Radius; + + /// + /// Sets the center of the . + /// + public static Sphere3D SetCenter(Sphere3D sphere, Vector3D center) => new(center, sphere.Radius); + + /// + /// Sets the radius of the . + /// + public static Sphere3D SetRadius(Sphere3D sphere, float radius) => new(sphere.Center, radius); + + /// + /// Displaces the by the specified . + /// + public static Sphere3D Displace(Sphere3D sphere, Vector3D displaceVector) => new(sphere.Center + displaceVector, sphere.Radius); + + /// + /// Projects the onto the specified . + /// + public static Projection1D Project(Sphere3D sphere, Vector3D projectionVector) + { + float projectedCenter = sphere.Center.Dot(projectionVector); + return new(projectedCenter - sphere.Radius, projectedCenter + sphere.Radius); + } + + /// + /// Transforms the by the specified . + /// + public static Sphere3D Transform(ITransform3D transform, Sphere3D sphere) + => new(transform.Transform(sphere.Center), sphere.Radius * (transform.Scale.Magnitude / Vector3D.One.Magnitude)); + + /// + /// 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(Sphere3D left, Sphere3D right, float epsilon = float.Epsilon) + => left.Center.ApproximatelyEquals(right.Center, epsilon) && left.Radius.ApproximatelyEquals(right.Radius, 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 Sphere3D sphere && this == sphere; + public bool Equals(Sphere3D other) => this == other; + + /// + /// Generates a hash code for the . + /// + /// A hash code for the . + public override int GetHashCode() => System.HashCode.Combine(Center, Radius); + + /// + /// Converts the to its string representation. + /// + /// A string representation of the . + public override string ToString() => $"{nameof(Sphere3D)}({Center}, {Radius})"; +} + +/// +/// Provides extension methods for the struct. +/// +public static class SphereExtensions +{ + /// + public static Sphere3D SetCenter(this Sphere3D sphere, Vector3D center) => Sphere3D.SetCenter(sphere, center); + + /// + public static Sphere3D SetRadius(this Sphere3D sphere, float radius) => Sphere3D.SetRadius(sphere, radius); + + /// + public static Sphere3D Displace(this Sphere3D sphere, Vector3D displaceVector) => Sphere3D.Displace(sphere, displaceVector); + + /// + public static Projection1D ProjectTo(this Sphere3D sphere, Vector3D projectionVector) => Sphere3D.Project(sphere, projectionVector); + + /// + public static Sphere3D Transform(this ITransform3D transform, Sphere3D sphere) => Sphere3D.Transform(transform, sphere); + + /// + public static Sphere3D Transform(this Sphere3D sphere, ITransform3D transform) => Sphere3D.Transform(transform, sphere); + + /// + public static bool ApproximatelyEquals(this Sphere3D left, Sphere3D right, float epsilon = float.Epsilon) => Sphere3D.ApproximatelyEquals(left, right, epsilon); +} diff --git a/Engine.Integration/Engine.Integration.LiteNetLib/LiteNetLibCommunicatorBase.cs b/Engine.Integration/Engine.Integration.LiteNetLib/LiteNetLibCommunicatorBase.cs index 6319785..0965e57 100644 --- a/Engine.Integration/Engine.Integration.LiteNetLib/LiteNetLibCommunicatorBase.cs +++ b/Engine.Integration/Engine.Integration.LiteNetLib/LiteNetLibCommunicatorBase.cs @@ -158,6 +158,7 @@ public abstract class LiteNetLibCommunicatorBase : Behaviour, INetworkCommunicat netPacketProcessor.RegisterNestedType(Ray2DNetPacker.Write, Ray2DNetPacker.Read); netPacketProcessor.RegisterNestedType(Ray3DNetPacker.Write, Ray3DNetPacker.Read); netPacketProcessor.RegisterNestedType(Shape2DNetPacker.Write, Shape2DNetPacker.Read); + netPacketProcessor.RegisterNestedType(Sphere3DNetPacker.Write, Sphere3DNetPacker.Read); netPacketProcessor.RegisterNestedType(TriangleNetPacker.Write, TriangleNetPacker.Read); netPacketProcessor.RegisterNestedType(Vector2DNetPacker.Write, Vector2DNetPacker.Read); netPacketProcessor.RegisterNestedType(Vector2DIntNetPacker.Write, Vector2DIntNetPacker.Read); diff --git a/Engine.Integration/Engine.Integration.LiteNetLib/Packers/Sphere3DNetPacker.cs b/Engine.Integration/Engine.Integration.LiteNetLib/Packers/Sphere3DNetPacker.cs new file mode 100644 index 0000000..8d7cc45 --- /dev/null +++ b/Engine.Integration/Engine.Integration.LiteNetLib/Packers/Sphere3DNetPacker.cs @@ -0,0 +1,22 @@ +using LiteNetLib.Utils; + +using Engine.Core; + +namespace Engine.Systems.Network; + +internal static class Sphere3DNetPacker +{ + internal static void Write(NetDataWriter writer, Sphere3D data) + { + Vector3DNetPacker.Write(writer, data.Center); + writer.Put(data.Radius); + } + + internal static Sphere3D Read(NetDataReader reader) + { + Vector3D center = Vector3DNetPacker.Read(reader); + float radius = reader.GetFloat(); + + return new Sphere3D(center, radius); + } +} diff --git a/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Sphere3DConverter.cs b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Sphere3DConverter.cs new file mode 100644 index 0000000..1fc0e71 --- /dev/null +++ b/Engine.Integration/Engine.Integration.Yaml/Converters/Primitives/Sphere3DConverter.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 Sphere3DConverter : EngineTypeYamlSerializerBase +{ + public override Sphere3D Read(IParser parser, Type type, ObjectDeserializer rootDeserializer) + { + parser.Consume(); + + if (parser.Consume().Value.CompareTo(nameof(Sphere3D.Center)) != 0) + throw new ArgumentException($"{nameof(Sphere3D)} mapping must start with {nameof(Sphere3D.Center)}"); + Vector3D lowerBoundary = (Vector3D)rootDeserializer(typeof(Vector3D))!; + + if (parser.Consume().Value.CompareTo(nameof(Sphere3D.Radius)) != 0) + throw new ArgumentException($"{nameof(Sphere3D)} mapping must end with {nameof(Sphere3D.Radius)}"); + float radius = (float)rootDeserializer(typeof(float))!; + + parser.Consume(); + + return new Sphere3D(lowerBoundary, radius); + } + + public override void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer) + { + Sphere3D sphere = (Sphere3D)value!; + + emitter.Emit(new MappingStart()); + emitter.Emit(new Scalar(nameof(Sphere3D.Center))); + serializer(sphere.Center, typeof(Vector3D)); + emitter.Emit(new Scalar(nameof(Sphere3D.Radius))); + serializer(sphere.Radius, typeof(float)); + emitter.Emit(new MappingEnd()); + } +} diff --git a/Engine.Systems/Tween/EngineExtensions/TweenSphere3DExtensions.cs b/Engine.Systems/Tween/EngineExtensions/TweenSphere3DExtensions.cs new file mode 100644 index 0000000..a50ec3f --- /dev/null +++ b/Engine.Systems/Tween/EngineExtensions/TweenSphere3DExtensions.cs @@ -0,0 +1,31 @@ +using Engine.Core; + +namespace Engine.Systems.Tween; + +public static class TweenSphere3DExtensions +{ + private static readonly BoxedPool boxedSphere3DPool = new(2); + + public static ITween TweenSphere3D(this Sphere3D initialSphere3D, ITweenManager tweenManager, float duration, Sphere3D targetSphere3D, System.Action setMethod) + { + Boxed boxedInitial = boxedSphere3DPool.Get(initialSphere3D); + Boxed boxedTarget = boxedSphere3DPool.Get(targetSphere3D); + + ITween tween = tweenManager.StartTween(duration, + t => setMethod?.Invoke( + new Sphere3D( + boxedInitial.Value.Center.Lerp(boxedTarget.Value.Center, t), + boxedInitial.Value.Diameter.Lerp(boxedTarget.Value.Diameter, t) + ) + ) + ); + + tween.OnComplete(() => + { + boxedSphere3DPool.Return(boxedInitial); + boxedSphere3DPool.Return(boxedTarget); + }); + + return tween; + } +}