@forgeax/engine-physics-rapier3d 0.1.20 → 0.1.23
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/index.mjs +126 -57
- package/dist/index.mjs.map +1 -1
- package/dist/rapier-physics-world-3d.d.ts +1 -1
- package/dist/rapier-physics-world-3d.d.ts.map +1 -1
- package/package.json +7 -7
- package/src/__tests__/physics-rapier3d.unit.test.ts +306 -316
- package/src/rapier-physics-world-3d.ts +179 -72
- package/dist/.tsbuildinfo +0 -1
|
@@ -24,7 +24,12 @@ import {
|
|
|
24
24
|
RigidBodyTypeValue,
|
|
25
25
|
registerPhysicsComponents,
|
|
26
26
|
} from '@forgeax/engine-physics';
|
|
27
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
ChildOf,
|
|
29
|
+
GlobalTransform,
|
|
30
|
+
registerPropagateTransforms,
|
|
31
|
+
Transform,
|
|
32
|
+
} from '@forgeax/engine-scene';
|
|
28
33
|
import { describe, expect, it, vi } from 'vitest';
|
|
29
34
|
import { createRapier3DPhysicsWorld, registerPhysicsSystems } from '../rapier-physics-world-3d';
|
|
30
35
|
import { loadRapier3D } from '../wasm-loader';
|
|
@@ -32,10 +37,16 @@ import { loadRapier3D } from '../wasm-loader';
|
|
|
32
37
|
function prepareWorld(): World {
|
|
33
38
|
const world = new World();
|
|
34
39
|
world.components.register(Transform).unwrap();
|
|
40
|
+
world.components.register(GlobalTransform).unwrap();
|
|
35
41
|
registerPhysicsComponents(world);
|
|
36
42
|
return world;
|
|
37
43
|
}
|
|
38
44
|
|
|
45
|
+
// biome-ignore lint/suspicious/noExplicitAny: test fixture accepts the ECS spawn component tuple.
|
|
46
|
+
function spawnPairedEntity(world: World, ...components: any[]) {
|
|
47
|
+
return world.spawn(...components);
|
|
48
|
+
}
|
|
49
|
+
|
|
39
50
|
function runPhysicsTicks(world: World, count = 1): void {
|
|
40
51
|
for (let index = 0; index < count; index += 1) {
|
|
41
52
|
world.update(1 / 60).unwrap();
|
|
@@ -278,28 +289,26 @@ function rapierBodyFor(pw: any, entity: number): any | undefined {
|
|
|
278
289
|
world.insertResource('PhysicsWorld', pw);
|
|
279
290
|
|
|
280
291
|
// Ground: static cuboid, top at y=0.
|
|
281
|
-
const ground =
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
.unwrap();
|
|
292
|
+
const ground = spawnPairedEntity(
|
|
293
|
+
world,
|
|
294
|
+
{ component: Transform as never, data: { pos: [0, -0.5, 0] } },
|
|
295
|
+
{ component: RigidBody as never, data: { type: RigidBodyTypeValue.static } },
|
|
296
|
+
{
|
|
297
|
+
component: Collider as never,
|
|
298
|
+
data: { shape: ColliderShapeValue.cuboid, halfExtents: [10, 0.5, 10] },
|
|
299
|
+
},
|
|
300
|
+
).unwrap();
|
|
291
301
|
|
|
292
302
|
// Target: static cuboid centred at x=5 (near face x=4), well above ground.
|
|
293
|
-
const target =
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
.unwrap();
|
|
303
|
+
const target = spawnPairedEntity(
|
|
304
|
+
world,
|
|
305
|
+
{ component: Transform as never, data: { pos: [5, 1, 0] } },
|
|
306
|
+
{ component: RigidBody as never, data: { type: RigidBodyTypeValue.static } },
|
|
307
|
+
{
|
|
308
|
+
component: Collider as never,
|
|
309
|
+
data: { shape: ColliderShapeValue.cuboid, halfExtents: [1, 1, 1] },
|
|
310
|
+
},
|
|
311
|
+
).unwrap();
|
|
303
312
|
|
|
304
313
|
registerPhysicsSystems(world);
|
|
305
314
|
for (let i = 0; i < 5; i++) {
|
|
@@ -406,19 +415,18 @@ function rapierBodyFor(pw: any, entity: number): any | undefined {
|
|
|
406
415
|
const pw = createRapier3DPhysicsWorld(RAPIER);
|
|
407
416
|
world.insertResource('PhysicsWorld', pw);
|
|
408
417
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
.unwrap();
|
|
418
|
+
spawnPairedEntity(
|
|
419
|
+
world,
|
|
420
|
+
{ component: Transform as never, data: { pos: [0, 1, 0] } },
|
|
421
|
+
{
|
|
422
|
+
component: RigidBody as never,
|
|
423
|
+
data: { type: RigidBodyTypeValue.kinematic, ccdEnabled: true },
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
component: Collider as never,
|
|
427
|
+
data: { shape: ColliderShapeValue.sphere, radius: 0.2 },
|
|
428
|
+
},
|
|
429
|
+
).unwrap();
|
|
422
430
|
|
|
423
431
|
registerPhysicsSystems(world);
|
|
424
432
|
world.update(1 / 60).unwrap();
|
|
@@ -465,44 +473,42 @@ function rapierBodyFor(pw: any, entity: number): any | undefined {
|
|
|
465
473
|
const pw = createRapier3DPhysicsWorld(RAPIER);
|
|
466
474
|
world.insertResource('PhysicsWorld', pw);
|
|
467
475
|
|
|
468
|
-
const dynamicEntity =
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
},
|
|
480
|
-
},
|
|
481
|
-
{
|
|
482
|
-
component: Collider as never,
|
|
483
|
-
data: { shape: 1, radius: 0.5, friction: 0.5, restitution: 0 },
|
|
476
|
+
const dynamicEntity = spawnPairedEntity(
|
|
477
|
+
world,
|
|
478
|
+
{ component: Transform as never, data: { pos: [0, 5, 0] } },
|
|
479
|
+
{
|
|
480
|
+
component: RigidBody as never,
|
|
481
|
+
data: {
|
|
482
|
+
type: RigidBodyTypeValue.dynamic,
|
|
483
|
+
mass: 1,
|
|
484
|
+
linearDamping: 0,
|
|
485
|
+
angularDamping: 0,
|
|
486
|
+
gravityScale: 1,
|
|
484
487
|
},
|
|
485
|
-
|
|
486
|
-
|
|
488
|
+
},
|
|
489
|
+
{
|
|
490
|
+
component: Collider as never,
|
|
491
|
+
data: { shape: 1, radius: 0.5, friction: 0.5, restitution: 0 },
|
|
492
|
+
},
|
|
493
|
+
).unwrap();
|
|
487
494
|
|
|
488
|
-
const staticEntity =
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
},
|
|
495
|
+
const staticEntity = spawnPairedEntity(
|
|
496
|
+
world,
|
|
497
|
+
{ component: Transform as never, data: { pos: [0, 0, 0] } },
|
|
498
|
+
{
|
|
499
|
+
component: RigidBody as never,
|
|
500
|
+
data: { type: RigidBodyTypeValue.static },
|
|
501
|
+
},
|
|
502
|
+
{
|
|
503
|
+
component: Collider as never,
|
|
504
|
+
data: {
|
|
505
|
+
shape: 0,
|
|
506
|
+
halfExtents: [10, 1, 10],
|
|
507
|
+
friction: 0.5,
|
|
508
|
+
restitution: 0,
|
|
503
509
|
},
|
|
504
|
-
|
|
505
|
-
|
|
510
|
+
},
|
|
511
|
+
).unwrap();
|
|
506
512
|
|
|
507
513
|
const initDynamic = world.get(dynamicEntity, Transform as never);
|
|
508
514
|
const initStatic = world.get(staticEntity, Transform as never);
|
|
@@ -558,46 +564,44 @@ function rapierBodyFor(pw: any, entity: number): any | undefined {
|
|
|
558
564
|
world.insertResource('PhysicsWorld', pw);
|
|
559
565
|
|
|
560
566
|
// Ball: dynamic body + sphere collider dropped from y=5.
|
|
561
|
-
const ball =
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
},
|
|
567
|
+
const ball = spawnPairedEntity(
|
|
568
|
+
world,
|
|
569
|
+
{ component: Transform as never, data: { pos: [0, 5, 0] } },
|
|
570
|
+
{
|
|
571
|
+
component: RigidBody as never,
|
|
572
|
+
data: {
|
|
573
|
+
type: RigidBodyTypeValue.dynamic,
|
|
574
|
+
mass: 1,
|
|
575
|
+
linearDamping: 0,
|
|
576
|
+
angularDamping: 0,
|
|
577
|
+
gravityScale: 1,
|
|
573
578
|
},
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
579
|
+
},
|
|
580
|
+
{
|
|
581
|
+
component: Collider as never,
|
|
582
|
+
data: {
|
|
583
|
+
shape: ColliderShapeValue.sphere,
|
|
584
|
+
radius: 0.5,
|
|
585
|
+
friction: 0.5,
|
|
586
|
+
restitution: 0,
|
|
582
587
|
},
|
|
583
|
-
|
|
584
|
-
|
|
588
|
+
},
|
|
589
|
+
).unwrap();
|
|
585
590
|
|
|
586
591
|
// Floor: a BARE Collider — NO RigidBody. Cuboid top at y = 0 + 0.5 = 0.5.
|
|
587
|
-
const floor =
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
},
|
|
592
|
+
const floor = spawnPairedEntity(
|
|
593
|
+
world,
|
|
594
|
+
{ component: Transform as never, data: { pos: [0, 0, 0] } },
|
|
595
|
+
{
|
|
596
|
+
component: Collider as never,
|
|
597
|
+
data: {
|
|
598
|
+
shape: ColliderShapeValue.cuboid,
|
|
599
|
+
halfExtents: [10, 0.5, 10],
|
|
600
|
+
friction: 0.5,
|
|
601
|
+
restitution: 0,
|
|
598
602
|
},
|
|
599
|
-
|
|
600
|
-
|
|
603
|
+
},
|
|
604
|
+
).unwrap();
|
|
601
605
|
|
|
602
606
|
// Sanity: the floor archetype genuinely has no RigidBody column.
|
|
603
607
|
expect(world.get(floor, RigidBody as never).ok).toBe(false);
|
|
@@ -634,22 +638,21 @@ function rapierBodyFor(pw: any, entity: number): any | undefined {
|
|
|
634
638
|
const world = prepareWorld();
|
|
635
639
|
const pw = createRapier3DPhysicsWorld(RAPIER);
|
|
636
640
|
world.insertResource('PhysicsWorld', pw);
|
|
637
|
-
const obstacle =
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
},
|
|
645
|
-
},
|
|
646
|
-
{ component: RigidBody as never, data: { type: RigidBodyTypeValue.static } },
|
|
647
|
-
{
|
|
648
|
-
component: Collider as never,
|
|
649
|
-
data: { shape: ColliderShapeValue.cuboid, halfExtents: [1, 0.25, 0.25] },
|
|
641
|
+
const obstacle = spawnPairedEntity(
|
|
642
|
+
world,
|
|
643
|
+
{
|
|
644
|
+
component: Transform as never,
|
|
645
|
+
data: {
|
|
646
|
+
quat: [0, 0, Math.SQRT1_2, Math.SQRT1_2],
|
|
647
|
+
scale: [2, 1, 1],
|
|
650
648
|
},
|
|
651
|
-
|
|
652
|
-
.
|
|
649
|
+
},
|
|
650
|
+
{ component: RigidBody as never, data: { type: RigidBodyTypeValue.static } },
|
|
651
|
+
{
|
|
652
|
+
component: Collider as never,
|
|
653
|
+
data: { shape: ColliderShapeValue.cuboid, halfExtents: [1, 0.25, 0.25] },
|
|
654
|
+
},
|
|
655
|
+
).unwrap();
|
|
653
656
|
registerPropagateTransforms(world);
|
|
654
657
|
registerPhysicsSystems(world);
|
|
655
658
|
world.update(1 / 60).unwrap();
|
|
@@ -683,16 +686,15 @@ function rapierBodyFor(pw: any, entity: number): any | undefined {
|
|
|
683
686
|
const world = prepareWorld();
|
|
684
687
|
const pw = createRapier3DPhysicsWorld(RAPIER);
|
|
685
688
|
world.insertResource('PhysicsWorld', pw);
|
|
686
|
-
const obstacle =
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
.unwrap();
|
|
689
|
+
const obstacle = spawnPairedEntity(
|
|
690
|
+
world,
|
|
691
|
+
{ component: Transform as never, data: { scale: [1, 1, 1] } },
|
|
692
|
+
{ component: RigidBody as never, data: { type: RigidBodyTypeValue.static } },
|
|
693
|
+
{
|
|
694
|
+
component: Collider as never,
|
|
695
|
+
data: { shape: ColliderShapeValue.cuboid, halfExtents: [1, 0.25, 0.25] },
|
|
696
|
+
},
|
|
697
|
+
).unwrap();
|
|
696
698
|
registerPropagateTransforms(world);
|
|
697
699
|
registerPhysicsSystems(world);
|
|
698
700
|
world.update(1 / 60).unwrap();
|
|
@@ -731,29 +733,27 @@ function rapierBodyFor(pw: any, entity: number): any | undefined {
|
|
|
731
733
|
const world = prepareWorld();
|
|
732
734
|
const pw = createRapier3DPhysicsWorld(RAPIER as never);
|
|
733
735
|
world.insertResource('PhysicsWorld', pw);
|
|
734
|
-
const ball =
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
)
|
|
756
|
-
.unwrap();
|
|
736
|
+
const ball = spawnPairedEntity(
|
|
737
|
+
world,
|
|
738
|
+
{ component: Transform as never, data: { pos: [0, 8, 0] } },
|
|
739
|
+
{
|
|
740
|
+
component: RigidBody as never,
|
|
741
|
+
data: { type: RigidBodyTypeValue.dynamic, mass: 1, gravityScale: 1 },
|
|
742
|
+
},
|
|
743
|
+
{
|
|
744
|
+
component: Collider as never,
|
|
745
|
+
data: { shape: 1, radius: 0.5, friction: 0.5, restitution: 0 },
|
|
746
|
+
},
|
|
747
|
+
).unwrap();
|
|
748
|
+
spawnPairedEntity(
|
|
749
|
+
world,
|
|
750
|
+
{ component: Transform as never, data: { pos: [0, 0, 0] } },
|
|
751
|
+
{ component: RigidBody as never, data: { type: RigidBodyTypeValue.static } },
|
|
752
|
+
{
|
|
753
|
+
component: Collider as never,
|
|
754
|
+
data: { shape: 0, halfExtents, friction: 0.5, restitution: 0 },
|
|
755
|
+
},
|
|
756
|
+
).unwrap();
|
|
757
757
|
registerPhysicsSystems(world);
|
|
758
758
|
for (let i = 0; i < 300; i++) {
|
|
759
759
|
world.update(1 / 60).unwrap();
|
|
@@ -800,57 +800,53 @@ function rapierBodyFor(pw: any, entity: number): any | undefined {
|
|
|
800
800
|
// Parent: a kinematic body (NO CharacterController, so the kinematic
|
|
801
801
|
// mirror -- not moveAndSlide -- drives it) placed far from the origin.
|
|
802
802
|
const PARENT_X = 8;
|
|
803
|
-
const parent =
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
.unwrap();
|
|
803
|
+
const parent = spawnPairedEntity(
|
|
804
|
+
world,
|
|
805
|
+
{ component: Transform as never, data: { pos: [PARENT_X, 0, 0] } },
|
|
806
|
+
{ component: RigidBody as never, data: { type: RigidBodyTypeValue.kinematic } },
|
|
807
|
+
{
|
|
808
|
+
component: Collider as never,
|
|
809
|
+
data: { shape: ColliderShapeValue.sphere, radius: 0.3 },
|
|
810
|
+
},
|
|
811
|
+
).unwrap();
|
|
813
812
|
|
|
814
813
|
// Child sensor: ChildOf the parent with LOCAL pos (0,0,0), so its world
|
|
815
814
|
// pos equals the parent's. This is the shape that regressed.
|
|
816
|
-
const sensor =
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
.unwrap();
|
|
815
|
+
const sensor = spawnPairedEntity(
|
|
816
|
+
world,
|
|
817
|
+
{ component: Transform as never, data: { pos: [0, 0, 0] } },
|
|
818
|
+
{ component: RigidBody as never, data: { type: RigidBodyTypeValue.kinematic } },
|
|
819
|
+
{
|
|
820
|
+
component: Collider as never,
|
|
821
|
+
data: { shape: ColliderShapeValue.sphere, radius: 1, isSensor: true },
|
|
822
|
+
},
|
|
823
|
+
{ component: ChildOf as never, data: { parent } },
|
|
824
|
+
{ component: CollidingEntities as never, data: { entities: [] } },
|
|
825
|
+
).unwrap();
|
|
828
826
|
|
|
829
827
|
// Probe at the ORIGIN: if the sensor collider were (wrongly) pinned at
|
|
830
828
|
// the origin, it would overlap this probe.
|
|
831
|
-
const originProbe =
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
.unwrap();
|
|
829
|
+
const originProbe = spawnPairedEntity(
|
|
830
|
+
world,
|
|
831
|
+
{ component: Transform as never, data: { pos: [0, 0, 0] } },
|
|
832
|
+
{ component: RigidBody as never, data: { type: RigidBodyTypeValue.static } },
|
|
833
|
+
{
|
|
834
|
+
component: Collider as never,
|
|
835
|
+
data: { shape: ColliderShapeValue.sphere, radius: 0.3 },
|
|
836
|
+
},
|
|
837
|
+
).unwrap();
|
|
841
838
|
|
|
842
839
|
// Probe at the PARENT world pos: the sensor must overlap THIS one once it
|
|
843
840
|
// correctly follows the parent.
|
|
844
|
-
const farProbe =
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
.unwrap();
|
|
841
|
+
const farProbe = spawnPairedEntity(
|
|
842
|
+
world,
|
|
843
|
+
{ component: Transform as never, data: { pos: [PARENT_X, 0, 0] } },
|
|
844
|
+
{ component: RigidBody as never, data: { type: RigidBodyTypeValue.static } },
|
|
845
|
+
{
|
|
846
|
+
component: Collider as never,
|
|
847
|
+
data: { shape: ColliderShapeValue.sphere, radius: 0.3 },
|
|
848
|
+
},
|
|
849
|
+
).unwrap();
|
|
854
850
|
|
|
855
851
|
registerPropagateTransforms(world);
|
|
856
852
|
registerPhysicsSystems(world);
|
|
@@ -974,8 +970,9 @@ describe('incremental physics ECS reconciliation', () => {
|
|
|
974
970
|
};
|
|
975
971
|
const result =
|
|
976
972
|
options.bodyType === 'implicit'
|
|
977
|
-
? world
|
|
978
|
-
:
|
|
973
|
+
? spawnPairedEntity(world, transform, collider)
|
|
974
|
+
: spawnPairedEntity(
|
|
975
|
+
world,
|
|
979
976
|
transform,
|
|
980
977
|
{
|
|
981
978
|
component: RigidBody as never,
|
|
@@ -1104,20 +1101,20 @@ describe('incremental physics ECS reconciliation', () => {
|
|
|
1104
1101
|
const world = prepareWorld();
|
|
1105
1102
|
const pw = createRapier3DPhysicsWorld(RAPIER);
|
|
1106
1103
|
world.insertResource('PhysicsWorld', pw);
|
|
1107
|
-
const parent = world
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
},
|
|
1119
|
-
|
|
1120
|
-
|
|
1104
|
+
const parent = spawnPairedEntity(world, {
|
|
1105
|
+
component: Transform as never,
|
|
1106
|
+
data: { pos: [1, 0, 0] },
|
|
1107
|
+
}).unwrap();
|
|
1108
|
+
const child = spawnPairedEntity(
|
|
1109
|
+
world,
|
|
1110
|
+
{ component: Transform as never, data: { pos: [2, 0, 0] } },
|
|
1111
|
+
{ component: ChildOf as never, data: { parent } },
|
|
1112
|
+
{ component: RigidBody as never, data: { type: RigidBodyTypeValue.static } },
|
|
1113
|
+
{
|
|
1114
|
+
component: Collider as never,
|
|
1115
|
+
data: { shape: ColliderShapeValue.cuboid, halfExtents: [1, 1, 1] },
|
|
1116
|
+
},
|
|
1117
|
+
).unwrap();
|
|
1121
1118
|
const unrelated = spawnBox(world, { pos: [20, 0, 0] });
|
|
1122
1119
|
registerPropagateTransforms(world);
|
|
1123
1120
|
registerPhysicsSystems(world);
|
|
@@ -1148,26 +1145,27 @@ describe('incremental physics ECS reconciliation', () => {
|
|
|
1148
1145
|
const pw = createRapier3DPhysicsWorld(RAPIER);
|
|
1149
1146
|
world.insertResource('PhysicsWorld', pw);
|
|
1150
1147
|
const root = spawnBox(world, { bodyType });
|
|
1151
|
-
const parent = world
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
},
|
|
1163
|
-
|
|
1164
|
-
|
|
1148
|
+
const parent = spawnPairedEntity(world, {
|
|
1149
|
+
component: Transform as never,
|
|
1150
|
+
data: { pos: [1, 0, 0] },
|
|
1151
|
+
}).unwrap();
|
|
1152
|
+
const child = spawnPairedEntity(
|
|
1153
|
+
world,
|
|
1154
|
+
{ component: Transform as never, data: { pos: [-1, 0, 0] } },
|
|
1155
|
+
{ component: ChildOf as never, data: { parent } },
|
|
1156
|
+
{ component: RigidBody as never, data: { type: bodyType } },
|
|
1157
|
+
{
|
|
1158
|
+
component: Collider as never,
|
|
1159
|
+
data: { shape: ColliderShapeValue.cuboid, halfExtents: [1, 1, 1] },
|
|
1160
|
+
},
|
|
1161
|
+
).unwrap();
|
|
1165
1162
|
registerPropagateTransforms(world);
|
|
1166
1163
|
registerPhysicsSystems(world);
|
|
1167
1164
|
runPhysicsTicks(world);
|
|
1168
1165
|
|
|
1169
|
-
const rootWorld = world.get(root as never,
|
|
1170
|
-
|
|
1166
|
+
const rootWorld = world.get(root as never, GlobalTransform as never).unwrap()
|
|
1167
|
+
.world as Float32Array;
|
|
1168
|
+
const childWorld = world.get(child, GlobalTransform as never).unwrap().world as Float32Array;
|
|
1171
1169
|
expect(Array.from(rootWorld)).toEqual([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
|
|
1172
1170
|
expect(Array.from(childWorld)).toEqual([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
|
|
1173
1171
|
expect(rapierBodyFor(pw, root)?.translation().x).toBeCloseTo(0, 5);
|
|
@@ -1418,7 +1416,7 @@ describe('incremental physics ECS reconciliation', () => {
|
|
|
1418
1416
|
expect(rapierBodyFor(pw, replacement)?.translation().x).toBeCloseTo(11, 5);
|
|
1419
1417
|
});
|
|
1420
1418
|
|
|
1421
|
-
it('
|
|
1419
|
+
it('coalesces more than 65k writes into one final component-version sync', {
|
|
1422
1420
|
timeout: 20_000,
|
|
1423
1421
|
}, async () => {
|
|
1424
1422
|
const RAPIER = await loadOrSkip();
|
|
@@ -1441,9 +1439,9 @@ describe('incremental physics ECS reconciliation', () => {
|
|
|
1441
1439
|
}
|
|
1442
1440
|
runPhysicsTicks(world);
|
|
1443
1441
|
|
|
1444
|
-
expect(removeSpy).
|
|
1445
|
-
expect(ensureSpy).
|
|
1446
|
-
expect(syncSpy).
|
|
1442
|
+
expect(removeSpy).not.toHaveBeenCalled();
|
|
1443
|
+
expect(ensureSpy).not.toHaveBeenCalled();
|
|
1444
|
+
expect(syncSpy).toHaveBeenCalledTimes(1);
|
|
1447
1445
|
expect(pw.getBodyCount()).toBe(1);
|
|
1448
1446
|
expect(rapierBodyFor(pw, entity)?.translation().x).toBeCloseTo(finalX, 5);
|
|
1449
1447
|
});
|
|
@@ -1508,17 +1506,16 @@ describe('incremental physics ECS reconciliation', () => {
|
|
|
1508
1506
|
cc?: Record<string, number>,
|
|
1509
1507
|
bodyType: number = RigidBodyTypeValue.kinematic,
|
|
1510
1508
|
): number {
|
|
1511
|
-
const entity =
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
.unwrap();
|
|
1509
|
+
const entity = spawnPairedEntity(
|
|
1510
|
+
world,
|
|
1511
|
+
{ component: Transform as never, data: { pos: [pos[0], pos[1], pos[2]] } },
|
|
1512
|
+
{ component: RigidBody as never, data: { type: bodyType } },
|
|
1513
|
+
{
|
|
1514
|
+
component: Collider as never,
|
|
1515
|
+
data: { shape: 2, radius: 0.3, halfHeight: 0.5, friction: 0.5, restitution: 0 },
|
|
1516
|
+
},
|
|
1517
|
+
{ component: CharacterController as never, data: cc ?? {} },
|
|
1518
|
+
).unwrap();
|
|
1522
1519
|
return entity as unknown as number;
|
|
1523
1520
|
}
|
|
1524
1521
|
|
|
@@ -1529,16 +1526,15 @@ describe('incremental physics ECS reconciliation', () => {
|
|
|
1529
1526
|
friction: 0.5,
|
|
1530
1527
|
restitution: 0,
|
|
1531
1528
|
};
|
|
1532
|
-
const entity =
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
.unwrap();
|
|
1529
|
+
const entity = spawnPairedEntity(
|
|
1530
|
+
world,
|
|
1531
|
+
{
|
|
1532
|
+
component: Transform as never,
|
|
1533
|
+
data: { pos: [box.pos[0], box.pos[1], box.pos[2]] },
|
|
1534
|
+
},
|
|
1535
|
+
{ component: RigidBody as never, data: { type: RigidBodyTypeValue.static } },
|
|
1536
|
+
{ component: Collider as never, data },
|
|
1537
|
+
).unwrap();
|
|
1542
1538
|
return entity as unknown as number;
|
|
1543
1539
|
}
|
|
1544
1540
|
|
|
@@ -1679,16 +1675,15 @@ describe('incremental physics ECS reconciliation', () => {
|
|
|
1679
1675
|
// collectathon guardian attack-sensor whose physics body stayed at world
|
|
1680
1676
|
// origin). A sensor reports overlaps but must NEVER act as a solid wall
|
|
1681
1677
|
// for the KCC -- before the fix this froze the character in place.
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
.unwrap();
|
|
1678
|
+
spawnPairedEntity(
|
|
1679
|
+
world,
|
|
1680
|
+
{ component: Transform as never, data: { pos: [0, 0, 0] } },
|
|
1681
|
+
{ component: RigidBody as never, data: { type: RigidBodyTypeValue.kinematic } },
|
|
1682
|
+
{
|
|
1683
|
+
component: Collider as never,
|
|
1684
|
+
data: { shape: ColliderShapeValue.sphere, radius: 1.5, isSensor: 1 },
|
|
1685
|
+
},
|
|
1686
|
+
).unwrap();
|
|
1692
1687
|
|
|
1693
1688
|
world.update(1 / 60).unwrap();
|
|
1694
1689
|
world.update(1 / 60).unwrap();
|
|
@@ -2081,7 +2076,7 @@ describe('incremental physics ECS reconciliation', () => {
|
|
|
2081
2076
|
const world = prepareWorld();
|
|
2082
2077
|
const pw = createRapier3DPhysicsWorld(RAPIER);
|
|
2083
2078
|
world.insertResource('PhysicsWorld', pw);
|
|
2084
|
-
// The kinematic mirror drives the collider from
|
|
2079
|
+
// The kinematic mirror drives the collider from GlobalTransform.world (so a
|
|
2085
2080
|
// ChildOf collider follows its parent), which propagateTransforms
|
|
2086
2081
|
// populates; register it (createApp always does, and physicsSyncBackend
|
|
2087
2082
|
// declares `after: propagateTransforms`). For this root platform
|
|
@@ -2090,16 +2085,15 @@ describe('incremental physics ECS reconciliation', () => {
|
|
|
2090
2085
|
registerPhysicsSystems(world);
|
|
2091
2086
|
|
|
2092
2087
|
// Platform: kinematic body + collider, NO CharacterController.
|
|
2093
|
-
const platform =
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
.unwrap() as unknown as number;
|
|
2088
|
+
const platform = spawnPairedEntity(
|
|
2089
|
+
world,
|
|
2090
|
+
{ component: Transform as never, data: { pos: [0, 0, 0] } },
|
|
2091
|
+
{ component: RigidBody as never, data: { type: RigidBodyTypeValue.kinematic } },
|
|
2092
|
+
{
|
|
2093
|
+
component: Collider as never,
|
|
2094
|
+
data: { shape: 0, halfExtents: [1, 0.5, 1] },
|
|
2095
|
+
},
|
|
2096
|
+
).unwrap() as unknown as number;
|
|
2103
2097
|
|
|
2104
2098
|
// Move the platform via Transform; syncBackend should mirror it.
|
|
2105
2099
|
world.set(platform as never, Transform as never, { pos: [5, 2, 0] });
|
|
@@ -2277,30 +2271,28 @@ describe('incremental physics ECS reconciliation', () => {
|
|
|
2277
2271
|
registerPhysicsSystems(world);
|
|
2278
2272
|
|
|
2279
2273
|
// A "player" kinematic body + CollidingEntities, sitting at the origin.
|
|
2280
|
-
const player =
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
.unwrap();
|
|
2274
|
+
const player = spawnPairedEntity(
|
|
2275
|
+
world,
|
|
2276
|
+
{ component: Transform as never, data: { pos: [0, 0, 0] } },
|
|
2277
|
+
{ component: RigidBody as never, data: { type: RigidBodyTypeValue.kinematic } },
|
|
2278
|
+
{
|
|
2279
|
+
component: Collider as never,
|
|
2280
|
+
data: { shape: ColliderShapeValue.capsule, radius: 0.3, halfHeight: 0.5 },
|
|
2281
|
+
},
|
|
2282
|
+
{ component: CollidingEntities as never, data: { entities: [] } },
|
|
2283
|
+
).unwrap();
|
|
2291
2284
|
|
|
2292
2285
|
// A "Core" kinematic SENSOR overlapping the player.
|
|
2293
|
-
const core =
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
.unwrap();
|
|
2286
|
+
const core = spawnPairedEntity(
|
|
2287
|
+
world,
|
|
2288
|
+
{ component: Transform as never, data: { pos: [0, 0, 0] } },
|
|
2289
|
+
{ component: RigidBody as never, data: { type: RigidBodyTypeValue.kinematic } },
|
|
2290
|
+
{
|
|
2291
|
+
component: Collider as never,
|
|
2292
|
+
data: { shape: ColliderShapeValue.sphere, radius: 0.35, isSensor: true },
|
|
2293
|
+
},
|
|
2294
|
+
{ component: CollidingEntities as never, data: { entities: [] } },
|
|
2295
|
+
).unwrap();
|
|
2304
2296
|
|
|
2305
2297
|
world.update(1 / 60).unwrap();
|
|
2306
2298
|
// A few ticks: tick 1 builds bodies (ensureBody), the next steps + drains.
|
|
@@ -2323,28 +2315,26 @@ describe('incremental physics ECS reconciliation', () => {
|
|
|
2323
2315
|
world.insertResource('PhysicsWorld', pw);
|
|
2324
2316
|
registerPhysicsSystems(world);
|
|
2325
2317
|
|
|
2326
|
-
const player =
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
)
|
|
2347
|
-
.unwrap();
|
|
2318
|
+
const player = spawnPairedEntity(
|
|
2319
|
+
world,
|
|
2320
|
+
{ component: Transform as never, data: { pos: [0, 0, 0] } },
|
|
2321
|
+
{ component: RigidBody as never, data: { type: RigidBodyTypeValue.kinematic } },
|
|
2322
|
+
{
|
|
2323
|
+
component: Collider as never,
|
|
2324
|
+
data: { shape: ColliderShapeValue.capsule, radius: 0.3, halfHeight: 0.5 },
|
|
2325
|
+
},
|
|
2326
|
+
{ component: CollidingEntities as never, data: { entities: [] } },
|
|
2327
|
+
).unwrap();
|
|
2328
|
+
const core = spawnPairedEntity(
|
|
2329
|
+
world,
|
|
2330
|
+
{ component: Transform as never, data: { pos: [0, 0, 0] } },
|
|
2331
|
+
{ component: RigidBody as never, data: { type: RigidBodyTypeValue.kinematic } },
|
|
2332
|
+
{
|
|
2333
|
+
component: Collider as never,
|
|
2334
|
+
data: { shape: ColliderShapeValue.sphere, radius: 0.35, isSensor: true },
|
|
2335
|
+
},
|
|
2336
|
+
{ component: CollidingEntities as never, data: { entities: [] } },
|
|
2337
|
+
).unwrap();
|
|
2348
2338
|
|
|
2349
2339
|
world.update(1 / 60).unwrap();
|
|
2350
2340
|
for (let i = 0; i < 4; i++) world.update(1 / 60).unwrap();
|