AxisConstraintPart.h 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682
  1. // Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
  2. // SPDX-FileCopyrightText: 2021 Jorrit Rouwe
  3. // SPDX-License-Identifier: MIT
  4. #pragma once
  5. #include <Jolt/Physics/Body/Body.h>
  6. #include <Jolt/Physics/Constraints/ConstraintPart/SpringPart.h>
  7. #include <Jolt/Physics/Constraints/SpringSettings.h>
  8. #include <Jolt/Physics/StateRecorder.h>
  9. #include <Jolt/Physics/DeterminismLog.h>
  10. JPH_NAMESPACE_BEGIN
  11. /// Constraint that constrains motion along 1 axis
  12. ///
  13. /// @see "Constraints Derivation for Rigid Body Simulation in 3D" - Daniel Chappuis, section 2.1.1
  14. /// (we're not using the approximation of eq 27 but instead add the U term as in eq 55)
  15. ///
  16. /// Constraint equation (eq 25):
  17. ///
  18. /// \f[C = (p_2 - p_1) \cdot n\f]
  19. ///
  20. /// Jacobian (eq 28):
  21. ///
  22. /// \f[J = \begin{bmatrix} -n^T & (-(r_1 + u) \times n)^T & n^T & (r_2 \times n)^T \end{bmatrix}\f]
  23. ///
  24. /// Used terms (here and below, everything in world space):\n
  25. /// n = constraint axis (normalized).\n
  26. /// p1, p2 = constraint points.\n
  27. /// r1 = p1 - x1.\n
  28. /// r2 = p2 - x2.\n
  29. /// u = x2 + r2 - x1 - r1 = p2 - p1.\n
  30. /// x1, x2 = center of mass for the bodies.\n
  31. /// v = [v1, w1, v2, w2].\n
  32. /// v1, v2 = linear velocity of body 1 and 2.\n
  33. /// w1, w2 = angular velocity of body 1 and 2.\n
  34. /// M = mass matrix, a diagonal matrix of the mass and inertia with diagonal [m1, I1, m2, I2].\n
  35. /// \f$K^{-1} = \left( J M^{-1} J^T \right)^{-1}\f$ = effective mass.\n
  36. /// b = velocity bias.\n
  37. /// \f$\beta\f$ = baumgarte constant.
  38. class AxisConstraintPart
  39. {
  40. /// Internal helper function to update velocities of bodies after Lagrange multiplier is calculated
  41. template <EMotionType Type1, EMotionType Type2>
  42. JPH_INLINE bool ApplyVelocityStep(MotionProperties *ioMotionProperties1, float inInvMass1, MotionProperties *ioMotionProperties2, float inInvMass2, Vec3Arg inWorldSpaceAxis, float inLambda) const
  43. {
  44. // Apply impulse if delta is not zero
  45. if (inLambda != 0.0f)
  46. {
  47. // Calculate velocity change due to constraint
  48. //
  49. // Impulse:
  50. // P = J^T lambda
  51. //
  52. // Euler velocity integration:
  53. // v' = v + M^-1 P
  54. if constexpr (Type1 == EMotionType::Dynamic)
  55. {
  56. ioMotionProperties1->SubLinearVelocityStep((inLambda * inInvMass1) * inWorldSpaceAxis);
  57. ioMotionProperties1->SubAngularVelocityStep(inLambda * Vec3::sLoadFloat3Unsafe(mInvI1_R1PlusUxAxis));
  58. }
  59. if constexpr (Type2 == EMotionType::Dynamic)
  60. {
  61. ioMotionProperties2->AddLinearVelocityStep((inLambda * inInvMass2) * inWorldSpaceAxis);
  62. ioMotionProperties2->AddAngularVelocityStep(inLambda * Vec3::sLoadFloat3Unsafe(mInvI2_R2xAxis));
  63. }
  64. return true;
  65. }
  66. return false;
  67. }
  68. /// Internal helper function to calculate the inverse effective mass
  69. template <EMotionType Type1, EMotionType Type2>
  70. JPH_INLINE float TemplatedCalculateInverseEffectiveMass(float inInvMass1, Mat44Arg inInvI1, Vec3Arg inR1PlusU, float inInvMass2, Mat44Arg inInvI2, Vec3Arg inR2, Vec3Arg inWorldSpaceAxis)
  71. {
  72. JPH_ASSERT(inWorldSpaceAxis.IsNormalized(1.0e-5f));
  73. // Calculate properties used below
  74. Vec3 r1_plus_u_x_axis;
  75. if constexpr (Type1 != EMotionType::Static)
  76. {
  77. r1_plus_u_x_axis = inR1PlusU.Cross(inWorldSpaceAxis);
  78. r1_plus_u_x_axis.StoreFloat3(&mR1PlusUxAxis);
  79. }
  80. else
  81. {
  82. #ifdef _DEBUG
  83. Vec3::sNaN().StoreFloat3(&mR1PlusUxAxis);
  84. #endif
  85. }
  86. Vec3 r2_x_axis;
  87. if constexpr (Type2 != EMotionType::Static)
  88. {
  89. r2_x_axis = inR2.Cross(inWorldSpaceAxis);
  90. r2_x_axis.StoreFloat3(&mR2xAxis);
  91. }
  92. else
  93. {
  94. #ifdef _DEBUG
  95. Vec3::sNaN().StoreFloat3(&mR2xAxis);
  96. #endif
  97. }
  98. // Calculate inverse effective mass: K = J M^-1 J^T
  99. float inv_effective_mass;
  100. if constexpr (Type1 == EMotionType::Dynamic)
  101. {
  102. Vec3 invi1_r1_plus_u_x_axis = inInvI1.Multiply3x3(r1_plus_u_x_axis);
  103. invi1_r1_plus_u_x_axis.StoreFloat3(&mInvI1_R1PlusUxAxis);
  104. inv_effective_mass = inInvMass1 + invi1_r1_plus_u_x_axis.Dot(r1_plus_u_x_axis);
  105. }
  106. else
  107. {
  108. (void)r1_plus_u_x_axis; // Fix compiler warning: Not using this (it's not calculated either)
  109. JPH_IF_DEBUG(Vec3::sNaN().StoreFloat3(&mInvI1_R1PlusUxAxis);)
  110. inv_effective_mass = 0.0f;
  111. }
  112. if constexpr (Type2 == EMotionType::Dynamic)
  113. {
  114. Vec3 invi2_r2_x_axis = inInvI2.Multiply3x3(r2_x_axis);
  115. invi2_r2_x_axis.StoreFloat3(&mInvI2_R2xAxis);
  116. inv_effective_mass += inInvMass2 + invi2_r2_x_axis.Dot(r2_x_axis);
  117. }
  118. else
  119. {
  120. (void)r2_x_axis; // Fix compiler warning: Not using this (it's not calculated either)
  121. JPH_IF_DEBUG(Vec3::sNaN().StoreFloat3(&mInvI2_R2xAxis);)
  122. }
  123. return inv_effective_mass;
  124. }
  125. /// Internal helper function to calculate the inverse effective mass
  126. JPH_INLINE float CalculateInverseEffectiveMass(const Body &inBody1, Vec3Arg inR1PlusU, const Body &inBody2, Vec3Arg inR2, Vec3Arg inWorldSpaceAxis)
  127. {
  128. // Dispatch to the correct templated form
  129. switch (inBody1.GetMotionType())
  130. {
  131. case EMotionType::Dynamic:
  132. {
  133. const MotionProperties *mp1 = inBody1.GetMotionPropertiesUnchecked();
  134. float inv_m1 = mp1->GetInverseMass();
  135. Mat44 inv_i1 = inBody1.GetInverseInertia();
  136. switch (inBody2.GetMotionType())
  137. {
  138. case EMotionType::Dynamic:
  139. return TemplatedCalculateInverseEffectiveMass<EMotionType::Dynamic, EMotionType::Dynamic>(inv_m1, inv_i1, inR1PlusU, inBody2.GetMotionPropertiesUnchecked()->GetInverseMass(), inBody2.GetInverseInertia(), inR2, inWorldSpaceAxis);
  140. case EMotionType::Kinematic:
  141. return TemplatedCalculateInverseEffectiveMass<EMotionType::Dynamic, EMotionType::Kinematic>(inv_m1, inv_i1, inR1PlusU, 0 /* Will not be used */, Mat44() /* Will not be used */, inR2, inWorldSpaceAxis);
  142. case EMotionType::Static:
  143. return TemplatedCalculateInverseEffectiveMass<EMotionType::Dynamic, EMotionType::Static>(inv_m1, inv_i1, inR1PlusU, 0 /* Will not be used */, Mat44() /* Will not be used */, inR2, inWorldSpaceAxis);
  144. default:
  145. break;
  146. }
  147. break;
  148. }
  149. case EMotionType::Kinematic:
  150. JPH_ASSERT(inBody2.IsDynamic());
  151. return TemplatedCalculateInverseEffectiveMass<EMotionType::Kinematic, EMotionType::Dynamic>(0 /* Will not be used */, Mat44() /* Will not be used */, inR1PlusU, inBody2.GetMotionPropertiesUnchecked()->GetInverseMass(), inBody2.GetInverseInertia(), inR2, inWorldSpaceAxis);
  152. case EMotionType::Static:
  153. JPH_ASSERT(inBody2.IsDynamic());
  154. return TemplatedCalculateInverseEffectiveMass<EMotionType::Static, EMotionType::Dynamic>(0 /* Will not be used */, Mat44() /* Will not be used */, inR1PlusU, inBody2.GetMotionPropertiesUnchecked()->GetInverseMass(), inBody2.GetInverseInertia(), inR2, inWorldSpaceAxis);
  155. default:
  156. break;
  157. }
  158. JPH_ASSERT(false);
  159. return 0.0f;
  160. }
  161. /// Internal helper function to calculate the inverse effective mass, version that supports mass scaling
  162. JPH_INLINE float CalculateInverseEffectiveMassWithMassOverride(const Body &inBody1, float inInvMass1, float inInvInertiaScale1, Vec3Arg inR1PlusU, const Body &inBody2, float inInvMass2, float inInvInertiaScale2, Vec3Arg inR2, Vec3Arg inWorldSpaceAxis)
  163. {
  164. // Dispatch to the correct templated form
  165. switch (inBody1.GetMotionType())
  166. {
  167. case EMotionType::Dynamic:
  168. {
  169. Mat44 inv_i1 = inInvInertiaScale1 * inBody1.GetInverseInertia();
  170. switch (inBody2.GetMotionType())
  171. {
  172. case EMotionType::Dynamic:
  173. return TemplatedCalculateInverseEffectiveMass<EMotionType::Dynamic, EMotionType::Dynamic>(inInvMass1, inv_i1, inR1PlusU, inInvMass2, inInvInertiaScale2 * inBody2.GetInverseInertia(), inR2, inWorldSpaceAxis);
  174. case EMotionType::Kinematic:
  175. return TemplatedCalculateInverseEffectiveMass<EMotionType::Dynamic, EMotionType::Kinematic>(inInvMass1, inv_i1, inR1PlusU, 0 /* Will not be used */, Mat44() /* Will not be used */, inR2, inWorldSpaceAxis);
  176. case EMotionType::Static:
  177. return TemplatedCalculateInverseEffectiveMass<EMotionType::Dynamic, EMotionType::Static>(inInvMass1, inv_i1, inR1PlusU, 0 /* Will not be used */, Mat44() /* Will not be used */, inR2, inWorldSpaceAxis);
  178. default:
  179. break;
  180. }
  181. break;
  182. }
  183. case EMotionType::Kinematic:
  184. JPH_ASSERT(inBody2.IsDynamic());
  185. return TemplatedCalculateInverseEffectiveMass<EMotionType::Kinematic, EMotionType::Dynamic>(0 /* Will not be used */, Mat44() /* Will not be used */, inR1PlusU, inInvMass2, inInvInertiaScale2 * inBody2.GetInverseInertia(), inR2, inWorldSpaceAxis);
  186. case EMotionType::Static:
  187. JPH_ASSERT(inBody2.IsDynamic());
  188. return TemplatedCalculateInverseEffectiveMass<EMotionType::Static, EMotionType::Dynamic>(0 /* Will not be used */, Mat44() /* Will not be used */, inR1PlusU, inInvMass2, inInvInertiaScale2 * inBody2.GetInverseInertia(), inR2, inWorldSpaceAxis);
  189. default:
  190. break;
  191. }
  192. JPH_ASSERT(false);
  193. return 0.0f;
  194. }
  195. public:
  196. /// Templated form of CalculateConstraintProperties with the motion types baked in
  197. template <EMotionType Type1, EMotionType Type2>
  198. JPH_INLINE void TemplatedCalculateConstraintProperties(float inInvMass1, Mat44Arg inInvI1, Vec3Arg inR1PlusU, float inInvMass2, Mat44Arg inInvI2, Vec3Arg inR2, Vec3Arg inWorldSpaceAxis, float inBias = 0.0f)
  199. {
  200. float inv_effective_mass = TemplatedCalculateInverseEffectiveMass<Type1, Type2>(inInvMass1, inInvI1, inR1PlusU, inInvMass2, inInvI2, inR2, inWorldSpaceAxis);
  201. if (inv_effective_mass == 0.0f)
  202. Deactivate();
  203. else
  204. {
  205. mEffectiveMass = 1.0f / inv_effective_mass;
  206. mSpringPart.CalculateSpringPropertiesWithBias(inBias);
  207. }
  208. JPH_DET_LOG("TemplatedCalculateConstraintProperties: invM1: " << inInvMass1 << " invI1: " << inInvI1 << " r1PlusU: " << inR1PlusU << " invM2: " << inInvMass2 << " invI2: " << inInvI2 << " r2: " << inR2 << " bias: " << inBias << " r1PlusUxAxis: " << mR1PlusUxAxis << " r2xAxis: " << mR2xAxis << " invI1_R1PlusUxAxis: " << mInvI1_R1PlusUxAxis << " invI2_R2xAxis: " << mInvI2_R2xAxis << " effectiveMass: " << mEffectiveMass << " totalLambda: " << mTotalLambda);
  209. }
  210. /// Calculate properties used during the functions below
  211. /// @param inBody1 The first body that this constraint is attached to
  212. /// @param inBody2 The second body that this constraint is attached to
  213. /// @param inR1PlusU See equations above (r1 + u)
  214. /// @param inR2 See equations above (r2)
  215. /// @param inWorldSpaceAxis Axis along which the constraint acts (normalized, pointing from body 1 to 2)
  216. /// @param inBias Bias term (b) for the constraint impulse: lambda = J v + b
  217. inline void CalculateConstraintProperties(const Body &inBody1, Vec3Arg inR1PlusU, const Body &inBody2, Vec3Arg inR2, Vec3Arg inWorldSpaceAxis, float inBias = 0.0f)
  218. {
  219. float inv_effective_mass = CalculateInverseEffectiveMass(inBody1, inR1PlusU, inBody2, inR2, inWorldSpaceAxis);
  220. if (inv_effective_mass == 0.0f)
  221. Deactivate();
  222. else
  223. {
  224. mEffectiveMass = 1.0f / inv_effective_mass;
  225. mSpringPart.CalculateSpringPropertiesWithBias(inBias);
  226. }
  227. }
  228. /// Calculate properties used during the functions below, version that supports mass scaling
  229. /// @param inBody1 The first body that this constraint is attached to
  230. /// @param inBody2 The second body that this constraint is attached to
  231. /// @param inInvMass1 The inverse mass of body 1 (only used when body 1 is dynamic)
  232. /// @param inInvMass2 The inverse mass of body 2 (only used when body 2 is dynamic)
  233. /// @param inInvInertiaScale1 Scale factor for the inverse inertia of body 1
  234. /// @param inInvInertiaScale2 Scale factor for the inverse inertia of body 2
  235. /// @param inR1PlusU See equations above (r1 + u)
  236. /// @param inR2 See equations above (r2)
  237. /// @param inWorldSpaceAxis Axis along which the constraint acts (normalized, pointing from body 1 to 2)
  238. /// @param inBias Bias term (b) for the constraint impulse: lambda = J v + b
  239. inline void CalculateConstraintPropertiesWithMassOverride(const Body &inBody1, float inInvMass1, float inInvInertiaScale1, Vec3Arg inR1PlusU, const Body &inBody2, float inInvMass2, float inInvInertiaScale2, Vec3Arg inR2, Vec3Arg inWorldSpaceAxis, float inBias = 0.0f)
  240. {
  241. float inv_effective_mass = CalculateInverseEffectiveMassWithMassOverride(inBody1, inInvMass1, inInvInertiaScale1, inR1PlusU, inBody2, inInvMass2, inInvInertiaScale2, inR2, inWorldSpaceAxis);
  242. if (inv_effective_mass == 0.0f)
  243. Deactivate();
  244. else
  245. {
  246. mEffectiveMass = 1.0f / inv_effective_mass;
  247. mSpringPart.CalculateSpringPropertiesWithBias(inBias);
  248. }
  249. }
  250. /// Calculate properties used during the functions below
  251. /// @param inDeltaTime Time step
  252. /// @param inBody1 The first body that this constraint is attached to
  253. /// @param inBody2 The second body that this constraint is attached to
  254. /// @param inR1PlusU See equations above (r1 + u)
  255. /// @param inR2 See equations above (r2)
  256. /// @param inWorldSpaceAxis Axis along which the constraint acts (normalized, pointing from body 1 to 2)
  257. /// @param inBias Bias term (b) for the constraint impulse: lambda = J v + b
  258. /// @param inC Value of the constraint equation (C).
  259. /// @param inFrequency Oscillation frequency (Hz).
  260. /// @param inDamping Damping factor (0 = no damping, 1 = critical damping).
  261. inline void CalculateConstraintPropertiesWithFrequencyAndDamping(float inDeltaTime, const Body &inBody1, Vec3Arg inR1PlusU, const Body &inBody2, Vec3Arg inR2, Vec3Arg inWorldSpaceAxis, float inBias, float inC, float inFrequency, float inDamping)
  262. {
  263. float inv_effective_mass = CalculateInverseEffectiveMass(inBody1, inR1PlusU, inBody2, inR2, inWorldSpaceAxis);
  264. if (inv_effective_mass == 0.0f)
  265. Deactivate();
  266. else
  267. mSpringPart.CalculateSpringPropertiesWithFrequencyAndDamping(inDeltaTime, inv_effective_mass, inBias, inC, inFrequency, inDamping, mEffectiveMass);
  268. }
  269. /// Calculate properties used during the functions below
  270. /// @param inDeltaTime Time step
  271. /// @param inBody1 The first body that this constraint is attached to
  272. /// @param inBody2 The second body that this constraint is attached to
  273. /// @param inR1PlusU See equations above (r1 + u)
  274. /// @param inR2 See equations above (r2)
  275. /// @param inWorldSpaceAxis Axis along which the constraint acts (normalized, pointing from body 1 to 2)
  276. /// @param inBias Bias term (b) for the constraint impulse: lambda = J v + b
  277. /// @param inC Value of the constraint equation (C).
  278. /// @param inStiffness Spring stiffness k.
  279. /// @param inDamping Spring damping coefficient c.
  280. inline void CalculateConstraintPropertiesWithStiffnessAndDamping(float inDeltaTime, const Body &inBody1, Vec3Arg inR1PlusU, const Body &inBody2, Vec3Arg inR2, Vec3Arg inWorldSpaceAxis, float inBias, float inC, float inStiffness, float inDamping)
  281. {
  282. float inv_effective_mass = CalculateInverseEffectiveMass(inBody1, inR1PlusU, inBody2, inR2, inWorldSpaceAxis);
  283. if (inv_effective_mass == 0.0f)
  284. Deactivate();
  285. else
  286. mSpringPart.CalculateSpringPropertiesWithStiffnessAndDamping(inDeltaTime, inv_effective_mass, inBias, inC, inStiffness, inDamping, mEffectiveMass);
  287. }
  288. /// Selects one of the above functions based on the spring settings
  289. inline void CalculateConstraintPropertiesWithSettings(float inDeltaTime, const Body &inBody1, Vec3Arg inR1PlusU, const Body &inBody2, Vec3Arg inR2, Vec3Arg inWorldSpaceAxis, float inBias, float inC, const SpringSettings &inSpringSettings)
  290. {
  291. float inv_effective_mass = CalculateInverseEffectiveMass(inBody1, inR1PlusU, inBody2, inR2, inWorldSpaceAxis);
  292. if (inv_effective_mass == 0.0f)
  293. Deactivate();
  294. else if (inSpringSettings.mMode == ESpringMode::FrequencyAndDamping)
  295. mSpringPart.CalculateSpringPropertiesWithFrequencyAndDamping(inDeltaTime, inv_effective_mass, inBias, inC, inSpringSettings.mFrequency, inSpringSettings.mDamping, mEffectiveMass);
  296. else
  297. mSpringPart.CalculateSpringPropertiesWithStiffnessAndDamping(inDeltaTime, inv_effective_mass, inBias, inC, inSpringSettings.mStiffness, inSpringSettings.mDamping, mEffectiveMass);
  298. }
  299. /// Deactivate this constraint
  300. inline void Deactivate()
  301. {
  302. mEffectiveMass = 0.0f;
  303. mTotalLambda = 0.0f;
  304. }
  305. /// Check if constraint is active
  306. inline bool IsActive() const
  307. {
  308. return mEffectiveMass != 0.0f;
  309. }
  310. /// Templated form of WarmStart with the motion types baked in
  311. template <EMotionType Type1, EMotionType Type2>
  312. inline void TemplatedWarmStart(MotionProperties *ioMotionProperties1, float inInvMass1, MotionProperties *ioMotionProperties2, float inInvMass2, Vec3Arg inWorldSpaceAxis, float inWarmStartImpulseRatio)
  313. {
  314. mTotalLambda *= inWarmStartImpulseRatio;
  315. ApplyVelocityStep<Type1, Type2>(ioMotionProperties1, inInvMass1, ioMotionProperties2, inInvMass2, inWorldSpaceAxis, mTotalLambda);
  316. }
  317. /// Must be called from the WarmStartVelocityConstraint call to apply the previous frame's impulses
  318. /// @param ioBody1 The first body that this constraint is attached to
  319. /// @param ioBody2 The second body that this constraint is attached to
  320. /// @param inWorldSpaceAxis Axis along which the constraint acts (normalized)
  321. /// @param inWarmStartImpulseRatio Ratio of new step to old time step (dt_new / dt_old) for scaling the lagrange multiplier of the previous frame
  322. inline void WarmStart(Body &ioBody1, Body &ioBody2, Vec3Arg inWorldSpaceAxis, float inWarmStartImpulseRatio)
  323. {
  324. EMotionType motion_type1 = ioBody1.GetMotionType();
  325. MotionProperties *motion_properties1 = ioBody1.GetMotionPropertiesUnchecked();
  326. EMotionType motion_type2 = ioBody2.GetMotionType();
  327. MotionProperties *motion_properties2 = ioBody2.GetMotionPropertiesUnchecked();
  328. // Dispatch to the correct templated form
  329. // Note: Warm starting doesn't differentiate between kinematic/static bodies so we handle both as static bodies
  330. if (motion_type1 == EMotionType::Dynamic)
  331. {
  332. if (motion_type2 == EMotionType::Dynamic)
  333. TemplatedWarmStart<EMotionType::Dynamic, EMotionType::Dynamic>(motion_properties1, motion_properties1->GetInverseMass(), motion_properties2, motion_properties2->GetInverseMass(), inWorldSpaceAxis, inWarmStartImpulseRatio);
  334. else
  335. TemplatedWarmStart<EMotionType::Dynamic, EMotionType::Static>(motion_properties1, motion_properties1->GetInverseMass(), motion_properties2, 0.0f /* Unused */, inWorldSpaceAxis, inWarmStartImpulseRatio);
  336. }
  337. else
  338. {
  339. JPH_ASSERT(motion_type2 == EMotionType::Dynamic);
  340. TemplatedWarmStart<EMotionType::Static, EMotionType::Dynamic>(motion_properties1, 0.0f /* Unused */, motion_properties2, motion_properties2->GetInverseMass(), inWorldSpaceAxis, inWarmStartImpulseRatio);
  341. }
  342. }
  343. /// Templated form of SolveVelocityConstraint with the motion types baked in, part 1: get the total lambda
  344. template <EMotionType Type1, EMotionType Type2>
  345. JPH_INLINE float TemplatedSolveVelocityConstraintGetTotalLambda(const MotionProperties *ioMotionProperties1, const MotionProperties *ioMotionProperties2, Vec3Arg inWorldSpaceAxis) const
  346. {
  347. // Calculate jacobian multiplied by linear velocity
  348. float jv;
  349. if constexpr (Type1 != EMotionType::Static && Type2 != EMotionType::Static)
  350. jv = inWorldSpaceAxis.Dot(ioMotionProperties1->GetLinearVelocity() - ioMotionProperties2->GetLinearVelocity());
  351. else if constexpr (Type1 != EMotionType::Static)
  352. jv = inWorldSpaceAxis.Dot(ioMotionProperties1->GetLinearVelocity());
  353. else if constexpr (Type2 != EMotionType::Static)
  354. jv = inWorldSpaceAxis.Dot(-ioMotionProperties2->GetLinearVelocity());
  355. else
  356. JPH_ASSERT(false); // Static vs static is nonsensical!
  357. // Calculate jacobian multiplied by angular velocity
  358. if constexpr (Type1 != EMotionType::Static)
  359. jv += Vec3::sLoadFloat3Unsafe(mR1PlusUxAxis).Dot(ioMotionProperties1->GetAngularVelocity());
  360. if constexpr (Type2 != EMotionType::Static)
  361. jv -= Vec3::sLoadFloat3Unsafe(mR2xAxis).Dot(ioMotionProperties2->GetAngularVelocity());
  362. // Lagrange multiplier is:
  363. //
  364. // lambda = -K^-1 (J v + b)
  365. float lambda = mEffectiveMass * (jv - mSpringPart.GetBias(mTotalLambda));
  366. // Return the total accumulated lambda
  367. return mTotalLambda + lambda;
  368. }
  369. /// Templated form of SolveVelocityConstraint with the motion types baked in, part 2: apply new lambda
  370. template <EMotionType Type1, EMotionType Type2>
  371. JPH_INLINE bool TemplatedSolveVelocityConstraintApplyLambda(MotionProperties *ioMotionProperties1, float inInvMass1, MotionProperties *ioMotionProperties2, float inInvMass2, Vec3Arg inWorldSpaceAxis, float inTotalLambda)
  372. {
  373. float delta_lambda = inTotalLambda - mTotalLambda; // Calculate change in lambda
  374. mTotalLambda = inTotalLambda; // Store accumulated impulse
  375. return ApplyVelocityStep<Type1, Type2>(ioMotionProperties1, inInvMass1, ioMotionProperties2, inInvMass2, inWorldSpaceAxis, delta_lambda);
  376. }
  377. /// Templated form of SolveVelocityConstraint with the motion types baked in
  378. template <EMotionType Type1, EMotionType Type2>
  379. inline bool TemplatedSolveVelocityConstraint(MotionProperties *ioMotionProperties1, float inInvMass1, MotionProperties *ioMotionProperties2, float inInvMass2, Vec3Arg inWorldSpaceAxis, float inMinLambda, float inMaxLambda)
  380. {
  381. float total_lambda = TemplatedSolveVelocityConstraintGetTotalLambda<Type1, Type2>(ioMotionProperties1, ioMotionProperties2, inWorldSpaceAxis);
  382. // Clamp impulse to specified range
  383. total_lambda = Clamp(total_lambda, inMinLambda, inMaxLambda);
  384. return TemplatedSolveVelocityConstraintApplyLambda<Type1, Type2>(ioMotionProperties1, inInvMass1, ioMotionProperties2, inInvMass2, inWorldSpaceAxis, total_lambda);
  385. }
  386. /// Iteratively update the velocity constraint. Makes sure d/dt C(...) = 0, where C is the constraint equation.
  387. /// @param ioBody1 The first body that this constraint is attached to
  388. /// @param ioBody2 The second body that this constraint is attached to
  389. /// @param inWorldSpaceAxis Axis along which the constraint acts (normalized)
  390. /// @param inMinLambda Minimum value of constraint impulse to apply (N s)
  391. /// @param inMaxLambda Maximum value of constraint impulse to apply (N s)
  392. inline bool SolveVelocityConstraint(Body &ioBody1, Body &ioBody2, Vec3Arg inWorldSpaceAxis, float inMinLambda, float inMaxLambda)
  393. {
  394. EMotionType motion_type1 = ioBody1.GetMotionType();
  395. MotionProperties *motion_properties1 = ioBody1.GetMotionPropertiesUnchecked();
  396. EMotionType motion_type2 = ioBody2.GetMotionType();
  397. MotionProperties *motion_properties2 = ioBody2.GetMotionPropertiesUnchecked();
  398. // Dispatch to the correct templated form
  399. switch (motion_type1)
  400. {
  401. case EMotionType::Dynamic:
  402. switch (motion_type2)
  403. {
  404. case EMotionType::Dynamic:
  405. return TemplatedSolveVelocityConstraint<EMotionType::Dynamic, EMotionType::Dynamic>(motion_properties1, motion_properties1->GetInverseMass(), motion_properties2, motion_properties2->GetInverseMass(), inWorldSpaceAxis, inMinLambda, inMaxLambda);
  406. case EMotionType::Kinematic:
  407. return TemplatedSolveVelocityConstraint<EMotionType::Dynamic, EMotionType::Kinematic>(motion_properties1, motion_properties1->GetInverseMass(), motion_properties2, 0.0f /* Unused */, inWorldSpaceAxis, inMinLambda, inMaxLambda);
  408. case EMotionType::Static:
  409. return TemplatedSolveVelocityConstraint<EMotionType::Dynamic, EMotionType::Static>(motion_properties1, motion_properties1->GetInverseMass(), motion_properties2, 0.0f /* Unused */, inWorldSpaceAxis, inMinLambda, inMaxLambda);
  410. default:
  411. JPH_ASSERT(false);
  412. break;
  413. }
  414. break;
  415. case EMotionType::Kinematic:
  416. JPH_ASSERT(motion_type2 == EMotionType::Dynamic);
  417. return TemplatedSolveVelocityConstraint<EMotionType::Kinematic, EMotionType::Dynamic>(motion_properties1, 0.0f /* Unused */, motion_properties2, motion_properties2->GetInverseMass(), inWorldSpaceAxis, inMinLambda, inMaxLambda);
  418. case EMotionType::Static:
  419. JPH_ASSERT(motion_type2 == EMotionType::Dynamic);
  420. return TemplatedSolveVelocityConstraint<EMotionType::Static, EMotionType::Dynamic>(motion_properties1, 0.0f /* Unused */, motion_properties2, motion_properties2->GetInverseMass(), inWorldSpaceAxis, inMinLambda, inMaxLambda);
  421. default:
  422. JPH_ASSERT(false);
  423. break;
  424. }
  425. return false;
  426. }
  427. /// Iteratively update the velocity constraint. Makes sure d/dt C(...) = 0, where C is the constraint equation.
  428. /// @param ioBody1 The first body that this constraint is attached to
  429. /// @param ioBody2 The second body that this constraint is attached to
  430. /// @param inInvMass1 The inverse mass of body 1 (only used when body 1 is dynamic)
  431. /// @param inInvMass2 The inverse mass of body 2 (only used when body 2 is dynamic)
  432. /// @param inWorldSpaceAxis Axis along which the constraint acts (normalized)
  433. /// @param inMinLambda Minimum value of constraint impulse to apply (N s)
  434. /// @param inMaxLambda Maximum value of constraint impulse to apply (N s)
  435. inline bool SolveVelocityConstraintWithMassOverride(Body &ioBody1, float inInvMass1, Body &ioBody2, float inInvMass2, Vec3Arg inWorldSpaceAxis, float inMinLambda, float inMaxLambda)
  436. {
  437. EMotionType motion_type1 = ioBody1.GetMotionType();
  438. MotionProperties *motion_properties1 = ioBody1.GetMotionPropertiesUnchecked();
  439. EMotionType motion_type2 = ioBody2.GetMotionType();
  440. MotionProperties *motion_properties2 = ioBody2.GetMotionPropertiesUnchecked();
  441. // Dispatch to the correct templated form
  442. switch (motion_type1)
  443. {
  444. case EMotionType::Dynamic:
  445. switch (motion_type2)
  446. {
  447. case EMotionType::Dynamic:
  448. return TemplatedSolveVelocityConstraint<EMotionType::Dynamic, EMotionType::Dynamic>(motion_properties1, inInvMass1, motion_properties2, inInvMass2, inWorldSpaceAxis, inMinLambda, inMaxLambda);
  449. case EMotionType::Kinematic:
  450. return TemplatedSolveVelocityConstraint<EMotionType::Dynamic, EMotionType::Kinematic>(motion_properties1, inInvMass1, motion_properties2, 0.0f /* Unused */, inWorldSpaceAxis, inMinLambda, inMaxLambda);
  451. case EMotionType::Static:
  452. return TemplatedSolveVelocityConstraint<EMotionType::Dynamic, EMotionType::Static>(motion_properties1, inInvMass1, motion_properties2, 0.0f /* Unused */, inWorldSpaceAxis, inMinLambda, inMaxLambda);
  453. default:
  454. JPH_ASSERT(false);
  455. break;
  456. }
  457. break;
  458. case EMotionType::Kinematic:
  459. JPH_ASSERT(motion_type2 == EMotionType::Dynamic);
  460. return TemplatedSolveVelocityConstraint<EMotionType::Kinematic, EMotionType::Dynamic>(motion_properties1, 0.0f /* Unused */, motion_properties2, inInvMass2, inWorldSpaceAxis, inMinLambda, inMaxLambda);
  461. case EMotionType::Static:
  462. JPH_ASSERT(motion_type2 == EMotionType::Dynamic);
  463. return TemplatedSolveVelocityConstraint<EMotionType::Static, EMotionType::Dynamic>(motion_properties1, 0.0f /* Unused */, motion_properties2, inInvMass2, inWorldSpaceAxis, inMinLambda, inMaxLambda);
  464. default:
  465. JPH_ASSERT(false);
  466. break;
  467. }
  468. return false;
  469. }
  470. /// Iteratively update the position constraint. Makes sure C(...) = 0.
  471. /// @param ioBody1 The first body that this constraint is attached to
  472. /// @param ioBody2 The second body that this constraint is attached to
  473. /// @param inWorldSpaceAxis Axis along which the constraint acts (normalized)
  474. /// @param inC Value of the constraint equation (C)
  475. /// @param inBaumgarte Baumgarte constant (fraction of the error to correct)
  476. inline bool SolvePositionConstraint(Body &ioBody1, Body &ioBody2, Vec3Arg inWorldSpaceAxis, float inC, float inBaumgarte) const
  477. {
  478. // Only apply position constraint when the constraint is hard, otherwise the velocity bias will fix the constraint
  479. if (inC != 0.0f && !mSpringPart.IsActive())
  480. {
  481. // Calculate lagrange multiplier (lambda) for Baumgarte stabilization:
  482. //
  483. // lambda = -K^-1 * beta / dt * C
  484. //
  485. // We should divide by inDeltaTime, but we should multiply by inDeltaTime in the Euler step below so they're cancelled out
  486. float lambda = -mEffectiveMass * inBaumgarte * inC;
  487. // Directly integrate velocity change for one time step
  488. //
  489. // Euler velocity integration:
  490. // dv = M^-1 P
  491. //
  492. // Impulse:
  493. // P = J^T lambda
  494. //
  495. // Euler position integration:
  496. // x' = x + dv * dt
  497. //
  498. // Note we don't accumulate velocities for the stabilization. This is using the approach described in 'Modeling and
  499. // Solving Constraints' by Erin Catto presented at GDC 2007. On slide 78 it is suggested to split up the Baumgarte
  500. // stabilization for positional drift so that it does not actually add to the momentum. We combine an Euler velocity
  501. // integrate + a position integrate and then discard the velocity change.
  502. if (ioBody1.IsDynamic())
  503. {
  504. ioBody1.SubPositionStep((lambda * ioBody1.GetMotionProperties()->GetInverseMass()) * inWorldSpaceAxis);
  505. ioBody1.SubRotationStep(lambda * Vec3::sLoadFloat3Unsafe(mInvI1_R1PlusUxAxis));
  506. }
  507. if (ioBody2.IsDynamic())
  508. {
  509. ioBody2.AddPositionStep((lambda * ioBody2.GetMotionProperties()->GetInverseMass()) * inWorldSpaceAxis);
  510. ioBody2.AddRotationStep(lambda * Vec3::sLoadFloat3Unsafe(mInvI2_R2xAxis));
  511. }
  512. return true;
  513. }
  514. return false;
  515. }
  516. /// Iteratively update the position constraint. Makes sure C(...) = 0.
  517. /// @param ioBody1 The first body that this constraint is attached to
  518. /// @param ioBody2 The second body that this constraint is attached to
  519. /// @param inInvMass1 The inverse mass of body 1 (only used when body 1 is dynamic)
  520. /// @param inInvMass2 The inverse mass of body 2 (only used when body 2 is dynamic)
  521. /// @param inWorldSpaceAxis Axis along which the constraint acts (normalized)
  522. /// @param inC Value of the constraint equation (C)
  523. /// @param inBaumgarte Baumgarte constant (fraction of the error to correct)
  524. inline bool SolvePositionConstraintWithMassOverride(Body &ioBody1, float inInvMass1, Body &ioBody2, float inInvMass2, Vec3Arg inWorldSpaceAxis, float inC, float inBaumgarte) const
  525. {
  526. // Only apply position constraint when the constraint is hard, otherwise the velocity bias will fix the constraint
  527. if (inC != 0.0f && !mSpringPart.IsActive())
  528. {
  529. // Calculate lagrange multiplier (lambda) for Baumgarte stabilization:
  530. //
  531. // lambda = -K^-1 * beta / dt * C
  532. //
  533. // We should divide by inDeltaTime, but we should multiply by inDeltaTime in the Euler step below so they're cancelled out
  534. float lambda = -mEffectiveMass * inBaumgarte * inC;
  535. // Directly integrate velocity change for one time step
  536. //
  537. // Euler velocity integration:
  538. // dv = M^-1 P
  539. //
  540. // Impulse:
  541. // P = J^T lambda
  542. //
  543. // Euler position integration:
  544. // x' = x + dv * dt
  545. //
  546. // Note we don't accumulate velocities for the stabilization. This is using the approach described in 'Modeling and
  547. // Solving Constraints' by Erin Catto presented at GDC 2007. On slide 78 it is suggested to split up the Baumgarte
  548. // stabilization for positional drift so that it does not actually add to the momentum. We combine an Euler velocity
  549. // integrate + a position integrate and then discard the velocity change.
  550. if (ioBody1.IsDynamic())
  551. {
  552. ioBody1.SubPositionStep((lambda * inInvMass1) * inWorldSpaceAxis);
  553. ioBody1.SubRotationStep(lambda * Vec3::sLoadFloat3Unsafe(mInvI1_R1PlusUxAxis));
  554. }
  555. if (ioBody2.IsDynamic())
  556. {
  557. ioBody2.AddPositionStep((lambda * inInvMass2) * inWorldSpaceAxis);
  558. ioBody2.AddRotationStep(lambda * Vec3::sLoadFloat3Unsafe(mInvI2_R2xAxis));
  559. }
  560. return true;
  561. }
  562. return false;
  563. }
  564. /// Override total lagrange multiplier, can be used to set the initial value for warm starting
  565. inline void SetTotalLambda(float inLambda)
  566. {
  567. mTotalLambda = inLambda;
  568. }
  569. /// Return lagrange multiplier
  570. inline float GetTotalLambda() const
  571. {
  572. return mTotalLambda;
  573. }
  574. /// Save state of this constraint part
  575. void SaveState(StateRecorder &inStream) const
  576. {
  577. inStream.Write(mTotalLambda);
  578. }
  579. /// Restore state of this constraint part
  580. void RestoreState(StateRecorder &inStream)
  581. {
  582. inStream.Read(mTotalLambda);
  583. }
  584. private:
  585. Float3 mR1PlusUxAxis;
  586. Float3 mR2xAxis;
  587. Float3 mInvI1_R1PlusUxAxis;
  588. Float3 mInvI2_R2xAxis;
  589. float mEffectiveMass = 0.0f;
  590. SpringPart mSpringPart;
  591. float mTotalLambda = 0.0f;
  592. };
  593. JPH_NAMESPACE_END