@forgeax/engine-physics-rapier3d 0.1.29 → 0.1.31
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.
- package/dist/__tests__/contact-observation.unit.test.d.ts +2 -0
- package/dist/__tests__/contact-observation.unit.test.d.ts.map +1 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +512 -321
- package/dist/index.mjs.map +1 -1
- package/dist/rapier-physics-world-3d.d.ts +4 -2
- package/dist/rapier-physics-world-3d.d.ts.map +1 -1
- package/package.json +7 -7
- package/src/__tests__/contact-observation.unit.test.ts +113 -0
- package/src/__tests__/derived-physics.unit.test.ts +482 -1
- package/src/index.ts +4 -4
- package/src/rapier-physics-world-3d.ts +643 -384
package/dist/index.mjs
CHANGED
|
@@ -214,7 +214,9 @@ var RapierPhysicsWorld3D = class {
|
|
|
214
214
|
}
|
|
215
215
|
step(deltaTime) {
|
|
216
216
|
this.assertActive("step");
|
|
217
|
+
if (!Number.isFinite(deltaTime) || deltaTime <= 0 || deltaTime > PHYSICS_DT_MAX) return;
|
|
217
218
|
try {
|
|
219
|
+
this.raw.timestep = deltaTime;
|
|
218
220
|
this.processDerivedCandidates();
|
|
219
221
|
if (this.derivedPoisonedEntities.size > 0) return;
|
|
220
222
|
this.raw.step(this.eventQueue);
|
|
@@ -287,6 +289,8 @@ var RapierPhysicsWorld3D = class {
|
|
|
287
289
|
recordContactObservation(observation, handleA, handleB) {
|
|
288
290
|
let point;
|
|
289
291
|
let normal;
|
|
292
|
+
let geometricPoint;
|
|
293
|
+
let geometricNormal;
|
|
290
294
|
try {
|
|
291
295
|
const colliderA = this.raw.getCollider(handleA);
|
|
292
296
|
const colliderB = this.raw.getCollider(handleB);
|
|
@@ -295,6 +299,34 @@ var RapierPhysicsWorld3D = class {
|
|
|
295
299
|
colliderA,
|
|
296
300
|
colliderB,
|
|
297
301
|
(manifold, flipped) => {
|
|
302
|
+
if (manifold.numSolverContacts() === 0 && geometricPoint === void 0 && manifold.numContacts() > 0) {
|
|
303
|
+
const first = flipped ? colliderB : colliderA;
|
|
304
|
+
const second = flipped ? colliderA : colliderB;
|
|
305
|
+
const types = this.rapierModule.ShapeType;
|
|
306
|
+
for (const [collider, local] of [
|
|
307
|
+
[first, manifold.localContactPoint1(0)],
|
|
308
|
+
[second, manifold.localContactPoint2(0)]
|
|
309
|
+
]) {
|
|
310
|
+
if (local == null || ![types.Ball, types.Cuboid, types.Capsule].includes(collider.shapeType()))
|
|
311
|
+
continue;
|
|
312
|
+
const rotation = collider.rotation();
|
|
313
|
+
const rotated = quat.transformVec3(
|
|
314
|
+
vec3.create(),
|
|
315
|
+
[rotation.x, rotation.y, rotation.z, rotation.w],
|
|
316
|
+
[local.x, local.y, local.z]
|
|
317
|
+
);
|
|
318
|
+
const position = collider.translation();
|
|
319
|
+
geometricPoint = [
|
|
320
|
+
rotated[0] + position.x,
|
|
321
|
+
rotated[1] + position.y,
|
|
322
|
+
rotated[2] + position.z
|
|
323
|
+
];
|
|
324
|
+
const n = manifold.normal();
|
|
325
|
+
const direction = flipped ? -1 : 1;
|
|
326
|
+
geometricNormal = [n.x * direction, n.y * direction, n.z * direction];
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
298
330
|
if (manifold.numSolverContacts?.() > 0) {
|
|
299
331
|
const contact = manifold.solverContactPoint(0);
|
|
300
332
|
const n = manifold.normal();
|
|
@@ -311,6 +343,10 @@ var RapierPhysicsWorld3D = class {
|
|
|
311
343
|
}
|
|
312
344
|
} catch {
|
|
313
345
|
}
|
|
346
|
+
if (point === void 0 && geometricPoint !== void 0) {
|
|
347
|
+
point = geometricPoint;
|
|
348
|
+
normal = geometricNormal;
|
|
349
|
+
}
|
|
314
350
|
this.derivedContacts.push({
|
|
315
351
|
...observation,
|
|
316
352
|
...point === void 0 ? {} : { point },
|
|
@@ -538,17 +574,72 @@ var RapierPhysicsWorld3D = class {
|
|
|
538
574
|
}
|
|
539
575
|
return admitted;
|
|
540
576
|
}
|
|
577
|
+
admitDerivedShapeCandidates(candidates, commitGeometry) {
|
|
578
|
+
this.assertActive("admitDerivedShapeCandidates");
|
|
579
|
+
const records = [];
|
|
580
|
+
const sources = /* @__PURE__ */ new Map();
|
|
581
|
+
const constraints = /* @__PURE__ */ new Set();
|
|
582
|
+
const invalid = (reason) => new DerivedPhysicsError(
|
|
583
|
+
"derived-candidate-invalid",
|
|
584
|
+
"one bounded admission contains distinct prepared bodies and constraint updates",
|
|
585
|
+
"prepare one candidate per body and submit the complete replacement together",
|
|
586
|
+
{ reason }
|
|
587
|
+
);
|
|
588
|
+
if (candidates.length === 0 || candidates.length > DERIVED_PHYSICS_LIMITS.maxCandidates)
|
|
589
|
+
return err(invalid("invalid batch size"));
|
|
590
|
+
for (const candidate of candidates) {
|
|
591
|
+
const record = this.derivedCandidates.get(candidate.candidateId);
|
|
592
|
+
if (record === void 0 || candidate.owner !== this.physicsOwner || candidate.generation !== this.backendGeneration || record.state !== "ready")
|
|
593
|
+
return err(invalid("batch member is not a current prepared candidate"));
|
|
594
|
+
if (sources.has(record.input.entity)) return err(invalid("duplicate body"));
|
|
595
|
+
for (const constraint of record.input.constraints ?? []) {
|
|
596
|
+
if (constraints.has(constraint.id)) return err(invalid("duplicate constraint update"));
|
|
597
|
+
constraints.add(constraint.id);
|
|
598
|
+
}
|
|
599
|
+
records.push(record);
|
|
600
|
+
sources.set(record.input.entity, {
|
|
601
|
+
sourceKey: record.input.sourceKey,
|
|
602
|
+
revision: record.input.revision
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
for (const candidate of candidates) {
|
|
606
|
+
const checked = this.admitDerivedShapeCandidateInternal(candidate, sources, true);
|
|
607
|
+
if (!checked.ok) {
|
|
608
|
+
for (const record of records) {
|
|
609
|
+
if (this.derivedCandidates.has(record.token.candidateId))
|
|
610
|
+
this.rejectPreparedCandidate(record, checked.error);
|
|
611
|
+
}
|
|
612
|
+
return checked;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
const batch = Object.freeze({
|
|
616
|
+
records: Object.freeze(records),
|
|
617
|
+
...commitGeometry === void 0 ? {} : { commitGeometry }
|
|
618
|
+
});
|
|
619
|
+
for (const record of records) record.batch = batch;
|
|
620
|
+
const queued = [];
|
|
621
|
+
for (const candidate of candidates) {
|
|
622
|
+
const admitted = this.admitDerivedShapeCandidateInternal(candidate, sources);
|
|
623
|
+
if (!admitted.ok) {
|
|
624
|
+
const first = records[0];
|
|
625
|
+
if (first !== void 0) this.rejectPreparedCandidate(first, admitted.error);
|
|
626
|
+
return admitted;
|
|
627
|
+
}
|
|
628
|
+
queued.push(admitted.value);
|
|
629
|
+
}
|
|
630
|
+
return ok(Object.freeze(queued));
|
|
631
|
+
}
|
|
541
632
|
getDerivedAdmission(entity) {
|
|
542
|
-
const
|
|
543
|
-
|
|
544
|
-
|
|
633
|
+
const active = this.activeDerivedAdmission;
|
|
634
|
+
const record = entity === void 0 ? active : active?.batch?.records.find((item) => item.input.entity === entity) ?? (active?.input.entity === entity ? active : void 0);
|
|
635
|
+
if (record === void 0) return void 0;
|
|
545
636
|
return {
|
|
546
637
|
entity: record.input.entity,
|
|
547
638
|
revision: record.input.revision,
|
|
548
639
|
fixedStep: this.syncState?.world.getResource(FixedTime).tick ?? this.fixedStep + 1
|
|
549
640
|
};
|
|
550
641
|
}
|
|
551
|
-
admitDerivedShapeCandidateInternal(candidate, sourceOverrides) {
|
|
642
|
+
admitDerivedShapeCandidateInternal(candidate, sourceOverrides, validateOnly = false) {
|
|
552
643
|
this.assertActive("admitDerivedShapeCandidate");
|
|
553
644
|
const record = this.derivedCandidates.get(candidate.candidateId);
|
|
554
645
|
if (record === void 0 || candidate.owner !== this.physicsOwner || record.token.owner !== candidate.owner || candidate.generation !== this.backendGeneration) {
|
|
@@ -631,6 +722,7 @@ var RapierPhysicsWorld3D = class {
|
|
|
631
722
|
this.rejectPreparedCandidate(record, stale);
|
|
632
723
|
return err(stale);
|
|
633
724
|
}
|
|
725
|
+
if (validateOnly) return ok(record.token);
|
|
634
726
|
record.state = "queued";
|
|
635
727
|
this.pendingDerivedCandidates.add(candidate.candidateId);
|
|
636
728
|
const queued = Object.freeze({ ...record.token, state: "queued" });
|
|
@@ -692,10 +784,12 @@ var RapierPhysicsWorld3D = class {
|
|
|
692
784
|
)
|
|
693
785
|
);
|
|
694
786
|
}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
787
|
+
for (const member of record.batch?.records ?? [record]) {
|
|
788
|
+
this.pendingDerivedCandidates.delete(member.token.candidateId);
|
|
789
|
+
for (const collider of member.nativeColliders) this.removeNativeCollider(collider.handle);
|
|
790
|
+
member.state = "cancelled";
|
|
791
|
+
this.releaseDerivedCandidate(member.token.candidateId);
|
|
792
|
+
}
|
|
699
793
|
return ok(void 0);
|
|
700
794
|
}
|
|
701
795
|
/** Reject all in-flight derived work while preserving the last publication. */
|
|
@@ -742,12 +836,68 @@ var RapierPhysicsWorld3D = class {
|
|
|
742
836
|
const com = body.worldCom();
|
|
743
837
|
const linear = body.linvel();
|
|
744
838
|
const angular = body.angvel();
|
|
839
|
+
const rotation = body.rotation();
|
|
745
840
|
return Object.freeze({
|
|
746
841
|
centerOfMass: [com.x, com.y, com.z],
|
|
747
842
|
linearVelocity: [linear.x, linear.y, linear.z],
|
|
748
|
-
angularVelocity: [angular.x, angular.y, angular.z]
|
|
843
|
+
angularVelocity: [angular.x, angular.y, angular.z],
|
|
844
|
+
rotation: [rotation.x, rotation.y, rotation.z, rotation.w]
|
|
749
845
|
});
|
|
750
846
|
}
|
|
847
|
+
applyDerivedImpulse(input) {
|
|
848
|
+
this.assertActive("applyDerivedImpulse");
|
|
849
|
+
const refuse = (code, expected) => err(
|
|
850
|
+
new DerivedPhysicsError(
|
|
851
|
+
code,
|
|
852
|
+
expected,
|
|
853
|
+
"read the committed dynamic body and submit a finite impulse outside pending admission",
|
|
854
|
+
{ entity: input.entity }
|
|
855
|
+
)
|
|
856
|
+
);
|
|
857
|
+
if (this.recoveryBlocked())
|
|
858
|
+
return refuse("derived-recovery-invalid", "PhysicsWorld is healthy");
|
|
859
|
+
const record = this.derivedBodies.get(input.entity);
|
|
860
|
+
const body = this.bodyForEntity(input.entity);
|
|
861
|
+
if (record === void 0 || body === void 0)
|
|
862
|
+
return refuse("derived-body-not-found", "a committed derived body exists");
|
|
863
|
+
if (record.sourceKey !== input.sourceKey || record.revision !== input.revision)
|
|
864
|
+
return refuse(
|
|
865
|
+
"derived-candidate-stale",
|
|
866
|
+
"impulse identity matches the committed body revision"
|
|
867
|
+
);
|
|
868
|
+
if (this.derivedPublicationPending || [...this.pendingDerivedCandidates].some(
|
|
869
|
+
(id) => this.derivedCandidates.get(id)?.input.entity === input.entity
|
|
870
|
+
))
|
|
871
|
+
return refuse("derived-candidate-pending", "the body has no unpublished native mutation");
|
|
872
|
+
if (rapierBodyTypeToString(this.rapierModule, body.bodyType()) !== "dynamic" || ![input.impulse, input.point].every(
|
|
873
|
+
(vector) => Array.isArray(vector) && vector.length === 3 && vector.every((value) => Number.isFinite(value) && Number.isFinite(Math.fround(value)))
|
|
874
|
+
))
|
|
875
|
+
return refuse(
|
|
876
|
+
"derived-candidate-invalid",
|
|
877
|
+
"a dynamic body receives finite Float32 world vectors"
|
|
878
|
+
);
|
|
879
|
+
try {
|
|
880
|
+
body.applyImpulseAtPoint(
|
|
881
|
+
{ x: input.impulse[0], y: input.impulse[1], z: input.impulse[2] },
|
|
882
|
+
{ x: input.point[0], y: input.point[1], z: input.point[2] },
|
|
883
|
+
true
|
|
884
|
+
);
|
|
885
|
+
const linear = body.linvel(), angular = body.angvel();
|
|
886
|
+
if (![linear.x, linear.y, linear.z, angular.x, angular.y, angular.z].every(Number.isFinite))
|
|
887
|
+
throw new Error("native impulse produced non-finite motion");
|
|
888
|
+
return ok(void 0);
|
|
889
|
+
} catch (cause) {
|
|
890
|
+
this.derivedPoisonedEntities.add(input.entity);
|
|
891
|
+
return err(
|
|
892
|
+
new DerivedPhysicsError(
|
|
893
|
+
"derived-backend-failed",
|
|
894
|
+
"native impulse completes with finite motion",
|
|
895
|
+
"rebuild the World from a previously committed snapshot",
|
|
896
|
+
{ entity: input.entity, reason: cause instanceof Error ? cause.message : String(cause) }
|
|
897
|
+
)
|
|
898
|
+
);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
751
901
|
getDerivedRecoveryState() {
|
|
752
902
|
return this.recoveryBlocked() ? "rebuild-required" : "ready";
|
|
753
903
|
}
|
|
@@ -844,16 +994,8 @@ var RapierPhysicsWorld3D = class {
|
|
|
844
994
|
}
|
|
845
995
|
preparedCandidates.push(prepared.value);
|
|
846
996
|
}
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
const admitted = this.admitDerivedShapeCandidateInternal(prepared, snapshotSources);
|
|
850
|
-
if (!admitted.ok) {
|
|
851
|
-
for (const candidate of preparedCandidates) this.cancelDerivedShapeCandidate(candidate);
|
|
852
|
-
return admitted;
|
|
853
|
-
}
|
|
854
|
-
candidates.push(admitted.value);
|
|
855
|
-
}
|
|
856
|
-
return ok(candidates);
|
|
997
|
+
if (preparedCandidates.length === 0) return ok([]);
|
|
998
|
+
return this.admitDerivedShapeCandidates(preparedCandidates);
|
|
857
999
|
}
|
|
858
1000
|
createDerivedConstraint(input) {
|
|
859
1001
|
return this.installDerivedConstraint(input, false);
|
|
@@ -969,8 +1111,11 @@ var RapierPhysicsWorld3D = class {
|
|
|
969
1111
|
this.releaseDerivedCandidate(record.token.candidateId);
|
|
970
1112
|
}
|
|
971
1113
|
rejectPreparedCandidate(record, error) {
|
|
972
|
-
for (const
|
|
973
|
-
|
|
1114
|
+
for (const member of record.batch?.records ?? [record]) {
|
|
1115
|
+
if (!this.derivedCandidates.has(member.token.candidateId)) continue;
|
|
1116
|
+
for (const collider of member.nativeColliders) this.removeNativeCollider(collider.handle);
|
|
1117
|
+
this.rememberDerivedFailure(member, error, "old-state-retained");
|
|
1118
|
+
}
|
|
974
1119
|
}
|
|
975
1120
|
processDerivedCandidates() {
|
|
976
1121
|
if (this.pendingDerivedCandidates.size === 0) return;
|
|
@@ -986,329 +1131,358 @@ var RapierPhysicsWorld3D = class {
|
|
|
986
1131
|
const visiting = /* @__PURE__ */ new Set();
|
|
987
1132
|
const visited = /* @__PURE__ */ new Set();
|
|
988
1133
|
const visit = (record) => {
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
for (const endpoint of [
|
|
994
|
-
[constraint.bodyA, constraint.bodyASource],
|
|
995
|
-
[constraint.bodyB, constraint.bodyBSource]
|
|
996
|
-
]) {
|
|
997
|
-
const dependency = endpoint[1];
|
|
998
|
-
const target = pendingByEntity.get(endpoint[0]);
|
|
999
|
-
if (target !== void 0 && target.input.sourceKey === dependency.sourceKey && target.input.revision === dependency.revision) {
|
|
1000
|
-
visit(target);
|
|
1001
|
-
}
|
|
1002
|
-
}
|
|
1003
|
-
}
|
|
1004
|
-
visiting.delete(record.token.candidateId);
|
|
1005
|
-
visited.add(record.token.candidateId);
|
|
1006
|
-
ordered.push(record);
|
|
1007
|
-
};
|
|
1008
|
-
for (const record of pendingRecords) visit(record);
|
|
1009
|
-
for (const record of ordered) {
|
|
1010
|
-
const id = record.token.candidateId;
|
|
1011
|
-
if (!this.pendingDerivedCandidates.has(id) || record.state !== "queued") continue;
|
|
1012
|
-
const pendingSources = this.pendingDerivedSources();
|
|
1013
|
-
const projected = pendingSources.get(record.input.entity);
|
|
1014
|
-
if (projected !== void 0 && record.input.revision < projected.revision) {
|
|
1134
|
+
const group = record.batch?.records ?? [record];
|
|
1135
|
+
const key = group[0]?.token.candidateId ?? record.token.candidateId;
|
|
1136
|
+
if (visited.has(key)) return;
|
|
1137
|
+
if (visiting.has(key)) {
|
|
1015
1138
|
this.rejectPreparedCandidate(
|
|
1016
1139
|
record,
|
|
1017
1140
|
new DerivedPhysicsError(
|
|
1018
|
-
"derived-
|
|
1019
|
-
"
|
|
1020
|
-
"
|
|
1021
|
-
{
|
|
1022
|
-
entity: record.input.entity,
|
|
1023
|
-
candidateId: record.token.candidateId,
|
|
1024
|
-
expected: `>=${projected.revision}`,
|
|
1025
|
-
actual: record.input.revision
|
|
1026
|
-
}
|
|
1141
|
+
"derived-constraint-invalid",
|
|
1142
|
+
"cyclic endpoint updates share one batch",
|
|
1143
|
+
"submit mutually dependent body replacements together",
|
|
1144
|
+
{ candidateId: key }
|
|
1027
1145
|
)
|
|
1028
1146
|
);
|
|
1029
|
-
|
|
1147
|
+
return;
|
|
1030
1148
|
}
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1149
|
+
visiting.add(key);
|
|
1150
|
+
for (const member of group) {
|
|
1151
|
+
for (const constraint of member.input.constraints ?? []) {
|
|
1152
|
+
for (const [entity, dependency] of [
|
|
1153
|
+
[constraint.bodyA, constraint.bodyASource],
|
|
1154
|
+
[constraint.bodyB, constraint.bodyBSource]
|
|
1155
|
+
]) {
|
|
1156
|
+
const target = pendingByEntity.get(entity);
|
|
1157
|
+
if (target !== void 0 && !group.includes(target) && target.input.sourceKey === dependency.sourceKey && target.input.revision === dependency.revision)
|
|
1158
|
+
visit(target);
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1035
1161
|
}
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
);
|
|
1162
|
+
visiting.delete(key);
|
|
1163
|
+
visited.add(key);
|
|
1164
|
+
ordered.push(group);
|
|
1165
|
+
};
|
|
1166
|
+
for (const record of pendingRecords) visit(record);
|
|
1167
|
+
for (const group of ordered) {
|
|
1168
|
+
if (this.derivedPoisonedEntities.size > 0) break;
|
|
1169
|
+
const first = group[0];
|
|
1170
|
+
if (first === void 0) continue;
|
|
1171
|
+
if (group.some(
|
|
1172
|
+
(record) => record.state !== "queued" || !this.pendingDerivedCandidates.has(record.token.candidateId)
|
|
1173
|
+
))
|
|
1049
1174
|
continue;
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1175
|
+
const sources = this.pendingDerivedSources();
|
|
1176
|
+
let rejected;
|
|
1177
|
+
for (const record of group) {
|
|
1178
|
+
const projected = sources.get(record.input.entity);
|
|
1179
|
+
if (projected !== void 0 && record.input.revision < projected.revision) {
|
|
1180
|
+
rejected = new DerivedPhysicsError(
|
|
1181
|
+
"derived-candidate-stale",
|
|
1182
|
+
"fixed-step admission publishes the final queued body revisions",
|
|
1183
|
+
"discard the older group and prepare a complete replacement",
|
|
1184
|
+
{ entity: record.input.entity }
|
|
1185
|
+
);
|
|
1186
|
+
} else if (this.derivedPoisonedEntities.has(record.input.entity)) {
|
|
1187
|
+
rejected = new DerivedPhysicsError(
|
|
1188
|
+
"derived-recovery-invalid",
|
|
1189
|
+
"every batch body has recoverable native state",
|
|
1190
|
+
"rebuild the PhysicsWorld before retrying",
|
|
1191
|
+
{ entity: record.input.entity }
|
|
1192
|
+
);
|
|
1193
|
+
} else if (this.bodyForEntity(record.input.entity) === void 0) {
|
|
1194
|
+
rejected = new DerivedPhysicsError(
|
|
1057
1195
|
"derived-body-not-found",
|
|
1058
|
-
"
|
|
1059
|
-
"reconcile
|
|
1060
|
-
{ entity: record.input.entity
|
|
1061
|
-
)
|
|
1062
|
-
|
|
1063
|
-
);
|
|
1196
|
+
"every batch body remains live through admission",
|
|
1197
|
+
"reconcile entities and prepare again",
|
|
1198
|
+
{ entity: record.input.entity }
|
|
1199
|
+
);
|
|
1200
|
+
} else rejected = this.validateDerivedAdmission(record.input, sources);
|
|
1201
|
+
if (rejected !== void 0) break;
|
|
1202
|
+
}
|
|
1203
|
+
if (rejected !== void 0) {
|
|
1204
|
+
this.rejectPreparedCandidate(first, rejected);
|
|
1064
1205
|
continue;
|
|
1065
1206
|
}
|
|
1066
|
-
const
|
|
1067
|
-
const
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
record.nativeColliders[index]?.handle
|
|
1112
|
-
])
|
|
1113
|
-
);
|
|
1114
|
-
for (const seam of record.input.seams ?? []) {
|
|
1115
|
-
const firstHandle = nativeById.get(seam.shapeA);
|
|
1116
|
-
const secondHandle = nativeById.get(seam.shapeB);
|
|
1117
|
-
const first = firstHandle === void 0 ? void 0 : this.raw.getCollider(firstHandle);
|
|
1118
|
-
const second = secondHandle === void 0 ? void 0 : this.raw.getCollider(secondHandle);
|
|
1119
|
-
if (first === void 0 || second === void 0 || first === null || second === null) {
|
|
1120
|
-
throw new Error(`derived seam references missing shape ${seam.shapeA}`);
|
|
1207
|
+
const undos = [];
|
|
1208
|
+
for (const record of group) {
|
|
1209
|
+
const id = record.token.candidateId;
|
|
1210
|
+
const old = this.derivedBodies.get(record.input.entity);
|
|
1211
|
+
const body = this.bodyForEntity(record.input.entity);
|
|
1212
|
+
const oldBodyType = body.bodyType();
|
|
1213
|
+
const oldBodyEnabled = body.isEnabled();
|
|
1214
|
+
const oldVelocity = body.linvel();
|
|
1215
|
+
const oldAngularVelocity = body.angvel();
|
|
1216
|
+
const oldTranslation = body.translation();
|
|
1217
|
+
const oldRotation = body.rotation();
|
|
1218
|
+
const oldCom = body.worldCom();
|
|
1219
|
+
const oldMass = body.mass();
|
|
1220
|
+
const oldAutomaticAdditionalMass = this.entityMap.get(record.input.entity)?.automaticAdditionalMass ?? 0;
|
|
1221
|
+
const oldAutomaticRecordMass = this.entityMap.get(
|
|
1222
|
+
record.input.entity
|
|
1223
|
+
)?.automaticAdditionalMass;
|
|
1224
|
+
const oldSource = this.derivedBodySources.get(record.input.entity);
|
|
1225
|
+
const oldPublication = this.derivedPublications.get(record.input.entity);
|
|
1226
|
+
const oldDensities = this.bodyColliders(body).map((collider) => ({
|
|
1227
|
+
collider,
|
|
1228
|
+
density: typeof collider.density === "function" ? collider.density() : void 0,
|
|
1229
|
+
enabled: typeof collider.isEnabled === "function" ? collider.isEnabled() : true
|
|
1230
|
+
}));
|
|
1231
|
+
const oldConstraints = new Map(this.derivedConstraints);
|
|
1232
|
+
const stagedConstraints = /* @__PURE__ */ new Map();
|
|
1233
|
+
let geometryCommitUncertain = false;
|
|
1234
|
+
const rollback = () => {
|
|
1235
|
+
for (const staged of stagedConstraints.values())
|
|
1236
|
+
this.removeNativeConstraint(staged.handle);
|
|
1237
|
+
for (const current of this.derivedConstraints.values())
|
|
1238
|
+
this.removeNativeConstraint(current.handle);
|
|
1239
|
+
this.derivedConstraints.clear();
|
|
1240
|
+
if (old !== void 0) {
|
|
1241
|
+
for (const shape of old.shapes) {
|
|
1242
|
+
const collider = this.raw.getCollider(shape.colliderHandle);
|
|
1243
|
+
if (collider !== null && collider !== void 0) collider.setEnabled(true);
|
|
1244
|
+
}
|
|
1245
|
+
const retiredIndex = this.retiredDerivedBodies.indexOf(old);
|
|
1246
|
+
if (retiredIndex >= 0) this.retiredDerivedBodies.splice(retiredIndex, 1);
|
|
1247
|
+
this.derivedBodies.set(record.input.entity, old);
|
|
1248
|
+
this.derivedBodySources.set(record.input.entity, {
|
|
1249
|
+
sourceKey: old.sourceKey,
|
|
1250
|
+
revision: old.revision
|
|
1251
|
+
});
|
|
1121
1252
|
}
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
this.applyDerivedMass(
|
|
1125
|
-
body,
|
|
1126
|
-
record.input.massProperties,
|
|
1127
|
-
oldCom,
|
|
1128
|
-
record.input.velocityPolicy ?? "preserve",
|
|
1129
|
-
this.entityMap.get(record.input.entity)?.additionalMass ?? 0,
|
|
1130
|
-
record.input.entity,
|
|
1131
|
-
record.input.shapes,
|
|
1132
|
-
record.nativeColliders
|
|
1133
|
-
);
|
|
1134
|
-
if (record.input.motion !== void 0) {
|
|
1135
|
-
const currentCom = body.worldCom();
|
|
1136
|
-
const targetCom = record.input.motion.centerOfMass;
|
|
1137
|
-
const translation = body.translation();
|
|
1138
|
-
body.setTranslation(
|
|
1139
|
-
{
|
|
1140
|
-
x: translation.x + targetCom[0] - currentCom.x,
|
|
1141
|
-
y: translation.y + targetCom[1] - currentCom.y,
|
|
1142
|
-
z: translation.z + targetCom[2] - currentCom.z
|
|
1143
|
-
},
|
|
1144
|
-
true
|
|
1145
|
-
);
|
|
1146
|
-
body.setLinvel(
|
|
1147
|
-
{
|
|
1148
|
-
x: record.input.motion.linearVelocity[0],
|
|
1149
|
-
y: record.input.motion.linearVelocity[1],
|
|
1150
|
-
z: record.input.motion.linearVelocity[2]
|
|
1151
|
-
},
|
|
1152
|
-
true
|
|
1153
|
-
);
|
|
1154
|
-
body.setAngvel(
|
|
1155
|
-
{
|
|
1156
|
-
x: record.input.motion.angularVelocity[0],
|
|
1157
|
-
y: record.input.motion.angularVelocity[1],
|
|
1158
|
-
z: record.input.motion.angularVelocity[2]
|
|
1159
|
-
},
|
|
1160
|
-
true
|
|
1161
|
-
);
|
|
1162
|
-
}
|
|
1163
|
-
const committedSources = new Map(this.derivedBodySources);
|
|
1164
|
-
committedSources.set(record.input.entity, {
|
|
1165
|
-
sourceKey: record.input.sourceKey,
|
|
1166
|
-
revision: record.input.revision
|
|
1167
|
-
});
|
|
1168
|
-
const replacementConstraintIds = new Set(
|
|
1169
|
-
(record.input.constraints ?? []).map((constraint) => constraint.id)
|
|
1170
|
-
);
|
|
1171
|
-
for (const [constraintId, current] of [...this.derivedConstraints]) {
|
|
1172
|
-
if (replacementConstraintIds.has(constraintId) || this.constraintDependenciesMatch(current.input, committedSources))
|
|
1173
|
-
continue;
|
|
1174
|
-
this.removeNativeConstraint(current.handle);
|
|
1175
|
-
this.derivedConstraints.delete(constraintId);
|
|
1176
|
-
}
|
|
1177
|
-
if (old !== void 0) {
|
|
1178
|
-
for (const shape of old.shapes) {
|
|
1179
|
-
const collider = this.raw.getCollider(shape.colliderHandle);
|
|
1253
|
+
for (const native of record.nativeColliders) {
|
|
1254
|
+
const collider = this.raw.getCollider(native.handle);
|
|
1180
1255
|
if (collider !== null && collider !== void 0) collider.setEnabled(false);
|
|
1181
1256
|
}
|
|
1182
|
-
this.
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1257
|
+
for (const native of record.nativeColliders) this.removeNativeCollider(native.handle);
|
|
1258
|
+
for (const { collider, density, enabled } of oldDensities) {
|
|
1259
|
+
if (this.raw.getCollider(collider.handle) === null) continue;
|
|
1260
|
+
if (density !== void 0 && typeof collider.setDensity === "function")
|
|
1261
|
+
collider.setDensity(density);
|
|
1262
|
+
if (typeof collider.setEnabled === "function") collider.setEnabled(enabled);
|
|
1263
|
+
}
|
|
1264
|
+
let restored = true;
|
|
1265
|
+
try {
|
|
1266
|
+
body.setBodyType(oldBodyType, true);
|
|
1267
|
+
this.restoreCommittedMass(body, old, oldAutomaticAdditionalMass);
|
|
1268
|
+
body.setTranslation(oldTranslation, true);
|
|
1269
|
+
body.setRotation(oldRotation, true);
|
|
1270
|
+
body.setLinvel(oldVelocity, true);
|
|
1271
|
+
body.setAngvel(oldAngularVelocity, true);
|
|
1272
|
+
body.setEnabled(oldBodyEnabled);
|
|
1273
|
+
} catch {
|
|
1274
|
+
restored = false;
|
|
1275
|
+
}
|
|
1276
|
+
if (restored && !this.restoreNativeConstraints(oldConstraints)) restored = false;
|
|
1277
|
+
if (restored) {
|
|
1278
|
+
const currentMass = body.mass();
|
|
1279
|
+
const currentCom = body.worldCom();
|
|
1280
|
+
const currentVelocity = body.linvel();
|
|
1281
|
+
const currentAngularVelocity = body.angvel();
|
|
1282
|
+
const currentRotation = body.rotation();
|
|
1283
|
+
restored = Number.isFinite(currentMass) && Math.abs(currentMass - oldMass) <= 1e-6 * Math.max(1, Math.abs(oldMass)) && Math.abs(currentCom.x - oldCom.x) <= 1e-6 && Math.abs(currentCom.y - oldCom.y) <= 1e-6 && Math.abs(currentCom.z - oldCom.z) <= 1e-6 && Math.abs(currentVelocity.x - oldVelocity.x) <= 1e-6 && Math.abs(currentVelocity.y - oldVelocity.y) <= 1e-6 && Math.abs(currentVelocity.z - oldVelocity.z) <= 1e-6 && Math.abs(currentAngularVelocity.x - oldAngularVelocity.x) <= 1e-6 && Math.abs(currentAngularVelocity.y - oldAngularVelocity.y) <= 1e-6 && Math.abs(currentAngularVelocity.z - oldAngularVelocity.z) <= 1e-6 && Math.abs(
|
|
1284
|
+
currentRotation.x * oldRotation.x + currentRotation.y * oldRotation.y + currentRotation.z * oldRotation.z + currentRotation.w * oldRotation.w
|
|
1285
|
+
) >= 1 - 1e-6 && body.bodyType() === oldBodyType && body.isEnabled() === oldBodyEnabled;
|
|
1286
|
+
}
|
|
1287
|
+
if (old === void 0) this.derivedBodies.delete(record.input.entity);
|
|
1288
|
+
else this.derivedBodies.set(record.input.entity, old);
|
|
1289
|
+
if (oldSource === void 0) this.derivedBodySources.delete(record.input.entity);
|
|
1290
|
+
else this.derivedBodySources.set(record.input.entity, oldSource);
|
|
1291
|
+
if (oldPublication === void 0) this.derivedPublications.delete(record.input.entity);
|
|
1292
|
+
else this.derivedPublications.set(record.input.entity, oldPublication);
|
|
1293
|
+
const entityRecord = this.entityMap.get(record.input.entity);
|
|
1294
|
+
if (entityRecord !== void 0 && oldAutomaticRecordMass !== void 0)
|
|
1295
|
+
entityRecord.automaticAdditionalMass = oldAutomaticRecordMass;
|
|
1296
|
+
return restored;
|
|
1208
1297
|
};
|
|
1209
|
-
|
|
1210
|
-
this.activeDerivedAdmission = record;
|
|
1298
|
+
const restore = (undo) => {
|
|
1211
1299
|
try {
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
if (!geometry.ok) throw geometry.error;
|
|
1216
|
-
} finally {
|
|
1217
|
-
this.activeDerivedAdmission = void 0;
|
|
1300
|
+
return undo();
|
|
1301
|
+
} catch {
|
|
1302
|
+
return false;
|
|
1218
1303
|
}
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
revision: record.input.revision
|
|
1229
|
-
});
|
|
1230
|
-
this.derivedFailures.delete(record.input.entity);
|
|
1231
|
-
record.state = "queued";
|
|
1232
|
-
this.pendingDerivedCandidates.delete(id);
|
|
1233
|
-
} catch (cause) {
|
|
1234
|
-
for (const staged of stagedConstraints.values()) this.removeNativeConstraint(staged.handle);
|
|
1235
|
-
for (const current of this.derivedConstraints.values())
|
|
1236
|
-
this.removeNativeConstraint(current.handle);
|
|
1237
|
-
this.derivedConstraints.clear();
|
|
1238
|
-
if (old !== void 0) {
|
|
1239
|
-
for (const shape of old.shapes) {
|
|
1240
|
-
const collider = this.raw.getCollider(shape.colliderHandle);
|
|
1241
|
-
if (collider !== null && collider !== void 0) collider.setEnabled(true);
|
|
1304
|
+
};
|
|
1305
|
+
try {
|
|
1306
|
+
for (const constraint of record.input.constraints ?? []) {
|
|
1307
|
+
const created = this.createNativeConstraint(constraint);
|
|
1308
|
+
if (!created.ok) throw created.error;
|
|
1309
|
+
stagedConstraints.set(constraint.id, {
|
|
1310
|
+
input: { ...constraint },
|
|
1311
|
+
handle: created.value.handle
|
|
1312
|
+
});
|
|
1242
1313
|
}
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1314
|
+
if (record.input.bodyType !== void 0) {
|
|
1315
|
+
const RAPIER = this.rapierModule;
|
|
1316
|
+
const bodyType = record.input.bodyType === "static" ? RAPIER.RigidBodyType.Fixed : record.input.bodyType === "kinematic" ? RAPIER.RigidBodyType.KinematicPositionBased : RAPIER.RigidBodyType.Dynamic;
|
|
1317
|
+
body.setBodyType(bodyType, true);
|
|
1318
|
+
}
|
|
1319
|
+
for (const native of record.nativeColliders) {
|
|
1320
|
+
const collider = this.raw.getCollider(native.handle);
|
|
1321
|
+
if (collider === null || collider === void 0)
|
|
1322
|
+
throw new Error("candidate collider disappeared");
|
|
1323
|
+
collider.setEnabled(true);
|
|
1324
|
+
}
|
|
1325
|
+
const nativeById = new Map(
|
|
1326
|
+
record.input.shapes.map((shape, index) => [
|
|
1327
|
+
shape.id,
|
|
1328
|
+
record.nativeColliders[index]?.handle
|
|
1329
|
+
])
|
|
1330
|
+
);
|
|
1331
|
+
for (const seam of record.input.seams ?? []) {
|
|
1332
|
+
const firstHandle = nativeById.get(seam.shapeA);
|
|
1333
|
+
const secondHandle = nativeById.get(seam.shapeB);
|
|
1334
|
+
const first2 = firstHandle === void 0 ? void 0 : this.raw.getCollider(firstHandle);
|
|
1335
|
+
const second = secondHandle === void 0 ? void 0 : this.raw.getCollider(secondHandle);
|
|
1336
|
+
if (first2 === void 0 || second === void 0 || first2 === null || second === null) {
|
|
1337
|
+
throw new Error(`derived seam references missing shape ${seam.shapeA}`);
|
|
1338
|
+
}
|
|
1339
|
+
first2.combineVoxelStates(second, seam.offset[0], seam.offset[1], seam.offset[2]);
|
|
1340
|
+
}
|
|
1341
|
+
if (record.input.motion?.rotation !== void 0) {
|
|
1342
|
+
const [x, y, z, w] = record.input.motion.rotation;
|
|
1343
|
+
body.setRotation({ x, y, z, w }, true);
|
|
1344
|
+
}
|
|
1345
|
+
this.applyDerivedMass(
|
|
1346
|
+
body,
|
|
1347
|
+
record.input.massProperties,
|
|
1348
|
+
oldCom,
|
|
1349
|
+
record.input.velocityPolicy ?? "preserve",
|
|
1350
|
+
this.entityMap.get(record.input.entity)?.additionalMass ?? 0,
|
|
1351
|
+
record.input.entity,
|
|
1352
|
+
record.input.shapes,
|
|
1353
|
+
record.nativeColliders
|
|
1354
|
+
);
|
|
1355
|
+
if (record.input.motion !== void 0) {
|
|
1356
|
+
const currentCom = body.worldCom();
|
|
1357
|
+
const targetCom = record.input.motion.centerOfMass;
|
|
1358
|
+
const translation = body.translation();
|
|
1359
|
+
body.setTranslation(
|
|
1360
|
+
{
|
|
1361
|
+
x: translation.x + targetCom[0] - currentCom.x,
|
|
1362
|
+
y: translation.y + targetCom[1] - currentCom.y,
|
|
1363
|
+
z: translation.z + targetCom[2] - currentCom.z
|
|
1364
|
+
},
|
|
1365
|
+
true
|
|
1366
|
+
);
|
|
1367
|
+
body.setLinvel(
|
|
1368
|
+
{
|
|
1369
|
+
x: record.input.motion.linearVelocity[0],
|
|
1370
|
+
y: record.input.motion.linearVelocity[1],
|
|
1371
|
+
z: record.input.motion.linearVelocity[2]
|
|
1372
|
+
},
|
|
1373
|
+
true
|
|
1374
|
+
);
|
|
1375
|
+
body.setAngvel(
|
|
1376
|
+
{
|
|
1377
|
+
x: record.input.motion.angularVelocity[0],
|
|
1378
|
+
y: record.input.motion.angularVelocity[1],
|
|
1379
|
+
z: record.input.motion.angularVelocity[2]
|
|
1380
|
+
},
|
|
1381
|
+
true
|
|
1382
|
+
);
|
|
1383
|
+
}
|
|
1384
|
+
const committedSources = new Map(this.derivedBodySources);
|
|
1385
|
+
committedSources.set(record.input.entity, {
|
|
1386
|
+
sourceKey: record.input.sourceKey,
|
|
1387
|
+
revision: record.input.revision
|
|
1249
1388
|
});
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
const
|
|
1279
|
-
const currentAngularVelocity = body.angvel();
|
|
1280
|
-
restored = Number.isFinite(currentMass) && Math.abs(currentMass - oldMass) <= 1e-6 * Math.max(1, Math.abs(oldMass)) && Math.abs(currentCom.x - oldCom.x) <= 1e-6 && Math.abs(currentCom.y - oldCom.y) <= 1e-6 && Math.abs(currentCom.z - oldCom.z) <= 1e-6 && Math.abs(currentVelocity.x - oldVelocity.x) <= 1e-6 && Math.abs(currentVelocity.y - oldVelocity.y) <= 1e-6 && Math.abs(currentVelocity.z - oldVelocity.z) <= 1e-6 && Math.abs(currentAngularVelocity.x - oldAngularVelocity.x) <= 1e-6 && Math.abs(currentAngularVelocity.y - oldAngularVelocity.y) <= 1e-6 && Math.abs(currentAngularVelocity.z - oldAngularVelocity.z) <= 1e-6 && body.bodyType() === oldBodyType && body.isEnabled() === oldBodyEnabled;
|
|
1281
|
-
}
|
|
1282
|
-
if (old === void 0) this.derivedBodies.delete(record.input.entity);
|
|
1283
|
-
else this.derivedBodies.set(record.input.entity, old);
|
|
1284
|
-
if (oldSource === void 0) this.derivedBodySources.delete(record.input.entity);
|
|
1285
|
-
else this.derivedBodySources.set(record.input.entity, oldSource);
|
|
1286
|
-
if (oldPublication === void 0) this.derivedPublications.delete(record.input.entity);
|
|
1287
|
-
else this.derivedPublications.set(record.input.entity, oldPublication);
|
|
1288
|
-
const entityRecord = this.entityMap.get(record.input.entity);
|
|
1289
|
-
if (entityRecord !== void 0 && oldAutomaticRecordMass !== void 0)
|
|
1290
|
-
entityRecord.automaticAdditionalMass = oldAutomaticRecordMass;
|
|
1291
|
-
const error = cause instanceof DerivedPhysicsError ? cause : new DerivedPhysicsError(
|
|
1292
|
-
"derived-backend-failed",
|
|
1293
|
-
"derived admission either commits completely or preserves the prior body state",
|
|
1294
|
-
"inspect the failure receipt and rebuild the PhysicsWorld if recovery is required",
|
|
1295
|
-
{
|
|
1389
|
+
const replacementConstraintIds = new Set(
|
|
1390
|
+
(record.input.constraints ?? []).map((constraint) => constraint.id)
|
|
1391
|
+
);
|
|
1392
|
+
for (const [constraintId, current] of [...this.derivedConstraints]) {
|
|
1393
|
+
if (replacementConstraintIds.has(constraintId) || this.constraintDependenciesMatch(current.input, committedSources))
|
|
1394
|
+
continue;
|
|
1395
|
+
this.removeNativeConstraint(current.handle);
|
|
1396
|
+
this.derivedConstraints.delete(constraintId);
|
|
1397
|
+
}
|
|
1398
|
+
if (old !== void 0) {
|
|
1399
|
+
for (const shape of old.shapes) {
|
|
1400
|
+
const collider = this.raw.getCollider(shape.colliderHandle);
|
|
1401
|
+
if (collider !== null && collider !== void 0) collider.setEnabled(false);
|
|
1402
|
+
}
|
|
1403
|
+
this.retiredDerivedBodies.push(old);
|
|
1404
|
+
}
|
|
1405
|
+
for (const constraint of record.input.constraints ?? []) {
|
|
1406
|
+
const previous = this.derivedConstraints.get(constraint.id);
|
|
1407
|
+
if (previous !== void 0) this.removeNativeConstraint(previous.handle);
|
|
1408
|
+
const staged = stagedConstraints.get(constraint.id);
|
|
1409
|
+
if (staged !== void 0) this.derivedConstraints.set(constraint.id, staged);
|
|
1410
|
+
}
|
|
1411
|
+
const shapes = record.input.shapes.map(
|
|
1412
|
+
(shape, index) => ({
|
|
1413
|
+
input: shape,
|
|
1414
|
+
colliderHandle: record.nativeColliders[index]?.handle
|
|
1415
|
+
})
|
|
1416
|
+
);
|
|
1417
|
+
const committed = {
|
|
1296
1418
|
entity: record.input.entity,
|
|
1419
|
+
sourceKey: record.input.sourceKey,
|
|
1420
|
+
generation: this.backendGeneration,
|
|
1421
|
+
revision: record.input.revision,
|
|
1422
|
+
bodyType: record.input.bodyType,
|
|
1423
|
+
velocityPolicy: record.input.velocityPolicy,
|
|
1297
1424
|
candidateId: record.token.candidateId,
|
|
1298
|
-
|
|
1425
|
+
shapes,
|
|
1426
|
+
seams: [...record.input.seams ?? []],
|
|
1427
|
+
massProperties: record.input.massProperties,
|
|
1428
|
+
constraints: [...record.input.constraints ?? []]
|
|
1429
|
+
};
|
|
1430
|
+
const commitGeometry = record === group[group.length - 1] ? record.batch?.commitGeometry ?? record.commitGeometry : void 0;
|
|
1431
|
+
if (commitGeometry !== void 0) {
|
|
1432
|
+
this.activeDerivedAdmission = record;
|
|
1433
|
+
try {
|
|
1434
|
+
geometryCommitUncertain = true;
|
|
1435
|
+
const geometry = commitGeometry();
|
|
1436
|
+
geometryCommitUncertain = false;
|
|
1437
|
+
if (!geometry.ok) throw geometry.error;
|
|
1438
|
+
} finally {
|
|
1439
|
+
this.activeDerivedAdmission = void 0;
|
|
1440
|
+
}
|
|
1441
|
+
delete record.commitGeometry;
|
|
1299
1442
|
}
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
this.
|
|
1443
|
+
this.derivedBodies.set(record.input.entity, committed);
|
|
1444
|
+
const entityRecord = this.entityMap.get(record.input.entity);
|
|
1445
|
+
if (entityRecord !== void 0) {
|
|
1446
|
+
entityRecord.automaticAdditionalMass = record.input.massProperties?.mode === "explicit" ? 0 : entityRecord.additionalMass;
|
|
1447
|
+
}
|
|
1448
|
+
this.derivedBodySources.set(record.input.entity, {
|
|
1449
|
+
sourceKey: record.input.sourceKey,
|
|
1450
|
+
revision: record.input.revision
|
|
1451
|
+
});
|
|
1452
|
+
this.derivedFailures.delete(record.input.entity);
|
|
1453
|
+
record.state = "queued";
|
|
1454
|
+
this.pendingDerivedCandidates.delete(id);
|
|
1455
|
+
undos.push(rollback);
|
|
1456
|
+
} catch (cause) {
|
|
1457
|
+
let restored = restore(rollback);
|
|
1458
|
+
for (const undo of undos.reverse()) {
|
|
1459
|
+
if (!restore(undo)) restored = false;
|
|
1460
|
+
}
|
|
1461
|
+
const error = cause instanceof DerivedPhysicsError ? cause : new DerivedPhysicsError(
|
|
1462
|
+
"derived-backend-failed",
|
|
1463
|
+
"derived admission either commits completely or preserves the prior body state",
|
|
1464
|
+
"inspect the failure receipt and rebuild the PhysicsWorld if recovery is required",
|
|
1465
|
+
{
|
|
1466
|
+
entity: record.input.entity,
|
|
1467
|
+
candidateId: record.token.candidateId,
|
|
1468
|
+
reason: cause instanceof Error ? cause.message : String(cause)
|
|
1469
|
+
}
|
|
1470
|
+
);
|
|
1471
|
+
if (geometryCommitUncertain) restored = false;
|
|
1472
|
+
for (const member of group) {
|
|
1473
|
+
if (!restored) {
|
|
1474
|
+
this.derivedPoisonedEntities.add(member.input.entity);
|
|
1475
|
+
this.derivedBodies.delete(member.input.entity);
|
|
1476
|
+
this.derivedPublications.delete(member.input.entity);
|
|
1477
|
+
}
|
|
1478
|
+
this.rememberDerivedFailure(
|
|
1479
|
+
member,
|
|
1480
|
+
error,
|
|
1481
|
+
restored ? "old-state-retained" : "rebuild-required"
|
|
1482
|
+
);
|
|
1483
|
+
}
|
|
1484
|
+
break;
|
|
1306
1485
|
}
|
|
1307
|
-
this.rememberDerivedFailure(
|
|
1308
|
-
record,
|
|
1309
|
-
error,
|
|
1310
|
-
restored ? "old-state-retained" : "rebuild-required"
|
|
1311
|
-
);
|
|
1312
1486
|
}
|
|
1313
1487
|
}
|
|
1314
1488
|
}
|
|
@@ -1328,6 +1502,7 @@ var RapierPhysicsWorld3D = class {
|
|
|
1328
1502
|
})
|
|
1329
1503
|
);
|
|
1330
1504
|
candidate.state = "published";
|
|
1505
|
+
delete candidate.batch;
|
|
1331
1506
|
}
|
|
1332
1507
|
}
|
|
1333
1508
|
retireDerivedBodies() {
|
|
@@ -1360,6 +1535,7 @@ var RapierPhysicsWorld3D = class {
|
|
|
1360
1535
|
{ x: frame[0], y: frame[1], z: frame[2], w: frame[3] },
|
|
1361
1536
|
true
|
|
1362
1537
|
);
|
|
1538
|
+
body.recomputeMassPropertiesFromColliders();
|
|
1363
1539
|
} else {
|
|
1364
1540
|
this.restoreAutomaticDensities(
|
|
1365
1541
|
entity,
|
|
@@ -1404,6 +1580,7 @@ var RapierPhysicsWorld3D = class {
|
|
|
1404
1580
|
{ x: frame[0], y: frame[1], z: frame[2], w: frame[3] },
|
|
1405
1581
|
true
|
|
1406
1582
|
);
|
|
1583
|
+
body.recomputeMassPropertiesFromColliders();
|
|
1407
1584
|
return;
|
|
1408
1585
|
}
|
|
1409
1586
|
this.restoreAutomaticMass(body, automaticAdditionalMass);
|
|
@@ -2189,6 +2366,7 @@ var RapierPhysicsWorld3D = class {
|
|
|
2189
2366
|
);
|
|
2190
2367
|
if (collider === void 0) {
|
|
2191
2368
|
const record = this.entityMap.get(entity);
|
|
2369
|
+
this.restoreAutomaticMass(body, record?.additionalMass ?? 0);
|
|
2192
2370
|
if (record !== void 0) record.automaticAdditionalMass = record.additionalMass;
|
|
2193
2371
|
return;
|
|
2194
2372
|
}
|
|
@@ -2346,6 +2524,19 @@ var RapierPhysicsWorld3D = class {
|
|
|
2346
2524
|
removeEntity(entity) {
|
|
2347
2525
|
const record = this.entityMap.get(entity);
|
|
2348
2526
|
if (!record) return;
|
|
2527
|
+
for (const candidate of this.derivedCandidates.values()) {
|
|
2528
|
+
if (candidate.input.entity !== entity || candidate.batch === void 0 || candidate.state !== "ready" && candidate.state !== "queued")
|
|
2529
|
+
continue;
|
|
2530
|
+
this.rejectPreparedCandidate(
|
|
2531
|
+
candidate,
|
|
2532
|
+
new DerivedPhysicsError(
|
|
2533
|
+
"derived-body-not-found",
|
|
2534
|
+
"all grouped bodies remain live until admission",
|
|
2535
|
+
"prepare a new complete group after entity reconciliation",
|
|
2536
|
+
{ entity }
|
|
2537
|
+
)
|
|
2538
|
+
);
|
|
2539
|
+
}
|
|
2349
2540
|
const derived = this.derivedBodies.get(entity);
|
|
2350
2541
|
if (derived !== void 0) {
|
|
2351
2542
|
for (const shape of derived.shapes) this.removeNativeCollider(shape.colliderHandle);
|