@opsydyn/effect-three-rapier 0.3.0 → 0.6.4
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/CHANGELOG.md +43 -0
- package/README.md +61 -0
- package/dist/deterministic.d.ts +7 -0
- package/dist/deterministic.d.ts.map +1 -0
- package/dist/deterministic.js +387 -0
- package/dist/deterministic.js.map +12 -0
- package/dist/engine.d.ts +14 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +160 -27
- package/dist/index.js.map +6 -4
- package/dist/snapshot.d.ts +41 -0
- package/dist/snapshot.d.ts.map +1 -0
- package/dist/worker.d.ts +131 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +361 -0
- package/dist/worker.js.map +11 -0
- package/package.json +21 -4
- package/src/deterministic.ts +33 -0
- package/src/engine.ts +400 -0
- package/src/index.ts +6 -329
- package/src/snapshot.ts +130 -0
- package/src/worker.ts +432 -0
package/src/engine.ts
ADDED
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The engine itself, for whichever Rapier build is loaded: the standard one
|
|
3
|
+
* (`@dimforge/rapier3d-compat`, from the package's main entry) or the deterministic one
|
|
4
|
+
* (`/deterministic`). Both have the same API.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
import type RAPIER from '@dimforge/rapier3d-compat';
|
|
9
|
+
import { interactionBits } from '@opsydyn/effect-three';
|
|
10
|
+
import type {
|
|
11
|
+
Body,
|
|
12
|
+
BodyPose,
|
|
13
|
+
Collider,
|
|
14
|
+
ColliderShape,
|
|
15
|
+
Collision,
|
|
16
|
+
Physics,
|
|
17
|
+
ContactForce,
|
|
18
|
+
JointLimits,
|
|
19
|
+
JointMotor,
|
|
20
|
+
PhysicsEngine,
|
|
21
|
+
PhysicsJoint,
|
|
22
|
+
PhysicsWorld,
|
|
23
|
+
Vec3,
|
|
24
|
+
} from '@opsydyn/effect-three';
|
|
25
|
+
|
|
26
|
+
import { type IndexedBody, readSnapshot, type SnapshotIndex, writeSnapshot } from './snapshot.ts';
|
|
27
|
+
|
|
28
|
+
/** A loaded Rapier module: either build. */
|
|
29
|
+
export type Rapier = typeof RAPIER;
|
|
30
|
+
|
|
31
|
+
const vector = ([x, y, z]: Vec3) => ({ x, y, z });
|
|
32
|
+
|
|
33
|
+
const rotation = ([x, y, z, w]: BodyPose['quaternion']) => ({ x, y, z, w });
|
|
34
|
+
|
|
35
|
+
/** Body types by our names. Kinematic bodies are position based, as react-three-rapier's default. */
|
|
36
|
+
const bodyType = (rapier: Rapier, type: Body['type']): RAPIER.RigidBodyType =>
|
|
37
|
+
({
|
|
38
|
+
dynamic: rapier.RigidBodyType.Dynamic,
|
|
39
|
+
fixed: rapier.RigidBodyType.Fixed,
|
|
40
|
+
kinematic: rapier.RigidBodyType.KinematicPositionBased,
|
|
41
|
+
})[type];
|
|
42
|
+
|
|
43
|
+
/** The collider description for a shape; a hull Rapier cannot build becomes a small ball. */
|
|
44
|
+
const shapeDesc = (rapier: Rapier, shape: ColliderShape): RAPIER.ColliderDesc => {
|
|
45
|
+
switch (shape._tag) {
|
|
46
|
+
case 'Cuboid':
|
|
47
|
+
return rapier.ColliderDesc.cuboid(...shape.halfExtents);
|
|
48
|
+
case 'Ball':
|
|
49
|
+
return rapier.ColliderDesc.ball(shape.radius);
|
|
50
|
+
case 'Hull':
|
|
51
|
+
return rapier.ColliderDesc.convexHull(shape.points) ?? rapier.ColliderDesc.ball(1e-3);
|
|
52
|
+
case 'Capsule':
|
|
53
|
+
return rapier.ColliderDesc.capsule(shape.halfHeight, shape.radius);
|
|
54
|
+
case 'Cylinder':
|
|
55
|
+
return rapier.ColliderDesc.cylinder(shape.halfHeight, shape.radius);
|
|
56
|
+
case 'Cone':
|
|
57
|
+
return rapier.ColliderDesc.cone(shape.halfHeight, shape.radius);
|
|
58
|
+
case 'Trimesh':
|
|
59
|
+
return rapier.ColliderDesc.trimesh(shape.vertices, shape.indices);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/** A collider placed in its body. */
|
|
64
|
+
const colliderDesc = (rapier: Rapier, collider: Collider): RAPIER.ColliderDesc =>
|
|
65
|
+
shapeDesc(rapier, collider.shape)
|
|
66
|
+
.setTranslation(...collider.translation)
|
|
67
|
+
.setRotation(rotation(collider.rotation));
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The events a body's colliders report: collisions always, contact forces over its
|
|
71
|
+
* threshold when it has one (react-three-rapier's `onContactForce` turns on
|
|
72
|
+
* `CONTACT_FORCE_EVENTS`).
|
|
73
|
+
*/
|
|
74
|
+
const reporting = (rapier: Rapier, colliders: ReadonlyArray<RAPIER.Collider>, body: Body): void => {
|
|
75
|
+
for (const collider of colliders) {
|
|
76
|
+
if (body.contactForces === null) {
|
|
77
|
+
collider.setActiveEvents(rapier.ActiveEvents.COLLISION_EVENTS);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
collider.setActiveEvents(
|
|
81
|
+
rapier.ActiveEvents.COLLISION_EVENTS | rapier.ActiveEvents.CONTACT_FORCE_EVENTS,
|
|
82
|
+
);
|
|
83
|
+
collider.setContactForceEventThreshold(body.contactForces);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Applies a body's surface and mass settings to its colliders. A given mass is shared
|
|
89
|
+
* among them; otherwise each has density 1.
|
|
90
|
+
*/
|
|
91
|
+
const surface = (colliders: ReadonlyArray<RAPIER.Collider>, body: Body): void => {
|
|
92
|
+
for (const collider of colliders) {
|
|
93
|
+
collider.setFriction(body.friction);
|
|
94
|
+
collider.setRestitution(body.restitution);
|
|
95
|
+
collider.setSensor(body.sensor);
|
|
96
|
+
if (body.mass === null) collider.setDensity(1);
|
|
97
|
+
else collider.setMass(body.mass / colliders.length);
|
|
98
|
+
collider.setCollisionGroups(interactionBits(body.collisionGroups));
|
|
99
|
+
collider.setSolverGroups(interactionBits(body.solverGroups));
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/** Applies a body's motion settings. */
|
|
104
|
+
const motion = (rapier: Rapier, rigidBody: RAPIER.RigidBody, body: Body): void => {
|
|
105
|
+
rigidBody.setBodyType(bodyType(rapier, body.type), true);
|
|
106
|
+
rigidBody.setLinearDamping(body.linearDamping);
|
|
107
|
+
rigidBody.setAngularDamping(body.angularDamping);
|
|
108
|
+
rigidBody.setGravityScale(body.gravityScale, true);
|
|
109
|
+
rigidBody.enableCcd(body.ccd);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/** The limits a joint has, if it is a hinge or slider with limits. */
|
|
113
|
+
const limitsOf = (joint: PhysicsJoint): JointLimits | null =>
|
|
114
|
+
joint.kind._tag === 'Revolute' || joint.kind._tag === 'Prismatic' ? joint.kind.limits : null;
|
|
115
|
+
|
|
116
|
+
/** The motor a joint has, if it is a hinge or slider with one. */
|
|
117
|
+
const motorOf = (joint: PhysicsJoint): JointMotor | null =>
|
|
118
|
+
joint.kind._tag === 'Revolute' || joint.kind._tag === 'Prismatic' ? joint.kind.motor : null;
|
|
119
|
+
|
|
120
|
+
/** Drives a hinge or slider once made: Rapier's `configureMotor`, set on the joint. */
|
|
121
|
+
const drive = (rapier: Rapier, made: RAPIER.ImpulseJoint, motor: JointMotor | null): void => {
|
|
122
|
+
if (motor !== null && made instanceof rapier.UnitImpulseJoint) {
|
|
123
|
+
made.configureMotor(motor.position, motor.velocity, motor.stiffness, motor.damping);
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Limits a hinge or slider once made. React-three-rapier sets them on the joint data, but
|
|
129
|
+
* Rapier's `JointData.intoRaw` (0.21) passes them on for prismatic joints only, so a
|
|
130
|
+
* hinge's would be lost: they are set on the joint instead.
|
|
131
|
+
*/
|
|
132
|
+
const limit = (rapier: Rapier, made: RAPIER.ImpulseJoint, limits: JointLimits | null): void => {
|
|
133
|
+
if (limits !== null && made instanceof rapier.UnitImpulseJoint) made.setLimits(...limits);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
/** Rapier's joint data for a joint, anchors in each body's space. */
|
|
137
|
+
const jointData = (rapier: Rapier, joint: PhysicsJoint): RAPIER.JointData => {
|
|
138
|
+
const a = vector(joint.anchorA);
|
|
139
|
+
const b = vector(joint.anchorB);
|
|
140
|
+
const { kind } = joint;
|
|
141
|
+
switch (kind._tag) {
|
|
142
|
+
case 'Fixed':
|
|
143
|
+
return rapier.JointData.fixed(a, rotation(kind.frameA), b, rotation(kind.frameB));
|
|
144
|
+
case 'Spherical':
|
|
145
|
+
return rapier.JointData.spherical(a, b);
|
|
146
|
+
case 'Revolute':
|
|
147
|
+
return rapier.JointData.revolute(a, b, vector(kind.axis));
|
|
148
|
+
case 'Prismatic':
|
|
149
|
+
return rapier.JointData.prismatic(a, b, vector(kind.axis));
|
|
150
|
+
case 'Rope':
|
|
151
|
+
return rapier.JointData.rope(kind.length, a, b);
|
|
152
|
+
case 'Spring':
|
|
153
|
+
return rapier.JointData.spring(kind.restLength, kind.stiffness, kind.damping, a, b);
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
/** Which body a collider belongs to, and which part of it, by name. */
|
|
158
|
+
type Owner = { readonly key: string; readonly part?: string };
|
|
159
|
+
|
|
160
|
+
/** Two colliders' owners as an event's `a` and `b`, with their parts. */
|
|
161
|
+
const pair = (a: Owner, b: Owner) => ({
|
|
162
|
+
a: a.key,
|
|
163
|
+
b: b.key,
|
|
164
|
+
...(a.part === undefined ? {} : { aPart: a.part }),
|
|
165
|
+
...(b.part === undefined ? {} : { bPart: b.part }),
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
type Held = {
|
|
169
|
+
readonly rigidBody: RAPIER.RigidBody;
|
|
170
|
+
readonly colliders: ReadonlyArray<RAPIER.Collider>;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
type Joined = { readonly joint: RAPIER.ImpulseJoint; readonly a: string; readonly b: string };
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* A Rapier world as a `PhysicsWorld`: a new one, or one restored from a snapshot with the
|
|
177
|
+
* index of what it holds.
|
|
178
|
+
*/
|
|
179
|
+
const hostWorld =
|
|
180
|
+
(rapier: Rapier) =>
|
|
181
|
+
(world: RAPIER.World, index: SnapshotIndex = { bodies: [], joints: [] }): PhysicsWorld => {
|
|
182
|
+
const events = new rapier.EventQueue(true);
|
|
183
|
+
const bodies = new Map<string, Held>(
|
|
184
|
+
index.bodies.map((body) => [
|
|
185
|
+
body.key,
|
|
186
|
+
{
|
|
187
|
+
rigidBody: world.getRigidBody(body.handle),
|
|
188
|
+
colliders: body.colliders.map((collider) => world.getCollider(collider.handle)),
|
|
189
|
+
},
|
|
190
|
+
]),
|
|
191
|
+
);
|
|
192
|
+
/** Each collider's body, and the part of it the collider is (when named). */
|
|
193
|
+
const keysByCollider = new Map<number, Owner>(
|
|
194
|
+
index.bodies.flatMap(({ key, colliders }) =>
|
|
195
|
+
colliders.map(({ handle, part }) => [handle, part === undefined ? { key } : { key, part }]),
|
|
196
|
+
),
|
|
197
|
+
);
|
|
198
|
+
const joints = new Map<string, Joined>(
|
|
199
|
+
index.joints.map(({ id, handle, a, b }) => [
|
|
200
|
+
id,
|
|
201
|
+
{ joint: world.getImpulseJoint(handle), a, b },
|
|
202
|
+
]),
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
const add: PhysicsWorld['add'] = (key, body, colliders, pose) => {
|
|
206
|
+
const desc = new rapier.RigidBodyDesc(bodyType(rapier, body.type))
|
|
207
|
+
.setTranslation(...pose.position)
|
|
208
|
+
.setRotation(rotation(pose.quaternion))
|
|
209
|
+
.setLinvel(...body.linearVelocity)
|
|
210
|
+
.setAngvel(vector(body.angularVelocity))
|
|
211
|
+
.setLinearDamping(body.linearDamping)
|
|
212
|
+
.setAngularDamping(body.angularDamping)
|
|
213
|
+
.setGravityScale(body.gravityScale)
|
|
214
|
+
.setCcdEnabled(body.ccd);
|
|
215
|
+
const rigidBody = world.createRigidBody(desc);
|
|
216
|
+
const made = colliders.map((collider) =>
|
|
217
|
+
world.createCollider(colliderDesc(rapier, collider), rigidBody),
|
|
218
|
+
);
|
|
219
|
+
surface(made, body);
|
|
220
|
+
reporting(rapier, made, body);
|
|
221
|
+
bodies.set(key, { rigidBody, colliders: made });
|
|
222
|
+
made.forEach((collider, i) => {
|
|
223
|
+
const part = colliders[i]?.part;
|
|
224
|
+
keysByCollider.set(collider.handle, { key, ...(part === undefined ? {} : { part }) });
|
|
225
|
+
});
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const remove: PhysicsWorld['remove'] = (key) => {
|
|
229
|
+
const held = bodies.get(key);
|
|
230
|
+
if (held === undefined) return;
|
|
231
|
+
for (const collider of held.colliders) keysByCollider.delete(collider.handle);
|
|
232
|
+
// Rapier removes the body's joints with it; forget them, as their handles are reused.
|
|
233
|
+
for (const [id, joined] of joints)
|
|
234
|
+
if (joined.a === key || joined.b === key) joints.delete(id);
|
|
235
|
+
world.removeRigidBody(held.rigidBody);
|
|
236
|
+
bodies.delete(key);
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const addJoint: PhysicsWorld['addJoint'] = (id, joint, a, b) => {
|
|
240
|
+
const first = bodies.get(a);
|
|
241
|
+
const second = bodies.get(b);
|
|
242
|
+
if (first === undefined || second === undefined) return;
|
|
243
|
+
const made = world.createImpulseJoint(
|
|
244
|
+
jointData(rapier, joint),
|
|
245
|
+
first.rigidBody,
|
|
246
|
+
second.rigidBody,
|
|
247
|
+
true,
|
|
248
|
+
);
|
|
249
|
+
limit(rapier, made, limitsOf(joint));
|
|
250
|
+
drive(rapier, made, motorOf(joint));
|
|
251
|
+
made.setContactsEnabled(joint.contacts);
|
|
252
|
+
joints.set(id, { joint: made, a, b });
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
const removeJoint: PhysicsWorld['removeJoint'] = (id) => {
|
|
256
|
+
const joined = joints.get(id);
|
|
257
|
+
if (joined === undefined) return;
|
|
258
|
+
world.removeImpulseJoint(joined.joint, true);
|
|
259
|
+
joints.delete(id);
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
const collisionsOf = (): ReadonlyArray<Collision> => {
|
|
263
|
+
const collisions: Array<Collision> = [];
|
|
264
|
+
events.drainCollisionEvents((first, second, started) => {
|
|
265
|
+
const a = keysByCollider.get(first);
|
|
266
|
+
const b = keysByCollider.get(second);
|
|
267
|
+
if (a !== undefined && b !== undefined) collisions.push({ ...pair(a, b), started });
|
|
268
|
+
});
|
|
269
|
+
return collisions;
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
/** Contact force events since the last call, kept across steps until asked for. */
|
|
273
|
+
let forces: Array<ContactForce> = [];
|
|
274
|
+
function recordForce(event: RAPIER.TempContactForceEvent): void {
|
|
275
|
+
const a = keysByCollider.get(event.collider1());
|
|
276
|
+
const b = keysByCollider.get(event.collider2());
|
|
277
|
+
if (a === undefined || b === undefined) return;
|
|
278
|
+
const total = event.totalForce();
|
|
279
|
+
const direction = event.maxForceDirection();
|
|
280
|
+
forces.push({
|
|
281
|
+
...pair(a, b),
|
|
282
|
+
totalForce: [total.x, total.y, total.z],
|
|
283
|
+
totalForceMagnitude: event.totalForceMagnitude(),
|
|
284
|
+
maxForceDirection: [direction.x, direction.y, direction.z],
|
|
285
|
+
maxForceMagnitude: event.maxForceMagnitude(),
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
const drainForces = (): void => events.drainContactForceEvents(recordForce);
|
|
289
|
+
|
|
290
|
+
/** A collider as a snapshot indexes it: its handle, and its part when named. */
|
|
291
|
+
function indexed(collider: RAPIER.Collider): IndexedBody['colliders'][number] {
|
|
292
|
+
const part = keysByCollider.get(collider.handle)?.part;
|
|
293
|
+
return part === undefined ? { handle: collider.handle } : { handle: collider.handle, part };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return {
|
|
297
|
+
configure: (physics) => {
|
|
298
|
+
world.gravity = vector(physics.gravity);
|
|
299
|
+
},
|
|
300
|
+
add,
|
|
301
|
+
update: (key, body) => {
|
|
302
|
+
const held = bodies.get(key);
|
|
303
|
+
if (held === undefined) return;
|
|
304
|
+
motion(rapier, held.rigidBody, body);
|
|
305
|
+
surface(held.colliders, body);
|
|
306
|
+
reporting(rapier, held.colliders, body);
|
|
307
|
+
},
|
|
308
|
+
teleport: (key, pose) => {
|
|
309
|
+
const held = bodies.get(key);
|
|
310
|
+
if (held === undefined) return;
|
|
311
|
+
held.rigidBody.setTranslation(vector(pose.position), true);
|
|
312
|
+
held.rigidBody.setRotation(rotation(pose.quaternion), true);
|
|
313
|
+
held.rigidBody.setLinvel({ x: 0, y: 0, z: 0 }, true);
|
|
314
|
+
held.rigidBody.setAngvel({ x: 0, y: 0, z: 0 }, true);
|
|
315
|
+
},
|
|
316
|
+
moveKinematic: (key, pose) => {
|
|
317
|
+
const held = bodies.get(key);
|
|
318
|
+
if (held === undefined) return;
|
|
319
|
+
held.rigidBody.setNextKinematicTranslation(vector(pose.position));
|
|
320
|
+
held.rigidBody.setNextKinematicRotation(rotation(pose.quaternion));
|
|
321
|
+
},
|
|
322
|
+
applyImpulse: (key, impulse) => {
|
|
323
|
+
bodies.get(key)?.rigidBody.applyImpulse(vector(impulse), true);
|
|
324
|
+
},
|
|
325
|
+
remove,
|
|
326
|
+
addJoint,
|
|
327
|
+
removeJoint,
|
|
328
|
+
step: (seconds) => {
|
|
329
|
+
world.timestep = seconds;
|
|
330
|
+
world.step(events);
|
|
331
|
+
drainForces();
|
|
332
|
+
return collisionsOf();
|
|
333
|
+
},
|
|
334
|
+
contactForces: () => {
|
|
335
|
+
const drained = forces;
|
|
336
|
+
forces = [];
|
|
337
|
+
return drained;
|
|
338
|
+
},
|
|
339
|
+
// react-three-rapier's Debug: Rapier's own line rendering of colliders and joints.
|
|
340
|
+
debugLines: () => {
|
|
341
|
+
const { vertices, colors } = world.debugRender();
|
|
342
|
+
return { vertices, colors };
|
|
343
|
+
},
|
|
344
|
+
pose: (key) => {
|
|
345
|
+
const held = bodies.get(key);
|
|
346
|
+
if (held === undefined) return undefined;
|
|
347
|
+
const { x, y, z } = held.rigidBody.translation();
|
|
348
|
+
const turn = held.rigidBody.rotation();
|
|
349
|
+
return { position: [x, y, z], quaternion: [turn.x, turn.y, turn.z, turn.w] };
|
|
350
|
+
},
|
|
351
|
+
awake: () => {
|
|
352
|
+
for (const { rigidBody } of bodies.values()) {
|
|
353
|
+
if (rigidBody.isDynamic() && !rigidBody.isSleeping()) return true;
|
|
354
|
+
}
|
|
355
|
+
return false;
|
|
356
|
+
},
|
|
357
|
+
// Rapier's world, and which of its bodies, colliders and joints are which.
|
|
358
|
+
snapshot: () =>
|
|
359
|
+
writeSnapshot(
|
|
360
|
+
{
|
|
361
|
+
bodies: [...bodies].map(([key, held]) => ({
|
|
362
|
+
key,
|
|
363
|
+
handle: held.rigidBody.handle,
|
|
364
|
+
colliders: held.colliders.map(indexed),
|
|
365
|
+
})),
|
|
366
|
+
joints: [...joints].map(([id, { joint, a, b }]) => ({
|
|
367
|
+
id,
|
|
368
|
+
handle: joint.handle,
|
|
369
|
+
a,
|
|
370
|
+
b,
|
|
371
|
+
})),
|
|
372
|
+
},
|
|
373
|
+
world.takeSnapshot(),
|
|
374
|
+
),
|
|
375
|
+
dispose: () => {
|
|
376
|
+
bodies.clear();
|
|
377
|
+
keysByCollider.clear();
|
|
378
|
+
joints.clear();
|
|
379
|
+
events.free();
|
|
380
|
+
world.free();
|
|
381
|
+
},
|
|
382
|
+
};
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
/** The engine over a loaded Rapier module. */
|
|
386
|
+
export const engineOf = (rapier: Rapier): PhysicsEngine => {
|
|
387
|
+
const host = hostWorld(rapier);
|
|
388
|
+
return {
|
|
389
|
+
createWorld: (initial: Physics) => host(new rapier.World(vector(initial.gravity))),
|
|
390
|
+
// A snapshot carries its gravity and everything else in Rapier's world.
|
|
391
|
+
restoreWorld: (_physics, snapshot) => {
|
|
392
|
+
const { index, world } = readSnapshot(snapshot);
|
|
393
|
+
return {
|
|
394
|
+
world: host(rapier.World.restoreSnapshot(world), index),
|
|
395
|
+
bodies: index.bodies.map((body) => body.key),
|
|
396
|
+
joints: index.joints.map((joint) => joint.id),
|
|
397
|
+
};
|
|
398
|
+
},
|
|
399
|
+
};
|
|
400
|
+
};
|