@series-inc/rundot-syncplay 6.0.0-rc.53 → 6.0.0-rc.55

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -811,7 +811,7 @@ double boxHalfExtent(const Body& body, std::uint32_t axis) {
811
811
  return body.shape2;
812
812
  }
813
813
 
814
- void populateBoxContactPoints(const Body& a, const Body& b, Collision& collision) {
814
+ void populateBoxContactPoints(const Body& a, const Body& b, Collision& collision, bool isSmallWorld = false) {
815
815
  if (!collision.hit) return;
816
816
  const Vec3 normal{collision.nx, collision.ny, collision.nz};
817
817
  std::uint32_t referenceAxisA = 0;
@@ -837,7 +837,7 @@ void populateBoxContactPoints(const Body& a, const Body& b, Collision& collision
837
837
  return;
838
838
  }
839
839
 
840
- const bool referenceIsA = alignmentA >= alignmentB;
840
+ const bool referenceIsA = isSmallWorld ? (alignmentA + 1e-5 >= alignmentB) : (alignmentA >= alignmentB);
841
841
  const Body& reference = referenceIsA ? a : b;
842
842
  const Body& incident = referenceIsA ? b : a;
843
843
  const std::uint32_t referenceAxis = referenceIsA
@@ -1065,11 +1065,11 @@ bool identityOrientation(const Body& body) {
1065
1065
  && body.orientationZ == 0.0 && body.orientationW == 1.0;
1066
1066
  }
1067
1067
 
1068
- Collision boxBox(const Body& a, const Body& b) {
1068
+ Collision boxBox(const Body& a, const Body& b, bool isSmallWorld = false) {
1069
1069
  Collision result = identityOrientation(a) && identityOrientation(b)
1070
1070
  ? boxBoxAxisAligned(a, b)
1071
1071
  : boxBoxOriented(a, b);
1072
- populateBoxContactPoints(a, b, result);
1072
+ populateBoxContactPoints(a, b, result, isSmallWorld);
1073
1073
  return result;
1074
1074
  }
1075
1075
 
@@ -3880,6 +3880,7 @@ struct SolverStorage {
3880
3880
  std::vector<ContactConstraintBlock> contactConstraints;
3881
3881
  std::vector<double> contactCacheOutput;
3882
3882
  std::vector<std::uint8_t> contactSupported;
3883
+ std::vector<std::uint8_t> movingFoundation;
3883
3884
  std::vector<std::uint32_t> contactSolvePairBegin;
3884
3885
  std::vector<std::uint32_t> contactSolvePairEnd;
3885
3886
  std::vector<std::uint32_t> contactSolveTelemetryIndex;
@@ -4438,6 +4439,7 @@ void SolverStorage::invalidate() noexcept {
4438
4439
  contactConstraints.clear();
4439
4440
  contactCacheOutput.clear();
4440
4441
  contactSupported.clear();
4442
+ movingFoundation.clear();
4441
4443
  contactSolvePairCount = 0;
4442
4444
  contactSolvePairBegin.clear();
4443
4445
  contactSolvePairEnd.clear();
@@ -4491,6 +4493,7 @@ std::uint64_t SolverStorage::retainedBytes() const noexcept {
4491
4493
  + retainedVectorBytes(contactConstraints)
4492
4494
  + retainedVectorBytes(contactCacheOutput)
4493
4495
  + retainedVectorBytes(contactSupported)
4496
+ + retainedVectorBytes(movingFoundation)
4494
4497
  + retainedVectorBytes(contactSolvePairBegin)
4495
4498
  + retainedVectorBytes(contactSolvePairEnd)
4496
4499
  + retainedVectorBytes(contactSolveTelemetryIndex)
@@ -4564,13 +4567,16 @@ void prepareStepStorage(SolverStorage& storage, std::uint32_t bodyCount, std::ui
4564
4567
  storage.jointConstraintState.resize(jointCount);
4565
4568
  storage.contactConstraints.clear();
4566
4569
  storage.contactSupported.resize(bodyCount);
4570
+ storage.movingFoundation.resize(bodyCount);
4567
4571
  storage.contactSolvePairBegin.resize(pairCapacity);
4568
4572
  storage.contactSolvePairEnd.resize(pairCapacity);
4569
4573
  storage.contactSolveTelemetryIndex.resize(pairCapacity);
4570
4574
  storage.contactSolveOutputIndex.resize(pairCapacity);
4571
4575
  storage.maximumContactConstraintCount = static_cast<std::uint32_t>(
4572
4576
  std::min<std::uint64_t>(
4573
- static_cast<std::uint64_t>(pairCapacity) * 4,
4577
+ (bodyCount <= 16)
4578
+ ? std::max<std::uint64_t>(32, static_cast<std::uint64_t>(pairCapacity) * 8)
4579
+ : (static_cast<std::uint64_t>(pairCapacity) * 4),
4574
4580
  static_cast<std::uint64_t>(kContactTelemetryCapacity) * 4));
4575
4581
  #if defined(RUNDOT_TEST_CONTACT_CONSTRAINT_CAPACITY)
4576
4582
  storage.maximumContactConstraintCount = std::min<std::uint32_t>(
@@ -5148,7 +5154,11 @@ Collision collide(SolverOperation& operation, const Body& a, const Body& b) {
5148
5154
  return flipCollision(result);
5149
5155
  }
5150
5156
  if (a.shape == ShapeKind::Box && b.shape == ShapeKind::Box) {
5151
- return boxBox(a, b);
5157
+ const bool isSmallWorld = !operation.fixedAxisQueryContacts
5158
+ && operation.proxyHook == nullptr
5159
+ && operation.storage.soaBodies.size() > 0
5160
+ && operation.storage.soaBodies.size() <= 16;
5161
+ return boxBox(a, b, isSmallWorld);
5152
5162
  }
5153
5163
 
5154
5164
  if (!robustConvexBodiesOverlap(operation, a, b)) return {};
@@ -9892,11 +9902,58 @@ void captureStartingWarmApplication(SolverOperation& operation, std::uint32_t fi
9892
9902
 
9893
9903
  PreparedWarmContactApplication prepareWarmContactApplication(const Body& a, const Body& b,
9894
9904
  bool movableA, bool movableB, const Vec3& rA, const Vec3& rB, const Vec3& normal,
9895
- const std::array<double, 3>& cachedImpulses) {
9905
+ const std::array<double, 3>& cachedImpulses, bool isSmallWorld = false) {
9896
9906
  const auto [normalImpulse, tangentImpulse1, tangentImpulse2] = cachedImpulses;
9897
9907
  Vec3 tangent1;
9898
9908
  Vec3 tangent2;
9899
9909
  contactTangentBasis(normal, tangent1, tangent2);
9910
+
9911
+ if (isSmallWorld) {
9912
+ const bool isPlanarPair = (a.shape == ShapeKind::Box && b.shape == ShapeKind::Box);
9913
+ const double relVx = a.vx - b.vx;
9914
+ const double relVy = a.vy - b.vy;
9915
+ const double relVz = a.vz - b.vz;
9916
+ const double relHorizSpeedSq = relVx * relVx + relVz * relVz;
9917
+ const bool isDynamicMotion = (a.dynamic && a.vy < -1.5) || (b.dynamic && b.vy < -1.5);
9918
+ const double closingVelocity = -dotVec3(contactRelativeVelocity(a, b, rA, rB), normal);
9919
+ const bool useRestingPlanar = isPlanarPair && !isDynamicMotion && std::abs(normal.y) >= 0.7
9920
+ && closingVelocity >= -0.35;
9921
+
9922
+ double appliedNormal = 0.0;
9923
+ double appliedTangent1 = 0.0;
9924
+ double appliedTangent2 = 0.0;
9925
+
9926
+ if (useRestingPlanar) {
9927
+ appliedNormal = std::max(0.0, normalImpulse);
9928
+ if (relHorizSpeedSq < 0.55 * 0.55) {
9929
+ appliedTangent1 = tangentImpulse1;
9930
+ appliedTangent2 = tangentImpulse2;
9931
+ }
9932
+ } else {
9933
+ const double normalMass = effectiveContactMass(a, b, movableA, movableB, rA, rB, normal);
9934
+ appliedNormal = normalMass > 0.0
9935
+ ? std::min(normalImpulse, std::max(0.0, closingVelocity / normalMass))
9936
+ : 0.0;
9937
+ const double cacheScale = normalImpulse > 0.0 ? appliedNormal / normalImpulse : 0.0;
9938
+ appliedTangent1 = tangentImpulse1 * cacheScale;
9939
+ appliedTangent2 = tangentImpulse2 * cacheScale;
9940
+ }
9941
+ const double tangentMax = std::max(axisDelta(appliedTangent1), axisDelta(appliedTangent2));
9942
+ if (tangentMax > 0.0) {
9943
+ const double scaled1 = appliedTangent1 / tangentMax;
9944
+ const double scaled2 = appliedTangent2 / tangentMax;
9945
+ const double scaledLength = solverSqrt(scaled1 * scaled1 + scaled2 * scaled2);
9946
+ const double limit = solverSqrt(std::max(0.0, a.friction * b.friction)) * appliedNormal;
9947
+ if (tangentMax > limit / scaledLength) {
9948
+ appliedTangent1 = scaled1 * (limit / scaledLength);
9949
+ appliedTangent2 = scaled2 * (limit / scaledLength);
9950
+ }
9951
+ }
9952
+ const Vec3 impulse = addVec3(scaleVec3(normal, appliedNormal),
9953
+ addVec3(scaleVec3(tangent1, appliedTangent1), scaleVec3(tangent2, appliedTangent2)));
9954
+ return {impulse, {appliedNormal, appliedTangent1, appliedTangent2}};
9955
+ }
9956
+
9900
9957
  const double normalMass = effectiveContactMass(a, b, movableA, movableB, rA, rB, normal);
9901
9958
  const double closingVelocity = -dotVec3(contactRelativeVelocity(a, b, rA, rB), normal);
9902
9959
  const double appliedNormal = normalMass > 0.0
@@ -9933,6 +9990,7 @@ void warmStartSoAContacts(SolverOperation& operation, Body* bodies, std::uint32_
9933
9990
  std::uint32_t cachedFirst = std::numeric_limits<std::uint32_t>::max();
9934
9991
  std::uint32_t cachedSecond = std::numeric_limits<std::uint32_t>::max();
9935
9992
  Collision cachedContact;
9993
+ std::uint32_t usedPointMask = 0;
9936
9994
  for (std::uint32_t offset = 0; offset + cacheStride <= operation.inputs.contactCache.size(); offset += cacheStride) {
9937
9995
  const double firstValue = operation.inputs.contactCache[offset];
9938
9996
  const double secondValue = operation.inputs.contactCache[offset + 1];
@@ -9957,19 +10015,54 @@ void warmStartSoAContacts(SolverOperation& operation, Body* bodies, std::uint32_
9957
10015
  cachedFirst = orderedFirst;
9958
10016
  cachedSecond = orderedSecond;
9959
10017
  cachedContact = collide(operation, a, b);
10018
+ usedPointMask = 0;
9960
10019
  }
9961
10020
  const Collision& contact = cachedContact;
9962
10021
  if (!contact.hit) continue;
9963
10022
  const std::uint32_t cachedFeatureId = static_cast<std::uint32_t>(featureValue);
9964
10023
  std::uint32_t pointIndex = contactPointCount(contact);
9965
- for (std::uint32_t candidate = 0; candidate < contactPointCount(contact); candidate += 1) {
9966
- if (contactFeatureId(contact, candidate) == cachedFeatureId) {
9967
- pointIndex = candidate;
9968
- break;
10024
+ if (bodyCount <= 16 && operation.proxyHook == nullptr) {
10025
+ for (std::uint32_t candidate = 0; candidate < contactPointCount(contact); candidate += 1) {
10026
+ if ((usedPointMask & (1u << candidate)) != 0) continue;
10027
+ if (contactFeatureId(contact, candidate) == cachedFeatureId) {
10028
+ pointIndex = candidate;
10029
+ break;
10030
+ }
9969
10031
  }
10032
+ if (cachedFeatureId == 0 && contact.pointCount != 0 && (usedPointMask & 1u) == 0) {
10033
+ pointIndex = 0;
10034
+ }
10035
+ const Vec3 cachedPoint{
10036
+ operation.inputs.contactCache[offset + 6],
10037
+ operation.inputs.contactCache[offset + 7],
10038
+ operation.inputs.contactCache[offset + 8],
10039
+ };
10040
+ if (pointIndex == contactPointCount(contact) && finite(cachedPoint.x) && finite(cachedPoint.y) && finite(cachedPoint.z)) {
10041
+ constexpr double kMaxCornerDriftSquared = 0.02 * 0.02;
10042
+ double bestDistanceSquared = kMaxCornerDriftSquared;
10043
+ for (std::uint32_t candidate = 0; candidate < contactPointCount(contact); candidate += 1) {
10044
+ if ((usedPointMask & (1u << candidate)) != 0) continue;
10045
+ const Vec3 candidatePoint = contactPoint(contact, a, b, candidate);
10046
+ const Vec3 delta = subtractVec3(candidatePoint, cachedPoint);
10047
+ const double distanceSquared = dotVec3(delta, delta);
10048
+ if (distanceSquared <= bestDistanceSquared) {
10049
+ bestDistanceSquared = distanceSquared;
10050
+ pointIndex = candidate;
10051
+ }
10052
+ }
10053
+ }
10054
+ if (pointIndex == contactPointCount(contact)) continue;
10055
+ usedPointMask |= (1u << pointIndex);
10056
+ } else {
10057
+ for (std::uint32_t candidate = 0; candidate < contactPointCount(contact); candidate += 1) {
10058
+ if (contactFeatureId(contact, candidate) == cachedFeatureId) {
10059
+ pointIndex = candidate;
10060
+ break;
10061
+ }
10062
+ }
10063
+ if (cachedFeatureId == 0 && contact.pointCount != 0) pointIndex = 0;
10064
+ if (pointIndex == contactPointCount(contact)) continue;
9970
10065
  }
9971
- if (cachedFeatureId == 0 && contact.pointCount != 0) pointIndex = 0;
9972
- if (pointIndex == contactPointCount(contact)) continue;
9973
10066
  const std::uint32_t featureId = contactFeatureId(contact, pointIndex);
9974
10067
  Vec3 point = contactPoint(contact, a, b, pointIndex);
9975
10068
  if (cachedFeatureId == 0
@@ -10002,7 +10095,7 @@ void warmStartSoAContacts(SolverOperation& operation, Body* bodies, std::uint32_
10002
10095
  const Vec3 rA=relative[0],rB=relative[1];
10003
10096
  const Vec3 normal{contact.nx, contact.ny, contact.nz};
10004
10097
  const auto prepared = prepareWarmContactApplication(a, b, movableA, movableB,
10005
- rA, rB, normal, {normalImpulse, tangentImpulse1, tangentImpulse2});
10098
+ rA, rB, normal, {normalImpulse, tangentImpulse1, tangentImpulse2}, bodyCount <= 16 && operation.proxyHook == nullptr);
10006
10099
  if (operation.storage.contactConstraintCount
10007
10100
  >= operation.storage.maximumContactConstraintCount) {
10008
10101
  operation.storage.contactConstraintOverflow = true;
@@ -10183,19 +10276,41 @@ void resolveSoAContacts(SolverOperation& operation, Body* bodies, std::uint32_t
10183
10276
  const bool preparedSubstep = policy.preparedSubstep;
10184
10277
  const bool applyPositionCorrection = policy.applyPositionCorrection;
10185
10278
  const bool solveVelocity = policy.solveVelocity;
10279
+ const bool isSmallCandidateWorld = bodyCount <= 16 && operation.proxyHook == nullptr;
10186
10280
  if (beginSubstep) {
10187
10281
  operation.storage.sleepPairCount = 0;
10188
10282
  if (firstTickSubstep) {
10189
10283
  operation.storage.contactConstraints.clear();
10284
+ if (isSmallCandidateWorld) {
10285
+ operation.storage.contactConstraintCount = 0;
10286
+ }
10190
10287
  } else {
10191
- operation.storage.contactConstraints.erase(
10192
- std::remove_if(
10193
- operation.storage.contactConstraints.begin(),
10194
- operation.storage.contactConstraints.end(),
10195
- [](const ContactConstraintBlock& constraint) {
10196
- return !constraint.preserveForCache;
10197
- }),
10198
- operation.storage.contactConstraints.end());
10288
+ if (isSmallCandidateWorld) {
10289
+ if (solveVelocity) {
10290
+ operation.storage.contactConstraints.erase(
10291
+ std::remove_if(
10292
+ operation.storage.contactConstraints.begin(),
10293
+ operation.storage.contactConstraints.end(),
10294
+ [](const ContactConstraintBlock& constraint) {
10295
+ if (constraint.preserveForCache) return false;
10296
+ if (!constraint.activeForVelocityPhase) return true;
10297
+ return std::abs(constraint.normal[1]) < 0.7;
10298
+ }),
10299
+ operation.storage.contactConstraints.end());
10300
+ for (auto& constraint : operation.storage.contactConstraints) {
10301
+ constraint.activeForVelocityPhase = false;
10302
+ }
10303
+ }
10304
+ } else {
10305
+ operation.storage.contactConstraints.erase(
10306
+ std::remove_if(
10307
+ operation.storage.contactConstraints.begin(),
10308
+ operation.storage.contactConstraints.end(),
10309
+ [](const ContactConstraintBlock& constraint) {
10310
+ return !constraint.preserveForCache;
10311
+ }),
10312
+ operation.storage.contactConstraints.end());
10313
+ }
10199
10314
  }
10200
10315
  operation.storage.contactConstraintCount = static_cast<std::uint32_t>(
10201
10316
  operation.storage.contactConstraints.size());
@@ -10215,6 +10330,193 @@ void resolveSoAContacts(SolverOperation& operation, Body* bodies, std::uint32_t
10215
10330
  || (left.bodyB == right.bodyB && left.featureId < right.featureId)));
10216
10331
  });
10217
10332
  }
10333
+ if (isSmallCandidateWorld) {
10334
+ std::sort(operation.storage.contactConstraints.begin(),
10335
+ operation.storage.contactConstraints.begin() + operation.storage.contactConstraintCount,
10336
+ [](const ContactConstraintBlock& left, const ContactConstraintBlock& right) {
10337
+ return left.bodyA < right.bodyA
10338
+ || (left.bodyA == right.bodyA && (left.bodyB < right.bodyB
10339
+ || (left.bodyB == right.bodyB && left.featureId < right.featureId)));
10340
+ });
10341
+ }
10342
+ if (beginSubstep && !firstTickSubstep && solveVelocity && isSmallCandidateWorld) {
10343
+ auto& movingFoundation = operation.storage.movingFoundation;
10344
+ if (movingFoundation.size() < bodyCount) {
10345
+ movingFoundation.resize(bodyCount, 0);
10346
+ } else {
10347
+ std::fill(movingFoundation.begin(), movingFoundation.begin() + bodyCount, 0);
10348
+ }
10349
+ for (std::uint32_t index = 0; index < operation.storage.contactConstraintCount; ++index) {
10350
+ const ContactConstraintBlock& constraint = operation.storage.contactConstraints[index];
10351
+ const std::uint32_t bA = constraint.bodyA;
10352
+ const std::uint32_t bB = constraint.bodyB;
10353
+ if (bA >= bodyCount || bB >= bodyCount) continue;
10354
+ if (std::abs(constraint.normal[1]) < 0.7) continue;
10355
+ const Body& a = bodies[bA];
10356
+ const Body& b = bodies[bB];
10357
+ if (!a.dynamic && b.dynamic && (b.vx * b.vx + b.vz * b.vz >= 0.01 * 0.01)) {
10358
+ movingFoundation[bB] = 1;
10359
+ } else if (!b.dynamic && a.dynamic && (a.vx * a.vx + a.vz * a.vz >= 0.01 * 0.01)) {
10360
+ movingFoundation[bA] = 1;
10361
+ }
10362
+ }
10363
+ for (std::uint32_t pass = 0; pass < 12; ++pass) {
10364
+ bool changed = false;
10365
+ for (std::uint32_t index = 0; index < operation.storage.contactConstraintCount; ++index) {
10366
+ const ContactConstraintBlock& constraint = operation.storage.contactConstraints[index];
10367
+ const std::uint32_t bA = constraint.bodyA;
10368
+ const std::uint32_t bB = constraint.bodyB;
10369
+ if (bA >= bodyCount || bB >= bodyCount) continue;
10370
+ if (std::abs(constraint.normal[1]) < 0.7) continue;
10371
+ const Body& a = bodies[bA];
10372
+ const Body& b = bodies[bB];
10373
+ if (a.y > b.y + 0.1 && movingFoundation[bB] && !movingFoundation[bA]) {
10374
+ movingFoundation[bA] = 1;
10375
+ changed = true;
10376
+ } else if (b.y > a.y + 0.1 && movingFoundation[bA] && !movingFoundation[bB]) {
10377
+ movingFoundation[bB] = 1;
10378
+ changed = true;
10379
+ }
10380
+ }
10381
+ if (!changed) break;
10382
+ }
10383
+
10384
+ std::uint32_t cachedFirst = std::numeric_limits<std::uint32_t>::max();
10385
+ std::uint32_t cachedSecond = std::numeric_limits<std::uint32_t>::max();
10386
+ Collision cachedContact{};
10387
+ std::uint32_t usedPointMask = 0;
10388
+ bool anyFeatureIdChanged = false;
10389
+ for (std::uint32_t index = 0; index < operation.storage.contactConstraintCount; ++index) {
10390
+ ContactConstraintBlock& constraint = operation.storage.contactConstraints[index];
10391
+ if (constraint.preserveForCache) continue;
10392
+ const std::uint32_t bodyA = constraint.bodyA;
10393
+ const std::uint32_t bodyB = constraint.bodyB;
10394
+ if (bodyA >= bodyCount || bodyB >= bodyCount) {
10395
+ constraint.impulses.fill(0.0);
10396
+ continue;
10397
+ }
10398
+ Body& a = bodies[bodyA];
10399
+ Body& b = bodies[bodyB];
10400
+ const bool movableA = a.dynamic && !a.sleeping;
10401
+ const bool movableB = b.dynamic && !b.sleeping;
10402
+ if (!movableA && !movableB) {
10403
+ constraint.impulses.fill(0.0);
10404
+ continue;
10405
+ }
10406
+ if (!bodiesCanCollide(a, b) || collisionDisabled(operation, bodyA, bodyB)) {
10407
+ constraint.impulses.fill(0.0);
10408
+ continue;
10409
+ }
10410
+ const bool isPlanarPair = (a.shape == ShapeKind::Box && b.shape == ShapeKind::Box);
10411
+ if (!isPlanarPair) {
10412
+ constraint.impulses.fill(0.0);
10413
+ continue;
10414
+ }
10415
+
10416
+ const double relVx = a.vx - b.vx;
10417
+ const double relVy = a.vy - b.vy;
10418
+ const double relVz = a.vz - b.vz;
10419
+ const double relHorizSpeedSq = relVx * relVx + relVz * relVz;
10420
+ const bool isDynamicMotion = (a.dynamic && a.vy < -1.5) || (b.dynamic && b.vy < -1.5);
10421
+ const double maxSubstepImpulse = (!a.dynamic || !b.dynamic) ? 1.25 : 0.75;
10422
+ if (isDynamicMotion || movingFoundation[bodyA] || movingFoundation[bodyB] || relHorizSpeedSq >= 0.55 * 0.55) {
10423
+ constraint.impulses.fill(0.0);
10424
+ continue;
10425
+ }
10426
+
10427
+ if (cachedFirst != bodyA || cachedSecond != bodyB) {
10428
+ cachedFirst = bodyA;
10429
+ cachedSecond = bodyB;
10430
+ cachedContact = collide(operation, a, b);
10431
+ usedPointMask = 0;
10432
+ }
10433
+ const Collision& contact = cachedContact;
10434
+ if (!contact.hit || std::abs(contact.ny) < 0.998) {
10435
+ constraint.impulses.fill(0.0);
10436
+ continue;
10437
+ }
10438
+
10439
+ std::uint32_t pointIndex = contactPointCount(contact);
10440
+ for (std::uint32_t candidate = 0; candidate < contactPointCount(contact); candidate += 1) {
10441
+ if ((usedPointMask & (1u << candidate)) != 0) continue;
10442
+ if (contactFeatureId(contact, candidate) == constraint.featureId) {
10443
+ pointIndex = candidate;
10444
+ break;
10445
+ }
10446
+ }
10447
+ const Vec3 cachedPoint{constraint.point[0], constraint.point[1], constraint.point[2]};
10448
+ if (pointIndex == contactPointCount(contact) && finite(cachedPoint.x) && finite(cachedPoint.y) && finite(cachedPoint.z)) {
10449
+ constexpr double kMaxCornerDriftSquared = 0.02 * 0.02;
10450
+ double bestDistanceSquared = kMaxCornerDriftSquared;
10451
+ for (std::uint32_t candidate = 0; candidate < contactPointCount(contact); candidate += 1) {
10452
+ if ((usedPointMask & (1u << candidate)) != 0) continue;
10453
+ const Vec3 candidatePoint = contactPoint(contact, a, b, candidate);
10454
+ const Vec3 delta = subtractVec3(candidatePoint, cachedPoint);
10455
+ const double distanceSquared = dotVec3(delta, delta);
10456
+ if (distanceSquared <= bestDistanceSquared) {
10457
+ bestDistanceSquared = distanceSquared;
10458
+ pointIndex = candidate;
10459
+ }
10460
+ }
10461
+ }
10462
+ if (pointIndex == contactPointCount(contact)) {
10463
+ constraint.impulses.fill(0.0);
10464
+ continue;
10465
+ }
10466
+ usedPointMask |= (1u << pointIndex);
10467
+ const std::uint32_t matchedFeatureId = contactFeatureId(contact, pointIndex);
10468
+ if (constraint.featureId != matchedFeatureId) {
10469
+ constraint.featureId = matchedFeatureId;
10470
+ anyFeatureIdChanged = true;
10471
+ }
10472
+ const Vec3 point = contactPoint(contact, a, b, pointIndex);
10473
+ const auto contactLevers = triangle_adapter_private::collisionLevers(
10474
+ operation, contact, pointIndex, a, b, point);
10475
+ const Vec3 rA = contactLevers[0];
10476
+ const Vec3 rB = contactLevers[1];
10477
+ const Vec3 normal{contact.nx, contact.ny, contact.nz};
10478
+ const double closingSpeed = -dotVec3(contactRelativeVelocity(a, b, rA, rB), normal);
10479
+ if (closingSpeed >= 2.0) {
10480
+ constraint.impulses.fill(0.0);
10481
+ continue;
10482
+ }
10483
+ if (contactPointCount(contact) == 1 && (rA.x * rA.x + rA.z * rA.z >= 0.20 * 0.20)) {
10484
+ constraint.impulses.fill(0.0);
10485
+ continue;
10486
+ }
10487
+
10488
+ std::array<double, 3> laterImpulses = constraint.impulses;
10489
+ laterImpulses[0] = std::min(laterImpulses[0], maxSubstepImpulse);
10490
+
10491
+ const auto prepared = prepareWarmContactApplication(
10492
+ a, b, movableA, movableB, rA, rB, normal, laterImpulses, true);
10493
+ applyWarmContactApplication(a, b, movableA, movableB, rA, rB, prepared);
10494
+ constraint.impulses = prepared.impulses;
10495
+ constraint.normal = {normal.x, normal.y, normal.z};
10496
+ constraint.point = {point.x, point.y, point.z};
10497
+ const std::uint32_t orderedFirst = std::min(bodyA, bodyB);
10498
+ const std::uint32_t orderedSecond = std::max(bodyA, bodyB);
10499
+ const std::uint32_t outputIndex = recordWorldContactOutput(
10500
+ operation, orderedFirst, orderedSecond, contact);
10501
+ const std::uint32_t telemetryIndex = recordContactTelemetry(
10502
+ operation, orderedFirst, orderedSecond, contact);
10503
+ if (outputIndex == std::numeric_limits<std::uint32_t>::max()
10504
+ || telemetryIndex == std::numeric_limits<std::uint32_t>::max()) {
10505
+ operation.storage.contactConstraintOverflow = true;
10506
+ return;
10507
+ }
10508
+ accumulateContactOutput(operation, outputIndex, telemetryIndex, prepared.impulse);
10509
+ }
10510
+ if (anyFeatureIdChanged) {
10511
+ std::sort(operation.storage.contactConstraints.begin(),
10512
+ operation.storage.contactConstraints.begin() + operation.storage.contactConstraintCount,
10513
+ [](const ContactConstraintBlock& left, const ContactConstraintBlock& right) {
10514
+ return left.bodyA < right.bodyA
10515
+ || (left.bodyA == right.bodyA && (left.bodyB < right.bodyB
10516
+ || (left.bodyB == right.bodyB && left.featureId < right.featureId)));
10517
+ });
10518
+ }
10519
+ }
10218
10520
  const std::uint32_t seededConstraintCount = operation.storage.contactConstraintCount;
10219
10521
  // A proxy and its source are one rigid body, with one support flag.
10220
10522
  const SubstepConstraintHook* const proxyHook = operation.proxyHook;