SwingTwistConstraintPart.h 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597
  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/Geometry/Ellipse.h>
  6. #include <Jolt/Physics/Constraints/ConstraintPart/RotationEulerConstraintPart.h>
  7. #include <Jolt/Physics/Constraints/ConstraintPart/AngleConstraintPart.h>
  8. JPH_NAMESPACE_BEGIN
  9. /// How the swing limit behaves
  10. enum class ESwingType : uint8
  11. {
  12. Cone, ///< 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.
  13. Pyramid, ///< Swing is limited by a pyramid shape, note that this pyramid starts to deform for larger swing angles.
  14. };
  15. /// Quaternion based constraint that decomposes the rotation in constraint space in swing and twist: q = q_swing * q_twist
  16. /// where q_swing.x = 0 and where q_twist.y = q_twist.z = 0
  17. ///
  18. /// - Rotation around the twist (x-axis) is within [inTwistMinAngle, inTwistMaxAngle].
  19. /// - Rotation around the swing axis (y and z axis) are limited to an ellipsoid in quaternion space formed by the equation:
  20. ///
  21. /// (q_swing.y / sin(inSwingYHalfAngle / 2))^2 + (q_swing.z / sin(inSwingZHalfAngle / 2))^2 <= 1
  22. ///
  23. /// Which roughly corresponds to an elliptic cone shape with major axis (inSwingYHalfAngle, inSwingZHalfAngle).
  24. ///
  25. /// In case inSwingYHalfAngle = 0, the rotation around Y will be constrained to 0 and the rotation around Z
  26. /// will be constrained between [-inSwingZHalfAngle, inSwingZHalfAngle]. Vice versa if inSwingZHalfAngle = 0.
  27. class SwingTwistConstraintPart
  28. {
  29. public:
  30. /// Override the swing type
  31. void SetSwingType(ESwingType inSwingType)
  32. {
  33. mSwingType = inSwingType;
  34. }
  35. /// Get the swing type for this part
  36. ESwingType GetSwingType() const
  37. {
  38. return mSwingType;
  39. }
  40. /// Set limits for this constraint (see description above for parameters)
  41. void SetLimits(float inTwistMinAngle, float inTwistMaxAngle, float inSwingYMinAngle, float inSwingYMaxAngle, float inSwingZMinAngle, float inSwingZMaxAngle)
  42. {
  43. constexpr float cLockedAngle = DegreesToRadians(0.5f);
  44. constexpr float cFreeAngle = DegreesToRadians(179.5f);
  45. // Assume sane input
  46. JPH_ASSERT(inTwistMinAngle <= inTwistMinAngle);
  47. JPH_ASSERT(inSwingYMinAngle <= inSwingYMaxAngle);
  48. JPH_ASSERT(inSwingZMinAngle <= inSwingZMaxAngle);
  49. JPH_ASSERT(inSwingYMinAngle >= -JPH_PI && inSwingYMaxAngle <= JPH_PI);
  50. JPH_ASSERT(inSwingZMinAngle >= -JPH_PI && inSwingZMaxAngle <= JPH_PI);
  51. // Calculate the sine and cosine of the half angles
  52. Vec4 half_twist = 0.5f * Vec4(inTwistMinAngle, inTwistMaxAngle, 0, 0);
  53. Vec4 twist_s, twist_c;
  54. half_twist.SinCos(twist_s, twist_c);
  55. Vec4 half_swing = 0.5f * Vec4(inSwingYMinAngle, inSwingYMaxAngle, inSwingZMinAngle, inSwingZMaxAngle);
  56. Vec4 swing_s, swing_c;
  57. half_swing.SinCos(swing_s, swing_c);
  58. // Store half angles for pyramid limit
  59. mSwingYHalfMinAngle = half_swing.GetX();
  60. mSwingYHalfMaxAngle = half_swing.GetY();
  61. mSwingZHalfMinAngle = half_swing.GetZ();
  62. mSwingZHalfMaxAngle = half_swing.GetW();
  63. // Store axis flags which are used at runtime to quickly decided which contraints to apply
  64. mRotationFlags = 0;
  65. if (inTwistMinAngle > -cLockedAngle && inTwistMaxAngle < cLockedAngle)
  66. {
  67. mRotationFlags |= TwistXLocked;
  68. mSinTwistHalfMinAngle = 0.0f;
  69. mSinTwistHalfMaxAngle = 0.0f;
  70. mCosTwistHalfMinAngle = 1.0f;
  71. mCosTwistHalfMaxAngle = 1.0f;
  72. }
  73. else if (inTwistMinAngle < -cFreeAngle && inTwistMaxAngle > cFreeAngle)
  74. {
  75. mRotationFlags |= TwistXFree;
  76. mSinTwistHalfMinAngle = -1.0f;
  77. mSinTwistHalfMaxAngle = 1.0f;
  78. mCosTwistHalfMinAngle = 0.0f;
  79. mCosTwistHalfMaxAngle = 0.0f;
  80. }
  81. else
  82. {
  83. mSinTwistHalfMinAngle = twist_s.GetX();
  84. mSinTwistHalfMaxAngle = twist_s.GetY();
  85. mCosTwistHalfMinAngle = twist_c.GetX();
  86. mCosTwistHalfMaxAngle = twist_c.GetY();
  87. }
  88. if (inSwingYMinAngle > -cLockedAngle && inSwingYMaxAngle < cLockedAngle)
  89. {
  90. mRotationFlags |= SwingYLocked;
  91. mSinSwingYHalfMinAngle = 0.0f;
  92. mSinSwingYHalfMaxAngle = 0.0f;
  93. mCosSwingYHalfMinAngle = 1.0f;
  94. mCosSwingYHalfMaxAngle = 1.0f;
  95. }
  96. else if (inSwingYMinAngle < -cFreeAngle && inSwingYMaxAngle > cFreeAngle)
  97. {
  98. mRotationFlags |= SwingYFree;
  99. mSinSwingYHalfMinAngle = -1.0f;
  100. mSinSwingYHalfMaxAngle = 1.0f;
  101. mCosSwingYHalfMinAngle = 0.0f;
  102. mCosSwingYHalfMaxAngle = 0.0f;
  103. }
  104. else
  105. {
  106. mSinSwingYHalfMinAngle = swing_s.GetX();
  107. mSinSwingYHalfMaxAngle = swing_s.GetY();
  108. mCosSwingYHalfMinAngle = swing_c.GetX();
  109. mCosSwingYHalfMaxAngle = swing_c.GetY();
  110. JPH_ASSERT(mSinSwingYHalfMinAngle <= mSinSwingYHalfMaxAngle);
  111. }
  112. if (inSwingZMinAngle > -cLockedAngle && inSwingZMaxAngle < cLockedAngle)
  113. {
  114. mRotationFlags |= SwingZLocked;
  115. mSinSwingZHalfMinAngle = 0.0f;
  116. mSinSwingZHalfMaxAngle = 0.0f;
  117. mCosSwingZHalfMinAngle = 1.0f;
  118. mCosSwingZHalfMaxAngle = 1.0f;
  119. }
  120. else if (inSwingZMinAngle < -cFreeAngle && inSwingZMaxAngle > cFreeAngle)
  121. {
  122. mRotationFlags |= SwingZFree;
  123. mSinSwingZHalfMinAngle = -1.0f;
  124. mSinSwingZHalfMaxAngle = 1.0f;
  125. mCosSwingZHalfMinAngle = 0.0f;
  126. mCosSwingZHalfMaxAngle = 0.0f;
  127. }
  128. else
  129. {
  130. mSinSwingZHalfMinAngle = swing_s.GetZ();
  131. mSinSwingZHalfMaxAngle = swing_s.GetW();
  132. mCosSwingZHalfMinAngle = swing_c.GetZ();
  133. mCosSwingZHalfMaxAngle = swing_c.GetW();
  134. JPH_ASSERT(mSinSwingZHalfMinAngle <= mSinSwingZHalfMaxAngle);
  135. }
  136. }
  137. /// Flags to indicate which axis got clamped by ClampSwingTwist
  138. static constexpr uint cClampedTwistMin = 1 << 0;
  139. static constexpr uint cClampedTwistMax = 1 << 1;
  140. static constexpr uint cClampedSwingYMin = 1 << 2;
  141. static constexpr uint cClampedSwingYMax = 1 << 3;
  142. static constexpr uint cClampedSwingZMin = 1 << 4;
  143. static constexpr uint cClampedSwingZMax = 1 << 5;
  144. /// Helper function to determine if we're clamped against the min or max limit
  145. static JPH_INLINE bool sDistanceToMinShorter(float inDeltaMin, float inDeltaMax)
  146. {
  147. // We're outside of the limits, get actual delta to min/max range
  148. // 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)
  149. // We should actually be working with angles rather than sin(angle / 2). When the difference is small the approximation is accurate, but
  150. // 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 clamp
  151. // to 180 rather than 0 (you'd expect anything > -90 to go to 0).
  152. inDeltaMin = abs(inDeltaMin);
  153. if (inDeltaMin > 1.0f) inDeltaMin = 2.0f - inDeltaMin;
  154. inDeltaMax = abs(inDeltaMax);
  155. if (inDeltaMax > 1.0f) inDeltaMax = 2.0f - inDeltaMax;
  156. return inDeltaMin < inDeltaMax;
  157. }
  158. /// Clamp twist and swing against the constraint limits, returns which parts were clamped (everything assumed in constraint space)
  159. inline void ClampSwingTwist(Quat &ioSwing, Quat &ioTwist, uint &outClampedAxis) const
  160. {
  161. // Start with not clamped
  162. outClampedAxis = 0;
  163. // Check that swing and twist quaternions don't contain rotations around the wrong axis
  164. JPH_ASSERT(ioSwing.GetX() == 0.0f);
  165. JPH_ASSERT(ioTwist.GetY() == 0.0f);
  166. JPH_ASSERT(ioTwist.GetZ() == 0.0f);
  167. // Ensure quaternions have w > 0
  168. bool negate_swing = ioSwing.GetW() < 0.0f;
  169. if (negate_swing)
  170. ioSwing = -ioSwing;
  171. bool negate_twist = ioTwist.GetW() < 0.0f;
  172. if (negate_twist)
  173. ioTwist = -ioTwist;
  174. if (mRotationFlags & TwistXLocked)
  175. {
  176. // Twist axis is locked, clamp whenever twist is not identity
  177. outClampedAxis |= ioTwist.GetX() != 0.0f? (cClampedTwistMin | cClampedTwistMax) : 0;
  178. ioTwist = Quat::sIdentity();
  179. }
  180. else if ((mRotationFlags & TwistXFree) == 0)
  181. {
  182. // Twist axis has limit, clamp whenever out of range
  183. float delta_min = mSinTwistHalfMinAngle - ioTwist.GetX();
  184. float delta_max = ioTwist.GetX() - mSinTwistHalfMaxAngle;
  185. if (delta_min > 0.0f || delta_max > 0.0f)
  186. {
  187. // Pick the twist that corresponds to the smallest delta
  188. if (sDistanceToMinShorter(delta_min, delta_max))
  189. {
  190. ioTwist = Quat(mSinTwistHalfMinAngle, 0, 0, mCosTwistHalfMinAngle);
  191. outClampedAxis |= cClampedTwistMin;
  192. }
  193. else
  194. {
  195. ioTwist = Quat(mSinTwistHalfMaxAngle, 0, 0, mCosTwistHalfMaxAngle);
  196. outClampedAxis |= cClampedTwistMax;
  197. }
  198. }
  199. }
  200. // Clamp swing
  201. if (mRotationFlags & SwingYLocked)
  202. {
  203. if (mRotationFlags & SwingZLocked)
  204. {
  205. // Both swing Y and Z are disabled, no degrees of freedom in swing
  206. outClampedAxis |= ioSwing.GetY() != 0.0f? (cClampedSwingYMin | cClampedSwingYMax) : 0;
  207. outClampedAxis |= ioSwing.GetZ() != 0.0f? (cClampedSwingZMin | cClampedSwingZMax) : 0;
  208. ioSwing = Quat::sIdentity();
  209. }
  210. else
  211. {
  212. // Swing Y angle disabled, only 1 degree of freedom in swing
  213. outClampedAxis |= ioSwing.GetY() != 0.0f? (cClampedSwingYMin | cClampedSwingYMax) : 0;
  214. float delta_min = mSinSwingZHalfMinAngle - ioSwing.GetZ();
  215. float delta_max = ioSwing.GetZ() - mSinSwingZHalfMaxAngle;
  216. if (delta_min > 0.0f || delta_max > 0.0f)
  217. {
  218. // Pick the swing that corresponds to the smallest delta
  219. if (sDistanceToMinShorter(delta_min, delta_max))
  220. {
  221. ioSwing = Quat(0, 0, mSinSwingZHalfMinAngle, mCosSwingZHalfMinAngle);
  222. outClampedAxis |= cClampedSwingZMin;
  223. }
  224. else
  225. {
  226. ioSwing = Quat(0, 0, mSinSwingZHalfMaxAngle, mCosSwingZHalfMaxAngle);
  227. outClampedAxis |= cClampedSwingZMax;
  228. }
  229. }
  230. else if ((outClampedAxis & cClampedSwingYMin) != 0)
  231. {
  232. float z = ioSwing.GetZ();
  233. ioSwing = Quat(0, 0, z, sqrt(1.0f - Square(z)));
  234. }
  235. }
  236. }
  237. else if (mRotationFlags & SwingZLocked)
  238. {
  239. // Swing Z angle disabled, only 1 degree of freedom in swing
  240. outClampedAxis |= ioSwing.GetZ() != 0.0f? (cClampedSwingZMin | cClampedSwingZMax) : 0;
  241. float delta_min = mSinSwingYHalfMinAngle - ioSwing.GetY();
  242. float delta_max = ioSwing.GetY() - mSinSwingYHalfMaxAngle;
  243. if (delta_min > 0.0f || delta_max > 0.0f)
  244. {
  245. // Pick the swing that corresponds to the smallest delta
  246. if (sDistanceToMinShorter(delta_min, delta_max))
  247. {
  248. ioSwing = Quat(0, mSinSwingYHalfMinAngle, 0, mCosSwingYHalfMinAngle);
  249. outClampedAxis |= cClampedSwingYMin;
  250. }
  251. else
  252. {
  253. ioSwing = Quat(0, mSinSwingYHalfMaxAngle, 0, mCosSwingYHalfMaxAngle);
  254. outClampedAxis |= cClampedSwingYMax;
  255. }
  256. }
  257. else if ((outClampedAxis & cClampedSwingZMin) != 0)
  258. {
  259. float y = ioSwing.GetY();
  260. ioSwing = Quat(0, y, 0, sqrt(1.0f - Square(y)));
  261. }
  262. }
  263. else
  264. {
  265. // Two degrees of freedom
  266. if (mSwingType == ESwingType::Cone)
  267. {
  268. // Use ellipse to solve limits
  269. Ellipse ellipse(mSinSwingYHalfMaxAngle, mSinSwingZHalfMaxAngle);
  270. Float2 point(ioSwing.GetY(), ioSwing.GetZ());
  271. if (!ellipse.IsInside(point))
  272. {
  273. Float2 closest = ellipse.GetClosestPoint(point);
  274. ioSwing = Quat(0, closest.x, closest.y, sqrt(max(0.0f, 1.0f - Square(closest.x) - Square(closest.y))));
  275. outClampedAxis |= cClampedSwingYMin | cClampedSwingYMax | cClampedSwingZMin | cClampedSwingZMax; // We're not using the flags on which side we got clamped here
  276. }
  277. }
  278. else
  279. {
  280. // Use pyramid to solve limits
  281. // The quaterion rotating by angle y around the Y axis then rotating by angle z around the Z axis is:
  282. // q = Quat::sRotation(Vec3::sAxisZ(), z) * Quat::sRotation(Vec3::sAxisY(), y)
  283. // [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)]
  284. // So we can calculate y / 2 = atan2(q.y, q.w) and z / 2 = atan2(q.z, q.w)
  285. Vec4 half_angle = Vec4::sATan2(ioSwing.GetXYZW().Swizzle<SWIZZLE_Y, SWIZZLE_Y, SWIZZLE_Z, SWIZZLE_Z>(), ioSwing.GetXYZW().SplatW());
  286. Vec4 min_half_angle(mSwingYHalfMinAngle, mSwingYHalfMinAngle, mSwingZHalfMinAngle, mSwingZHalfMinAngle);
  287. Vec4 max_half_angle(mSwingYHalfMaxAngle, mSwingYHalfMaxAngle, mSwingZHalfMaxAngle, mSwingZHalfMaxAngle);
  288. Vec4 clamped_half_angle = Vec4::sMin(Vec4::sMax(half_angle, min_half_angle), max_half_angle);
  289. UVec4 unclamped = Vec4::sEquals(half_angle, clamped_half_angle);
  290. if (!unclamped.TestAllTrue())
  291. {
  292. // We now calculate the quaternion again using the formula for q above,
  293. // but we leave out the x component in order to not introduce twist
  294. Vec4 s, c;
  295. clamped_half_angle.SinCos(s, c);
  296. ioSwing = Quat(0, s.GetY() * c.GetZ(), c.GetY() * s.GetZ(), c.GetY() * c.GetZ()).Normalized();
  297. outClampedAxis |= cClampedSwingYMin | cClampedSwingYMax | cClampedSwingZMin | cClampedSwingZMax; // We're not using the flags on which side we got clamped here
  298. }
  299. }
  300. }
  301. // Flip sign back
  302. if (negate_swing)
  303. ioSwing = -ioSwing;
  304. if (negate_twist)
  305. ioTwist = -ioTwist;
  306. JPH_ASSERT(ioSwing.IsNormalized());
  307. JPH_ASSERT(ioTwist.IsNormalized());
  308. }
  309. /// Calculate properties used during the functions below
  310. /// @param inBody1 The first body that this constraint is attached to
  311. /// @param inBody2 The second body that this constraint is attached to
  312. /// @param inConstraintRotation The current rotation of the constraint in constraint space
  313. /// @param inConstraintToWorld Rotates from constraint space into world space
  314. inline void CalculateConstraintProperties(const Body &inBody1, const Body &inBody2, QuatArg inConstraintRotation, QuatArg inConstraintToWorld)
  315. {
  316. // Decompose into swing and twist
  317. Quat q_swing, q_twist;
  318. inConstraintRotation.GetSwingTwist(q_swing, q_twist);
  319. // Clamp against joint limits
  320. Quat q_clamped_swing = q_swing, q_clamped_twist = q_twist;
  321. uint clamped_axis;
  322. ClampSwingTwist(q_clamped_swing, q_clamped_twist, clamped_axis);
  323. if (mRotationFlags & SwingYLocked)
  324. {
  325. Quat twist_to_world = inConstraintToWorld * q_swing;
  326. mWorldSpaceSwingLimitYRotationAxis = twist_to_world.RotateAxisY();
  327. mWorldSpaceSwingLimitZRotationAxis = twist_to_world.RotateAxisZ();
  328. if (mRotationFlags & SwingZLocked)
  329. {
  330. // Swing fully locked
  331. mSwingLimitYConstraintPart.CalculateConstraintProperties(inBody1, inBody2, mWorldSpaceSwingLimitYRotationAxis);
  332. mSwingLimitZConstraintPart.CalculateConstraintProperties(inBody1, inBody2, mWorldSpaceSwingLimitZRotationAxis);
  333. }
  334. else
  335. {
  336. // Swing only locked around Y
  337. mSwingLimitYConstraintPart.CalculateConstraintProperties(inBody1, inBody2, mWorldSpaceSwingLimitYRotationAxis);
  338. if ((clamped_axis & (cClampedSwingZMin | cClampedSwingZMax)) != 0)
  339. {
  340. if ((clamped_axis & cClampedSwingZMin) != 0)
  341. mWorldSpaceSwingLimitZRotationAxis = -mWorldSpaceSwingLimitZRotationAxis; // Flip axis if hitting min limit because the impulse limit is going to be between [-FLT_MAX, 0]
  342. mSwingLimitZConstraintPart.CalculateConstraintProperties(inBody1, inBody2, mWorldSpaceSwingLimitZRotationAxis);
  343. }
  344. else
  345. mSwingLimitZConstraintPart.Deactivate();
  346. }
  347. }
  348. else if (mRotationFlags & SwingZLocked)
  349. {
  350. // Swing only locked around Z
  351. Quat twist_to_world = inConstraintToWorld * q_swing;
  352. mWorldSpaceSwingLimitYRotationAxis = twist_to_world.RotateAxisY();
  353. mWorldSpaceSwingLimitZRotationAxis = twist_to_world.RotateAxisZ();
  354. if ((clamped_axis & (cClampedSwingYMin | cClampedSwingYMax)) != 0)
  355. {
  356. if ((clamped_axis & cClampedSwingYMin) != 0)
  357. mWorldSpaceSwingLimitYRotationAxis = -mWorldSpaceSwingLimitYRotationAxis; // Flip axis if hitting min limit because the impulse limit is going to be between [-FLT_MAX, 0]
  358. mSwingLimitYConstraintPart.CalculateConstraintProperties(inBody1, inBody2, mWorldSpaceSwingLimitYRotationAxis);
  359. }
  360. else
  361. mSwingLimitYConstraintPart.Deactivate();
  362. mSwingLimitZConstraintPart.CalculateConstraintProperties(inBody1, inBody2, mWorldSpaceSwingLimitZRotationAxis);
  363. }
  364. else if ((mRotationFlags & SwingYZFree) != SwingYZFree)
  365. {
  366. // Swing has limits around Y and Z
  367. if ((clamped_axis & (cClampedSwingYMin | cClampedSwingYMax | cClampedSwingZMin | cClampedSwingZMax)) != 0)
  368. {
  369. // Calculate axis of rotation from clamped swing to swing
  370. Vec3 current = (inConstraintToWorld * q_swing).RotateAxisX();
  371. Vec3 desired = (inConstraintToWorld * q_clamped_swing).RotateAxisX();
  372. mWorldSpaceSwingLimitYRotationAxis = desired.Cross(current);
  373. float len = mWorldSpaceSwingLimitYRotationAxis.Length();
  374. if (len != 0.0f)
  375. {
  376. mWorldSpaceSwingLimitYRotationAxis /= len;
  377. mSwingLimitYConstraintPart.CalculateConstraintProperties(inBody1, inBody2, mWorldSpaceSwingLimitYRotationAxis);
  378. }
  379. else
  380. mSwingLimitYConstraintPart.Deactivate();
  381. }
  382. else
  383. mSwingLimitYConstraintPart.Deactivate();
  384. mSwingLimitZConstraintPart.Deactivate();
  385. }
  386. else
  387. {
  388. // No swing limits
  389. mSwingLimitYConstraintPart.Deactivate();
  390. mSwingLimitZConstraintPart.Deactivate();
  391. }
  392. if (mRotationFlags & TwistXLocked)
  393. {
  394. // Twist locked, always activate constraint
  395. mWorldSpaceTwistLimitRotationAxis = (inConstraintToWorld * q_swing).RotateAxisX();
  396. mTwistLimitConstraintPart.CalculateConstraintProperties(inBody1, inBody2, mWorldSpaceTwistLimitRotationAxis);
  397. }
  398. else if ((mRotationFlags & TwistXFree) == 0)
  399. {
  400. // Twist has limits
  401. if ((clamped_axis & (cClampedTwistMin | cClampedTwistMax)) != 0)
  402. {
  403. mWorldSpaceTwistLimitRotationAxis = (inConstraintToWorld * q_swing).RotateAxisX();
  404. if ((clamped_axis & cClampedTwistMin) != 0)
  405. mWorldSpaceTwistLimitRotationAxis = -mWorldSpaceTwistLimitRotationAxis; // Flip axis if hittin min limit because the impulse limit is going to be between [-FLT_MAX, 0]
  406. mTwistLimitConstraintPart.CalculateConstraintProperties(inBody1, inBody2, mWorldSpaceTwistLimitRotationAxis);
  407. }
  408. else
  409. mTwistLimitConstraintPart.Deactivate();
  410. }
  411. else
  412. {
  413. // No twist limits
  414. mTwistLimitConstraintPart.Deactivate();
  415. }
  416. }
  417. /// Deactivate this constraint
  418. void Deactivate()
  419. {
  420. mSwingLimitYConstraintPart.Deactivate();
  421. mSwingLimitZConstraintPart.Deactivate();
  422. mTwistLimitConstraintPart.Deactivate();
  423. }
  424. /// Check if constraint is active
  425. inline bool IsActive() const
  426. {
  427. return mSwingLimitYConstraintPart.IsActive() || mSwingLimitZConstraintPart.IsActive() || mTwistLimitConstraintPart.IsActive();
  428. }
  429. /// Must be called from the WarmStartVelocityConstraint call to apply the previous frame's impulses
  430. inline void WarmStart(Body &ioBody1, Body &ioBody2, float inWarmStartImpulseRatio)
  431. {
  432. mSwingLimitYConstraintPart.WarmStart(ioBody1, ioBody2, inWarmStartImpulseRatio);
  433. mSwingLimitZConstraintPart.WarmStart(ioBody1, ioBody2, inWarmStartImpulseRatio);
  434. mTwistLimitConstraintPart.WarmStart(ioBody1, ioBody2, inWarmStartImpulseRatio);
  435. }
  436. /// Iteratively update the velocity constraint. Makes sure d/dt C(...) = 0, where C is the constraint equation.
  437. inline bool SolveVelocityConstraint(Body &ioBody1, Body &ioBody2)
  438. {
  439. bool impulse = false;
  440. // Solve swing constraint
  441. if (mSwingLimitYConstraintPart.IsActive())
  442. impulse |= mSwingLimitYConstraintPart.SolveVelocityConstraint(ioBody1, ioBody2, mWorldSpaceSwingLimitYRotationAxis, -FLT_MAX, mSinSwingYHalfMinAngle == mSinSwingYHalfMaxAngle? FLT_MAX : 0.0f);
  443. if (mSwingLimitZConstraintPart.IsActive())
  444. impulse |= mSwingLimitZConstraintPart.SolveVelocityConstraint(ioBody1, ioBody2, mWorldSpaceSwingLimitZRotationAxis, -FLT_MAX, mSinSwingZHalfMinAngle == mSinSwingZHalfMaxAngle? FLT_MAX : 0.0f);
  445. // Solve twist constraint
  446. if (mTwistLimitConstraintPart.IsActive())
  447. impulse |= mTwistLimitConstraintPart.SolveVelocityConstraint(ioBody1, ioBody2, mWorldSpaceTwistLimitRotationAxis, -FLT_MAX, mSinTwistHalfMinAngle == mSinTwistHalfMaxAngle? FLT_MAX : 0.0f);
  448. return impulse;
  449. }
  450. /// Iteratively update the position constraint. Makes sure C(...) = 0.
  451. /// @param ioBody1 The first body that this constraint is attached to
  452. /// @param ioBody2 The second body that this constraint is attached to
  453. /// @param inConstraintRotation The current rotation of the constraint in constraint space
  454. /// @param inConstraintToBody1 , inConstraintToBody2 Rotates from constraint space to body 1/2 space
  455. /// @param inBaumgarte Baumgarte constant (fraction of the error to correct)
  456. inline bool SolvePositionConstraint(Body &ioBody1, Body &ioBody2, QuatArg inConstraintRotation, QuatArg inConstraintToBody1, QuatArg inConstraintToBody2, float inBaumgarte) const
  457. {
  458. Quat q_swing, q_twist;
  459. inConstraintRotation.GetSwingTwist(q_swing, q_twist);
  460. uint clamped_axis;
  461. ClampSwingTwist(q_swing, q_twist, clamped_axis);
  462. // Solve rotation violations
  463. if (clamped_axis != 0)
  464. {
  465. RotationEulerConstraintPart part;
  466. Quat inv_initial_orientation = inConstraintToBody2 * (inConstraintToBody1 * q_swing * q_twist).Conjugated();
  467. part.CalculateConstraintProperties(ioBody1, Mat44::sRotation(ioBody1.GetRotation()), ioBody2, Mat44::sRotation(ioBody2.GetRotation()));
  468. return part.SolvePositionConstraint(ioBody1, ioBody2, inv_initial_orientation, inBaumgarte);
  469. }
  470. return false;
  471. }
  472. /// Return lagrange multiplier for swing
  473. inline float GetTotalSwingYLambda() const
  474. {
  475. return mSwingLimitYConstraintPart.GetTotalLambda();
  476. }
  477. inline float GetTotalSwingZLambda() const
  478. {
  479. return mSwingLimitZConstraintPart.GetTotalLambda();
  480. }
  481. /// Return lagrange multiplier for twist
  482. inline float GetTotalTwistLambda() const
  483. {
  484. return mTwistLimitConstraintPart.GetTotalLambda();
  485. }
  486. /// Save state of this constraint part
  487. void SaveState(StateRecorder &inStream) const
  488. {
  489. mSwingLimitYConstraintPart.SaveState(inStream);
  490. mSwingLimitZConstraintPart.SaveState(inStream);
  491. mTwistLimitConstraintPart.SaveState(inStream);
  492. }
  493. /// Restore state of this constraint part
  494. void RestoreState(StateRecorder &inStream)
  495. {
  496. mSwingLimitYConstraintPart.RestoreState(inStream);
  497. mSwingLimitZConstraintPart.RestoreState(inStream);
  498. mTwistLimitConstraintPart.RestoreState(inStream);
  499. }
  500. private:
  501. // CONFIGURATION PROPERTIES FOLLOW
  502. enum ERotationFlags
  503. {
  504. /// Indicates that axis is completely locked (cannot rotate around this axis)
  505. TwistXLocked = 1 << 0,
  506. SwingYLocked = 1 << 1,
  507. SwingZLocked = 1 << 2,
  508. /// Indicates that axis is completely free (can rotate around without limits)
  509. TwistXFree = 1 << 3,
  510. SwingYFree = 1 << 4,
  511. SwingZFree = 1 << 5,
  512. SwingYZFree = SwingYFree | SwingZFree
  513. };
  514. uint8 mRotationFlags;
  515. // Constants
  516. ESwingType mSwingType = ESwingType::Cone;
  517. float mSinTwistHalfMinAngle;
  518. float mSinTwistHalfMaxAngle;
  519. float mCosTwistHalfMinAngle;
  520. float mCosTwistHalfMaxAngle;
  521. float mSwingYHalfMinAngle;
  522. float mSwingYHalfMaxAngle;
  523. float mSwingZHalfMinAngle;
  524. float mSwingZHalfMaxAngle;
  525. float mSinSwingYHalfMinAngle;
  526. float mSinSwingYHalfMaxAngle;
  527. float mSinSwingZHalfMinAngle;
  528. float mSinSwingZHalfMaxAngle;
  529. float mCosSwingYHalfMinAngle;
  530. float mCosSwingYHalfMaxAngle;
  531. float mCosSwingZHalfMinAngle;
  532. float mCosSwingZHalfMaxAngle;
  533. // RUN TIME PROPERTIES FOLLOW
  534. /// Rotation axis for the angle constraint parts
  535. Vec3 mWorldSpaceSwingLimitYRotationAxis;
  536. Vec3 mWorldSpaceSwingLimitZRotationAxis;
  537. Vec3 mWorldSpaceTwistLimitRotationAxis;
  538. /// The constraint parts
  539. AngleConstraintPart mSwingLimitYConstraintPart;
  540. AngleConstraintPart mSwingLimitZConstraintPart;
  541. AngleConstraintPart mTwistLimitConstraintPart;
  542. };
  543. JPH_NAMESPACE_END