CharacterVirtual.cpp 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841
  1. // SPDX-FileCopyrightText: 2021 Jorrit Rouwe
  2. // SPDX-License-Identifier: MIT
  3. #include <Jolt/Jolt.h>
  4. #include <Jolt/Physics/Character/CharacterVirtual.h>
  5. #include <Jolt/Physics/Body/Body.h>
  6. #include <Jolt/Physics/PhysicsSystem.h>
  7. #include <Jolt/Physics/Collision/ShapeCast.h>
  8. #include <Jolt/Physics/Collision/CollideShape.h>
  9. JPH_NAMESPACE_BEGIN
  10. CharacterVirtual::CharacterVirtual(const CharacterVirtualSettings *inSettings, Vec3Arg inPosition, QuatArg inRotation, PhysicsSystem *inSystem) :
  11. CharacterBase(inSettings, inSystem),
  12. mUp(inSettings->mUp),
  13. mPredictiveContactDistance(inSettings->mPredictiveContactDistance),
  14. mMaxCollisionIterations(inSettings->mMaxCollisionIterations),
  15. mMaxConstraintIterations(inSettings->mMaxConstraintIterations),
  16. mMinTimeRemaining(inSettings->mMinTimeRemaining),
  17. mCollisionTolerance(inSettings->mCollisionTolerance),
  18. mCharacterPadding(inSettings->mCharacterPadding),
  19. mMaxNumHits(inSettings->mMaxNumHits),
  20. mPenetrationRecoverySpeed(inSettings->mPenetrationRecoverySpeed),
  21. mPosition(inPosition),
  22. mRotation(inRotation)
  23. {
  24. // Copy settings
  25. SetMaxStrength(inSettings->mMaxStrength);
  26. SetMass(inSettings->mMass);
  27. }
  28. template <class taCollector>
  29. void CharacterVirtual::sFillContactProperties(Contact &outContact, const Body &inBody, const taCollector &inCollector, const CollideShapeResult &inResult)
  30. {
  31. outContact.mPosition = inResult.mContactPointOn2;
  32. outContact.mLinearVelocity = inBody.GetPointVelocity(inResult.mContactPointOn2);
  33. outContact.mNormal = -inResult.mPenetrationAxis.NormalizedOr(Vec3::sZero());
  34. outContact.mDistance = -inResult.mPenetrationDepth;
  35. outContact.mBodyB = inResult.mBodyID2;
  36. outContact.mSubShapeIDB = inResult.mSubShapeID2;
  37. outContact.mMotionTypeB = inBody.GetMotionType();
  38. outContact.mUserData = inBody.GetUserData();
  39. outContact.mMaterial = inCollector.GetContext()->GetMaterial(inResult.mSubShapeID2);
  40. }
  41. void CharacterVirtual::ContactCollector::AddHit(const CollideShapeResult &inResult)
  42. {
  43. BodyLockRead lock(mSystem->GetBodyLockInterface(), inResult.mBodyID2);
  44. if (lock.SucceededAndIsInBroadPhase())
  45. {
  46. const Body &body = lock.GetBody();
  47. mContacts.emplace_back();
  48. Contact &contact = mContacts.back();
  49. sFillContactProperties(contact, body, *this, inResult);
  50. contact.mFraction = 0.0f;
  51. // Protection from excess of contact points
  52. if (mContacts.size() == mMaxHits)
  53. ForceEarlyOut();
  54. }
  55. }
  56. void CharacterVirtual::ContactCastCollector::AddHit(const ShapeCastResult &inResult)
  57. {
  58. if (inResult.mFraction > 0.0f // Ignore collisions at fraction = 0
  59. && inResult.mPenetrationAxis.Dot(mDisplacement) > 0.0f) // Ignore penetrations that we're moving away from
  60. {
  61. // Test if this contact should be ignored
  62. for (const IgnoredContact &c : mIgnoredContacts)
  63. if (c.mBodyID == inResult.mBodyID2 && c.mSubShapeID == inResult.mSubShapeID2)
  64. return;
  65. BodyLockRead lock(mSystem->GetBodyLockInterface(), inResult.mBodyID2);
  66. if (lock.SucceededAndIsInBroadPhase())
  67. {
  68. const Body &body = lock.GetBody();
  69. mContacts.emplace_back();
  70. Contact &contact = mContacts.back();
  71. sFillContactProperties(contact, body, *this, inResult);
  72. contact.mFraction = inResult.mFraction;
  73. // Protection from excess of contact points
  74. if (mContacts.size() == mMaxHits)
  75. ForceEarlyOut();
  76. }
  77. }
  78. }
  79. void CharacterVirtual::CheckCollision(Vec3Arg inPosition, QuatArg inRotation, Vec3Arg inMovementDirection, float inMaxSeparationDistance, const Shape *inShape, CollideShapeCollector &ioCollector, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter) const
  80. {
  81. // Query shape transform
  82. Mat44 transform = GetCenterOfMassTransform(inPosition, inRotation, inShape);
  83. // Settings for collide shape
  84. CollideShapeSettings settings;
  85. settings.mActiveEdgeMode = EActiveEdgeMode::CollideOnlyWithActive;
  86. settings.mBackFaceMode = EBackFaceMode::CollideWithBackFaces;
  87. settings.mActiveEdgeMovementDirection = inMovementDirection;
  88. settings.mMaxSeparationDistance = mCharacterPadding + inMaxSeparationDistance;
  89. // Collide shape
  90. mSystem->GetNarrowPhaseQuery().CollideShape(inShape, Vec3::sReplicate(1.0f), transform, settings, ioCollector, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter);
  91. }
  92. void CharacterVirtual::GetContactsAtPosition(Vec3Arg inPosition, Vec3Arg inMovementDirection, const Shape *inShape, TempContactList &outContacts, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter) const
  93. {
  94. // Remove previous results
  95. outContacts.clear();
  96. // Collide shape
  97. ContactCollector collector(mSystem, mMaxNumHits, outContacts);
  98. CheckCollision(inPosition, mRotation, inMovementDirection, mPredictiveContactDistance, inShape, collector, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter);
  99. // Reduce distance to contact by padding to ensure we stay away from the object by a little margin
  100. // (this will make collision detection cheaper - especially for sweep tests as they won't hit the surface if we're properly sliding)
  101. for (Contact &c : outContacts)
  102. c.mDistance -= mCharacterPadding;
  103. }
  104. void CharacterVirtual::RemoveConflictingContacts(TempContactList &ioContacts, IgnoredContactList &outIgnoredContacts) const
  105. {
  106. // Only use this algorithm if we're penetrating further than this (due to numerical precision issues we can always penetrate a little bit and we don't want to discard contacts if they just have a tiny penetration)
  107. // We do need to account for padding (see GetContactsAtPosition) that is removed from the contact distances, to compensate we add it to the cMinRequiredPenetration
  108. const float cMinRequiredPenetration = 1.25f * mCharacterPadding;
  109. // Discard conflicting penetrating contacts
  110. for (size_t c1 = 0; c1 < ioContacts.size(); c1++)
  111. {
  112. Contact &contact1 = ioContacts[c1];
  113. if (contact1.mDistance <= -cMinRequiredPenetration) // Only for penetrations
  114. for (size_t c2 = c1 + 1; c2 < ioContacts.size(); c2++)
  115. {
  116. Contact &contact2 = ioContacts[c2];
  117. if (contact1.mBodyB == contact2.mBodyB // Only same body
  118. && contact2.mDistance <= -cMinRequiredPenetration // Only for penetrations
  119. && contact1.mNormal.Dot(contact2.mNormal) < 0.0f) // Only opposing normals
  120. {
  121. // Discard contacts with the least amount of penetration
  122. if (contact1.mDistance < contact2.mDistance)
  123. {
  124. // Discard the 2nd contact
  125. outIgnoredContacts.emplace_back(contact2.mBodyB, contact2.mSubShapeIDB);
  126. ioContacts.erase(ioContacts.begin() + c2);
  127. c2--;
  128. }
  129. else
  130. {
  131. // Discard the first contact
  132. outIgnoredContacts.emplace_back(contact1.mBodyB, contact1.mSubShapeIDB);
  133. ioContacts.erase(ioContacts.begin() + c1);
  134. c1--;
  135. break;
  136. }
  137. }
  138. }
  139. }
  140. }
  141. bool CharacterVirtual::ValidateContact(const Contact &inContact) const
  142. {
  143. if (mListener == nullptr)
  144. return true;
  145. return mListener->OnContactValidate(this, inContact.mBodyB, inContact.mSubShapeIDB);
  146. }
  147. bool CharacterVirtual::GetFirstContactForSweep(Vec3Arg inPosition, Vec3Arg inDisplacement, Contact &outContact, const IgnoredContactList &inIgnoredContacts, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, TempAllocator &inAllocator) const
  148. {
  149. // Too small distance -> skip checking
  150. if (inDisplacement.LengthSq() < 1.0e-8f)
  151. return false;
  152. // Calculate start transform
  153. Mat44 start = GetCenterOfMassTransform(inPosition, mRotation, mShape);
  154. // Settings for the cast
  155. ShapeCastSettings settings;
  156. settings.mBackFaceModeTriangles = EBackFaceMode::CollideWithBackFaces;
  157. settings.mBackFaceModeConvex = EBackFaceMode::IgnoreBackFaces;
  158. settings.mActiveEdgeMode = EActiveEdgeMode::CollideOnlyWithActive;
  159. settings.mUseShrunkenShapeAndConvexRadius = true;
  160. settings.mReturnDeepestPoint = false;
  161. // Cast shape
  162. TempContactList contacts(inAllocator);
  163. contacts.reserve(mMaxNumHits);
  164. ContactCastCollector collector(mSystem, inDisplacement, mMaxNumHits, inIgnoredContacts, contacts);
  165. ShapeCast shape_cast(mShape, Vec3::sReplicate(1.0f), start, inDisplacement);
  166. mSystem->GetNarrowPhaseQuery().CastShape(shape_cast, settings, collector, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter);
  167. if (contacts.empty())
  168. return false;
  169. // Sort the contacts on fraction
  170. sort(contacts.begin(), contacts.end(), [](const Contact &inLHS, const Contact &inRHS) { return inLHS.mFraction < inRHS.mFraction; });
  171. // Check the first contact that will make us penetrate more than the allowed tolerance
  172. bool valid_contact = false;
  173. for (const Contact &c : contacts)
  174. if (c.mDistance + c.mNormal.Dot(inDisplacement) < -mCollisionTolerance
  175. && ValidateContact(c))
  176. {
  177. outContact = c;
  178. valid_contact = true;
  179. break;
  180. }
  181. if (!valid_contact)
  182. return false;
  183. // Correct fraction for the padding that we want to keep from geometry
  184. // We want to maintain distance of cCharacterPadding (p) along plane normal outContact.mNormal (n) to the capsule by moving back along inDisplacement (d) by amount d'
  185. // cos(angle between d and -n) = -n dot d / |d| = p / d'
  186. // <=> d' = -p |d| / n dot d
  187. // The new fraction of collision is then:
  188. // f' = f - d' / |d| = f + p / n dot d
  189. float dot = outContact.mNormal.Dot(inDisplacement);
  190. if (dot < 0.0f) // We should not divide by zero and we should only update the fraction if normal is pointing towards displacement
  191. outContact.mFraction = max(0.0f, outContact.mFraction + mCharacterPadding / dot);
  192. return true;
  193. }
  194. void CharacterVirtual::DetermineConstraints(TempContactList &inContacts, ConstraintList &outConstraints) const
  195. {
  196. for (Contact &c : inContacts)
  197. {
  198. Vec3 contact_velocity = c.mLinearVelocity;
  199. // Penetrating contact: Add a contact velocity that pushes the character out at the desired speed
  200. if (c.mDistance < 0.0f)
  201. contact_velocity -= c.mNormal * c.mDistance * mPenetrationRecoverySpeed;
  202. // Convert to a constraint
  203. outConstraints.emplace_back();
  204. Constraint &constraint = outConstraints.back();
  205. constraint.mContact = &c;
  206. constraint.mLinearVelocity = contact_velocity;
  207. constraint.mPlane = Plane(c.mNormal, c.mDistance);
  208. // Next check if the angle is too steep and if it is add an additional constraint that holds the character back
  209. if (mCosMaxSlopeAngle < 0.999f) // If cos(slope angle) is close to 1 then there's no limit
  210. {
  211. float dot = c.mNormal.Dot(mUp);
  212. if (dot > 0.0f && dot < mCosMaxSlopeAngle)
  213. {
  214. // Make horizontal normal
  215. Vec3 normal = (c.mNormal - dot * mUp).Normalized();
  216. // Create a secondary constraint that blocks horizontal movement
  217. outConstraints.emplace_back();
  218. Constraint &vertical_constraint = outConstraints.back();
  219. vertical_constraint.mContact = &c;
  220. vertical_constraint.mLinearVelocity = contact_velocity.Dot(normal) * normal; // Project the contact velocity on the new normal so that both planes push at an equal rate
  221. vertical_constraint.mPlane = Plane(normal, c.mDistance / normal.Dot(c.mNormal)); // Calculate the distance we have to travel horizontally to hit the contact plane
  222. }
  223. }
  224. }
  225. }
  226. bool CharacterVirtual::HandleContact(Vec3Arg inVelocity, Constraint &ioConstraint, Vec3Arg inGravity, float inDeltaTime) const
  227. {
  228. Contact &contact = *ioConstraint.mContact;
  229. // Validate the contact point
  230. if (!ValidateContact(contact))
  231. return false;
  232. // Send contact added event
  233. CharacterContactSettings settings;
  234. if (mListener != nullptr)
  235. mListener->OnContactAdded(this, contact.mBodyB, contact.mSubShapeIDB, contact.mPosition, -contact.mNormal, settings);
  236. contact.mCanPushCharacter = settings.mCanPushCharacter;
  237. // If body B cannot receive an impulse, we're done
  238. if (!settings.mCanReceiveImpulses || contact.mMotionTypeB != EMotionType::Dynamic)
  239. return true;
  240. // Lock the body we're colliding with
  241. BodyLockWrite lock(mSystem->GetBodyLockInterface(), contact.mBodyB);
  242. if (!lock.SucceededAndIsInBroadPhase())
  243. return false; // Body has been removed, we should not collide with it anymore
  244. const Body &body = lock.GetBody();
  245. // Calculate the velocity that we want to apply at B so that it will start moving at the character's speed at the contact point
  246. constexpr float cDamping = 0.9f;
  247. constexpr float cPenetrationResolution = 0.4f;
  248. Vec3 relative_velocity = inVelocity - contact.mLinearVelocity;
  249. float projected_velocity = relative_velocity.Dot(contact.mNormal);
  250. float delta_velocity = -projected_velocity * cDamping - min(contact.mDistance, 0.0f) * cPenetrationResolution / inDeltaTime;
  251. // Don't apply impulses if we're separating
  252. if (delta_velocity < 0.0f)
  253. return true;
  254. // Determine mass properties of the body we're colliding with
  255. const MotionProperties *motion_properties = body.GetMotionProperties();
  256. Vec3 center_of_mass = body.GetCenterOfMassPosition();
  257. Mat44 inverse_inertia = body.GetInverseInertia();
  258. float inverse_mass = motion_properties->GetInverseMass();
  259. // Calculate the inverse of the mass of body B as seen at the contact point in the direction of the contact normal
  260. Vec3 jacobian = (contact.mPosition - center_of_mass).Cross(contact.mNormal);
  261. float inv_effective_mass = inverse_inertia.Multiply3x3(jacobian).Dot(jacobian) + inverse_mass;
  262. // Impulse P = M dv
  263. float impulse = delta_velocity / inv_effective_mass;
  264. // Clamp the impulse according to the character strength, character strength is a force in newtons, P = F dt
  265. float max_impulse = mMaxStrength * inDeltaTime;
  266. impulse = min(impulse, max_impulse);
  267. // Calculate the world space impulse to apply
  268. Vec3 world_impulse = -impulse * contact.mNormal;
  269. // Add the impulse due to gravity working on the player: P = F dt = M g dt
  270. float normal_dot_gravity = contact.mNormal.Dot(inGravity);
  271. if (normal_dot_gravity < 0.0f)
  272. world_impulse -= (mMass * normal_dot_gravity / inGravity.Length() * inDeltaTime) * inGravity;
  273. // Now apply the impulse (body is already locked so we use the no-lock interface)
  274. mSystem->GetBodyInterfaceNoLock().AddImpulse(contact.mBodyB, world_impulse, contact.mPosition);
  275. return true;
  276. }
  277. void CharacterVirtual::SolveConstraints(Vec3Arg inVelocity, Vec3Arg inGravity, float inDeltaTime, float inTimeRemaining, ConstraintList &ioConstraints, IgnoredContactList &ioIgnoredContacts, float &outTimeSimulated, Vec3 &outDisplacement, TempAllocator &inAllocator) const
  278. {
  279. // If there are no constraints we can immediately move to our target
  280. if (ioConstraints.empty())
  281. {
  282. outDisplacement = inVelocity * inTimeRemaining;
  283. outTimeSimulated = inTimeRemaining;
  284. return;
  285. }
  286. // Create array that holds the constraints in order of time of impact (sort will happen later)
  287. vector<Constraint *, STLTempAllocator<Constraint *>> sorted_constraints(inAllocator);
  288. sorted_constraints.resize(ioConstraints.size());
  289. for (size_t index = 0; index < sorted_constraints.size(); index++)
  290. sorted_constraints[index] = &ioConstraints[index];
  291. // This is the velocity we use for the displacement, if we hit something it will be shortened
  292. Vec3 velocity = inVelocity;
  293. // Start with no displacement
  294. outDisplacement = Vec3::sZero();
  295. outTimeSimulated = 0.0f;
  296. // These are the contacts that we hit previously without moving a significant distance
  297. vector<Constraint *, STLTempAllocator<Constraint *>> previous_contacts(inAllocator);
  298. previous_contacts.resize(mMaxConstraintIterations);
  299. int num_previous_contacts = 0;
  300. // Loop for a max amount of iterations
  301. for (uint iteration = 0; iteration < mMaxConstraintIterations; iteration++)
  302. {
  303. // Calculate time of impact for all constraints
  304. for (Constraint &c : ioConstraints)
  305. {
  306. // Project velocity on plane direction
  307. c.mProjectedVelocity = c.mPlane.GetNormal().Dot(c.mLinearVelocity - velocity);
  308. if (c.mProjectedVelocity < 1.0e-6f)
  309. {
  310. c.mTOI = FLT_MAX;
  311. }
  312. else
  313. {
  314. // Distance to plane
  315. float dist = c.mPlane.SignedDistance(outDisplacement);
  316. if (dist - c.mProjectedVelocity * inTimeRemaining > -1.0e-4f)
  317. {
  318. // Too little penetration, accept the movement
  319. c.mTOI = FLT_MAX;
  320. }
  321. else
  322. {
  323. // Calculate time of impact
  324. c.mTOI = max(0.0f, dist / c.mProjectedVelocity);
  325. }
  326. }
  327. }
  328. // Sort constraints on proximity
  329. sort(sorted_constraints.begin(), sorted_constraints.end(), [](const Constraint *inLHS, const Constraint *inRHS) {
  330. // If both constraints hit at t = 0 then order the one that will push the character furthest first
  331. // Note that because we add velocity to penetrating contacts, this will also resolve contacts that penetrate the most
  332. if (inLHS->mTOI <= 0.0f && inRHS->mTOI <= 0.0f)
  333. return inLHS->mProjectedVelocity > inRHS->mProjectedVelocity;
  334. // Then sort on time of impact
  335. if (inLHS->mTOI != inRHS->mTOI)
  336. return inLHS->mTOI < inRHS->mTOI;
  337. // As a tie breaker sort static first so it has the most influence
  338. return inLHS->mContact->mMotionTypeB > inRHS->mContact->mMotionTypeB;
  339. });
  340. // Find the first valid constraint
  341. Constraint *constraint = nullptr;
  342. for (Constraint *c : sorted_constraints)
  343. {
  344. // Take the first contact and see if we can reach it
  345. if (c->mTOI >= inTimeRemaining)
  346. {
  347. // We can reach our goal!
  348. outDisplacement += velocity * inTimeRemaining;
  349. outTimeSimulated += inTimeRemaining;
  350. return;
  351. }
  352. // Test if this contact was discarded by the contact callback before
  353. if (c->mContact->mWasDiscarded)
  354. continue;
  355. // Check if we made contact with this before
  356. if (!c->mContact->mHadCollision)
  357. {
  358. // Handle the contact
  359. if (!HandleContact(velocity, *c, inGravity, inDeltaTime))
  360. {
  361. // Constraint should be ignored, remove it from the list
  362. c->mContact->mWasDiscarded = true;
  363. // Mark it as ignored for GetFirstContactForSweep
  364. ioIgnoredContacts.emplace_back(c->mContact->mBodyB, c->mContact->mSubShapeIDB);
  365. continue;
  366. }
  367. c->mContact->mHadCollision = true;
  368. }
  369. // Cancel velocity of constraint if it cannot push the character
  370. if (!c->mContact->mCanPushCharacter)
  371. c->mLinearVelocity = Vec3::sZero();
  372. // We found the first constraint that we want to collide with
  373. constraint = c;
  374. break;
  375. }
  376. if (constraint == nullptr)
  377. {
  378. // All constraints were discarded, we can reach our goal!
  379. outDisplacement += velocity * inTimeRemaining;
  380. outTimeSimulated += inTimeRemaining;
  381. return;
  382. }
  383. // Move to the contact
  384. outDisplacement += velocity * constraint->mTOI;
  385. inTimeRemaining -= constraint->mTOI;
  386. outTimeSimulated += constraint->mTOI;
  387. // If there's not enough time left to be simulated, bail
  388. if (inTimeRemaining < mMinTimeRemaining)
  389. return;
  390. // If we've moved significantly, clear all previous contacts
  391. if (constraint->mTOI > 1.0e-4f)
  392. num_previous_contacts = 0;
  393. // Get the normal of the plane we're hitting
  394. Vec3 plane_normal = constraint->mPlane.GetNormal();
  395. // Get the relative velocity between the character and the constraint
  396. Vec3 relative_velocity = velocity - constraint->mLinearVelocity;
  397. // Calculate new velocity if we cancel the relative velocity in the normal direction
  398. Vec3 new_velocity = velocity - relative_velocity.Dot(plane_normal) * plane_normal;
  399. // Find the normal of the previous contact that we will violate the most if we move in this new direction
  400. float highest_penetration = 0.0f;
  401. Constraint *other_constraint = nullptr;
  402. for (Constraint **c = previous_contacts.data(); c < previous_contacts.data() + num_previous_contacts; ++c)
  403. if (*c != constraint)
  404. {
  405. // Calculate how much we will penetrate if we move in this direction
  406. Vec3 other_normal = (*c)->mPlane.GetNormal();
  407. float penetration = ((*c)->mLinearVelocity - new_velocity).Dot(other_normal);
  408. if (penetration > highest_penetration)
  409. {
  410. // We don't want parallel or anti-parallel normals as that will cause our cross product below to become zero. Slack is approx 10 degrees.
  411. float dot = other_normal.Dot(plane_normal);
  412. if (dot < 0.984f && dot > -0.984f)
  413. {
  414. highest_penetration = penetration;
  415. other_constraint = *c;
  416. }
  417. }
  418. }
  419. // Check if we found a 2nd constraint
  420. if (other_constraint != nullptr)
  421. {
  422. // Calculate the sliding direction and project the new velocity onto that sliding direction
  423. Vec3 other_normal = other_constraint->mPlane.GetNormal();
  424. Vec3 slide_dir = plane_normal.Cross(other_normal).Normalized();
  425. Vec3 velocity_in_slide_dir = new_velocity.Dot(slide_dir) * slide_dir;
  426. // Cancel the constraint velocity in the other constraint plane's direction so that we won't try to apply it again and keep ping ponging between planes
  427. constraint->mLinearVelocity -= min(0.0f, constraint->mLinearVelocity.Dot(other_normal)) * other_normal;
  428. // Cancel the other constraints velocity in this constraint plane's direction so that we won't try to apply it again and keep ping ponging between planes
  429. other_constraint->mLinearVelocity -= min(0.0f, other_constraint->mLinearVelocity.Dot(plane_normal)) * plane_normal;
  430. // Calculate the velocity of this constraint perpendicular to the slide direction
  431. Vec3 perpendicular_velocity = constraint->mLinearVelocity - constraint->mLinearVelocity.Dot(slide_dir) * slide_dir;
  432. // Calculate the velocity of the other constraint perpendicular to the slide direction
  433. Vec3 other_perpendicular_velocity = other_constraint->mLinearVelocity - other_constraint->mLinearVelocity.Dot(slide_dir) * slide_dir;
  434. // Add all components together
  435. velocity = velocity_in_slide_dir + perpendicular_velocity + other_perpendicular_velocity;
  436. }
  437. else
  438. {
  439. // Update the velocity
  440. velocity = new_velocity;
  441. }
  442. // Add the contact to the list so that next iteration we can avoid violating it again
  443. previous_contacts[num_previous_contacts] = constraint;
  444. num_previous_contacts++;
  445. // If there's not enough velocity left, bail
  446. if (velocity.LengthSq() < 1.0e-8f)
  447. return;
  448. }
  449. }
  450. void CharacterVirtual::UpdateSupportingContact(TempAllocator &inAllocator)
  451. {
  452. // Flag contacts as having a collision if they're close enough.
  453. // Note that if we did MoveShape before we want to preserve any contacts that it marked as colliding
  454. for (Contact &c : mActiveContacts)
  455. if (!c.mWasDiscarded)
  456. c.mHadCollision |= c.mDistance < mCollisionTolerance;
  457. // Determine if we're supported or not
  458. int num_supported = 0;
  459. int num_sliding = 0;
  460. int num_avg_normal = 0;
  461. Vec3 avg_normal = Vec3::sZero();
  462. Vec3 avg_velocity = Vec3::sZero();
  463. const Contact *supporting_contact = nullptr;
  464. float max_cos_angle = -FLT_MAX;
  465. for (const Contact &c : mActiveContacts)
  466. if (c.mHadCollision)
  467. {
  468. // Calculate the angle between the plane normal and the up direction
  469. float cos_angle = c.mNormal.Dot(mUp);
  470. // Find the contact with the normal that is pointing most upwards and store it in mSupportingContact
  471. if (max_cos_angle < cos_angle)
  472. {
  473. supporting_contact = &c;
  474. max_cos_angle = cos_angle;
  475. }
  476. // Check if this is a sliding or supported contact
  477. bool is_supported = cos_angle >= mCosMaxSlopeAngle;
  478. if (is_supported)
  479. num_supported++;
  480. else
  481. num_sliding++;
  482. // If the angle between the two is less than 85 degrees we also use it to calculate the average normal
  483. if (cos_angle >= 0.08f)
  484. {
  485. avg_normal += c.mNormal;
  486. num_avg_normal++;
  487. // For static or dynamic objects or for contacts that don't support us just take the contact velocity
  488. if (c.mMotionTypeB != EMotionType::Kinematic || !is_supported)
  489. avg_velocity += c.mLinearVelocity;
  490. else
  491. {
  492. // For keyframed objects that support us calculate the velocity at our position rather than at the contact position so that we properly follow the object
  493. // Note that we don't just take the point velocity because a point on an object with angular velocity traces an arc,
  494. // so if you just take point velocity * delta time you get an error that accumulates over time
  495. // Determine center of mass and angular velocity
  496. Vec3 angular_velocity, com;
  497. {
  498. BodyLockRead lock(mSystem->GetBodyLockInterface(), c.mBodyB);
  499. if (lock.SucceededAndIsInBroadPhase())
  500. {
  501. const Body &body = lock.GetBody();
  502. // Add the linear velocity to the average velocity
  503. avg_velocity += body.GetLinearVelocity();
  504. angular_velocity = body.GetAngularVelocity();
  505. com = body.GetCenterOfMassPosition();
  506. }
  507. else
  508. {
  509. angular_velocity = Vec3::sZero();
  510. com = Vec3::sZero();
  511. }
  512. }
  513. // Get angular velocity
  514. float angular_velocity_len_sq = angular_velocity.LengthSq();
  515. if (angular_velocity_len_sq > 1.0e-12f)
  516. {
  517. float angular_velocity_len = sqrt(angular_velocity_len_sq);
  518. // Calculate the rotation that the object will make in the time step
  519. Quat rotation = Quat::sRotation(angular_velocity / angular_velocity_len, angular_velocity_len * mLastDeltaTime);
  520. // Calculate where the new contact position will be
  521. Vec3 new_position = com + rotation * (mPosition - com);
  522. // Calculate the velocity
  523. avg_velocity += (new_position - mPosition) / mLastDeltaTime;
  524. }
  525. }
  526. }
  527. }
  528. // Calculate average normal and velocity
  529. if (num_avg_normal >= 1)
  530. {
  531. mGroundNormal = avg_normal.Normalized();
  532. mGroundVelocity = avg_velocity / float(num_avg_normal);
  533. }
  534. else
  535. {
  536. mGroundNormal = Vec3::sZero();
  537. mGroundVelocity = Vec3::sZero();
  538. }
  539. // Copy supporting contact properties
  540. if (supporting_contact != nullptr)
  541. {
  542. mGroundBodyID = supporting_contact->mBodyB;
  543. mGroundBodySubShapeID = supporting_contact->mSubShapeIDB;
  544. mGroundPosition = supporting_contact->mPosition;
  545. mGroundMaterial = supporting_contact->mMaterial;
  546. mGroundUserData = supporting_contact->mUserData;
  547. }
  548. else
  549. {
  550. mGroundBodyID = BodyID();
  551. mGroundBodySubShapeID = SubShapeID();
  552. mGroundPosition = Vec3::sZero();
  553. mGroundMaterial = PhysicsMaterial::sDefault;
  554. mGroundUserData = 0;
  555. }
  556. // Determine ground state
  557. if (num_supported > 0)
  558. {
  559. // We made contact with something that supports us
  560. mGroundState = EGroundState::OnGround;
  561. }
  562. else if (num_sliding > 0)
  563. {
  564. // If we're sliding we may actually be standing on multiple sliding contacts in such a way that we can't slide off, in this case we're also supported
  565. // Convert the contacts into constraints
  566. TempContactList contacts(mActiveContacts.begin(), mActiveContacts.end(), inAllocator);
  567. ConstraintList constraints(inAllocator);
  568. constraints.reserve(contacts.size() * 2);
  569. DetermineConstraints(contacts, constraints);
  570. // Solve the displacement using these constraints, this is used to check if we didn't move at all because we are supported
  571. Vec3 displacement;
  572. float time_simulated;
  573. IgnoredContactList ignored_contacts(inAllocator);
  574. ignored_contacts.reserve(contacts.size());
  575. SolveConstraints(-mUp, mSystem->GetGravity(), 1.0f, 1.0f, constraints, ignored_contacts, time_simulated, displacement, inAllocator);
  576. // If we're blocked then we're supported, otherwise we're sliding
  577. constexpr float cMinRequiredDisplacementSquared = Square(0.01f);
  578. if (time_simulated < 0.001f || displacement.LengthSq() < cMinRequiredDisplacementSquared)
  579. mGroundState = EGroundState::OnGround;
  580. else
  581. mGroundState = EGroundState::Sliding;
  582. }
  583. else
  584. {
  585. // Not in contact with anything
  586. mGroundState = EGroundState::InAir;
  587. }
  588. }
  589. void CharacterVirtual::StoreActiveContacts(const TempContactList &inContacts, TempAllocator &inAllocator)
  590. {
  591. mActiveContacts.assign(inContacts.begin(), inContacts.end());
  592. UpdateSupportingContact(inAllocator);
  593. }
  594. void CharacterVirtual::MoveShape(Vec3 &ioPosition, Vec3Arg inVelocity, Vec3Arg inGravity, float inDeltaTime, ContactList *outActiveContacts, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, TempAllocator &inAllocator) const
  595. {
  596. Vec3 movement_direction = inVelocity.NormalizedOr(Vec3::sZero());
  597. float time_remaining = inDeltaTime;
  598. for (uint iteration = 0; iteration < mMaxCollisionIterations && time_remaining >= mMinTimeRemaining; iteration++)
  599. {
  600. // Determine contacts in the neighborhood
  601. TempContactList contacts(inAllocator);
  602. contacts.reserve(mMaxNumHits);
  603. GetContactsAtPosition(ioPosition, movement_direction, mShape, contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter);
  604. // Remove contacts with the same body that have conflicting normals
  605. IgnoredContactList ignored_contacts(inAllocator);
  606. ignored_contacts.reserve(contacts.size());
  607. RemoveConflictingContacts(contacts, ignored_contacts);
  608. // Convert contacts into constraints
  609. ConstraintList constraints(inAllocator);
  610. constraints.reserve(contacts.size() * 2);
  611. DetermineConstraints(contacts, constraints);
  612. #ifdef JPH_DEBUG_RENDERER
  613. if (sDrawConstraints && iteration == 0)
  614. {
  615. for (const Constraint &c : constraints)
  616. {
  617. // Draw contact point
  618. DebugRenderer::sInstance->DrawMarker(c.mContact->mPosition, Color::sYellow, 0.05f);
  619. Vec3 dist_to_plane = -c.mPlane.GetConstant() * c.mPlane.GetNormal();
  620. // Draw arrow towards surface that we're hitting
  621. DebugRenderer::sInstance->DrawArrow(c.mContact->mPosition, c.mContact->mPosition - dist_to_plane, Color::sYellow, 0.05f);
  622. // Draw plane around the player posiiton indicating the space that we can move
  623. DebugRenderer::sInstance->DrawPlane(mPosition + dist_to_plane, c.mPlane.GetNormal(), Color::sCyan, 1.0f);
  624. }
  625. }
  626. #endif // JPH_DEBUG_RENDERER
  627. // Solve the displacement using these constraints
  628. Vec3 displacement;
  629. float time_simulated;
  630. SolveConstraints(inVelocity, inGravity, inDeltaTime, time_remaining, constraints, ignored_contacts, time_simulated, displacement, inAllocator);
  631. // Store the contacts now that the colliding ones have been marked
  632. if (outActiveContacts != nullptr)
  633. outActiveContacts->assign(contacts.begin(), contacts.end());
  634. // Do a sweep to test if the path is really unobstructed
  635. Contact cast_contact;
  636. if (GetFirstContactForSweep(ioPosition, displacement, cast_contact, ignored_contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inAllocator))
  637. {
  638. displacement *= cast_contact.mFraction;
  639. time_simulated *= cast_contact.mFraction;
  640. }
  641. // Update the position
  642. ioPosition += displacement;
  643. time_remaining -= time_simulated;
  644. // If the displacement during this iteration was too small we assume we cannot further progress this update
  645. if (displacement.LengthSq() < 1.0e-8f)
  646. break;
  647. }
  648. }
  649. void CharacterVirtual::Update(float inDeltaTime, Vec3Arg inGravity, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, TempAllocator &inAllocator)
  650. {
  651. // If there's no delta time, we don't need to do anything
  652. if (inDeltaTime <= 0.0f)
  653. return;
  654. // Remember delta time for checking if we're supported by the ground
  655. mLastDeltaTime = inDeltaTime;
  656. // Slide the shape through the world
  657. MoveShape(mPosition, mLinearVelocity, inGravity, inDeltaTime, &mActiveContacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inAllocator);
  658. // Determine the object that we're standing on
  659. UpdateSupportingContact(inAllocator);
  660. }
  661. void CharacterVirtual::RefreshContacts(const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, TempAllocator &inAllocator)
  662. {
  663. // Determine the contacts
  664. TempContactList contacts(inAllocator);
  665. contacts.reserve(mMaxNumHits);
  666. GetContactsAtPosition(mPosition, mLinearVelocity.NormalizedOr(Vec3::sZero()), mShape, contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter);
  667. StoreActiveContacts(contacts, inAllocator);
  668. }
  669. bool CharacterVirtual::SetShape(const Shape *inShape, float inMaxPenetrationDepth, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, TempAllocator &inAllocator)
  670. {
  671. if (mShape == nullptr || mSystem == nullptr)
  672. {
  673. // It hasn't been initialized yet
  674. mShape = inShape;
  675. return true;
  676. }
  677. if (inShape != mShape && inShape != nullptr)
  678. {
  679. if (inMaxPenetrationDepth < FLT_MAX)
  680. {
  681. // Check collision around the new shape
  682. TempContactList contacts(inAllocator);
  683. contacts.reserve(mMaxNumHits);
  684. GetContactsAtPosition(mPosition, mLinearVelocity.NormalizedOr(Vec3::sZero()), inShape, contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter);
  685. // Test if this results in penetration, if so cancel the transition
  686. for (const Contact &c : contacts)
  687. if (c.mDistance < -inMaxPenetrationDepth)
  688. return false;
  689. StoreActiveContacts(contacts, inAllocator);
  690. }
  691. // Set new shape
  692. mShape = inShape;
  693. }
  694. return mShape == inShape;
  695. }
  696. void CharacterVirtual::SaveState(StateRecorder &inStream) const
  697. {
  698. CharacterBase::SaveState(inStream);
  699. inStream.Write(mPosition);
  700. inStream.Write(mRotation);
  701. inStream.Write(mLinearVelocity);
  702. }
  703. void CharacterVirtual::RestoreState(StateRecorder &inStream)
  704. {
  705. CharacterBase::RestoreState(inStream);
  706. inStream.Read(mPosition);
  707. inStream.Read(mRotation);
  708. inStream.Read(mLinearVelocity);
  709. }
  710. JPH_NAMESPACE_END