diff --git a/Engine.Core/Abstract/IBehaviourController.cs b/Engine.Core/Abstract/IBehaviourController.cs index 87af209..b1500da 100644 --- a/Engine.Core/Abstract/IBehaviourController.cs +++ b/Engine.Core/Abstract/IBehaviourController.cs @@ -62,7 +62,8 @@ public interface IBehaviourController : IEntity, IHasUniverseObject /// /// The type of s to get. /// The list to store the s. - void GetBehaviours(IList results); + /// Whether to clear the before collection or append the results to the list. + void GetBehaviours(IList results, CollectionMethod collectionMethod = CollectionMethod.Clear); /// /// Removes s of the specified type from the . @@ -78,6 +79,8 @@ public interface IBehaviourController : IEntity, IHasUniverseObject /// The to remove. void RemoveBehaviour(T behaviour) where T : class, IBehaviour; + enum CollectionMethod { Clear, Append }; + readonly record struct BehaviourAddedArguments(IBehaviour BehaviourAdded); readonly record struct BehaviourRemovedArguments(IBehaviour BehaviourRemoved); } diff --git a/Engine.Core/Abstract/ICamera3D.cs b/Engine.Core/Abstract/ICamera3D.cs index 01ce185..a0024a0 100644 --- a/Engine.Core/Abstract/ICamera3D.cs +++ b/Engine.Core/Abstract/ICamera3D.cs @@ -6,7 +6,32 @@ namespace Engine.Core; public interface ICamera3D : IBehaviour3D { /// - /// Field of View (FOV) value of the camera + /// Event triggered when the near plane of the changes. + /// + Event OnNearPlaneChanged { get; } + + /// + /// Event triggered when the far plane of the changes. + /// + Event OnFarPlaneChanged { get; } + + /// + /// Event triggered when the field of view of the changes. + /// + Event OnFieldOfViewChanged { get; } + + /// + /// Near plane distance of the camera. + /// + float NearPlane { get; set; } + + /// + /// Far plane distance of the camera. + /// + float FarPlane { get; set; } + + /// + /// Field of View (FOV) value of the camera in degrees. /// float FieldOfView { get; set; } @@ -23,4 +48,8 @@ public interface ICamera3D : IBehaviour3D /// The position in world coordinates. /// The position in screen coordinates. Vector2D WorldToScreenPosition(Vector3D worldPosition); + + readonly record struct NearPlaneChangedArguments(float PreviousNearPlane); + readonly record struct FarPlaneChangedArguments(float PreviousFarPlane); + readonly record struct FieldOfViewChangedArguments(float PreviousFieldOfView); } diff --git a/Engine.Core/Abstract/IEntity.cs b/Engine.Core/Abstract/IEntity.cs index 53a719a..0903fa4 100644 --- a/Engine.Core/Abstract/IEntity.cs +++ b/Engine.Core/Abstract/IEntity.cs @@ -3,18 +3,4 @@ namespace Engine.Core; /// /// Represents a basic entity in the engine. /// -public interface IEntity : IInitializable, IHasStateEnable -{ - /// - /// Event triggered when the of the changes. - /// The string action parameter is the previous of the . - /// - Event OnIdChanged { get; } - - /// - /// The ID of the . - /// - string Id { get; set; } - - readonly record struct IdChangedArguments(string PreviousId); -} +public interface IEntity : IInitializable, IIdentifiable, IHasStateEnable; diff --git a/Engine.Core/Abstract/IHasId.cs b/Engine.Core/Abstract/IHasId.cs new file mode 100644 index 0000000..c4cd50a --- /dev/null +++ b/Engine.Core/Abstract/IHasId.cs @@ -0,0 +1,20 @@ +namespace Engine.Core; + +/// +/// Represents any instance in the engine with an id. +/// +public interface IIdentifiable +{ + /// + /// Event triggered when the of the changes. + /// The string action parameter is the previous of the . + /// + Event OnIdChanged { get; } + + /// + /// The ID of the . + /// + string Id { get; set; } + + readonly record struct IdChangedArguments(string PreviousId); +} diff --git a/Engine.Core/Abstract/IUniverse.cs b/Engine.Core/Abstract/IUniverse.cs index e0896a1..9b95611 100644 --- a/Engine.Core/Abstract/IUniverse.cs +++ b/Engine.Core/Abstract/IUniverse.cs @@ -78,9 +78,9 @@ public interface IUniverse : IEntity, IEnumerable UniverseTime UnscaledTime { get; } /// - /// Gets a read-only list of s managed by the . + /// Gets the root of the . /// - IReadOnlyList UniverseObjects { get; } + IUniverseObject Root { get; } /// /// Registers an to the . diff --git a/Engine.Core/BaseEntity.cs b/Engine.Core/BaseEntity.cs index a16b072..f848e3d 100644 --- a/Engine.Core/BaseEntity.cs +++ b/Engine.Core/BaseEntity.cs @@ -4,7 +4,7 @@ namespace Engine.Core; public abstract class BaseEntity : IEntity { - public Event OnIdChanged { get; } = new(); + public Event OnIdChanged { get; } = new(); public Event OnInitialized { get; } = new(); public Event OnFinalized { get; } = new(); public Event OnStateEnableAssigned { get; } = new(); diff --git a/Engine.Core/BehaviourController.cs b/Engine.Core/BehaviourController.cs index 00618a8..0adff40 100644 --- a/Engine.Core/BehaviourController.cs +++ b/Engine.Core/BehaviourController.cs @@ -58,9 +58,11 @@ public class BehaviourController : BaseEntity, IBehaviourController return behaviours; } - public void GetBehaviours(IList results) + public void GetBehaviours(IList results, IBehaviourController.CollectionMethod collectionMethod = IBehaviourController.CollectionMethod.Clear) { - results.Clear(); + if (collectionMethod == IBehaviourController.CollectionMethod.Clear) + results.Clear(); + foreach (IBehaviour behaviourItem in behaviours) { if (behaviourItem is not T behaviour) diff --git a/Engine.Core/Collectors/ActiveBehaviourCollectorBase.cs b/Engine.Core/Collectors/ActiveBehaviourCollectorBase.cs index f389a61..fe97bcd 100644 --- a/Engine.Core/Collectors/ActiveBehaviourCollectorBase.cs +++ b/Engine.Core/Collectors/ActiveBehaviourCollectorBase.cs @@ -29,7 +29,7 @@ public abstract class ActiveBehaviourCollectorBase : IBehaviourCollector w if (Universe is not null) return false; - foreach (IUniverseObject universeObject in universe.UniverseObjects) + foreach (IUniverseObject universeObject in universe) OnUniverseObjectRegistered(universe, new(universeObject)); universe.OnUniverseObjectRegistered.AddListener(delegateOnUniverseObjectRegistered); @@ -46,7 +46,7 @@ public abstract class ActiveBehaviourCollectorBase : IBehaviourCollector w if (Universe is null) return false; - foreach (IUniverseObject universeObject in Universe.UniverseObjects) + foreach (IUniverseObject universeObject in Universe) OnUniverseObjectUnregistered(Universe, new(universeObject)); Universe.OnUniverseObjectRegistered.RemoveListener(delegateOnUniverseObjectRegistered); diff --git a/Engine.Core/Collectors/ActiveBehaviourCollectorOrdered.cs b/Engine.Core/Collectors/ActiveBehaviourCollectorOrdered.cs index 502e45c..927d009 100644 --- a/Engine.Core/Collectors/ActiveBehaviourCollectorOrdered.cs +++ b/Engine.Core/Collectors/ActiveBehaviourCollectorOrdered.cs @@ -5,9 +5,8 @@ namespace Engine.Core; public class ActiveBehaviourCollectorOrdered : ActiveBehaviourCollector where TItem : class, IBehaviour where TIndex : IComparable { - private readonly Event.EventHandler delegateOnPriorityChanged = null!; - private readonly SortedDictionary> behaviours = null!; + private readonly Dictionary indexCache = []; private readonly Func getIndexFunc = null!; private readonly IComparer sortBy = null!; @@ -39,11 +38,10 @@ public class ActiveBehaviourCollectorOrdered : ActiveBehaviourCol protected override bool RemoveBehaviour(TItem tBehaviour) { - TIndex index = getIndexFunc(tBehaviour); - if (!behaviours.TryGetValue(index, out FastList? list)) + if (!indexCache.TryGetValue(tBehaviour, out TIndex? index) || !behaviours.TryGetValue(index, out FastList? list)) throw new Exceptions.NotFoundException($"Index of '{index}' is not found in the collector"); - if (!list.Remove(tBehaviour)) + if (!list.Remove(tBehaviour) || !indexCache.Remove(tBehaviour)) return false; count--; @@ -58,21 +56,11 @@ public class ActiveBehaviourCollectorOrdered : ActiveBehaviourCol count++; list.Add(behaviour); - } - - protected override void OnBehaviourAdd(IBehaviour behaviour) => behaviour.OnPriorityChanged.AddListener(delegateOnPriorityChanged); - protected override void OnBehaviourRemove(IBehaviour behaviour) => behaviour.OnPriorityChanged.RemoveListener(delegateOnPriorityChanged); - - private void OnPriorityChanged(IBehaviour sender, IBehaviour.PriorityChangedArguments args) - { - TItem behaviour = (TItem)sender; - RemoveBehaviour(behaviour); - AddBehaviour(behaviour); + indexCache.Add(behaviour, key); } public ActiveBehaviourCollectorOrdered(Func getIndexFunc, Comparison sortBy) { - delegateOnPriorityChanged = OnPriorityChanged; this.getIndexFunc = getIndexFunc; this.sortBy = Comparer.Create(sortBy); behaviours = new(this.sortBy); @@ -80,7 +68,6 @@ public class ActiveBehaviourCollectorOrdered : ActiveBehaviourCol public ActiveBehaviourCollectorOrdered(IUniverse universe, Func getIndexFunc, Comparison sortBy) : base(universe) { - delegateOnPriorityChanged = OnPriorityChanged; this.getIndexFunc = getIndexFunc; this.sortBy = Comparer.Create(sortBy); behaviours = new(this.sortBy); @@ -89,14 +76,12 @@ public class ActiveBehaviourCollectorOrdered : ActiveBehaviourCol public ActiveBehaviourCollectorOrdered(Func getIndexFunc, IComparer sortBy) { this.getIndexFunc = getIndexFunc; - delegateOnPriorityChanged = OnPriorityChanged; this.sortBy = sortBy; behaviours = new(sortBy); } public ActiveBehaviourCollectorOrdered(IUniverse universe, Func getIndexFunc, IComparer sortBy) : base(universe) { - delegateOnPriorityChanged = OnPriorityChanged; this.getIndexFunc = getIndexFunc; this.sortBy = sortBy; behaviours = new(sortBy); diff --git a/Engine.Core/Collectors/BehaviourCollectorBase.cs b/Engine.Core/Collectors/BehaviourCollectorBase.cs index e531466..6e7ca0e 100644 --- a/Engine.Core/Collectors/BehaviourCollectorBase.cs +++ b/Engine.Core/Collectors/BehaviourCollectorBase.cs @@ -25,7 +25,7 @@ public abstract class BehaviourCollectorBase : IBehaviourCollector where T if (Universe is not null) return false; - foreach (IUniverseObject universeObject in universe.UniverseObjects) + foreach (IUniverseObject universeObject in universe) OnUniverseObjectRegistered(universe, new(universeObject)); universe.OnUniverseObjectRegistered.AddListener(delegateOnUniverseObjectRegistered); @@ -43,7 +43,7 @@ public abstract class BehaviourCollectorBase : IBehaviourCollector where T if (Universe is null) return false; - foreach (IUniverseObject universeObject in Universe.UniverseObjects) + foreach (IUniverseObject universeObject in Universe) OnUniverseObjectUnregistered(Universe, new(universeObject)); Universe.OnUniverseObjectRegistered.RemoveListener(delegateOnUniverseObjectRegistered); diff --git a/Engine.Core/Collectors/BehaviourCollectorOrdered.cs b/Engine.Core/Collectors/BehaviourCollectorOrdered.cs index 6f1077a..4fe27ff 100644 --- a/Engine.Core/Collectors/BehaviourCollectorOrdered.cs +++ b/Engine.Core/Collectors/BehaviourCollectorOrdered.cs @@ -5,9 +5,8 @@ namespace Engine.Core; public class BehaviourCollectorOrdered : BehaviourCollectorBase where TItem : class where TIndex : IComparable { - private readonly Event.EventHandler delegateOnPriorityChanged = null!; - private readonly SortedDictionary> behaviours = null!; + private readonly Dictionary indexCache = null!; private readonly Func getIndexFunc = null!; private readonly IComparer sortBy = null!; @@ -39,11 +38,10 @@ public class BehaviourCollectorOrdered : BehaviourCollectorBase? list)) + if (!indexCache.TryGetValue(tBehaviour, out TIndex? index) || !behaviours.TryGetValue(index, out FastList? list)) throw new Exceptions.NotFoundException($"Index of '{index}' is not found in the collector"); - if (!list.Remove(tBehaviour)) + if (!list.Remove(tBehaviour) || !indexCache.Remove(tBehaviour)) return false; count--; @@ -58,21 +56,11 @@ public class BehaviourCollectorOrdered : BehaviourCollectorBase behaviour.OnPriorityChanged.AddListener(delegateOnPriorityChanged); - protected override void OnBehaviourRemove(IBehaviour behaviour) => behaviour.OnPriorityChanged.RemoveListener(delegateOnPriorityChanged); - - private void OnPriorityChanged(IBehaviour sender, IBehaviour.PriorityChangedArguments args) - { - TItem behaviour = (TItem)sender; - RemoveBehaviour(behaviour); - AddBehaviour(behaviour); + indexCache.Add(behaviour, key); } public BehaviourCollectorOrdered(Func getIndexFunc, Comparison sortBy) { - delegateOnPriorityChanged = OnPriorityChanged; this.getIndexFunc = getIndexFunc; this.sortBy = Comparer.Create(sortBy); behaviours = new(this.sortBy); @@ -80,7 +68,6 @@ public class BehaviourCollectorOrdered : BehaviourCollectorBase getIndexFunc, Comparison sortBy) : base(universe) { - delegateOnPriorityChanged = OnPriorityChanged; this.getIndexFunc = getIndexFunc; this.sortBy = Comparer.Create(sortBy); behaviours = new(this.sortBy); @@ -89,14 +76,12 @@ public class BehaviourCollectorOrdered : BehaviourCollectorBase getIndexFunc, IComparer sortBy) { this.getIndexFunc = getIndexFunc; - delegateOnPriorityChanged = OnPriorityChanged; this.sortBy = sortBy; behaviours = new(sortBy); } public BehaviourCollectorOrdered(IUniverse universe, Func getIndexFunc, IComparer sortBy) : base(universe) { - delegateOnPriorityChanged = OnPriorityChanged; this.getIndexFunc = getIndexFunc; this.sortBy = sortBy; behaviours = new(sortBy); diff --git a/Engine.Core/Extensions/BehaviourControllerExtensions.cs b/Engine.Core/Extensions/BehaviourControllerExtensions.cs index bcf0773..9228992 100644 --- a/Engine.Core/Extensions/BehaviourControllerExtensions.cs +++ b/Engine.Core/Extensions/BehaviourControllerExtensions.cs @@ -100,18 +100,16 @@ public static class BehaviourControllerExtensions /// /// The type of s to get. /// The list to store the s. - public static void GetBehavioursInParent(this IBehaviourController behaviourController, IList behavioursInParent) where T : class + /// Whether to clear the before collection or append the results to the list. + public static void GetBehavioursInParent(this IBehaviourController behaviourController, IList behavioursInParent, IBehaviourController.CollectionMethod collectionMethod = IBehaviourController.CollectionMethod.Clear) where T : class { - IBehaviourController? controller = behaviourController; - List cache = []; - behavioursInParent.Clear(); + if (collectionMethod == IBehaviourController.CollectionMethod.Clear) + behavioursInParent.Clear(); + IBehaviourController? controller = behaviourController; while (controller is not null) { - controller.GetBehaviours(cache); - foreach (T behaviour in cache) - behavioursInParent.Add(behaviour); - + controller.GetBehaviours(behavioursInParent, IBehaviourController.CollectionMethod.Append); controller = controller.UniverseObject.Parent?.BehaviourController; } } @@ -161,22 +159,20 @@ public static class BehaviourControllerExtensions /// /// The type of s to get. /// The list to store the s. - public static void GetBehavioursInChildren(this IBehaviourController behaviourController, IList behavioursInChildren) where T : class + /// Whether to clear the before collection or append the results to the list. + public static void GetBehavioursInChildren(this IBehaviourController behaviourController, IList behavioursInChildren, IBehaviourController.CollectionMethod collectionMethod = IBehaviourController.CollectionMethod.Clear) where T : class { - List cache = []; - behavioursInChildren.Clear(); + if (collectionMethod == IBehaviourController.CollectionMethod.Clear) + behavioursInChildren.Clear(); - TraverseChildrenForBehaviour(behaviourController.UniverseObject, behavioursInChildren, cache); + TraverseChildrenForBehaviour(behaviourController.UniverseObject, behavioursInChildren); } - private static void TraverseChildrenForBehaviour(IUniverseObject universeObject, IList behaviours, IList cache) where T : class + private static void TraverseChildrenForBehaviour(IUniverseObject universeObject, IList behaviours) where T : class { - universeObject.BehaviourController.GetBehaviours(cache); - - foreach (T behaviour in cache) - behaviours.Add(behaviour); + universeObject.BehaviourController.GetBehaviours(behaviours, IBehaviourController.CollectionMethod.Append); foreach (IUniverseObject child in universeObject.Children) - TraverseChildrenForBehaviour(child, behaviours, cache); + TraverseChildrenForBehaviour(child, behaviours); } } diff --git a/Engine.Core/Extensions/UniverseExtensions.cs b/Engine.Core/Extensions/UniverseExtensions.cs index e7379b0..d6a40e8 100644 --- a/Engine.Core/Extensions/UniverseExtensions.cs +++ b/Engine.Core/Extensions/UniverseExtensions.cs @@ -32,5 +32,5 @@ public static class UniverseExtensions /// Type to be searched through the . /// The specified type if found; otherwise, throws . public static T FindRequired(this IUniverse universe) where T : class - => universe.Find() ?? throw new NotFoundException($"{universe.GetType().FullName}({universe.Id}) does not contain any {nameof(IUniverseObject)} or {nameof(IBehaviour)} of type {typeof(T).FullName}"); + => universe.Root.BehaviourController.GetBehaviourInChildren() ?? throw new NotFoundException($"{universe.GetType().FullName}({universe.Id}) does not contain any {nameof(IUniverseObject)} or {nameof(IBehaviour)} of type {typeof(T).FullName}"); } diff --git a/Engine.Core/Extensions/UniverseObjectExtensions.cs b/Engine.Core/Extensions/UniverseObjectExtensions.cs index 2f843cb..4b630bc 100644 --- a/Engine.Core/Extensions/UniverseObjectExtensions.cs +++ b/Engine.Core/Extensions/UniverseObjectExtensions.cs @@ -16,6 +16,21 @@ public static class UniverseObjectExtensions return universeObject; } + public static IEnumerator TraverseChildren(this IUniverseObject universeObject) + { + static IEnumerable Traverse(IUniverseObject obj) + { + foreach (IUniverseObject child in obj.Children) + { + yield return child; + foreach (IUniverseObject descendant in Traverse(child)) + yield return descendant; + } + } + + return Traverse(universeObject).GetEnumerator(); + } + #region Universe Object Search /// /// Gets a of the specified type. diff --git a/Engine.Core/Math.cs b/Engine.Core/Math.cs index b65cc5e..14ba680 100644 --- a/Engine.Core/Math.cs +++ b/Engine.Core/Math.cs @@ -110,6 +110,13 @@ public static class Math /// The sine of . public static float Sin(float x) => MathF.Sin(x); + /// + /// Returns the tangent of a number. + /// + /// The angle, in radians. + /// The tangent of . + public static float Tan(float x) => MathF.Tan(x); + /// /// Returns the arccosine of a number. /// @@ -124,6 +131,13 @@ public static class Math /// The arcsine of . public static float Asin(float x) => MathF.Asin(x); + /// + /// Returns the angle whose tangent is the specified number. + /// + /// The tangent value. + /// The angle, in radians. + public static float Atan(float x) => MathF.Atan(x); + /// /// Returns the angle whose tangent is the quotient of two specified numbers. /// diff --git a/Engine.Core/MathExtensions.cs b/Engine.Core/MathExtensions.cs index d1fd4aa..ac67041 100644 --- a/Engine.Core/MathExtensions.cs +++ b/Engine.Core/MathExtensions.cs @@ -32,12 +32,18 @@ public static class MathExtensions /// public static float Sin(this float x) => Math.Sin(x); + /// + public static float Tan(this float x) => Math.Tan(x); + /// public static float Acos(this float x) => Math.Acos(x); /// public static float Asin(this float x) => Math.Asin(x); + /// + public static float Atan(this float x) => Math.Atan(x); + /// public static float Atan2(this float y, float x) => Math.Atan2(y, x); diff --git a/Engine.Core/Primitives/Matrix4x4.cs b/Engine.Core/Primitives/Matrix4x4.cs new file mode 100644 index 0000000..0288b41 --- /dev/null +++ b/Engine.Core/Primitives/Matrix4x4.cs @@ -0,0 +1,294 @@ +using System; +using System.Numerics; + +namespace Engine.Core; + +// TODO Comments + +/// +/// Represents a 4D left handed space matrix. +/// +/// +/// Initializes a new instance of the struct with the specified values. +/// +[System.Diagnostics.DebuggerDisplay("{ToString(),nq}")] +public readonly struct Matrix4x4( + float m11, float m12, float m13, float m14, + float m21, float m22, float m23, float m24, + float m31, float m32, float m33, float m34, + float m41, float m42, float m43, float m44 +) : IEquatable +{ + public readonly float M11 = m11, M12 = m12, M13 = m13, M14 = m14; + public readonly float M21 = m21, M22 = m22, M23 = m23, M24 = m24; + public readonly float M31 = m31, M32 = m32, M33 = m33, M34 = m34; + public readonly float M41 = m41, M42 = m42, M43 = m43, M44 = m44; + + /// + /// Extracts the position (translation) from the . + /// + public readonly Vector3D Position => new(M41, M42, M43); + + /// + /// Extracts the scale from the . + /// + public readonly Vector3D Scale + { + get + { + float scaleX = new Vector3D(M11, M12, M13).Length(); + float scaleY = new Vector3D(M21, M22, M23).Length(); + float scaleZ = new Vector3D(M31, M32, M33).Length(); + + if (Determinant(this) < 0) + scaleX *= -1; + + return new(scaleX, scaleY, scaleZ); + } + } + + /// + /// Extracts the rotation from the . + /// + public readonly Quaternion Rotation => Quaternion.FromRotationMatrix4x4(this).Normalized; + + /// + /// Represents the identity . + /// + public static Matrix4x4 Identity => new( + 1f, 0f, 0f, 0f, + 0f, 1f, 0f, 0f, + 0f, 0f, 1f, 0f, + 0f, 0f, 0f, 1f + ); + + public static Matrix4x4 operator *(Matrix4x4 a, Matrix4x4 b) => new( + a.M11 * b.M11 + a.M12 * b.M21 + a.M13 * b.M31 + a.M14 * b.M41, + a.M11 * b.M12 + a.M12 * b.M22 + a.M13 * b.M32 + a.M14 * b.M42, + a.M11 * b.M13 + a.M12 * b.M23 + a.M13 * b.M33 + a.M14 * b.M43, + a.M11 * b.M14 + a.M12 * b.M24 + a.M13 * b.M34 + a.M14 * b.M44, + + a.M21 * b.M11 + a.M22 * b.M21 + a.M23 * b.M31 + a.M24 * b.M41, + a.M21 * b.M12 + a.M22 * b.M22 + a.M23 * b.M32 + a.M24 * b.M42, + a.M21 * b.M13 + a.M22 * b.M23 + a.M23 * b.M33 + a.M24 * b.M43, + a.M21 * b.M14 + a.M22 * b.M24 + a.M23 * b.M34 + a.M24 * b.M44, + + a.M31 * b.M11 + a.M32 * b.M21 + a.M33 * b.M31 + a.M34 * b.M41, + a.M31 * b.M12 + a.M32 * b.M22 + a.M33 * b.M32 + a.M34 * b.M42, + a.M31 * b.M13 + a.M32 * b.M23 + a.M33 * b.M33 + a.M34 * b.M43, + a.M31 * b.M14 + a.M32 * b.M24 + a.M33 * b.M34 + a.M34 * b.M44, + + a.M41 * b.M11 + a.M42 * b.M21 + a.M43 * b.M31 + a.M44 * b.M41, + a.M41 * b.M12 + a.M42 * b.M22 + a.M43 * b.M32 + a.M44 * b.M42, + a.M41 * b.M13 + a.M42 * b.M23 + a.M43 * b.M33 + a.M44 * b.M43, + a.M41 * b.M14 + a.M42 * b.M24 + a.M43 * b.M34 + a.M44 * b.M44 + ); + + public static bool operator ==(Matrix4x4 left, Matrix4x4 right) => + left.M11 == right.M11 && left.M12 == right.M12 && left.M13 == right.M13 && left.M14 == right.M14 && + left.M21 == right.M21 && left.M22 == right.M22 && left.M23 == right.M23 && left.M24 == right.M24 && + left.M31 == right.M31 && left.M32 == right.M32 && left.M33 == right.M33 && left.M34 == right.M34 && + left.M41 == right.M41 && left.M42 == right.M42 && left.M43 == right.M43 && left.M44 == right.M44; + + public static bool operator !=(Matrix4x4 left, Matrix4x4 right) => + left.M11 != right.M11 || left.M12 != right.M12 || left.M13 != right.M13 || left.M14 != right.M14 || + left.M21 != right.M21 || left.M22 != right.M22 || left.M23 != right.M23 || left.M24 != right.M24 || + left.M31 != right.M31 || left.M32 != right.M32 || left.M33 != right.M33 || left.M34 != right.M34 || + left.M41 != right.M41 || left.M42 != right.M42 || left.M43 != right.M43 || left.M44 != right.M44; + + public static implicit operator System.Numerics.Matrix4x4(Matrix4x4 m) => new( + m.M11, m.M12, m.M13, m.M14, + m.M21, m.M22, m.M23, m.M24, + m.M31, m.M32, m.M33, m.M34, + m.M41, m.M42, m.M43, m.M44 + ); + + public static implicit operator Matrix4x4(System.Numerics.Matrix4x4 m) => new( + m.M11, m.M12, m.M13, m.M14, + m.M21, m.M22, m.M23, m.M24, + m.M31, m.M32, m.M33, m.M34, + m.M41, m.M42, m.M43, m.M44 + ); + + /// + /// Calculates the determinant of the . + /// + /// The . + /// The determinant of the . + public static float Determinant(Matrix4x4 m) => // https://www.euclideanspace.com/maths/algebra/matrix/functions/determinant/fourD/index.htm + m.M14 * m.M23 * m.M32 * m.M41 - m.M13 * m.M24 * m.M32 * m.M41 - + m.M14 * m.M22 * m.M33 * m.M41 + m.M12 * m.M24 * m.M33 * m.M41 + + m.M13 * m.M22 * m.M34 * m.M41 - m.M12 * m.M23 * m.M34 * m.M41 - + m.M14 * m.M23 * m.M31 * m.M42 + m.M13 * m.M24 * m.M31 * m.M42 + + m.M14 * m.M21 * m.M33 * m.M42 - m.M11 * m.M24 * m.M33 * m.M42 - + m.M13 * m.M21 * m.M34 * m.M42 + m.M11 * m.M23 * m.M34 * m.M42 + + m.M14 * m.M22 * m.M31 * m.M43 - m.M12 * m.M24 * m.M31 * m.M43 - + m.M14 * m.M21 * m.M32 * m.M43 + m.M11 * m.M24 * m.M32 * m.M43 + + m.M12 * m.M21 * m.M34 * m.M43 - m.M11 * m.M22 * m.M34 * m.M43 - + m.M13 * m.M22 * m.M31 * m.M44 + m.M12 * m.M23 * m.M31 * m.M44 + + m.M13 * m.M21 * m.M32 * m.M44 - m.M11 * m.M23 * m.M32 * m.M44 - + m.M12 * m.M21 * m.M33 * m.M44 + m.M11 * m.M22 * m.M33 * m.M44; + + public static Matrix4x4 CreateTranslation(Vector3D position) => new( + 1f, 0f, 0f, 0f, + 0f, 1f, 0f, 0f, + 0f, 0f, 1f, 0f, + position.X, position.Y, position.Z, 1 + ); + + public static Matrix4x4 CreateScale(Vector3D scale) => new( + scale.X, 0f, 0f, 0f, + 0f, scale.Y, 0f, 0f, + 0f, 0f, scale.Z, 0f, + 0f, 0f, 0f, 1f + ); + + public static Matrix4x4 CreateRotationX(float radians) + { + float c = Math.Cos(radians); + float s = Math.Sin(radians); + + return new Matrix4x4( + 1f, 0f, 0f, 0f, + 0f, c, s, 0f, + 0f, -s, c, 0f, + 0f, 0f, 0f, 1f + ); + } + + public static Matrix4x4 CreateRotationY(float radians) + { + float c = Math.Cos(radians); + float s = Math.Sin(radians); + + return new Matrix4x4( + c, 0f, -s, 0f, + 0f, 1f, 0f, 0f, + s, 0f, c, 0f, + 0f, 0f, 0f, 1f + ); + } + + public static Matrix4x4 CreateRotationZ(float radians) + { + float c = Math.Cos(radians); + float s = Math.Sin(radians); + + return new Matrix4x4( + c, s, 0f, 0f, + -s, c, 0f, 0f, + 0f, 0f, 1f, 0f, + 0f, 0f, 0f, 1f + ); + } + + // TODO Find a better calculation for this + public static Matrix4x4 CreateRotation(Quaternion quaternion) + { + Vector3D angles = quaternion.ToAngles(); + return Identity * CreateRotationX(angles.X) * CreateRotationY(angles.Y) * CreateRotationZ(angles.Z); + } + + public static Matrix4x4 CreateLookMatrix(Vector3D forward, Vector3D up) + { + Vector3D z = forward.Normalized; + Vector3D x = up.Cross(z).Normalized; + Vector3D y = z.Cross(x); + + return new Matrix4x4( + x.X, y.X, z.X, 0f, + x.Y, y.Y, z.Y, 0f, + x.Z, y.Z, z.Z, 0f, + 0f, 0f, 0f, 1f + ); + } + + public static Matrix4x4 CreateLookMatrix(Vector3D position, Vector3D target, Vector3D up) + { + Vector3D z = position.FromTo(target).Normalized; + Vector3D x = up.Cross(z).Normalized; + Vector3D y = z.Cross(x); + + return new Matrix4x4( + x.X, y.X, z.X, 0f, + x.Y, y.Y, z.Y, 0f, + x.Z, y.Z, z.Z, 0f, + -x.Dot(position), -y.Dot(position), -z.Dot(position), 1f + ); + } + + public static Matrix4x4 CreatePerspectiveFieldOfView(float fieldOfViewInRadians, float aspectRatio, float nearPlane, float farPlane) + { + float yScale = 1f / Math.Tan(fieldOfViewInRadians / 2f); + float xScale = yScale / aspectRatio; + + return new Matrix4x4( + xScale, 0f, 0f, 0f, + 0f, yScale, 0f, 0f, + 0f, 0f, farPlane / (farPlane - nearPlane), 1f, + 0f, 0f, -nearPlane * farPlane / (farPlane - nearPlane), 0f + ); + } + + public static Matrix4x4 ToRightHanded(Matrix4x4 m) => new( + m.M11, m.M12, m.M13, m.M14, + m.M31, m.M32, m.M33, m.M34, + m.M21, m.M22, m.M23, m.M24, + m.M41, m.M42, m.M43, m.M44 + ); + + public override bool Equals(object? obj) => obj is Matrix4x4 matrix && this == matrix; + public bool Equals(Matrix4x4 other) => this == other; + + public override int GetHashCode() + { + HashCode hashCode = new(); + hashCode.Add(M11); hashCode.Add(M12); hashCode.Add(M13); hashCode.Add(M14); + hashCode.Add(M21); hashCode.Add(M22); hashCode.Add(M23); hashCode.Add(M24); + hashCode.Add(M31); hashCode.Add(M32); hashCode.Add(M33); hashCode.Add(M34); + hashCode.Add(M41); hashCode.Add(M42); hashCode.Add(M43); hashCode.Add(M44); + return hashCode.ToHashCode(); + } + + public override string ToString() => $"Matrix4x4({M11}, {M12}, {M13}, {M14},{M21}, {M22}, {M23}, {M24},{M31}, {M32}, {M33}, {M34},{M41}, {M42}, {M43}, {M44})"; +} + +/// +/// Provides extension methods for type. +/// +public static class Matrix4x4Extensions +{ + /// + public static float Determinant(this Matrix4x4 matrix) => Matrix4x4.Determinant(matrix); + + /// + public static Matrix4x4 ApplyTranslation(this Matrix4x4 matrix, Vector3D translation) => matrix * Matrix4x4.CreateTranslation(translation); + + /// + public static Matrix4x4 ApplyScale(this Matrix4x4 matrix, Vector3 scale) => matrix * Matrix4x4.CreateScale(scale); + + /// + public static Matrix4x4 ApplyRotationX(this Matrix4x4 matrix, float radians) => matrix * Matrix4x4.CreateRotationX(radians); + + /// + public static Matrix4x4 ApplyRotationY(this Matrix4x4 matrix, float radians) => matrix * Matrix4x4.CreateRotationY(radians); + + /// + public static Matrix4x4 ApplyRotationZ(this Matrix4x4 matrix, float radians) => matrix * Matrix4x4.CreateRotationZ(radians); + + /// + public static Matrix4x4 ApplyRotation(this Matrix4x4 matrix, Quaternion quaternion) => matrix * Matrix4x4.CreateRotation(quaternion); + + /// + public static Matrix4x4 ApplyLookRotationTo(this Matrix4x4 matrix, Vector3D forward, Vector3 up) => matrix * Matrix4x4.CreateLookMatrix(forward, up); + + /// + public static Matrix4x4 CreateLookMatrixTo(this Vector3D from, Vector3D to, Vector3 up) => Matrix4x4.CreateLookMatrix(from, to, up); + + /// + public static Matrix4x4 ApplyPerspectiveFieldOfView(this Matrix4x4 matrix, float fieldOfViewInRadians, float aspectRatio, float nearPlane, float farPlane) + => matrix * Matrix4x4.CreatePerspectiveFieldOfView(fieldOfViewInRadians, aspectRatio, nearPlane, farPlane); + + /// . public static Quaternion Normalize(Quaternion quaternion) => quaternion / Length(quaternion); + public static Quaternion LookAt(Vector3D origin, Vector3D target, Vector3D up) => LookAt(target - origin, up); + public static Quaternion LookAt(Vector3D target, Vector3D up) + { + Vector3D forward = target.Normalized; + + if (forward.LengthSquared() < 1e-6f) + return Identity; + + Vector3D right = up.Cross(forward).Normalized; + Vector3D newUp = forward.Cross(right); + + Matrix4x4 rot = new( + right.X, right.Y, right.Z, 0f, + newUp.X, newUp.Y, newUp.Z, 0f, + forward.X, forward.Y, forward.Z, 0f, + 0f, 0f, 0f, 1f + ); + + return FromRotationMatrix4x4(rot); + } + /// /// Rotates a around a axis by the specified angle (in radians). /// @@ -212,8 +233,8 @@ public readonly struct Quaternion(float x, float y, float z, float w) : IEquatab dot = -dot; } - if (dot > 0.9995f) - return Lerp(from, to, t); + if (dot > 0.999999f) + return to; float angle = Math.Acos(dot); float sinAngle = Math.Sin(angle); @@ -276,12 +297,11 @@ public readonly struct Quaternion(float x, float y, float z, float w) : IEquatab } /// - /// Calculates the from given . + /// Calculates the from given . /// - /// The axis of the rotation in . - /// The angle in radians. - /// The rotation calculated by the given . - public static System.Numerics.Matrix4x4 ToRotationMatrix4x4(Quaternion quaternion) + /// The rotation . + /// The rotation calculated by the given . + public static Matrix4x4 ToRotationMatrix4x4(Quaternion quaternion) { float m00 = 1 - 2 * (quaternion.Y * quaternion.Y + quaternion.Z * quaternion.Z); float m01 = 2 * (quaternion.X * quaternion.Y - quaternion.W * quaternion.Z); @@ -311,6 +331,52 @@ public readonly struct Quaternion(float x, float y, float z, float w) : IEquatab ); } + /// + /// Calculates the from given . + /// + /// The rotation . + /// The rotation calculated by the given . + public static Quaternion FromRotationMatrix4x4(Matrix4x4 martix) + { + float trace = martix.M11 + martix.M22 + martix.M33; + float w, x, y, z; + + if (trace > 0) + { + float s = Math.Sqrt(trace + 1.0f) * 2f; + w = .25f * s; + x = (martix.M23 - martix.M32) / s; + y = (martix.M31 - martix.M13) / s; + z = (martix.M12 - martix.M21) / s; + } + else if ((martix.M11 > martix.M22) && (martix.M11 > martix.M33)) + { + float s = Math.Sqrt(1.0f + martix.M11 - martix.M22 - martix.M33) * 2f; + w = (martix.M23 - martix.M32) / s; + x = .25f * s; + y = (martix.M12 + martix.M21) / s; + z = (martix.M31 + martix.M13) / s; + } + else if (martix.M22 > martix.M33) + { + float s = Math.Sqrt(1.0f + martix.M22 - martix.M11 - martix.M33) * 2f; + w = (martix.M31 - martix.M13) / s; + x = (martix.M12 + martix.M21) / s; + y = .25f * s; + z = (martix.M23 + martix.M32) / s; + } + else + { + float s = Math.Sqrt(1.0f + martix.M33 - martix.M11 - martix.M22) * 2f; + w = (martix.M12 - martix.M21) / s; + x = (martix.M31 + martix.M13) / s; + y = (martix.M23 + martix.M32) / s; + z = .25f * s; + } + + return new(x, y, z, w); + } + /// /// Checks if two s are approximately equal within a specified epsilon range. /// @@ -393,7 +459,7 @@ public static class QuaternionExtensions public static float Dot(this Quaternion left, Quaternion right) => Quaternion.Dot(left, right); /// - public static System.Numerics.Matrix4x4 ToRotationMatrix4x4(this Quaternion quaternion) => Quaternion.ToRotationMatrix4x4(quaternion); + public static Matrix4x4 ToRotationMatrix4x4(this Quaternion quaternion) => Quaternion.ToRotationMatrix4x4(quaternion); /// public static Quaternion CreateRotation(this Vector3D axis, float angle) => Quaternion.FromAxisAngle(axis, angle); diff --git a/Engine.Core/Serialization/EntityRegistry.cs b/Engine.Core/Serialization/EntityRegistry.cs deleted file mode 100644 index 6339ba1..0000000 --- a/Engine.Core/Serialization/EntityRegistry.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Engine.Core.Serialization; - -public class EntityRegistry -{ - public Event OnEntityRegistered = null!; - - private readonly Dictionary?> assignCallbacks = []; - private readonly Dictionary registeredEntities = []; - public IReadOnlyDictionary RegisteredEntities => registeredEntities; - - public void Add(IEntity entity) - { - if (registeredEntities.TryAdd(entity.Id, entity)) - OnEntityRegistered?.Invoke(this, new(entity)); - } - - public void QueueAssign(string id, Action setMethod) - { - assignCallbacks.TryAdd(id, null); - assignCallbacks[id] = assignCallbacks[id] + setMethod; - } - - public void AssignAll() - { - foreach ((string id, Action? action) in assignCallbacks) - action?.Invoke(registeredEntities[id]); - } - - public void Reset() - { - assignCallbacks.Clear(); - registeredEntities.Clear(); - } - - public readonly record struct EntityRegisteredArguments(IEntity Entity); -} diff --git a/Engine.Core/Serialization/IdentifiableRegistry.cs b/Engine.Core/Serialization/IdentifiableRegistry.cs new file mode 100644 index 0000000..a69e160 --- /dev/null +++ b/Engine.Core/Serialization/IdentifiableRegistry.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; + +namespace Engine.Core.Serialization; + +public class IdentifiableRegistry +{ + public Event OnEntityRegistered = null!; + + private readonly Dictionary?> assignCallbacks = []; + private readonly Dictionary registeredEntities = []; + public IReadOnlyDictionary RegisteredEntities => registeredEntities; + + public void Add(IIdentifiable identifiable) + { + if (registeredEntities.TryAdd(identifiable.Id, identifiable)) + OnEntityRegistered?.Invoke(this, new(identifiable)); + } + + public void QueueAssign(string id, Action setMethod) + { + assignCallbacks.TryAdd(id, null); + assignCallbacks[id] = assignCallbacks[id] + setMethod; + } + + public void AssignAll() + { + foreach ((string id, Action? action) in assignCallbacks) + action?.Invoke(registeredEntities[id]); + } + + public void Reset() + { + assignCallbacks.Clear(); + registeredEntities.Clear(); + } + + public readonly record struct EntityRegisteredArguments(IIdentifiable Entity); +} diff --git a/Engine.Core/Serialization/SerializedClass.cs b/Engine.Core/Serialization/SerializedClass.cs index 803c83b..4abe1b6 100644 --- a/Engine.Core/Serialization/SerializedClass.cs +++ b/Engine.Core/Serialization/SerializedClass.cs @@ -43,8 +43,8 @@ public class SerializedClass continue; object? value = privatePropertyInfo.GetValue(@class); - if (value is IEntity entity) - Private.Add(privatePropertyInfo.Name, entity.Id); + if (value is IIdentifiable identifiable) + Private.Add(privatePropertyInfo.Name, identifiable.Id); else Private.Add(privatePropertyInfo.Name, value); } @@ -61,8 +61,8 @@ public class SerializedClass continue; object? value = publicPropertyInfo.GetValue(@class); - if (value is IEntity entity) - Public.Add(publicPropertyInfo.Name, entity.Id); + if (value is IIdentifiable identifiable) + Public.Add(publicPropertyInfo.Name, identifiable.Id); else Public.Add(publicPropertyInfo.Name, value); } @@ -76,8 +76,8 @@ public class SerializedClass continue; object? value = privateFieldInfo.GetValue(@class); - if (value is IEntity entity) - Private.Add(privateFieldInfo.Name, entity.Id); + if (value is IIdentifiable identifiable) + Private.Add(privateFieldInfo.Name, identifiable.Id); else Private.Add(privateFieldInfo.Name, value); } @@ -91,8 +91,8 @@ public class SerializedClass continue; object? value = publicFieldInfo.GetValue(@class); - if (value is IEntity entity) - Public.Add(publicFieldInfo.Name, entity.Id); + if (value is IIdentifiable identifiable) + Public.Add(publicFieldInfo.Name, identifiable.Id); else Public.Add(publicFieldInfo.Name, value); } @@ -112,36 +112,36 @@ public class SerializedClass return instance; } - public object CreateInstance(EntityRegistry? entityRegistry) + public object CreateInstance(IdentifiableRegistry? identifiableRegistry) { - if (entityRegistry is null) + if (identifiableRegistry is null) return CreateInstance(); Type type = TypeFactory.GetType(Type); object instance = TypeFactory.Get(type); foreach ((string key, object? value) in Private) - AssignVariable(key, type, instance, value, PRIVATE_BINDING_FLAGS, entityRegistry); + AssignVariable(key, type, instance, value, PRIVATE_BINDING_FLAGS, identifiableRegistry); foreach ((string key, object? value) in Public) - AssignVariable(key, type, instance, value, PUBLIC_BINDING_FLAGS, entityRegistry); + AssignVariable(key, type, instance, value, PUBLIC_BINDING_FLAGS, identifiableRegistry); return instance; } - private static void AssignVariable(string key, Type type, object instance, object? value, BindingFlags bindingFlags, EntityRegistry entityRegistry) + private static void AssignVariable(string key, Type type, object instance, object? value, BindingFlags bindingFlags, IdentifiableRegistry identifiableRegistry) { if (type.GetField(key, bindingFlags) is FieldInfo fieldInfo) { - if (typeof(IEntity).IsAssignableFrom(fieldInfo.FieldType)) - entityRegistry.QueueAssign(value?.ToString() ?? "", (entity) => fieldInfo.SetValue(instance, entity)); + if (typeof(IIdentifiable).IsAssignableFrom(fieldInfo.FieldType)) + identifiableRegistry.QueueAssign(value?.ToString() ?? "", (entity) => fieldInfo.SetValue(instance, entity)); else fieldInfo.SetValue(instance, value); } else if (type.GetProperty(key, bindingFlags) is PropertyInfo propertyInfo) { - if (typeof(IEntity).IsAssignableFrom(propertyInfo.PropertyType)) - entityRegistry.QueueAssign(value?.ToString() ?? "", (entity) => propertyInfo.SetValue(instance, entity)); + if (typeof(IIdentifiable).IsAssignableFrom(propertyInfo.PropertyType)) + identifiableRegistry.QueueAssign(value?.ToString() ?? "", (entity) => propertyInfo.SetValue(instance, entity)); else propertyInfo.SetValue(instance, value); } diff --git a/Engine.Core/Systems/UniverseEntranceManager.cs b/Engine.Core/Systems/UniverseEntranceManager.cs index 60aea30..85f579e 100644 --- a/Engine.Core/Systems/UniverseEntranceManager.cs +++ b/Engine.Core/Systems/UniverseEntranceManager.cs @@ -9,7 +9,6 @@ public class UniverseEntranceManager : Internal.BehaviourIndependent private static System.Func GetPriority() => (b) => b.Priority; private readonly ActiveBehaviourCollectorOrdered enterUniverses = new(GetPriority(), SortByAscendingPriority()); - private readonly ActiveBehaviourCollectorOrdered exitUniverses = new(GetPriority(), SortByAscendingPriority()); private readonly List toCallEnterUniverses = new(32); private readonly List toCallExitUniverses = new(32); @@ -21,9 +20,8 @@ public class UniverseEntranceManager : Internal.BehaviourIndependent // event it tries to call OnUniverseObjectRegistered again on the same object, causing a duplicate entry error. Debug.Assert.AssertTrue(BehaviourController.Count == 1, $"{nameof(UniverseEntranceManager)} must be in it's own {nameof(IUniverseObject)} with no other {nameof(IBehaviour)}s attached at the moment. Failing to do so might cause instantiation or serialization issues."); enterUniverses.Assign(universe); - exitUniverses.Assign(universe); - foreach (IUniverseObject universeObject in universe.UniverseObjects) + foreach (IUniverseObject universeObject in universe) OnUniverseObjectRegistered(universe, new(universeObject)); universe.OnUniverseObjectRegistered.AddListener(OnUniverseObjectRegistered); @@ -33,9 +31,8 @@ public class UniverseEntranceManager : Internal.BehaviourIndependent protected override void OnExitedUniverse(IUniverse universe) { enterUniverses.Unassign(); - exitUniverses.Unassign(); - foreach (IUniverseObject universeObject in universe.UniverseObjects) + foreach (IUniverseObject universeObject in universe) OnUniverseObjectUnRegistered(universe, new(universeObject)); universe.OnUniverseObjectRegistered.RemoveListener(OnUniverseObjectRegistered); @@ -44,6 +41,8 @@ public class UniverseEntranceManager : Internal.BehaviourIndependent private void OnUniverseObjectUnRegistered(IUniverse sender, IUniverse.UniverseObjectUnRegisteredArguments args) { + args.UniverseObjectUnregistered.BehaviourController.GetBehavioursInChildren(toCallExitUniverses); + for (int i = toCallExitUniverses.Count - 1; i >= 0; i--) { IExitUniverse exitUniverse = toCallExitUniverses[i]; @@ -67,14 +66,8 @@ public class UniverseEntranceManager : Internal.BehaviourIndependent toCallEnterUniverses.Add(args.BehaviourCollected); } - private void OnExitUniverseCollected(IBehaviourCollector sender, IBehaviourCollector.BehaviourCollectedArguments args) - { - toCallExitUniverses.Add(args.BehaviourCollected); - } - public UniverseEntranceManager() { enterUniverses.OnCollected.AddListener(OnEnterUniverseCollected); - exitUniverses.OnCollected.AddListener(OnExitUniverseCollected); } } diff --git a/Engine.Core/Universe.cs b/Engine.Core/Universe.cs index 8e3c28f..95041e5 100644 --- a/Engine.Core/Universe.cs +++ b/Engine.Core/Universe.cs @@ -31,7 +31,7 @@ public class Universe : BaseEntity, IUniverse delegateOnUniverseObjectExitedUniverse = OnUniverseObjectExitedUniverse; } - public IReadOnlyList UniverseObjects => _universeObjects; + public IUniverseObject Root { get; private set; } = Factory.UniverseObjectFactory.Instantiate().SetUniverseObject("Root"); public UniverseTime Time { get; private set; } = new(); public UniverseTime UnscaledTime { get; private set; } = new(); @@ -63,14 +63,17 @@ public class Universe : BaseEntity, IUniverse if (!universeObject.Initialize()) throw new Exception($"{universeObject.Name} can't be initialized"); - for (int i = 0; i < universeObject.Children.Count; i++) - Register(universeObject.Children[i]); + if (universeObject.Parent == null) + universeObject.Parent = Root; _universeObjects.Add(universeObject); if (!universeObject.EnterUniverse(this)) throw new Exception($"{universeObject.Name} can't enter the universe"); + for (int i = 0; i < universeObject.Children.Count; i++) + Register(universeObject.Children[i]); + OnUniverseObjectRegistered?.Invoke(this, new(universeObject)); } @@ -113,15 +116,15 @@ public class Universe : BaseEntity, IUniverse protected override void InitializeInternal() { - foreach (IUniverseObject universeObject in UniverseObjects) + foreach (IUniverseObject universeObject in _universeObjects) universeObject.Initialize(); } protected override void FinalizeInternal() { base.FinalizeInternal(); - for (int i = UniverseObjects.Count - 1; i >= 0; i--) - Remove(UniverseObjects[i]); + for (int i = _universeObjects.Count - 1; i >= 0; i--) + Remove(_universeObjects[i]); } public void Update(UniverseTime engineTime) @@ -158,6 +161,6 @@ public class Universe : BaseEntity, IUniverse Remove(universeObject); } - public IEnumerator GetEnumerator() => _universeObjects.GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => _universeObjects.GetEnumerator(); + public IEnumerator GetEnumerator() => Root.TraverseChildren(); + IEnumerator IEnumerable.GetEnumerator() => Root.TraverseChildren(); } diff --git a/Engine.Integration/Engine.Integration.LiteNetLib/LiteNetLibClient.cs b/Engine.Integration/Engine.Integration.LiteNetLib/LiteNetLibClient.cs index cec0006..c6f578c 100644 --- a/Engine.Integration/Engine.Integration.LiteNetLib/LiteNetLibClient.cs +++ b/Engine.Integration/Engine.Integration.LiteNetLib/LiteNetLibClient.cs @@ -11,6 +11,7 @@ namespace Engine.Systems.Network; public class LiteNetLibClient : LiteNetLibCommunicatorBase, INetworkCommunicatorClient { private readonly NetDataWriter netDataWriter = new(); + private readonly NetDataWriter netDataWriterEncrypted = new(); private CancellationTokenSource? cancellationTokenSource = null; @@ -42,7 +43,20 @@ public class LiteNetLibClient : LiteNetLibCommunicatorBase, INetworkCommunicator public INetworkCommunicatorClient SendToServer(T packet, PacketDelivery packetDelivery) where T : class, new() { netDataWriter.Reset(); - netPacketProcessor.Write(netDataWriter, packet); + netDataWriterEncrypted.Reset(); + + if (packet is INetworkPacketEncrypted) + { + netPacketProcessor.Write(netDataWriterEncrypted, packet); + byte[] encryptedData = cryptor.Encrypt(netDataWriterEncrypted.CopyData()); + netDataWriter.Put(true); + netDataWriter.PutBytesWithLength(encryptedData); + } + else + { + netDataWriter.Put(false); + netPacketProcessor.Write(netDataWriter, packet); + } switch (packetDelivery) { diff --git a/Engine.Integration/Engine.Integration.LiteNetLib/LiteNetLibCommunicatorBase.cs b/Engine.Integration/Engine.Integration.LiteNetLib/LiteNetLibCommunicatorBase.cs index 4dd1016..daa7a0d 100644 --- a/Engine.Integration/Engine.Integration.LiteNetLib/LiteNetLibCommunicatorBase.cs +++ b/Engine.Integration/Engine.Integration.LiteNetLib/LiteNetLibCommunicatorBase.cs @@ -12,6 +12,7 @@ namespace Engine.Systems.Network; public abstract class LiteNetLibCommunicatorBase : Behaviour, IEnterUniverse, IExitUniverse, INetworkCommunicator { protected readonly NetPacketProcessor netPacketProcessor = new(); + protected readonly PacketCryptor cryptor = new("At4ywW9PGoWH3g==", "NmpMFTvd3pvUbA=="); // TODO implement public key exchange private readonly Dictionary> listeners = []; private readonly Dictionary _connections = []; @@ -54,7 +55,20 @@ public abstract class LiteNetLibCommunicatorBase : Behaviour, IEnterUniverse, IE private void NetworkReceiveEvent(NetPeer peer, NetPacketReader reader, byte channel, DeliveryMethod deliveryMethod) { - try { netPacketProcessor.ReadAllPackets(reader, peer); } + try + { + bool isEncrypted = reader.GetBool(); + if (isEncrypted) // TODO performance improvements + { + byte[] encryptedData = reader.GetBytesWithLength(); + byte[] decryptedData = cryptor.Decrypt(encryptedData); + NetDataReader innerReader = new(decryptedData); + netPacketProcessor.ReadAllPackets(innerReader, peer); + return; + } + + netPacketProcessor.ReadAllPackets(reader, peer); + } catch (Exception exception) { logger?.LogException(this, exception, force: true); } } diff --git a/Engine.Integration/Engine.Integration.LiteNetLib/LiteNetLibServer.cs b/Engine.Integration/Engine.Integration.LiteNetLib/LiteNetLibServer.cs index c9e8d62..0ba6fa4 100644 --- a/Engine.Integration/Engine.Integration.LiteNetLib/LiteNetLibServer.cs +++ b/Engine.Integration/Engine.Integration.LiteNetLib/LiteNetLibServer.cs @@ -13,6 +13,7 @@ public class LiteNetLibServer : LiteNetLibCommunicatorBase, INetworkCommunicator public int Port { get; private set; } = 8888; private readonly NetDataWriter netDataWriter = new(); + private readonly NetDataWriter netDataWriterEncrypted = new(); public LiteNetLibServer() : this(8888, 2) { } public LiteNetLibServer(int port, int maxConnectionCount) : base() @@ -53,11 +54,25 @@ public class LiteNetLibServer : LiteNetLibCommunicatorBase, INetworkCommunicator public INetworkCommunicatorServer SendToClient(IConnection connection, T packet, PacketDelivery packetDelivery) where T : class, new() { netDataWriter.Reset(); - netPacketProcessor.Write(netDataWriter, packet); - if (Manager.ConnectedPeerList.FirstOrDefault(p => p.Id.CompareTo(connection.Id) == 0) is not NetPeer netPeer) + if (Manager.ConnectedPeerList.FirstOrDefault(p => p.Id.ToString().CompareTo(connection.Id) == 0) is not NetPeer netPeer) throw new($"Peer {connection} couldn't be found."); + if (packet is INetworkPacketEncrypted) // TODO performance improvements + { + netDataWriterEncrypted.Reset(); + netPacketProcessor.Write(netDataWriterEncrypted, packet); + byte[] encryptedData = cryptor.Encrypt(netDataWriterEncrypted.CopyData()); + + netDataWriter.Put(true); + netDataWriter.PutBytesWithLength(encryptedData); + } + else + { + netDataWriter.Put(false); + netPacketProcessor.Write(netDataWriter, packet); + } + switch (packetDelivery) { case PacketDelivery.ReliableInOrder: netPeer.Send(netDataWriter, DeliveryMethod.ReliableOrdered); break; @@ -75,6 +90,23 @@ public class LiteNetLibServer : LiteNetLibCommunicatorBase, INetworkCommunicator netDataWriter.Reset(); netPacketProcessor.Write(netDataWriter, packet); + if (packet is INetworkPacketEncrypted) + { + netDataWriterEncrypted.Reset(); + + logger?.Log($"Encrypted Packet Sending"); + netPacketProcessor.Write(netDataWriterEncrypted, packet); + byte[] encryptedData = cryptor.Encrypt(netDataWriterEncrypted.CopyData()); + netDataWriter.PutBytesWithLength(encryptedData); + netDataWriter.Put(true); + } + else + { + logger?.Log($"Regular Packet Sending"); + netPacketProcessor.Write(netDataWriter, packet); + netDataWriter.Put(false); + } + switch (packetDelivery) { case PacketDelivery.ReliableInOrder: Manager.SendToAll(netDataWriter, DeliveryMethod.ReliableOrdered); break; diff --git a/Engine.Integration/Engine.Integration.LiteNetLib/PacketCryptor.cs b/Engine.Integration/Engine.Integration.LiteNetLib/PacketCryptor.cs new file mode 100644 index 0000000..3a3c5ba --- /dev/null +++ b/Engine.Integration/Engine.Integration.LiteNetLib/PacketCryptor.cs @@ -0,0 +1,26 @@ +using System.Security.Cryptography; +using System.Text; + +namespace Engine.Systems.Network; + +public class PacketCryptor // TODO performance improvements +{ + private readonly Aes aes = null!; + + private readonly ICryptoTransform encrpytor = null!; + private readonly ICryptoTransform decryptor = null!; + + public byte[] Encrypt(byte[] data) => encrpytor.TransformFinalBlock(data, 0, data.Length); + public byte[] Decrypt(byte[] data) => decryptor.TransformFinalBlock(data, 0, data.Length); + + public PacketCryptor(string key, string initializationVector) + { + aes = Aes.Create(); + + aes.Key = Encoding.UTF8.GetBytes(key); + aes.IV = Encoding.UTF8.GetBytes(initializationVector); + + encrpytor = aes.CreateEncryptor(); + decryptor = aes.CreateDecryptor(); + } +} diff --git a/Engine.Integration/Engine.Integration.MonoGame/Behaviours/MonoGameCamera2D.cs b/Engine.Integration/Engine.Integration.MonoGame/Behaviours/MonoGameCamera2D.cs index 911a7af..e8827be 100644 --- a/Engine.Integration/Engine.Integration.MonoGame/Behaviours/MonoGameCamera2D.cs +++ b/Engine.Integration/Engine.Integration.MonoGame/Behaviours/MonoGameCamera2D.cs @@ -5,7 +5,7 @@ using Engine.Core; namespace Engine.Integration.MonoGame; -public class MonoGameCamera2D : Behaviour, ICamera2D, IFirstFrameUpdate, IPreDraw +public class MonoGameCamera2D : Behaviour, ICamera2D, IFirstFrameUpdate, ILastFrameUpdate, IPreDraw { public Event OnMatrixTransformChanged { get; } = new(); public Event OnViewportChanged { get; } = new(); @@ -56,7 +56,7 @@ public class MonoGameCamera2D : Behaviour, ICamera2D, IFirstFrameUpdate, IPreDra get => _zoom; set { - float newValue = Engine.Core.Math.Max(0.1f, value); + float newValue = Math.Max(0.1f, value); if (_zoom == newValue) return; @@ -84,22 +84,21 @@ public class MonoGameCamera2D : Behaviour, ICamera2D, IFirstFrameUpdate, IPreDra return screenPosition.Scale(EngineConverterExtensions.screenScale); } + public void LastActiveFrame() => Transform = null!; public void FirstActiveFrame() { Graphics = BehaviourController.UniverseObject.Universe.FindRequiredBehaviour().Window.Graphics; Viewport = Graphics.GraphicsDevice.Viewport; + Transform = BehaviourController.GetRequiredBehaviour(); } public void PreDraw() { MatrixTransform = Matrix.CreateTranslation(new Vector3(-Position.X, Position.Y, 0f)) * - Matrix.CreateRotationZ(Rotation * Engine.Core.Math.DegreeToRadian) * + Matrix.CreateRotationZ(Rotation * Math.DegreeToRadian) * Matrix.CreateScale(Transform.Scale.X.Max(Transform.Scale.Y)) * Matrix.CreateScale(Zoom) * Matrix.CreateTranslation(new Vector3(_viewport.Width * .5f, _viewport.Height * .5f, 0f)); } - - protected sealed override void InitializeInternal() => Transform = BehaviourController.GetRequiredBehaviour(); - protected sealed override void FinalizeInternal() => Transform = null!; } diff --git a/Engine.Integration/Engine.Integration.MonoGame/Behaviours/MonoGameCamera3D.cs b/Engine.Integration/Engine.Integration.MonoGame/Behaviours/MonoGameCamera3D.cs index 591ed3b..1bf3468 100644 --- a/Engine.Integration/Engine.Integration.MonoGame/Behaviours/MonoGameCamera3D.cs +++ b/Engine.Integration/Engine.Integration.MonoGame/Behaviours/MonoGameCamera3D.cs @@ -5,17 +5,23 @@ using Engine.Core; namespace Engine.Integration.MonoGame; -public class MonoGameCamera3D : Behaviour, ICamera3D, IFirstFrameUpdate, IPreDraw +public class MonoGameCamera3D : Behaviour, ICamera3D, IFirstFrameUpdate, ILastFrameUpdate, IPreDraw { public Event OnViewChanged { get; } = new(); public Event OnProjectionChanged { get; } = new(); public Event OnViewportChanged { get; } = new(); - public Event OnFieldOfViewChanged { get; } = new(); + + public Event OnNearPlaneChanged { get; } = new(); + public Event OnFarPlaneChanged { get; } = new(); + public Event OnFieldOfViewChanged { get; } = new(); private Matrix _view = Matrix.Identity; private Matrix _projection = Matrix.Identity; private Viewport _viewport = default; - private float _fieldOfView = 1f; + private float _nearPlane = 0.01f; + private float _farPlane = 100f; + private float _fieldOfView = Math.DegreeToRadian * 70f; + private bool isRecalculationNeeded = true; public GraphicsDeviceManager Graphics { get; private set; } = null!; public ITransform3D Transform { get; private set; } = null!; @@ -47,12 +53,6 @@ public class MonoGameCamera3D : Behaviour, ICamera3D, IFirstFrameUpdate, IPreDra } } - public Vector3D Position - { - get => Transform.Position; - set => Transform.Position = value; - } - public Viewport Viewport { get => _viewport; @@ -63,32 +63,52 @@ public class MonoGameCamera3D : Behaviour, ICamera3D, IFirstFrameUpdate, IPreDra Viewport previousViewport = _viewport; _viewport = value; + SetForRecalculation(); OnViewportChanged.Invoke(this, new(previousViewport)); } } + public float NearPlane + { + get => _nearPlane; + set + { + float previousNearPlane = _nearPlane; + _nearPlane = value.Max(0.0001f); + SetForRecalculation(); + OnNearPlaneChanged.Invoke(this, new(previousNearPlane)); + } + } + + public float FarPlane + { + get => _farPlane; + set + { + float previousFarPlane = _farPlane; + _farPlane = value.Max(NearPlane); + SetForRecalculation(); + OnFarPlaneChanged.Invoke(this, new(previousFarPlane)); + } + } + public float FieldOfView { - get => _fieldOfView; + get => _fieldOfView * Math.RadianToDegree; set { - float newValue = Math.Max(0.1f, value); + value = value.Max(0.1f) * Math.DegreeToRadian; - if (_fieldOfView == newValue) + if (_fieldOfView == value) return; float previousFieldOfView = _fieldOfView; - _fieldOfView = newValue; + _fieldOfView = value; + SetForRecalculation(); OnFieldOfViewChanged.Invoke(this, new(previousFieldOfView)); } } - public Engine.Core.Quaternion Rotation - { - get => Transform.Rotation; - set => Transform.Rotation = value; - } - // TODO This causes delay since OnPreDraw calls assuming this is called in in Update public Ray3D ScreenToWorldRay(Vector2D screenPosition) { @@ -104,28 +124,61 @@ public class MonoGameCamera3D : Behaviour, ICamera3D, IFirstFrameUpdate, IPreDra public Vector2D WorldToScreenPosition(Vector3D worldPosition) => Viewport.Project(worldPosition.ToVector3(), _projection, _view, Matrix.Identity).ToVector3D(); + public void LastActiveFrame() => Transform.OnTransformUpdated.RemoveListener(SetDirtyOnTransformUpdate); public void FirstActiveFrame() { + Transform = BehaviourController.GetRequiredBehaviour(); Graphics = BehaviourController.UniverseObject.Universe.FindRequiredBehaviour().Window.Graphics; Viewport = Graphics.GraphicsDevice.Viewport; + + Transform.OnTransformUpdated.AddListener(SetDirtyOnTransformUpdate); } + private void SetDirtyOnTransformUpdate(ITransform3D sender) => SetForRecalculation(); + private void SetForRecalculation() => isRecalculationNeeded = true; + public void PreDraw() { - Vector3 cameraPosition = Position.ToVector3(); - View = Matrix.CreateLookAt( - cameraPosition, - Transform.Forward.ToVector3() + cameraPosition, - Transform.Up.ToVector3() - ); - Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, Viewport.AspectRatio, 0.1f, 100f); + if (!isRecalculationNeeded) + return; + + CalculateView(); + CalculateProjection(); + isRecalculationNeeded = false; } - protected sealed override void InitializeInternal() => Transform = BehaviourController.GetRequiredBehaviour(); - protected sealed override void FinalizeInternal() => Transform = null!; + private void CalculateView() + { + Vector3 forward = Vector3.Normalize(Transform.Forward.ToVector3()); + Vector3 up = Vector3.Normalize(Transform.Up.ToVector3()); + Vector3 right = Vector3.Normalize(Transform.Right.ToVector3()); + Vector3 position = Transform.Position.ToVector3(); + + View = new Matrix( + right.X, up.X, forward.X, 0, + right.Y, up.Y, forward.Y, 0, + right.Z, up.Z, forward.Z, 0, + -Vector3.Dot(right, position), + -Vector3.Dot(up, position), + -Vector3.Dot(forward, position), + 1 + ); + } + + private void CalculateProjection() + { + float yScale = 1f / (float)Math.Tan(_fieldOfView / 2f); + float xScale = yScale / Viewport.AspectRatio; + + Projection = new Matrix( + xScale, 0, 0, 0, + 0, yScale, 0, 0, + 0, 0, _farPlane / (_farPlane - _nearPlane), 1, + 0, 0, -_nearPlane * _farPlane / (_farPlane - _nearPlane), 0 + ); + } public readonly record struct ViewChangedArguments(Matrix PreviousView); public readonly record struct ProjectionChangedArguments(Matrix PreviousProjection); public readonly record struct ViewportChangedArguments(Viewport PreviousViewport); - public readonly record struct FieldOfViewChangedArguments(float PreviousFieldOfView); } diff --git a/Engine.Integration/Engine.Integration.MonoGame/EngineConverter.cs b/Engine.Integration/Engine.Integration.MonoGame/EngineConverter.cs index 32e48d1..354b196 100644 --- a/Engine.Integration/Engine.Integration.MonoGame/EngineConverter.cs +++ b/Engine.Integration/Engine.Integration.MonoGame/EngineConverter.cs @@ -41,6 +41,14 @@ public static class EngineConverterExtensions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector3 ToVector3(this Vector3D vector) => new(vector.X, vector.Y, vector.Z); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Matrix ToXnaMatrix(this Matrix4x4 m) => new( + m.M11, m.M12, m.M13, m.M14, + m.M21, m.M22, m.M23, m.M24, + m.M31, m.M32, m.M33, m.M34, + m.M41, m.M42, m.M43, m.M44 + ); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Microsoft.Xna.Framework.Quaternion ToXnaQuaternion(this Core.Quaternion quaternion) => new(quaternion.X, quaternion.Y, quaternion.Z, quaternion.W); diff --git a/Engine.Integration/Engine.Integration.MonoGame/MonoGameWindow.cs b/Engine.Integration/Engine.Integration.MonoGame/MonoGameWindow.cs index 7decf16..dd39082 100644 --- a/Engine.Integration/Engine.Integration.MonoGame/MonoGameWindow.cs +++ b/Engine.Integration/Engine.Integration.MonoGame/MonoGameWindow.cs @@ -19,10 +19,12 @@ public class MonoGameWindow : Game IsMouseVisible = true; Universe = universe ?? new Universe(); - Universe.InstantiateUniverseObject().SetUniverseObject("Window Container") + IUniverseObject monogameParent = Universe.InstantiateUniverseObject().SetUniverseObject("MonoGame"); + + Universe.InstantiateUniverseObject().SetUniverseObject("Window Container", monogameParent) .BehaviourController.AddBehaviour(this); - Universe.InstantiateUniverseObject().SetUniverseObject("Content Loader") + Universe.InstantiateUniverseObject().SetUniverseObject("Content Loader", monogameParent) .BehaviourController.AddBehaviour(); } diff --git a/Engine.Integration/Engine.Integration.Yaml/Converters/Abstract/IEngineTypeYamlConverter.cs b/Engine.Integration/Engine.Integration.Yaml/Converters/Abstract/IEngineTypeYamlConverter.cs index 873b796..f641442 100644 --- a/Engine.Integration/Engine.Integration.Yaml/Converters/Abstract/IEngineTypeYamlConverter.cs +++ b/Engine.Integration/Engine.Integration.Yaml/Converters/Abstract/IEngineTypeYamlConverter.cs @@ -8,6 +8,6 @@ namespace Engine.Serializers.Yaml; public interface IEngineTypeYamlConverter : IYamlTypeConverter { YamlSerializer Serializer { get; set; } - EntityRegistry EntityRegistry { get; set; } + IdentifiableRegistry IdentifiableRegistry { get; set; } IProgressionTracker ProgressionTracker { get; set; } } diff --git a/Engine.Integration/Engine.Integration.Yaml/Converters/BehaviourControllerConverter.cs b/Engine.Integration/Engine.Integration.Yaml/Converters/BehaviourControllerConverter.cs index 89391b4..530ae2c 100644 --- a/Engine.Integration/Engine.Integration.Yaml/Converters/BehaviourControllerConverter.cs +++ b/Engine.Integration/Engine.Integration.Yaml/Converters/BehaviourControllerConverter.cs @@ -36,7 +36,7 @@ public class BehaviourControllerConverter : EngineTypeYamlSerializerBase().Value; if (value.CompareTo(nameof(IBehaviourController.StateEnable)) != 0) diff --git a/Engine.Integration/Engine.Integration.Yaml/Converters/BehaviourConverter.cs b/Engine.Integration/Engine.Integration.Yaml/Converters/BehaviourConverter.cs index abc3b97..a475f96 100644 --- a/Engine.Integration/Engine.Integration.Yaml/Converters/BehaviourConverter.cs +++ b/Engine.Integration/Engine.Integration.Yaml/Converters/BehaviourConverter.cs @@ -36,7 +36,7 @@ public class BehaviourConverter : EngineTypeYamlSerializerBase throw new(); SerializedClass instanceSerializedClass = (SerializedClass)rootDeserializer(typeof(SerializedClass))!; ProgressionTracker.Set(isTrackingController ? .5f : ProgressionTracker.Progression, $"Creating {instanceSerializedClass.Type}"); - behaviour = (IBehaviour)instanceSerializedClass.CreateInstance(EntityRegistry); + behaviour = (IBehaviour)instanceSerializedClass.CreateInstance(IdentifiableRegistry); if (parser.Consume().Value.CompareTo(nameof(IBehaviour.StateEnable)) != 0) throw new(); diff --git a/Engine.Integration/Engine.Integration.Yaml/Converters/EngineTypeYamlConverterBase.cs b/Engine.Integration/Engine.Integration.Yaml/Converters/EngineTypeYamlConverterBase.cs index 134e922..99167ee 100644 --- a/Engine.Integration/Engine.Integration.Yaml/Converters/EngineTypeYamlConverterBase.cs +++ b/Engine.Integration/Engine.Integration.Yaml/Converters/EngineTypeYamlConverterBase.cs @@ -12,7 +12,7 @@ public abstract class EngineTypeYamlSerializerBase : IEngineTypeYamlConverter { protected const string SERIALIZED_SCALAR_NAME = "Properties"; - public EntityRegistry EntityRegistry { get; set; } = null!; + public IdentifiableRegistry IdentifiableRegistry { get; set; } = null!; public YamlSerializer Serializer { get; set; } = null!; public IProgressionTracker ProgressionTracker { get; set; } = null!; @@ -24,7 +24,7 @@ public abstract class EngineTypeYamlSerializerBase : IEngineTypeYamlConverter T? result = Read(parser, type, rootDeserializer); if (result is IEntity entity) - EntityRegistry.Add(entity); + IdentifiableRegistry.Add(entity); return result; } diff --git a/Engine.Integration/Engine.Integration.Yaml/Converters/StateEnableConverter.cs b/Engine.Integration/Engine.Integration.Yaml/Converters/StateEnableConverter.cs index 9c317b0..d5c0bc0 100644 --- a/Engine.Integration/Engine.Integration.Yaml/Converters/StateEnableConverter.cs +++ b/Engine.Integration/Engine.Integration.Yaml/Converters/StateEnableConverter.cs @@ -29,7 +29,7 @@ public class StateEnableConverter : EngineTypeYamlSerializerBase if (parser.Consume().Value.CompareTo(SERIALIZED_SCALAR_NAME) != 0) throw new(); SerializedClass instanceSerializedClass = (SerializedClass)rootDeserializer(typeof(SerializedClass))!; - stateEnable = (IStateEnable)instanceSerializedClass.CreateInstance(EntityRegistry); + stateEnable = (IStateEnable)instanceSerializedClass.CreateInstance(IdentifiableRegistry); parser.Consume(); diff --git a/Engine.Integration/Engine.Integration.Yaml/Converters/UniverseConverter.cs b/Engine.Integration/Engine.Integration.Yaml/Converters/UniverseConverter.cs index 6fdf87e..4a21d0e 100644 --- a/Engine.Integration/Engine.Integration.Yaml/Converters/UniverseConverter.cs +++ b/Engine.Integration/Engine.Integration.Yaml/Converters/UniverseConverter.cs @@ -22,7 +22,7 @@ public class UniverseConverter : EngineTypeYamlSerializerBase IUniverse universe; IStateEnable stateEnable; - List universeObjects; + IUniverseObject rootUniverseObject; parser.Consume(); @@ -34,16 +34,16 @@ public class UniverseConverter : EngineTypeYamlSerializerBase throw new(); SerializedClass instanceSerializedClass = (SerializedClass)rootDeserializer(typeof(SerializedClass))!; ProgressionTracker.Set(isTrackingController ? .2f : ProgressionTracker.Progression, $"Creating {instanceSerializedClass.Type}"); - universe = (IUniverse)instanceSerializedClass.CreateInstance(EntityRegistry); + universe = (IUniverse)instanceSerializedClass.CreateInstance(IdentifiableRegistry); if (parser.Consume().Value.CompareTo(nameof(IUniverse.StateEnable)) != 0) throw new(); stateEnable = (IStateEnable)rootDeserializer(typeof(IStateEnable))!; ProgressionTracker.Set(isTrackingController ? .5f : ProgressionTracker.Progression, $"Reading universe objects"); - if (parser.Consume().Value.CompareTo(nameof(IUniverse.UniverseObjects)) != 0) + if (parser.Consume().Value.CompareTo(nameof(IUniverse.Root)) != 0) throw new(); - universeObjects = (List)rootDeserializer(typeof(List))!; + rootUniverseObject = (IUniverseObject)rootDeserializer(typeof(IUniverseObject))!; parser.Consume(); @@ -52,13 +52,12 @@ public class UniverseConverter : EngineTypeYamlSerializerBase stateEnable.Assign(universe); universe.Assign(stateEnable); - ProgressionTracker.Set(isTrackingController ? .9f : ProgressionTracker.Progression, "Registering universe objects"); - for (int i = 0; i < universeObjects.Count; i++) + ProgressionTracker.Set(isTrackingController ? .9f : ProgressionTracker.Progression, "Registering root universe object"); + for (int i = rootUniverseObject.Children.Count - 1; i >= 0; i--) { - IUniverseObject uo = universeObjects[i]; - ProgressionTracker.Set(isTrackingController ? .9f + .1f * ((float)i / universeObjects.Count) : ProgressionTracker.Progression, $"Registering {uo.Name}"); - - universe.Register(uo); + IUniverseObject uo = rootUniverseObject.Children[i]; + ProgressionTracker.Set(isTrackingController ? .9f + .1f * ((float)i / rootUniverseObject.Children.Count) : ProgressionTracker.Progression, $"Registering {uo.Name}"); + universe.Root.AddChild(uo); } ProgressionTracker.Set(isTrackingController ? 1f : ProgressionTracker.Progression, $"Created {instanceSerializedClass.Type}"); @@ -72,7 +71,7 @@ public class UniverseConverter : EngineTypeYamlSerializerBase bool isTrackingController = ProgressionTracker.Progression.ApproximatelyEquals(0f); ProgressionTracker.Set(isTrackingController ? .25f : ProgressionTracker.Progression, $"Serializing universe"); - IEnumerable rootUniverseObjects = universe.UniverseObjects.Where(uo => uo.Parent is null); + IUniverseObject rootUniverseObject = universe.Root; emitter.Emit(new MappingStart()); @@ -85,8 +84,8 @@ public class UniverseConverter : EngineTypeYamlSerializerBase emitter.Emit(new Scalar(nameof(IUniverse.StateEnable))); serializer(universe.StateEnable); - emitter.Emit(new Scalar(nameof(IUniverse.UniverseObjects))); - serializer(rootUniverseObjects); + emitter.Emit(new Scalar(nameof(IUniverse.Root))); + serializer(rootUniverseObject); ProgressionTracker.Set(isTrackingController ? 1f : ProgressionTracker.Progression, $"Serialized universe"); emitter.Emit(new MappingEnd()); diff --git a/Engine.Integration/Engine.Integration.Yaml/Converters/UniverseObjectConverter.cs b/Engine.Integration/Engine.Integration.Yaml/Converters/UniverseObjectConverter.cs index 76268fe..47d8513 100644 --- a/Engine.Integration/Engine.Integration.Yaml/Converters/UniverseObjectConverter.cs +++ b/Engine.Integration/Engine.Integration.Yaml/Converters/UniverseObjectConverter.cs @@ -40,7 +40,7 @@ public class UniverseObjectSerializer : EngineTypeYamlSerializerBase().Value.CompareTo(nameof(IUniverseObject.StateEnable)) != 0) throw new(); diff --git a/Engine.Integration/Engine.Integration.Yaml/YamlSerializer.cs b/Engine.Integration/Engine.Integration.Yaml/YamlSerializer.cs index 1dfadd1..6df1e67 100644 --- a/Engine.Integration/Engine.Integration.Yaml/YamlSerializer.cs +++ b/Engine.Integration/Engine.Integration.Yaml/YamlSerializer.cs @@ -17,14 +17,14 @@ public class YamlSerializer : Core.Serialization.ISerializer private readonly YamlDotNet.Serialization.ISerializer serializer = null!; private readonly YamlDotNet.Serialization.IDeserializer deserializer = null!; - private readonly EntityRegistry entityRegistry = null!; + private readonly IdentifiableRegistry identifiableRegistry = null!; private readonly IProgressionTracker progressionTracker = null!; private readonly System.Threading.Lock Lock = new(); public YamlSerializer() { - entityRegistry = new(); + identifiableRegistry = new(); progressionTracker = new ProgressionTracker(); SerializerBuilder serializerBuilder = new SerializerBuilder() @@ -37,7 +37,7 @@ public class YamlSerializer : Core.Serialization.ISerializer foreach (IEngineTypeYamlConverter typeConverter in GetEngineYamlTypeConverters()) { typeConverter.Serializer = this; - typeConverter.EntityRegistry = entityRegistry; + typeConverter.IdentifiableRegistry = identifiableRegistry; typeConverter.ProgressionTracker = progressionTracker; deserializerBuilder = deserializerBuilder.WithTypeConverter(typeConverter); @@ -66,9 +66,9 @@ public class YamlSerializer : Core.Serialization.ISerializer { lock (Lock) { - entityRegistry.Reset(); + identifiableRegistry.Reset(); object result = deserializer.Deserialize(configuration)!; - entityRegistry.AssignAll(); + identifiableRegistry.AssignAll(); return result; } } @@ -77,9 +77,9 @@ public class YamlSerializer : Core.Serialization.ISerializer { lock (Lock) { - entityRegistry.Reset(); + identifiableRegistry.Reset(); object result = deserializer.Deserialize(configuration, type)!; - entityRegistry.AssignAll(); + identifiableRegistry.AssignAll(); return result; } } @@ -88,9 +88,9 @@ public class YamlSerializer : Core.Serialization.ISerializer { lock (Lock) { - entityRegistry.Reset(); + identifiableRegistry.Reset(); T result = deserializer.Deserialize(configuration); - entityRegistry.AssignAll(); + identifiableRegistry.AssignAll(); return result; } } diff --git a/Engine.Systems/Network/Abstract/INetworkPacketEncrypted.cs b/Engine.Systems/Network/Abstract/INetworkPacketEncrypted.cs new file mode 100644 index 0000000..f7c2bb1 --- /dev/null +++ b/Engine.Systems/Network/Abstract/INetworkPacketEncrypted.cs @@ -0,0 +1,3 @@ +namespace Engine.Systems.Network; + +public interface INetworkPacketEncrypted : INetworkPacket; diff --git a/Engine.Systems/Network/ClientBehaviour.cs b/Engine.Systems/Network/ClientBehaviour.cs new file mode 100644 index 0000000..889de6f --- /dev/null +++ b/Engine.Systems/Network/ClientBehaviour.cs @@ -0,0 +1,16 @@ +using Engine.Core; + +namespace Engine.Systems.Network; + +/// +/// Basic client behaviour that finds the in the universe in it's first active frame. +///
+/// Disclaimer: It implements and in virtual methods. +///
+public class ClientBehaviour : Behaviour, IFirstFrameUpdate, ILastFrameUpdate +{ + public INetworkCommunicatorClient Client { get; private set; } = null!; + + public virtual void FirstActiveFrame() => Client = Universe.FindRequiredBehaviour(); + public virtual void LastActiveFrame() => Client = null!; +} diff --git a/Engine.Systems/Network/Hasher.cs b/Engine.Systems/Network/Hasher.cs new file mode 100644 index 0000000..0f881e5 --- /dev/null +++ b/Engine.Systems/Network/Hasher.cs @@ -0,0 +1,21 @@ +namespace Engine.Systems.Network; + +public static class Hasher +{ + public static long FNV1a(string name) + { + const long fnvPrime = 1099511628211; + long result = 1469598103934665603; + + unchecked + { + foreach (char c in name) + { + result ^= c; + result *= fnvPrime; + } + } + + return result; + } +} diff --git a/Engine.Systems/Network/ServerBehaviour.cs b/Engine.Systems/Network/ServerBehaviour.cs new file mode 100644 index 0000000..9ffe2eb --- /dev/null +++ b/Engine.Systems/Network/ServerBehaviour.cs @@ -0,0 +1,16 @@ +using Engine.Core; + +namespace Engine.Systems.Network; + +/// +/// Basic server behaviour that finds the in the universe in it's first active frame. +///
+/// Disclaimer: It implements and in virtual methods. +///
+public class ServerBehaviour : Behaviour, IFirstFrameUpdate, ILastFrameUpdate +{ + public INetworkCommunicatorServer Server { get; private set; } = null!; + + public virtual void FirstActiveFrame() => Server = Universe.FindRequiredBehaviour(); + public virtual void LastActiveFrame() => Server = null!; +} diff --git a/Engine.Systems/Network/TypeHasher.cs b/Engine.Systems/Network/TypeHasher.cs index 82451d4..58dc572 100644 --- a/Engine.Systems/Network/TypeHasher.cs +++ b/Engine.Systems/Network/TypeHasher.cs @@ -8,18 +8,7 @@ public static class TypeHasher get { if (_fnv1a == 0) - unchecked - { - const long fnvPrime = 1099511628211; - _fnv1a = 1469598103934665603; - - string typeName = typeof(T).FullName ?? typeof(T).Name; - foreach (char c in typeName) - { - _fnv1a ^= c; - _fnv1a *= fnvPrime; - } - } + _fnv1a = Hasher.FNV1a(typeof(T).FullName ?? typeof(T).Name); return _fnv1a; }