Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/Constraints/HingeConstraint.cpp
9912 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::SetLimits(float inLimitsMin, float inLimitsMax)137{138JPH_ASSERT(inLimitsMin <= 0.0f && inLimitsMin >= -JPH_PI);139JPH_ASSERT(inLimitsMax >= 0.0f && inLimitsMax <= JPH_PI);140mLimitsMin = inLimitsMin;141mLimitsMax = inLimitsMax;142mHasLimits = mLimitsMin > -JPH_PI || mLimitsMax < JPH_PI;143}144145void HingeConstraint::CalculateA1AndTheta()146{147if (mHasLimits || mMotorState != EMotorState::Off || mMaxFrictionTorque > 0.0f)148{149Quat rotation1 = mBody1->GetRotation();150151// Calculate relative rotation in world space152//153// The rest rotation is:154//155// q2 = q1 r0156//157// But the actual rotation is158//159// q2 = diff q1 r0160// <=> diff = q2 r0^-1 q1^-1161//162// Where:163// q1 = current rotation of body 1164// q2 = current rotation of body 2165// diff = relative rotation in world space166Quat diff = mBody2->GetRotation() * mInvInitialOrientation * rotation1.Conjugated();167168// Calculate hinge axis in world space169mA1 = rotation1 * mLocalSpaceHingeAxis1;170171// Get rotation angle around the hinge axis172mTheta = diff.GetRotationAngle(mA1);173}174}175176void HingeConstraint::CalculateRotationLimitsConstraintProperties(float inDeltaTime)177{178// Apply constraint if outside of limits179if (mHasLimits && (mTheta <= mLimitsMin || mTheta >= mLimitsMax))180mRotationLimitsConstraintPart.CalculateConstraintPropertiesWithSettings(inDeltaTime, *mBody1, *mBody2, mA1, 0.0f, GetSmallestAngleToLimit(), mLimitsSpringSettings);181else182mRotationLimitsConstraintPart.Deactivate();183}184185void HingeConstraint::CalculateMotorConstraintProperties(float inDeltaTime)186{187switch (mMotorState)188{189case EMotorState::Off:190if (mMaxFrictionTorque > 0.0f)191mMotorConstraintPart.CalculateConstraintProperties(*mBody1, *mBody2, mA1);192else193mMotorConstraintPart.Deactivate();194break;195196case EMotorState::Velocity:197mMotorConstraintPart.CalculateConstraintProperties(*mBody1, *mBody2, mA1, -mTargetAngularVelocity);198break;199200case EMotorState::Position:201if (mMotorSettings.mSpringSettings.HasStiffness())202mMotorConstraintPart.CalculateConstraintPropertiesWithSettings(inDeltaTime, *mBody1, *mBody2, mA1, 0.0f, CenterAngleAroundZero(mTheta - mTargetAngle), mMotorSettings.mSpringSettings);203else204mMotorConstraintPart.Deactivate();205break;206}207}208209void HingeConstraint::SetupVelocityConstraint(float inDeltaTime)210{211// Cache constraint values that are valid until the bodies move212Mat44 rotation1 = Mat44::sRotation(mBody1->GetRotation());213Mat44 rotation2 = Mat44::sRotation(mBody2->GetRotation());214mPointConstraintPart.CalculateConstraintProperties(*mBody1, rotation1, mLocalSpacePosition1, *mBody2, rotation2, mLocalSpacePosition2);215mRotationConstraintPart.CalculateConstraintProperties(*mBody1, rotation1, rotation1.Multiply3x3(mLocalSpaceHingeAxis1), *mBody2, rotation2, rotation2.Multiply3x3(mLocalSpaceHingeAxis2));216CalculateA1AndTheta();217CalculateRotationLimitsConstraintProperties(inDeltaTime);218CalculateMotorConstraintProperties(inDeltaTime);219}220221void HingeConstraint::ResetWarmStart()222{223mMotorConstraintPart.Deactivate();224mPointConstraintPart.Deactivate();225mRotationConstraintPart.Deactivate();226mRotationLimitsConstraintPart.Deactivate();227}228229void HingeConstraint::WarmStartVelocityConstraint(float inWarmStartImpulseRatio)230{231// Warm starting: Apply previous frame impulse232mMotorConstraintPart.WarmStart(*mBody1, *mBody2, inWarmStartImpulseRatio);233mPointConstraintPart.WarmStart(*mBody1, *mBody2, inWarmStartImpulseRatio);234mRotationConstraintPart.WarmStart(*mBody1, *mBody2, inWarmStartImpulseRatio);235mRotationLimitsConstraintPart.WarmStart(*mBody1, *mBody2, inWarmStartImpulseRatio);236}237238float HingeConstraint::GetSmallestAngleToLimit() const239{240float dist_to_min = CenterAngleAroundZero(mTheta - mLimitsMin);241float dist_to_max = CenterAngleAroundZero(mTheta - mLimitsMax);242return abs(dist_to_min) < abs(dist_to_max)? dist_to_min : dist_to_max;243}244245bool HingeConstraint::IsMinLimitClosest() const246{247float dist_to_min = CenterAngleAroundZero(mTheta - mLimitsMin);248float dist_to_max = CenterAngleAroundZero(mTheta - mLimitsMax);249return abs(dist_to_min) < abs(dist_to_max);250}251252bool HingeConstraint::SolveVelocityConstraint(float inDeltaTime)253{254// Solve motor255bool motor = false;256if (mMotorConstraintPart.IsActive())257{258switch (mMotorState)259{260case EMotorState::Off:261{262float max_lambda = mMaxFrictionTorque * inDeltaTime;263motor = mMotorConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2, mA1, -max_lambda, max_lambda);264break;265}266267case EMotorState::Velocity:268case EMotorState::Position:269motor = mMotorConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2, mA1, inDeltaTime * mMotorSettings.mMinTorqueLimit, inDeltaTime * mMotorSettings.mMaxTorqueLimit);270break;271}272}273274// Solve point constraint275bool pos = mPointConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2);276277// Solve rotation constraint278bool rot = mRotationConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2);279280// Solve rotation limits281bool limit = false;282if (mRotationLimitsConstraintPart.IsActive())283{284float min_lambda, max_lambda;285if (mLimitsMin == mLimitsMax)286{287min_lambda = -FLT_MAX;288max_lambda = FLT_MAX;289}290else if (IsMinLimitClosest())291{292min_lambda = 0.0f;293max_lambda = FLT_MAX;294}295else296{297min_lambda = -FLT_MAX;298max_lambda = 0.0f;299}300limit = mRotationLimitsConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2, mA1, min_lambda, max_lambda);301}302303return motor || pos || rot || limit;304}305306bool HingeConstraint::SolvePositionConstraint(float inDeltaTime, float inBaumgarte)307{308// Motor operates on velocities only, don't call SolvePositionConstraint309310// Solve point constraint311mPointConstraintPart.CalculateConstraintProperties(*mBody1, Mat44::sRotation(mBody1->GetRotation()), mLocalSpacePosition1, *mBody2, Mat44::sRotation(mBody2->GetRotation()), mLocalSpacePosition2);312bool pos = mPointConstraintPart.SolvePositionConstraint(*mBody1, *mBody2, inBaumgarte);313314// Solve rotation constraint315Mat44 rotation1 = Mat44::sRotation(mBody1->GetRotation()); // Note that previous call to GetRotation() is out of date since the rotation has changed316Mat44 rotation2 = Mat44::sRotation(mBody2->GetRotation());317mRotationConstraintPart.CalculateConstraintProperties(*mBody1, rotation1, rotation1.Multiply3x3(mLocalSpaceHingeAxis1), *mBody2, rotation2, rotation2.Multiply3x3(mLocalSpaceHingeAxis2));318bool rot = mRotationConstraintPart.SolvePositionConstraint(*mBody1, *mBody2, inBaumgarte);319320// Solve rotation limits321bool limit = false;322if (mHasLimits && mLimitsSpringSettings.mFrequency <= 0.0f)323{324CalculateA1AndTheta();325CalculateRotationLimitsConstraintProperties(inDeltaTime);326if (mRotationLimitsConstraintPart.IsActive())327limit = mRotationLimitsConstraintPart.SolvePositionConstraint(*mBody1, *mBody2, GetSmallestAngleToLimit(), inBaumgarte);328}329330return pos || rot || limit;331}332333#ifdef JPH_DEBUG_RENDERER334void HingeConstraint::DrawConstraint(DebugRenderer *inRenderer) const335{336RMat44 transform1 = mBody1->GetCenterOfMassTransform();337RMat44 transform2 = mBody2->GetCenterOfMassTransform();338339// Draw constraint340RVec3 constraint_pos1 = transform1 * mLocalSpacePosition1;341inRenderer->DrawMarker(constraint_pos1, Color::sRed, 0.1f);342inRenderer->DrawLine(constraint_pos1, transform1 * (mLocalSpacePosition1 + mDrawConstraintSize * mLocalSpaceHingeAxis1), Color::sRed);343344RVec3 constraint_pos2 = transform2 * mLocalSpacePosition2;345inRenderer->DrawMarker(constraint_pos2, Color::sGreen, 0.1f);346inRenderer->DrawLine(constraint_pos2, transform2 * (mLocalSpacePosition2 + mDrawConstraintSize * mLocalSpaceHingeAxis2), Color::sGreen);347inRenderer->DrawLine(constraint_pos2, transform2 * (mLocalSpacePosition2 + mDrawConstraintSize * mLocalSpaceNormalAxis2), Color::sWhite);348}349350void HingeConstraint::DrawConstraintLimits(DebugRenderer *inRenderer) const351{352if (mHasLimits && mLimitsMax > mLimitsMin)353{354// Get constraint properties in world space355RMat44 transform1 = mBody1->GetCenterOfMassTransform();356RVec3 position1 = transform1 * mLocalSpacePosition1;357Vec3 hinge_axis1 = transform1.Multiply3x3(mLocalSpaceHingeAxis1);358Vec3 normal_axis1 = transform1.Multiply3x3(mLocalSpaceNormalAxis1);359360inRenderer->DrawPie(position1, mDrawConstraintSize, hinge_axis1, normal_axis1, mLimitsMin, mLimitsMax, Color::sPurple, DebugRenderer::ECastShadow::Off);361}362}363#endif // JPH_DEBUG_RENDERER364365void HingeConstraint::SaveState(StateRecorder &inStream) const366{367TwoBodyConstraint::SaveState(inStream);368369mMotorConstraintPart.SaveState(inStream);370mRotationConstraintPart.SaveState(inStream);371mPointConstraintPart.SaveState(inStream);372mRotationLimitsConstraintPart.SaveState(inStream);373374inStream.Write(mMotorState);375inStream.Write(mTargetAngularVelocity);376inStream.Write(mTargetAngle);377}378379void HingeConstraint::RestoreState(StateRecorder &inStream)380{381TwoBodyConstraint::RestoreState(inStream);382383mMotorConstraintPart.RestoreState(inStream);384mRotationConstraintPart.RestoreState(inStream);385mPointConstraintPart.RestoreState(inStream);386mRotationLimitsConstraintPart.RestoreState(inStream);387388inStream.Read(mMotorState);389inStream.Read(mTargetAngularVelocity);390inStream.Read(mTargetAngle);391}392393394Ref<ConstraintSettings> HingeConstraint::GetConstraintSettings() const395{396HingeConstraintSettings *settings = new HingeConstraintSettings;397ToConstraintSettings(*settings);398settings->mSpace = EConstraintSpace::LocalToBodyCOM;399settings->mPoint1 = RVec3(mLocalSpacePosition1);400settings->mHingeAxis1 = mLocalSpaceHingeAxis1;401settings->mNormalAxis1 = mLocalSpaceNormalAxis1;402settings->mPoint2 = RVec3(mLocalSpacePosition2);403settings->mHingeAxis2 = mLocalSpaceHingeAxis2;404settings->mNormalAxis2 = mLocalSpaceNormalAxis2;405settings->mLimitsMin = mLimitsMin;406settings->mLimitsMax = mLimitsMax;407settings->mLimitsSpringSettings = mLimitsSpringSettings;408settings->mMaxFrictionTorque = mMaxFrictionTorque;409settings->mMotorSettings = mMotorSettings;410return settings;411}412413Mat44 HingeConstraint::GetConstraintToBody1Matrix() const414{415return Mat44(Vec4(mLocalSpaceHingeAxis1, 0), Vec4(mLocalSpaceNormalAxis1, 0), Vec4(mLocalSpaceHingeAxis1.Cross(mLocalSpaceNormalAxis1), 0), Vec4(mLocalSpacePosition1, 1));416}417418Mat44 HingeConstraint::GetConstraintToBody2Matrix() const419{420return Mat44(Vec4(mLocalSpaceHingeAxis2, 0), Vec4(mLocalSpaceNormalAxis2, 0), Vec4(mLocalSpaceHingeAxis2.Cross(mLocalSpaceNormalAxis2), 0), Vec4(mLocalSpacePosition2, 1));421}422423JPH_NAMESPACE_END424425426