Prechádzať zdrojové kódy

Support specifying local frames for fixed constraint (#166)

* Ability to get back the constraint settings for fixed constraint
* Added random rotation to hinge constraint test

Warning: This is a breaking interface change! You need to call FixedConstraint::SetPoint(body1, body2) to automatically link fixed bodies in their current positions / orientations.
Jorrit Rouwe 3 rokov pred
rodič
commit
4f7c925c31

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

@@ -74,6 +74,69 @@ public:
 		return inBody2.GetRotation().Conjugated() * inBody1.GetRotation();
 	}
 
+	/// @brief Return inverse of initial rotation from body 1 to body 2 in body 1 space
+	/// @param inAxisX1 Reference axis X for body 1
+	/// @param inAxisY1 Reference axis Y for body 1
+	/// @param inAxisX2 Reference axis X for body 2
+	/// @param inAxisY2 Reference axis Y for body 2
+	/// @return 
+	static Quat					sGetInvInitialOrientationXY(Vec3Arg inAxisX1, Vec3Arg inAxisY1, Vec3Arg inAxisX2, Vec3Arg inAxisY2)
+	{
+		// Store inverse of initial rotation from body 1 to body 2 in body 1 space:
+		//
+		// q20 = q10 r0 
+		// <=> r0 = q10^-1 q20 
+		// <=> r0^-1 = q20^-1 q10
+		//
+		// where:
+		//
+		// q10, q20 = world space initial orientation of body 1 and 2
+		// r0 = initial rotation rotation from body 1 to body 2 in local space of body 1
+		//
+		// We can also write this in terms of the constraint matrices:
+		// 
+		// q20 c2 = q10 c1
+		// <=> q20 = q10 c1 c2^-1
+		// => r0 = c1 c2^-1
+		// <=> r0^-1 = c2 c1^-1
+		// 
+		// where:
+		// 
+		// c1, c2 = matrix that takes us from body 1 and 2 COM to constraint space 1 and 2
+		if (inAxisX1 == inAxisX2 && inAxisY1 == inAxisY2)
+		{
+			// Axis are the same -> identity transform
+			return Quat::sIdentity();
+		}
+		else
+		{
+			Mat44 constraint1(Vec4(inAxisX1, 0), Vec4(inAxisY1, 0), Vec4(inAxisX1.Cross(inAxisY1), 0), Vec4(0, 0, 0, 1));
+			Mat44 constraint2(Vec4(inAxisX2, 0), Vec4(inAxisY2, 0), Vec4(inAxisX2.Cross(inAxisY2), 0), Vec4(0, 0, 0, 1));
+			return constraint2.GetQuaternion() * constraint1.GetQuaternion().Conjugated();
+		}
+	}
+
+	/// @brief Return inverse of initial rotation from body 1 to body 2 in body 1 space
+	/// @param inAxisX1 Reference axis X for body 1
+	/// @param inAxisZ1 Reference axis Z for body 1
+	/// @param inAxisX2 Reference axis X for body 2
+	/// @param inAxisZ2 Reference axis Z for body 2
+	/// @return 
+	static Quat					sGetInvInitialOrientationXZ(Vec3Arg inAxisX1, Vec3Arg inAxisZ1, Vec3Arg inAxisX2, Vec3Arg inAxisZ2)
+	{
+		// See comment at sGetInvInitialOrientationXY
+		if (inAxisX1 == inAxisX2 && inAxisZ1 == inAxisZ2)
+		{
+			return Quat::sIdentity();
+		}
+		else
+		{
+			Mat44 constraint1(Vec4(inAxisX1, 0), Vec4(inAxisZ1.Cross(inAxisX1), 0), Vec4(inAxisZ1, 0), Vec4(0, 0, 0, 1));
+			Mat44 constraint2(Vec4(inAxisX2, 0), Vec4(inAxisZ2.Cross(inAxisX2), 0), Vec4(inAxisZ2, 0), Vec4(0, 0, 0, 1));
+			return constraint2.GetQuaternion() * constraint1.GetQuaternion().Conjugated();
+		}
+	}
+
 	/// Calculate properties used during the functions below
 	inline void					CalculateConstraintProperties(const Body &inBody1, Mat44Arg inRotation1, const Body &inBody2, Mat44Arg inRotation2)
 	{

+ 75 - 17
Jolt/Physics/Constraints/FixedConstraint.cpp

@@ -15,6 +15,40 @@ JPH_NAMESPACE_BEGIN
 JPH_IMPLEMENT_SERIALIZABLE_VIRTUAL(FixedConstraintSettings)
 {
 	JPH_ADD_BASE_CLASS(FixedConstraintSettings, TwoBodyConstraintSettings)
+
+	JPH_ADD_ENUM_ATTRIBUTE(FixedConstraintSettings, mSpace)
+	JPH_ADD_ATTRIBUTE(FixedConstraintSettings, mPoint1)
+	JPH_ADD_ATTRIBUTE(FixedConstraintSettings, mAxisX1)
+	JPH_ADD_ATTRIBUTE(FixedConstraintSettings, mAxisY1)
+	JPH_ADD_ATTRIBUTE(FixedConstraintSettings, mPoint2)
+	JPH_ADD_ATTRIBUTE(FixedConstraintSettings, mAxisX2)
+	JPH_ADD_ATTRIBUTE(FixedConstraintSettings, mAxisY2)
+}
+
+void FixedConstraintSettings::SaveBinaryState(StreamOut &inStream) const
+{ 
+	ConstraintSettings::SaveBinaryState(inStream);
+
+	inStream.Write(mSpace);
+	inStream.Write(mPoint1);
+	inStream.Write(mAxisX1);
+	inStream.Write(mAxisY1);
+	inStream.Write(mPoint2);
+	inStream.Write(mAxisX2);
+	inStream.Write(mAxisY2);
+}
+
+void FixedConstraintSettings::RestoreBinaryState(StreamIn &inStream)
+{
+	ConstraintSettings::RestoreBinaryState(inStream);
+
+	inStream.Read(mSpace);
+	inStream.Read(mPoint1);
+	inStream.Read(mAxisX1);
+	inStream.Read(mAxisY1);
+	inStream.Read(mPoint2);
+	inStream.Read(mAxisX2);
+	inStream.Read(mAxisY2);
 }
 
 TwoBodyConstraint *FixedConstraintSettings::Create(Body &inBody1, Body &inBody2) const
@@ -22,29 +56,45 @@ TwoBodyConstraint *FixedConstraintSettings::Create(Body &inBody1, Body &inBody2)
 	return new FixedConstraint(inBody1, inBody2, *this);
 }
 
-FixedConstraint::FixedConstraint(Body &inBody1, Body &inBody2, const FixedConstraintSettings &inSettings) :
-	TwoBodyConstraint(inBody1, inBody2, inSettings)
-{	
+void FixedConstraintSettings::SetPoint(const Body &inBody1, const Body &inBody2)
+{
+	JPH_ASSERT(mSpace == EConstraintSpace::WorldSpace);
+
 	// Determine anchor point: If any of the bodies can never be dynamic use the other body as anchor point
 	Vec3 anchor;
-	if (!mBody1->CanBeKinematicOrDynamic())
-		anchor = mBody2->GetCenterOfMassPosition();
-	else if (!mBody2->CanBeKinematicOrDynamic())
-		anchor = mBody1->GetCenterOfMassPosition();
+	if (!inBody1.CanBeKinematicOrDynamic())
+		anchor = inBody2.GetCenterOfMassPosition();
+	else if (!inBody2.CanBeKinematicOrDynamic())
+		anchor = inBody1.GetCenterOfMassPosition();
 	else
 	{
 		// Otherwise use weighted anchor point towards the lightest body
-		float inv_m1 = mBody1->GetMotionPropertiesUnchecked()->GetInverseMassUnchecked();
-		float inv_m2 = mBody2->GetMotionPropertiesUnchecked()->GetInverseMassUnchecked();
-		anchor = (inv_m1 * mBody1->GetCenterOfMassPosition() + inv_m2 * mBody2->GetCenterOfMassPosition()) / (inv_m1 + inv_m2);
+		float inv_m1 = inBody1.GetMotionPropertiesUnchecked()->GetInverseMassUnchecked();
+		float inv_m2 = inBody2.GetMotionPropertiesUnchecked()->GetInverseMassUnchecked();
+		anchor = (inv_m1 * inBody1.GetCenterOfMassPosition() + inv_m2 * inBody2.GetCenterOfMassPosition()) / (inv_m1 + inv_m2);
 	}
 
-	// Store local positions
-	mLocalSpacePosition1 = inBody1.GetInverseCenterOfMassTransform() * anchor;
-	mLocalSpacePosition2 = inBody2.GetInverseCenterOfMassTransform() * anchor;
+	mPoint1 = mPoint2 = anchor;
+}
+
+FixedConstraint::FixedConstraint(Body &inBody1, Body &inBody2, const FixedConstraintSettings &inSettings) :
+	TwoBodyConstraint(inBody1, inBody2, inSettings),
+	mLocalSpacePosition1(inSettings.mPoint1),
+	mLocalSpacePosition2(inSettings.mPoint2)
+{	
+	// Store inverse of initial rotation from body 1 to body 2 in body 1 space
+	mInvInitialOrientation = RotationEulerConstraintPart::sGetInvInitialOrientationXY(inSettings.mAxisX1, inSettings.mAxisY1, inSettings.mAxisX2, inSettings.mAxisY2);
+
+	if (inSettings.mSpace == EConstraintSpace::WorldSpace)
+	{
+		// Store local positions
+		mLocalSpacePosition1 = inBody1.GetInverseCenterOfMassTransform() * mLocalSpacePosition1;
+		mLocalSpacePosition2 = inBody2.GetInverseCenterOfMassTransform() * mLocalSpacePosition2;
 
-	// Inverse of initial rotation from body 1 to body 2 in body 1 space
-	mInvInitialOrientation = RotationEulerConstraintPart::sGetInvInitialOrientation(inBody1, inBody2);
+		// Constraints were specified in world space, so we should have replaced c1 with q10^-1 c1 and c2 with q20^-1 c2
+		// => r0^-1 = (q20^-1 c2) (q10^-1 c1)^1 = q20^-1 (c2 c1^-1) q10
+		mInvInitialOrientation = inBody2.GetRotation().Conjugated() * mInvInitialOrientation * inBody1.GetRotation();
+	}
 }
 
 void FixedConstraint::SetupVelocityConstraint(float inDeltaTime)
@@ -116,8 +166,16 @@ void FixedConstraint::RestoreState(StateRecorder &inStream)
 
 Ref<ConstraintSettings> FixedConstraint::GetConstraintSettings() const
 {
-	JPH_ASSERT(false); // Not implemented yet
-	return nullptr;
+	FixedConstraintSettings *settings = new FixedConstraintSettings;
+	ToConstraintSettings(*settings);
+	settings->mSpace = EConstraintSpace::LocalToBodyCOM;
+	settings->mPoint1 = mLocalSpacePosition1;
+	settings->mAxisX1 = Vec3::sAxisX();
+	settings->mAxisY1 = Vec3::sAxisY();
+	settings->mPoint2 = mLocalSpacePosition2;
+	settings->mAxisX2 = mInvInitialOrientation.RotateAxisX();
+	settings->mAxisY2 = mInvInitialOrientation.RotateAxisY();
+	return settings;
 }
 
 JPH_NAMESPACE_END

+ 23 - 0
Jolt/Physics/Constraints/FixedConstraint.h

@@ -15,8 +15,31 @@ class FixedConstraintSettings final : public TwoBodyConstraintSettings
 public:
 	JPH_DECLARE_SERIALIZABLE_VIRTUAL(FixedConstraintSettings)
 
+	// 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;
+
+	/// Simple way of calculating the parameters below to fixate two bodies in their current relative position/orientation
+	void						SetPoint(const Body &inBody1, const Body &inBody2);
+
+	/// 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						mPoint1 = Vec3::sZero();
+	Vec3						mAxisX1 = Vec3::sAxisX();
+	Vec3						mAxisY1 = Vec3::sAxisY();
+
+	/// Body 2 constraint reference frame (space determined by mSpace)
+	Vec3						mPoint2 = Vec3::sZero();
+	Vec3						mAxisX2 = Vec3::sAxisX();
+	Vec3						mAxisY2 = Vec3::sAxisY();
+
+protected:
+	// See: ConstraintSettings::RestoreBinaryState
+	virtual void				RestoreBinaryState(StreamIn &inStream) override;
 };
 
 /// A fixed constraint welds two bodies together removing all degrees of freedom between them.

+ 3 - 32
Jolt/Physics/Constraints/HingeConstraint.cpp

@@ -4,6 +4,7 @@
 #include <Jolt/Jolt.h>
 
 #include <Jolt/Physics/Constraints/HingeConstraint.h>
+#include <Jolt/Physics/Constraints/ConstraintPart/RotationEulerConstraintPart.h>
 #include <Jolt/Physics/Body/Body.h>
 #include <Jolt/ObjectStream/TypeDeclarations.h>
 #include <Jolt/Core/StreamIn.h>
@@ -84,38 +85,8 @@ HingeConstraint::HingeConstraint(Body &inBody1, Body &inBody2, const HingeConstr
 	JPH_ASSERT(inSettings.mLimitsMin != inSettings.mLimitsMax, "Better use a fixed constraint in this case");
 	SetLimits(inSettings.mLimitsMin, inSettings.mLimitsMax);
 
-	// Store inverse of initial rotation from body 1 to body 2 in body 1 space:
-	//
-	// q20 = q10 r0 
-	// <=> r0 = q10^-1 q20 
-	// <=> r0^-1 = q20^-1 q10
-	//
-	// where:
-	//
-	// q10, q20 = world space initial orientation of body 1 and 2
-	// r0 = initial rotation rotation from body 1 to body 2 in local space of body 1
-	//
-	// We can also write this in terms of the constraint matrices:
-	// 
-	// q20 c2 = q10 c1
-	// <=> q20 = q10 c1 c2^-1
-	// => r0 = c1 c2^-1
-	// <=> r0^-1 = c2 c1^-1
-	// 
-	// where:
-	// 
-	// c1, c2 = matrix that takes us from body 1 and 2 COM to constraint space 1 and 2
-	if (inSettings.mHingeAxis1 == inSettings.mHingeAxis2 && inSettings.mNormalAxis1 == inSettings.mNormalAxis2)
-	{
-		// Axis are the same -> identity transform
-		mInvInitialOrientation = Quat::sIdentity();
-	}
-	else
-	{
-		Mat44 constraint1(Vec4(inSettings.mNormalAxis1, 0), Vec4(inSettings.mHingeAxis1.Cross(inSettings.mNormalAxis1), 0), Vec4(inSettings.mHingeAxis1, 0), Vec4(0, 0, 0, 1));
-		Mat44 constraint2(Vec4(inSettings.mNormalAxis2, 0), Vec4(inSettings.mHingeAxis2.Cross(inSettings.mNormalAxis2), 0), Vec4(inSettings.mHingeAxis2, 0), Vec4(0, 0, 0, 1));
-		mInvInitialOrientation = constraint2.GetQuaternion() * constraint1.GetQuaternion().Conjugated();
-	}
+	// Store inverse of initial rotation from body 1 to body 2 in body 1 space
+	mInvInitialOrientation = RotationEulerConstraintPart::sGetInvInitialOrientationXZ(inSettings.mNormalAxis1, inSettings.mHingeAxis1, inSettings.mNormalAxis2, inSettings.mHingeAxis2);
 
 	if (inSettings.mSpace == EConstraintSpace::WorldSpace)
 	{

+ 2 - 32
Jolt/Physics/Constraints/SliderConstraint.cpp

@@ -108,38 +108,8 @@ SliderConstraint::SliderConstraint(Body &inBody1, Body &inBody2, const SliderCon
 	mMaxFrictionForce(inSettings.mMaxFrictionForce),
 	mMotorSettings(inSettings.mMotorSettings)
 {
-	// Store inverse of initial rotation from body 1 to body 2 in body 1 space:
-	//
-	// q20 = q10 r0 
-	// <=> r0 = q10^-1 q20 
-	// <=> r0^-1 = q20^-1 q10
-	//
-	// where:
-	//
-	// q10, q20 = world space initial orientation of body 1 and 2
-	// r0 = initial rotation rotation from body 1 to body 2 in local space of body 1
-	//
-	// We can also write this in terms of the constraint matrices:
-	// 
-	// q20 c2 = q10 c1
-	// <=> q20 = q10 c1 c2^-1
-	// => r0 = c1 c2^-1
-	// <=> r0^-1 = c2 c1^-1
-	// 
-	// where:
-	// 
-	// c1, c2 = matrix that takes us from body 1 and 2 COM to constraint space 1 and 2
-	if (inSettings.mSliderAxis1 == inSettings.mSliderAxis2 && inSettings.mNormalAxis1 == inSettings.mNormalAxis2)
-	{
-		// Axis are the same -> identity transform
-		mInvInitialOrientation = Quat::sIdentity();
-	}
-	else
-	{
-		Mat44 constraint1(Vec4(inSettings.mSliderAxis1, 0), Vec4(inSettings.mNormalAxis1, 0), Vec4(inSettings.mSliderAxis1.Cross(inSettings.mNormalAxis1), 0), Vec4(0, 0, 0, 1));
-		Mat44 constraint2(Vec4(inSettings.mSliderAxis2, 0), Vec4(inSettings.mNormalAxis2, 0), Vec4(inSettings.mSliderAxis2.Cross(inSettings.mNormalAxis2), 0), Vec4(0, 0, 0, 1));
-		mInvInitialOrientation = constraint2.GetQuaternion() * constraint1.GetQuaternion().Conjugated();
-	}
+	// Store inverse of initial rotation from body 1 to body 2 in body 1 space
+	mInvInitialOrientation = RotationEulerConstraintPart::sGetInvInitialOrientationXY(inSettings.mSliderAxis1, inSettings.mNormalAxis1, inSettings.mSliderAxis2, inSettings.mNormalAxis2);
 
 	if (inSettings.mSpace == EConstraintSpace::WorldSpace)
 	{

+ 6 - 2
Samples/Tests/Constraints/ConstraintSingularityTest.cpp

@@ -60,8 +60,12 @@ void ConstraintSingularityTest::Initialize()
 				}
 
 			default:
-				constraint = FixedConstraintSettings().Create(body1, body2);
-				break;
+				{
+					FixedConstraintSettings settings;
+					settings.SetPoint(body1, body2);
+					constraint = settings.Create(body1, body2);
+					break;
+				}
 			}
 				
 			mPhysicsSystem->AddConstraint(constraint);

+ 10 - 3
Samples/Tests/Constraints/FixedConstraintTest.cpp

@@ -62,7 +62,9 @@ void FixedConstraintTest::Initialize()
 			segment.SetCollisionGroup(CollisionGroup(group_filter, group_id, CollisionGroup::SubGroupID(i)));
 			mBodyInterface->AddBody(segment.GetID(), EActivation::Activate);
 
-			Ref<Constraint> c = FixedConstraintSettings().Create(*prev, segment);
+			FixedConstraintSettings settings;
+			settings.SetPoint(*prev, segment);
+			Ref<Constraint> c = settings.Create(*prev, segment);
 			mPhysicsSystem->AddConstraint(c);
 					
 			prev = &segment;
@@ -77,6 +79,11 @@ void FixedConstraintTest::Initialize()
 	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);
 
-	mPhysicsSystem->AddConstraint(FixedConstraintSettings().Create(*light1, *heavy));
-	mPhysicsSystem->AddConstraint(FixedConstraintSettings().Create(*heavy, *light2));
+	FixedConstraintSettings light1_heavy;
+	light1_heavy.SetPoint(*light1, *heavy);
+	mPhysicsSystem->AddConstraint(light1_heavy.Create(*light1, *heavy));
+
+	FixedConstraintSettings heavy_light2;
+	heavy_light2.SetPoint(*heavy, *light2);
+	mPhysicsSystem->AddConstraint(heavy_light2.Create(*heavy, *light2));
 }

+ 48 - 31
Samples/Tests/Constraints/HingeConstraintTest.cpp

@@ -23,7 +23,9 @@ void HingeConstraintTest::Initialize()
 	float box_size = 4.0f;
 	RefConst<Shape> box = new BoxShape(Vec3::sReplicate(0.5f * box_size));
 
-	const int cChainLength = 15;
+	constexpr int cChainLength = 15;
+	constexpr float cMinAngle = DegreesToRadians(-10.0f);
+	constexpr float cMaxAngle = DegreesToRadians(20.0f);
 
 	// Build a collision group filter that disables collision between adjacent bodies
 	Ref<GroupFilterTable> group_filter = new GroupFilterTable(cChainLength);
@@ -31,40 +33,55 @@ void HingeConstraintTest::Initialize()
 		group_filter->DisableCollision(i, i + 1);
 
 	// Bodies attached through hinge constraints
-	Vec3 position(0, 50, 0);
-	Body &top = *mBodyInterface->CreateBody(BodyCreationSettings(box, position, Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING));
-	top.SetCollisionGroup(CollisionGroup(group_filter, 0, 0));
-	mBodyInterface->AddBody(top.GetID(), EActivation::DontActivate);
-
-	constexpr float min_angle = DegreesToRadians(-10.0f);
-	constexpr float max_angle = DegreesToRadians(20.0f);
-
-	Body *prev = &top;
-	for (int i = 1; i < cChainLength; ++i)
+	for (int randomness = 0; randomness < 2; ++randomness)
 	{
-		position += Vec3(box_size, 0, 0);
+		CollisionGroup::GroupID group_id = CollisionGroup::GroupID(randomness);
 
-		Body &segment = *mBodyInterface->CreateBody(BodyCreationSettings(box, position, Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING));
-		segment.SetCollisionGroup(CollisionGroup(group_filter, 0, CollisionGroup::SubGroupID(i)));
-		mBodyInterface->AddBody(segment.GetID(), EActivation::Activate);
+		Vec3 position(0, 50, -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));
+		mBodyInterface->AddBody(top.GetID(), EActivation::DontActivate);
 
-		HingeConstraintSettings settings;
-		if ((i & 1) == 0)
+		default_random_engine random;
+		uniform_real_distribution<float> displacement(-1.0f, 1.0f);
+
+		Body *prev = &top;
+		for (int i = 1; i < cChainLength; ++i)
 		{
-			settings.mPoint1 = settings.mPoint2 = position + Vec3(-0.5f * box_size, 0, 0.5f * box_size);
-			settings.mHingeAxis1 = settings.mHingeAxis2 = Vec3::sAxisY();
-			settings.mNormalAxis1 = settings.mNormalAxis2 = Vec3::sAxisX();
-		}
-		else
-		{ 
-			settings.mPoint1 = settings.mPoint2 = position + Vec3(-0.5f * box_size, -0.5f * box_size, 0);
-			settings.mHingeAxis1 = settings.mHingeAxis2 = Vec3::sAxisZ();
-			settings.mNormalAxis1 = settings.mNormalAxis2 = Vec3::sAxisX();
-		}
-		settings.mLimitsMin = min_angle;
-		settings.mLimitsMax = max_angle;
-		mPhysicsSystem->AddConstraint(settings.Create(*prev, segment));
+			Quat rotation;
+			if (randomness == 0)
+			{
+				position += Vec3(box_size, 0, 0);
+				rotation = Quat::sIdentity();
+			}
+			else
+			{
+				position += Vec3(box_size + abs(displacement(random)), displacement(random), displacement(random));
+				rotation = Quat::sRandom(random);
+			}
+
+			Body &segment = *mBodyInterface->CreateBody(BodyCreationSettings(box, position, rotation, EMotionType::Dynamic, Layers::MOVING));
+			segment.SetCollisionGroup(CollisionGroup(group_filter, group_id, CollisionGroup::SubGroupID(i)));
+			mBodyInterface->AddBody(segment.GetID(), EActivation::Activate);
 
-		prev = &segment;
+			HingeConstraintSettings settings;
+			if ((i & 1) == 0)
+			{
+				settings.mPoint1 = settings.mPoint2 = position + Vec3(-0.5f * box_size, 0, 0.5f * box_size);
+				settings.mHingeAxis1 = settings.mHingeAxis2 = Vec3::sAxisY();
+				settings.mNormalAxis1 = settings.mNormalAxis2 = Vec3::sAxisX();
+			}
+			else
+			{ 
+				settings.mPoint1 = settings.mPoint2 = position + Vec3(-0.5f * box_size, -0.5f * box_size, 0);
+				settings.mHingeAxis1 = settings.mHingeAxis2 = Vec3::sAxisZ();
+				settings.mNormalAxis1 = settings.mNormalAxis2 = Vec3::sAxisX();
+			}
+			settings.mLimitsMin = cMinAngle;
+			settings.mLimitsMax = cMaxAngle;
+			mPhysicsSystem->AddConstraint(settings.Create(*prev, segment));
+
+			prev = &segment;
+		}
 	}
 }

+ 23 - 33
Samples/Tests/Constraints/SliderConstraintTest.cpp

@@ -32,53 +32,43 @@ void SliderConstraintTest::Initialize()
 	RefConst<Shape> box = new BoxShape(Vec3::sReplicate(0.5f * box_size));
 
 	// Bodies attached through slider constraints
+	for (int randomness = 0; randomness < 2; ++randomness)
 	{
-		Vec3 position(0, 25, 0);
-		Body &top = *mBodyInterface->CreateBody(BodyCreationSettings(box, position, Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING));
-		top.SetCollisionGroup(CollisionGroup(group_filter, 0, 0));
-		mBodyInterface->AddBody(top.GetID(), EActivation::DontActivate);
-
-		Body *prev = &top;
-		for (int i = 1; i < cChainLength; ++i)
-		{
-			position += Vec3(box_size, 0, 0);
+		CollisionGroup::GroupID group_id = CollisionGroup::GroupID(randomness);
 
-			Body &segment = *mBodyInterface->CreateBody(BodyCreationSettings(box, position, Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING));
-			segment.SetCollisionGroup(CollisionGroup(group_filter, 0, CollisionGroup::SubGroupID(i)));
-			mBodyInterface->AddBody(segment.GetID(), EActivation::Activate);
-
-			SliderConstraintSettings settings;
-			settings.SetPoint(*prev, segment);
-			settings.SetSliderAxis(Quat::sRotation(Vec3::sAxisZ(), -DegreesToRadians(10)).RotateAxisX());
-			settings.mLimitsMin = -5.0f;
-			settings.mLimitsMax = 10.0f;
-			mPhysicsSystem->AddConstraint(settings.Create(*prev, segment));
-
-			prev = &segment;
-		}
-	}
-
-	// Bodies attached through slider constraints with random rotations and translations
-	{
-		Vec3 position(0, 25, -20);
+		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, 1, 0));
+		top.SetCollisionGroup(CollisionGroup(group_filter, group_id, 0));
 		mBodyInterface->AddBody(top.GetID(), EActivation::DontActivate);
 
 		default_random_engine random;
 		uniform_real_distribution<float> displacement(-1.0f, 1.0f);
+
 		Body *prev = &top;
 		for (int i = 1; i < cChainLength; ++i)
 		{
-			position += Vec3(box_size + abs(displacement(random)), displacement(random), displacement(random));
-
-			Body &segment = *mBodyInterface->CreateBody(BodyCreationSettings(box, position, Quat::sRandom(random), EMotionType::Dynamic, Layers::MOVING));
-			segment.SetCollisionGroup(CollisionGroup(group_filter, 1, CollisionGroup::SubGroupID(i)));
+			Quat rotation;
+			Vec3 slider_axis;
+			if (randomness == 0)
+			{
+				position += Vec3(box_size, 0, 0);
+				rotation = Quat::sIdentity();
+				slider_axis = Quat::sRotation(Vec3::sAxisZ(), -DegreesToRadians(10)).RotateAxisX();
+			}
+			else
+			{
+				position += Vec3(box_size + abs(displacement(random)), displacement(random), displacement(random));
+				rotation = Quat::sRandom(random);
+				slider_axis = Quat::sRotation(Vec3::sAxisY(), displacement(random) * DegreesToRadians(20)) * Quat::sRotation(Vec3::sAxisZ(), -DegreesToRadians(10)).RotateAxisX();
+			}
+
+			Body &segment = *mBodyInterface->CreateBody(BodyCreationSettings(box, position, rotation, EMotionType::Dynamic, Layers::MOVING));
+			segment.SetCollisionGroup(CollisionGroup(group_filter, group_id, CollisionGroup::SubGroupID(i)));
 			mBodyInterface->AddBody(segment.GetID(), EActivation::Activate);
 
 			SliderConstraintSettings settings;
 			settings.SetPoint(*prev, segment);
-			settings.SetSliderAxis(Quat::sRotation(Vec3::sAxisY(), displacement(random) * DegreesToRadians(20)) * Quat::sRotation(Vec3::sAxisZ(), -DegreesToRadians(10)).RotateAxisX());
+			settings.SetSliderAxis(slider_axis);
 			settings.mLimitsMin = -5.0f;
 			settings.mLimitsMax = 10.0f;
 			mPhysicsSystem->AddConstraint(settings.Create(*prev, segment));

+ 6 - 2
Samples/Utils/RagdollLoader.cpp

@@ -36,8 +36,12 @@ RagdollSettings *RagdollLoader::sLoad(const char *inFileName, EMotionType inMoti
 			switch (inConstraintOverride)
 			{
 			case EConstraintOverride::TypeFixed:
-				p.mToParent = new FixedConstraintSettings();
-				break;
+				{
+					FixedConstraintSettings *settings = new FixedConstraintSettings();
+					settings->mPoint1 = settings->mPoint2 = original->mPosition1;
+					p.mToParent = settings;
+					break;
+				}
 
 			case EConstraintOverride::TypePoint:
 				{