SwingTwistConstraint.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455
  1. // SPDX-FileCopyrightText: 2021 Jorrit Rouwe
  2. // SPDX-License-Identifier: MIT
  3. #include <Jolt.h>
  4. #include <Physics/Constraints/SwingTwistConstraint.h>
  5. #include <Physics/Body/Body.h>
  6. #include <ObjectStream/TypeDeclarations.h>
  7. #include <Core/StreamIn.h>
  8. #include <Core/StreamOut.h>
  9. #ifdef JPH_DEBUG_RENDERER
  10. #include <Renderer/DebugRenderer.h>
  11. #endif // JPH_DEBUG_RENDERER
  12. namespace JPH {
  13. JPH_IMPLEMENT_SERIALIZABLE_VIRTUAL(SwingTwistConstraintSettings)
  14. {
  15. JPH_ADD_BASE_CLASS(SwingTwistConstraintSettings, TwoBodyConstraintSettings)
  16. JPH_ADD_ATTRIBUTE(SwingTwistConstraintSettings, mPosition1)
  17. JPH_ADD_ATTRIBUTE(SwingTwistConstraintSettings, mTwistAxis1)
  18. JPH_ADD_ATTRIBUTE(SwingTwistConstraintSettings, mPlaneAxis1)
  19. JPH_ADD_ATTRIBUTE(SwingTwistConstraintSettings, mPosition2)
  20. JPH_ADD_ATTRIBUTE(SwingTwistConstraintSettings, mTwistAxis2)
  21. JPH_ADD_ATTRIBUTE(SwingTwistConstraintSettings, mPlaneAxis2)
  22. JPH_ADD_ATTRIBUTE(SwingTwistConstraintSettings, mNormalHalfConeAngle)
  23. JPH_ADD_ATTRIBUTE(SwingTwistConstraintSettings, mPlaneHalfConeAngle)
  24. JPH_ADD_ATTRIBUTE(SwingTwistConstraintSettings, mTwistMinAngle)
  25. JPH_ADD_ATTRIBUTE(SwingTwistConstraintSettings, mTwistMaxAngle)
  26. JPH_ADD_ATTRIBUTE(SwingTwistConstraintSettings, mMaxFrictionTorque)
  27. JPH_ADD_ATTRIBUTE(SwingTwistConstraintSettings, mSwingMotorSettings)
  28. JPH_ADD_ATTRIBUTE(SwingTwistConstraintSettings, mTwistMotorSettings)
  29. }
  30. void SwingTwistConstraintSettings::SaveBinaryState(StreamOut &inStream) const
  31. {
  32. ConstraintSettings::SaveBinaryState(inStream);
  33. inStream.Write(mPosition1);
  34. inStream.Write(mTwistAxis1);
  35. inStream.Write(mPlaneAxis1);
  36. inStream.Write(mPosition2);
  37. inStream.Write(mTwistAxis2);
  38. inStream.Write(mPlaneAxis2);
  39. inStream.Write(mNormalHalfConeAngle);
  40. inStream.Write(mPlaneHalfConeAngle);
  41. inStream.Write(mTwistMinAngle);
  42. inStream.Write(mTwistMaxAngle);
  43. inStream.Write(mMaxFrictionTorque);
  44. mSwingMotorSettings.SaveBinaryState(inStream);
  45. mTwistMotorSettings.SaveBinaryState(inStream);
  46. }
  47. void SwingTwistConstraintSettings::RestoreBinaryState(StreamIn &inStream)
  48. {
  49. ConstraintSettings::RestoreBinaryState(inStream);
  50. inStream.Read(mPosition1);
  51. inStream.Read(mTwistAxis1);
  52. inStream.Read(mPlaneAxis1);
  53. inStream.Read(mPosition2);
  54. inStream.Read(mTwistAxis2);
  55. inStream.Read(mPlaneAxis2);
  56. inStream.Read(mNormalHalfConeAngle);
  57. inStream.Read(mPlaneHalfConeAngle);
  58. inStream.Read(mTwistMinAngle);
  59. inStream.Read(mTwistMaxAngle);
  60. inStream.Read(mMaxFrictionTorque);
  61. mSwingMotorSettings.RestoreBinaryState(inStream);
  62. mTwistMotorSettings.RestoreBinaryState(inStream);
  63. }
  64. TwoBodyConstraint *SwingTwistConstraintSettings::Create(Body &inBody1, Body &inBody2) const
  65. {
  66. return new SwingTwistConstraint(inBody1, inBody2, *this);
  67. }
  68. void SwingTwistConstraint::UpdateLimits()
  69. {
  70. // Pass limits on to swing twist constraint part
  71. mSwingTwistConstraintPart.SetLimits(mTwistMinAngle, mTwistMaxAngle, mPlaneHalfConeAngle, mNormalHalfConeAngle);
  72. }
  73. SwingTwistConstraint::SwingTwistConstraint(Body &inBody1, Body &inBody2, const SwingTwistConstraintSettings &inSettings) :
  74. TwoBodyConstraint(inBody1, inBody2, inSettings),
  75. mNormalHalfConeAngle(inSettings.mNormalHalfConeAngle),
  76. mPlaneHalfConeAngle(inSettings.mPlaneHalfConeAngle),
  77. mTwistMinAngle(inSettings.mTwistMinAngle),
  78. mTwistMaxAngle(inSettings.mTwistMaxAngle),
  79. mMaxFrictionTorque(inSettings.mMaxFrictionTorque),
  80. mSwingMotorSettings(inSettings.mSwingMotorSettings),
  81. mTwistMotorSettings(inSettings.mTwistMotorSettings)
  82. {
  83. // Calculate position of the constraint in body1 local space
  84. Mat44 inv_transform1 = inBody1.GetInverseCenterOfMassTransform();
  85. mLocalSpacePosition1 = inv_transform1 * inSettings.mPosition1;
  86. // Calculate position of the constraint in body2 local space
  87. Mat44 inv_transform2 = inBody2.GetInverseCenterOfMassTransform();
  88. mLocalSpacePosition2 = inv_transform2 * inSettings.mPosition2;
  89. // Calculate rotation needed to go from constraint space to body1 local space
  90. Vec3 normal_axis1 = inSettings.mPlaneAxis1.Cross(inSettings.mTwistAxis1);
  91. Mat44 c_to_b1(Vec4(inSettings.mTwistAxis1, 0), Vec4(normal_axis1, 0), Vec4(inSettings.mPlaneAxis1, 0), Vec4(0, 0, 0, 1));
  92. mConstraintToBody1 = inBody1.GetRotation().Conjugated() * c_to_b1.GetQuaternion();
  93. // Calculate rotation needed to go from constraint space to body2 local space
  94. Vec3 normal_axis2 = inSettings.mPlaneAxis2.Cross(inSettings.mTwistAxis2);
  95. Mat44 c_to_b2(Vec4(inSettings.mTwistAxis2, 0), Vec4(normal_axis2, 0), Vec4(inSettings.mPlaneAxis2, 0), Vec4(0, 0, 0, 1));
  96. mConstraintToBody2 = inBody2.GetRotation().Conjugated() * c_to_b2.GetQuaternion();
  97. UpdateLimits();
  98. }
  99. Quat SwingTwistConstraint::GetRotationInConstraintSpace() const
  100. {
  101. // Let b1, b2 be the center of mass transform of body1 and body2 (For body1 this is mBody1->GetCenterOfMassTransform())
  102. // Let c1, c2 be the transform that takes a vector from constraint space to local space of body1 and body2 (For body1 this is Mat44::sRotationTranslation(mConstraintToBody1, mLocalSpacePosition1))
  103. // Let q be the rotation of the constraint in constraint space
  104. // b2 takes a vector from the local space of body2 to world space
  105. // To express this in terms of b1: b2 = b1 * c1 * q * c2^-1
  106. // c2^-1 goes from local body 2 space to constraint space
  107. // q rotates the constraint
  108. // c1 goes from constraint space to body 1 local space
  109. // b1 goes from body 1 local space to world space
  110. // So when the body rotations are given, q = (b1 * c1)^-1 * b2 c2
  111. // Or: q = (q1 * c1)^-1 * (q2 * c2) if we're only interested in rotations
  112. Quat constraint_body1_to_world = mBody1->GetRotation() * mConstraintToBody1;
  113. Quat constraint_body2_to_world = mBody2->GetRotation() * mConstraintToBody2;
  114. return constraint_body1_to_world.Conjugated() * constraint_body2_to_world;
  115. }
  116. void SwingTwistConstraint::SetSwingMotorState(EMotorState inState)
  117. {
  118. JPH_ASSERT(inState == EMotorState::Off || mSwingMotorSettings.IsValid());
  119. if (mSwingMotorState != inState)
  120. {
  121. mSwingMotorState = inState;
  122. // Ensure that warm starting next frame doesn't apply any impulses (motor parts are repurposed for different modes)
  123. for (int i = 1; i < 3; ++i)
  124. mMotorConstraintPart[i].Deactivate();
  125. }
  126. }
  127. void SwingTwistConstraint::SetTwistMotorState(EMotorState inState)
  128. {
  129. JPH_ASSERT(inState == EMotorState::Off || mTwistMotorSettings.IsValid());
  130. if (mTwistMotorState != inState)
  131. {
  132. mTwistMotorState = inState;
  133. // Ensure that warm starting next frame doesn't apply any impulses (motor parts are repurposed for different modes)
  134. mMotorConstraintPart[0].Deactivate();
  135. }
  136. }
  137. void SwingTwistConstraint::SetTargetOrientationCS(QuatArg inOrientation)
  138. {
  139. Quat q_swing, q_twist;
  140. inOrientation.GetSwingTwist(q_swing, q_twist);
  141. bool swing_y_clamped, swing_z_clamped, twist_clamped;
  142. mSwingTwistConstraintPart.ClampSwingTwist(q_swing, swing_y_clamped, swing_z_clamped, q_twist, twist_clamped);
  143. if (swing_y_clamped || swing_z_clamped || twist_clamped)
  144. mTargetOrientation = q_swing * q_twist;
  145. else
  146. mTargetOrientation = inOrientation;
  147. }
  148. void SwingTwistConstraint::SetupVelocityConstraint(float inDeltaTime)
  149. {
  150. // Setup point constraint
  151. Mat44 rotation1 = Mat44::sRotation(mBody1->GetRotation());
  152. Mat44 rotation2 = Mat44::sRotation(mBody2->GetRotation());
  153. mPointConstraintPart.CalculateConstraintProperties(*mBody1, rotation1, mLocalSpacePosition1, *mBody2, rotation2, mLocalSpacePosition2);
  154. // GetRotationInConstraintSpace written out since we reuse the sub expressions
  155. Quat constraint_body1_to_world = mBody1->GetRotation() * mConstraintToBody1;
  156. Quat constraint_body2_to_world = mBody2->GetRotation() * mConstraintToBody2;
  157. Quat q = constraint_body1_to_world.Conjugated() * constraint_body2_to_world;
  158. // Calculate constraint properties for the swing twist limit
  159. mSwingTwistConstraintPart.CalculateConstraintProperties(inDeltaTime, *mBody1, *mBody2, q, constraint_body1_to_world);
  160. if (mSwingMotorState != EMotorState::Off || mTwistMotorState != EMotorState::Off || mMaxFrictionTorque > 0.0f)
  161. {
  162. // Calculate rotation motor axis
  163. Mat44 ws_axis = Mat44::sRotation(constraint_body2_to_world);
  164. for (int i = 0; i < 3; ++i)
  165. mWorldSpaceMotorAxis[i] = ws_axis.GetColumn3(i);
  166. Vec3 rotation_error;
  167. if (mSwingMotorState == EMotorState::Position || mTwistMotorState == EMotorState::Position)
  168. {
  169. // Get target orientation along the shortest path from q
  170. Quat target_orientation = q.Dot(mTargetOrientation) > 0.0f? mTargetOrientation : -mTargetOrientation;
  171. // The definition of the constraint rotation q:
  172. // R2 * ConstraintToBody2 = R1 * ConstraintToBody1 * q (1)
  173. //
  174. // R2' is the rotation of body 2 when reaching the target_orientation:
  175. // R2' * ConstraintToBody2 = R1 * ConstraintToBody1 * target_orientation (2)
  176. //
  177. // The difference in body 2 space:
  178. // R2' = R2 * diff_body2 (3)
  179. //
  180. // We want to specify the difference in the constraint space of body 2:
  181. // diff_body2 = ConstraintToBody2 * diff * ConstraintToBody2^* (4)
  182. //
  183. // Extracting R2' from 2: R2' = R1 * ConstraintToBody1 * target_orientation * ConstraintToBody2^* (5)
  184. // Combining 3 & 4: R2' = R2 * ConstraintToBody2 * diff * ConstraintToBody2^* (6)
  185. // Combining 1 & 6: R2' = R1 * ConstraintToBody1 * q * diff * ConstraintToBody2^* (7)
  186. // Combining 5 & 7: R1 * ConstraintToBody1 * target_orientation * ConstraintToBody2^* = R1 * ConstraintToBody1 * q * diff * ConstraintToBody2^*
  187. // <=> target_orientation = q * diff
  188. // <=> diff = q^* * target_orientation
  189. Quat diff = q.Conjugated() * target_orientation;
  190. // Approximate error angles
  191. // The imaginary part of a quaternion is rotation_axis * sin(angle / 2)
  192. // If angle is small, sin(x) = x so angle[i] ~ 2.0f * rotation_axis[i]
  193. // We'll be making small time steps, so if the angle is not small at least the sign will be correct and we'll move in the right direction
  194. rotation_error = -2.0f * diff.GetXYZ();
  195. }
  196. // Swing motor
  197. switch (mSwingMotorState)
  198. {
  199. case EMotorState::Off:
  200. if (mMaxFrictionTorque > 0.0f)
  201. {
  202. // Enable friction
  203. for (int i = 1; i < 3; ++i)
  204. mMotorConstraintPart[i].CalculateConstraintProperties(inDeltaTime, *mBody1, *mBody2, mWorldSpaceMotorAxis[i], 0.0f);
  205. }
  206. else
  207. {
  208. // Disable friction
  209. for (int i = 1; i < 3; ++i)
  210. mMotorConstraintPart[i].Deactivate();
  211. }
  212. break;
  213. case EMotorState::Velocity:
  214. // Use motor to create angular velocity around desired axis
  215. for (int i = 1; i < 3; ++i)
  216. mMotorConstraintPart[i].CalculateConstraintProperties(inDeltaTime, *mBody1, *mBody2, mWorldSpaceMotorAxis[i], -mTargetAngularVelocity[i]);
  217. break;
  218. case EMotorState::Position:
  219. // Use motor to drive rotation error to zero
  220. for (int i = 1; i < 3; ++i)
  221. mMotorConstraintPart[i].CalculateConstraintProperties(inDeltaTime, *mBody1, *mBody2, mWorldSpaceMotorAxis[i], 0.0f, rotation_error[i], mSwingMotorSettings.mFrequency, mSwingMotorSettings.mDamping);
  222. break;
  223. }
  224. // Twist motor
  225. switch (mTwistMotorState)
  226. {
  227. case EMotorState::Off:
  228. if (mMaxFrictionTorque > 0.0f)
  229. {
  230. // Enable friction
  231. mMotorConstraintPart[0].CalculateConstraintProperties(inDeltaTime, *mBody1, *mBody2, mWorldSpaceMotorAxis[0], 0.0f);
  232. }
  233. else
  234. {
  235. // Disable friction
  236. mMotorConstraintPart[0].Deactivate();
  237. }
  238. break;
  239. case EMotorState::Velocity:
  240. // Use motor to create angular velocity around desired axis
  241. mMotorConstraintPart[0].CalculateConstraintProperties(inDeltaTime, *mBody1, *mBody2, mWorldSpaceMotorAxis[0], -mTargetAngularVelocity[0]);
  242. break;
  243. case EMotorState::Position:
  244. // Use motor to drive rotation error to zero
  245. mMotorConstraintPart[0].CalculateConstraintProperties(inDeltaTime, *mBody1, *mBody2, mWorldSpaceMotorAxis[0], 0.0f, rotation_error[0], mTwistMotorSettings.mFrequency, mTwistMotorSettings.mDamping);
  246. break;
  247. }
  248. }
  249. else
  250. {
  251. // Disable rotation motor
  252. for (int i = 0; i < 3; ++i)
  253. mMotorConstraintPart[i].Deactivate();
  254. }
  255. }
  256. void SwingTwistConstraint::WarmStartVelocityConstraint(float inWarmStartImpulseRatio)
  257. {
  258. // Warm starting: Apply previous frame impulse
  259. for (int i = 0; i < 3; ++i)
  260. mMotorConstraintPart[i].WarmStart(*mBody1, *mBody2, inWarmStartImpulseRatio);
  261. mSwingTwistConstraintPart.WarmStart(*mBody1, *mBody2, inWarmStartImpulseRatio);
  262. mPointConstraintPart.WarmStart(*mBody1, *mBody2, inWarmStartImpulseRatio);
  263. }
  264. bool SwingTwistConstraint::SolveVelocityConstraint(float inDeltaTime)
  265. {
  266. bool impulse = false;
  267. // Solve twist rotation motor
  268. if (mMotorConstraintPart[0].IsActive())
  269. {
  270. // Twist limits
  271. float min_twist_limit, max_twist_limit;
  272. if (mTwistMotorState == EMotorState::Off)
  273. {
  274. max_twist_limit = inDeltaTime * mMaxFrictionTorque;
  275. min_twist_limit = -max_twist_limit;
  276. }
  277. else
  278. {
  279. min_twist_limit = inDeltaTime * mTwistMotorSettings.mMinTorqueLimit;
  280. max_twist_limit = inDeltaTime * mTwistMotorSettings.mMaxTorqueLimit;
  281. }
  282. impulse |= mMotorConstraintPart[0].SolveVelocityConstraint(*mBody1, *mBody2, mWorldSpaceMotorAxis[0], min_twist_limit, max_twist_limit);
  283. }
  284. // Solve swing rotation motor
  285. if (mMotorConstraintPart[1].IsActive())
  286. {
  287. // Swing parts should turn on / off together
  288. JPH_ASSERT(mMotorConstraintPart[2].IsActive());
  289. // Swing limits
  290. float min_swing_limit, max_swing_limit;
  291. if (mSwingMotorState == EMotorState::Off)
  292. {
  293. max_swing_limit = inDeltaTime * mMaxFrictionTorque;
  294. min_swing_limit = -max_swing_limit;
  295. }
  296. else
  297. {
  298. min_swing_limit = inDeltaTime * mSwingMotorSettings.mMinTorqueLimit;
  299. max_swing_limit = inDeltaTime * mSwingMotorSettings.mMaxTorqueLimit;
  300. }
  301. for (int i = 1; i < 3; ++i)
  302. impulse |= mMotorConstraintPart[i].SolveVelocityConstraint(*mBody1, *mBody2, mWorldSpaceMotorAxis[i], min_swing_limit, max_swing_limit);
  303. }
  304. else
  305. {
  306. // Swing parts should turn on / off together
  307. JPH_ASSERT(!mMotorConstraintPart[2].IsActive());
  308. }
  309. // Solve rotation limits
  310. impulse |= mSwingTwistConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2);
  311. // Solve position constraint
  312. impulse |= mPointConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2);
  313. return impulse;
  314. }
  315. bool SwingTwistConstraint::SolvePositionConstraint(float inDeltaTime, float inBaumgarte)
  316. {
  317. bool impulse = false;
  318. // Solve rotation violations
  319. Quat q = GetRotationInConstraintSpace();
  320. impulse |= mSwingTwistConstraintPart.SolvePositionConstraint(*mBody1, *mBody2, q, mConstraintToBody1, mConstraintToBody2, inBaumgarte);
  321. // Solve position violations
  322. mPointConstraintPart.CalculateConstraintProperties(*mBody1, Mat44::sRotation(mBody1->GetRotation()), mLocalSpacePosition1, *mBody2, Mat44::sRotation(mBody2->GetRotation()), mLocalSpacePosition2);
  323. impulse |= mPointConstraintPart.SolvePositionConstraint(*mBody1, *mBody2, inBaumgarte);
  324. return impulse;
  325. }
  326. #ifdef JPH_DEBUG_RENDERER
  327. void SwingTwistConstraint::DrawConstraint(DebugRenderer *inRenderer) const
  328. {
  329. // Get constraint properties in world space
  330. Mat44 transform1 = mBody1->GetCenterOfMassTransform();
  331. Vec3 position1 = transform1 * mLocalSpacePosition1;
  332. Quat rotation1 = mBody1->GetRotation() * mConstraintToBody1;
  333. Quat rotation2 = mBody2->GetRotation() * mConstraintToBody2;
  334. // Draw constraint orientation
  335. inRenderer->DrawCoordinateSystem(Mat44::sRotationTranslation(rotation1, position1), mDrawConstraintSize);
  336. // Draw current swing and twist
  337. Quat q = GetRotationInConstraintSpace();
  338. Quat q_swing, q_twist;
  339. q.GetSwingTwist(q_swing, q_twist);
  340. inRenderer->DrawLine(position1, position1 + mDrawConstraintSize * (rotation1 * q_twist).RotateAxisY(), Color::sWhite);
  341. inRenderer->DrawLine(position1, position1 + mDrawConstraintSize * (rotation1 * q_swing).RotateAxisX(), Color::sWhite);
  342. if (mSwingMotorState == EMotorState::Velocity || mTwistMotorState == EMotorState::Velocity)
  343. {
  344. // Draw target angular velocity
  345. inRenderer->DrawArrow(position1, position1 + rotation2 * mTargetAngularVelocity, Color::sRed, 0.1f);
  346. }
  347. if (mSwingMotorState == EMotorState::Position || mTwistMotorState == EMotorState::Position)
  348. {
  349. // Draw motor swing and twist
  350. Quat swing, twist;
  351. mTargetOrientation.GetSwingTwist(swing, twist);
  352. inRenderer->DrawLine(position1, position1 + mDrawConstraintSize * (rotation1 * twist).RotateAxisY(), Color::sYellow);
  353. inRenderer->DrawLine(position1, position1 + mDrawConstraintSize * (rotation1 * swing).RotateAxisX(), Color::sCyan);
  354. }
  355. }
  356. void SwingTwistConstraint::DrawConstraintLimits(DebugRenderer *inRenderer) const
  357. {
  358. // Get matrix that transforms from constraint space to world space
  359. Mat44 constraint_to_world = Mat44::sRotationTranslation(mBody1->GetRotation() * mConstraintToBody1, mBody1->GetCenterOfMassTransform() * mLocalSpacePosition1);
  360. // Draw limits
  361. inRenderer->DrawSwingLimits(constraint_to_world, mPlaneHalfConeAngle, mNormalHalfConeAngle, mDrawConstraintSize, Color::sGreen, DebugRenderer::ECastShadow::Off);
  362. inRenderer->DrawPie(constraint_to_world.GetTranslation(), mDrawConstraintSize, constraint_to_world.GetAxisX(), constraint_to_world.GetAxisY(), mTwistMinAngle, mTwistMaxAngle, Color::sPurple, DebugRenderer::ECastShadow::Off);
  363. }
  364. #endif // JPH_DEBUG_RENDERER
  365. void SwingTwistConstraint::SaveState(StateRecorder &inStream) const
  366. {
  367. TwoBodyConstraint::SaveState(inStream);
  368. mPointConstraintPart.SaveState(inStream);
  369. mSwingTwistConstraintPart.SaveState(inStream);
  370. for (int i = 0; i < 3; ++i)
  371. mMotorConstraintPart[i].SaveState(inStream);
  372. inStream.Write(mSwingMotorState);
  373. inStream.Write(mTwistMotorState);
  374. inStream.Write(mTargetAngularVelocity);
  375. inStream.Write(mTargetOrientation);
  376. }
  377. void SwingTwistConstraint::RestoreState(StateRecorder &inStream)
  378. {
  379. TwoBodyConstraint::RestoreState(inStream);
  380. mPointConstraintPart.RestoreState(inStream);
  381. mSwingTwistConstraintPart.RestoreState(inStream);
  382. for (int i = 0; i < 3; ++i)
  383. mMotorConstraintPart[i].RestoreState(inStream);
  384. inStream.Read(mSwingMotorState);
  385. inStream.Read(mTwistMotorState);
  386. inStream.Read(mTargetAngularVelocity);
  387. inStream.Read(mTargetOrientation);
  388. }
  389. } // JPH