PathConstraint.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. // SPDX-FileCopyrightText: 2021 Jorrit Rouwe
  2. // SPDX-License-Identifier: MIT
  3. #include <Jolt.h>
  4. #include <Physics/Constraints/PathConstraint.h>
  5. #include <Physics/Body/Body.h>
  6. #include <Core/StringTools.h>
  7. #include <ObjectStream/TypeDeclarations.h>
  8. #include <Core/StreamIn.h>
  9. #include <Core/StreamOut.h>
  10. #ifdef JPH_DEBUG_RENDERER
  11. #include <Renderer/DebugRenderer.h>
  12. #endif // JPH_DEBUG_RENDERER
  13. namespace JPH {
  14. JPH_IMPLEMENT_SERIALIZABLE_VIRTUAL(PathConstraintSettings)
  15. {
  16. JPH_ADD_BASE_CLASS(PathConstraintSettings, TwoBodyConstraintSettings)
  17. JPH_ADD_ATTRIBUTE(PathConstraintSettings, mPath)
  18. JPH_ADD_ATTRIBUTE(PathConstraintSettings, mPathPosition)
  19. JPH_ADD_ATTRIBUTE(PathConstraintSettings, mPathRotation)
  20. JPH_ADD_ATTRIBUTE(PathConstraintSettings, mPathFraction)
  21. JPH_ADD_ATTRIBUTE(PathConstraintSettings, mMaxFrictionForce)
  22. JPH_ADD_ATTRIBUTE(PathConstraintSettings, mPositionMotorSettings)
  23. JPH_ADD_ENUM_ATTRIBUTE(PathConstraintSettings, mRotationConstraintType)
  24. }
  25. void PathConstraintSettings::SaveBinaryState(StreamOut &inStream) const
  26. {
  27. ConstraintSettings::SaveBinaryState(inStream);
  28. mPath->SaveBinaryState(inStream);
  29. inStream.Write(mPathPosition);
  30. inStream.Write(mPathRotation);
  31. inStream.Write(mPathFraction);
  32. inStream.Write(mMaxFrictionForce);
  33. inStream.Write(mRotationConstraintType);
  34. mPositionMotorSettings.SaveBinaryState(inStream);
  35. }
  36. void PathConstraintSettings::RestoreBinaryState(StreamIn &inStream)
  37. {
  38. ConstraintSettings::RestoreBinaryState(inStream);
  39. PathConstraintPath::PathResult result = PathConstraintPath::sRestoreFromBinaryState(inStream);
  40. if (!result.HasError())
  41. mPath = result.Get();
  42. inStream.Read(mPathPosition);
  43. inStream.Read(mPathRotation);
  44. inStream.Read(mPathFraction);
  45. inStream.Read(mMaxFrictionForce);
  46. inStream.Read(mRotationConstraintType);
  47. mPositionMotorSettings.RestoreBinaryState(inStream);
  48. }
  49. TwoBodyConstraint *PathConstraintSettings::Create(Body &inBody1, Body &inBody2) const
  50. {
  51. return new PathConstraint(inBody1, inBody2, *this);
  52. }
  53. PathConstraint::PathConstraint(Body &inBody1, Body &inBody2, const PathConstraintSettings &inSettings) :
  54. TwoBodyConstraint(inBody1, inBody2, inSettings)
  55. {
  56. // Copy properties
  57. mMaxFrictionForce = inSettings.mMaxFrictionForce;
  58. mPositionMotorSettings = inSettings.mPositionMotorSettings;
  59. mRotationConstraintType = inSettings.mRotationConstraintType;
  60. // Calculate transform that takes us from the path start to center of mass space of body 1
  61. mPathToBody1 = Mat44::sRotationTranslation(inSettings.mPathRotation, inSettings.mPathPosition - inBody1.GetShape()->GetCenterOfMass());
  62. SetPath(inSettings.mPath, inSettings.mPathFraction);
  63. }
  64. void PathConstraint::SetPath(const PathConstraintPath *inPath, float inPathFraction)
  65. {
  66. mPath = inPath;
  67. mPathFraction = inPathFraction;
  68. if (mPath != nullptr)
  69. {
  70. // Get the point on the path for this fraction
  71. Vec3 path_point, path_tangent, path_normal, path_binormal;
  72. mPath->GetPointOnPath(mPathFraction, path_point, path_tangent, path_normal, path_binormal);
  73. // Construct the matrix that takes us from the closest point on the path to body 2 center of mass space
  74. Mat44 closest_point_to_path(Vec4(path_tangent, 0), Vec4(path_binormal, 0), Vec4(path_normal, 0), Vec4(path_point, 1));
  75. Mat44 inv_transform2 = mBody2->GetInverseCenterOfMassTransform();
  76. Mat44 path_to_world = mBody1->GetCenterOfMassTransform() * mPathToBody1;
  77. mPathToBody2 = inv_transform2 * path_to_world * closest_point_to_path;
  78. // Calculate initial orientation
  79. if (mRotationConstraintType == EPathRotationConstraintType::FullyConstrained)
  80. mInvInitialOrientation = RotationQuatConstraintPart::sGetInvInitialOrientation(*mBody1, *mBody2);
  81. }
  82. }
  83. void PathConstraint::CalculateConstraintProperties(float inDeltaTime)
  84. {
  85. // Get transforms of body 1 and 2
  86. Mat44 transform1 = mBody1->GetCenterOfMassTransform();
  87. Mat44 transform2 = mBody2->GetCenterOfMassTransform();
  88. // Get the transform of the path transform as seen from body 1 in world space
  89. Mat44 path_to_world_1 = transform1 * mPathToBody1;
  90. // Get the transform of from the point on path that body 2 is attached to in world space
  91. Mat44 path_to_world_2 = transform2 * mPathToBody2;
  92. // Calculate new closest point on path
  93. Vec3 position2 = path_to_world_2.GetTranslation();
  94. Vec3 position2_local_to_path = path_to_world_1.InversedRotationTranslation() * position2;
  95. mPathFraction = mPath->GetClosestPoint(position2_local_to_path);
  96. // Get the point on the path for this fraction
  97. Vec3 path_point, path_tangent, path_normal, path_binormal;
  98. mPath->GetPointOnPath(mPathFraction, path_point, path_tangent, path_normal, path_binormal);
  99. // Calculate R1 and R2
  100. path_point = path_to_world_1 * path_point;
  101. mR1 = path_point - mBody1->GetCenterOfMassPosition();
  102. mR2 = position2 - mBody2->GetCenterOfMassPosition();
  103. // Calculate U = X2 + R2 - X1 - R1
  104. mU = position2 - path_point;
  105. // Calculate world space normals
  106. mPathNormal = path_to_world_1.Multiply3x3(path_normal);
  107. mPathBinormal = path_to_world_1.Multiply3x3(path_binormal);
  108. // Calculate slide axis
  109. mPathTangent = path_to_world_1.Multiply3x3(path_tangent);
  110. // Prepare constraint part for position constraint to slide along the path
  111. mPositionConstraintPart.CalculateConstraintProperties(*mBody1, transform1.GetRotation(), mR1 + mU, *mBody2, transform2.GetRotation(), mR2, mPathNormal, mPathBinormal);
  112. // Check if closest point is on the boundary of the path and if so apply limit
  113. if (!mPath->IsLooping() && (mPathFraction <= 0.0f || mPathFraction >= mPath->GetPathMaxFraction()))
  114. mPositionLimitsConstraintPart.CalculateConstraintProperties(inDeltaTime, *mBody1, mR1 + mU, *mBody2, mR2, mPathTangent);
  115. else
  116. mPositionLimitsConstraintPart.Deactivate();
  117. // Prepare rotation constraint part
  118. switch (mRotationConstraintType)
  119. {
  120. case EPathRotationConstraintType::Free:
  121. // No rotational limits
  122. break;
  123. case EPathRotationConstraintType::ConstrainAroundTangent:
  124. mHingeConstraintPart.CalculateConstraintProperties(*mBody1, transform1.GetRotation(), mPathTangent, *mBody2, transform2.GetRotation(), path_to_world_2.GetAxisX());
  125. break;
  126. case EPathRotationConstraintType::ConstrainAroundNormal:
  127. mHingeConstraintPart.CalculateConstraintProperties(*mBody1, transform1.GetRotation(), mPathNormal, *mBody2, transform2.GetRotation(), path_to_world_2.GetAxisZ());
  128. break;
  129. case EPathRotationConstraintType::ConstrainAroundBinormal:
  130. mHingeConstraintPart.CalculateConstraintProperties(*mBody1, transform1.GetRotation(), mPathBinormal, *mBody2, transform2.GetRotation(), path_to_world_2.GetAxisY());
  131. break;
  132. case EPathRotationConstraintType::ConstaintToPath:
  133. // We need to calculate the inverse of the rotation from body 1 to body 2 for the current path position (see: RotationQuatConstraintPart::sGetInvInitialOrientation)
  134. // RotationBody2 = RotationBody1 * InitialOrientation <=> InitialOrientation^-1 = RotationBody2^-1 * RotationBody1
  135. // We can express RotationBody2 in terms of RotationBody1: RotationBody2 = RotationBody1 * PathToBody1 * RotationClosestPointOnPath * PathToBody2^-1
  136. // Combining these two: InitialOrientation^-1 = PathToBody2 * (PathToBody1 * RotationClosestPointOnPath)^-1
  137. mInvInitialOrientation = mPathToBody2.Multiply3x3RightTransposed(mPathToBody1.Multiply3x3(Mat44(Vec4(path_tangent, 0), Vec4(path_binormal, 0), Vec4(path_normal, 0), Vec4::sZero()))).GetQuaternion();
  138. [[fallthrough]];
  139. case EPathRotationConstraintType::FullyConstrained:
  140. mRotationConstraintPart.CalculateConstraintProperties(*mBody1, transform1.GetRotation(), *mBody2, transform2.GetRotation(), mInvInitialOrientation);
  141. break;
  142. }
  143. // Motor properties
  144. switch (mPositionMotorState)
  145. {
  146. case EMotorState::Off:
  147. if (mMaxFrictionForce > 0.0f)
  148. mPositionMotorConstraintPart.CalculateConstraintProperties(inDeltaTime, *mBody1, mR1 + mU, *mBody2, mR2, mPathTangent);
  149. else
  150. mPositionMotorConstraintPart.Deactivate();
  151. break;
  152. case EMotorState::Velocity:
  153. mPositionMotorConstraintPart.CalculateConstraintProperties(inDeltaTime, *mBody1, mR1 + mU, *mBody2, mR2, mPathTangent, -mTargetVelocity);
  154. break;
  155. case EMotorState::Position:
  156. {
  157. // Calculate constraint value to drive to
  158. float c;
  159. if (mPath->IsLooping())
  160. {
  161. float max_fraction = mPath->GetPathMaxFraction();
  162. c = fmod(mPathFraction - mTargetPathFraction, max_fraction);
  163. float half_max_fraction = 0.5f * max_fraction;
  164. if (c > half_max_fraction)
  165. c -= max_fraction;
  166. else if (c < -half_max_fraction)
  167. c += max_fraction;
  168. }
  169. else
  170. c = mPathFraction - mTargetPathFraction;
  171. mPositionMotorConstraintPart.CalculateConstraintProperties(inDeltaTime, *mBody1, mR1 + mU, *mBody2, mR2, mPathTangent, 0.0f, c, mPositionMotorSettings.mFrequency, mPositionMotorSettings.mDamping);
  172. break;
  173. }
  174. }
  175. }
  176. void PathConstraint::SetupVelocityConstraint(float inDeltaTime)
  177. {
  178. CalculateConstraintProperties(inDeltaTime);
  179. }
  180. void PathConstraint::WarmStartVelocityConstraint(float inWarmStartImpulseRatio)
  181. {
  182. // Warm starting: Apply previous frame impulse
  183. mPositionMotorConstraintPart.WarmStart(*mBody1, *mBody2, mPathTangent, inWarmStartImpulseRatio);
  184. mPositionConstraintPart.WarmStart(*mBody1, *mBody2, mPathNormal, mPathBinormal, inWarmStartImpulseRatio);
  185. mPositionLimitsConstraintPart.WarmStart(*mBody1, *mBody2, mPathTangent, inWarmStartImpulseRatio);
  186. switch (mRotationConstraintType)
  187. {
  188. case EPathRotationConstraintType::Free:
  189. // No rotational limits
  190. break;
  191. case EPathRotationConstraintType::ConstrainAroundTangent:
  192. case EPathRotationConstraintType::ConstrainAroundNormal:
  193. case EPathRotationConstraintType::ConstrainAroundBinormal:
  194. mHingeConstraintPart.WarmStart(*mBody1, *mBody2, inWarmStartImpulseRatio);
  195. break;
  196. case EPathRotationConstraintType::ConstaintToPath:
  197. case EPathRotationConstraintType::FullyConstrained:
  198. mRotationConstraintPart.WarmStart(*mBody1, *mBody2, inWarmStartImpulseRatio);
  199. break;
  200. }
  201. }
  202. bool PathConstraint::SolveVelocityConstraint(float inDeltaTime)
  203. {
  204. // Solve motor
  205. bool motor = false;
  206. if (mPositionMotorConstraintPart.IsActive())
  207. {
  208. switch (mPositionMotorState)
  209. {
  210. case EMotorState::Off:
  211. {
  212. float max_lambda = mMaxFrictionForce * inDeltaTime;
  213. motor = mPositionMotorConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2, mPathTangent, -max_lambda, max_lambda);
  214. break;
  215. }
  216. case EMotorState::Velocity:
  217. case EMotorState::Position:
  218. motor = mPositionMotorConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2, mPathTangent, inDeltaTime * mPositionMotorSettings.mMinForceLimit, inDeltaTime * mPositionMotorSettings.mMaxForceLimit);
  219. break;
  220. }
  221. }
  222. // Solve position constraint along 2 axis
  223. bool pos = mPositionConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2, mPathNormal, mPathBinormal);
  224. // Solve limits along path axis
  225. bool limit = false;
  226. if (mPositionLimitsConstraintPart.IsActive())
  227. {
  228. if (mPathFraction <= 0.0f)
  229. limit = mPositionLimitsConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2, mPathTangent, 0, FLT_MAX);
  230. else
  231. {
  232. JPH_ASSERT(mPathFraction >= mPath->GetPathMaxFraction());
  233. limit = mPositionLimitsConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2, mPathTangent, -FLT_MAX, 0);
  234. }
  235. }
  236. // Solve rotational constraint
  237. // Note, this is not entirely correct, we should apply a velocity constraint so that the body will actually follow the path
  238. // by looking at the derivative of the tangent, normal or binormal but we don't. This means the position constraint solver
  239. // will need to correct the orientation error that builds up, which in turn means that the simulation is not physically correct.
  240. bool rot = false;
  241. switch (mRotationConstraintType)
  242. {
  243. case EPathRotationConstraintType::Free:
  244. // No rotational limits
  245. break;
  246. case EPathRotationConstraintType::ConstrainAroundTangent:
  247. case EPathRotationConstraintType::ConstrainAroundNormal:
  248. case EPathRotationConstraintType::ConstrainAroundBinormal:
  249. rot = mHingeConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2);
  250. break;
  251. case EPathRotationConstraintType::ConstaintToPath:
  252. case EPathRotationConstraintType::FullyConstrained:
  253. rot = mRotationConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2);
  254. break;
  255. }
  256. return motor || pos || limit || rot;
  257. }
  258. bool PathConstraint::SolvePositionConstraint(float inDeltaTime, float inBaumgarte)
  259. {
  260. // Update constraint properties (bodies may have moved)
  261. CalculateConstraintProperties(inDeltaTime);
  262. // Solve position constraint along 2 axis
  263. bool pos = mPositionConstraintPart.SolvePositionConstraint(*mBody1, *mBody2, mU, mPathNormal, mPathBinormal, inBaumgarte);
  264. // Solve limits along path axis
  265. bool limit = false;
  266. if (mPositionLimitsConstraintPart.IsActive())
  267. {
  268. if (mPathFraction <= 0.0f)
  269. limit = mPositionLimitsConstraintPart.SolvePositionConstraint(*mBody1, *mBody2, mPathTangent, mU.Dot(mPathTangent), inBaumgarte);
  270. else
  271. {
  272. JPH_ASSERT(mPathFraction >= mPath->GetPathMaxFraction());
  273. limit = mPositionLimitsConstraintPart.SolvePositionConstraint(*mBody1, *mBody2, mPathTangent, mU.Dot(mPathTangent), inBaumgarte);
  274. }
  275. }
  276. // Solve rotational constraint
  277. bool rot = false;
  278. switch (mRotationConstraintType)
  279. {
  280. case EPathRotationConstraintType::Free:
  281. // No rotational limits
  282. break;
  283. case EPathRotationConstraintType::ConstrainAroundTangent:
  284. case EPathRotationConstraintType::ConstrainAroundNormal:
  285. case EPathRotationConstraintType::ConstrainAroundBinormal:
  286. rot = mHingeConstraintPart.SolvePositionConstraint(*mBody1, *mBody2, inBaumgarte);
  287. break;
  288. case EPathRotationConstraintType::ConstaintToPath:
  289. case EPathRotationConstraintType::FullyConstrained:
  290. rot = mRotationConstraintPart.SolvePositionConstraint(*mBody1, *mBody2, mInvInitialOrientation, inBaumgarte);
  291. break;
  292. }
  293. return pos || limit || rot;
  294. }
  295. #ifdef JPH_DEBUG_RENDERER
  296. void PathConstraint::DrawConstraint(DebugRenderer *inRenderer) const
  297. {
  298. // Draw the path in world space
  299. Mat44 path_to_world = mBody1->GetCenterOfMassTransform() * mPathToBody1;
  300. mPath->DrawPath(inRenderer, path_to_world);
  301. // Draw anchor point of both bodies in world space
  302. Vec3 x1 = mBody1->GetCenterOfMassPosition() + mR1;
  303. Vec3 x2 = mBody2->GetCenterOfMassPosition() + mR2;
  304. inRenderer->DrawMarker(x1, Color::sYellow, 0.1f);
  305. inRenderer->DrawMarker(x2, Color::sYellow, 0.1f);
  306. inRenderer->DrawArrow(x1, x1 + mPathTangent, Color::sBlue, 0.1f);
  307. inRenderer->DrawArrow(x1, x1 + mPathNormal, Color::sRed, 0.1f);
  308. inRenderer->DrawArrow(x1, x1 + mPathBinormal, Color::sGreen, 0.1f);
  309. inRenderer->DrawText3D(x1, StringFormat("%.1f", (double)mPathFraction));
  310. // Draw motor
  311. switch (mPositionMotorState)
  312. {
  313. case EMotorState::Position:
  314. {
  315. // Draw target marker
  316. Vec3 position, tangent, normal, binormal;
  317. mPath->GetPointOnPath(mTargetPathFraction, position, tangent, normal, binormal);
  318. inRenderer->DrawMarker(path_to_world * position, Color::sYellow, 1.0f);
  319. break;
  320. }
  321. case EMotorState::Velocity:
  322. {
  323. Vec3 position = mBody2->GetCenterOfMassPosition() + mR2;
  324. inRenderer->DrawArrow(position, position + mPathTangent * mTargetVelocity, Color::sRed, 0.1f);
  325. break;
  326. }
  327. case EMotorState::Off:
  328. break;
  329. }
  330. }
  331. #endif // JPH_DEBUG_RENDERER
  332. void PathConstraint::SaveState(StateRecorder &inStream) const
  333. {
  334. TwoBodyConstraint::SaveState(inStream);
  335. mPositionConstraintPart.SaveState(inStream);
  336. mPositionLimitsConstraintPart.SaveState(inStream);
  337. mPositionMotorConstraintPart.SaveState(inStream);
  338. mHingeConstraintPart.SaveState(inStream);
  339. mRotationConstraintPart.SaveState(inStream);
  340. inStream.Write(mMaxFrictionForce);
  341. inStream.Write(mPositionMotorSettings);
  342. inStream.Write(mPositionMotorState);
  343. inStream.Write(mTargetVelocity);
  344. inStream.Write(mTargetPathFraction);
  345. inStream.Write(mPathFraction);
  346. }
  347. void PathConstraint::RestoreState(StateRecorder &inStream)
  348. {
  349. TwoBodyConstraint::RestoreState(inStream);
  350. mPositionConstraintPart.RestoreState(inStream);
  351. mPositionLimitsConstraintPart.RestoreState(inStream);
  352. mPositionMotorConstraintPart.RestoreState(inStream);
  353. mHingeConstraintPart.RestoreState(inStream);
  354. mRotationConstraintPart.RestoreState(inStream);
  355. inStream.Read(mMaxFrictionForce);
  356. inStream.Read(mPositionMotorSettings);
  357. inStream.Read(mPositionMotorState);
  358. inStream.Read(mTargetVelocity);
  359. inStream.Read(mTargetPathFraction);
  360. inStream.Read(mPathFraction);
  361. }
  362. } // JPH