From 22ab2b7e2cb388ecba7ca6a3349365f035edcfd1 Mon Sep 17 00:00:00 2001 From: Wulferis Date: Sun, 26 Jan 2025 17:36:56 +1100 Subject: [PATCH 01/15] Component Execution Order --- .../Attributes/ExecutionOrderAttribute.cs | 22 +++++++++++++++++++ Prowl.Runtime/GameObject/GameObject.cs | 14 ++++++++++++ Prowl.Runtime/Utils/RuntimeUtils.cs | 16 ++++++++++++++ 3 files changed, 52 insertions(+) create mode 100644 Prowl.Runtime/GameObject/Attributes/ExecutionOrderAttribute.cs diff --git a/Prowl.Runtime/GameObject/Attributes/ExecutionOrderAttribute.cs b/Prowl.Runtime/GameObject/Attributes/ExecutionOrderAttribute.cs new file mode 100644 index 000000000..91cef0122 --- /dev/null +++ b/Prowl.Runtime/GameObject/Attributes/ExecutionOrderAttribute.cs @@ -0,0 +1,22 @@ +// This file is part of the Prowl Game Engine +// Licensed under the MIT License. See the LICENSE file in the project root for details. + +using System; + +namespace Prowl.Runtime; + +/// +/// Indicates the order in which the marked component should execute its update methods in the Prowl Game Engine. +/// +/// +/// This attribute can only be applied to classes and cannot be used multiple times on the same class. +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] +public class ExecutionOrderAttribute : Attribute +{ + public int Order { get; } + public ExecutionOrderAttribute(int order) + { + Order = order; + } +} diff --git a/Prowl.Runtime/GameObject/GameObject.cs b/Prowl.Runtime/GameObject/GameObject.cs index 36064a6d6..9fd6f7ef1 100644 --- a/Prowl.Runtime/GameObject/GameObject.cs +++ b/Prowl.Runtime/GameObject/GameObject.cs @@ -512,6 +512,8 @@ public MonoBehaviour AddComponent(Type type) newComponent.Do(newComponent.InternalAwake); } + SortComponents(); + return newComponent; } @@ -548,6 +550,8 @@ public void AddComponent(MonoBehaviour comp) { comp.Do(comp.InternalAwake); } + + SortComponents(); } /// @@ -888,6 +892,16 @@ internal bool IsComponentRequired(MonoBehaviour requiredComponent, out Type depe return false; } + private void SortComponents() + { + _components.Sort((a, b) => + { + int orderA = RuntimeUtils.GetExecutionOrder(a) ?? 0; + int orderB = RuntimeUtils.GetExecutionOrder(b) ?? 0; + return orderA.CompareTo(orderB); + }); + } + private static GameObject Internal_Instantiate(GameObject obj) { if (obj.IsDestroyed) throw new Exception(obj.Name + " has been destroyed."); diff --git a/Prowl.Runtime/Utils/RuntimeUtils.cs b/Prowl.Runtime/Utils/RuntimeUtils.cs index 5781a224b..59bf0675d 100644 --- a/Prowl.Runtime/Utils/RuntimeUtils.cs +++ b/Prowl.Runtime/Utils/RuntimeUtils.cs @@ -43,11 +43,13 @@ public enum Platform public static class RuntimeUtils { private static readonly Dictionary s_deepCopyByAssignmentCache = []; + private static readonly Dictionary s_executionOrderCache = []; [OnAssemblyUnload] public static void ClearCache() { s_deepCopyByAssignmentCache.Clear(); + s_executionOrderCache.Clear(); } public static bool IsARM() => @@ -469,4 +471,18 @@ public static unsafe int AsInt(this bool b) => [MethodImpl(MethodImplOptions.AggressiveInlining)] private static unsafe int NonNormalizedAsInt(bool b) => (int)(*(byte*)(&b)); + + internal static int? GetExecutionOrder(MonoBehaviour a) + { + Type type = a.GetType(); + if (s_executionOrderCache.TryGetValue(type, out int order)) + return order; + ExecutionOrderAttribute? attr = type.GetCustomAttribute(); + if (attr != null) + { + s_executionOrderCache[type] = attr.Order; + return attr.Order; + } + return null; + } } From de6daf2c8fd27e315998beb28ce5656bbc3aafe5 Mon Sep 17 00:00:00 2001 From: Wulferis Date: Sun, 26 Jan 2025 17:37:45 +1100 Subject: [PATCH 02/15] Rigidbody3D Added Properties For Velocity, AngularVelocity and Torque --- .../Components/Physics/Rigidbody3D.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/Prowl.Runtime/Components/Physics/Rigidbody3D.cs b/Prowl.Runtime/Components/Physics/Rigidbody3D.cs index ad45428b1..2f3757d3b 100644 --- a/Prowl.Runtime/Components/Physics/Rigidbody3D.cs +++ b/Prowl.Runtime/Components/Physics/Rigidbody3D.cs @@ -123,6 +123,33 @@ public double Restitution } } + /// + /// Gets or sets the Linear Velocity of this Rigidbody3D. + /// + public Vector3 Velocity + { + get => new(_body.Velocity.X, _body.Velocity.Y, _body.Velocity.Z); + set => _body.Velocity = new(value.x, value.y, value.z); + } + + /// + /// Gets or sets the Angular Velocity of this Rigidbody3D. + /// + public Vector3 AngularVelocity + { + get => new(_body.AngularVelocity.X, _body.AngularVelocity.Y, _body.AngularVelocity.Z); + set => _body.AngularVelocity = new(value.x, value.y, value.z); + } + + /// + /// Gets or sets the Torque of this Rigidbody3D. + /// + public Vector3 Torque + { + get => new Vector3(_body.Torque.X, _body.Torque.Y, _body.Torque.Z); + set => _body.Torque = new JVector(value.x, value.y, value.z); + } + [SerializeIgnore, CloneField(CloneFieldFlags.Skip)] internal RigidBody _body; From 517e42d435486a0e83d8232c1c19b86a8f8d832e Mon Sep 17 00:00:00 2001 From: Wulferis Date: Sun, 26 Jan 2025 17:38:04 +1100 Subject: [PATCH 03/15] Rigidbody3D AddForce, AddTorque and AddForceAtPosition --- Prowl.Runtime/Components/Physics/Rigidbody3D.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Prowl.Runtime/Components/Physics/Rigidbody3D.cs b/Prowl.Runtime/Components/Physics/Rigidbody3D.cs index 2f3757d3b..5563d712d 100644 --- a/Prowl.Runtime/Components/Physics/Rigidbody3D.cs +++ b/Prowl.Runtime/Components/Physics/Rigidbody3D.cs @@ -246,4 +246,19 @@ internal void UpdateTransform(RigidBody rb) rb.Orientation = new JQuaternion(Transform.rotation.x, Transform.rotation.y, Transform.rotation.z, Transform.rotation.w); } } + + public void AddForce(Vector3 velocity) + { + _body.AddForce(new JVector(velocity.x, velocity.y, velocity.z)); + } + + public void AddForceAtPosition(Vector3 velocity, Vector3 worldPosition) + { + _body.AddForce(new JVector(velocity.x, velocity.y, velocity.z), new JVector(worldPosition.x, worldPosition.y, worldPosition.z)); + } + + public void AddTorque(Vector3 torque) + { + Torque += torque; + } } From 2c7a59a0ef77ab68142a04b820c219cedd5bc221 Mon Sep 17 00:00:00 2001 From: Wulferis Date: Sun, 26 Jan 2025 19:08:49 +1100 Subject: [PATCH 04/15] Dont recompile in playmode --- Prowl.Editor/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Prowl.Editor/Program.cs b/Prowl.Editor/Program.cs index 96c98b4ba..27f00279b 100644 --- a/Prowl.Editor/Program.cs +++ b/Prowl.Editor/Program.cs @@ -178,7 +178,7 @@ private static int Run(CliOpenOptions options) public static void CheckReloadingAssemblies() { - if (IsReloadingExternalAssemblies && Screen.IsFocused) + if (IsReloadingExternalAssemblies && Screen.IsFocused && PlayMode.Current == PlayMode.Mode.Editing) { IsReloadingExternalAssemblies = false; From 8dd213a08d1f8597e70e7f21bfbc786a405117dd Mon Sep 17 00:00:00 2001 From: Wulferis Date: Sun, 26 Jan 2025 19:09:08 +1100 Subject: [PATCH 05/15] Exit playmode on close so Temp Scene isnt the active playing scene --- Prowl.Editor/Program.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Prowl.Editor/Program.cs b/Prowl.Editor/Program.cs index 27f00279b..8fd56a8d2 100644 --- a/Prowl.Editor/Program.cs +++ b/Prowl.Editor/Program.cs @@ -167,6 +167,9 @@ private static int Run(CliOpenOptions options) Application.Quitting += () => { + if (PlayMode.Current == PlayMode.Mode.Playing) + PlayMode.Stop(); + if (Project.HasProject) Project.Active.SaveTempScene(); }; From cf7b3ceac7b49934df0bfcc9d028a65c56811cf4 Mon Sep 17 00:00:00 2001 From: Wulferis Date: Sun, 26 Jan 2025 19:09:33 +1100 Subject: [PATCH 06/15] Debug.DrawWireCircle & Fixed API using Floats not Doubles --- Prowl.Runtime/Debug.cs | 98 ++++++++++++++++++++++++------------------ 1 file changed, 57 insertions(+), 41 deletions(-) diff --git a/Prowl.Runtime/Debug.cs b/Prowl.Runtime/Debug.cs index 9f9abb57c..1ed44f784 100644 --- a/Prowl.Runtime/Debug.cs +++ b/Prowl.Runtime/Debug.cs @@ -217,12 +217,13 @@ public static void PopMatrix() public static void DrawTriangle(Vector3 a, Vector3 b, Vector3 c, Color color) => s_gizmoBuilder.DrawTriangle(a, b, c, color); public static void DrawWireCube(Vector3 center, Vector3 halfExtents, Color color) => s_gizmoBuilder.DrawWireCube(center, halfExtents, color); public static void DrawCube(Vector3 center, Vector3 halfExtents, Color color) => s_gizmoBuilder.DrawCube(center, halfExtents, color); - public static void DrawWireSphere(Vector3 center, float radius, Color color, int segments = 16) => s_gizmoBuilder.DrawWireSphere(center, radius, color, segments); - public static void DrawSphere(Vector3 center, float radius, Color color, int segments = 16) => s_gizmoBuilder.DrawSphere(center, radius, color, segments); - public static void DrawWireCone(Vector3 start, Vector3 direction, float radius, Color color, int segments = 16) => s_gizmoBuilder.DrawWireCone(start, direction, radius, color, segments); + public static void DrawWireCircle(Vector3 center, Vector3 normal, double radius, Color color, int segments = 16) => s_gizmoBuilder.DrawCircle(center, normal, radius, color, segments); + public static void DrawWireSphere(Vector3 center, double radius, Color color, int segments = 16) => s_gizmoBuilder.DrawWireSphere(center, radius, color, segments); + public static void DrawSphere(Vector3 center, double radius, Color color, int segments = 16) => s_gizmoBuilder.DrawSphere(center, radius, color, segments); + public static void DrawWireCone(Vector3 start, Vector3 direction, double radius, Color color, int segments = 16) => s_gizmoBuilder.DrawWireCone(start, direction, radius, color, segments); public static void DrawArrow(Vector3 start, Vector3 direction, Color color) => s_gizmoBuilder.DrawArrow(start, direction, color); - public static void DrawIcon(Texture2D icon, Vector3 center, float scale, Color color) => s_gizmoBuilder.DrawIcon(icon, center, scale, color); + public static void DrawIcon(Texture2D icon, Vector3 center, double scale, Color color) => s_gizmoBuilder.DrawIcon(icon, center, scale, color); #endregion @@ -259,7 +260,7 @@ public struct IconDrawCall { public Texture2D texture; public Vector3 center; - public float scale; + public double scale; public Color color; } @@ -412,22 +413,22 @@ public void DrawCube(Vector3 center, Vector3 halfExtents, Color color) AddTriangle(vertices[0], vertices[5], vertices[1], uvs[0], uvs[2], uvs[3], color); } - public void DrawWireSphere(Vector3 center, float radius, Color color, int segments = 16) + public void DrawWireSphere(Vector3 center, double radius, Color color, int segments = 16) { - float step = MathF.PI * 2 / segments; + double step = MathF.PI * 2 / segments; for (int i = 0; i < segments; i++) { - float angle1 = i * step; - float angle2 = (i + 1) * step; + double angle1 = i * step; + double angle2 = (i + 1) * step; - Vector3 a = new(MathF.Cos(angle1) * radius + center.x, - MathF.Sin(angle1) * radius + center.y, + Vector3 a = new(Math.Cos(angle1) * radius + center.x, + Math.Sin(angle1) * radius + center.y, center.z ); - Vector3 b = new(MathF.Cos(angle2) * radius + center.x, - MathF.Sin(angle2) * radius + center.y, + Vector3 b = new(Math.Cos(angle2) * radius + center.x, + Math.Sin(angle2) * radius + center.y, center.z ); @@ -436,17 +437,17 @@ public void DrawWireSphere(Vector3 center, float radius, Color color, int segmen for (int i = 0; i < segments; i++) { - float angle1 = i * step; - float angle2 = (i + 1) * step; + double angle1 = i * step; + double angle2 = (i + 1) * step; - Vector3 a = new(MathF.Cos(angle1) * radius + center.x, + Vector3 a = new(Math.Cos(angle1) * radius + center.x, center.y, - MathF.Sin(angle1) * radius + center.z + Math.Sin(angle1) * radius + center.z ); - Vector3 b = new(MathF.Cos(angle2) * radius + center.x, + Vector3 b = new(Math.Cos(angle2) * radius + center.x, center.y, - MathF.Sin(angle2) * radius + center.z + Math.Sin(angle2) * radius + center.z ); AddLine(a, b, color); @@ -454,37 +455,52 @@ public void DrawWireSphere(Vector3 center, float radius, Color color, int segmen for (int i = 0; i < segments; i++) { - float angle1 = i * step; - float angle2 = (i + 1) * step; + double angle1 = i * step; + double angle2 = (i + 1) * step; Vector3 a = new(center.x, - MathF.Cos(angle1) * radius + center.y, - MathF.Sin(angle1) * radius + center.z + Math.Cos(angle1) * radius + center.y, + Math.Sin(angle1) * radius + center.z ); Vector3 b = new(center.x, - MathF.Cos(angle2) * radius + center.y, - MathF.Sin(angle2) * radius + center.z + Math.Cos(angle2) * radius + center.y, + Math.Sin(angle2) * radius + center.z ); AddLine(a, b, color); } } - public void DrawSphere(Vector3 center, float radius, Color color, int segments = 16) + public void DrawCircle(Vector3 center, Vector3 normal, double radius, Color color, int segments) + { + Vector3 u = Vector3.Normalize(Vector3.Cross(normal, Vector3.up)); + Vector3 v = Vector3.Normalize(Vector3.Cross(u, normal)); + double step = MathF.PI * 2 / segments; + for (int i = 0; i < segments; i++) + { + double angle1 = i * step; + double angle2 = (i + 1) * step; + Vector3 a = center + radius * (Math.Cos(angle1) * u + Math.Sin(angle1) * v); + Vector3 b = center + radius * (Math.Cos(angle2) * u + Math.Sin(angle2) * v); + AddLine(a, b, color); + } + } + + public void DrawSphere(Vector3 center, double radius, Color color, int segments = 16) { int latitudeSegments = segments; int longitudeSegments = segments * 2; for (int lat = 0; lat < latitudeSegments; lat++) { - float theta1 = lat * MathF.PI / latitudeSegments; - float theta2 = (lat + 1) * MathF.PI / latitudeSegments; + double theta1 = lat * MathF.PI / latitudeSegments; + double theta2 = (lat + 1) * MathF.PI / latitudeSegments; for (int lon = 0; lon < longitudeSegments; lon++) { - float phi1 = lon * 2 * MathF.PI / longitudeSegments; - float phi2 = (lon + 1) * 2 * MathF.PI / longitudeSegments; + double phi1 = lon * 2 * MathF.PI / longitudeSegments; + double phi2 = (lon + 1) * 2 * MathF.PI / longitudeSegments; Vector3 v1 = CalculatePointOnSphere(theta1, phi1, radius, center); Vector3 v2 = CalculatePointOnSphere(theta1, phi2, radius, center); @@ -500,11 +516,11 @@ public void DrawSphere(Vector3 center, float radius, Color color, int segments = } } - private Vector3 CalculatePointOnSphere(float theta, float phi, float radius, Vector3 center) + private Vector3 CalculatePointOnSphere(double theta, double phi, double radius, Vector3 center) { - float x = MathF.Sin(theta) * MathF.Cos(phi); - float y = MathF.Cos(theta); - float z = MathF.Sin(theta) * MathF.Sin(phi); + double x = Math.Sin(theta) * Math.Cos(phi); + double y = Math.Cos(theta); + double z = Math.Sin(theta) * Math.Sin(phi); return new Vector3( x * radius + center.x, @@ -513,9 +529,9 @@ private Vector3 CalculatePointOnSphere(float theta, float phi, float radius, Vec ); } - public void DrawWireCone(Vector3 start, Vector3 direction, float radius, Color color, int segments = 16) + public void DrawWireCone(Vector3 start, Vector3 direction, double radius, Color color, int segments = 16) { - float step = MathF.PI * 2 / segments; + double step = MathF.PI * 2 / segments; Vector3 tip = start + direction; // Normalize the direction vector @@ -527,12 +543,12 @@ public void DrawWireCone(Vector3 start, Vector3 direction, float radius, Color c for (int i = 0; i < segments; i++) { - float angle1 = i * step; - float angle2 = (i + 1) * step; + double angle1 = i * step; + double angle2 = (i + 1) * step; // Calculate circle points using the perpendicular vectors - Vector3 a = start + radius * (MathF.Cos(angle1) * u + MathF.Sin(angle1) * v); - Vector3 b = start + radius * (MathF.Cos(angle2) * u + MathF.Sin(angle2) * v); + Vector3 a = start + radius * (Math.Cos(angle1) * u + Math.Sin(angle1) * v); + Vector3 b = start + radius * (Math.Cos(angle2) * u + Math.Sin(angle2) * v); AddLine(a, b, color); if (i == 0 || i == segments / 4 || i == segments / 2 || i == segments * 3 / 4) @@ -562,7 +578,7 @@ public void DrawArrow(Vector3 start, Vector3 direction, Color color) } - public void DrawIcon(Texture2D icon, Vector3 center, float scale, Color color) => _icons.Add(new IconDrawCall { texture = icon, center = center, scale = scale, color = color }); + public void DrawIcon(Texture2D icon, Vector3 center, double scale, Color color) => _icons.Add(new IconDrawCall { texture = icon, center = center, scale = scale, color = color }); public (Mesh? wire, Mesh? solid) UpdateMesh(bool cameraRelative, Vector3 cameraPosition) { From b458eb6cf706b211256b5c3c76b35b758b3d0d68 Mon Sep 17 00:00:00 2001 From: Wulferis Date: Sun, 26 Jan 2025 19:09:51 +1100 Subject: [PATCH 07/15] Fixed Physics Raycast API using Floats not Doubles --- Prowl.Runtime/Physics.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Prowl.Runtime/Physics.cs b/Prowl.Runtime/Physics.cs index 355f45f30..12b7a7bc0 100644 --- a/Prowl.Runtime/Physics.cs +++ b/Prowl.Runtime/Physics.cs @@ -291,7 +291,7 @@ public static bool Raycast(Vector3 origin, Vector3 direction, out RaycastHit hit /// /// Casts a ray against all colliders in the scene within a maximum distance. /// - public static bool Raycast(Vector3 origin, Vector3 direction, float maxDistance) + public static bool Raycast(Vector3 origin, Vector3 direction, double maxDistance) { direction = direction.normalized * maxDistance; var jOrigin = new JVector(origin.x, origin.y, origin.z); @@ -305,7 +305,7 @@ public static bool Raycast(Vector3 origin, Vector3 direction, float maxDistance) /// /// Casts a ray against all colliders in the scene within a maximum distance and returns detailed information about the hit. /// - public static bool Raycast(Vector3 origin, Vector3 direction, float maxDistance, out RaycastHit hitInfo) + public static bool Raycast(Vector3 origin, Vector3 direction, double maxDistance, out RaycastHit hitInfo) { direction = direction.normalized * maxDistance; var jOrigin = new JVector(origin.x, origin.y, origin.z); @@ -333,7 +333,7 @@ public static bool Raycast(Vector3 origin, Vector3 direction, float maxDistance, /// /// Casts a ray against all colliders in the scene with the specified layer mask. /// - public static bool Raycast(Vector3 origin, Vector3 direction, float maxDistance, LayerMask layerMask) + public static bool Raycast(Vector3 origin, Vector3 direction, double maxDistance, LayerMask layerMask) { direction = direction.normalized * maxDistance; var jOrigin = new JVector(origin.x, origin.y, origin.z); @@ -347,7 +347,7 @@ public static bool Raycast(Vector3 origin, Vector3 direction, float maxDistance, /// /// Casts a ray against all colliders in the scene with the specified layer mask and returns detailed information about the hit. /// - public static bool Raycast(Vector3 origin, Vector3 direction, out RaycastHit hitInfo, float maxDistance, LayerMask layerMask) + public static bool Raycast(Vector3 origin, Vector3 direction, out RaycastHit hitInfo, double maxDistance, LayerMask layerMask) { direction = direction.normalized * maxDistance; var jOrigin = new JVector(origin.x, origin.y, origin.z); From b429f101e101dec8243cdd0a79dee07341cc312b Mon Sep 17 00:00:00 2001 From: Wulferis Date: Sun, 26 Jan 2025 19:10:00 +1100 Subject: [PATCH 08/15] Fixed Physics Raycast MaxDistance not working --- Prowl.Runtime/Physics.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Prowl.Runtime/Physics.cs b/Prowl.Runtime/Physics.cs index 12b7a7bc0..a8bf67c19 100644 --- a/Prowl.Runtime/Physics.cs +++ b/Prowl.Runtime/Physics.cs @@ -293,13 +293,13 @@ public static bool Raycast(Vector3 origin, Vector3 direction, out RaycastHit hit /// public static bool Raycast(Vector3 origin, Vector3 direction, double maxDistance) { - direction = direction.normalized * maxDistance; + direction = direction.normalized; var jOrigin = new JVector(origin.x, origin.y, origin.z); var jDirection = new JVector(direction.x, direction.y, direction.z); return _world.DynamicTree.RayCast(jOrigin, jDirection, PreFilter, PostFilter, - out _, out _, out _); + out _, out _, out var dist) && dist <= maxDistance; } /// @@ -307,14 +307,14 @@ public static bool Raycast(Vector3 origin, Vector3 direction, double maxDistance /// public static bool Raycast(Vector3 origin, Vector3 direction, double maxDistance, out RaycastHit hitInfo) { - direction = direction.normalized * maxDistance; + direction = direction.normalized; var jOrigin = new JVector(origin.x, origin.y, origin.z); var jDirection = new JVector(direction.x, direction.y, direction.z); hitInfo = new RaycastHit(); bool hit = _world.DynamicTree.RayCast(jOrigin, jDirection, PreFilter, PostFilter, - out IDynamicTreeProxy shape, out JVector normal, out double lambda); + out IDynamicTreeProxy shape, out JVector normal, out double lambda) && lambda <= maxDistance; if (hit) { @@ -335,13 +335,13 @@ public static bool Raycast(Vector3 origin, Vector3 direction, double maxDistance /// public static bool Raycast(Vector3 origin, Vector3 direction, double maxDistance, LayerMask layerMask) { - direction = direction.normalized * maxDistance; + direction = direction.normalized; var jOrigin = new JVector(origin.x, origin.y, origin.z); var jDirection = new JVector(direction.x, direction.y, direction.z); return _world.DynamicTree.RayCast(jOrigin, jDirection, shape => PreFilterWithLayer(shape, layerMask), PostFilter, - out _, out _, out _); + out _, out _, out double lambda) && lambda <= maxDistance; } /// @@ -349,14 +349,14 @@ public static bool Raycast(Vector3 origin, Vector3 direction, double maxDistance /// public static bool Raycast(Vector3 origin, Vector3 direction, out RaycastHit hitInfo, double maxDistance, LayerMask layerMask) { - direction = direction.normalized * maxDistance; + direction = direction.normalized; var jOrigin = new JVector(origin.x, origin.y, origin.z); var jDirection = new JVector(direction.x, direction.y, direction.z); hitInfo = new RaycastHit(); bool hit = _world.DynamicTree.RayCast(jOrigin, jDirection, shape => PreFilterWithLayer(shape, layerMask), PostFilter, - out IDynamicTreeProxy shape, out JVector normal, out double lambda); + out IDynamicTreeProxy shape, out JVector normal, out double lambda) && lambda <= maxDistance; if (hit) { From f491f3aaff2352e9c8ae3bab1691fac567f0ad18 Mon Sep 17 00:00:00 2001 From: Wulferis Date: Sun, 26 Jan 2025 19:42:31 +1100 Subject: [PATCH 09/15] Fixed RaycastHit.Rigidbody not always being assigned --- Prowl.Runtime/Components/Physics/Rigidbody3D.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Prowl.Runtime/Components/Physics/Rigidbody3D.cs b/Prowl.Runtime/Components/Physics/Rigidbody3D.cs index 5563d712d..53dfccdb6 100644 --- a/Prowl.Runtime/Components/Physics/Rigidbody3D.cs +++ b/Prowl.Runtime/Components/Physics/Rigidbody3D.cs @@ -218,6 +218,7 @@ internal void UpdateProperties(RigidBody rb) rb.Restitution = restitution; rb.Tag = new RigidBodyUserData() { + Rigidbody = this, Layer = GameObject.layerIndex, //HasTransformConstraints = rotationConstraints != Vector3Int.one || translationConstraints != Vector3Int.one, //RotationConstraint = new JVector(rotationConstraints.x, rotationConstraints.y, rotationConstraints.z), From 2322041649eae46acb5c4f408b8e351c41b359d7 Mon Sep 17 00:00:00 2001 From: Wulferis Date: Sun, 26 Jan 2025 21:12:38 +1100 Subject: [PATCH 10/15] Coroutine Wait for FixedUpdate should call After FixedUpdate --- Prowl.Runtime/SceneManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Prowl.Runtime/SceneManager.cs b/Prowl.Runtime/SceneManager.cs index 69acca8ec..e10b2fdf5 100644 --- a/Prowl.Runtime/SceneManager.cs +++ b/Prowl.Runtime/SceneManager.cs @@ -126,8 +126,8 @@ public static void PhysicsUpdate() List activeGOs = Scene.ActiveObjects.ToList(); ForeachComponent(activeGOs, (x) => { - x.Do(x.UpdateFixedUpdateCoroutines); x.Do(x.FixedUpdate); + x.Do(x.UpdateFixedUpdateCoroutines); }); } From 194a56f2164515451567c10b391f895d7b76da50 Mon Sep 17 00:00:00 2001 From: Wulferis Date: Sun, 26 Jan 2025 21:54:49 +1100 Subject: [PATCH 11/15] Prevent colliders with size 0, as it results in 0 mass and breaks jitter --- Prowl.Runtime/Components/Physics/Colliders/BoxCollider.cs | 2 +- Prowl.Runtime/Components/Physics/Colliders/CapsuleCollider.cs | 2 +- Prowl.Runtime/Components/Physics/Colliders/ConeCollider.cs | 2 +- Prowl.Runtime/Components/Physics/Colliders/CylinderCollider.cs | 2 +- Prowl.Runtime/Components/Physics/Colliders/SphereCollider.cs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Prowl.Runtime/Components/Physics/Colliders/BoxCollider.cs b/Prowl.Runtime/Components/Physics/Colliders/BoxCollider.cs index 407845ef3..d7a914479 100644 --- a/Prowl.Runtime/Components/Physics/Colliders/BoxCollider.cs +++ b/Prowl.Runtime/Components/Physics/Colliders/BoxCollider.cs @@ -26,5 +26,5 @@ public Vector3 Size } } - public override RigidBodyShape[] CreateShapes() => [new BoxShape(size.x, size.y, size.z)]; + public override RigidBodyShape[] CreateShapes() => [new BoxShape(MathD.Max(size.x, 0.01), MathD.Max(size.y, 0.01), MathD.Max(size.z, 0.01))]; } diff --git a/Prowl.Runtime/Components/Physics/Colliders/CapsuleCollider.cs b/Prowl.Runtime/Components/Physics/Colliders/CapsuleCollider.cs index 17a8ab83a..216ec8d51 100644 --- a/Prowl.Runtime/Components/Physics/Colliders/CapsuleCollider.cs +++ b/Prowl.Runtime/Components/Physics/Colliders/CapsuleCollider.cs @@ -34,5 +34,5 @@ public float Height } } - public override RigidBodyShape[] CreateShapes() => [new CapsuleShape(radius, height)]; + public override RigidBodyShape[] CreateShapes() => [new CapsuleShape(MathD.Max(radius, 0.01), MathD.Max(height, 0.01))]; } diff --git a/Prowl.Runtime/Components/Physics/Colliders/ConeCollider.cs b/Prowl.Runtime/Components/Physics/Colliders/ConeCollider.cs index 40268701c..14cf1b939 100644 --- a/Prowl.Runtime/Components/Physics/Colliders/ConeCollider.cs +++ b/Prowl.Runtime/Components/Physics/Colliders/ConeCollider.cs @@ -34,5 +34,5 @@ public float Height } } - public override RigidBodyShape[] CreateShapes() => [new ConeShape(radius, height)]; + public override RigidBodyShape[] CreateShapes() => [new ConeShape(MathD.Max(radius, 0.01), MathD.Max(height, 0.01))]; } diff --git a/Prowl.Runtime/Components/Physics/Colliders/CylinderCollider.cs b/Prowl.Runtime/Components/Physics/Colliders/CylinderCollider.cs index 9bdb5dac1..c6cbe86e2 100644 --- a/Prowl.Runtime/Components/Physics/Colliders/CylinderCollider.cs +++ b/Prowl.Runtime/Components/Physics/Colliders/CylinderCollider.cs @@ -34,5 +34,5 @@ public float Height } } - public override RigidBodyShape[] CreateShapes() => [new CylinderShape(radius, height)]; + public override RigidBodyShape[] CreateShapes() => [new CylinderShape(MathD.Max(radius, 0.01), MathD.Max(height, 0.01))]; } diff --git a/Prowl.Runtime/Components/Physics/Colliders/SphereCollider.cs b/Prowl.Runtime/Components/Physics/Colliders/SphereCollider.cs index 78ba76ee3..eb582a10f 100644 --- a/Prowl.Runtime/Components/Physics/Colliders/SphereCollider.cs +++ b/Prowl.Runtime/Components/Physics/Colliders/SphereCollider.cs @@ -23,5 +23,5 @@ public float Radius } } - public override RigidBodyShape[] CreateShapes() => [new SphereShape(radius)]; + public override RigidBodyShape[] CreateShapes() => [new SphereShape(MathD.Max(radius, 0.01))]; } From 9b57c20bb5c2746686cfe51719e9b0691d19f2bd Mon Sep 17 00:00:00 2001 From: Wulferis Date: Sun, 26 Jan 2025 22:06:03 +1100 Subject: [PATCH 12/15] Also dont allow transformed shape collider to be 0 in scale --- Prowl.Runtime/Components/Physics/Colliders/Collider.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Prowl.Runtime/Components/Physics/Colliders/Collider.cs b/Prowl.Runtime/Components/Physics/Colliders/Collider.cs index 78d21b34d..9e422d14b 100644 --- a/Prowl.Runtime/Components/Physics/Colliders/Collider.cs +++ b/Prowl.Runtime/Components/Physics/Colliders/Collider.cs @@ -41,6 +41,8 @@ public RigidBodyShape[] CreateTransformedShapes() current = current.parent; } + cumulativeScale = Vector3.Max(cumulativeScale, Vector3.one * 0.05); + // Get the local rotation and position in world space Quaternion localRotation = Quaternion.Euler(rotation); Vector3 scaledCenter = Vector3.Scale(this.center, cumulativeScale); From 14eb07642521c194915139ddd93badfc63e37436 Mon Sep 17 00:00:00 2001 From: Wulferis Date: Sun, 26 Jan 2025 22:12:22 +1100 Subject: [PATCH 13/15] Partial Wheel Collider --- .../Physics/Colliders/WheelCollider.cs | 344 ++++++++++++++++++ 1 file changed, 344 insertions(+) create mode 100644 Prowl.Runtime/Components/Physics/Colliders/WheelCollider.cs diff --git a/Prowl.Runtime/Components/Physics/Colliders/WheelCollider.cs b/Prowl.Runtime/Components/Physics/Colliders/WheelCollider.cs new file mode 100644 index 000000000..82248246b --- /dev/null +++ b/Prowl.Runtime/Components/Physics/Colliders/WheelCollider.cs @@ -0,0 +1,344 @@ +// This file is part of the Prowl Game Engine +// Licensed under the MIT License. See the LICENSE file in the project root for details. + +using System; +using System.Collections.Generic; +using System.Linq; + +using Prowl.Icons; + +namespace Prowl.Runtime; + +// Based on the Jitter2 Demo Raycast Car + +[AddComponentMenu($"{FontAwesome6.HillRockslide} Physics/Experimental/{FontAwesome6.Circle} Wheel Collider")] +[ExecutionOrder(-1000)] +public sealed class WheelCollider : MonoBehaviour +{ + public bool IsLocked; + public double SteerAngle; + public double Damping = 1500; + public double Spring = 35000; + public double Inertia = 5; + public double Radius = 0.5f; + public double SideFriction = 3.2f; + public double ForwardFriction = 5.0f; + public double SuspensionTravel = 0.2f; + public double MaximumAngularVelocity = 200f; + public int NumberOfRays = 1; + + // Private + private double _displacement = 0f, _lastDisplacement = 0f; + private double _upSpeed = 0f; + private bool _grounded = false; + private double _driveTorque = 0f; + private double _angularVelocity = 0f; + private double _angularVelocityForGrip = 0f; + private double _torque = 0f; + + private List _debugRayStart = new(); + private List _debugRayEnd = new(); + + private bool _hasPreStepped = false; + + // Internal + internal Rigidbody3D _attachedBody = null; + + public Rigidbody3D Body + { + get + { + if (_attachedBody == null) + _attachedBody = GameObject.GetComponentInParent(true, true) ?? throw new Exception("WheelCollider: No Rigidbody3D component found in parent GameObject."); + + return _attachedBody; + } + } + + public bool IsGrounded => _grounded; + public double Displacement => _displacement; + public Vector3 WorldPosition => Transform.position + (Transform.up * _displacement); + public double WheelRotation { get; private set; } = 0.0; + + public void AddTorque(double torque) + { + _driveTorque += torque; + } + + public void PostStep() + { + double timeStep = Time.fixedDeltaTime; + + // Check for no update + if (timeStep <= 0.0 || Body == null || Body.Enabled == false) + return; + + double origAngVel = _angularVelocity; + _upSpeed = (_displacement - _lastDisplacement) / timeStep; + + // Check for wheel locked in place + if (IsLocked == true) + { + _angularVelocity = 0; + _torque = 0; + } + else + { + _angularVelocity += _torque * timeStep / Inertia; + _torque = 0; + + // Don't apply much torque if not grounded + if (_grounded == false) + _driveTorque *= 0.1; + + // Prevent friction from reversing dir - todo do this better by limiting the torque + if ((origAngVel > _angularVelocityForGrip && _angularVelocity < _angularVelocityForGrip) || + (origAngVel < _angularVelocityForGrip && _angularVelocity > _angularVelocityForGrip)) + _angularVelocity = _angularVelocityForGrip; + + _angularVelocity += _driveTorque * timeStep / Inertia; + _driveTorque = 0; + + // Update rotation velocity + double maxAngVel = MaximumAngularVelocity; + _angularVelocity = Math.Clamp(_angularVelocity, -maxAngVel, maxAngVel); + + // Update rotation value + WheelRotation += timeStep * _angularVelocity; + } + } + + public override void FixedUpdate() + { + // First pass wont execute PostStep + if (_hasPreStepped) + PostStep(); + + _hasPreStepped = true; + + // Check for no update + if (Body == null || Body.Enabled == false) + return; + + Vector3 force = Vector3.zero; + _lastDisplacement = _displacement; + _displacement = 0.0f; + + Vector3 worldPos = Transform.position; + Vector3 worldAxis = Body.Transform.up; + + Vector3 forward = Body.Transform.forward; + Vector3 wheelFwd = Quaternion.AngleAxis((float)SteerAngle, worldAxis) * forward; + + Vector3 wheelLeft = Vector3.Cross(worldAxis, wheelFwd); + wheelLeft.Normalize(); + + Vector3 wheelUp = Vector3.Cross(wheelFwd, wheelLeft); + + double rayLen = 2.0 * Radius + SuspensionTravel; + + Vector3 wheelRayEnd = worldPos - Radius * worldAxis; + Vector3 wheelRayOrigin = wheelRayEnd + rayLen * worldAxis; + Vector3 wheelRayDelta = wheelRayEnd - wheelRayOrigin; + + double deltaFwd = 2.0 * Radius / (NumberOfRays + 1); + double deltaFwdStart = deltaFwd; + + _grounded = false; + + Vector3 groundNormal = Vector3.zero; + Vector3 groundPos = Vector3.zero; + Rigidbody3D worldBody = null!; + + double deepestFrac = double.MaxValue; + + _debugRayStart.Clear(); + _debugRayEnd.Clear(); + for (int i = 0; i < NumberOfRays; i++) + { + double distFwd = deltaFwdStart + i * deltaFwd - Radius; + double zOffset = Radius * (1.0 - Math.Cos(Math.PI / 2.0 * (distFwd / Radius))); + Vector3 newOrigin = wheelRayOrigin + distFwd * wheelFwd + zOffset * wheelUp; + + _debugRayStart.Add(newOrigin); + bool result = Physics.Raycast(newOrigin, wheelRayDelta, out RaycastHit hitInfo, rayLen, LayerMask.Everything); + + //Vector3 minBox = worldPos - new Vector3(Radius); + //Vector3 maxBox = worldPos + new Vector3(Radius); + + if (result) + { + if (hitInfo.distance < deepestFrac) + { + deepestFrac = hitInfo.distance; + groundPos = newOrigin + hitInfo.distance * wheelRayDelta; + groundNormal = hitInfo.normal; + worldBody = hitInfo.rigidbody; + } + + _grounded = true; + + _debugRayEnd.Add(newOrigin + (hitInfo.distance * wheelRayDelta)); + } + else + { + _debugRayEnd.Add(newOrigin + wheelRayDelta); + } + } + + if (!_grounded) return; + + if (groundNormal.sqrMagnitude > 0.0) groundNormal.Normalize(); + + _displacement = (rayLen - deepestFrac); + _displacement = Math.Clamp(_displacement, 0.0, SuspensionTravel); + + double displacementForceMag = _displacement * Spring; + + // reduce force when suspension is par to ground + displacementForceMag *= Vector3.Dot(groundNormal, worldAxis); + + // apply damping + double dampingForceMag = _upSpeed * Damping; + + double totalForceMag = displacementForceMag + dampingForceMag; + + if (totalForceMag < 0.0) totalForceMag = 0.0; + + Vector3 extraForce = totalForceMag * worldAxis; + + force += extraForce; + + + + // side-slip friction and drive force. Work out wheel- and floor-relative coordinate frame + Vector3 groundUp = groundNormal; + Vector3 groundLeft = Vector3.Cross(groundNormal, wheelFwd); + + if (groundLeft.sqrMagnitude > 0.0) groundLeft.Normalize(); + + Vector3 groundFwd = Vector3.Cross(groundLeft, groundUp); + + Vector3 wheelCenterVel = Body.Velocity + Vector3.Cross(Body.AngularVelocity, Vector3.Transform(this.Transform.localPosition, Body.Transform.rotation)); + + // rimVel=(wxr)*v + Vector3 rimVel = _angularVelocity * Vector3.Cross(wheelLeft, groundPos - worldPos); + Vector3 wheelPointVel = wheelCenterVel + rimVel; + + if (worldBody == null) throw new Exception("world Body is null."); + + Vector3 worldVel = worldBody.Velocity + Vector3.Cross(worldBody.AngularVelocity, groundPos - worldBody.Transform.position); + + wheelPointVel -= worldVel; + + // sideways forces + double noslipVel = 0.2; + double slipVel = 0.4; + double slipFactor = 0.7; + + double smallVel = 3.0; + double friction = SideFriction; + + double sideVel = Vector3.Dot(wheelPointVel, groundLeft); + + if (sideVel > slipVel || sideVel < -slipVel) + { + friction *= slipFactor; + } + else if (sideVel > noslipVel || sideVel < -noslipVel) + { + friction *= 1.0 - (1.0 - slipFactor) * (Math.Abs(sideVel) - noslipVel) / (slipVel - noslipVel); + } + + if (sideVel < 0.0) + friction *= -1.0; + + if (Math.Abs(sideVel) < smallVel) + friction *= Math.Abs(sideVel) / smallVel; + + double sideForce = -friction * totalForceMag; + + extraForce = sideForce * groundLeft; + force += extraForce; + + + + // TODO: These are for some reason unstable to use, once used the results are very chaotic + // But their kinda needed to actually move the car, and also to track wheel rotation + //// fwd/back forces + //friction = ForwardFriction; + //double fwdVel = Vector3.Dot(wheelPointVel, groundFwd); + // + //if (fwdVel > slipVel || fwdVel < -slipVel) + //{ + // friction *= slipFactor; + //} + //else if (fwdVel > noslipVel || fwdVel < -noslipVel) + //{ + // friction *= 1.0 - (1.0 - slipFactor) * (Math.Abs(fwdVel) - noslipVel) / (slipVel - noslipVel); + //} + // + //if (fwdVel < 0.0) + // friction *= -1.0; + // + //if (Math.Abs(fwdVel) < smallVel) + // friction *= Math.Abs(fwdVel) / smallVel; + // + //double fwdForce = -friction * totalForceMag; + // + //extraForce = fwdForce * groundFwd; + //force += extraForce; + // + //// fwd force also spins the wheel + //_angularVelocityForGrip = Vector3.Dot(wheelCenterVel, groundFwd) / Radius; + //_torque += -fwdForce * Radius; + + // add force to car + Body.AddForceAtPosition(force, groundPos); + Body._body.DeactivationTime = TimeSpan.MaxValue; + } + + public override void DrawGizmos() + { + Vector3 wheelFwd = Quaternion.AngleAxis((float)SteerAngle, Body.Transform.up) * Body.Transform.forward; + Vector3 wheelLeft = Vector3.Cross(Body.Transform.up, wheelFwd); + wheelLeft.Normalize(); + + // Debug Draw + Debug.DrawWireCircle(WorldPosition, wheelLeft, Radius, Color.green, 32); + + // Draw Wheel Rotation With a Line + Vector3 wheelEnd = WorldPosition + ((Quaternion.AngleAxis((float)WheelRotation, wheelLeft) * wheelFwd) * Radius); + Debug.DrawLine(WorldPosition, wheelEnd, Color.green); + + // Draw Raycasts + for (int i = 0; i < _debugRayStart.Count; i++) + { + Debug.DrawLine(_debugRayStart[i], _debugRayEnd[i], Color.yellow); + } + } + + [GUIButton("Auto-Assign Spring, Inertia, Damping")] + public void AdjustWheelValues() + { + if (Body == null) + { + Debug.LogWarning("Cannot Auto-Assign wheel values when no parent rigidbody is present."); + return; + } + + int numberOfWheels = Body.GetComponentsInChildren(true).Count(); + + Debug.LogWarning($"Auto-Assigning wheel parameters, found {numberOfWheels} wheels present on vehicle."); + + const double dampingFrac = 0.8; + const double springFrac = 0.45; + + double mass = Body.Mass / numberOfWheels; + double wheelMass = Body.Mass * 0.03; + + Inertia = 0.5 * (Radius * Radius) * wheelMass; + Spring = mass * Physics.World.Gravity.Length() / (SuspensionTravel * springFrac); + Damping = 2.0 * Math.Sqrt(Spring * Body.Mass) * 0.25 * dampingFrac; + } +} From 4ab9a4a37f8c1061978af10d355db3cfca1e391a Mon Sep 17 00:00:00 2001 From: Wulferis Date: Mon, 27 Jan 2025 01:42:01 +1100 Subject: [PATCH 14/15] Fixed UI being broken when anti aliasing is turned off --- Prowl.Runtime/GUI/Graphics/UIDrawList.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Prowl.Runtime/GUI/Graphics/UIDrawList.cs b/Prowl.Runtime/GUI/Graphics/UIDrawList.cs index 3a146b584..7c3e4b03e 100644 --- a/Prowl.Runtime/GUI/Graphics/UIDrawList.cs +++ b/Prowl.Runtime/GUI/Graphics/UIDrawList.cs @@ -698,7 +698,7 @@ public void AddConvexPolyFilled(List points, int pointsCount, List Date: Mon, 27 Jan 2025 01:43:16 +1100 Subject: [PATCH 15/15] Made Raycasts more precise with positioning in WheelCollider --- .../Components/Physics/Colliders/WheelCollider.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Prowl.Runtime/Components/Physics/Colliders/WheelCollider.cs b/Prowl.Runtime/Components/Physics/Colliders/WheelCollider.cs index 82248246b..5ddb7f1b1 100644 --- a/Prowl.Runtime/Components/Physics/Colliders/WheelCollider.cs +++ b/Prowl.Runtime/Components/Physics/Colliders/WheelCollider.cs @@ -157,7 +157,8 @@ public override void FixedUpdate() for (int i = 0; i < NumberOfRays; i++) { double distFwd = deltaFwdStart + i * deltaFwd - Radius; - double zOffset = Radius * (1.0 - Math.Cos(Math.PI / 2.0 * (distFwd / Radius))); + double normalizedDist = distFwd / Radius; + double zOffset = Radius - Math.Sqrt(Math.Max(0, Radius * Radius - distFwd * distFwd)); Vector3 newOrigin = wheelRayOrigin + distFwd * wheelFwd + zOffset * wheelUp; _debugRayStart.Add(newOrigin); @@ -263,8 +264,6 @@ public override void FixedUpdate() - // TODO: These are for some reason unstable to use, once used the results are very chaotic - // But their kinda needed to actually move the car, and also to track wheel rotation //// fwd/back forces //friction = ForwardFriction; //double fwdVel = Vector3.Dot(wheelPointVel, groundFwd); @@ -314,7 +313,7 @@ public override void DrawGizmos() // Draw Raycasts for (int i = 0; i < _debugRayStart.Count; i++) { - Debug.DrawLine(_debugRayStart[i], _debugRayEnd[i], Color.yellow); + //Debug.DrawLine(_debugRayStart[i], _debugRayEnd[i], Color.yellow); } }