PathConstraint.cpp 16 KB

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