Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/Constraints/ConstraintPart/SwingTwistConstraintPart.h
9913 views
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)1// SPDX-FileCopyrightText: 2021 Jorrit Rouwe2// SPDX-License-Identifier: MIT34#pragma once56#include <Jolt/Geometry/Ellipse.h>7#include <Jolt/Physics/Constraints/ConstraintPart/RotationEulerConstraintPart.h>8#include <Jolt/Physics/Constraints/ConstraintPart/AngleConstraintPart.h>910JPH_NAMESPACE_BEGIN1112/// How the swing limit behaves13enum class ESwingType : uint814{15Cone, ///< Swing is limited by a cone shape, note that this cone starts to deform for larger swing angles. Cone limits only support limits that are symmetric around 0.16Pyramid, ///< Swing is limited by a pyramid shape, note that this pyramid starts to deform for larger swing angles.17};1819/// Quaternion based constraint that decomposes the rotation in constraint space in swing and twist: q = q_swing * q_twist20/// where q_swing.x = 0 and where q_twist.y = q_twist.z = 021///22/// - Rotation around the twist (x-axis) is within [inTwistMinAngle, inTwistMaxAngle].23/// - Rotation around the swing axis (y and z axis) are limited to an ellipsoid in quaternion space formed by the equation:24///25/// (q_swing.y / sin(inSwingYHalfAngle / 2))^2 + (q_swing.z / sin(inSwingZHalfAngle / 2))^2 <= 126///27/// Which roughly corresponds to an elliptic cone shape with major axis (inSwingYHalfAngle, inSwingZHalfAngle).28///29/// In case inSwingYHalfAngle = 0, the rotation around Y will be constrained to 0 and the rotation around Z30/// will be constrained between [-inSwingZHalfAngle, inSwingZHalfAngle]. Vice versa if inSwingZHalfAngle = 0.31class SwingTwistConstraintPart32{33public:34/// Override the swing type35void SetSwingType(ESwingType inSwingType)36{37mSwingType = inSwingType;38}3940/// Get the swing type for this part41ESwingType GetSwingType() const42{43return mSwingType;44}4546/// Set limits for this constraint (see description above for parameters)47void SetLimits(float inTwistMinAngle, float inTwistMaxAngle, float inSwingYMinAngle, float inSwingYMaxAngle, float inSwingZMinAngle, float inSwingZMaxAngle)48{49constexpr float cLockedAngle = DegreesToRadians(0.5f);50constexpr float cFreeAngle = DegreesToRadians(179.5f);5152// Assume sane input53JPH_ASSERT(inTwistMinAngle <= inTwistMaxAngle);54JPH_ASSERT(inSwingYMinAngle <= inSwingYMaxAngle);55JPH_ASSERT(inSwingZMinAngle <= inSwingZMaxAngle);56JPH_ASSERT(inSwingYMinAngle >= -JPH_PI && inSwingYMaxAngle <= JPH_PI);57JPH_ASSERT(inSwingZMinAngle >= -JPH_PI && inSwingZMaxAngle <= JPH_PI);5859// Calculate the sine and cosine of the half angles60Vec4 half_twist = 0.5f * Vec4(inTwistMinAngle, inTwistMaxAngle, 0, 0);61Vec4 twist_s, twist_c;62half_twist.SinCos(twist_s, twist_c);63Vec4 half_swing = 0.5f * Vec4(inSwingYMinAngle, inSwingYMaxAngle, inSwingZMinAngle, inSwingZMaxAngle);64Vec4 swing_s, swing_c;65half_swing.SinCos(swing_s, swing_c);6667// Store half angles for pyramid limit68mSwingYHalfMinAngle = half_swing.GetX();69mSwingYHalfMaxAngle = half_swing.GetY();70mSwingZHalfMinAngle = half_swing.GetZ();71mSwingZHalfMaxAngle = half_swing.GetW();7273// Store axis flags which are used at runtime to quickly decided which constraints to apply74mRotationFlags = 0;75if (inTwistMinAngle > -cLockedAngle && inTwistMaxAngle < cLockedAngle)76{77mRotationFlags |= TwistXLocked;78mSinTwistHalfMinAngle = 0.0f;79mSinTwistHalfMaxAngle = 0.0f;80mCosTwistHalfMinAngle = 1.0f;81mCosTwistHalfMaxAngle = 1.0f;82}83else if (inTwistMinAngle < -cFreeAngle && inTwistMaxAngle > cFreeAngle)84{85mRotationFlags |= TwistXFree;86mSinTwistHalfMinAngle = -1.0f;87mSinTwistHalfMaxAngle = 1.0f;88mCosTwistHalfMinAngle = 0.0f;89mCosTwistHalfMaxAngle = 0.0f;90}91else92{93mSinTwistHalfMinAngle = twist_s.GetX();94mSinTwistHalfMaxAngle = twist_s.GetY();95mCosTwistHalfMinAngle = twist_c.GetX();96mCosTwistHalfMaxAngle = twist_c.GetY();97}9899if (inSwingYMinAngle > -cLockedAngle && inSwingYMaxAngle < cLockedAngle)100{101mRotationFlags |= SwingYLocked;102mSinSwingYHalfMinAngle = 0.0f;103mSinSwingYHalfMaxAngle = 0.0f;104mCosSwingYHalfMinAngle = 1.0f;105mCosSwingYHalfMaxAngle = 1.0f;106}107else if (inSwingYMinAngle < -cFreeAngle && inSwingYMaxAngle > cFreeAngle)108{109mRotationFlags |= SwingYFree;110mSinSwingYHalfMinAngle = -1.0f;111mSinSwingYHalfMaxAngle = 1.0f;112mCosSwingYHalfMinAngle = 0.0f;113mCosSwingYHalfMaxAngle = 0.0f;114}115else116{117mSinSwingYHalfMinAngle = swing_s.GetX();118mSinSwingYHalfMaxAngle = swing_s.GetY();119mCosSwingYHalfMinAngle = swing_c.GetX();120mCosSwingYHalfMaxAngle = swing_c.GetY();121JPH_ASSERT(mSinSwingYHalfMinAngle <= mSinSwingYHalfMaxAngle);122}123124if (inSwingZMinAngle > -cLockedAngle && inSwingZMaxAngle < cLockedAngle)125{126mRotationFlags |= SwingZLocked;127mSinSwingZHalfMinAngle = 0.0f;128mSinSwingZHalfMaxAngle = 0.0f;129mCosSwingZHalfMinAngle = 1.0f;130mCosSwingZHalfMaxAngle = 1.0f;131}132else if (inSwingZMinAngle < -cFreeAngle && inSwingZMaxAngle > cFreeAngle)133{134mRotationFlags |= SwingZFree;135mSinSwingZHalfMinAngle = -1.0f;136mSinSwingZHalfMaxAngle = 1.0f;137mCosSwingZHalfMinAngle = 0.0f;138mCosSwingZHalfMaxAngle = 0.0f;139}140else141{142mSinSwingZHalfMinAngle = swing_s.GetZ();143mSinSwingZHalfMaxAngle = swing_s.GetW();144mCosSwingZHalfMinAngle = swing_c.GetZ();145mCosSwingZHalfMaxAngle = swing_c.GetW();146JPH_ASSERT(mSinSwingZHalfMinAngle <= mSinSwingZHalfMaxAngle);147}148}149150/// Flags to indicate which axis got clamped by ClampSwingTwist151static constexpr uint cClampedTwistMin = 1 << 0;152static constexpr uint cClampedTwistMax = 1 << 1;153static constexpr uint cClampedSwingYMin = 1 << 2;154static constexpr uint cClampedSwingYMax = 1 << 3;155static constexpr uint cClampedSwingZMin = 1 << 4;156static constexpr uint cClampedSwingZMax = 1 << 5;157158/// Helper function to determine if we're clamped against the min or max limit159static JPH_INLINE bool sDistanceToMinShorter(float inDeltaMin, float inDeltaMax)160{161// We're outside of the limits, get actual delta to min/max range162// Note that a swing/twist of -1 and 1 represent the same angle, so if the difference is bigger than 1, the shortest angle is the other way around (2 - difference)163// We should actually be working with angles rather than sin(angle / 2). When the difference is small the approximation is accurate, but164// when working with extreme values the calculation is off and e.g. when the limit is between 0 and 180 a value of approx -60 will clamp165// to 180 rather than 0 (you'd expect anything > -90 to go to 0).166inDeltaMin = abs(inDeltaMin);167if (inDeltaMin > 1.0f) inDeltaMin = 2.0f - inDeltaMin;168inDeltaMax = abs(inDeltaMax);169if (inDeltaMax > 1.0f) inDeltaMax = 2.0f - inDeltaMax;170return inDeltaMin < inDeltaMax;171}172173/// Clamp twist and swing against the constraint limits, returns which parts were clamped (everything assumed in constraint space)174inline void ClampSwingTwist(Quat &ioSwing, Quat &ioTwist, uint &outClampedAxis) const175{176// Start with not clamped177outClampedAxis = 0;178179// Check that swing and twist quaternions don't contain rotations around the wrong axis180JPH_ASSERT(ioSwing.GetX() == 0.0f);181JPH_ASSERT(ioTwist.GetY() == 0.0f);182JPH_ASSERT(ioTwist.GetZ() == 0.0f);183184// Ensure quaternions have w > 0185bool negate_swing = ioSwing.GetW() < 0.0f;186if (negate_swing)187ioSwing = -ioSwing;188bool negate_twist = ioTwist.GetW() < 0.0f;189if (negate_twist)190ioTwist = -ioTwist;191192if (mRotationFlags & TwistXLocked)193{194// Twist axis is locked, clamp whenever twist is not identity195outClampedAxis |= ioTwist.GetX() != 0.0f? (cClampedTwistMin | cClampedTwistMax) : 0;196ioTwist = Quat::sIdentity();197}198else if ((mRotationFlags & TwistXFree) == 0)199{200// Twist axis has limit, clamp whenever out of range201float delta_min = mSinTwistHalfMinAngle - ioTwist.GetX();202float delta_max = ioTwist.GetX() - mSinTwistHalfMaxAngle;203if (delta_min > 0.0f || delta_max > 0.0f)204{205// Pick the twist that corresponds to the smallest delta206if (sDistanceToMinShorter(delta_min, delta_max))207{208ioTwist = Quat(mSinTwistHalfMinAngle, 0, 0, mCosTwistHalfMinAngle);209outClampedAxis |= cClampedTwistMin;210}211else212{213ioTwist = Quat(mSinTwistHalfMaxAngle, 0, 0, mCosTwistHalfMaxAngle);214outClampedAxis |= cClampedTwistMax;215}216}217}218219// Clamp swing220if (mRotationFlags & SwingYLocked)221{222if (mRotationFlags & SwingZLocked)223{224// Both swing Y and Z are disabled, no degrees of freedom in swing225outClampedAxis |= ioSwing.GetY() != 0.0f? (cClampedSwingYMin | cClampedSwingYMax) : 0;226outClampedAxis |= ioSwing.GetZ() != 0.0f? (cClampedSwingZMin | cClampedSwingZMax) : 0;227ioSwing = Quat::sIdentity();228}229else230{231// Swing Y angle disabled, only 1 degree of freedom in swing232outClampedAxis |= ioSwing.GetY() != 0.0f? (cClampedSwingYMin | cClampedSwingYMax) : 0;233float delta_min = mSinSwingZHalfMinAngle - ioSwing.GetZ();234float delta_max = ioSwing.GetZ() - mSinSwingZHalfMaxAngle;235if (delta_min > 0.0f || delta_max > 0.0f)236{237// Pick the swing that corresponds to the smallest delta238if (sDistanceToMinShorter(delta_min, delta_max))239{240ioSwing = Quat(0, 0, mSinSwingZHalfMinAngle, mCosSwingZHalfMinAngle);241outClampedAxis |= cClampedSwingZMin;242}243else244{245ioSwing = Quat(0, 0, mSinSwingZHalfMaxAngle, mCosSwingZHalfMaxAngle);246outClampedAxis |= cClampedSwingZMax;247}248}249else if ((outClampedAxis & cClampedSwingYMin) != 0)250{251float z = ioSwing.GetZ();252ioSwing = Quat(0, 0, z, sqrt(1.0f - Square(z)));253}254}255}256else if (mRotationFlags & SwingZLocked)257{258// Swing Z angle disabled, only 1 degree of freedom in swing259outClampedAxis |= ioSwing.GetZ() != 0.0f? (cClampedSwingZMin | cClampedSwingZMax) : 0;260float delta_min = mSinSwingYHalfMinAngle - ioSwing.GetY();261float delta_max = ioSwing.GetY() - mSinSwingYHalfMaxAngle;262if (delta_min > 0.0f || delta_max > 0.0f)263{264// Pick the swing that corresponds to the smallest delta265if (sDistanceToMinShorter(delta_min, delta_max))266{267ioSwing = Quat(0, mSinSwingYHalfMinAngle, 0, mCosSwingYHalfMinAngle);268outClampedAxis |= cClampedSwingYMin;269}270else271{272ioSwing = Quat(0, mSinSwingYHalfMaxAngle, 0, mCosSwingYHalfMaxAngle);273outClampedAxis |= cClampedSwingYMax;274}275}276else if ((outClampedAxis & cClampedSwingZMin) != 0)277{278float y = ioSwing.GetY();279ioSwing = Quat(0, y, 0, sqrt(1.0f - Square(y)));280}281}282else283{284// Two degrees of freedom285if (mSwingType == ESwingType::Cone)286{287// Use ellipse to solve limits288Ellipse ellipse(mSinSwingYHalfMaxAngle, mSinSwingZHalfMaxAngle);289Float2 point(ioSwing.GetY(), ioSwing.GetZ());290if (!ellipse.IsInside(point))291{292Float2 closest = ellipse.GetClosestPoint(point);293ioSwing = Quat(0, closest.x, closest.y, sqrt(max(0.0f, 1.0f - Square(closest.x) - Square(closest.y))));294outClampedAxis |= cClampedSwingYMin | cClampedSwingYMax | cClampedSwingZMin | cClampedSwingZMax; // We're not using the flags on which side we got clamped here295}296}297else298{299// Use pyramid to solve limits300// The quaternion rotating by angle y around the Y axis then rotating by angle z around the Z axis is:301// q = Quat::sRotation(Vec3::sAxisZ(), z) * Quat::sRotation(Vec3::sAxisY(), y)302// [q.x, q.y, q.z, q.w] = [-sin(y / 2) * sin(z / 2), sin(y / 2) * cos(z / 2), cos(y / 2) * sin(z / 2), cos(y / 2) * cos(z / 2)]303// So we can calculate y / 2 = atan2(q.y, q.w) and z / 2 = atan2(q.z, q.w)304Vec4 half_angle = Vec4::sATan2(ioSwing.GetXYZW().Swizzle<SWIZZLE_Y, SWIZZLE_Y, SWIZZLE_Z, SWIZZLE_Z>(), ioSwing.GetXYZW().SplatW());305Vec4 min_half_angle(mSwingYHalfMinAngle, mSwingYHalfMinAngle, mSwingZHalfMinAngle, mSwingZHalfMinAngle);306Vec4 max_half_angle(mSwingYHalfMaxAngle, mSwingYHalfMaxAngle, mSwingZHalfMaxAngle, mSwingZHalfMaxAngle);307Vec4 clamped_half_angle = Vec4::sMin(Vec4::sMax(half_angle, min_half_angle), max_half_angle);308UVec4 unclamped = Vec4::sEquals(half_angle, clamped_half_angle);309if (!unclamped.TestAllTrue())310{311// We now calculate the quaternion again using the formula for q above,312// but we leave out the x component in order to not introduce twist313Vec4 s, c;314clamped_half_angle.SinCos(s, c);315ioSwing = Quat(0, s.GetY() * c.GetZ(), c.GetY() * s.GetZ(), c.GetY() * c.GetZ()).Normalized();316outClampedAxis |= cClampedSwingYMin | cClampedSwingYMax | cClampedSwingZMin | cClampedSwingZMax; // We're not using the flags on which side we got clamped here317}318}319}320321// Flip sign back322if (negate_swing)323ioSwing = -ioSwing;324if (negate_twist)325ioTwist = -ioTwist;326327JPH_ASSERT(ioSwing.IsNormalized());328JPH_ASSERT(ioTwist.IsNormalized());329}330331/// Calculate properties used during the functions below332/// @param inBody1 The first body that this constraint is attached to333/// @param inBody2 The second body that this constraint is attached to334/// @param inConstraintRotation The current rotation of the constraint in constraint space335/// @param inConstraintToWorld Rotates from constraint space into world space336inline void CalculateConstraintProperties(const Body &inBody1, const Body &inBody2, QuatArg inConstraintRotation, QuatArg inConstraintToWorld)337{338// Decompose into swing and twist339Quat q_swing, q_twist;340inConstraintRotation.GetSwingTwist(q_swing, q_twist);341342// Clamp against joint limits343Quat q_clamped_swing = q_swing, q_clamped_twist = q_twist;344uint clamped_axis;345ClampSwingTwist(q_clamped_swing, q_clamped_twist, clamped_axis);346347if (mRotationFlags & SwingYLocked)348{349Quat twist_to_world = inConstraintToWorld * q_swing;350mWorldSpaceSwingLimitYRotationAxis = twist_to_world.RotateAxisY();351mWorldSpaceSwingLimitZRotationAxis = twist_to_world.RotateAxisZ();352353if (mRotationFlags & SwingZLocked)354{355// Swing fully locked356mSwingLimitYConstraintPart.CalculateConstraintProperties(inBody1, inBody2, mWorldSpaceSwingLimitYRotationAxis);357mSwingLimitZConstraintPart.CalculateConstraintProperties(inBody1, inBody2, mWorldSpaceSwingLimitZRotationAxis);358}359else360{361// Swing only locked around Y362mSwingLimitYConstraintPart.CalculateConstraintProperties(inBody1, inBody2, mWorldSpaceSwingLimitYRotationAxis);363if ((clamped_axis & (cClampedSwingZMin | cClampedSwingZMax)) != 0)364{365if ((clamped_axis & cClampedSwingZMin) != 0)366mWorldSpaceSwingLimitZRotationAxis = -mWorldSpaceSwingLimitZRotationAxis; // Flip axis if hitting min limit because the impulse limit is going to be between [-FLT_MAX, 0]367mSwingLimitZConstraintPart.CalculateConstraintProperties(inBody1, inBody2, mWorldSpaceSwingLimitZRotationAxis);368}369else370mSwingLimitZConstraintPart.Deactivate();371}372}373else if (mRotationFlags & SwingZLocked)374{375// Swing only locked around Z376Quat twist_to_world = inConstraintToWorld * q_swing;377mWorldSpaceSwingLimitYRotationAxis = twist_to_world.RotateAxisY();378mWorldSpaceSwingLimitZRotationAxis = twist_to_world.RotateAxisZ();379380if ((clamped_axis & (cClampedSwingYMin | cClampedSwingYMax)) != 0)381{382if ((clamped_axis & cClampedSwingYMin) != 0)383mWorldSpaceSwingLimitYRotationAxis = -mWorldSpaceSwingLimitYRotationAxis; // Flip axis if hitting min limit because the impulse limit is going to be between [-FLT_MAX, 0]384mSwingLimitYConstraintPart.CalculateConstraintProperties(inBody1, inBody2, mWorldSpaceSwingLimitYRotationAxis);385}386else387mSwingLimitYConstraintPart.Deactivate();388mSwingLimitZConstraintPart.CalculateConstraintProperties(inBody1, inBody2, mWorldSpaceSwingLimitZRotationAxis);389}390else if ((mRotationFlags & SwingYZFree) != SwingYZFree)391{392// Swing has limits around Y and Z393if ((clamped_axis & (cClampedSwingYMin | cClampedSwingYMax | cClampedSwingZMin | cClampedSwingZMax)) != 0)394{395// Calculate axis of rotation from clamped swing to swing396Vec3 current = (inConstraintToWorld * q_swing).RotateAxisX();397Vec3 desired = (inConstraintToWorld * q_clamped_swing).RotateAxisX();398mWorldSpaceSwingLimitYRotationAxis = desired.Cross(current);399float len = mWorldSpaceSwingLimitYRotationAxis.Length();400if (len != 0.0f)401{402mWorldSpaceSwingLimitYRotationAxis /= len;403mSwingLimitYConstraintPart.CalculateConstraintProperties(inBody1, inBody2, mWorldSpaceSwingLimitYRotationAxis);404}405else406mSwingLimitYConstraintPart.Deactivate();407}408else409mSwingLimitYConstraintPart.Deactivate();410mSwingLimitZConstraintPart.Deactivate();411}412else413{414// No swing limits415mSwingLimitYConstraintPart.Deactivate();416mSwingLimitZConstraintPart.Deactivate();417}418419if (mRotationFlags & TwistXLocked)420{421// Twist locked, always activate constraint422mWorldSpaceTwistLimitRotationAxis = (inConstraintToWorld * q_swing).RotateAxisX();423mTwistLimitConstraintPart.CalculateConstraintProperties(inBody1, inBody2, mWorldSpaceTwistLimitRotationAxis);424}425else if ((mRotationFlags & TwistXFree) == 0)426{427// Twist has limits428if ((clamped_axis & (cClampedTwistMin | cClampedTwistMax)) != 0)429{430mWorldSpaceTwistLimitRotationAxis = (inConstraintToWorld * q_swing).RotateAxisX();431if ((clamped_axis & cClampedTwistMin) != 0)432mWorldSpaceTwistLimitRotationAxis = -mWorldSpaceTwistLimitRotationAxis; // Flip axis if hitting min limit because the impulse limit is going to be between [-FLT_MAX, 0]433mTwistLimitConstraintPart.CalculateConstraintProperties(inBody1, inBody2, mWorldSpaceTwistLimitRotationAxis);434}435else436mTwistLimitConstraintPart.Deactivate();437}438else439{440// No twist limits441mTwistLimitConstraintPart.Deactivate();442}443}444445/// Deactivate this constraint446void Deactivate()447{448mSwingLimitYConstraintPart.Deactivate();449mSwingLimitZConstraintPart.Deactivate();450mTwistLimitConstraintPart.Deactivate();451}452453/// Check if constraint is active454inline bool IsActive() const455{456return mSwingLimitYConstraintPart.IsActive() || mSwingLimitZConstraintPart.IsActive() || mTwistLimitConstraintPart.IsActive();457}458459/// Must be called from the WarmStartVelocityConstraint call to apply the previous frame's impulses460inline void WarmStart(Body &ioBody1, Body &ioBody2, float inWarmStartImpulseRatio)461{462mSwingLimitYConstraintPart.WarmStart(ioBody1, ioBody2, inWarmStartImpulseRatio);463mSwingLimitZConstraintPart.WarmStart(ioBody1, ioBody2, inWarmStartImpulseRatio);464mTwistLimitConstraintPart.WarmStart(ioBody1, ioBody2, inWarmStartImpulseRatio);465}466467/// Iteratively update the velocity constraint. Makes sure d/dt C(...) = 0, where C is the constraint equation.468inline bool SolveVelocityConstraint(Body &ioBody1, Body &ioBody2)469{470bool impulse = false;471472// Solve swing constraint473if (mSwingLimitYConstraintPart.IsActive())474impulse |= mSwingLimitYConstraintPart.SolveVelocityConstraint(ioBody1, ioBody2, mWorldSpaceSwingLimitYRotationAxis, -FLT_MAX, mSinSwingYHalfMinAngle == mSinSwingYHalfMaxAngle? FLT_MAX : 0.0f);475476if (mSwingLimitZConstraintPart.IsActive())477impulse |= mSwingLimitZConstraintPart.SolveVelocityConstraint(ioBody1, ioBody2, mWorldSpaceSwingLimitZRotationAxis, -FLT_MAX, mSinSwingZHalfMinAngle == mSinSwingZHalfMaxAngle? FLT_MAX : 0.0f);478479// Solve twist constraint480if (mTwistLimitConstraintPart.IsActive())481impulse |= mTwistLimitConstraintPart.SolveVelocityConstraint(ioBody1, ioBody2, mWorldSpaceTwistLimitRotationAxis, -FLT_MAX, mSinTwistHalfMinAngle == mSinTwistHalfMaxAngle? FLT_MAX : 0.0f);482483return impulse;484}485486/// Iteratively update the position constraint. Makes sure C(...) = 0.487/// @param ioBody1 The first body that this constraint is attached to488/// @param ioBody2 The second body that this constraint is attached to489/// @param inConstraintRotation The current rotation of the constraint in constraint space490/// @param inConstraintToBody1 , inConstraintToBody2 Rotates from constraint space to body 1/2 space491/// @param inBaumgarte Baumgarte constant (fraction of the error to correct)492inline bool SolvePositionConstraint(Body &ioBody1, Body &ioBody2, QuatArg inConstraintRotation, QuatArg inConstraintToBody1, QuatArg inConstraintToBody2, float inBaumgarte) const493{494Quat q_swing, q_twist;495inConstraintRotation.GetSwingTwist(q_swing, q_twist);496497uint clamped_axis;498ClampSwingTwist(q_swing, q_twist, clamped_axis);499500// Solve rotation violations501if (clamped_axis != 0)502{503RotationEulerConstraintPart part;504Quat inv_initial_orientation = inConstraintToBody2 * (inConstraintToBody1 * q_swing * q_twist).Conjugated();505part.CalculateConstraintProperties(ioBody1, Mat44::sRotation(ioBody1.GetRotation()), ioBody2, Mat44::sRotation(ioBody2.GetRotation()));506return part.SolvePositionConstraint(ioBody1, ioBody2, inv_initial_orientation, inBaumgarte);507}508509return false;510}511512/// Return lagrange multiplier for swing513inline float GetTotalSwingYLambda() const514{515return mSwingLimitYConstraintPart.GetTotalLambda();516}517518inline float GetTotalSwingZLambda() const519{520return mSwingLimitZConstraintPart.GetTotalLambda();521}522523/// Return lagrange multiplier for twist524inline float GetTotalTwistLambda() const525{526return mTwistLimitConstraintPart.GetTotalLambda();527}528529/// Save state of this constraint part530void SaveState(StateRecorder &inStream) const531{532mSwingLimitYConstraintPart.SaveState(inStream);533mSwingLimitZConstraintPart.SaveState(inStream);534mTwistLimitConstraintPart.SaveState(inStream);535}536537/// Restore state of this constraint part538void RestoreState(StateRecorder &inStream)539{540mSwingLimitYConstraintPart.RestoreState(inStream);541mSwingLimitZConstraintPart.RestoreState(inStream);542mTwistLimitConstraintPart.RestoreState(inStream);543}544545private:546// CONFIGURATION PROPERTIES FOLLOW547548enum ERotationFlags549{550/// Indicates that axis is completely locked (cannot rotate around this axis)551TwistXLocked = 1 << 0,552SwingYLocked = 1 << 1,553SwingZLocked = 1 << 2,554555/// Indicates that axis is completely free (can rotate around without limits)556TwistXFree = 1 << 3,557SwingYFree = 1 << 4,558SwingZFree = 1 << 5,559SwingYZFree = SwingYFree | SwingZFree560};561562uint8 mRotationFlags;563564// Constants565ESwingType mSwingType = ESwingType::Cone;566float mSinTwistHalfMinAngle;567float mSinTwistHalfMaxAngle;568float mCosTwistHalfMinAngle;569float mCosTwistHalfMaxAngle;570float mSwingYHalfMinAngle;571float mSwingYHalfMaxAngle;572float mSwingZHalfMinAngle;573float mSwingZHalfMaxAngle;574float mSinSwingYHalfMinAngle;575float mSinSwingYHalfMaxAngle;576float mSinSwingZHalfMinAngle;577float mSinSwingZHalfMaxAngle;578float mCosSwingYHalfMinAngle;579float mCosSwingYHalfMaxAngle;580float mCosSwingZHalfMinAngle;581float mCosSwingZHalfMaxAngle;582583// RUN TIME PROPERTIES FOLLOW584585/// Rotation axis for the angle constraint parts586Vec3 mWorldSpaceSwingLimitYRotationAxis;587Vec3 mWorldSpaceSwingLimitZRotationAxis;588Vec3 mWorldSpaceTwistLimitRotationAxis;589590/// The constraint parts591AngleConstraintPart mSwingLimitYConstraintPart;592AngleConstraintPart mSwingLimitZConstraintPart;593AngleConstraintPart mTwistLimitConstraintPart;594};595596JPH_NAMESPACE_END597598599