Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/Constraints/SixDOFConstraint.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/SixDOFConstraint.h>7#include <Jolt/Physics/Body/Body.h>8#include <Jolt/Geometry/Ellipse.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(SixDOFConstraintSettings)19{20JPH_ADD_BASE_CLASS(SixDOFConstraintSettings, TwoBodyConstraintSettings)2122JPH_ADD_ENUM_ATTRIBUTE(SixDOFConstraintSettings, mSpace)23JPH_ADD_ATTRIBUTE(SixDOFConstraintSettings, mPosition1)24JPH_ADD_ATTRIBUTE(SixDOFConstraintSettings, mAxisX1)25JPH_ADD_ATTRIBUTE(SixDOFConstraintSettings, mAxisY1)26JPH_ADD_ATTRIBUTE(SixDOFConstraintSettings, mPosition2)27JPH_ADD_ATTRIBUTE(SixDOFConstraintSettings, mAxisX2)28JPH_ADD_ATTRIBUTE(SixDOFConstraintSettings, mAxisY2)29JPH_ADD_ATTRIBUTE(SixDOFConstraintSettings, mMaxFriction)30JPH_ADD_ENUM_ATTRIBUTE(SixDOFConstraintSettings, mSwingType)31JPH_ADD_ATTRIBUTE(SixDOFConstraintSettings, mLimitMin)32JPH_ADD_ATTRIBUTE(SixDOFConstraintSettings, mLimitMax)33JPH_ADD_ATTRIBUTE(SixDOFConstraintSettings, mLimitsSpringSettings)34JPH_ADD_ATTRIBUTE(SixDOFConstraintSettings, mMotorSettings)35}3637void SixDOFConstraintSettings::SaveBinaryState(StreamOut &inStream) const38{39ConstraintSettings::SaveBinaryState(inStream);4041inStream.Write(mSpace);42inStream.Write(mPosition1);43inStream.Write(mAxisX1);44inStream.Write(mAxisY1);45inStream.Write(mPosition2);46inStream.Write(mAxisX2);47inStream.Write(mAxisY2);48inStream.Write(mMaxFriction);49inStream.Write(mSwingType);50inStream.Write(mLimitMin);51inStream.Write(mLimitMax);52for (const SpringSettings &s : mLimitsSpringSettings)53s.SaveBinaryState(inStream);54for (const MotorSettings &m : mMotorSettings)55m.SaveBinaryState(inStream);56}5758void SixDOFConstraintSettings::RestoreBinaryState(StreamIn &inStream)59{60ConstraintSettings::RestoreBinaryState(inStream);6162inStream.Read(mSpace);63inStream.Read(mPosition1);64inStream.Read(mAxisX1);65inStream.Read(mAxisY1);66inStream.Read(mPosition2);67inStream.Read(mAxisX2);68inStream.Read(mAxisY2);69inStream.Read(mMaxFriction);70inStream.Read(mSwingType);71inStream.Read(mLimitMin);72inStream.Read(mLimitMax);73for (SpringSettings &s : mLimitsSpringSettings)74s.RestoreBinaryState(inStream);75for (MotorSettings &m : mMotorSettings)76m.RestoreBinaryState(inStream);77}7879TwoBodyConstraint *SixDOFConstraintSettings::Create(Body &inBody1, Body &inBody2) const80{81return new SixDOFConstraint(inBody1, inBody2, *this);82}8384void SixDOFConstraint::UpdateTranslationLimits()85{86// Set to zero if the limits are inversed87for (int i = EAxis::TranslationX; i <= EAxis::TranslationZ; ++i)88if (mLimitMin[i] > mLimitMax[i])89mLimitMin[i] = mLimitMax[i] = 0.0f;90}9192void SixDOFConstraint::UpdateRotationLimits()93{94if (mSwingTwistConstraintPart.GetSwingType() == ESwingType::Cone)95{96// Cone swing upper limit needs to be positive97mLimitMax[EAxis::RotationY] = max(0.0f, mLimitMax[EAxis::RotationY]);98mLimitMax[EAxis::RotationZ] = max(0.0f, mLimitMax[EAxis::RotationZ]);99100// Cone swing limits only support symmetric ranges101mLimitMin[EAxis::RotationY] = -mLimitMax[EAxis::RotationY];102mLimitMin[EAxis::RotationZ] = -mLimitMax[EAxis::RotationZ];103}104105for (int i = EAxis::RotationX; i <= EAxis::RotationZ; ++i)106{107// Clamp to [-PI, PI] range108mLimitMin[i] = Clamp(mLimitMin[i], -JPH_PI, JPH_PI);109mLimitMax[i] = Clamp(mLimitMax[i], -JPH_PI, JPH_PI);110111// Set to zero if the limits are inversed112if (mLimitMin[i] > mLimitMax[i])113mLimitMin[i] = mLimitMax[i] = 0.0f;114}115116// Pass limits on to constraint part117mSwingTwistConstraintPart.SetLimits(mLimitMin[EAxis::RotationX], mLimitMax[EAxis::RotationX], mLimitMin[EAxis::RotationY], mLimitMax[EAxis::RotationY], mLimitMin[EAxis::RotationZ], mLimitMax[EAxis::RotationZ]);118}119120void SixDOFConstraint::UpdateFixedFreeAxis()121{122uint8 old_free_axis = mFreeAxis;123uint8 old_fixed_axis = mFixedAxis;124125// Cache which axis are fixed and which ones are free126mFreeAxis = 0;127mFixedAxis = 0;128for (int a = 0; a < EAxis::Num; ++a)129{130float limit = a >= EAxis::RotationX? JPH_PI : FLT_MAX;131132if (mLimitMin[a] >= mLimitMax[a])133mFixedAxis |= 1 << a;134else if (mLimitMin[a] <= -limit && mLimitMax[a] >= limit)135mFreeAxis |= 1 << a;136}137138// On change we deactivate all constraints to reset warm starting139if (old_free_axis != mFreeAxis || old_fixed_axis != mFixedAxis)140{141for (AxisConstraintPart &c : mTranslationConstraintPart)142c.Deactivate();143mPointConstraintPart.Deactivate();144mSwingTwistConstraintPart.Deactivate();145mRotationConstraintPart.Deactivate();146for (AxisConstraintPart &c : mMotorTranslationConstraintPart)147c.Deactivate();148for (AngleConstraintPart &c : mMotorRotationConstraintPart)149c.Deactivate();150}151}152153SixDOFConstraint::SixDOFConstraint(Body &inBody1, Body &inBody2, const SixDOFConstraintSettings &inSettings) :154TwoBodyConstraint(inBody1, inBody2, inSettings)155{156// Override swing type157mSwingTwistConstraintPart.SetSwingType(inSettings.mSwingType);158159// Calculate rotation needed to go from constraint space to body1 local space160Vec3 axis_z1 = inSettings.mAxisX1.Cross(inSettings.mAxisY1);161Mat44 c_to_b1(Vec4(inSettings.mAxisX1, 0), Vec4(inSettings.mAxisY1, 0), Vec4(axis_z1, 0), Vec4(0, 0, 0, 1));162mConstraintToBody1 = c_to_b1.GetQuaternion();163164// Calculate rotation needed to go from constraint space to body2 local space165Vec3 axis_z2 = inSettings.mAxisX2.Cross(inSettings.mAxisY2);166Mat44 c_to_b2(Vec4(inSettings.mAxisX2, 0), Vec4(inSettings.mAxisY2, 0), Vec4(axis_z2, 0), Vec4(0, 0, 0, 1));167mConstraintToBody2 = c_to_b2.GetQuaternion();168169if (inSettings.mSpace == EConstraintSpace::WorldSpace)170{171// If all properties were specified in world space, take them to local space now172mLocalSpacePosition1 = Vec3(inBody1.GetInverseCenterOfMassTransform() * inSettings.mPosition1);173mConstraintToBody1 = inBody1.GetRotation().Conjugated() * mConstraintToBody1;174175mLocalSpacePosition2 = Vec3(inBody2.GetInverseCenterOfMassTransform() * inSettings.mPosition2);176mConstraintToBody2 = inBody2.GetRotation().Conjugated() * mConstraintToBody2;177}178else179{180mLocalSpacePosition1 = Vec3(inSettings.mPosition1);181mLocalSpacePosition2 = Vec3(inSettings.mPosition2);182}183184// Copy translation and rotation limits185memcpy(mLimitMin, inSettings.mLimitMin, sizeof(mLimitMin));186memcpy(mLimitMax, inSettings.mLimitMax, sizeof(mLimitMax));187memcpy(mLimitsSpringSettings, inSettings.mLimitsSpringSettings, sizeof(mLimitsSpringSettings));188UpdateTranslationLimits();189UpdateRotationLimits();190UpdateFixedFreeAxis();191CacheHasSpringLimits();192193// Store friction settings194memcpy(mMaxFriction, inSettings.mMaxFriction, sizeof(mMaxFriction));195196// Store motor settings197for (int i = 0; i < EAxis::Num; ++i)198mMotorSettings[i] = inSettings.mMotorSettings[i];199200// Cache if motors are active (motors are off initially, but we may have friction)201CacheTranslationMotorActive();202CacheRotationMotorActive();203}204205void SixDOFConstraint::NotifyShapeChanged(const BodyID &inBodyID, Vec3Arg inDeltaCOM)206{207if (mBody1->GetID() == inBodyID)208mLocalSpacePosition1 -= inDeltaCOM;209else if (mBody2->GetID() == inBodyID)210mLocalSpacePosition2 -= inDeltaCOM;211}212213void SixDOFConstraint::SetTranslationLimits(Vec3Arg inLimitMin, Vec3Arg inLimitMax)214{215mLimitMin[EAxis::TranslationX] = inLimitMin.GetX();216mLimitMin[EAxis::TranslationY] = inLimitMin.GetY();217mLimitMin[EAxis::TranslationZ] = inLimitMin.GetZ();218mLimitMax[EAxis::TranslationX] = inLimitMax.GetX();219mLimitMax[EAxis::TranslationY] = inLimitMax.GetY();220mLimitMax[EAxis::TranslationZ] = inLimitMax.GetZ();221222UpdateTranslationLimits();223UpdateFixedFreeAxis();224}225226void SixDOFConstraint::SetRotationLimits(Vec3Arg inLimitMin, Vec3Arg inLimitMax)227{228mLimitMin[EAxis::RotationX] = inLimitMin.GetX();229mLimitMin[EAxis::RotationY] = inLimitMin.GetY();230mLimitMin[EAxis::RotationZ] = inLimitMin.GetZ();231mLimitMax[EAxis::RotationX] = inLimitMax.GetX();232mLimitMax[EAxis::RotationY] = inLimitMax.GetY();233mLimitMax[EAxis::RotationZ] = inLimitMax.GetZ();234235UpdateRotationLimits();236UpdateFixedFreeAxis();237}238239void SixDOFConstraint::SetMaxFriction(EAxis inAxis, float inFriction)240{241mMaxFriction[inAxis] = inFriction;242243if (inAxis >= EAxis::TranslationX && inAxis <= EAxis::TranslationZ)244CacheTranslationMotorActive();245else246CacheRotationMotorActive();247}248249void SixDOFConstraint::GetPositionConstraintProperties(Vec3 &outR1PlusU, Vec3 &outR2, Vec3 &outU) const250{251RVec3 p1 = mBody1->GetCenterOfMassTransform() * mLocalSpacePosition1;252RVec3 p2 = mBody2->GetCenterOfMassTransform() * mLocalSpacePosition2;253outR1PlusU = Vec3(p2 - mBody1->GetCenterOfMassPosition()); // r1 + u = (p1 - x1) + (p2 - p1) = p2 - x1254outR2 = Vec3(p2 - mBody2->GetCenterOfMassPosition());255outU = Vec3(p2 - p1);256}257258Quat SixDOFConstraint::GetRotationInConstraintSpace() const259{260// Let b1, b2 be the center of mass transform of body1 and body2 (For body1 this is mBody1->GetCenterOfMassTransform())261// Let c1, c2 be the transform that takes a vector from constraint space to local space of body1 and body2 (For body1 this is Mat44::sRotationTranslation(mConstraintToBody1, mLocalSpacePosition1))262// Let q be the rotation of the constraint in constraint space263// b2 takes a vector from the local space of body2 to world space264// To express this in terms of b1: b2 = b1 * c1 * q * c2^-1265// c2^-1 goes from local body 2 space to constraint space266// q rotates the constraint267// c1 goes from constraint space to body 1 local space268// b1 goes from body 1 local space to world space269// So when the body rotations are given, q = (b1 * c1)^-1 * b2 c2270// Or: q = (q1 * c1)^-1 * (q2 * c2) if we're only interested in rotations271return (mBody1->GetRotation() * mConstraintToBody1).Conjugated() * mBody2->GetRotation() * mConstraintToBody2;272}273274void SixDOFConstraint::CacheTranslationMotorActive()275{276mTranslationMotorActive = mMotorState[EAxis::TranslationX] != EMotorState::Off277|| mMotorState[EAxis::TranslationY] != EMotorState::Off278|| mMotorState[EAxis::TranslationZ] != EMotorState::Off279|| HasFriction(EAxis::TranslationX)280|| HasFriction(EAxis::TranslationY)281|| HasFriction(EAxis::TranslationZ);282}283284void SixDOFConstraint::CacheRotationMotorActive()285{286mRotationMotorActive = mMotorState[EAxis::RotationX] != EMotorState::Off287|| mMotorState[EAxis::RotationY] != EMotorState::Off288|| mMotorState[EAxis::RotationZ] != EMotorState::Off289|| HasFriction(EAxis::RotationX)290|| HasFriction(EAxis::RotationY)291|| HasFriction(EAxis::RotationZ);292}293294void SixDOFConstraint::CacheRotationPositionMotorActive()295{296mRotationPositionMotorActive = 0;297for (int i = 0; i < 3; ++i)298if (mMotorState[EAxis::RotationX + i] == EMotorState::Position)299mRotationPositionMotorActive |= 1 << i;300}301302void SixDOFConstraint::CacheHasSpringLimits()303{304mHasSpringLimits = mLimitsSpringSettings[EAxis::TranslationX].mFrequency > 0.0f305|| mLimitsSpringSettings[EAxis::TranslationY].mFrequency > 0.0f306|| mLimitsSpringSettings[EAxis::TranslationZ].mFrequency > 0.0f;307}308309void SixDOFConstraint::SetMotorState(EAxis inAxis, EMotorState inState)310{311JPH_ASSERT(inState == EMotorState::Off || mMotorSettings[inAxis].IsValid());312313if (mMotorState[inAxis] != inState)314{315mMotorState[inAxis] = inState;316317// Ensure that warm starting next frame doesn't apply any impulses (motor parts are repurposed for different modes)318if (inAxis >= EAxis::TranslationX && inAxis <= EAxis::TranslationZ)319{320mMotorTranslationConstraintPart[inAxis - EAxis::TranslationX].Deactivate();321322CacheTranslationMotorActive();323}324else325{326JPH_ASSERT(inAxis >= EAxis::RotationX && inAxis <= EAxis::RotationZ);327328mMotorRotationConstraintPart[inAxis - EAxis::RotationX].Deactivate();329330CacheRotationMotorActive();331CacheRotationPositionMotorActive();332}333}334}335336void SixDOFConstraint::SetTargetOrientationCS(QuatArg inOrientation)337{338Quat q_swing, q_twist;339inOrientation.GetSwingTwist(q_swing, q_twist);340341uint clamped_axis;342mSwingTwistConstraintPart.ClampSwingTwist(q_swing, q_twist, clamped_axis);343344if (clamped_axis != 0)345mTargetOrientation = q_swing * q_twist;346else347mTargetOrientation = inOrientation;348}349350void SixDOFConstraint::SetupVelocityConstraint(float inDeltaTime)351{352// Get body rotations353Quat rotation1 = mBody1->GetRotation();354Quat rotation2 = mBody2->GetRotation();355356// Quaternion that rotates from body1's constraint space to world space357Quat constraint_body1_to_world = rotation1 * mConstraintToBody1;358359// Store world space axis of constraint space360Mat44 translation_axis_mat = Mat44::sRotation(constraint_body1_to_world);361for (int i = 0; i < 3; ++i)362mTranslationAxis[i] = translation_axis_mat.GetColumn3(i);363364if (IsTranslationFullyConstrained())365{366// All translation locked: Setup point constraint367mPointConstraintPart.CalculateConstraintProperties(*mBody1, Mat44::sRotation(rotation1), mLocalSpacePosition1, *mBody2, Mat44::sRotation(rotation2), mLocalSpacePosition2);368}369else if (IsTranslationConstrained() || mTranslationMotorActive)370{371// Update world space positions (the bodies may have moved)372Vec3 r1_plus_u, r2, u;373GetPositionConstraintProperties(r1_plus_u, r2, u);374375// Setup axis constraint parts376for (int i = 0; i < 3; ++i)377{378EAxis axis = EAxis(EAxis::TranslationX + i);379380Vec3 translation_axis = mTranslationAxis[i];381382// Calculate displacement along this axis383float d = translation_axis.Dot(u);384mDisplacement[i] = d; // Store for SolveVelocityConstraint385386// Setup limit constraint387bool constraint_active = false;388float constraint_value = 0.0f;389if (IsFixedAxis(axis))390{391// When constraint is fixed it is always active392constraint_value = d - mLimitMin[i];393constraint_active = true;394}395else if (!IsFreeAxis(axis))396{397// When constraint is limited, it is only active when outside of the allowed range398if (d <= mLimitMin[i])399{400constraint_value = d - mLimitMin[i];401constraint_active = true;402}403else if (d >= mLimitMax[i])404{405constraint_value = d - mLimitMax[i];406constraint_active = true;407}408}409410if (constraint_active)411mTranslationConstraintPart[i].CalculateConstraintPropertiesWithSettings(inDeltaTime, *mBody1, r1_plus_u, *mBody2, r2, translation_axis, 0.0f, constraint_value, mLimitsSpringSettings[i]);412else413mTranslationConstraintPart[i].Deactivate();414415// Setup motor constraint416switch (mMotorState[i])417{418case EMotorState::Off:419if (HasFriction(axis))420mMotorTranslationConstraintPart[i].CalculateConstraintProperties(*mBody1, r1_plus_u, *mBody2, r2, translation_axis);421else422mMotorTranslationConstraintPart[i].Deactivate();423break;424425case EMotorState::Velocity:426mMotorTranslationConstraintPart[i].CalculateConstraintProperties(*mBody1, r1_plus_u, *mBody2, r2, translation_axis, -mTargetVelocity[i]);427break;428429case EMotorState::Position:430{431const SpringSettings &spring_settings = mMotorSettings[i].mSpringSettings;432if (spring_settings.HasStiffness())433mMotorTranslationConstraintPart[i].CalculateConstraintPropertiesWithSettings(inDeltaTime, *mBody1, r1_plus_u, *mBody2, r2, translation_axis, 0.0f, translation_axis.Dot(u) - mTargetPosition[i], spring_settings);434else435mMotorTranslationConstraintPart[i].Deactivate();436break;437}438}439}440}441442// Setup rotation constraints443if (IsRotationFullyConstrained())444{445// All rotation locked: Setup rotation constraint446mRotationConstraintPart.CalculateConstraintProperties(*mBody1, Mat44::sRotation(mBody1->GetRotation()), *mBody2, Mat44::sRotation(mBody2->GetRotation()));447}448else if (IsRotationConstrained() || mRotationMotorActive)449{450// GetRotationInConstraintSpace without redoing the calculation of constraint_body1_to_world451Quat constraint_body2_to_world = mBody2->GetRotation() * mConstraintToBody2;452Quat q = constraint_body1_to_world.Conjugated() * constraint_body2_to_world;453454// Use swing twist constraint part455if (IsRotationConstrained())456mSwingTwistConstraintPart.CalculateConstraintProperties(*mBody1, *mBody2, q, constraint_body1_to_world);457else458mSwingTwistConstraintPart.Deactivate();459460if (mRotationMotorActive)461{462// Calculate rotation motor axis463Mat44 ws_axis = Mat44::sRotation(constraint_body2_to_world);464for (int i = 0; i < 3; ++i)465mRotationAxis[i] = ws_axis.GetColumn3(i);466467// Get target orientation along the shortest path from q468Quat target_orientation = q.Dot(mTargetOrientation) > 0.0f? mTargetOrientation : -mTargetOrientation;469470// The definition of the constraint rotation q:471// R2 * ConstraintToBody2 = R1 * ConstraintToBody1 * q (1)472//473// R2' is the rotation of body 2 when reaching the target_orientation:474// R2' * ConstraintToBody2 = R1 * ConstraintToBody1 * target_orientation (2)475//476// The difference in body 2 space:477// R2' = R2 * diff_body2 (3)478//479// We want to specify the difference in the constraint space of body 2:480// diff_body2 = ConstraintToBody2 * diff * ConstraintToBody2^* (4)481//482// Extracting R2' from 2: R2' = R1 * ConstraintToBody1 * target_orientation * ConstraintToBody2^* (5)483// Combining 3 & 4: R2' = R2 * ConstraintToBody2 * diff * ConstraintToBody2^* (6)484// Combining 1 & 6: R2' = R1 * ConstraintToBody1 * q * diff * ConstraintToBody2^* (7)485// Combining 5 & 7: R1 * ConstraintToBody1 * target_orientation * ConstraintToBody2^* = R1 * ConstraintToBody1 * q * diff * ConstraintToBody2^*486// <=> target_orientation = q * diff487// <=> diff = q^* * target_orientation488Quat diff = q.Conjugated() * target_orientation;489490// Project diff so that only rotation around axis that have a position motor are remaining491Quat projected_diff;492switch (mRotationPositionMotorActive)493{494case 0b001:495// Keep only rotation around X496projected_diff = diff.GetTwist(Vec3::sAxisX());497break;498499case 0b010:500// Keep only rotation around Y501projected_diff = diff.GetTwist(Vec3::sAxisY());502break;503504case 0b100:505// Keep only rotation around Z506projected_diff = diff.GetTwist(Vec3::sAxisZ());507break;508509case 0b011:510// Remove rotation around Z511// q = swing_xy * twist_z <=> swing_xy = q * twist_z^*512projected_diff = diff * diff.GetTwist(Vec3::sAxisZ()).Conjugated();513break;514515case 0b101:516// Remove rotation around Y517// q = swing_xz * twist_y <=> swing_xz = q * twist_y^*518projected_diff = diff * diff.GetTwist(Vec3::sAxisY()).Conjugated();519break;520521case 0b110:522// Remove rotation around X523// q = swing_yz * twist_x <=> swing_yz = q * twist_x^*524projected_diff = diff * diff.GetTwist(Vec3::sAxisX()).Conjugated();525break;526527case 0b111:528default: // All motors off is handled here but the results are unused529// Keep entire rotation530projected_diff = diff;531break;532}533534// Approximate error angles535// The imaginary part of a quaternion is rotation_axis * sin(angle / 2)536// If angle is small, sin(x) = x so angle[i] ~ 2.0f * rotation_axis[i]537// We'll be making small time steps, so if the angle is not small at least the sign will be correct and we'll move in the right direction538Vec3 rotation_error = -2.0f * projected_diff.GetXYZ();539540// Setup motors541for (int i = 0; i < 3; ++i)542{543EAxis axis = EAxis(EAxis::RotationX + i);544545Vec3 rotation_axis = mRotationAxis[i];546547switch (mMotorState[axis])548{549case EMotorState::Off:550if (HasFriction(axis))551mMotorRotationConstraintPart[i].CalculateConstraintProperties(*mBody1, *mBody2, rotation_axis);552else553mMotorRotationConstraintPart[i].Deactivate();554break;555556case EMotorState::Velocity:557mMotorRotationConstraintPart[i].CalculateConstraintProperties(*mBody1, *mBody2, rotation_axis, -mTargetAngularVelocity[i]);558break;559560case EMotorState::Position:561{562const SpringSettings &spring_settings = mMotorSettings[axis].mSpringSettings;563if (spring_settings.HasStiffness())564mMotorRotationConstraintPart[i].CalculateConstraintPropertiesWithSettings(inDeltaTime, *mBody1, *mBody2, rotation_axis, 0.0f, rotation_error[i], spring_settings);565else566mMotorRotationConstraintPart[i].Deactivate();567break;568}569}570}571}572}573}574575void SixDOFConstraint::ResetWarmStart()576{577for (AxisConstraintPart &c : mMotorTranslationConstraintPart)578c.Deactivate();579for (AngleConstraintPart &c : mMotorRotationConstraintPart)580c.Deactivate();581mRotationConstraintPart.Deactivate();582mSwingTwistConstraintPart.Deactivate();583mPointConstraintPart.Deactivate();584for (AxisConstraintPart &c : mTranslationConstraintPart)585c.Deactivate();586}587588void SixDOFConstraint::WarmStartVelocityConstraint(float inWarmStartImpulseRatio)589{590// Warm start translation motors591if (mTranslationMotorActive)592for (int i = 0; i < 3; ++i)593if (mMotorTranslationConstraintPart[i].IsActive())594mMotorTranslationConstraintPart[i].WarmStart(*mBody1, *mBody2, mTranslationAxis[i], inWarmStartImpulseRatio);595596// Warm start rotation motors597if (mRotationMotorActive)598for (AngleConstraintPart &c : mMotorRotationConstraintPart)599if (c.IsActive())600c.WarmStart(*mBody1, *mBody2, inWarmStartImpulseRatio);601602// Warm start rotation constraints603if (IsRotationFullyConstrained())604mRotationConstraintPart.WarmStart(*mBody1, *mBody2, inWarmStartImpulseRatio);605else if (IsRotationConstrained())606mSwingTwistConstraintPart.WarmStart(*mBody1, *mBody2, inWarmStartImpulseRatio);607608// Warm start translation constraints609if (IsTranslationFullyConstrained())610mPointConstraintPart.WarmStart(*mBody1, *mBody2, inWarmStartImpulseRatio);611else if (IsTranslationConstrained())612for (int i = 0; i < 3; ++i)613if (mTranslationConstraintPart[i].IsActive())614mTranslationConstraintPart[i].WarmStart(*mBody1, *mBody2, mTranslationAxis[i], inWarmStartImpulseRatio);615}616617bool SixDOFConstraint::SolveVelocityConstraint(float inDeltaTime)618{619bool impulse = false;620621// Solve translation motor622if (mTranslationMotorActive)623for (int i = 0; i < 3; ++i)624if (mMotorTranslationConstraintPart[i].IsActive())625switch (mMotorState[i])626{627case EMotorState::Off:628{629// Apply friction only630float max_lambda = mMaxFriction[i] * inDeltaTime;631impulse |= mMotorTranslationConstraintPart[i].SolveVelocityConstraint(*mBody1, *mBody2, mTranslationAxis[i], -max_lambda, max_lambda);632break;633}634635case EMotorState::Velocity:636case EMotorState::Position:637// Drive motor638impulse |= mMotorTranslationConstraintPart[i].SolveVelocityConstraint(*mBody1, *mBody2, mTranslationAxis[i], inDeltaTime * mMotorSettings[i].mMinForceLimit, inDeltaTime * mMotorSettings[i].mMaxForceLimit);639break;640}641642// Solve rotation motor643if (mRotationMotorActive)644for (int i = 0; i < 3; ++i)645{646EAxis axis = EAxis(EAxis::RotationX + i);647if (mMotorRotationConstraintPart[i].IsActive())648switch (mMotorState[axis])649{650case EMotorState::Off:651{652// Apply friction only653float max_lambda = mMaxFriction[axis] * inDeltaTime;654impulse |= mMotorRotationConstraintPart[i].SolveVelocityConstraint(*mBody1, *mBody2, mRotationAxis[i], -max_lambda, max_lambda);655break;656}657658case EMotorState::Velocity:659case EMotorState::Position:660// Drive motor661impulse |= mMotorRotationConstraintPart[i].SolveVelocityConstraint(*mBody1, *mBody2, mRotationAxis[i], inDeltaTime * mMotorSettings[axis].mMinTorqueLimit, inDeltaTime * mMotorSettings[axis].mMaxTorqueLimit);662break;663}664}665666// Solve rotation constraint667if (IsRotationFullyConstrained())668impulse |= mRotationConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2);669else if (IsRotationConstrained())670impulse |= mSwingTwistConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2);671672// Solve position constraint673if (IsTranslationFullyConstrained())674impulse |= mPointConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2);675else if (IsTranslationConstrained())676for (int i = 0; i < 3; ++i)677if (mTranslationConstraintPart[i].IsActive())678{679// If the axis is not fixed it must be limited (or else the constraint would not be active)680// Calculate the min and max constraint force based on on which side we're limited681float limit_min = -FLT_MAX, limit_max = FLT_MAX;682if (!IsFixedAxis(EAxis(EAxis::TranslationX + i)))683{684JPH_ASSERT(!IsFreeAxis(EAxis(EAxis::TranslationX + i)));685if (mDisplacement[i] <= mLimitMin[i])686limit_min = 0;687else if (mDisplacement[i] >= mLimitMax[i])688limit_max = 0;689}690691impulse |= mTranslationConstraintPart[i].SolveVelocityConstraint(*mBody1, *mBody2, mTranslationAxis[i], limit_min, limit_max);692}693694return impulse;695}696697bool SixDOFConstraint::SolvePositionConstraint(float inDeltaTime, float inBaumgarte)698{699bool impulse = false;700701if (IsRotationFullyConstrained())702{703// Rotation locked: Solve rotation constraint704705// Inverse of initial rotation from body 1 to body 2 in body 1 space706// Definition of initial orientation r0: q2 = q1 r0707// Initial rotation (see: GetRotationInConstraintSpace): q2 = q1 c1 c2^-1708// So: r0^-1 = (c1 c2^-1)^-1 = c2 * c1^-1709Quat constraint_to_body1 = mConstraintToBody1 * Quat::sEulerAngles(GetRotationLimitsMin());710Quat inv_initial_orientation = mConstraintToBody2 * constraint_to_body1.Conjugated();711712// Solve rotation violations713mRotationConstraintPart.CalculateConstraintProperties(*mBody1, Mat44::sRotation(mBody1->GetRotation()), *mBody2, Mat44::sRotation(mBody2->GetRotation()));714impulse |= mRotationConstraintPart.SolvePositionConstraint(*mBody1, *mBody2, inv_initial_orientation, inBaumgarte);715}716else if (IsRotationConstrained())717{718// Rotation partially constraint719720// Solve rotation violations721Quat q = GetRotationInConstraintSpace();722impulse |= mSwingTwistConstraintPart.SolvePositionConstraint(*mBody1, *mBody2, q, mConstraintToBody1, mConstraintToBody2, inBaumgarte);723}724725// Solve position violations726if (IsTranslationFullyConstrained())727{728// Translation locked: Solve point constraint729Vec3 local_space_position1 = mLocalSpacePosition1 + mConstraintToBody1 * GetTranslationLimitsMin();730mPointConstraintPart.CalculateConstraintProperties(*mBody1, Mat44::sRotation(mBody1->GetRotation()), local_space_position1, *mBody2, Mat44::sRotation(mBody2->GetRotation()), mLocalSpacePosition2);731impulse |= mPointConstraintPart.SolvePositionConstraint(*mBody1, *mBody2, inBaumgarte);732}733else if (IsTranslationConstrained())734{735// Translation partially locked: Solve per axis736for (int i = 0; i < 3; ++i)737if (mLimitsSpringSettings[i].mFrequency <= 0.0f) // If not soft limit738{739// Update world space positions (the bodies may have moved)740Vec3 r1_plus_u, r2, u;741GetPositionConstraintProperties(r1_plus_u, r2, u);742743// Quaternion that rotates from body1's constraint space to world space744Quat constraint_body1_to_world = mBody1->GetRotation() * mConstraintToBody1;745746// Calculate axis747Vec3 translation_axis;748switch (i)749{750case 0: translation_axis = constraint_body1_to_world.RotateAxisX(); break;751case 1: translation_axis = constraint_body1_to_world.RotateAxisY(); break;752default: JPH_ASSERT(i == 2); translation_axis = constraint_body1_to_world.RotateAxisZ(); break;753}754755// Determine position error756float error = 0.0f;757EAxis axis(EAxis(EAxis::TranslationX + i));758if (IsFixedAxis(axis))759error = u.Dot(translation_axis) - mLimitMin[axis];760else if (!IsFreeAxis(axis))761{762float displacement = u.Dot(translation_axis);763if (displacement <= mLimitMin[axis])764error = displacement - mLimitMin[axis];765else if (displacement >= mLimitMax[axis])766error = displacement - mLimitMax[axis];767}768769if (error != 0.0f)770{771// Setup axis constraint part and solve it772mTranslationConstraintPart[i].CalculateConstraintProperties(*mBody1, r1_plus_u, *mBody2, r2, translation_axis);773impulse |= mTranslationConstraintPart[i].SolvePositionConstraint(*mBody1, *mBody2, translation_axis, error, inBaumgarte);774}775}776}777778return impulse;779}780781#ifdef JPH_DEBUG_RENDERER782void SixDOFConstraint::DrawConstraint(DebugRenderer *inRenderer) const783{784// Get constraint properties in world space785RVec3 position1 = mBody1->GetCenterOfMassTransform() * mLocalSpacePosition1;786Quat rotation1 = mBody1->GetRotation() * mConstraintToBody1;787Quat rotation2 = mBody2->GetRotation() * mConstraintToBody2;788789// Draw constraint orientation790inRenderer->DrawCoordinateSystem(RMat44::sRotationTranslation(rotation1, position1), mDrawConstraintSize);791792if ((IsRotationConstrained() || mRotationPositionMotorActive != 0) && !IsRotationFullyConstrained())793{794// Draw current swing and twist795Quat q = GetRotationInConstraintSpace();796Quat q_swing, q_twist;797q.GetSwingTwist(q_swing, q_twist);798inRenderer->DrawLine(position1, position1 + mDrawConstraintSize * (rotation1 * q_twist).RotateAxisY(), Color::sWhite);799inRenderer->DrawLine(position1, position1 + mDrawConstraintSize * (rotation1 * q_swing).RotateAxisX(), Color::sWhite);800}801802// Draw target rotation803Quat m_swing, m_twist;804mTargetOrientation.GetSwingTwist(m_swing, m_twist);805if (mMotorState[EAxis::RotationX] == EMotorState::Position)806inRenderer->DrawLine(position1, position1 + mDrawConstraintSize * (rotation1 * m_twist).RotateAxisY(), Color::sYellow);807if (mMotorState[EAxis::RotationY] == EMotorState::Position || mMotorState[EAxis::RotationZ] == EMotorState::Position)808inRenderer->DrawLine(position1, position1 + mDrawConstraintSize * (rotation1 * m_swing).RotateAxisX(), Color::sYellow);809810// Draw target angular velocity811Vec3 target_angular_velocity = Vec3::sZero();812for (int i = 0; i < 3; ++i)813if (mMotorState[EAxis::RotationX + i] == EMotorState::Velocity)814target_angular_velocity.SetComponent(i, mTargetAngularVelocity[i]);815if (target_angular_velocity != Vec3::sZero())816inRenderer->DrawArrow(position1, position1 + rotation2 * target_angular_velocity, Color::sRed, 0.1f);817}818819void SixDOFConstraint::DrawConstraintLimits(DebugRenderer *inRenderer) const820{821// Get matrix that transforms from constraint space to world space822RMat44 constraint_body1_to_world = RMat44::sRotationTranslation(mBody1->GetRotation() * mConstraintToBody1, mBody1->GetCenterOfMassTransform() * mLocalSpacePosition1);823824// Draw limits825if (mSwingTwistConstraintPart.GetSwingType() == ESwingType::Pyramid)826inRenderer->DrawSwingPyramidLimits(constraint_body1_to_world, mLimitMin[EAxis::RotationY], mLimitMax[EAxis::RotationY], mLimitMin[EAxis::RotationZ], mLimitMax[EAxis::RotationZ], mDrawConstraintSize, Color::sGreen, DebugRenderer::ECastShadow::Off);827else828inRenderer->DrawSwingConeLimits(constraint_body1_to_world, mLimitMax[EAxis::RotationY], mLimitMax[EAxis::RotationZ], mDrawConstraintSize, Color::sGreen, DebugRenderer::ECastShadow::Off);829inRenderer->DrawPie(constraint_body1_to_world.GetTranslation(), mDrawConstraintSize, constraint_body1_to_world.GetAxisX(), constraint_body1_to_world.GetAxisY(), mLimitMin[EAxis::RotationX], mLimitMax[EAxis::RotationX], Color::sPurple, DebugRenderer::ECastShadow::Off);830}831#endif // JPH_DEBUG_RENDERER832833void SixDOFConstraint::SaveState(StateRecorder &inStream) const834{835TwoBodyConstraint::SaveState(inStream);836837for (const AxisConstraintPart &c : mTranslationConstraintPart)838c.SaveState(inStream);839mPointConstraintPart.SaveState(inStream);840mSwingTwistConstraintPart.SaveState(inStream);841mRotationConstraintPart.SaveState(inStream);842for (const AxisConstraintPart &c : mMotorTranslationConstraintPart)843c.SaveState(inStream);844for (const AngleConstraintPart &c : mMotorRotationConstraintPart)845c.SaveState(inStream);846847inStream.Write(mMotorState);848inStream.Write(mTargetVelocity);849inStream.Write(mTargetAngularVelocity);850inStream.Write(mTargetPosition);851inStream.Write(mTargetOrientation);852}853854void SixDOFConstraint::RestoreState(StateRecorder &inStream)855{856TwoBodyConstraint::RestoreState(inStream);857858for (AxisConstraintPart &c : mTranslationConstraintPart)859c.RestoreState(inStream);860mPointConstraintPart.RestoreState(inStream);861mSwingTwistConstraintPart.RestoreState(inStream);862mRotationConstraintPart.RestoreState(inStream);863for (AxisConstraintPart &c : mMotorTranslationConstraintPart)864c.RestoreState(inStream);865for (AngleConstraintPart &c : mMotorRotationConstraintPart)866c.RestoreState(inStream);867868inStream.Read(mMotorState);869inStream.Read(mTargetVelocity);870inStream.Read(mTargetAngularVelocity);871inStream.Read(mTargetPosition);872inStream.Read(mTargetOrientation);873874CacheTranslationMotorActive();875CacheRotationMotorActive();876CacheRotationPositionMotorActive();877}878879Ref<ConstraintSettings> SixDOFConstraint::GetConstraintSettings() const880{881SixDOFConstraintSettings *settings = new SixDOFConstraintSettings;882ToConstraintSettings(*settings);883settings->mSpace = EConstraintSpace::LocalToBodyCOM;884settings->mPosition1 = RVec3(mLocalSpacePosition1);885settings->mAxisX1 = mConstraintToBody1.RotateAxisX();886settings->mAxisY1 = mConstraintToBody1.RotateAxisY();887settings->mPosition2 = RVec3(mLocalSpacePosition2);888settings->mAxisX2 = mConstraintToBody2.RotateAxisX();889settings->mAxisY2 = mConstraintToBody2.RotateAxisY();890settings->mSwingType = mSwingTwistConstraintPart.GetSwingType();891memcpy(settings->mLimitMin, mLimitMin, sizeof(mLimitMin));892memcpy(settings->mLimitMax, mLimitMax, sizeof(mLimitMax));893memcpy(settings->mMaxFriction, mMaxFriction, sizeof(mMaxFriction));894for (int i = 0; i < EAxis::Num; ++i)895settings->mMotorSettings[i] = mMotorSettings[i];896return settings;897}898899JPH_NAMESPACE_END900901902