@react-three/rapier 0.6.4 → 0.6.5
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.
@@ -19,6 +19,7 @@ export interface RapierContext {
|
|
19
19
|
colliders: RigidBodyAutoCollider;
|
20
20
|
};
|
21
21
|
rigidBodyEvents: EventMap;
|
22
|
+
isPaused: boolean;
|
22
23
|
}
|
23
24
|
export declare const RapierContext: React.Context<RapierContext | undefined>;
|
24
25
|
declare type EventMap = Map<ColliderHandle | RigidBodyHandle, {
|
@@ -51,14 +52,15 @@ interface RapierWorldProps {
|
|
51
52
|
* Setting this to a number (eg. 1/60) will run the
|
52
53
|
* simulation at that framerate.
|
53
54
|
*
|
54
|
-
*
|
55
|
-
|
56
|
-
|
57
|
-
|
55
|
+
* @defaultValue 1/60
|
56
|
+
*/
|
57
|
+
timeStep?: number;
|
58
|
+
/**
|
59
|
+
* Maximum number of fixed steps to take per function call.
|
58
60
|
*
|
59
|
-
* @defaultValue
|
61
|
+
* @defaultValue 10
|
60
62
|
*/
|
61
|
-
|
63
|
+
maxSubSteps?: number;
|
62
64
|
/**
|
63
65
|
* Pause the physics simulation
|
64
66
|
*
|
@@ -469,10 +469,15 @@ const Physics = ({
|
|
469
469
|
colliders: _colliders = "cuboid",
|
470
470
|
gravity: _gravity = [0, -9.81, 0],
|
471
471
|
children,
|
472
|
-
timeStep: _timeStep =
|
472
|
+
timeStep: _timeStep = 1 / 60,
|
473
|
+
maxSubSteps: _maxSubSteps = 10,
|
473
474
|
paused: _paused = false
|
474
475
|
}) => {
|
475
476
|
const rapier = useAsset.useAsset(importRapier);
|
477
|
+
const [isPaused, setIsPaused] = React.useState(_paused);
|
478
|
+
React.useEffect(() => {
|
479
|
+
setIsPaused(_paused);
|
480
|
+
}, [_paused]);
|
476
481
|
const worldRef = React.useRef();
|
477
482
|
const getWorldRef = React.useRef(() => {
|
478
483
|
if (!worldRef.current) {
|
@@ -492,7 +497,6 @@ const Physics = ({
|
|
492
497
|
return () => {
|
493
498
|
if (world) {
|
494
499
|
world.free();
|
495
|
-
worldRef.current = undefined;
|
496
500
|
}
|
497
501
|
};
|
498
502
|
}, []); // Update gravity
|
@@ -504,22 +508,46 @@ const Physics = ({
|
|
504
508
|
world.gravity = vectorArrayToVector3(_gravity);
|
505
509
|
}
|
506
510
|
}, [_gravity]);
|
507
|
-
const
|
508
|
-
|
511
|
+
const [steppingState] = React.useState({
|
512
|
+
time: 0,
|
513
|
+
lastTime: 0,
|
514
|
+
accumulator: 0
|
515
|
+
});
|
516
|
+
fiber.useFrame((_, delta) => {
|
509
517
|
const world = worldRef.current;
|
510
|
-
if (!world) return;
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
518
|
+
if (!world) return;
|
519
|
+
world.timestep = _timeStep;
|
520
|
+
/**
|
521
|
+
* Fixed timeStep simulation progression
|
522
|
+
* @see https://gafferongames.com/post/fix_your_timestep/
|
523
|
+
*/
|
524
|
+
|
525
|
+
let previousTranslations = {}; // don't step time forwards if paused
|
526
|
+
|
527
|
+
const nowTime = steppingState.time += _paused ? 0 : delta * 1000;
|
528
|
+
const timeStepMs = _timeStep * 1000;
|
529
|
+
const timeSinceLast = nowTime - steppingState.lastTime;
|
530
|
+
steppingState.lastTime = nowTime;
|
531
|
+
steppingState.accumulator += timeSinceLast;
|
532
|
+
|
533
|
+
if (!_paused) {
|
534
|
+
let subSteps = 0;
|
535
|
+
|
536
|
+
while (steppingState.accumulator >= timeStepMs && subSteps < _maxSubSteps) {
|
537
|
+
// Collect previous state
|
538
|
+
world.bodies.forEach(b => {
|
539
|
+
previousTranslations[b.handle] = {
|
540
|
+
rotation: b.rotation(),
|
541
|
+
translation: b.translation()
|
542
|
+
};
|
543
|
+
});
|
544
|
+
world.step(eventQueue);
|
545
|
+
subSteps++;
|
546
|
+
steppingState.accumulator -= timeStepMs;
|
547
|
+
}
|
520
548
|
}
|
521
549
|
|
522
|
-
|
550
|
+
const interpolationAlpha = steppingState.accumulator % timeStepMs / timeStepMs; // Update meshes
|
523
551
|
|
524
552
|
rigidBodyStates.forEach((state, handle) => {
|
525
553
|
const rigidBody = world.getRigidBody(handle);
|
@@ -545,7 +573,12 @@ const Physics = ({
|
|
545
573
|
return;
|
546
574
|
}
|
547
575
|
|
548
|
-
|
576
|
+
let oldState = previousTranslations[rigidBody.handle];
|
577
|
+
let newTranslation = rapierVector3ToVector3(rigidBody.translation());
|
578
|
+
let newRotation = rapierQuaternionToQuaternion(rigidBody.rotation());
|
579
|
+
let interpolatedTranslation = oldState ? rapierVector3ToVector3(oldState.translation).lerp(newTranslation, interpolationAlpha) : newTranslation;
|
580
|
+
let interpolatedRotation = oldState ? rapierQuaternionToQuaternion(oldState.rotation).slerp(newRotation, interpolationAlpha) : newRotation;
|
581
|
+
state.setMatrix(_matrix4.compose(interpolatedTranslation, interpolatedRotation, state.worldScale).premultiply(state.invertedMatrixWorld));
|
549
582
|
|
550
583
|
if (state.mesh instanceof three.InstancedMesh) {
|
551
584
|
state.mesh.instanceMatrix.needsUpdate = true;
|
@@ -595,7 +628,6 @@ const Physics = ({
|
|
595
628
|
});
|
596
629
|
}
|
597
630
|
});
|
598
|
-
time.current = now;
|
599
631
|
});
|
600
632
|
const api = React.useMemo(() => createWorldApi(getWorldRef), []);
|
601
633
|
const context = React.useMemo(() => ({
|
@@ -607,8 +639,9 @@ const Physics = ({
|
|
607
639
|
},
|
608
640
|
colliderMeshes,
|
609
641
|
rigidBodyStates,
|
610
|
-
rigidBodyEvents
|
611
|
-
|
642
|
+
rigidBodyEvents,
|
643
|
+
isPaused
|
644
|
+
}), [isPaused]);
|
612
645
|
return /*#__PURE__*/React__default["default"].createElement(RapierContext.Provider, {
|
613
646
|
value: context
|
614
647
|
}, children);
|
@@ -469,10 +469,15 @@ const Physics = ({
|
|
469
469
|
colliders: _colliders = "cuboid",
|
470
470
|
gravity: _gravity = [0, -9.81, 0],
|
471
471
|
children,
|
472
|
-
timeStep: _timeStep =
|
472
|
+
timeStep: _timeStep = 1 / 60,
|
473
|
+
maxSubSteps: _maxSubSteps = 10,
|
473
474
|
paused: _paused = false
|
474
475
|
}) => {
|
475
476
|
const rapier = useAsset.useAsset(importRapier);
|
477
|
+
const [isPaused, setIsPaused] = React.useState(_paused);
|
478
|
+
React.useEffect(() => {
|
479
|
+
setIsPaused(_paused);
|
480
|
+
}, [_paused]);
|
476
481
|
const worldRef = React.useRef();
|
477
482
|
const getWorldRef = React.useRef(() => {
|
478
483
|
if (!worldRef.current) {
|
@@ -492,7 +497,6 @@ const Physics = ({
|
|
492
497
|
return () => {
|
493
498
|
if (world) {
|
494
499
|
world.free();
|
495
|
-
worldRef.current = undefined;
|
496
500
|
}
|
497
501
|
};
|
498
502
|
}, []); // Update gravity
|
@@ -504,22 +508,46 @@ const Physics = ({
|
|
504
508
|
world.gravity = vectorArrayToVector3(_gravity);
|
505
509
|
}
|
506
510
|
}, [_gravity]);
|
507
|
-
const
|
508
|
-
|
511
|
+
const [steppingState] = React.useState({
|
512
|
+
time: 0,
|
513
|
+
lastTime: 0,
|
514
|
+
accumulator: 0
|
515
|
+
});
|
516
|
+
fiber.useFrame((_, delta) => {
|
509
517
|
const world = worldRef.current;
|
510
|
-
if (!world) return;
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
518
|
+
if (!world) return;
|
519
|
+
world.timestep = _timeStep;
|
520
|
+
/**
|
521
|
+
* Fixed timeStep simulation progression
|
522
|
+
* @see https://gafferongames.com/post/fix_your_timestep/
|
523
|
+
*/
|
524
|
+
|
525
|
+
let previousTranslations = {}; // don't step time forwards if paused
|
526
|
+
|
527
|
+
const nowTime = steppingState.time += _paused ? 0 : delta * 1000;
|
528
|
+
const timeStepMs = _timeStep * 1000;
|
529
|
+
const timeSinceLast = nowTime - steppingState.lastTime;
|
530
|
+
steppingState.lastTime = nowTime;
|
531
|
+
steppingState.accumulator += timeSinceLast;
|
532
|
+
|
533
|
+
if (!_paused) {
|
534
|
+
let subSteps = 0;
|
535
|
+
|
536
|
+
while (steppingState.accumulator >= timeStepMs && subSteps < _maxSubSteps) {
|
537
|
+
// Collect previous state
|
538
|
+
world.bodies.forEach(b => {
|
539
|
+
previousTranslations[b.handle] = {
|
540
|
+
rotation: b.rotation(),
|
541
|
+
translation: b.translation()
|
542
|
+
};
|
543
|
+
});
|
544
|
+
world.step(eventQueue);
|
545
|
+
subSteps++;
|
546
|
+
steppingState.accumulator -= timeStepMs;
|
547
|
+
}
|
520
548
|
}
|
521
549
|
|
522
|
-
|
550
|
+
const interpolationAlpha = steppingState.accumulator % timeStepMs / timeStepMs; // Update meshes
|
523
551
|
|
524
552
|
rigidBodyStates.forEach((state, handle) => {
|
525
553
|
const rigidBody = world.getRigidBody(handle);
|
@@ -545,7 +573,12 @@ const Physics = ({
|
|
545
573
|
return;
|
546
574
|
}
|
547
575
|
|
548
|
-
|
576
|
+
let oldState = previousTranslations[rigidBody.handle];
|
577
|
+
let newTranslation = rapierVector3ToVector3(rigidBody.translation());
|
578
|
+
let newRotation = rapierQuaternionToQuaternion(rigidBody.rotation());
|
579
|
+
let interpolatedTranslation = oldState ? rapierVector3ToVector3(oldState.translation).lerp(newTranslation, interpolationAlpha) : newTranslation;
|
580
|
+
let interpolatedRotation = oldState ? rapierQuaternionToQuaternion(oldState.rotation).slerp(newRotation, interpolationAlpha) : newRotation;
|
581
|
+
state.setMatrix(_matrix4.compose(interpolatedTranslation, interpolatedRotation, state.worldScale).premultiply(state.invertedMatrixWorld));
|
549
582
|
|
550
583
|
if (state.mesh instanceof three.InstancedMesh) {
|
551
584
|
state.mesh.instanceMatrix.needsUpdate = true;
|
@@ -595,7 +628,6 @@ const Physics = ({
|
|
595
628
|
});
|
596
629
|
}
|
597
630
|
});
|
598
|
-
time.current = now;
|
599
631
|
});
|
600
632
|
const api = React.useMemo(() => createWorldApi(getWorldRef), []);
|
601
633
|
const context = React.useMemo(() => ({
|
@@ -607,8 +639,9 @@ const Physics = ({
|
|
607
639
|
},
|
608
640
|
colliderMeshes,
|
609
641
|
rigidBodyStates,
|
610
|
-
rigidBodyEvents
|
611
|
-
|
642
|
+
rigidBodyEvents,
|
643
|
+
isPaused
|
644
|
+
}), [isPaused]);
|
612
645
|
return /*#__PURE__*/React__default["default"].createElement(RapierContext.Provider, {
|
613
646
|
value: context
|
614
647
|
}, children);
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import { ColliderDesc, ActiveEvents, RigidBodyDesc, CoefficientCombineRule, EventQueue, ShapeType } from '@dimforge/rapier3d-compat';
|
2
2
|
export { CoefficientCombineRule, Collider as RapierCollider, RigidBody as RapierRigidBody } from '@dimforge/rapier3d-compat';
|
3
|
-
import React, {
|
3
|
+
import React, { useState, useEffect, useRef, useMemo, createContext, useContext, forwardRef, useImperativeHandle, memo, useLayoutEffect } from 'react';
|
4
4
|
import { useAsset } from 'use-asset';
|
5
5
|
import { useFrame } from '@react-three/fiber';
|
6
6
|
import { Quaternion, Euler, Vector3, Object3D, Matrix4, InstancedMesh, CylinderBufferGeometry, BufferGeometry, BufferAttribute, SphereBufferGeometry, BoxBufferGeometry, DynamicDrawUsage } from 'three';
|
@@ -444,10 +444,15 @@ const Physics = ({
|
|
444
444
|
colliders: _colliders = "cuboid",
|
445
445
|
gravity: _gravity = [0, -9.81, 0],
|
446
446
|
children,
|
447
|
-
timeStep: _timeStep =
|
447
|
+
timeStep: _timeStep = 1 / 60,
|
448
|
+
maxSubSteps: _maxSubSteps = 10,
|
448
449
|
paused: _paused = false
|
449
450
|
}) => {
|
450
451
|
const rapier = useAsset(importRapier);
|
452
|
+
const [isPaused, setIsPaused] = useState(_paused);
|
453
|
+
useEffect(() => {
|
454
|
+
setIsPaused(_paused);
|
455
|
+
}, [_paused]);
|
451
456
|
const worldRef = useRef();
|
452
457
|
const getWorldRef = useRef(() => {
|
453
458
|
if (!worldRef.current) {
|
@@ -467,7 +472,6 @@ const Physics = ({
|
|
467
472
|
return () => {
|
468
473
|
if (world) {
|
469
474
|
world.free();
|
470
|
-
worldRef.current = undefined;
|
471
475
|
}
|
472
476
|
};
|
473
477
|
}, []); // Update gravity
|
@@ -479,22 +483,46 @@ const Physics = ({
|
|
479
483
|
world.gravity = vectorArrayToVector3(_gravity);
|
480
484
|
}
|
481
485
|
}, [_gravity]);
|
482
|
-
const
|
483
|
-
|
486
|
+
const [steppingState] = useState({
|
487
|
+
time: 0,
|
488
|
+
lastTime: 0,
|
489
|
+
accumulator: 0
|
490
|
+
});
|
491
|
+
useFrame((_, delta) => {
|
484
492
|
const world = worldRef.current;
|
485
|
-
if (!world) return;
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
493
|
+
if (!world) return;
|
494
|
+
world.timestep = _timeStep;
|
495
|
+
/**
|
496
|
+
* Fixed timeStep simulation progression
|
497
|
+
* @see https://gafferongames.com/post/fix_your_timestep/
|
498
|
+
*/
|
499
|
+
|
500
|
+
let previousTranslations = {}; // don't step time forwards if paused
|
501
|
+
|
502
|
+
const nowTime = steppingState.time += _paused ? 0 : delta * 1000;
|
503
|
+
const timeStepMs = _timeStep * 1000;
|
504
|
+
const timeSinceLast = nowTime - steppingState.lastTime;
|
505
|
+
steppingState.lastTime = nowTime;
|
506
|
+
steppingState.accumulator += timeSinceLast;
|
507
|
+
|
508
|
+
if (!_paused) {
|
509
|
+
let subSteps = 0;
|
510
|
+
|
511
|
+
while (steppingState.accumulator >= timeStepMs && subSteps < _maxSubSteps) {
|
512
|
+
// Collect previous state
|
513
|
+
world.bodies.forEach(b => {
|
514
|
+
previousTranslations[b.handle] = {
|
515
|
+
rotation: b.rotation(),
|
516
|
+
translation: b.translation()
|
517
|
+
};
|
518
|
+
});
|
519
|
+
world.step(eventQueue);
|
520
|
+
subSteps++;
|
521
|
+
steppingState.accumulator -= timeStepMs;
|
522
|
+
}
|
495
523
|
}
|
496
524
|
|
497
|
-
|
525
|
+
const interpolationAlpha = steppingState.accumulator % timeStepMs / timeStepMs; // Update meshes
|
498
526
|
|
499
527
|
rigidBodyStates.forEach((state, handle) => {
|
500
528
|
const rigidBody = world.getRigidBody(handle);
|
@@ -520,7 +548,12 @@ const Physics = ({
|
|
520
548
|
return;
|
521
549
|
}
|
522
550
|
|
523
|
-
|
551
|
+
let oldState = previousTranslations[rigidBody.handle];
|
552
|
+
let newTranslation = rapierVector3ToVector3(rigidBody.translation());
|
553
|
+
let newRotation = rapierQuaternionToQuaternion(rigidBody.rotation());
|
554
|
+
let interpolatedTranslation = oldState ? rapierVector3ToVector3(oldState.translation).lerp(newTranslation, interpolationAlpha) : newTranslation;
|
555
|
+
let interpolatedRotation = oldState ? rapierQuaternionToQuaternion(oldState.rotation).slerp(newRotation, interpolationAlpha) : newRotation;
|
556
|
+
state.setMatrix(_matrix4.compose(interpolatedTranslation, interpolatedRotation, state.worldScale).premultiply(state.invertedMatrixWorld));
|
524
557
|
|
525
558
|
if (state.mesh instanceof InstancedMesh) {
|
526
559
|
state.mesh.instanceMatrix.needsUpdate = true;
|
@@ -570,7 +603,6 @@ const Physics = ({
|
|
570
603
|
});
|
571
604
|
}
|
572
605
|
});
|
573
|
-
time.current = now;
|
574
606
|
});
|
575
607
|
const api = useMemo(() => createWorldApi(getWorldRef), []);
|
576
608
|
const context = useMemo(() => ({
|
@@ -582,8 +614,9 @@ const Physics = ({
|
|
582
614
|
},
|
583
615
|
colliderMeshes,
|
584
616
|
rigidBodyStates,
|
585
|
-
rigidBodyEvents
|
586
|
-
|
617
|
+
rigidBodyEvents,
|
618
|
+
isPaused
|
619
|
+
}), [isPaused]);
|
587
620
|
return /*#__PURE__*/React.createElement(RapierContext.Provider, {
|
588
621
|
value: context
|
589
622
|
}, children);
|
package/package.json
CHANGED
package/readme.md
CHANGED
@@ -251,6 +251,7 @@ In order, but also not necessarily:
|
|
251
251
|
- [x] Collision events
|
252
252
|
- [x] Colliders outside RigidBodies
|
253
253
|
- [x] InstancedMesh support
|
254
|
+
- [x] Timestep improvements for determinism
|
254
255
|
- [ ] Normalize and improve collision events (add events to single Colliders, InstancedRigidBodies, etc)
|
255
256
|
- [ ] Docs
|
256
257
|
- [ ] CodeSandbox examples
|