PathConstraint.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  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(*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(*mBody1, mR1 + mU, *mBody2, mR2, mPathTangent);
  155. else
  156. mPositionMotorConstraintPart.Deactivate();
  157. break;
  158. case EMotorState::Velocity:
  159. mPositionMotorConstraintPart.CalculateConstraintProperties(*mBody1, mR1 + mU, *mBody2, mR2, mPathTangent, -mTargetVelocity);
  160. break;
  161. case EMotorState::Position:
  162. if (mPositionMotorSettings.mSpringSettings.HasStiffness())
  163. {
  164. // Calculate constraint value to drive to
  165. float c;
  166. if (mPath->IsLooping())
  167. {
  168. float max_fraction = mPath->GetPathMaxFraction();
  169. c = fmod(mPathFraction - mTargetPathFraction, max_fraction);
  170. float half_max_fraction = 0.5f * max_fraction;
  171. if (c > half_max_fraction)
  172. c -= max_fraction;
  173. else if (c < -half_max_fraction)
  174. c += max_fraction;
  175. }
  176. else
  177. c = mPathFraction - mTargetPathFraction;
  178. mPositionMotorConstraintPart.CalculateConstraintPropertiesWithSettings(inDeltaTime, *mBody1, mR1 + mU, *mBody2, mR2, mPathTangent, 0.0f, c, mPositionMotorSettings.mSpringSettings);
  179. }
  180. else
  181. mPositionMotorConstraintPart.Deactivate();
  182. break;
  183. }
  184. }
  185. void PathConstraint::SetupVelocityConstraint(float inDeltaTime)
  186. {
  187. CalculateConstraintProperties(inDeltaTime);
  188. }
  189. void PathConstraint::WarmStartVelocityConstraint(float inWarmStartImpulseRatio)
  190. {
  191. // Warm starting: Apply previous frame impulse
  192. mPositionMotorConstraintPart.WarmStart(*mBody1, *mBody2, mPathTangent, inWarmStartImpulseRatio);
  193. mPositionConstraintPart.WarmStart(*mBody1, *mBody2, mPathNormal, mPathBinormal, inWarmStartImpulseRatio);
  194. mPositionLimitsConstraintPart.WarmStart(*mBody1, *mBody2, mPathTangent, inWarmStartImpulseRatio);
  195. switch (mRotationConstraintType)
  196. {
  197. case EPathRotationConstraintType::Free:
  198. // No rotational limits
  199. break;
  200. case EPathRotationConstraintType::ConstrainAroundTangent:
  201. case EPathRotationConstraintType::ConstrainAroundNormal:
  202. case EPathRotationConstraintType::ConstrainAroundBinormal:
  203. mHingeConstraintPart.WarmStart(*mBody1, *mBody2, inWarmStartImpulseRatio);
  204. break;
  205. case EPathRotationConstraintType::ConstaintToPath:
  206. case EPathRotationConstraintType::FullyConstrained:
  207. mRotationConstraintPart.WarmStart(*mBody1, *mBody2, inWarmStartImpulseRatio);
  208. break;
  209. }
  210. }
  211. bool PathConstraint::SolveVelocityConstraint(float inDeltaTime)
  212. {
  213. // Solve motor
  214. bool motor = false;
  215. if (mPositionMotorConstraintPart.IsActive())
  216. {
  217. switch (mPositionMotorState)
  218. {
  219. case EMotorState::Off:
  220. {
  221. float max_lambda = mMaxFrictionForce * inDeltaTime;
  222. motor = mPositionMotorConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2, mPathTangent, -max_lambda, max_lambda);
  223. break;
  224. }
  225. case EMotorState::Velocity:
  226. case EMotorState::Position:
  227. motor = mPositionMotorConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2, mPathTangent, inDeltaTime * mPositionMotorSettings.mMinForceLimit, inDeltaTime * mPositionMotorSettings.mMaxForceLimit);
  228. break;
  229. }
  230. }
  231. // Solve position constraint along 2 axis
  232. bool pos = mPositionConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2, mPathNormal, mPathBinormal);
  233. // Solve limits along path axis
  234. bool limit = false;
  235. if (mPositionLimitsConstraintPart.IsActive())
  236. {
  237. if (mPathFraction <= 0.0f)
  238. limit = mPositionLimitsConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2, mPathTangent, 0, FLT_MAX);
  239. else
  240. {
  241. JPH_ASSERT(mPathFraction >= mPath->GetPathMaxFraction());
  242. limit = mPositionLimitsConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2, mPathTangent, -FLT_MAX, 0);
  243. }
  244. }
  245. // Solve rotational constraint
  246. // Note, this is not entirely correct, we should apply a velocity constraint so that the body will actually follow the path
  247. // by looking at the derivative of the tangent, normal or binormal but we don't. This means the position constraint solver
  248. // will need to correct the orientation error that builds up, which in turn means that the simulation is not physically correct.
  249. bool rot = false;
  250. switch (mRotationConstraintType)
  251. {
  252. case EPathRotationConstraintType::Free:
  253. // No rotational limits
  254. break;
  255. case EPathRotationConstraintType::ConstrainAroundTangent:
  256. case EPathRotationConstraintType::ConstrainAroundNormal:
  257. case EPathRotationConstraintType::ConstrainAroundBinormal:
  258. rot = mHingeConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2);
  259. break;
  260. case EPathRotationConstraintType::ConstaintToPath:
  261. case EPathRotationConstraintType::FullyConstrained:
  262. rot = mRotationConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2);
  263. break;
  264. }
  265. return motor || pos || limit || rot;
  266. }
  267. bool PathConstraint::SolvePositionConstraint(float inDeltaTime, float inBaumgarte)
  268. {
  269. // Update constraint properties (bodies may have moved)
  270. CalculateConstraintProperties(inDeltaTime);
  271. // Solve position constraint along 2 axis
  272. bool pos = mPositionConstraintPart.SolvePositionConstraint(*mBody1, *mBody2, mU, mPathNormal, mPathBinormal, inBaumgarte);
  273. // Solve limits along path axis
  274. bool limit = false;
  275. if (mPositionLimitsConstraintPart.IsActive())
  276. {
  277. if (mPathFraction <= 0.0f)
  278. limit = mPositionLimitsConstraintPart.SolvePositionConstraint(*mBody1, *mBody2, mPathTangent, mU.Dot(mPathTangent), inBaumgarte);
  279. else
  280. {
  281. JPH_ASSERT(mPathFraction >= mPath->GetPathMaxFraction());
  282. limit = mPositionLimitsConstraintPart.SolvePositionConstraint(*mBody1, *mBody2, mPathTangent, mU.Dot(mPathTangent), inBaumgarte);
  283. }
  284. }
  285. // Solve rotational constraint
  286. bool rot = false;
  287. switch (mRotationConstraintType)
  288. {
  289. case EPathRotationConstraintType::Free:
  290. // No rotational limits
  291. break;
  292. case EPathRotationConstraintType::ConstrainAroundTangent:
  293. case EPathRotationConstraintType::ConstrainAroundNormal:
  294. case EPathRotationConstraintType::ConstrainAroundBinormal:
  295. rot = mHingeConstraintPart.SolvePositionConstraint(*mBody1, *mBody2, inBaumgarte);
  296. break;
  297. case EPathRotationConstraintType::ConstaintToPath:
  298. case EPathRotationConstraintType::FullyConstrained:
  299. rot = mRotationConstraintPart.SolvePositionConstraint(*mBody1, *mBody2, mInvInitialOrientation, inBaumgarte);
  300. break;
  301. }
  302. return pos || limit || rot;
  303. }
  304. #ifdef JPH_DEBUG_RENDERER
  305. void PathConstraint::DrawConstraint(DebugRenderer *inRenderer) const
  306. {
  307. if (mPath != nullptr)
  308. {
  309. // Draw the path in world space
  310. RMat44 path_to_world = mBody1->GetCenterOfMassTransform() * mPathToBody1;
  311. mPath->DrawPath(inRenderer, path_to_world);
  312. // Draw anchor point of both bodies in world space
  313. RVec3 x1 = mBody1->GetCenterOfMassPosition() + mR1;
  314. RVec3 x2 = mBody2->GetCenterOfMassPosition() + mR2;
  315. inRenderer->DrawMarker(x1, Color::sYellow, 0.1f);
  316. inRenderer->DrawMarker(x2, Color::sYellow, 0.1f);
  317. inRenderer->DrawArrow(x1, x1 + mPathTangent, Color::sBlue, 0.1f);
  318. inRenderer->DrawArrow(x1, x1 + mPathNormal, Color::sRed, 0.1f);
  319. inRenderer->DrawArrow(x1, x1 + mPathBinormal, Color::sGreen, 0.1f);
  320. inRenderer->DrawText3D(x1, StringFormat("%.1f", (double)mPathFraction));
  321. // Draw motor
  322. switch (mPositionMotorState)
  323. {
  324. case EMotorState::Position:
  325. {
  326. // Draw target marker
  327. Vec3 position, tangent, normal, binormal;
  328. mPath->GetPointOnPath(mTargetPathFraction, position, tangent, normal, binormal);
  329. inRenderer->DrawMarker(path_to_world * position, Color::sYellow, 1.0f);
  330. break;
  331. }
  332. case EMotorState::Velocity:
  333. {
  334. RVec3 position = mBody2->GetCenterOfMassPosition() + mR2;
  335. inRenderer->DrawArrow(position, position + mPathTangent * mTargetVelocity, Color::sRed, 0.1f);
  336. break;
  337. }
  338. case EMotorState::Off:
  339. break;
  340. }
  341. }
  342. }
  343. #endif // JPH_DEBUG_RENDERER
  344. void PathConstraint::SaveState(StateRecorder &inStream) const
  345. {
  346. TwoBodyConstraint::SaveState(inStream);
  347. mPositionConstraintPart.SaveState(inStream);
  348. mPositionLimitsConstraintPart.SaveState(inStream);
  349. mPositionMotorConstraintPart.SaveState(inStream);
  350. mHingeConstraintPart.SaveState(inStream);
  351. mRotationConstraintPart.SaveState(inStream);
  352. inStream.Write(mMaxFrictionForce);
  353. inStream.Write(mPositionMotorSettings);
  354. inStream.Write(mPositionMotorState);
  355. inStream.Write(mTargetVelocity);
  356. inStream.Write(mTargetPathFraction);
  357. inStream.Write(mPathFraction);
  358. }
  359. void PathConstraint::RestoreState(StateRecorder &inStream)
  360. {
  361. TwoBodyConstraint::RestoreState(inStream);
  362. mPositionConstraintPart.RestoreState(inStream);
  363. mPositionLimitsConstraintPart.RestoreState(inStream);
  364. mPositionMotorConstraintPart.RestoreState(inStream);
  365. mHingeConstraintPart.RestoreState(inStream);
  366. mRotationConstraintPart.RestoreState(inStream);
  367. inStream.Read(mMaxFrictionForce);
  368. inStream.Read(mPositionMotorSettings);
  369. inStream.Read(mPositionMotorState);
  370. inStream.Read(mTargetVelocity);
  371. inStream.Read(mTargetPathFraction);
  372. inStream.Read(mPathFraction);
  373. }
  374. Ref<ConstraintSettings> PathConstraint::GetConstraintSettings() const
  375. {
  376. JPH_ASSERT(false); // Not implemented yet
  377. return nullptr;
  378. }
  379. JPH_NAMESPACE_END