react-three-rapier-unified 1.0.0
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/LICENSE +9 -0
- package/README.md +166 -0
- package/dist/index.cjs.js +31 -0
- package/dist/index.d.ts +1191 -0
- package/dist/index.esm.js +1667 -0
- package/package.json +53 -0
- package/src/addons/attractor/Attractor.tsx +178 -0
- package/src/addons/attractor/AttractorDebugHelper.tsx +59 -0
- package/src/components/AnyCollider.tsx +286 -0
- package/src/components/Debug.tsx +33 -0
- package/src/components/FrameStepper.tsx +36 -0
- package/src/components/InstancedRigidBodies.tsx +135 -0
- package/src/components/MeshCollider.tsx +53 -0
- package/src/components/Physics.tsx +894 -0
- package/src/components/RigidBody.tsx +153 -0
- package/src/hooks/hooks.ts +211 -0
- package/src/hooks/joints.ts +221 -0
- package/src/hooks/use-forwarded-ref.ts +19 -0
- package/src/hooks/use-imperative-instance.ts +33 -0
- package/src/index.ts +55 -0
- package/src/types.ts +541 -0
- package/src/utils/interaction-groups.ts +43 -0
- package/src/utils/shared-objects.ts +10 -0
- package/src/utils/singleton-proxy.ts +51 -0
- package/src/utils/three-object-helpers.ts +25 -0
- package/src/utils/utils-collider.ts +491 -0
- package/src/utils/utils-physics.ts +26 -0
- package/src/utils/utils-rigidbody.ts +231 -0
- package/src/utils/utils.ts +105 -0
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Collider,
|
|
3
|
+
ColliderDesc,
|
|
4
|
+
ActiveEvents,
|
|
5
|
+
RigidBody,
|
|
6
|
+
World
|
|
7
|
+
} from "@dimforge/rapier3d-compat";
|
|
8
|
+
import { useEffect, useMemo } from "react";
|
|
9
|
+
import { BufferGeometry, Euler, Mesh, Object3D, Vector3 } from "three";
|
|
10
|
+
import { mergeVertices } from "three-stdlib";
|
|
11
|
+
import { ColliderProps, RigidBodyProps } from "..";
|
|
12
|
+
import {
|
|
13
|
+
ColliderState,
|
|
14
|
+
ColliderStateMap,
|
|
15
|
+
EventMap
|
|
16
|
+
} from "../components/Physics";
|
|
17
|
+
import {
|
|
18
|
+
_matrix4,
|
|
19
|
+
_position,
|
|
20
|
+
_rotation,
|
|
21
|
+
_scale,
|
|
22
|
+
_vector3
|
|
23
|
+
} from "./shared-objects";
|
|
24
|
+
import { ColliderShape, RigidBodyAutoCollider } from "../types";
|
|
25
|
+
import { scaleVertices, vectorToTuple } from "./utils";
|
|
26
|
+
|
|
27
|
+
export const scaleColliderArgs = (
|
|
28
|
+
shape: ColliderShape,
|
|
29
|
+
args: (number | ArrayLike<number> | { x: number; y: number; z: number })[],
|
|
30
|
+
scale: Vector3
|
|
31
|
+
) => {
|
|
32
|
+
const newArgs = args.slice();
|
|
33
|
+
|
|
34
|
+
// Heightfield uses a vector
|
|
35
|
+
if (shape === "heightfield") {
|
|
36
|
+
const s = newArgs[3] as { x: number; y: number; z: number };
|
|
37
|
+
s.x *= scale.x;
|
|
38
|
+
s.x *= scale.y;
|
|
39
|
+
s.x *= scale.z;
|
|
40
|
+
|
|
41
|
+
return newArgs;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Trimesh and convex scale the vertices
|
|
45
|
+
if (shape === "trimesh" || shape === "convexHull") {
|
|
46
|
+
newArgs[0] = scaleVertices(newArgs[0] as ArrayLike<number>, scale);
|
|
47
|
+
return newArgs;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Prepfill with some extra
|
|
51
|
+
const scaleArray = [scale.x, scale.y, scale.z, scale.x, scale.x];
|
|
52
|
+
return newArgs.map((arg, index) => scaleArray[index] * (arg as number));
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const createColliderFromOptions = (
|
|
56
|
+
options: ColliderProps,
|
|
57
|
+
world: World,
|
|
58
|
+
scale: Vector3,
|
|
59
|
+
getRigidBody?: () => RigidBody
|
|
60
|
+
) => {
|
|
61
|
+
const scaledArgs = scaleColliderArgs(options.shape!, options.args, scale);
|
|
62
|
+
// @ts-ignore
|
|
63
|
+
const desc = ColliderDesc[options.shape!](...scaledArgs);
|
|
64
|
+
|
|
65
|
+
return world.createCollider(desc!, getRigidBody?.());
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
type ImmutableColliderOptions = (keyof ColliderProps)[];
|
|
69
|
+
|
|
70
|
+
export const immutableColliderOptions: ImmutableColliderOptions = [
|
|
71
|
+
"shape",
|
|
72
|
+
"args"
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
type MutableColliderOptions = {
|
|
76
|
+
[key in keyof ColliderProps]: (
|
|
77
|
+
collider: Collider,
|
|
78
|
+
value: Exclude<ColliderProps[key], undefined>,
|
|
79
|
+
options: ColliderProps
|
|
80
|
+
) => void;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const massPropertiesConflictError =
|
|
84
|
+
"Please pick ONLY ONE of the `density`, `mass` and `massProperties` options.";
|
|
85
|
+
|
|
86
|
+
type MassPropertiesType = "mass" | "massProperties" | "density";
|
|
87
|
+
const setColliderMassOptions = (
|
|
88
|
+
collider: Collider,
|
|
89
|
+
options: Pick<ColliderProps, MassPropertiesType>
|
|
90
|
+
) => {
|
|
91
|
+
if (options.density !== undefined) {
|
|
92
|
+
if (options.mass !== undefined || options.massProperties !== undefined) {
|
|
93
|
+
throw new Error(massPropertiesConflictError);
|
|
94
|
+
}
|
|
95
|
+
collider.setDensity(options.density);
|
|
96
|
+
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (options.mass !== undefined) {
|
|
101
|
+
if (options.massProperties !== undefined) {
|
|
102
|
+
throw new Error(massPropertiesConflictError);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
collider.setMass(options.mass);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (options.massProperties !== undefined) {
|
|
110
|
+
collider.setMassProperties(
|
|
111
|
+
options.massProperties.mass,
|
|
112
|
+
options.massProperties.centerOfMass,
|
|
113
|
+
options.massProperties.principalAngularInertia,
|
|
114
|
+
options.massProperties.angularInertiaLocalFrame
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const mutableColliderOptions: MutableColliderOptions = {
|
|
120
|
+
sensor: (collider, value: boolean) => {
|
|
121
|
+
collider.setSensor(value);
|
|
122
|
+
},
|
|
123
|
+
collisionGroups: (collider, value: number) => {
|
|
124
|
+
collider.setCollisionGroups(value);
|
|
125
|
+
},
|
|
126
|
+
solverGroups: (collider, value: number) => {
|
|
127
|
+
collider.setSolverGroups(value);
|
|
128
|
+
},
|
|
129
|
+
friction: (collider, value: number) => {
|
|
130
|
+
collider.setFriction(value);
|
|
131
|
+
},
|
|
132
|
+
frictionCombineRule: (collider, value) => {
|
|
133
|
+
collider.setFrictionCombineRule(value);
|
|
134
|
+
},
|
|
135
|
+
restitution: (collider, value: number) => {
|
|
136
|
+
collider.setRestitution(value);
|
|
137
|
+
},
|
|
138
|
+
restitutionCombineRule: (collider, value) => {
|
|
139
|
+
collider.setRestitutionCombineRule(value);
|
|
140
|
+
},
|
|
141
|
+
activeCollisionTypes: (collider, value: number) => {
|
|
142
|
+
collider.setActiveCollisionTypes(value);
|
|
143
|
+
},
|
|
144
|
+
contactSkin: (collider, value: number) => {
|
|
145
|
+
collider.setContactSkin(value);
|
|
146
|
+
},
|
|
147
|
+
// To make sure the options all mutable options are listed
|
|
148
|
+
quaternion: () => {},
|
|
149
|
+
position: () => {},
|
|
150
|
+
rotation: () => {},
|
|
151
|
+
scale: () => {}
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const mutableColliderOptionKeys = Object.keys(
|
|
155
|
+
mutableColliderOptions
|
|
156
|
+
) as (keyof ColliderProps)[];
|
|
157
|
+
|
|
158
|
+
export const setColliderOptions = (
|
|
159
|
+
collider: Collider,
|
|
160
|
+
options: ColliderProps,
|
|
161
|
+
states: ColliderStateMap
|
|
162
|
+
) => {
|
|
163
|
+
const state = states.get(collider.handle);
|
|
164
|
+
|
|
165
|
+
if (state) {
|
|
166
|
+
// Update collider position based on the object's position
|
|
167
|
+
const parentWorldScale = state.object.parent!.getWorldScale(_vector3);
|
|
168
|
+
const parentInvertedWorldMatrix = state.worldParent?.matrixWorld
|
|
169
|
+
.clone()
|
|
170
|
+
.invert();
|
|
171
|
+
|
|
172
|
+
state.object.updateWorldMatrix(true, false);
|
|
173
|
+
|
|
174
|
+
_matrix4.copy(state.object.matrixWorld);
|
|
175
|
+
|
|
176
|
+
if (parentInvertedWorldMatrix) {
|
|
177
|
+
_matrix4.premultiply(parentInvertedWorldMatrix);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
_matrix4.decompose(_position, _rotation, _scale);
|
|
181
|
+
|
|
182
|
+
if (collider.parent()) {
|
|
183
|
+
collider.setTranslationWrtParent({
|
|
184
|
+
x: _position.x * parentWorldScale.x,
|
|
185
|
+
y: _position.y * parentWorldScale.y,
|
|
186
|
+
z: _position.z * parentWorldScale.z
|
|
187
|
+
});
|
|
188
|
+
collider.setRotationWrtParent(_rotation);
|
|
189
|
+
} else {
|
|
190
|
+
collider.setTranslation({
|
|
191
|
+
x: _position.x * parentWorldScale.x,
|
|
192
|
+
y: _position.y * parentWorldScale.y,
|
|
193
|
+
z: _position.z * parentWorldScale.z
|
|
194
|
+
});
|
|
195
|
+
collider.setRotation(_rotation);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
mutableColliderOptionKeys.forEach((key) => {
|
|
199
|
+
if (key in options) {
|
|
200
|
+
const option = options[key];
|
|
201
|
+
mutableColliderOptions[key]!(
|
|
202
|
+
collider,
|
|
203
|
+
// @ts-ignore Option does not want to fit into the function, but it will
|
|
204
|
+
option,
|
|
205
|
+
options
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// handle mass separately, because the assignments
|
|
211
|
+
// are exclusive.
|
|
212
|
+
setColliderMassOptions(collider, options);
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
export const useUpdateColliderOptions = (
|
|
217
|
+
getCollider: () => Collider,
|
|
218
|
+
props: ColliderProps,
|
|
219
|
+
states: ColliderStateMap
|
|
220
|
+
) => {
|
|
221
|
+
// TODO: Improve this, split each prop into its own effect
|
|
222
|
+
const mutablePropsAsFlatArray = useMemo(
|
|
223
|
+
() =>
|
|
224
|
+
mutableColliderOptionKeys.flatMap((key) => {
|
|
225
|
+
return vectorToTuple(props[key as keyof ColliderProps]);
|
|
226
|
+
}),
|
|
227
|
+
[props]
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
useEffect(() => {
|
|
231
|
+
const collider = getCollider();
|
|
232
|
+
setColliderOptions(collider, props, states);
|
|
233
|
+
}, [...mutablePropsAsFlatArray, getCollider]);
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
const isChildOfMeshCollider = (child: Mesh) => {
|
|
237
|
+
let flag = false;
|
|
238
|
+
child.traverseAncestors((a) => {
|
|
239
|
+
if (a.userData.r3RapierType === "MeshCollider") flag = true;
|
|
240
|
+
});
|
|
241
|
+
return flag;
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
export const createColliderState = (
|
|
245
|
+
collider: Collider,
|
|
246
|
+
object: Object3D,
|
|
247
|
+
rigidBodyObject?: Object3D | null
|
|
248
|
+
): ColliderState => {
|
|
249
|
+
return {
|
|
250
|
+
collider,
|
|
251
|
+
worldParent: rigidBodyObject || undefined,
|
|
252
|
+
object
|
|
253
|
+
};
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const autoColliderMap: Record<string, string> = {
|
|
257
|
+
cuboid: "cuboid",
|
|
258
|
+
ball: "ball",
|
|
259
|
+
hull: "convexHull",
|
|
260
|
+
trimesh: "trimesh"
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
interface CreateColliderPropsFromChildren {
|
|
264
|
+
(options: {
|
|
265
|
+
object: Object3D;
|
|
266
|
+
ignoreMeshColliders: boolean;
|
|
267
|
+
options: RigidBodyProps;
|
|
268
|
+
}): ColliderProps[];
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export const createColliderPropsFromChildren: CreateColliderPropsFromChildren =
|
|
272
|
+
({ object, ignoreMeshColliders = true, options }): ColliderProps[] => {
|
|
273
|
+
const childColliderProps: ColliderProps[] = [];
|
|
274
|
+
|
|
275
|
+
object.updateWorldMatrix(true, false);
|
|
276
|
+
const invertedParentMatrixWorld = object.matrixWorld.clone().invert();
|
|
277
|
+
|
|
278
|
+
const colliderFromChild = (child: Object3D) => {
|
|
279
|
+
if ("isMesh" in child) {
|
|
280
|
+
if (ignoreMeshColliders && isChildOfMeshCollider(child as Mesh)) return;
|
|
281
|
+
|
|
282
|
+
const worldScale = child.getWorldScale(_scale);
|
|
283
|
+
const shape = autoColliderMap[
|
|
284
|
+
options.colliders || "cuboid"
|
|
285
|
+
] as ColliderShape;
|
|
286
|
+
|
|
287
|
+
child.updateWorldMatrix(true, false);
|
|
288
|
+
_matrix4
|
|
289
|
+
.copy(child.matrixWorld)
|
|
290
|
+
.premultiply(invertedParentMatrixWorld)
|
|
291
|
+
.decompose(_position, _rotation, _scale);
|
|
292
|
+
|
|
293
|
+
const rotationEuler = new Euler().setFromQuaternion(_rotation, "XYZ");
|
|
294
|
+
|
|
295
|
+
const { geometry } = child as Mesh;
|
|
296
|
+
const { args, offset } = getColliderArgsFromGeometry(
|
|
297
|
+
geometry,
|
|
298
|
+
options.colliders || "cuboid"
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
const colliderProps: ColliderProps = {
|
|
302
|
+
...cleanRigidBodyPropsForCollider(options),
|
|
303
|
+
args: args,
|
|
304
|
+
shape: shape,
|
|
305
|
+
rotation: [rotationEuler.x, rotationEuler.y, rotationEuler.z],
|
|
306
|
+
position: [
|
|
307
|
+
_position.x + offset.x * worldScale.x,
|
|
308
|
+
_position.y + offset.y * worldScale.y,
|
|
309
|
+
_position.z + offset.z * worldScale.z
|
|
310
|
+
],
|
|
311
|
+
scale: [worldScale.x, worldScale.y, worldScale.z]
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
childColliderProps.push(colliderProps);
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
if (options.includeInvisible) {
|
|
319
|
+
object.traverse(colliderFromChild);
|
|
320
|
+
} else {
|
|
321
|
+
object.traverseVisible(colliderFromChild);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return childColliderProps;
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
export const getColliderArgsFromGeometry = (
|
|
328
|
+
geometry: BufferGeometry,
|
|
329
|
+
colliders: RigidBodyAutoCollider
|
|
330
|
+
): { args: unknown[]; offset: Vector3 } => {
|
|
331
|
+
switch (colliders) {
|
|
332
|
+
case "cuboid":
|
|
333
|
+
{
|
|
334
|
+
geometry.computeBoundingBox();
|
|
335
|
+
const { boundingBox } = geometry;
|
|
336
|
+
|
|
337
|
+
const size = boundingBox!.getSize(new Vector3());
|
|
338
|
+
|
|
339
|
+
return {
|
|
340
|
+
args: [size.x / 2, size.y / 2, size.z / 2],
|
|
341
|
+
offset: boundingBox!.getCenter(new Vector3())
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
break;
|
|
345
|
+
|
|
346
|
+
case "ball":
|
|
347
|
+
{
|
|
348
|
+
geometry.computeBoundingSphere();
|
|
349
|
+
const { boundingSphere } = geometry;
|
|
350
|
+
|
|
351
|
+
const radius = boundingSphere!.radius;
|
|
352
|
+
|
|
353
|
+
return {
|
|
354
|
+
args: [radius],
|
|
355
|
+
offset: boundingSphere!.center
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
break;
|
|
359
|
+
|
|
360
|
+
case "trimesh":
|
|
361
|
+
{
|
|
362
|
+
const clonedGeometry = geometry.index
|
|
363
|
+
? geometry.clone()
|
|
364
|
+
: mergeVertices(geometry);
|
|
365
|
+
|
|
366
|
+
return {
|
|
367
|
+
args: [
|
|
368
|
+
clonedGeometry.attributes.position.array as Float32Array,
|
|
369
|
+
clonedGeometry.index?.array as Uint32Array
|
|
370
|
+
],
|
|
371
|
+
offset: new Vector3()
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
break;
|
|
375
|
+
|
|
376
|
+
case "hull":
|
|
377
|
+
{
|
|
378
|
+
const g = geometry.clone();
|
|
379
|
+
|
|
380
|
+
return {
|
|
381
|
+
args: [g.attributes.position.array as Float32Array],
|
|
382
|
+
offset: new Vector3()
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
break;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return { args: [], offset: new Vector3() };
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
export const getActiveCollisionEventsFromProps = (props?: ColliderProps) => {
|
|
392
|
+
return {
|
|
393
|
+
collision: !!(
|
|
394
|
+
props?.onCollisionEnter ||
|
|
395
|
+
props?.onCollisionExit ||
|
|
396
|
+
props?.onIntersectionEnter ||
|
|
397
|
+
props?.onIntersectionExit
|
|
398
|
+
),
|
|
399
|
+
contactForce: !!props?.onContactForce
|
|
400
|
+
};
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
export const useColliderEvents = (
|
|
404
|
+
getCollider: () => Collider,
|
|
405
|
+
props: ColliderProps,
|
|
406
|
+
events: EventMap,
|
|
407
|
+
/**
|
|
408
|
+
* The RigidBody can pass down active events to the collider without attaching the event listners
|
|
409
|
+
*/
|
|
410
|
+
activeEvents: {
|
|
411
|
+
collision?: boolean;
|
|
412
|
+
contactForce?: boolean;
|
|
413
|
+
} = {}
|
|
414
|
+
) => {
|
|
415
|
+
const {
|
|
416
|
+
onCollisionEnter,
|
|
417
|
+
onCollisionExit,
|
|
418
|
+
onIntersectionEnter,
|
|
419
|
+
onIntersectionExit,
|
|
420
|
+
onContactForce
|
|
421
|
+
} = props;
|
|
422
|
+
|
|
423
|
+
useEffect(() => {
|
|
424
|
+
const collider = getCollider();
|
|
425
|
+
|
|
426
|
+
if (collider) {
|
|
427
|
+
const {
|
|
428
|
+
collision: collisionEventsActive,
|
|
429
|
+
contactForce: contactForceEventsActive
|
|
430
|
+
} = getActiveCollisionEventsFromProps(props);
|
|
431
|
+
|
|
432
|
+
const hasCollisionEvent = collisionEventsActive || activeEvents.collision;
|
|
433
|
+
const hasContactForceEvent =
|
|
434
|
+
contactForceEventsActive || activeEvents.contactForce;
|
|
435
|
+
|
|
436
|
+
if (hasCollisionEvent && hasContactForceEvent) {
|
|
437
|
+
collider.setActiveEvents(
|
|
438
|
+
ActiveEvents.COLLISION_EVENTS | ActiveEvents.CONTACT_FORCE_EVENTS
|
|
439
|
+
);
|
|
440
|
+
} else if (hasCollisionEvent) {
|
|
441
|
+
collider.setActiveEvents(ActiveEvents.COLLISION_EVENTS);
|
|
442
|
+
} else if (hasContactForceEvent) {
|
|
443
|
+
collider.setActiveEvents(ActiveEvents.CONTACT_FORCE_EVENTS);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
events.set(collider.handle, {
|
|
447
|
+
onCollisionEnter,
|
|
448
|
+
onCollisionExit,
|
|
449
|
+
onIntersectionEnter,
|
|
450
|
+
onIntersectionExit,
|
|
451
|
+
onContactForce
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
return () => {
|
|
456
|
+
if (collider) {
|
|
457
|
+
events.delete(collider.handle);
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
}, [
|
|
461
|
+
onCollisionEnter,
|
|
462
|
+
onCollisionExit,
|
|
463
|
+
onIntersectionEnter,
|
|
464
|
+
onIntersectionExit,
|
|
465
|
+
onContactForce,
|
|
466
|
+
activeEvents
|
|
467
|
+
]);
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
export const cleanRigidBodyPropsForCollider = (props: RigidBodyProps = {}) => {
|
|
471
|
+
const {
|
|
472
|
+
mass,
|
|
473
|
+
linearDamping,
|
|
474
|
+
angularDamping,
|
|
475
|
+
type,
|
|
476
|
+
onCollisionEnter,
|
|
477
|
+
onCollisionExit,
|
|
478
|
+
onIntersectionEnter,
|
|
479
|
+
onIntersectionExit,
|
|
480
|
+
onContactForce,
|
|
481
|
+
children,
|
|
482
|
+
canSleep,
|
|
483
|
+
ccd,
|
|
484
|
+
gravityScale,
|
|
485
|
+
softCcdPrediction,
|
|
486
|
+
ref,
|
|
487
|
+
...rest
|
|
488
|
+
} = props;
|
|
489
|
+
|
|
490
|
+
return rest;
|
|
491
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
|
|
3
|
+
export const useRaf = (callback: (dt: number) => void) => {
|
|
4
|
+
const cb = useRef(callback);
|
|
5
|
+
const raf = useRef(0);
|
|
6
|
+
const lastFrame = useRef(0);
|
|
7
|
+
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
cb.current = callback;
|
|
10
|
+
}, [callback]);
|
|
11
|
+
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
const loop = () => {
|
|
14
|
+
const now = performance.now();
|
|
15
|
+
const delta = now - lastFrame.current;
|
|
16
|
+
|
|
17
|
+
raf.current = requestAnimationFrame(loop);
|
|
18
|
+
cb.current(delta / 1000);
|
|
19
|
+
lastFrame.current = now;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
raf.current = requestAnimationFrame(loop);
|
|
23
|
+
|
|
24
|
+
return () => cancelAnimationFrame(raf.current);
|
|
25
|
+
}, []);
|
|
26
|
+
};
|