From 790c7e8e2762e57fdda2a94b8c664767bb4446b2 Mon Sep 17 00:00:00 2001 From: notgiven688 Date: Tue, 13 Aug 2024 19:04:52 +0200 Subject: [PATCH 1/4] Simplify character controller and add kinetimatic body demo --- src/JitterDemo/Demos/Demo22.cs | 84 +++++++++++++++++++++++++++ src/JitterDemo/Demos/Player/Player.cs | 19 +----- src/JitterDemo/Playground.cs | 3 +- 3 files changed, 88 insertions(+), 18 deletions(-) create mode 100644 src/JitterDemo/Demos/Demo22.cs diff --git a/src/JitterDemo/Demos/Demo22.cs b/src/JitterDemo/Demos/Demo22.cs new file mode 100644 index 00000000..090dc8ed --- /dev/null +++ b/src/JitterDemo/Demos/Demo22.cs @@ -0,0 +1,84 @@ +using System; +using Jitter2; +using Jitter2.Collision.Shapes; +using Jitter2.Dynamics; +using Jitter2.Dynamics.Constraints; +using Jitter2.LinearMath; +using JitterDemo.Renderer; +using JitterDemo.Renderer.OpenGL; + +namespace JitterDemo; + +public class Demo22 : IDemo +{ + public string Name => "Kinematic bodies"; + + private Playground pg = null!; + private World world = null!; + private Player player = null!; + + private RigidBody platform = null!; + + private LinearMotor linearMotor = null!; + + public void Build() + { + pg = (Playground)RenderWindow.Instance; + world = pg.World; + + pg.ResetScene(true); + + var plankA = world.CreateRigidBody(); + plankA.AddShape(new BoxShape(20,0.1f,6)); + plankA.IsStatic = true; + + var plankC = world.CreateRigidBody(); + plankC.AddShape(new BoxShape(20,0.1f,6)); + plankC.IsStatic = true; + + plankA.Position = new JVector(-20, 11, 0); + plankC.Position = new JVector(20, 16, 0); + + // (*) The mass is set as inverse mass, so we have a mass of 100 here. + // Reduce the mass to make the body "more" kinematic, i.e. it behaves + // more like an unstoppable object. Be careful when setting the mass to infinity (zero + // inverse mass) - if Jitter detects a collision between and unstoppable object + // (the platform) and an immovable object (static object) the solver explodes. + + platform = world.CreateRigidBody(); + platform.AddShape(new BoxShape(4,0.1f,6)); + platform.SetMassInertia(JMatrix.Zero, 0.01f, true); // (*) + platform.AffectedByGravity = false; + platform.Position = new JVector(0, 12, 0); + + player = new Player(world, new JVector(-20, 12, 0)); + player.Body.Orientation = JQuaternion.CreateRotationY(-MathF.PI / 2.0f); + } + + public void Draw() + { + Keyboard kb = Keyboard.Instance; + + JVector path = new JVector(8.5f * MathF.Sin((float)pg.Time * 0.5f), + 14 + MathF.Cos((float)pg.Time * 0.5f) * 5.0f, 0); + + JVector delta = path - platform.Position; + + if (delta.LengthSquared() > 0.001f) + { + // setting velocities is absolutely fine, don't ever set + // positions - the solver wont properly deal with it. + platform.Velocity = delta; + } + + if (kb.IsKeyDown(Keyboard.Key.Left)) player.SetAngularInput(-1.0f); + else if (kb.IsKeyDown(Keyboard.Key.Right)) player.SetAngularInput(1.0f); + else player.SetAngularInput(0.0f); + + if (kb.IsKeyDown(Keyboard.Key.Up)) player.SetLinearInput(-JVector.UnitZ); + else if (kb.IsKeyDown(Keyboard.Key.Down)) player.SetLinearInput(JVector.UnitZ); + else player.SetLinearInput(JVector.Zero); + + if (kb.IsKeyDown(Keyboard.Key.LeftControl)) player.Jump(); + } +} \ No newline at end of file diff --git a/src/JitterDemo/Demos/Player/Player.cs b/src/JitterDemo/Demos/Player/Player.cs index b90c2de6..b518658e 100644 --- a/src/JitterDemo/Demos/Player/Player.cs +++ b/src/JitterDemo/Demos/Player/Player.cs @@ -12,7 +12,6 @@ namespace JitterDemo; public class Player { public RigidBody Body { get; } - public LinearMotor FrictionMotor { get; } public AngularMotor AngularMovement { get; } private readonly float capsuleHalfHeight; @@ -48,11 +47,8 @@ public Player(World world, JVector position) var ur = world.CreateConstraint(Body, world.NullBody); ur.Initialize(JVector.UnitY, AngularLimit.Full); - // Add a "motor" to the body. The motor target velocity is zero. - // This acts like friction and stops the player. - FrictionMotor = world.CreateConstraint(Body, world.NullBody); - FrictionMotor.Initialize(JVector.UnitZ, JVector.UnitX); - FrictionMotor.MaximumForce = 10; + // Add some friction + Body.Friction = 0.8f; // An angular motor for turning. AngularMovement = world.CreateConstraint(Body, world.NullBody); @@ -205,7 +201,6 @@ public void SetLinearInput(JVector deltaMove) { if (!CanJump(out _, out _)) { - FrictionMotor.IsEnabled = false; return; } @@ -226,15 +221,5 @@ public void SetLinearInput(JVector deltaMove) } } - if (bodyVelLen > 0.01f) - { - FrictionMotor.LocalAxis1 = JVector.TransposedTransform(bodyVel * (1.0f / bodyVelLen), Body.Orientation); - FrictionMotor.TargetVelocity = 0; - FrictionMotor.IsEnabled = true; - } - else - { - FrictionMotor.IsEnabled = false; - } } } \ No newline at end of file diff --git a/src/JitterDemo/Playground.cs b/src/JitterDemo/Playground.cs index 7a00185f..8102fa2c 100644 --- a/src/JitterDemo/Playground.cs +++ b/src/JitterDemo/Playground.cs @@ -51,7 +51,8 @@ public partial class Playground : RenderWindow // new Demo18(), // point test // new Demo19(), // ray cast test new Demo20(), - new Demo21() + new Demo21(), + new Demo22() }; private IDemo? currentDemo; From 35033b73f04b3bab85c341c5481afd1a4bf89df7 Mon Sep 17 00:00:00 2001 From: notgiven688 Date: Tue, 13 Aug 2024 19:22:47 +0200 Subject: [PATCH 2/4] Follow Newton's law in the player demo --- src/Jitter2/Dynamics/RigidBody.cs | 4 +++- src/JitterDemo/Demos/Player/Player.cs | 10 ++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/Jitter2/Dynamics/RigidBody.cs b/src/Jitter2/Dynamics/RigidBody.cs index 08742c38..8ba16449 100644 --- a/src/Jitter2/Dynamics/RigidBody.cs +++ b/src/Jitter2/Dynamics/RigidBody.cs @@ -430,10 +430,12 @@ public void AddForce(in JVector force) } /// - /// Applies a force to the rigid body, altering its velocity. This force is applied for a single frame only and is reset to zero with the subsequent call to . + /// Applies a force to the rigid body, altering its velocity. This force is applied for a single frame only and is + /// reset to zero with the subsequent call to . /// /// The force to be applied. /// The position where the force will be applied. + [ReferenceFrame(ReferenceFrame.World)] public void AddForce(in JVector force, in JVector position) { ref RigidBodyData data = ref Data; diff --git a/src/JitterDemo/Demos/Player/Player.cs b/src/JitterDemo/Demos/Player/Player.cs index b518658e..8de31c77 100644 --- a/src/JitterDemo/Demos/Player/Player.cs +++ b/src/JitterDemo/Demos/Player/Player.cs @@ -199,7 +199,7 @@ public void Jump() public void SetLinearInput(JVector deltaMove) { - if (!CanJump(out _, out _)) + if (!CanJump(out var floor, out JVector hitpoint)) { return; } @@ -217,7 +217,13 @@ public void SetLinearInput(JVector deltaMove) { if (bodyVelLen < 5f) { - Body.AddForce(JVector.Transform(deltaMove, Body.Orientation) * 10); + var force = JVector.Transform(deltaMove, Body.Orientation) * 10.0f; + + Body.AddForce(force); + + // follow Newton's law (for once) and add a force + // with equal magnitude in the opposite direction. + floor!.AddForce(-force, Body.Position + hitpoint); } } From cdd8c012c8d370826f7a5621fff5e35aa5f3b8a0 Mon Sep 17 00:00:00 2001 From: notgiven688 Date: Tue, 13 Aug 2024 19:28:05 +0200 Subject: [PATCH 3/4] Adjust hights in the kinematic body demo --- src/JitterDemo/Demos/Demo22.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/JitterDemo/Demos/Demo22.cs b/src/JitterDemo/Demos/Demo22.cs index 090dc8ed..d81da877 100644 --- a/src/JitterDemo/Demos/Demo22.cs +++ b/src/JitterDemo/Demos/Demo22.cs @@ -36,7 +36,7 @@ public void Build() plankC.AddShape(new BoxShape(20,0.1f,6)); plankC.IsStatic = true; - plankA.Position = new JVector(-20, 11, 0); + plankA.Position = new JVector(-20, 13, 0); plankC.Position = new JVector(20, 16, 0); // (*) The mass is set as inverse mass, so we have a mass of 100 here. @@ -51,7 +51,7 @@ public void Build() platform.AffectedByGravity = false; platform.Position = new JVector(0, 12, 0); - player = new Player(world, new JVector(-20, 12, 0)); + player = new Player(world, new JVector(-20, 15, 0)); player.Body.Orientation = JQuaternion.CreateRotationY(-MathF.PI / 2.0f); } From 08b8377372e156890bdcbb7d98b2103a8ea73128 Mon Sep 17 00:00:00 2001 From: notgiven688 Date: Wed, 14 Aug 2024 07:45:53 +0200 Subject: [PATCH 4/4] do the physics right --- src/JitterDemo/Demos/Demo22.cs | 97 +++++++++++++++++++++++++--------- 1 file changed, 72 insertions(+), 25 deletions(-) diff --git a/src/JitterDemo/Demos/Demo22.cs b/src/JitterDemo/Demos/Demo22.cs index d81da877..d3d152b1 100644 --- a/src/JitterDemo/Demos/Demo22.cs +++ b/src/JitterDemo/Demos/Demo22.cs @@ -11,6 +11,21 @@ namespace JitterDemo; public class Demo22 : IDemo { + private static class Curve + { + public static JVector Path(float time) + { + return new JVector(8.5f * MathF.Sin((float)time * 0.5f), + 14 + MathF.Cos(time * 0.5f) * 5.0f, 0); + } + + public static JVector Derivative(float time) + { + return new JVector(4.25f * MathF.Cos((float)time * 0.5f), + -2.5f * MathF.Sin(time * 0.5f), 0); + } + } + public string Name => "Kinematic bodies"; private Playground pg = null!; @@ -19,8 +34,6 @@ public class Demo22 : IDemo private RigidBody platform = null!; - private LinearMotor linearMotor = null!; - public void Build() { pg = (Playground)RenderWindow.Instance; @@ -28,25 +41,26 @@ public void Build() pg.ResetScene(true); - var plankA = world.CreateRigidBody(); - plankA.AddShape(new BoxShape(20,0.1f,6)); - plankA.IsStatic = true; + var leftPlank = world.CreateRigidBody(); + leftPlank.AddShape(new BoxShape(20,0.1f,6)); + leftPlank.IsStatic = true; - var plankC = world.CreateRigidBody(); - plankC.AddShape(new BoxShape(20,0.1f,6)); - plankC.IsStatic = true; + var rightPlank = world.CreateRigidBody(); + rightPlank.AddShape(new BoxShape(20,0.1f,6)); + rightPlank.IsStatic = true; - plankA.Position = new JVector(-20, 13, 0); - plankC.Position = new JVector(20, 16, 0); + leftPlank.Position = new JVector(-21, 13, 0); + rightPlank.Position = new JVector(21, 16, 0); - // (*) The mass is set as inverse mass, so we have a mass of 100 here. - // Reduce the mass to make the body "more" kinematic, i.e. it behaves - // more like an unstoppable object. Be careful when setting the mass to infinity (zero - // inverse mass) - if Jitter detects a collision between and unstoppable object - // (the platform) and an immovable object (static object) the solver explodes. + // (*) The mass is specified as the inverse mass, so an inverse mass of 0.01 corresponds to a mass of 100. + // Lower the inverse mass (i.e., increase the mass) to make the body behave more kinematically, + // akin to an unstoppable object. However, be cautious when setting the inverse mass to zero (infinite mass), + // as it can cause the solver to fail if a collision occurs between an unstoppable object (e.g., a platform) + // and an immovable object (e.g., a static object). platform = world.CreateRigidBody(); platform.AddShape(new BoxShape(4,0.1f,6)); + platform.AddShape(new SphereShape(0.2f)); // guide to the eye platform.SetMassInertia(JMatrix.Zero, 0.01f, true); // (*) platform.AffectedByGravity = false; platform.Position = new JVector(0, 12, 0); @@ -57,19 +71,52 @@ public void Build() public void Draw() { - Keyboard kb = Keyboard.Instance; + // What is happening in the following *two* lines of code? + // + // We want to platform to follow a path p(t). We can not set the position + // directly, as it will just teleport the body in tiny steps, which + // does not produce the kinematic physics we are after. + // + // We introduce k(t) and manually set the velocity of the platform to be + // + // v(t) = alpha(k(t) - x(t)) + // <=> x'(t) = alpha(k(t) - x(t)) + // <=> 1/alpha x'(t) + x(t) = k(t) + // + // where alpha is a constant. + // + // If we now set k(t) to be + // ______________________________ + // | k(t) = 1/alpha p'(t) + p(t) | + // ------------------------------- + // , we arrive at + // + // x(t) = C*exp(-alpha*t) + p(t) + // + // where the first term describes the offset of the target path p(t) + // and the position of the platform x(t). This term vanishes with larger t. + + JVector k = Curve.Derivative((float)pg.Time) + Curve.Path((float)pg.Time); + platform.Velocity = k - platform.Position; + + // Draw the curve + + const int stepMax = 100; + const float maxTime = 4.0f * MathF.PI; + + for (int step = 0; step < stepMax; step++) + { + float ta = maxTime / stepMax * step; + float tb = maxTime / stepMax * (step + 1); - JVector path = new JVector(8.5f * MathF.Sin((float)pg.Time * 0.5f), - 14 + MathF.Cos((float)pg.Time * 0.5f) * 5.0f, 0); + pg.DebugRenderer.PushLine(DebugRenderer.Color.Green, + Conversion.FromJitter(Curve.Path(ta)), + Conversion.FromJitter(Curve.Path(tb))); + } - JVector delta = path - platform.Position; + // Player handling with keyboard - if (delta.LengthSquared() > 0.001f) - { - // setting velocities is absolutely fine, don't ever set - // positions - the solver wont properly deal with it. - platform.Velocity = delta; - } + Keyboard kb = Keyboard.Instance; if (kb.IsKeyDown(Keyboard.Key.Left)) player.SetAngularInput(-1.0f); else if (kb.IsKeyDown(Keyboard.Key.Right)) player.SetAngularInput(1.0f);