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;
+ }
+}