Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/Constraints/ConstraintPart/DualAxisConstraintPart.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/Physics/Body/Body.h>7#include <Jolt/Physics/StateRecorder.h>8#include <Jolt/Math/Vector.h>9#include <Jolt/Math/Matrix.h>1011JPH_NAMESPACE_BEGIN1213/**14Constrains movement on 2 axis1516@see "Constraints Derivation for Rigid Body Simulation in 3D" - Daniel Chappuis, section 2.3.11718Constraint equation (eq 51):1920\f[C = \begin{bmatrix} (p_2 - p_1) \cdot n_1 \\ (p_2 - p_1) \cdot n_2\end{bmatrix}\f]2122Jacobian (transposed) (eq 55):2324\f[J^T = \begin{bmatrix}25-n_1 & -n_2 \\26-(r_1 + u) \times n_1 & -(r_1 + u) \times n_2 \\27n_1 & n_2 \\28r_2 \times n_1 & r_2 \times n_229\end{bmatrix}\f]3031Used terms (here and below, everything in world space):\n32n1, n2 = constraint axis (normalized).\n33p1, p2 = constraint points.\n34r1 = p1 - x1.\n35r2 = p2 - x2.\n36u = x2 + r2 - x1 - r1 = p2 - p1.\n37x1, x2 = center of mass for the bodies.\n38v = [v1, w1, v2, w2].\n39v1, v2 = linear velocity of body 1 and 2.\n40w1, w2 = angular velocity of body 1 and 2.\n41M = mass matrix, a diagonal matrix of the mass and inertia with diagonal [m1, I1, m2, I2].\n42\f$K^{-1} = \left( J M^{-1} J^T \right)^{-1}\f$ = effective mass.\n43b = velocity bias.\n44\f$\beta\f$ = baumgarte constant.45**/46class DualAxisConstraintPart47{48public:49using Vec2 = Vector<2>;50using Mat22 = Matrix<2, 2>;5152private:53/// Internal helper function to update velocities of bodies after Lagrange multiplier is calculated54JPH_INLINE bool ApplyVelocityStep(Body &ioBody1, Body &ioBody2, Vec3Arg inN1, Vec3Arg inN2, const Vec2 &inLambda) const55{56// Apply impulse if delta is not zero57if (!inLambda.IsZero())58{59// Calculate velocity change due to constraint60//61// Impulse:62// P = J^T lambda63//64// Euler velocity integration:65// v' = v + M^-1 P66Vec3 impulse = inN1 * inLambda[0] + inN2 * inLambda[1];67if (ioBody1.IsDynamic())68{69MotionProperties *mp1 = ioBody1.GetMotionProperties();70mp1->SubLinearVelocityStep(mp1->GetInverseMass() * impulse);71mp1->SubAngularVelocityStep(mInvI1_R1PlusUxN1 * inLambda[0] + mInvI1_R1PlusUxN2 * inLambda[1]);72}73if (ioBody2.IsDynamic())74{75MotionProperties *mp2 = ioBody2.GetMotionProperties();76mp2->AddLinearVelocityStep(mp2->GetInverseMass() * impulse);77mp2->AddAngularVelocityStep(mInvI2_R2xN1 * inLambda[0] + mInvI2_R2xN2 * inLambda[1]);78}79return true;80}8182return false;83}8485/// Internal helper function to calculate the lagrange multiplier86inline void CalculateLagrangeMultiplier(const Body &inBody1, const Body &inBody2, Vec3Arg inN1, Vec3Arg inN2, Vec2 &outLambda) const87{88// Calculate lagrange multiplier:89//90// lambda = -K^-1 (J v + b)91Vec3 delta_lin = inBody1.GetLinearVelocity() - inBody2.GetLinearVelocity();92Vec2 jv;93jv[0] = inN1.Dot(delta_lin) + mR1PlusUxN1.Dot(inBody1.GetAngularVelocity()) - mR2xN1.Dot(inBody2.GetAngularVelocity());94jv[1] = inN2.Dot(delta_lin) + mR1PlusUxN2.Dot(inBody1.GetAngularVelocity()) - mR2xN2.Dot(inBody2.GetAngularVelocity());95outLambda = mEffectiveMass * jv;96}9798public:99/// Calculate properties used during the functions below100/// All input vectors are in world space101inline void CalculateConstraintProperties(const Body &inBody1, Mat44Arg inRotation1, Vec3Arg inR1PlusU, const Body &inBody2, Mat44Arg inRotation2, Vec3Arg inR2, Vec3Arg inN1, Vec3Arg inN2)102{103JPH_ASSERT(inN1.IsNormalized(1.0e-5f));104JPH_ASSERT(inN2.IsNormalized(1.0e-5f));105106// Calculate properties used during constraint solving107mR1PlusUxN1 = inR1PlusU.Cross(inN1);108mR1PlusUxN2 = inR1PlusU.Cross(inN2);109mR2xN1 = inR2.Cross(inN1);110mR2xN2 = inR2.Cross(inN2);111112// Calculate effective mass: K^-1 = (J M^-1 J^T)^-1, eq 59113Mat22 inv_effective_mass;114if (inBody1.IsDynamic())115{116const MotionProperties *mp1 = inBody1.GetMotionProperties();117Mat44 inv_i1 = mp1->GetInverseInertiaForRotation(inRotation1);118mInvI1_R1PlusUxN1 = inv_i1.Multiply3x3(mR1PlusUxN1);119mInvI1_R1PlusUxN2 = inv_i1.Multiply3x3(mR1PlusUxN2);120121inv_effective_mass(0, 0) = mp1->GetInverseMass() + mR1PlusUxN1.Dot(mInvI1_R1PlusUxN1);122inv_effective_mass(0, 1) = mR1PlusUxN1.Dot(mInvI1_R1PlusUxN2);123inv_effective_mass(1, 0) = mR1PlusUxN2.Dot(mInvI1_R1PlusUxN1);124inv_effective_mass(1, 1) = mp1->GetInverseMass() + mR1PlusUxN2.Dot(mInvI1_R1PlusUxN2);125}126else127{128JPH_IF_DEBUG(mInvI1_R1PlusUxN1 = Vec3::sNaN();)129JPH_IF_DEBUG(mInvI1_R1PlusUxN2 = Vec3::sNaN();)130131inv_effective_mass = Mat22::sZero();132}133134if (inBody2.IsDynamic())135{136const MotionProperties *mp2 = inBody2.GetMotionProperties();137Mat44 inv_i2 = mp2->GetInverseInertiaForRotation(inRotation2);138mInvI2_R2xN1 = inv_i2.Multiply3x3(mR2xN1);139mInvI2_R2xN2 = inv_i2.Multiply3x3(mR2xN2);140141inv_effective_mass(0, 0) += mp2->GetInverseMass() + mR2xN1.Dot(mInvI2_R2xN1);142inv_effective_mass(0, 1) += mR2xN1.Dot(mInvI2_R2xN2);143inv_effective_mass(1, 0) += mR2xN2.Dot(mInvI2_R2xN1);144inv_effective_mass(1, 1) += mp2->GetInverseMass() + mR2xN2.Dot(mInvI2_R2xN2);145}146else147{148JPH_IF_DEBUG(mInvI2_R2xN1 = Vec3::sNaN();)149JPH_IF_DEBUG(mInvI2_R2xN2 = Vec3::sNaN();)150}151152if (!mEffectiveMass.SetInversed(inv_effective_mass))153Deactivate();154}155156/// Deactivate this constraint157inline void Deactivate()158{159mEffectiveMass.SetZero();160mTotalLambda.SetZero();161}162163/// Check if constraint is active164inline bool IsActive() const165{166return !mEffectiveMass.IsZero();167}168169/// Must be called from the WarmStartVelocityConstraint call to apply the previous frame's impulses170/// All input vectors are in world space171inline void WarmStart(Body &ioBody1, Body &ioBody2, Vec3Arg inN1, Vec3Arg inN2, float inWarmStartImpulseRatio)172{173mTotalLambda *= inWarmStartImpulseRatio;174ApplyVelocityStep(ioBody1, ioBody2, inN1, inN2, mTotalLambda);175}176177/// Iteratively update the velocity constraint. Makes sure d/dt C(...) = 0, where C is the constraint equation.178/// All input vectors are in world space179inline bool SolveVelocityConstraint(Body &ioBody1, Body &ioBody2, Vec3Arg inN1, Vec3Arg inN2)180{181Vec2 lambda;182CalculateLagrangeMultiplier(ioBody1, ioBody2, inN1, inN2, lambda);183184// Store accumulated lambda185mTotalLambda += lambda;186187return ApplyVelocityStep(ioBody1, ioBody2, inN1, inN2, lambda);188}189190/// Iteratively update the position constraint. Makes sure C(...) = 0.191/// All input vectors are in world space192inline bool SolvePositionConstraint(Body &ioBody1, Body &ioBody2, Vec3Arg inU, Vec3Arg inN1, Vec3Arg inN2, float inBaumgarte) const193{194Vec2 c;195c[0] = inU.Dot(inN1);196c[1] = inU.Dot(inN2);197if (!c.IsZero())198{199// Calculate lagrange multiplier (lambda) for Baumgarte stabilization:200//201// lambda = -K^-1 * beta / dt * C202//203// We should divide by inDeltaTime, but we should multiply by inDeltaTime in the Euler step below so they're cancelled out204Vec2 lambda = -inBaumgarte * (mEffectiveMass * c);205206// Directly integrate velocity change for one time step207//208// Euler velocity integration:209// dv = M^-1 P210//211// Impulse:212// P = J^T lambda213//214// Euler position integration:215// x' = x + dv * dt216//217// Note we don't accumulate velocities for the stabilization. This is using the approach described in 'Modeling and218// Solving Constraints' by Erin Catto presented at GDC 2007. On slide 78 it is suggested to split up the Baumgarte219// stabilization for positional drift so that it does not actually add to the momentum. We combine an Euler velocity220// integrate + a position integrate and then discard the velocity change.221Vec3 impulse = inN1 * lambda[0] + inN2 * lambda[1];222if (ioBody1.IsDynamic())223{224ioBody1.SubPositionStep(ioBody1.GetMotionProperties()->GetInverseMass() * impulse);225ioBody1.SubRotationStep(mInvI1_R1PlusUxN1 * lambda[0] + mInvI1_R1PlusUxN2 * lambda[1]);226}227if (ioBody2.IsDynamic())228{229ioBody2.AddPositionStep(ioBody2.GetMotionProperties()->GetInverseMass() * impulse);230ioBody2.AddRotationStep(mInvI2_R2xN1 * lambda[0] + mInvI2_R2xN2 * lambda[1]);231}232return true;233}234235return false;236}237238/// Override total lagrange multiplier, can be used to set the initial value for warm starting239inline void SetTotalLambda(const Vec2 &inLambda)240{241mTotalLambda = inLambda;242}243244/// Return lagrange multiplier245inline const Vec2 & GetTotalLambda() const246{247return mTotalLambda;248}249250/// Save state of this constraint part251void SaveState(StateRecorder &inStream) const252{253inStream.Write(mTotalLambda);254}255256/// Restore state of this constraint part257void RestoreState(StateRecorder &inStream)258{259inStream.Read(mTotalLambda);260}261262private:263Vec3 mR1PlusUxN1;264Vec3 mR1PlusUxN2;265Vec3 mR2xN1;266Vec3 mR2xN2;267Vec3 mInvI1_R1PlusUxN1;268Vec3 mInvI1_R1PlusUxN2;269Vec3 mInvI2_R2xN1;270Vec3 mInvI2_R2xN2;271Mat22 mEffectiveMass;272Vec2 mTotalLambda { Vec2::sZero() };273};274275JPH_NAMESPACE_END276277278