PathConstraint.cpp 16 KB

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