Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/Constraints/HingeConstraint.cpp
21534 views
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)1// SPDX-FileCopyrightText: 2021 Jorrit Rouwe2// SPDX-License-Identifier: MIT34#include <Jolt/Jolt.h>56#include <Jolt/Physics/Constraints/HingeConstraint.h>7#include <Jolt/Physics/Constraints/ConstraintPart/RotationEulerConstraintPart.h>8#include <Jolt/Physics/Body/Body.h>9#include <Jolt/ObjectStream/TypeDeclarations.h>10#include <Jolt/Core/StreamIn.h>11#include <Jolt/Core/StreamOut.h>12#ifdef JPH_DEBUG_RENDERER13#include <Jolt/Renderer/DebugRenderer.h>14#endif // JPH_DEBUG_RENDERER1516JPH_NAMESPACE_BEGIN1718JPH_IMPLEMENT_SERIALIZABLE_VIRTUAL(HingeConstraintSettings)19{20JPH_ADD_BASE_CLASS(HingeConstraintSettings, TwoBodyConstraintSettings)2122JPH_ADD_ENUM_ATTRIBUTE(HingeConstraintSettings, mSpace)23JPH_ADD_ATTRIBUTE(HingeConstraintSettings, mPoint1)24JPH_ADD_ATTRIBUTE(HingeConstraintSettings, mHingeAxis1)25JPH_ADD_ATTRIBUTE(HingeConstraintSettings, mNormalAxis1)26JPH_ADD_ATTRIBUTE(HingeConstraintSettings, mPoint2)27JPH_ADD_ATTRIBUTE(HingeConstraintSettings, mHingeAxis2)28JPH_ADD_ATTRIBUTE(HingeConstraintSettings, mNormalAxis2)29JPH_ADD_ATTRIBUTE(HingeConstraintSettings, mLimitsMin)30JPH_ADD_ATTRIBUTE(HingeConstraintSettings, mLimitsMax)31JPH_ADD_ATTRIBUTE(HingeConstraintSettings, mLimitsSpringSettings)32JPH_ADD_ATTRIBUTE(HingeConstraintSettings, mMaxFrictionTorque)33JPH_ADD_ATTRIBUTE(HingeConstraintSettings, mMotorSettings)34}3536void HingeConstraintSettings::SaveBinaryState(StreamOut &inStream) const37{38ConstraintSettings::SaveBinaryState(inStream);3940inStream.Write(mSpace);41inStream.Write(mPoint1);42inStream.Write(mHingeAxis1);43inStream.Write(mNormalAxis1);44inStream.Write(mPoint2);45inStream.Write(mHingeAxis2);46inStream.Write(mNormalAxis2);47inStream.Write(mLimitsMin);48inStream.Write(mLimitsMax);49inStream.Write(mMaxFrictionTorque);50mLimitsSpringSettings.SaveBinaryState(inStream);51mMotorSettings.SaveBinaryState(inStream);52}5354void HingeConstraintSettings::RestoreBinaryState(StreamIn &inStream)55{56ConstraintSettings::RestoreBinaryState(inStream);5758inStream.Read(mSpace);59inStream.Read(mPoint1);60inStream.Read(mHingeAxis1);61inStream.Read(mNormalAxis1);62inStream.Read(mPoint2);63inStream.Read(mHingeAxis2);64inStream.Read(mNormalAxis2);65inStream.Read(mLimitsMin);66inStream.Read(mLimitsMax);67inStream.Read(mMaxFrictionTorque);68mLimitsSpringSettings.RestoreBinaryState(inStream);69mMotorSettings.RestoreBinaryState(inStream);}7071TwoBodyConstraint *HingeConstraintSettings::Create(Body &inBody1, Body &inBody2) const72{73return new HingeConstraint(inBody1, inBody2, *this);74}7576HingeConstraint::HingeConstraint(Body &inBody1, Body &inBody2, const HingeConstraintSettings &inSettings) :77TwoBodyConstraint(inBody1, inBody2, inSettings),78mMaxFrictionTorque(inSettings.mMaxFrictionTorque),79mMotorSettings(inSettings.mMotorSettings)80{81// Store limits82JPH_ASSERT(inSettings.mLimitsMin != inSettings.mLimitsMax || inSettings.mLimitsSpringSettings.mFrequency > 0.0f, "Better use a fixed constraint in this case");83SetLimits(inSettings.mLimitsMin, inSettings.mLimitsMax);8485// Store inverse of initial rotation from body 1 to body 2 in body 1 space86mInvInitialOrientation = RotationEulerConstraintPart::sGetInvInitialOrientationXZ(inSettings.mNormalAxis1, inSettings.mHingeAxis1, inSettings.mNormalAxis2, inSettings.mHingeAxis2);8788if (inSettings.mSpace == EConstraintSpace::WorldSpace)89{90// If all properties were specified in world space, take them to local space now91RMat44 inv_transform1 = inBody1.GetInverseCenterOfMassTransform();92mLocalSpacePosition1 = Vec3(inv_transform1 * inSettings.mPoint1);93mLocalSpaceHingeAxis1 = inv_transform1.Multiply3x3(inSettings.mHingeAxis1).Normalized();94mLocalSpaceNormalAxis1 = inv_transform1.Multiply3x3(inSettings.mNormalAxis1).Normalized();9596RMat44 inv_transform2 = inBody2.GetInverseCenterOfMassTransform();97mLocalSpacePosition2 = Vec3(inv_transform2 * inSettings.mPoint2);98mLocalSpaceHingeAxis2 = inv_transform2.Multiply3x3(inSettings.mHingeAxis2).Normalized();99mLocalSpaceNormalAxis2 = inv_transform2.Multiply3x3(inSettings.mNormalAxis2).Normalized();100101// Constraints were specified in world space, so we should have replaced c1 with q10^-1 c1 and c2 with q20^-1 c2102// => r0^-1 = (q20^-1 c2) (q10^-1 c1)^1 = q20^-1 (c2 c1^-1) q10103mInvInitialOrientation = inBody2.GetRotation().Conjugated() * mInvInitialOrientation * inBody1.GetRotation();104}105else106{107mLocalSpacePosition1 = Vec3(inSettings.mPoint1);108mLocalSpaceHingeAxis1 = inSettings.mHingeAxis1;109mLocalSpaceNormalAxis1 = inSettings.mNormalAxis1;110111mLocalSpacePosition2 = Vec3(inSettings.mPoint2);112mLocalSpaceHingeAxis2 = inSettings.mHingeAxis2;113mLocalSpaceNormalAxis2 = inSettings.mNormalAxis2;114}115116// Store spring settings117SetLimitsSpringSettings(inSettings.mLimitsSpringSettings);118}119120void HingeConstraint::NotifyShapeChanged(const BodyID &inBodyID, Vec3Arg inDeltaCOM)121{122if (mBody1->GetID() == inBodyID)123mLocalSpacePosition1 -= inDeltaCOM;124else if (mBody2->GetID() == inBodyID)125mLocalSpacePosition2 -= inDeltaCOM;126}127128float HingeConstraint::GetCurrentAngle() const129{130// See: CalculateA1AndTheta131Quat rotation1 = mBody1->GetRotation();132Quat diff = mBody2->GetRotation() * mInvInitialOrientation * rotation1.Conjugated();133return diff.GetRotationAngle(rotation1 * mLocalSpaceHingeAxis1);134}135136void HingeConstraint::SetTargetOrientationBS(QuatArg inOrientation)137{138// See: CalculateA1AndTheta139//140// The rotation between body 1 and 2 can be written as:141//142// q2 = q1 rh1 r0143//144// where rh1 is a rotation around local hinge axis 1, also:145//146// q2 = q1 inOrientation147//148// This means:149//150// rh1 r0 = inOrientation <=> rh1 = inOrientation * r0^-1151Quat rh1 = inOrientation * mInvInitialOrientation;152SetTargetAngle(rh1.GetRotationAngle(mLocalSpaceHingeAxis1));153}154155void HingeConstraint::SetLimits(float inLimitsMin, float inLimitsMax)156{157JPH_ASSERT(inLimitsMin <= 0.0f && inLimitsMin >= -JPH_PI);158JPH_ASSERT(inLimitsMax >= 0.0f && inLimitsMax <= JPH_PI);159mLimitsMin = inLimitsMin;160mLimitsMax = inLimitsMax;161mHasLimits = mLimitsMin > -JPH_PI || mLimitsMax < JPH_PI;162}163164void HingeConstraint::CalculateA1AndTheta()165{166if (mHasLimits || mMotorState != EMotorState::Off || mMaxFrictionTorque > 0.0f)167{168Quat rotation1 = mBody1->GetRotation();169170// Calculate relative rotation in world space171//172// The rest rotation is:173//174// q2 = q1 r0175//176// But the actual rotation is177//178// q2 = diff q1 r0179// <=> diff = q2 r0^-1 q1^-1180//181// Where:182// q1 = current rotation of body 1183// q2 = current rotation of body 2184// diff = relative rotation in world space185Quat diff = mBody2->GetRotation() * mInvInitialOrientation * rotation1.Conjugated();186187// Calculate hinge axis in world space188mA1 = rotation1 * mLocalSpaceHingeAxis1;189190// Get rotation angle around the hinge axis191mTheta = diff.GetRotationAngle(mA1);192}193}194195void HingeConstraint::CalculateRotationLimitsConstraintProperties(float inDeltaTime)196{197// Apply constraint if outside of limits198if (mHasLimits && (mTheta <= mLimitsMin || mTheta >= mLimitsMax))199mRotationLimitsConstraintPart.CalculateConstraintPropertiesWithSettings(inDeltaTime, *mBody1, *mBody2, mA1, 0.0f, GetSmallestAngleToLimit(), mLimitsSpringSettings);200else201mRotationLimitsConstraintPart.Deactivate();202}203204void HingeConstraint::CalculateMotorConstraintProperties(float inDeltaTime)205{206switch (mMotorState)207{208case EMotorState::Off:209if (mMaxFrictionTorque > 0.0f)210mMotorConstraintPart.CalculateConstraintProperties(*mBody1, *mBody2, mA1);211else212mMotorConstraintPart.Deactivate();213break;214215case EMotorState::Velocity:216mMotorConstraintPart.CalculateConstraintProperties(*mBody1, *mBody2, mA1, -mTargetAngularVelocity);217break;218219case EMotorState::Position:220if (mMotorSettings.mSpringSettings.HasStiffness())221mMotorConstraintPart.CalculateConstraintPropertiesWithSettings(inDeltaTime, *mBody1, *mBody2, mA1, 0.0f, CenterAngleAroundZero(mTheta - mTargetAngle), mMotorSettings.mSpringSettings);222else223mMotorConstraintPart.Deactivate();224break;225}226}227228void HingeConstraint::SetupVelocityConstraint(float inDeltaTime)229{230// Cache constraint values that are valid until the bodies move231Mat44 rotation1 = Mat44::sRotation(mBody1->GetRotation());232Mat44 rotation2 = Mat44::sRotation(mBody2->GetRotation());233mPointConstraintPart.CalculateConstraintProperties(*mBody1, rotation1, mLocalSpacePosition1, *mBody2, rotation2, mLocalSpacePosition2);234mRotationConstraintPart.CalculateConstraintProperties(*mBody1, rotation1, rotation1.Multiply3x3(mLocalSpaceHingeAxis1), *mBody2, rotation2, rotation2.Multiply3x3(mLocalSpaceHingeAxis2));235CalculateA1AndTheta();236CalculateRotationLimitsConstraintProperties(inDeltaTime);237CalculateMotorConstraintProperties(inDeltaTime);238}239240void HingeConstraint::ResetWarmStart()241{242mMotorConstraintPart.Deactivate();243mPointConstraintPart.Deactivate();244mRotationConstraintPart.Deactivate();245mRotationLimitsConstraintPart.Deactivate();246}247248void HingeConstraint::WarmStartVelocityConstraint(float inWarmStartImpulseRatio)249{250// Warm starting: Apply previous frame impulse251mMotorConstraintPart.WarmStart(*mBody1, *mBody2, inWarmStartImpulseRatio);252mPointConstraintPart.WarmStart(*mBody1, *mBody2, inWarmStartImpulseRatio);253mRotationConstraintPart.WarmStart(*mBody1, *mBody2, inWarmStartImpulseRatio);254mRotationLimitsConstraintPart.WarmStart(*mBody1, *mBody2, inWarmStartImpulseRatio);255}256257float HingeConstraint::GetSmallestAngleToLimit() const258{259float dist_to_min = CenterAngleAroundZero(mTheta - mLimitsMin);260float dist_to_max = CenterAngleAroundZero(mTheta - mLimitsMax);261return abs(dist_to_min) < abs(dist_to_max)? dist_to_min : dist_to_max;262}263264bool HingeConstraint::IsMinLimitClosest() const265{266float dist_to_min = CenterAngleAroundZero(mTheta - mLimitsMin);267float dist_to_max = CenterAngleAroundZero(mTheta - mLimitsMax);268return abs(dist_to_min) < abs(dist_to_max);269}270271bool HingeConstraint::SolveVelocityConstraint(float inDeltaTime)272{273// Solve motor274bool motor = false;275if (mMotorConstraintPart.IsActive())276{277switch (mMotorState)278{279case EMotorState::Off:280{281float max_lambda = mMaxFrictionTorque * inDeltaTime;282motor = mMotorConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2, mA1, -max_lambda, max_lambda);283break;284}285286case EMotorState::Velocity:287case EMotorState::Position:288motor = mMotorConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2, mA1, inDeltaTime * mMotorSettings.mMinTorqueLimit, inDeltaTime * mMotorSettings.mMaxTorqueLimit);289break;290}291}292293// Solve point constraint294bool pos = mPointConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2);295296// Solve rotation constraint297bool rot = mRotationConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2);298299// Solve rotation limits300bool limit = false;301if (mRotationLimitsConstraintPart.IsActive())302{303float min_lambda, max_lambda;304if (mLimitsMin == mLimitsMax)305{306min_lambda = -FLT_MAX;307max_lambda = FLT_MAX;308}309else if (IsMinLimitClosest())310{311min_lambda = 0.0f;312max_lambda = FLT_MAX;313}314else315{316min_lambda = -FLT_MAX;317max_lambda = 0.0f;318}319limit = mRotationLimitsConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2, mA1, min_lambda, max_lambda);320}321322return motor || pos || rot || limit;323}324325bool HingeConstraint::SolvePositionConstraint(float inDeltaTime, float inBaumgarte)326{327// Motor operates on velocities only, don't call SolvePositionConstraint328329// Solve point constraint330mPointConstraintPart.CalculateConstraintProperties(*mBody1, Mat44::sRotation(mBody1->GetRotation()), mLocalSpacePosition1, *mBody2, Mat44::sRotation(mBody2->GetRotation()), mLocalSpacePosition2);331bool pos = mPointConstraintPart.SolvePositionConstraint(*mBody1, *mBody2, inBaumgarte);332333// Solve rotation constraint334Mat44 rotation1 = Mat44::sRotation(mBody1->GetRotation()); // Note that previous call to GetRotation() is out of date since the rotation has changed335Mat44 rotation2 = Mat44::sRotation(mBody2->GetRotation());336mRotationConstraintPart.CalculateConstraintProperties(*mBody1, rotation1, rotation1.Multiply3x3(mLocalSpaceHingeAxis1), *mBody2, rotation2, rotation2.Multiply3x3(mLocalSpaceHingeAxis2));337bool rot = mRotationConstraintPart.SolvePositionConstraint(*mBody1, *mBody2, inBaumgarte);338339// Solve rotation limits340bool limit = false;341if (mHasLimits && mLimitsSpringSettings.mFrequency <= 0.0f)342{343CalculateA1AndTheta();344CalculateRotationLimitsConstraintProperties(inDeltaTime);345if (mRotationLimitsConstraintPart.IsActive())346limit = mRotationLimitsConstraintPart.SolvePositionConstraint(*mBody1, *mBody2, GetSmallestAngleToLimit(), inBaumgarte);347}348349return pos || rot || limit;350}351352#ifdef JPH_DEBUG_RENDERER353void HingeConstraint::DrawConstraint(DebugRenderer *inRenderer) const354{355RMat44 transform1 = mBody1->GetCenterOfMassTransform();356RMat44 transform2 = mBody2->GetCenterOfMassTransform();357358// Draw constraint359RVec3 constraint_pos1 = transform1 * mLocalSpacePosition1;360inRenderer->DrawMarker(constraint_pos1, Color::sRed, 0.1f);361inRenderer->DrawLine(constraint_pos1, transform1 * (mLocalSpacePosition1 + mDrawConstraintSize * mLocalSpaceHingeAxis1), Color::sRed);362363RVec3 constraint_pos2 = transform2 * mLocalSpacePosition2;364inRenderer->DrawMarker(constraint_pos2, Color::sGreen, 0.1f);365inRenderer->DrawLine(constraint_pos2, transform2 * (mLocalSpacePosition2 + mDrawConstraintSize * mLocalSpaceHingeAxis2), Color::sGreen);366inRenderer->DrawLine(constraint_pos2, transform2 * (mLocalSpacePosition2 + mDrawConstraintSize * mLocalSpaceNormalAxis2), Color::sWhite);367}368369void HingeConstraint::DrawConstraintLimits(DebugRenderer *inRenderer) const370{371if (mHasLimits && mLimitsMax > mLimitsMin)372{373// Get constraint properties in world space374RMat44 transform1 = mBody1->GetCenterOfMassTransform();375RVec3 position1 = transform1 * mLocalSpacePosition1;376Vec3 hinge_axis1 = transform1.Multiply3x3(mLocalSpaceHingeAxis1);377Vec3 normal_axis1 = transform1.Multiply3x3(mLocalSpaceNormalAxis1);378379inRenderer->DrawPie(position1, mDrawConstraintSize, hinge_axis1, normal_axis1, mLimitsMin, mLimitsMax, Color::sPurple, DebugRenderer::ECastShadow::Off);380}381}382#endif // JPH_DEBUG_RENDERER383384void HingeConstraint::SaveState(StateRecorder &inStream) const385{386TwoBodyConstraint::SaveState(inStream);387388mMotorConstraintPart.SaveState(inStream);389mRotationConstraintPart.SaveState(inStream);390mPointConstraintPart.SaveState(inStream);391mRotationLimitsConstraintPart.SaveState(inStream);392393inStream.Write(mMotorState);394inStream.Write(mTargetAngularVelocity);395inStream.Write(mTargetAngle);396}397398void HingeConstraint::RestoreState(StateRecorder &inStream)399{400TwoBodyConstraint::RestoreState(inStream);401402mMotorConstraintPart.RestoreState(inStream);403mRotationConstraintPart.RestoreState(inStream);404mPointConstraintPart.RestoreState(inStream);405mRotationLimitsConstraintPart.RestoreState(inStream);406407inStream.Read(mMotorState);408inStream.Read(mTargetAngularVelocity);409inStream.Read(mTargetAngle);410}411412413Ref<ConstraintSettings> HingeConstraint::GetConstraintSettings() const414{415HingeConstraintSettings *settings = new HingeConstraintSettings;416ToConstraintSettings(*settings);417settings->mSpace = EConstraintSpace::LocalToBodyCOM;418settings->mPoint1 = RVec3(mLocalSpacePosition1);419settings->mHingeAxis1 = mLocalSpaceHingeAxis1;420settings->mNormalAxis1 = mLocalSpaceNormalAxis1;421settings->mPoint2 = RVec3(mLocalSpacePosition2);422settings->mHingeAxis2 = mLocalSpaceHingeAxis2;423settings->mNormalAxis2 = mLocalSpaceNormalAxis2;424settings->mLimitsMin = mLimitsMin;425settings->mLimitsMax = mLimitsMax;426settings->mLimitsSpringSettings = mLimitsSpringSettings;427settings->mMaxFrictionTorque = mMaxFrictionTorque;428settings->mMotorSettings = mMotorSettings;429return settings;430}431432Mat44 HingeConstraint::GetConstraintToBody1Matrix() const433{434return Mat44(Vec4(mLocalSpaceHingeAxis1, 0), Vec4(mLocalSpaceNormalAxis1, 0), Vec4(mLocalSpaceHingeAxis1.Cross(mLocalSpaceNormalAxis1), 0), Vec4(mLocalSpacePosition1, 1));435}436437Mat44 HingeConstraint::GetConstraintToBody2Matrix() const438{439return Mat44(Vec4(mLocalSpaceHingeAxis2, 0), Vec4(mLocalSpaceNormalAxis2, 0), Vec4(mLocalSpaceHingeAxis2.Cross(mLocalSpaceNormalAxis2), 0), Vec4(mLocalSpacePosition2, 1));440}441442JPH_NAMESPACE_END443444445