2
0
Эх сурвалжийг харах

New constraint types added (#181)

* Added rack and pinion constraint
* Added gear constraint
* Added soft limits to slider constraint
Jorrit Rouwe 3 жил өмнө
parent
commit
09d6d9d51c
28 өөрчлөгдсөн 1291 нэмэгдсэн , 36 устгасан
  1. 6 0
      Jolt/Jolt.cmake
  2. 2 0
      Jolt/Physics/Constraints/Constraint.h
  3. 0 1
      Jolt/Physics/Constraints/ConstraintPart/AngleConstraintPart.h
  4. 0 1
      Jolt/Physics/Constraints/ConstraintPart/AxisConstraintPart.h
  5. 0 1
      Jolt/Physics/Constraints/ConstraintPart/DualAxisConstraintPart.h
  6. 190 0
      Jolt/Physics/Constraints/ConstraintPart/GearConstraintPart.h
  7. 0 1
      Jolt/Physics/Constraints/ConstraintPart/HingeRotationConstraintPart.h
  8. 0 1
      Jolt/Physics/Constraints/ConstraintPart/PointConstraintPart.h
  9. 191 0
      Jolt/Physics/Constraints/ConstraintPart/RackAndPinionConstraintPart.h
  10. 0 1
      Jolt/Physics/Constraints/ConstraintPart/RotationEulerConstraintPart.h
  11. 0 1
      Jolt/Physics/Constraints/ConstraintPart/RotationQuatConstraintPart.h
  12. 1 0
      Jolt/Physics/Constraints/ContactConstraintManager.h
  13. 150 0
      Jolt/Physics/Constraints/GearConstraint.cpp
  14. 104 0
      Jolt/Physics/Constraints/GearConstraint.h
  15. 150 0
      Jolt/Physics/Constraints/RackAndPinionConstraint.cpp
  16. 106 0
      Jolt/Physics/Constraints/RackAndPinionConstraint.h
  17. 17 4
      Jolt/Physics/Constraints/SliderConstraint.cpp
  18. 17 0
      Jolt/Physics/Constraints/SliderConstraint.h
  19. 1 0
      Jolt/Physics/PhysicsSystem.h
  20. 4 0
      Jolt/RegisterTypes.cpp
  21. 2 0
      README.md
  22. 4 0
      Samples/Samples.cmake
  23. 4 0
      Samples/SamplesApp.cpp
  24. 119 0
      Samples/Tests/Constraints/GearConstraintTest.cpp
  25. 16 0
      Samples/Tests/Constraints/GearConstraintTest.h
  26. 121 0
      Samples/Tests/Constraints/RackAndPinionConstraintTest.cpp
  27. 16 0
      Samples/Tests/Constraints/RackAndPinionConstraintTest.h
  28. 70 25
      Samples/Tests/Constraints/SliderConstraintTest.cpp

+ 6 - 0
Jolt/Jolt.cmake

@@ -275,8 +275,10 @@ set(JOLT_PHYSICS_SRC_FILES
 	${JOLT_PHYSICS_ROOT}/Physics/Constraints/ConstraintPart/AngleConstraintPart.h
 	${JOLT_PHYSICS_ROOT}/Physics/Constraints/ConstraintPart/AxisConstraintPart.h
 	${JOLT_PHYSICS_ROOT}/Physics/Constraints/ConstraintPart/DualAxisConstraintPart.h
+	${JOLT_PHYSICS_ROOT}/Physics/Constraints/ConstraintPart/GearConstraintPart.h
 	${JOLT_PHYSICS_ROOT}/Physics/Constraints/ConstraintPart/HingeRotationConstraintPart.h
 	${JOLT_PHYSICS_ROOT}/Physics/Constraints/ConstraintPart/PointConstraintPart.h
+	${JOLT_PHYSICS_ROOT}/Physics/Constraints/ConstraintPart/RackAndPinionConstraintPart.h
 	${JOLT_PHYSICS_ROOT}/Physics/Constraints/ConstraintPart/RotationEulerConstraintPart.h
 	${JOLT_PHYSICS_ROOT}/Physics/Constraints/ConstraintPart/RotationQuatConstraintPart.h
 	${JOLT_PHYSICS_ROOT}/Physics/Constraints/ConstraintPart/SpringPart.h
@@ -287,6 +289,8 @@ set(JOLT_PHYSICS_SRC_FILES
 	${JOLT_PHYSICS_ROOT}/Physics/Constraints/DistanceConstraint.h
 	${JOLT_PHYSICS_ROOT}/Physics/Constraints/FixedConstraint.cpp
 	${JOLT_PHYSICS_ROOT}/Physics/Constraints/FixedConstraint.h
+	${JOLT_PHYSICS_ROOT}/Physics/Constraints/GearConstraint.cpp
+	${JOLT_PHYSICS_ROOT}/Physics/Constraints/GearConstraint.h
 	${JOLT_PHYSICS_ROOT}/Physics/Constraints/HingeConstraint.cpp
 	${JOLT_PHYSICS_ROOT}/Physics/Constraints/HingeConstraint.h
 	${JOLT_PHYSICS_ROOT}/Physics/Constraints/MotorSettings.cpp
@@ -299,6 +303,8 @@ set(JOLT_PHYSICS_SRC_FILES
 	${JOLT_PHYSICS_ROOT}/Physics/Constraints/PathConstraintPathHermite.h
 	${JOLT_PHYSICS_ROOT}/Physics/Constraints/PointConstraint.cpp
 	${JOLT_PHYSICS_ROOT}/Physics/Constraints/PointConstraint.h
+	${JOLT_PHYSICS_ROOT}/Physics/Constraints/RackAndPinionConstraint.cpp
+	${JOLT_PHYSICS_ROOT}/Physics/Constraints/RackAndPinionConstraint.h
 	${JOLT_PHYSICS_ROOT}/Physics/Constraints/SixDOFConstraint.cpp
 	${JOLT_PHYSICS_ROOT}/Physics/Constraints/SixDOFConstraint.h
 	${JOLT_PHYSICS_ROOT}/Physics/Constraints/SliderConstraint.cpp

+ 2 - 0
Jolt/Physics/Constraints/Constraint.h

@@ -39,6 +39,8 @@ enum class EConstraintSubType
 	SixDOF,
 	Path,
 	Vehicle,
+	RackAndPinion,
+	Gear,
 
 	/// User defined constraint types start here
 	User1,

+ 0 - 1
Jolt/Physics/Constraints/ConstraintPart/AngleConstraintPart.h

@@ -3,7 +3,6 @@
 
 #pragma once
 
-#include <Jolt/Physics/PhysicsSettings.h>
 #include <Jolt/Physics/Body/Body.h>
 #include <Jolt/Physics/Constraints/ConstraintPart/SpringPart.h>
 #include <Jolt/Physics/StateRecorder.h>

+ 0 - 1
Jolt/Physics/Constraints/ConstraintPart/AxisConstraintPart.h

@@ -3,7 +3,6 @@
 
 #pragma once
 
-#include <Jolt/Physics/PhysicsSettings.h>
 #include <Jolt/Physics/Body/Body.h>
 #include <Jolt/Physics/Constraints/ConstraintPart/SpringPart.h>
 #include <Jolt/Physics/StateRecorder.h>

+ 0 - 1
Jolt/Physics/Constraints/ConstraintPart/DualAxisConstraintPart.h

@@ -3,7 +3,6 @@
 
 #pragma once
 
-#include <Jolt/Physics/PhysicsSettings.h>
 #include <Jolt/Physics/Body/Body.h>
 #include <Jolt/Physics/StateRecorder.h>
 #include <Jolt/Math/Vector.h>

+ 190 - 0
Jolt/Physics/Constraints/ConstraintPart/GearConstraintPart.h

@@ -0,0 +1,190 @@
+// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
+// SPDX-License-Identifier: MIT
+
+#pragma once
+
+#include <Jolt/Physics/Body/Body.h>
+#include <Jolt/Physics/StateRecorder.h>
+
+JPH_NAMESPACE_BEGIN
+
+/// Constraint that constrains two rotations using a gear (rotating in opposite direction)
+///
+/// Constraint equation:
+///
+/// C = Rotation1(t) + r Rotation2(t)
+///
+/// Derivative:
+///
+/// d/dt C = 0
+/// <=> w1 . a + r w2 . b = 0
+///
+/// Jacobian:
+///
+/// \f[J = \begin{bmatrix}0 & a^T & 0 & r b^T\end{bmatrix}\f]
+///
+/// Used terms (here and below, everything in world space):\n
+/// a = axis around which body 1 rotates (normalized).\n
+/// b = axis along which body 2 slides (normalized).\n
+/// Rotation1(t) = rotation around a of body 1.\n
+/// Rotation2(t) = rotation around b of body 2.\n
+/// r = ratio between rotation for body 1 and 2.\n
+/// v = [v1, w1, v2, w2].\n
+/// v1, v2 = linear velocity of body 1 and 2.\n
+/// w1, w2 = angular velocity of body 1 and 2.\n
+/// M = mass matrix, a diagonal matrix of the mass and inertia with diagonal [m1, I1, m2, I2].\n
+/// \f$K^{-1} = \left( J M^{-1} J^T \right)^{-1}\f$ = effective mass.\n
+/// \f$\beta\f$ = baumgarte constant.
+class GearConstraintPart
+{
+	/// Internal helper function to update velocities of bodies after Lagrange multiplier is calculated
+	JPH_INLINE bool				ApplyVelocityStep(Body &ioBody1, Body &ioBody2, float inLambda) const
+	{
+		// Apply impulse if delta is not zero
+		if (inLambda != 0.0f)
+		{
+			// Calculate velocity change due to constraint
+			//
+			// Impulse:
+			// P = J^T lambda
+			//
+			// Euler velocity integration: 
+			// v' = v + M^-1 P
+			ioBody1.GetMotionProperties()->AddAngularVelocityStep(inLambda * mInvI1_A);
+			ioBody2.GetMotionProperties()->AddAngularVelocityStep(inLambda * mInvI2_B);
+			return true;
+		}
+
+		return false;
+	}
+
+public:
+	/// Calculate properties used during the functions below
+	/// @param inBody1 The first body that this constraint is attached to
+	/// @param inBody2 The second body that this constraint is attached to
+	/// @param inWorldSpaceHingeAxis1 The axis around which body 1 rotates
+	/// @param inWorldSpaceHingeAxis2 The axis around which body 2 rotates
+	/// @param inRatio The ratio between rotation and translation
+	inline void					CalculateConstraintProperties(const Body &inBody1, Vec3Arg inWorldSpaceHingeAxis1, const Body &inBody2, Vec3Arg inWorldSpaceHingeAxis2, float inRatio)
+	{
+		JPH_ASSERT(inWorldSpaceHingeAxis1.IsNormalized(1.0e-4f));
+		JPH_ASSERT(inWorldSpaceHingeAxis2.IsNormalized(1.0e-4f));
+
+		// Calculate: I1^-1 a
+		mInvI1_A = inBody1.GetMotionProperties()->MultiplyWorldSpaceInverseInertiaByVector(inBody1.GetRotation(), inWorldSpaceHingeAxis1);
+
+		// Calculate: I2^-1 b
+		mInvI2_B = inBody2.GetMotionProperties()->MultiplyWorldSpaceInverseInertiaByVector(inBody2.GetRotation(), inWorldSpaceHingeAxis2);
+
+		// K^-1 = 1 / (J M^-1 J^T) = 1 / (a^T I1^-1 a + r^2 * b^T I2^-1 b)
+		mEffectiveMass = 1.0f / (inWorldSpaceHingeAxis1.Dot(mInvI1_A) + inWorldSpaceHingeAxis2.Dot(mInvI2_B) * Square(inRatio));
+	}
+
+	/// Deactivate this constraint
+	inline void					Deactivate()
+	{
+		mEffectiveMass = 0.0f;
+		mTotalLambda = 0.0f;
+	}
+
+	/// Check if constraint is active
+	inline bool					IsActive() const
+	{
+		return mEffectiveMass != 0.0f;
+	}
+
+	/// Must be called from the WarmStartVelocityConstraint call to apply the previous frame's impulses
+	/// @param ioBody1 The first body that this constraint is attached to
+	/// @param ioBody2 The second body that this constraint is attached to
+	/// @param inWarmStartImpulseRatio Ratio of new step to old time step (dt_new / dt_old) for scaling the lagrange multiplier of the previous frame
+	inline void					WarmStart(Body &ioBody1, Body &ioBody2, float inWarmStartImpulseRatio)
+	{
+		mTotalLambda *= inWarmStartImpulseRatio;
+		ApplyVelocityStep(ioBody1, ioBody2, mTotalLambda);
+	}
+
+	/// Iteratively update the velocity constraint. Makes sure d/dt C(...) = 0, where C is the constraint equation.
+	/// @param ioBody1 The first body that this constraint is attached to
+	/// @param ioBody2 The second body that this constraint is attached to
+	/// @param inWorldSpaceHingeAxis1 The axis around which body 1 rotates
+	/// @param inWorldSpaceHingeAxis2 The axis around which body 2 rotates
+	/// @param inRatio The ratio between rotation and translation
+	inline bool					SolveVelocityConstraint(Body &ioBody1, Vec3Arg inWorldSpaceHingeAxis1, Body &ioBody2, Vec3Arg inWorldSpaceHingeAxis2, float inRatio)
+	{
+		// Lagrange multiplier is:
+		//
+		// lambda = -K^-1 (J v + b)
+		float lambda = -mEffectiveMass * (inWorldSpaceHingeAxis1.Dot(ioBody1.GetAngularVelocity()) + inRatio * inWorldSpaceHingeAxis2.Dot(ioBody2.GetAngularVelocity()));
+		mTotalLambda += lambda; // Store accumulated impulse
+
+		return ApplyVelocityStep(ioBody1, ioBody2, lambda);
+	}
+
+	/// Return lagrange multiplier
+	float						GetTotalLambda() const
+	{
+		return mTotalLambda;
+	}
+
+	/// Iteratively update the position constraint. Makes sure C(...) == 0.
+	/// @param ioBody1 The first body that this constraint is attached to
+	/// @param ioBody2 The second body that this constraint is attached to
+	/// @param inC Value of the constraint equation (C)
+	/// @param inBaumgarte Baumgarte constant (fraction of the error to correct)
+	inline bool					SolvePositionConstraint(Body &ioBody1, Body &ioBody2, float inC, float inBaumgarte) const
+	{
+		// Only apply position constraint when the constraint is hard, otherwise the velocity bias will fix the constraint
+		if (inC != 0.0f)
+		{
+			// Calculate lagrange multiplier (lambda) for Baumgarte stabilization:
+			//
+			// lambda = -K^-1 * beta / dt * C
+			//
+			// We should divide by inDeltaTime, but we should multiply by inDeltaTime in the Euler step below so they're cancelled out
+			float lambda = -mEffectiveMass * inBaumgarte * inC; 
+
+			// Directly integrate velocity change for one time step
+			//
+			// Euler velocity integration: 
+			// dv = M^-1 P
+			//
+			// Impulse:
+			// P = J^T lambda
+			//
+			// Euler position integration:
+			// x' = x + dv * dt
+			//
+			// Note we don't accumulate velocities for the stabilization. This is using the approach described in 'Modeling and 
+			// Solving Constraints' by Erin Catto presented at GDC 2007. On slide 78 it is suggested to split up the Baumgarte 
+			// stabilization for positional drift so that it does not actually add to the momentum. We combine an Euler velocity 
+			// integrate + a position integrate and then discard the velocity change.
+			if (ioBody1.IsDynamic())
+				ioBody1.AddRotationStep(lambda * mInvI1_A);
+			if (ioBody2.IsDynamic())
+				ioBody2.AddRotationStep(lambda * mInvI2_B);
+			return true;
+		}
+
+		return false;
+	}
+
+	/// Save state of this constraint part
+	void						SaveState(StateRecorder &inStream) const
+	{
+		inStream.Write(mTotalLambda);
+	}
+
+	/// Restore state of this constraint part
+	void						RestoreState(StateRecorder &inStream)
+	{
+		inStream.Read(mTotalLambda);
+	}
+
+private:
+	Vec3						mInvI1_A;
+	Vec3						mInvI2_B;
+	float						mEffectiveMass = 0.0f;
+	float						mTotalLambda = 0.0f;
+};
+
+JPH_NAMESPACE_END

+ 0 - 1
Jolt/Physics/Constraints/ConstraintPart/HingeRotationConstraintPart.h

@@ -3,7 +3,6 @@
 
 #pragma once
 
-#include <Jolt/Physics/PhysicsSettings.h>
 #include <Jolt/Physics/Body/Body.h>
 #include <Jolt/Physics/StateRecorder.h>
 #include <Jolt/Math/Vector.h>

+ 0 - 1
Jolt/Physics/Constraints/ConstraintPart/PointConstraintPart.h

@@ -3,7 +3,6 @@
 
 #pragma once
 
-#include <Jolt/Physics/PhysicsSettings.h>
 #include <Jolt/Physics/Body/Body.h>
 #include <Jolt/Physics/StateRecorder.h>
 

+ 191 - 0
Jolt/Physics/Constraints/ConstraintPart/RackAndPinionConstraintPart.h

@@ -0,0 +1,191 @@
+// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
+// SPDX-License-Identifier: MIT
+
+#pragma once
+
+#include <Jolt/Physics/Body/Body.h>
+#include <Jolt/Physics/StateRecorder.h>
+
+JPH_NAMESPACE_BEGIN
+
+/// Constraint that constrains a rotation to a translation
+///
+/// Constraint equation:
+///
+/// C = Theta(t) - r d(t)
+///
+/// Derivative:
+///
+/// d/dt C = 0
+/// <=> w1 . a - r v2 . b = 0
+///
+/// Jacobian:
+///
+/// \f[J = \begin{bmatrix}0 & a^T & -r b^T & 0\end{bmatrix}\f]
+///
+/// Used terms (here and below, everything in world space):\n
+/// a = axis around which body 1 rotates (normalized).\n
+/// b = axis along which body 2 slides (normalized).\n
+/// Theta(t) = rotation around a of body 1.\n
+/// d(t) = distance body 2 slides.\n
+/// r = ratio between rotation and translation.\n
+/// v = [v1, w1, v2, w2].\n
+/// v1, v2 = linear velocity of body 1 and 2.\n
+/// w1, w2 = angular velocity of body 1 and 2.\n
+/// M = mass matrix, a diagonal matrix of the mass and inertia with diagonal [m1, I1, m2, I2].\n
+/// \f$K^{-1} = \left( J M^{-1} J^T \right)^{-1}\f$ = effective mass.\n
+/// \f$\beta\f$ = baumgarte constant.
+class RackAndPinionConstraintPart
+{
+	/// Internal helper function to update velocities of bodies after Lagrange multiplier is calculated
+	JPH_INLINE bool				ApplyVelocityStep(Body &ioBody1, Body &ioBody2, float inLambda) const
+	{
+		// Apply impulse if delta is not zero
+		if (inLambda != 0.0f)
+		{
+			// Calculate velocity change due to constraint
+			//
+			// Impulse:
+			// P = J^T lambda
+			//
+			// Euler velocity integration: 
+			// v' = v + M^-1 P
+			ioBody1.GetMotionProperties()->AddAngularVelocityStep(inLambda * mInvI1_A);
+			ioBody2.GetMotionProperties()->SubLinearVelocityStep(inLambda * mRatio_InvM2_B);
+			return true;
+		}
+
+		return false;
+	}
+
+public:
+	/// Calculate properties used during the functions below
+	/// @param inBody1 The first body that this constraint is attached to
+	/// @param inBody2 The second body that this constraint is attached to
+	/// @param inWorldSpaceHingeAxis The axis around which body 1 rotates
+	/// @param inWorldSpaceSliderAxis The axis along which body 2 slides
+	/// @param inRatio The ratio between rotation and translation
+	inline void					CalculateConstraintProperties(const Body &inBody1, Vec3Arg inWorldSpaceHingeAxis, const Body &inBody2, Vec3Arg inWorldSpaceSliderAxis, float inRatio)
+	{
+		JPH_ASSERT(inWorldSpaceHingeAxis.IsNormalized(1.0e-4f));
+		JPH_ASSERT(inWorldSpaceSliderAxis.IsNormalized(1.0e-4f));
+
+		// Calculate: I1^-1 a
+		mInvI1_A = inBody1.GetMotionProperties()->MultiplyWorldSpaceInverseInertiaByVector(inBody1.GetRotation(), inWorldSpaceHingeAxis);
+
+		// Calculate: r/m2 b
+		float inv_m2 = inBody2.GetMotionProperties()->GetInverseMass();
+		mRatio_InvM2_B = inRatio * inv_m2 * inWorldSpaceSliderAxis;
+
+		// K^-1 = 1 / (J M^-1 J^T) = 1 / (a^T I1^-1 a + 1/m2 * r^2 * b . b)
+		mEffectiveMass = 1.0f / (inWorldSpaceHingeAxis.Dot(mInvI1_A) + inv_m2 * Square(inRatio));
+	}
+
+	/// Deactivate this constraint
+	inline void					Deactivate()
+	{
+		mEffectiveMass = 0.0f;
+		mTotalLambda = 0.0f;
+	}
+
+	/// Check if constraint is active
+	inline bool					IsActive() const
+	{
+		return mEffectiveMass != 0.0f;
+	}
+
+	/// Must be called from the WarmStartVelocityConstraint call to apply the previous frame's impulses
+	/// @param ioBody1 The first body that this constraint is attached to
+	/// @param ioBody2 The second body that this constraint is attached to
+	/// @param inWarmStartImpulseRatio Ratio of new step to old time step (dt_new / dt_old) for scaling the lagrange multiplier of the previous frame
+	inline void					WarmStart(Body &ioBody1, Body &ioBody2, float inWarmStartImpulseRatio)
+	{
+		mTotalLambda *= inWarmStartImpulseRatio;
+		ApplyVelocityStep(ioBody1, ioBody2, mTotalLambda);
+	}
+
+	/// Iteratively update the velocity constraint. Makes sure d/dt C(...) = 0, where C is the constraint equation.
+	/// @param ioBody1 The first body that this constraint is attached to
+	/// @param ioBody2 The second body that this constraint is attached to
+	/// @param inWorldSpaceHingeAxis The axis around which body 1 rotates
+	/// @param inWorldSpaceSliderAxis The axis along which body 2 slides
+	/// @param inRatio The ratio between rotation and translation
+	inline bool					SolveVelocityConstraint(Body &ioBody1, Vec3Arg inWorldSpaceHingeAxis, Body &ioBody2, Vec3Arg inWorldSpaceSliderAxis, float inRatio)
+	{
+		// Lagrange multiplier is:
+		//
+		// lambda = -K^-1 (J v + b)
+		float lambda = mEffectiveMass * (inRatio * inWorldSpaceSliderAxis.Dot(ioBody2.GetLinearVelocity()) - inWorldSpaceHingeAxis.Dot(ioBody1.GetAngularVelocity()));
+		mTotalLambda += lambda; // Store accumulated impulse
+
+		return ApplyVelocityStep(ioBody1, ioBody2, lambda);
+	}
+
+	/// Return lagrange multiplier
+	float						GetTotalLambda() const
+	{
+		return mTotalLambda;
+	}
+
+	/// Iteratively update the position constraint. Makes sure C(...) == 0.
+	/// @param ioBody1 The first body that this constraint is attached to
+	/// @param ioBody2 The second body that this constraint is attached to
+	/// @param inC Value of the constraint equation (C)
+	/// @param inBaumgarte Baumgarte constant (fraction of the error to correct)
+	inline bool					SolvePositionConstraint(Body &ioBody1, Body &ioBody2, float inC, float inBaumgarte) const
+	{
+		// Only apply position constraint when the constraint is hard, otherwise the velocity bias will fix the constraint
+		if (inC != 0.0f)
+		{
+			// Calculate lagrange multiplier (lambda) for Baumgarte stabilization:
+			//
+			// lambda = -K^-1 * beta / dt * C
+			//
+			// We should divide by inDeltaTime, but we should multiply by inDeltaTime in the Euler step below so they're cancelled out
+			float lambda = -mEffectiveMass * inBaumgarte * inC; 
+
+			// Directly integrate velocity change for one time step
+			//
+			// Euler velocity integration: 
+			// dv = M^-1 P
+			//
+			// Impulse:
+			// P = J^T lambda
+			//
+			// Euler position integration:
+			// x' = x + dv * dt
+			//
+			// Note we don't accumulate velocities for the stabilization. This is using the approach described in 'Modeling and 
+			// Solving Constraints' by Erin Catto presented at GDC 2007. On slide 78 it is suggested to split up the Baumgarte 
+			// stabilization for positional drift so that it does not actually add to the momentum. We combine an Euler velocity 
+			// integrate + a position integrate and then discard the velocity change.
+			if (ioBody1.IsDynamic())
+				ioBody1.AddRotationStep(lambda * mInvI1_A);
+			if (ioBody2.IsDynamic())
+				ioBody2.SubPositionStep(lambda * mRatio_InvM2_B);
+			return true;
+		}
+
+		return false;
+	}
+
+	/// Save state of this constraint part
+	void						SaveState(StateRecorder &inStream) const
+	{
+		inStream.Write(mTotalLambda);
+	}
+
+	/// Restore state of this constraint part
+	void						RestoreState(StateRecorder &inStream)
+	{
+		inStream.Read(mTotalLambda);
+	}
+
+private:
+	Vec3						mInvI1_A;
+	Vec3						mRatio_InvM2_B;
+	float						mEffectiveMass = 0.0f;
+	float						mTotalLambda = 0.0f;
+};
+
+JPH_NAMESPACE_END

+ 0 - 1
Jolt/Physics/Constraints/ConstraintPart/RotationEulerConstraintPart.h

@@ -3,7 +3,6 @@
 
 #pragma once
 
-#include <Jolt/Physics/PhysicsSettings.h>
 #include <Jolt/Physics/Body/Body.h>
 #include <Jolt/Physics/StateRecorder.h>
 

+ 0 - 1
Jolt/Physics/Constraints/ConstraintPart/RotationQuatConstraintPart.h

@@ -3,7 +3,6 @@
 
 #pragma once
 
-#include <Jolt/Physics/PhysicsSettings.h>
 #include <Jolt/Physics/Body/Body.h>
 #include <Jolt/Physics/StateRecorder.h>
 

+ 1 - 0
Jolt/Physics/Constraints/ContactConstraintManager.h

@@ -19,6 +19,7 @@ JPH_SUPPRESS_WARNINGS_STD_END
 
 JPH_NAMESPACE_BEGIN
 
+struct PhysicsSettings;
 class PhysicsUpdateContext;
 
 class ContactConstraintManager : public NonCopyable

+ 150 - 0
Jolt/Physics/Constraints/GearConstraint.cpp

@@ -0,0 +1,150 @@
+// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
+// SPDX-License-Identifier: MIT
+
+#include <Jolt/Jolt.h>
+
+#include <Jolt/Physics/Constraints/GearConstraint.h>
+#include <Jolt/Physics/Body/Body.h>
+#include <Jolt/ObjectStream/TypeDeclarations.h>
+#include <Jolt/Core/StreamIn.h>
+#include <Jolt/Core/StreamOut.h>
+#ifdef JPH_DEBUG_RENDERER
+	#include <Jolt/Renderer/DebugRenderer.h>
+#endif // JPH_DEBUG_RENDERER
+
+JPH_NAMESPACE_BEGIN
+
+JPH_IMPLEMENT_SERIALIZABLE_VIRTUAL(GearConstraintSettings)
+{
+	JPH_ADD_BASE_CLASS(GearConstraintSettings, TwoBodyConstraintSettings)
+
+	JPH_ADD_ENUM_ATTRIBUTE(GearConstraintSettings, mSpace)
+	JPH_ADD_ATTRIBUTE(GearConstraintSettings, mHingeAxis1)
+	JPH_ADD_ATTRIBUTE(GearConstraintSettings, mHingeAxis2)
+	JPH_ADD_ATTRIBUTE(GearConstraintSettings, mRatio)
+}
+
+void GearConstraintSettings::SaveBinaryState(StreamOut &inStream) const
+{ 
+	ConstraintSettings::SaveBinaryState(inStream);
+
+	inStream.Write(mSpace);
+	inStream.Write(mHingeAxis1);
+	inStream.Write(mHingeAxis2);
+	inStream.Write(mRatio);
+}
+
+void GearConstraintSettings::RestoreBinaryState(StreamIn &inStream)
+{
+	ConstraintSettings::RestoreBinaryState(inStream);
+
+	inStream.Read(mSpace);
+	inStream.Read(mHingeAxis1);
+	inStream.Read(mHingeAxis2);
+	inStream.Read(mRatio);
+}
+
+TwoBodyConstraint *GearConstraintSettings::Create(Body &inBody1, Body &inBody2) const
+{
+	return new GearConstraint(inBody1, inBody2, *this);
+}
+
+GearConstraint::GearConstraint(Body &inBody1, Body &inBody2, const GearConstraintSettings &inSettings) :
+	TwoBodyConstraint(inBody1, inBody2, inSettings),
+	mLocalSpaceHingeAxis1(inSettings.mHingeAxis1),
+	mLocalSpaceHingeAxis2(inSettings.mHingeAxis2),
+	mRatio(inSettings.mRatio)
+{
+	if (inSettings.mSpace == EConstraintSpace::WorldSpace)
+	{
+		// If all properties were specified in world space, take them to local space now
+		mLocalSpaceHingeAxis1 = inBody1.GetInverseCenterOfMassTransform().Multiply3x3(mLocalSpaceHingeAxis1).Normalized();
+		mLocalSpaceHingeAxis2 = inBody2.GetInverseCenterOfMassTransform().Multiply3x3(mLocalSpaceHingeAxis2).Normalized();
+	}
+}
+
+void GearConstraint::CalculateConstraintProperties(Mat44Arg inRotation1, Mat44Arg inRotation2)
+{
+	// Calculate world space normals
+	mWorldSpaceHingeAxis1 = inRotation1 * mLocalSpaceHingeAxis1;
+	mWorldSpaceHingeAxis2 = inRotation2 * mLocalSpaceHingeAxis2;
+
+	mGearConstraintPart.CalculateConstraintProperties(*mBody1, mWorldSpaceHingeAxis1, *mBody2, mWorldSpaceHingeAxis2, mRatio);
+}
+
+void GearConstraint::SetupVelocityConstraint(float inDeltaTime)
+{
+	// Calculate constraint properties that are constant while bodies don't move
+	Mat44 rotation1 = Mat44::sRotation(mBody1->GetRotation());
+	Mat44 rotation2 = Mat44::sRotation(mBody2->GetRotation());
+	CalculateConstraintProperties(rotation1, rotation2);
+}
+
+void GearConstraint::WarmStartVelocityConstraint(float inWarmStartImpulseRatio)
+{
+	// Warm starting: Apply previous frame impulse
+	mGearConstraintPart.WarmStart(*mBody1, *mBody2, inWarmStartImpulseRatio);
+}
+
+bool GearConstraint::SolveVelocityConstraint(float inDeltaTime)
+{
+	return mGearConstraintPart.SolveVelocityConstraint(*mBody1, mWorldSpaceHingeAxis1, *mBody2, mWorldSpaceHingeAxis2, mRatio);
+}
+
+bool GearConstraint::SolvePositionConstraint(float inDeltaTime, float inBaumgarte)
+{
+	// To be implemented
+	return false;
+}
+
+#ifdef JPH_DEBUG_RENDERER
+void GearConstraint::DrawConstraint(DebugRenderer *inRenderer) const
+{
+	Mat44 transform1 = mBody1->GetCenterOfMassTransform();
+	Mat44 transform2 = mBody2->GetCenterOfMassTransform();
+
+	// Draw constraint axis
+	inRenderer->DrawArrow(transform1.GetTranslation(), transform1 * mLocalSpaceHingeAxis1, Color::sGreen, 0.01f);
+	inRenderer->DrawArrow(transform2.GetTranslation(), transform2 * mLocalSpaceHingeAxis2, Color::sBlue, 0.01f);
+}
+
+#endif // JPH_DEBUG_RENDERER
+
+void GearConstraint::SaveState(StateRecorder &inStream) const
+{
+	TwoBodyConstraint::SaveState(inStream);
+
+	mGearConstraintPart.SaveState(inStream);
+}
+
+void GearConstraint::RestoreState(StateRecorder &inStream)
+{
+	TwoBodyConstraint::RestoreState(inStream);
+
+	mGearConstraintPart.RestoreState(inStream);
+}
+
+Ref<ConstraintSettings> GearConstraint::GetConstraintSettings() const
+{
+	GearConstraintSettings *settings = new GearConstraintSettings;
+	ToConstraintSettings(*settings);
+	settings->mSpace = EConstraintSpace::LocalToBodyCOM;
+	settings->mHingeAxis1 = mLocalSpaceHingeAxis1;
+	settings->mHingeAxis2 = mLocalSpaceHingeAxis2;
+	settings->mRatio = mRatio;
+	return settings;
+}
+
+Mat44 GearConstraint::GetConstraintToBody1Matrix() const
+{
+	Vec3 perp = mLocalSpaceHingeAxis1.GetNormalizedPerpendicular();
+	return Mat44(Vec4(mLocalSpaceHingeAxis1, 0), Vec4(perp, 0), Vec4(mLocalSpaceHingeAxis1.Cross(perp), 0), Vec4(0, 0, 0, 1)); 
+}
+
+Mat44 GearConstraint::GetConstraintToBody2Matrix() const 
+{ 
+	Vec3 perp = mLocalSpaceHingeAxis2.GetNormalizedPerpendicular();
+	return Mat44(Vec4(mLocalSpaceHingeAxis2, 0), Vec4(perp, 0), Vec4(mLocalSpaceHingeAxis2.Cross(perp), 0), Vec4(0, 0, 0, 1)); 
+}
+
+JPH_NAMESPACE_END

+ 104 - 0
Jolt/Physics/Constraints/GearConstraint.h

@@ -0,0 +1,104 @@
+// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
+// SPDX-License-Identifier: MIT
+
+#pragma once
+
+#include <Jolt/Physics/Constraints/TwoBodyConstraint.h>
+#include <Jolt/Physics/Constraints/ConstraintPart/GearConstraintPart.h>
+
+JPH_NAMESPACE_BEGIN
+
+/// Gear constraint settings
+class GearConstraintSettings final : public TwoBodyConstraintSettings
+{
+public:
+	JPH_DECLARE_SERIALIZABLE_VIRTUAL(GearConstraintSettings)
+
+	// See: ConstraintSettings::SaveBinaryState
+	virtual void				SaveBinaryState(StreamOut &inStream) const override;
+
+	/// Create an an instance of this constraint.
+	virtual TwoBodyConstraint *	Create(Body &inBody1, Body &inBody2) const override;
+
+	/// Defines the ratio between the rotation of both gears
+	/// The ratio is defined as: Gear1Rotation(t) = ratio * Gear2Rotation(t)
+	/// @param inNumTeethGear1 Number of teeth that body 1 has
+	/// @param inNumTeethGear2 Number of teeth that body 2 has
+	void						SetRatio(int inNumTeethGear1, int inNumTeethGear2)
+	{
+		mRatio = float(inNumTeethGear2) / float(inNumTeethGear1);
+	}
+
+	/// This determines in which space the constraint is setup, all properties below should be in the specified space
+	EConstraintSpace			mSpace = EConstraintSpace::WorldSpace;
+
+	/// Body 1 constraint reference frame (space determined by mSpace).
+	Vec3						mHingeAxis1 = Vec3::sAxisX();
+	
+	/// Body 2 constraint reference frame (space determined by mSpace)
+	Vec3						mHingeAxis2 = Vec3::sAxisX();
+
+	/// Ratio between both gears, see SetRatio.
+	float						mRatio = 1.0f;
+
+protected:
+	// See: ConstraintSettings::RestoreBinaryState
+	virtual void				RestoreBinaryState(StreamIn &inStream) override;
+};
+
+/// A gear constraint constrains the rotation of body1 to the rotation of body 2 using a gear.
+/// Note that this constraint needs to be used in conjunction with a two hinge constraints.
+class GearConstraint final : public TwoBodyConstraint
+{
+public:
+	/// Construct gear constraint
+								GearConstraint(Body &inBody1, Body &inBody2, const GearConstraintSettings &inSettings);
+
+	// Generic interface of a constraint
+	virtual EConstraintSubType	GetSubType() const override								{ return EConstraintSubType::Gear; }
+	virtual void				SetupVelocityConstraint(float inDeltaTime) override;
+	virtual void				WarmStartVelocityConstraint(float inWarmStartImpulseRatio) override;
+	virtual bool				SolveVelocityConstraint(float inDeltaTime) override;
+	virtual bool				SolvePositionConstraint(float inDeltaTime, float inBaumgarte) override;
+#ifdef JPH_DEBUG_RENDERER
+	virtual void				DrawConstraint(DebugRenderer *inRenderer) const override;
+#endif // JPH_DEBUG_RENDERER
+	virtual void				SaveState(StateRecorder &inStream) const override;
+	virtual void				RestoreState(StateRecorder &inStream) override;
+	virtual Ref<ConstraintSettings> GetConstraintSettings() const override;
+
+	// See: TwoBodyConstraint
+	virtual Mat44				GetConstraintToBody1Matrix() const override;
+	virtual Mat44				GetConstraintToBody2Matrix() const override;
+
+	///@name Get Lagrange multiplier from last physics update (relates to how much force/torque was applied to satisfy the constraint)
+	inline float				GetTotalLambda() const									{ return mGearConstraintPart.GetTotalLambda(); }
+
+private:
+	// Internal helper function to calculate the values below
+	void						CalculateConstraintProperties(Mat44Arg inRotation1, Mat44Arg inRotation2);
+
+	// CONFIGURATION PROPERTIES FOLLOW
+
+	// Local space hinge axis for body 1
+	Vec3						mLocalSpaceHingeAxis1;
+
+	// Local space hinge axis for body 2
+	Vec3						mLocalSpaceHingeAxis2;
+
+	// Ratio between gear 1 and 2
+	float						mRatio;
+
+	// RUN TIME PROPERTIES FOLLOW
+
+	// World space hinge axis for body 1
+	Vec3						mWorldSpaceHingeAxis1;
+
+	// World space hinge axis for body 2
+	Vec3						mWorldSpaceHingeAxis2;
+
+	// The constraint parts
+	GearConstraintPart			mGearConstraintPart;
+};
+
+JPH_NAMESPACE_END

+ 150 - 0
Jolt/Physics/Constraints/RackAndPinionConstraint.cpp

@@ -0,0 +1,150 @@
+// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
+// SPDX-License-Identifier: MIT
+
+#include <Jolt/Jolt.h>
+
+#include <Jolt/Physics/Constraints/RackAndPinionConstraint.h>
+#include <Jolt/Physics/Body/Body.h>
+#include <Jolt/ObjectStream/TypeDeclarations.h>
+#include <Jolt/Core/StreamIn.h>
+#include <Jolt/Core/StreamOut.h>
+#ifdef JPH_DEBUG_RENDERER
+	#include <Jolt/Renderer/DebugRenderer.h>
+#endif // JPH_DEBUG_RENDERER
+
+JPH_NAMESPACE_BEGIN
+
+JPH_IMPLEMENT_SERIALIZABLE_VIRTUAL(RackAndPinionConstraintSettings)
+{
+	JPH_ADD_BASE_CLASS(RackAndPinionConstraintSettings, TwoBodyConstraintSettings)
+
+	JPH_ADD_ENUM_ATTRIBUTE(RackAndPinionConstraintSettings, mSpace)
+	JPH_ADD_ATTRIBUTE(RackAndPinionConstraintSettings, mHingeAxis)
+	JPH_ADD_ATTRIBUTE(RackAndPinionConstraintSettings, mSliderAxis)
+	JPH_ADD_ATTRIBUTE(RackAndPinionConstraintSettings, mRatio)
+}
+
+void RackAndPinionConstraintSettings::SaveBinaryState(StreamOut &inStream) const
+{ 
+	ConstraintSettings::SaveBinaryState(inStream);
+
+	inStream.Write(mSpace);
+	inStream.Write(mHingeAxis);
+	inStream.Write(mSliderAxis);
+	inStream.Write(mRatio);
+}
+
+void RackAndPinionConstraintSettings::RestoreBinaryState(StreamIn &inStream)
+{
+	ConstraintSettings::RestoreBinaryState(inStream);
+
+	inStream.Read(mSpace);
+	inStream.Read(mHingeAxis);
+	inStream.Read(mSliderAxis);
+	inStream.Read(mRatio);
+}
+
+TwoBodyConstraint *RackAndPinionConstraintSettings::Create(Body &inBody1, Body &inBody2) const
+{
+	return new RackAndPinionConstraint(inBody1, inBody2, *this);
+}
+
+RackAndPinionConstraint::RackAndPinionConstraint(Body &inBody1, Body &inBody2, const RackAndPinionConstraintSettings &inSettings) :
+	TwoBodyConstraint(inBody1, inBody2, inSettings),
+	mLocalSpaceHingeAxis(inSettings.mHingeAxis),
+	mLocalSpaceSliderAxis(inSettings.mSliderAxis),
+	mRatio(inSettings.mRatio)
+{
+	if (inSettings.mSpace == EConstraintSpace::WorldSpace)
+	{
+		// If all properties were specified in world space, take them to local space now
+		mLocalSpaceHingeAxis = inBody1.GetInverseCenterOfMassTransform().Multiply3x3(mLocalSpaceHingeAxis).Normalized();
+		mLocalSpaceSliderAxis = inBody2.GetInverseCenterOfMassTransform().Multiply3x3(mLocalSpaceSliderAxis).Normalized();
+	}
+}
+
+void RackAndPinionConstraint::CalculateConstraintProperties(Mat44Arg inRotation1, Mat44Arg inRotation2)
+{
+	// Calculate world space normals
+	mWorldSpaceHingeAxis = inRotation1 * mLocalSpaceHingeAxis;
+	mWorldSpaceSliderAxis = inRotation2 * mLocalSpaceSliderAxis;
+
+	mRackAndPinionConstraintPart.CalculateConstraintProperties(*mBody1, mWorldSpaceHingeAxis, *mBody2, mWorldSpaceSliderAxis, mRatio);
+}
+
+void RackAndPinionConstraint::SetupVelocityConstraint(float inDeltaTime)
+{
+	// Calculate constraint properties that are constant while bodies don't move
+	Mat44 rotation1 = Mat44::sRotation(mBody1->GetRotation());
+	Mat44 rotation2 = Mat44::sRotation(mBody2->GetRotation());
+	CalculateConstraintProperties(rotation1, rotation2);
+}
+
+void RackAndPinionConstraint::WarmStartVelocityConstraint(float inWarmStartImpulseRatio)
+{
+	// Warm starting: Apply previous frame impulse
+	mRackAndPinionConstraintPart.WarmStart(*mBody1, *mBody2, inWarmStartImpulseRatio);
+}
+
+bool RackAndPinionConstraint::SolveVelocityConstraint(float inDeltaTime)
+{
+	return mRackAndPinionConstraintPart.SolveVelocityConstraint(*mBody1, mWorldSpaceHingeAxis, *mBody2, mWorldSpaceSliderAxis, mRatio);
+}
+
+bool RackAndPinionConstraint::SolvePositionConstraint(float inDeltaTime, float inBaumgarte)
+{
+	// To be implemented
+	return false;
+}
+
+#ifdef JPH_DEBUG_RENDERER
+void RackAndPinionConstraint::DrawConstraint(DebugRenderer *inRenderer) const
+{
+	Mat44 transform1 = mBody1->GetCenterOfMassTransform();
+	Mat44 transform2 = mBody2->GetCenterOfMassTransform();
+
+	// Draw constraint axis
+	inRenderer->DrawArrow(transform1.GetTranslation(), transform1 * mLocalSpaceHingeAxis, Color::sGreen, 0.01f);
+	inRenderer->DrawArrow(transform2.GetTranslation(), transform2 * mLocalSpaceSliderAxis, Color::sBlue, 0.01f);
+}
+
+#endif // JPH_DEBUG_RENDERER
+
+void RackAndPinionConstraint::SaveState(StateRecorder &inStream) const
+{
+	TwoBodyConstraint::SaveState(inStream);
+
+	mRackAndPinionConstraintPart.SaveState(inStream);
+}
+
+void RackAndPinionConstraint::RestoreState(StateRecorder &inStream)
+{
+	TwoBodyConstraint::RestoreState(inStream);
+
+	mRackAndPinionConstraintPart.RestoreState(inStream);
+}
+
+Ref<ConstraintSettings> RackAndPinionConstraint::GetConstraintSettings() const
+{
+	RackAndPinionConstraintSettings *settings = new RackAndPinionConstraintSettings;
+	ToConstraintSettings(*settings);
+	settings->mSpace = EConstraintSpace::LocalToBodyCOM;
+	settings->mHingeAxis = mLocalSpaceHingeAxis;
+	settings->mSliderAxis = mLocalSpaceSliderAxis;
+	settings->mRatio = mRatio;
+	return settings;
+}
+
+Mat44 RackAndPinionConstraint::GetConstraintToBody1Matrix() const
+{
+	Vec3 perp = mLocalSpaceHingeAxis.GetNormalizedPerpendicular();
+	return Mat44(Vec4(mLocalSpaceHingeAxis, 0), Vec4(perp, 0), Vec4(mLocalSpaceHingeAxis.Cross(perp), 0), Vec4(0, 0, 0, 1)); 
+}
+
+Mat44 RackAndPinionConstraint::GetConstraintToBody2Matrix() const 
+{ 
+	Vec3 perp = mLocalSpaceSliderAxis.GetNormalizedPerpendicular();
+	return Mat44(Vec4(mLocalSpaceSliderAxis, 0), Vec4(perp, 0), Vec4(mLocalSpaceSliderAxis.Cross(perp), 0), Vec4(0, 0, 0, 1)); 
+}
+
+JPH_NAMESPACE_END

+ 106 - 0
Jolt/Physics/Constraints/RackAndPinionConstraint.h

@@ -0,0 +1,106 @@
+// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
+// SPDX-License-Identifier: MIT
+
+#pragma once
+
+#include <Jolt/Physics/Constraints/TwoBodyConstraint.h>
+#include <Jolt/Physics/Constraints/ConstraintPart/RackAndPinionConstraintPart.h>
+
+JPH_NAMESPACE_BEGIN
+
+/// Rack and pinion constraint (slider & gear) settings
+class RackAndPinionConstraintSettings final : public TwoBodyConstraintSettings
+{
+public:
+	JPH_DECLARE_SERIALIZABLE_VIRTUAL(RackAndPinionConstraintSettings)
+
+	// See: ConstraintSettings::SaveBinaryState
+	virtual void				SaveBinaryState(StreamOut &inStream) const override;
+
+	/// Create an an instance of this constraint.
+	/// Body1 should be the pinion (gear) and body 2 the rack (slider).
+	virtual TwoBodyConstraint *	Create(Body &inBody1, Body &inBody2) const override;
+
+	/// Defines the ratio between the rotation of the pinion and the translation of the rack.
+	/// The ratio is defined as: PinionRotation(t) = ratio * RackTranslation(t)
+	/// @param inNumTeethRack Number of teeth that the rack has
+	/// @param inRackLength Length of the rack
+	/// @param inNumTeethPinion Number of teeth the pinion has
+	void						SetRatio(int inNumTeethRack, float inRackLength, int inNumTeethPinion)
+	{
+		mRatio = 2.0f * JPH_PI * inNumTeethRack / (inRackLength * inNumTeethPinion);
+	}
+
+	/// This determines in which space the constraint is setup, all properties below should be in the specified space
+	EConstraintSpace			mSpace = EConstraintSpace::WorldSpace;
+
+	/// Body 1 (pinion) constraint reference frame (space determined by mSpace).
+	Vec3						mHingeAxis = Vec3::sAxisX();
+	
+	/// Body 2 (rack) constraint reference frame (space determined by mSpace)
+	Vec3						mSliderAxis = Vec3::sAxisX();
+
+	/// Ratio between the rack and pinion, see SetRatio.
+	float						mRatio = 1.0f;
+
+protected:
+	// See: ConstraintSettings::RestoreBinaryState
+	virtual void				RestoreBinaryState(StreamIn &inStream) override;
+};
+
+/// A rack and pinion constraint constrains the rotation of body1 to the translation of body 2.
+/// Note that this constraint needs to be used in conjunction with a hinge constraint for body 1 and a slider constraint for body 2.
+class RackAndPinionConstraint final : public TwoBodyConstraint
+{
+public:
+	/// Construct gear constraint
+								RackAndPinionConstraint(Body &inBody1, Body &inBody2, const RackAndPinionConstraintSettings &inSettings);
+
+	// Generic interface of a constraint
+	virtual EConstraintSubType	GetSubType() const override								{ return EConstraintSubType::RackAndPinion; }
+	virtual void				SetupVelocityConstraint(float inDeltaTime) override;
+	virtual void				WarmStartVelocityConstraint(float inWarmStartImpulseRatio) override;
+	virtual bool				SolveVelocityConstraint(float inDeltaTime) override;
+	virtual bool				SolvePositionConstraint(float inDeltaTime, float inBaumgarte) override;
+#ifdef JPH_DEBUG_RENDERER
+	virtual void				DrawConstraint(DebugRenderer *inRenderer) const override;
+#endif // JPH_DEBUG_RENDERER
+	virtual void				SaveState(StateRecorder &inStream) const override;
+	virtual void				RestoreState(StateRecorder &inStream) override;
+	virtual Ref<ConstraintSettings> GetConstraintSettings() const override;
+
+	// See: TwoBodyConstraint
+	virtual Mat44				GetConstraintToBody1Matrix() const override;
+	virtual Mat44				GetConstraintToBody2Matrix() const override;
+
+	///@name Get Lagrange multiplier from last physics update (relates to how much force/torque was applied to satisfy the constraint)
+	inline float				GetTotalLambda() const									{ return mRackAndPinionConstraintPart.GetTotalLambda(); }
+
+private:
+	// Internal helper function to calculate the values below
+	void						CalculateConstraintProperties(Mat44Arg inRotation1, Mat44Arg inRotation2);
+
+	// CONFIGURATION PROPERTIES FOLLOW
+
+	// Local space hinge axis
+	Vec3						mLocalSpaceHingeAxis;
+
+	// Local space sliding direction
+	Vec3						mLocalSpaceSliderAxis;
+
+	// Ratio between rack and pinion
+	float						mRatio;
+
+	// RUN TIME PROPERTIES FOLLOW
+
+	// World space hinge axis
+	Vec3						mWorldSpaceHingeAxis;
+
+	// World space sliding direction
+	Vec3						mWorldSpaceSliderAxis;
+
+	// The constraint parts
+	RackAndPinionConstraintPart	mRackAndPinionConstraintPart;
+};
+
+JPH_NAMESPACE_END

+ 17 - 4
Jolt/Physics/Constraints/SliderConstraint.cpp

@@ -27,6 +27,8 @@ JPH_IMPLEMENT_SERIALIZABLE_VIRTUAL(SliderConstraintSettings)
 	JPH_ADD_ATTRIBUTE(SliderConstraintSettings, mNormalAxis2)
 	JPH_ADD_ATTRIBUTE(SliderConstraintSettings, mLimitsMin)
 	JPH_ADD_ATTRIBUTE(SliderConstraintSettings, mLimitsMax)
+	JPH_ADD_ATTRIBUTE(SliderConstraintSettings, mFrequency)
+	JPH_ADD_ATTRIBUTE(SliderConstraintSettings, mDamping)
 	JPH_ADD_ATTRIBUTE(SliderConstraintSettings, mMaxFrictionForce)
 	JPH_ADD_ATTRIBUTE(SliderConstraintSettings, mMotorSettings)
 }
@@ -73,6 +75,8 @@ void SliderConstraintSettings::SaveBinaryState(StreamOut &inStream) const
 	inStream.Write(mNormalAxis2);
 	inStream.Write(mLimitsMin);
 	inStream.Write(mLimitsMax);
+	inStream.Write(mFrequency);
+	inStream.Write(mDamping);
 	inStream.Write(mMaxFrictionForce);
 	mMotorSettings.SaveBinaryState(inStream);
 }
@@ -90,6 +94,8 @@ void SliderConstraintSettings::RestoreBinaryState(StreamIn &inStream)
 	inStream.Read(mNormalAxis2);
 	inStream.Read(mLimitsMin);
 	inStream.Read(mLimitsMax);
+	inStream.Read(mFrequency);
+	inStream.Read(mDamping);
 	inStream.Read(mMaxFrictionForce);
 	mMotorSettings.RestoreBinaryState(inStream);
 }
@@ -130,8 +136,12 @@ SliderConstraint::SliderConstraint(Body &inBody1, Body &inBody2, const SliderCon
 	mLocalSpaceNormal2 = mLocalSpaceSliderAxis1.Cross(mLocalSpaceNormal1);
 
 	// Store limits
-	JPH_ASSERT(inSettings.mLimitsMin != inSettings.mLimitsMax, "Better use a fixed constraint");
+	JPH_ASSERT(inSettings.mLimitsMin != inSettings.mLimitsMax || inSettings.mFrequency > 0.0f, "Better use a fixed constraint");
 	SetLimits(inSettings.mLimitsMin, inSettings.mLimitsMax);
+
+	// Store frequency and damping
+	SetFrequency(inSettings.mFrequency);
+	SetDamping(inSettings.mDamping);
 }
 
 void SliderConstraint::SetLimits(float inLimitsMin, float inLimitsMax)
@@ -177,8 +187,9 @@ void SliderConstraint::CalculateSlidingAxisAndPosition(Mat44Arg inRotation1)
 void SliderConstraint::CalculatePositionLimitsConstraintProperties(float inDeltaTime)
 {
 	// Check if distance is within limits
-	if (mHasLimits && (mD <= mLimitsMin || mD >= mLimitsMax))
-		mPositionLimitsConstraintPart.CalculateConstraintProperties(inDeltaTime, *mBody1, mR1 + mU, *mBody2, mR2, mWorldSpaceSliderAxis);
+	bool below_min = mD <= mLimitsMin;
+	if (mHasLimits && (below_min || mD >= mLimitsMax))
+		mPositionLimitsConstraintPart.CalculateConstraintProperties(inDeltaTime, *mBody1, mR1 + mU, *mBody2, mR2, mWorldSpaceSliderAxis, 0.0f, mD - (below_min? mLimitsMin : mLimitsMax), mFrequency, mDamping);
 	else
 		mPositionLimitsConstraintPart.Deactivate();
 }
@@ -287,7 +298,7 @@ bool SliderConstraint::SolvePositionConstraint(float inDeltaTime, float inBaumga
 
 	// Solve limits along slider axis
 	bool limit = false;
-	if (mHasLimits)
+	if (mHasLimits && mFrequency <= 0.0f)
 	{
 		rotation1 = Mat44::sRotation(mBody1->GetRotation());
 		rotation2 = Mat44::sRotation(mBody2->GetRotation());
@@ -412,6 +423,8 @@ Ref<ConstraintSettings> SliderConstraint::GetConstraintSettings() const
 	settings->mNormalAxis2 = inv_initial_rotation.Multiply3x3(mLocalSpaceNormal1);
 	settings->mLimitsMin = mLimitsMin;
 	settings->mLimitsMax = mLimitsMax;
+	settings->mFrequency = mFrequency;
+	settings->mDamping = mDamping;
 	settings->mMaxFrictionForce = mMaxFrictionForce;
 	settings->mMotorSettings = mMotorSettings;
 	return settings;

+ 17 - 0
Jolt/Physics/Constraints/SliderConstraint.h

@@ -48,6 +48,11 @@ public:
 	float						mLimitsMin = -FLT_MAX;
 	float						mLimitsMax = FLT_MAX;
 
+	/// If mFrequency > 0 the constraint limits will be soft and mFrequency specifies the oscillation frequency in Hz and mDamping the damping ratio (0 = no damping, 1 = critical damping).
+	/// If mFrequency <= 0, mDamping is ignored and the limits will be hard.
+	float						mFrequency = 0.0f;
+	float						mDamping = 0.0f;
+
 	/// Maximum amount of friction force to apply (N) when not driven by a motor.
 	float						mMaxFrictionForce = 0.0f;
 
@@ -106,6 +111,14 @@ public:
 	float						GetLimitsMax() const									{ return mLimitsMax; }
 	bool						HasLimits() const										{ return mHasLimits; }
 
+	/// Update the spring frequency for the limits constraint
+	void						SetFrequency(float inFrequency)							{ JPH_ASSERT(inFrequency >= 0.0f); mFrequency = inFrequency; }
+	float						GetFrequency() const									{ return mFrequency; }
+
+	/// Update the spring damping for the limits constraint
+	void						SetDamping(float inDamping)								{ JPH_ASSERT(inDamping >= 0.0f); mDamping = inDamping; }
+	float						GetDamping() const										{ return mDamping; }
+
 	///@name Get Lagrange multiplier from last physics update (relates to how much force/torque was applied to satisfy the constraint)
 	inline Vector<2> 			GetTotalLambdaPosition() const							{ return mPositionConstraintPart.GetTotalLambda(); }
 	inline float				GetTotalLambdaPositionLimits() const					{ return mPositionLimitsConstraintPart.GetTotalLambda(); }
@@ -141,6 +154,10 @@ private:
 	float						mLimitsMin;
 	float						mLimitsMax;
 
+	// Soft slider limits
+	float						mFrequency;
+	float						mDamping;
+
 	// Friction
 	float						mMaxFrictionForce;
 

+ 1 - 0
Jolt/Physics/PhysicsSystem.h

@@ -10,6 +10,7 @@
 #include <Jolt/Physics/Constraints/ConstraintManager.h>
 #include <Jolt/Physics/IslandBuilder.h>
 #include <Jolt/Physics/PhysicsUpdateContext.h>
+#include <Jolt/Physics/PhysicsSettings.h>
 
 JPH_NAMESPACE_BEGIN
 

+ 4 - 0
Jolt/RegisterTypes.cpp

@@ -53,6 +53,8 @@ JPH_DECLARE_RTTI_WITH_NAMESPACE_FOR_FACTORY(JPH, PathConstraintPath)
 JPH_DECLARE_RTTI_WITH_NAMESPACE_FOR_FACTORY(JPH, PathConstraintPathHermite)
 JPH_DECLARE_RTTI_WITH_NAMESPACE_FOR_FACTORY(JPH, VehicleConstraintSettings)
 JPH_DECLARE_RTTI_WITH_NAMESPACE_FOR_FACTORY(JPH, WheeledVehicleControllerSettings)
+JPH_DECLARE_RTTI_WITH_NAMESPACE_FOR_FACTORY(JPH, RackAndPinionConstraintSettings)
+JPH_DECLARE_RTTI_WITH_NAMESPACE_FOR_FACTORY(JPH, GearConstraintSettings)
 JPH_DECLARE_RTTI_WITH_NAMESPACE_FOR_FACTORY(JPH, MotorSettings)
 JPH_DECLARE_RTTI_WITH_NAMESPACE_FOR_FACTORY(JPH, PhysicsScene)
 JPH_DECLARE_RTTI_WITH_NAMESPACE_FOR_FACTORY(JPH, PhysicsMaterial)
@@ -126,6 +128,8 @@ void RegisterTypes()
 		JPH_RTTI(WheeledVehicleControllerSettings),
 		JPH_RTTI(PathConstraintPath),
 		JPH_RTTI(PathConstraintPathHermite),
+		JPH_RTTI(RackAndPinionConstraintSettings),
+		JPH_RTTI(GearConstraintSettings),
 		JPH_RTTI(MotorSettings),
 		JPH_RTTI(PhysicsScene),
 		JPH_RTTI(PhysicsMaterial),

+ 2 - 0
README.md

@@ -55,6 +55,8 @@ For more information see the [Architecture and API documentation](https://jrouwe
 	* Hinge.
 	* Slider (also called prismatic).
 	* Cone.
+	* Rack and Pinion.
+	* Gear.
 	* Smooth spline paths.
 	* Swing-twist (for humanoid shoulders).
 	* 6 DOF.

+ 4 - 0
Samples/Samples.cmake

@@ -27,6 +27,8 @@ set(SAMPLES_SRC_FILES
 	${SAMPLES_ROOT}/Tests/Constraints/DistanceConstraintTest.h
 	${SAMPLES_ROOT}/Tests/Constraints/FixedConstraintTest.cpp
 	${SAMPLES_ROOT}/Tests/Constraints/FixedConstraintTest.h
+	${SAMPLES_ROOT}/Tests/Constraints/GearConstraintTest.cpp
+	${SAMPLES_ROOT}/Tests/Constraints/GearConstraintTest.h
 	${SAMPLES_ROOT}/Tests/Constraints/HingeConstraintTest.cpp
 	${SAMPLES_ROOT}/Tests/Constraints/HingeConstraintTest.h
 	${SAMPLES_ROOT}/Tests/Constraints/PointConstraintTest.cpp
@@ -39,6 +41,8 @@ set(SAMPLES_SRC_FILES
 	${SAMPLES_ROOT}/Tests/Constraints/PoweredSwingTwistConstraintTest.h
 	${SAMPLES_ROOT}/Tests/Constraints/PoweredSliderConstraintTest.cpp
 	${SAMPLES_ROOT}/Tests/Constraints/PoweredSliderConstraintTest.h
+	${SAMPLES_ROOT}/Tests/Constraints/RackAndPinionConstraintTest.cpp
+	${SAMPLES_ROOT}/Tests/Constraints/RackAndPinionConstraintTest.h
 	${SAMPLES_ROOT}/Tests/Constraints/SwingTwistConstraintFrictionTest.cpp
 	${SAMPLES_ROOT}/Tests/Constraints/SwingTwistConstraintFrictionTest.h
 	${SAMPLES_ROOT}/Tests/Constraints/SwingTwistConstraintTest.cpp

+ 4 - 0
Samples/SamplesApp.cpp

@@ -132,6 +132,8 @@ JPH_DECLARE_RTTI_FOR_FACTORY(ConstraintSingularityTest)
 JPH_DECLARE_RTTI_FOR_FACTORY(PoweredSwingTwistConstraintTest)
 JPH_DECLARE_RTTI_FOR_FACTORY(SwingTwistConstraintFrictionTest)
 JPH_DECLARE_RTTI_FOR_FACTORY(PathConstraintTest)
+JPH_DECLARE_RTTI_FOR_FACTORY(RackAndPinionConstraintTest)
+JPH_DECLARE_RTTI_FOR_FACTORY(GearConstraintTest)
 
 static TestNameAndRTTI sConstraintTests[] =
 {
@@ -148,6 +150,8 @@ static TestNameAndRTTI sConstraintTests[] =
 	{ "Swing Twist Constraint Friction",	JPH_RTTI(SwingTwistConstraintFrictionTest) },
 	{ "Six DOF Constraint",					JPH_RTTI(SixDOFConstraintTest) },
 	{ "Path Constraint",					JPH_RTTI(PathConstraintTest) },
+	{ "Rack And Pinion Constraint",			JPH_RTTI(RackAndPinionConstraintTest) },
+	{ "Gear Constraint",					JPH_RTTI(GearConstraintTest) },
 	{ "Spring",								JPH_RTTI(SpringTest) },
 	{ "Constraint Singularity",				JPH_RTTI(ConstraintSingularityTest) },
 };

+ 119 - 0
Samples/Tests/Constraints/GearConstraintTest.cpp

@@ -0,0 +1,119 @@
+// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
+// SPDX-License-Identifier: MIT
+
+#include <TestFramework.h>
+
+#include <Tests/Constraints/GearConstraintTest.h>
+#include <Jolt/Physics/Collision/Shape/CylinderShape.h>
+#include <Jolt/Physics/Collision/Shape/ConvexHullShape.h>
+#include <Jolt/Physics/Collision/Shape/StaticCompoundShape.h>
+#include <Jolt/Physics/Collision/GroupFilterTable.h>
+#include <Jolt/Physics/Constraints/HingeConstraint.h>
+#include <Jolt/Physics/Constraints/GearConstraint.h>
+#include <Jolt/Physics/Body/BodyCreationSettings.h>
+#include <Layers.h>
+
+JPH_IMPLEMENT_RTTI_VIRTUAL(GearConstraintTest) 
+{ 
+	JPH_ADD_BASE_CLASS(GearConstraintTest, Test) 
+}
+
+void GearConstraintTest::Initialize()
+{
+	// Floor
+	CreateFloor();
+
+	constexpr float cGearHalfWidth = 0.05f;
+
+	constexpr float cGear1Radius = 0.5f;
+	constexpr int cGear1NumTeeth = 100;
+
+	constexpr float cGear2Radius = 2.0f;
+	constexpr int cGear2NumTeeth = int(cGear1NumTeeth * cGear2Radius / cGear1Radius);
+
+	constexpr float cToothThicknessBottom = 0.01f;
+	constexpr float cToothThicknessTop = 0.005f;
+	constexpr float cToothHeight = 0.02f;
+
+	// Create a tooth
+	vector<Vec3> tooth_points = {
+		Vec3(0, cGearHalfWidth, cToothThicknessBottom),
+		Vec3(0, -cGearHalfWidth, cToothThicknessBottom),
+		Vec3(0, cGearHalfWidth, -cToothThicknessBottom),
+		Vec3(0, -cGearHalfWidth, -cToothThicknessBottom),
+		Vec3(cToothHeight, -cGearHalfWidth, cToothThicknessTop),
+		Vec3(cToothHeight, cGearHalfWidth, cToothThicknessTop),
+		Vec3(cToothHeight, -cGearHalfWidth, -cToothThicknessTop),
+		Vec3(cToothHeight, cGearHalfWidth, -cToothThicknessTop),
+	};
+	ConvexHullShapeSettings tooth_settings(tooth_points);
+	tooth_settings.SetEmbedded();
+
+	// Create gear 1
+	CylinderShapeSettings gear1_cylinder(cGearHalfWidth, cGear1Radius);
+	gear1_cylinder.SetEmbedded();
+
+	StaticCompoundShapeSettings gear1_settings;
+	gear1_settings.SetEmbedded();
+
+	gear1_settings.AddShape(Vec3::sZero(), Quat::sIdentity(), &gear1_cylinder);
+	for (int i = 0; i < cGear1NumTeeth; ++i)
+	{
+		Quat rotation = Quat::sRotation(Vec3::sAxisY(), 2.0f * JPH_PI * i / cGear1NumTeeth);
+		gear1_settings.AddShape(rotation * Vec3(cGear1Radius, 0, 0), rotation, &tooth_settings);
+	}
+
+	Vec3 gear1_initial_p(0, 3.0f, 0);
+	Quat gear1_initial_r = Quat::sRotation(Vec3::sAxisX(), 0.5f * JPH_PI);
+	Body *gear1 = mBodyInterface->CreateBody(BodyCreationSettings(&gear1_settings, gear1_initial_p, gear1_initial_r, EMotionType::Dynamic, Layers::MOVING));
+	mBodyInterface->AddBody(gear1->GetID(), EActivation::Activate);
+
+	// Create gear 2
+	CylinderShapeSettings gear2_cylinder(cGearHalfWidth, cGear2Radius);
+	gear2_cylinder.SetEmbedded();
+
+	StaticCompoundShapeSettings gear2_settings;
+	gear2_settings.SetEmbedded();
+
+	gear2_settings.AddShape(Vec3::sZero(), Quat::sIdentity(), &gear2_cylinder);
+	for (int i = 0; i < cGear2NumTeeth; ++i)
+	{
+		Quat rotation = Quat::sRotation(Vec3::sAxisY(), 2.0f * JPH_PI * i / cGear2NumTeeth);
+		gear2_settings.AddShape(rotation * Vec3(cGear2Radius, 0, 0), rotation, &tooth_settings);
+	}
+
+	Vec3 gear2_initial_p = gear1_initial_p + Vec3(cGear1Radius + cGear2Radius + cToothHeight, 0, 0);
+	Quat gear2_initial_r = gear1_initial_r;
+	Body *gear2 = mBodyInterface->CreateBody(BodyCreationSettings(&gear2_settings, gear2_initial_p, gear2_initial_r, EMotionType::Dynamic, Layers::MOVING));
+	mBodyInterface->AddBody(gear2->GetID(), EActivation::Activate);
+
+	// Create a hinge for gear 1
+	HingeConstraintSettings hinge1;
+	hinge1.mPoint1 = hinge1.mPoint2 = gear1_initial_p;
+	hinge1.mHingeAxis1 = hinge1.mHingeAxis2 = Vec3::sAxisZ();
+	hinge1.mNormalAxis1 = hinge1.mNormalAxis2 = Vec3::sAxisX();
+	mPhysicsSystem->AddConstraint(hinge1.Create(*gear1, Body::sFixedToWorld));
+
+	// Create a hinge for gear 1
+	HingeConstraintSettings hinge2;
+	hinge2.mPoint1 = hinge2.mPoint2 = gear2_initial_p;
+	hinge2.mHingeAxis1 = hinge2.mHingeAxis2 = Vec3::sAxisZ();
+	hinge2.mNormalAxis1 = hinge2.mNormalAxis2 = Vec3::sAxisX();
+	mPhysicsSystem->AddConstraint(hinge2.Create(*gear2, Body::sFixedToWorld));
+
+	// Disable collision between gears
+	Ref<GroupFilterTable> group_filter = new GroupFilterTable(2);
+	group_filter->DisableCollision(0, 1);
+	gear1->SetCollisionGroup(CollisionGroup(group_filter, 0, 0));
+	gear2->SetCollisionGroup(CollisionGroup(group_filter, 0, 1));
+
+	// Create gear constraint to constrain the two bodies
+	GearConstraintSettings gear;
+	gear.mHingeAxis1 = hinge1.mHingeAxis1;
+	gear.mHingeAxis2 = hinge2.mHingeAxis1;
+	gear.SetRatio(cGear1NumTeeth, cGear2NumTeeth);
+	mPhysicsSystem->AddConstraint(gear.Create(*gear1, *gear2));
+
+	// Give the gear a spin
+	gear2->SetAngularVelocity(Vec3(0, 0, 3.0f));
+}

+ 16 - 0
Samples/Tests/Constraints/GearConstraintTest.h

@@ -0,0 +1,16 @@
+// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
+// SPDX-License-Identifier: MIT
+
+#pragma once
+
+#include <Tests/Test.h>
+
+// This test demonstrates the use of a gear constraint
+class GearConstraintTest : public Test
+{
+public:
+	JPH_DECLARE_RTTI_VIRTUAL(GearConstraintTest)
+
+	// See: Test
+	virtual void		Initialize() override;
+};

+ 121 - 0
Samples/Tests/Constraints/RackAndPinionConstraintTest.cpp

@@ -0,0 +1,121 @@
+// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
+// SPDX-License-Identifier: MIT
+
+#include <TestFramework.h>
+
+#include <Tests/Constraints/RackAndPinionConstraintTest.h>
+#include <Jolt/Physics/Collision/Shape/BoxShape.h>
+#include <Jolt/Physics/Collision/Shape/CylinderShape.h>
+#include <Jolt/Physics/Collision/Shape/ConvexHullShape.h>
+#include <Jolt/Physics/Collision/Shape/StaticCompoundShape.h>
+#include <Jolt/Physics/Collision/GroupFilterTable.h>
+#include <Jolt/Physics/Constraints/HingeConstraint.h>
+#include <Jolt/Physics/Constraints/SliderConstraint.h>
+#include <Jolt/Physics/Constraints/RackAndPinionConstraint.h>
+#include <Jolt/Physics/Body/BodyCreationSettings.h>
+#include <Layers.h>
+
+JPH_IMPLEMENT_RTTI_VIRTUAL(RackAndPinionConstraintTest) 
+{ 
+	JPH_ADD_BASE_CLASS(RackAndPinionConstraintTest, Test) 
+}
+
+void RackAndPinionConstraintTest::Initialize()
+{
+	// Floor
+	CreateFloor();
+
+	constexpr float cGearRadius = 0.5f;
+	constexpr float cGearHalfWidth = 0.05f;
+	constexpr int cGearNumTeeth = 100;
+
+	constexpr float cRackLength = 5.0f;
+	constexpr float cRackHalfHeight = 0.1f;
+	constexpr float cRackHalfWidth = 0.05f;
+	constexpr int cRackNumTeeth = int(cRackLength * cGearNumTeeth / (2.0f * JPH_PI * cGearRadius));
+
+	constexpr float cToothThicknessBottom = 0.01f;
+	constexpr float cToothThicknessTop = 0.005f;
+	constexpr float cToothHeight = 0.02f;
+
+	// Create a tooth
+	vector<Vec3> tooth_points = {
+		Vec3(0, cGearHalfWidth, cToothThicknessBottom),
+		Vec3(0, -cGearHalfWidth, cToothThicknessBottom),
+		Vec3(0, cGearHalfWidth, -cToothThicknessBottom),
+		Vec3(0, -cGearHalfWidth, -cToothThicknessBottom),
+		Vec3(cToothHeight, -cGearHalfWidth, cToothThicknessTop),
+		Vec3(cToothHeight, cGearHalfWidth, cToothThicknessTop),
+		Vec3(cToothHeight, -cGearHalfWidth, -cToothThicknessTop),
+		Vec3(cToothHeight, cGearHalfWidth, -cToothThicknessTop),
+	};
+	ConvexHullShapeSettings tooth_settings(tooth_points);
+	tooth_settings.SetEmbedded();
+
+	// Create gear
+	CylinderShapeSettings gear_cylinder(cGearHalfWidth, cGearRadius);
+	gear_cylinder.SetEmbedded();
+
+	StaticCompoundShapeSettings gear_settings;
+	gear_settings.SetEmbedded();
+
+	gear_settings.AddShape(Vec3::sZero(), Quat::sIdentity(), &gear_cylinder);
+	for (int i = 0; i < cGearNumTeeth; ++i)
+	{
+		Quat rotation = Quat::sRotation(Vec3::sAxisY(), 2.0f * JPH_PI * i / cGearNumTeeth);
+		gear_settings.AddShape(rotation * Vec3(cGearRadius, 0, 0), rotation, &tooth_settings);
+	}
+
+	Vec3 gear_initial_p(0, 2.0f, 0);
+	Quat gear_initial_r = Quat::sRotation(Vec3::sAxisX(), 0.5f * JPH_PI);
+	Body *gear = mBodyInterface->CreateBody(BodyCreationSettings(&gear_settings, gear_initial_p, gear_initial_r, EMotionType::Dynamic, Layers::MOVING));
+	mBodyInterface->AddBody(gear->GetID(), EActivation::Activate);
+
+	// Create rack
+	BoxShapeSettings rack_box(Vec3(cRackHalfHeight, cRackHalfWidth, 0.5f * cRackLength), 0.0f);
+	rack_box.SetEmbedded();
+
+	StaticCompoundShapeSettings rack_settings;
+	rack_settings.SetEmbedded();
+
+	rack_settings.AddShape(Vec3::sZero(), Quat::sIdentity(), &rack_box);
+	for (int i = 0; i < cRackNumTeeth; ++i)
+		rack_settings.AddShape(Vec3(cRackHalfHeight, 0, -0.5f * cRackLength + (i + 0.5f) * cRackLength / cRackNumTeeth), Quat::sIdentity(), &tooth_settings);
+
+	Vec3 slider_initial_p = gear_initial_p - Vec3(0, cGearRadius + cRackHalfHeight + cToothHeight, 0);
+	Quat slider_initial_r = Quat::sRotation(Vec3::sAxisZ(), 0.5f * JPH_PI) * gear_initial_r;
+	Body *rack = mBodyInterface->CreateBody(BodyCreationSettings(&rack_settings, slider_initial_p, slider_initial_r, EMotionType::Dynamic, Layers::MOVING));
+	mBodyInterface->AddBody(rack->GetID(), EActivation::Activate);
+
+	// Create a hinge for the gear
+	HingeConstraintSettings hinge;
+	hinge.mPoint1 = hinge.mPoint2 = gear_initial_p;
+	hinge.mHingeAxis1 = hinge.mHingeAxis2 = Vec3::sAxisZ();
+	hinge.mNormalAxis1 = hinge.mNormalAxis2 = Vec3::sAxisX();
+	mPhysicsSystem->AddConstraint(hinge.Create(*gear, Body::sFixedToWorld));
+
+	// Create a slider for the rack
+	SliderConstraintSettings slider;
+	slider.mPoint1 = slider.mPoint2 = gear_initial_p;
+	slider.mSliderAxis1 = slider.mSliderAxis2 = Vec3::sAxisX();
+	slider.mNormalAxis1 = slider.mNormalAxis2 = Vec3::sAxisZ();
+	slider.mLimitsMin = -0.5f * cRackLength;
+	slider.mLimitsMax = 0.5f * cRackLength;
+	mPhysicsSystem->AddConstraint(slider.Create(*rack, Body::sFixedToWorld));
+
+	// Disable collision between rack and gear (we want the rack and pinion constraint to take care of the relative movement)
+	Ref<GroupFilterTable> group_filter = new GroupFilterTable(2);
+	group_filter->DisableCollision(0, 1);
+	gear->SetCollisionGroup(CollisionGroup(group_filter, 0, 0));
+	rack->SetCollisionGroup(CollisionGroup(group_filter, 0, 1));
+
+	// Create rack and pinion constraint to constrain the two bodies
+	RackAndPinionConstraintSettings randp;
+	randp.mHingeAxis = hinge.mHingeAxis1;
+	randp.mSliderAxis = slider.mSliderAxis2;
+	randp.SetRatio(cRackNumTeeth, cRackLength, cGearNumTeeth);
+	mPhysicsSystem->AddConstraint(randp.Create(*gear, *rack));
+
+	// Give the gear a spin
+	gear->SetAngularVelocity(Vec3(0, 0, 6.0f));
+}

+ 16 - 0
Samples/Tests/Constraints/RackAndPinionConstraintTest.h

@@ -0,0 +1,16 @@
+// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
+// SPDX-License-Identifier: MIT
+
+#pragma once
+
+#include <Tests/Test.h>
+
+// This test demonstrates the use of a rack and pinion constraint
+class RackAndPinionConstraintTest : public Test
+{
+public:
+	JPH_DECLARE_RTTI_VIRTUAL(RackAndPinionConstraintTest)
+
+	// See: Test
+	virtual void		Initialize() override;
+};

+ 70 - 25
Samples/Tests/Constraints/SliderConstraintTest.cpp

@@ -26,6 +26,7 @@ void SliderConstraintTest::Initialize()
 	Ref<GroupFilterTable> group_filter = new GroupFilterTable(cChainLength);
 	for (CollisionGroup::SubGroupID i = 0; i < cChainLength - 1; ++i)
 		group_filter->DisableCollision(i, i + 1);
+	CollisionGroup::GroupID group_id = 0;
 
 	// Create box
 	float box_size = 4.0f;
@@ -34,8 +35,6 @@ void SliderConstraintTest::Initialize()
 	// Bodies attached through slider constraints
 	for (int randomness = 0; randomness < 2; ++randomness)
 	{
-		CollisionGroup::GroupID group_id = CollisionGroup::GroupID(randomness);
-
 		Vec3 position(0, 25.0f, -randomness * 20.0f);
 		Body &top = *mBodyInterface->CreateBody(BodyCreationSettings(box, position, Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING));
 		top.SetCollisionGroup(CollisionGroup(group_filter, group_id, 0));
@@ -75,29 +74,75 @@ void SliderConstraintTest::Initialize()
 
 			prev = &segment;
 		}
+
+		group_id++;
+	}
+
+	{
+		// Two light bodies attached to a heavy body (gives issues if the wrong anchor point is chosen)
+		Body *light1 = mBodyInterface->CreateBody(BodyCreationSettings(new BoxShape(Vec3::sReplicate(0.1f)), Vec3(-5.0f, 7.0f, -5.2f), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING));
+		light1->SetCollisionGroup(CollisionGroup(group_filter, group_id, 0));
+		mBodyInterface->AddBody(light1->GetID(), EActivation::Activate);
+		Body *heavy = mBodyInterface->CreateBody(BodyCreationSettings(new BoxShape(Vec3::sReplicate(5.0f)), Vec3(-5.0f, 7.0f, 0.0f), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING));
+		heavy->SetCollisionGroup(CollisionGroup(group_filter, group_id, 1));
+		mBodyInterface->AddBody(heavy->GetID(), EActivation::Activate);
+		Body *light2 = mBodyInterface->CreateBody(BodyCreationSettings(new BoxShape(Vec3::sReplicate(0.1f)), Vec3(-5.0f, 7.0f, 5.2f), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING));
+		light2->SetCollisionGroup(CollisionGroup(group_filter, group_id, 2));
+		mBodyInterface->AddBody(light2->GetID(), EActivation::Activate);
+		++group_id;
+
+		// Note: This violates the recommendation that body 1 is heavier than body 2, therefore this constraint will not work well (the rotation constraint will not be solved accurately)
+		SliderConstraintSettings slider1;
+		slider1.SetPoint(*light1, *heavy);
+		slider1.SetSliderAxis(Vec3::sAxisZ());
+		slider1.mLimitsMin = 0.0f;
+		slider1.mLimitsMax = 1.0f;
+		mPhysicsSystem->AddConstraint(slider1.Create(*light1, *heavy));
+
+		// This constraint has the heavy body as body 1 so will work fine
+		SliderConstraintSettings slider2;
+		slider2.SetPoint(*heavy, *light2);
+		slider2.SetSliderAxis(Vec3::sAxisZ());
+		slider2.mLimitsMin = 0.0f;
+		slider2.mLimitsMax = 1.0f;
+		mPhysicsSystem->AddConstraint(slider2.Create(*heavy, *light2));
 	}
 
-	// Two light bodies attached to a heavy body (gives issues if the wrong anchor point is chosen)
-	Body *light1 = mBodyInterface->CreateBody(BodyCreationSettings(new BoxShape(Vec3::sReplicate(0.1f)), Vec3(-5.0f, 7.0f, -5.2f), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING));
-	mBodyInterface->AddBody(light1->GetID(), EActivation::Activate);
-	Body *heavy = mBodyInterface->CreateBody(BodyCreationSettings(new BoxShape(Vec3::sReplicate(5.0f)), Vec3(-5.0f, 7.0f, 0.0f), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING));
-	mBodyInterface->AddBody(heavy->GetID(), EActivation::Activate);
-	Body *light2 = mBodyInterface->CreateBody(BodyCreationSettings(new BoxShape(Vec3::sReplicate(0.1f)), Vec3(-5.0f, 7.0f, 5.2f), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING));
-	mBodyInterface->AddBody(light2->GetID(), EActivation::Activate);
-
-	// Note: This violates the recommendation that body 1 is heavier than body 2, therefore this constraint will not work well (the rotation constraint will not be solved accurately)
-	SliderConstraintSettings slider1;
-	slider1.SetPoint(*light1, *heavy);
-	slider1.SetSliderAxis(Vec3::sAxisZ());
-	slider1.mLimitsMin = 0.0f;
-	slider1.mLimitsMax = 1.0f;
-	mPhysicsSystem->AddConstraint(slider1.Create(*light1, *heavy));
-
-	// This constraint has the heavy body as body 1 so will work fine
-	SliderConstraintSettings slider2;
-	slider2.SetPoint(*heavy, *light2);
-	slider2.SetSliderAxis(Vec3::sAxisZ());
-	slider2.mLimitsMin = 0.0f;
-	slider2.mLimitsMax = 1.0f;
-	mPhysicsSystem->AddConstraint(slider2.Create(*heavy, *light2));
+	{
+		// Two bodies vertically stacked with a slider constraint
+		Body *vert1 = mBodyInterface->CreateBody(BodyCreationSettings(new BoxShape(Vec3::sReplicate(1.0f)), Vec3(5, 9, 0), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING));
+		vert1->SetCollisionGroup(CollisionGroup(group_filter, group_id, 0));
+		mBodyInterface->AddBody(vert1->GetID(), EActivation::Activate);
+		Body *vert2 = mBodyInterface->CreateBody(BodyCreationSettings(new BoxShape(Vec3::sReplicate(1.0f)), Vec3(5, 3, 0), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING));
+		vert2->SetCollisionGroup(CollisionGroup(group_filter, group_id, 1));
+		mBodyInterface->AddBody(vert2->GetID(), EActivation::Activate);
+		++group_id;
+
+		SliderConstraintSettings slider;
+		slider.SetPoint(*vert1, *vert2);
+		slider.SetSliderAxis(Vec3::sAxisY());
+		slider.mLimitsMin = 0.0f;
+		slider.mLimitsMax = 2.0f;
+		mPhysicsSystem->AddConstraint(slider.Create(*vert1, *vert2));
+	}
+
+	{
+		// Two bodies vertically stacked with a slider constraint using soft limits
+		Body *vert1 = mBodyInterface->CreateBody(BodyCreationSettings(new BoxShape(Vec3::sReplicate(1.0f)), Vec3(10, 9, 0), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING));
+		vert1->SetCollisionGroup(CollisionGroup(group_filter, group_id, 0));
+		mBodyInterface->AddBody(vert1->GetID(), EActivation::Activate);
+		Body *vert2 = mBodyInterface->CreateBody(BodyCreationSettings(new BoxShape(Vec3::sReplicate(1.0f)), Vec3(10, 3, 0), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING));
+		vert2->SetCollisionGroup(CollisionGroup(group_filter, group_id, 1));
+		mBodyInterface->AddBody(vert2->GetID(), EActivation::Activate);
+		++group_id;
+
+		SliderConstraintSettings slider;
+		slider.SetPoint(*vert1, *vert2);
+		slider.SetSliderAxis(Vec3::sAxisY());
+		slider.mLimitsMin = 0.0f;
+		slider.mLimitsMax = 2.0f;
+		slider.mFrequency = 1.0f;
+		slider.mDamping = 0.5f;
+		mPhysicsSystem->AddConstraint(slider.Create(*vert1, *vert2));
+	}
 }