@gg-web-engine/rapier3d 0.0.72 → 0.0.74

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.
@@ -0,0 +1,68 @@
1
+ import { Pnt3 } from '@gg-web-engine/core';
2
+ import { Rapier3dFactory, Rapier3dWorldComponent } from '../../src';
3
+
4
+ describe('Rapier3dRigidBodyComponent.bodyOptions', () => {
5
+ let world: Rapier3dWorldComponent;
6
+ let factory: Rapier3dFactory;
7
+
8
+ beforeEach(async () => {
9
+ if (world) {
10
+ world.dispose();
11
+ }
12
+ world = new Rapier3dWorldComponent();
13
+ factory = new Rapier3dFactory(world);
14
+ await world.init();
15
+ world.gravity = Pnt3.O;
16
+ });
17
+
18
+ afterAll(() => {
19
+ world.dispose();
20
+ });
21
+
22
+ it("reads back a dynamic body's mass/friction/restitution/ccd exactly as created", () => {
23
+ const body = factory.createRigidBody(
24
+ {
25
+ shape: { shape: 'SPHERE', radius: 1 },
26
+ body: { bodyType: 'dynamic', mass: 5, friction: 0.3, restitution: 0.7, ccd: true },
27
+ },
28
+ { position: { x: 0, y: 0, z: 0 } },
29
+ );
30
+ body.addToWorld({ physicsWorld: world } as any);
31
+
32
+ expect(body.bodyOptions).toMatchObject({
33
+ bodyType: 'dynamic',
34
+ mass: 5,
35
+ friction: 0.3,
36
+ restitution: 0.7,
37
+ ccd: true,
38
+ });
39
+ });
40
+
41
+ it.each(['static', 'kinematic_pos', 'kinematic_vel'] as const)(
42
+ 'reports bodyType "%s" correctly, with ccd always false for a non-dynamic body',
43
+ bodyType => {
44
+ const body = factory.createRigidBody(
45
+ { shape: { shape: 'SPHERE', radius: 1 }, body: { bodyType, mass: 1, ccd: true } },
46
+ { position: { x: 0, y: 0, z: 0 } },
47
+ );
48
+ body.addToWorld({ physicsWorld: world } as any);
49
+
50
+ expect(body.bodyOptions.bodyType).toBe(bodyType);
51
+ expect(body.bodyOptions.ccd).toBe(false);
52
+ },
53
+ );
54
+
55
+ it('reflects a mutated ownCollisionGroups/interactWithCollisionGroups live', () => {
56
+ const body = factory.createRigidBody(
57
+ { shape: { shape: 'SPHERE', radius: 1 }, body: { bodyType: 'static', mass: 0 } },
58
+ { position: { x: 0, y: 0, z: 0 } },
59
+ );
60
+ body.addToWorld({ physicsWorld: world } as any);
61
+
62
+ body.ownCollisionGroups = [3];
63
+ body.interactWithCollisionGroups = [5, 6];
64
+
65
+ expect(body.bodyOptions.ownCollisionGroups).toEqual([3]);
66
+ expect(body.bodyOptions.interactWithCollisionGroups).toEqual([5, 6]);
67
+ });
68
+ });
@@ -0,0 +1,85 @@
1
+ import { CharacterController3dOptions, Pnt3 } from '@gg-web-engine/core';
2
+ import { Rapier3dFactory, Rapier3dWorldComponent } from '../../src';
3
+
4
+ // Regression coverage for the gap where `Trigger3dEntity.onEntityEntered`/`onEntityLeft` silently
5
+ // never fired for a `Rapier3dCharacterControllerComponent` (the "Player" built-in / any app-authored
6
+ // character), because `Rapier3dWorldComponent.handleIdEntityMap` never registered a character
7
+ // controller's native body handle - `dispatchCollisionEvents` resolved both sides of every sensor
8
+ // pair through that same map, so a pair involving a character controller always bailed out at
9
+ // `if (!comp1 || !comp2 ...)` before ever reaching `Rapier3dTriggerComponent.notifyOverlap`. See
10
+ // `Rapier3dCharacterControllerComponent`'s own class doc and `Rapier3dWorldComponent.handleIdEntityMap`'s
11
+ // doc for the fix. Vehicles need no equivalent test here - a raycast vehicle's chassis is an ordinary
12
+ // `Rapier3dRigidBodyComponent` (see `packages/core`'s `RaycastVehicle3dEntity`), already covered by
13
+ // `rapier-3d-trigger.component.spec.ts`'s plain rigid-body cases; Rapier3d itself doesn't implement
14
+ // raycast vehicles at all (`Rapier3dFactory.createRaycastVehicle` throws), so Ammo is the only adapter
15
+ // with real vehicle physics to exercise.
16
+ describe(`Rapier3dTriggerComponent x Rapier3dCharacterControllerComponent`, () => {
17
+ let world: Rapier3dWorldComponent;
18
+ let factory: Rapier3dFactory;
19
+
20
+ beforeEach(async () => {
21
+ if (world) {
22
+ world.dispose();
23
+ }
24
+ world = new Rapier3dWorldComponent();
25
+ factory = new Rapier3dFactory(world);
26
+ await world.init();
27
+ world.gravity = Pnt3.O;
28
+ });
29
+
30
+ afterAll(() => {
31
+ world.dispose();
32
+ });
33
+
34
+ const CHAR_OPTIONS: CharacterController3dOptions = { radius: 0.4, centersDistance: 1.0 };
35
+
36
+ // Mirrors `rapier-3d-trigger.component.spec.ts`'s own `advance` helper/doc - many small steps so a
37
+ // step-boundary lag in `EventQueue` drainage never reads as a missed event, and `checkOverlaps()`
38
+ // runs every step (not just once at the end) since `EventQueue` is constructed with `autoDrain: true`.
39
+ const advance = (totalMs: number, onStep: () => void, stepMs = 10) => {
40
+ for (let elapsed = 0; elapsed < totalMs; elapsed += stepMs) {
41
+ world.simulate(stepMs);
42
+ onStep();
43
+ }
44
+ };
45
+
46
+ it('fires onEntityEntered/onEntityLeft for a character controller walking through it', () => {
47
+ const trigger = factory.createTrigger({ shape: 'BOX', dimensions: { x: 10, y: 10, z: 10 } });
48
+ trigger.addToWorld({ physicsWorld: world } as any);
49
+
50
+ const character = factory.createCharacterController(CHAR_OPTIONS, { position: { x: -8, y: 0, z: 0 } });
51
+ character.addToWorld({ physicsWorld: world } as any);
52
+ // register the freshly-created colliders with Rapier's broad-phase before the first move, same
53
+ // as `rapier-3d-character-controller.component.spec.ts`'s own `settleWorld()`.
54
+ world.simulate(0);
55
+
56
+ let entered: unknown;
57
+ let exited: unknown;
58
+ trigger.onEntityEntered.subscribe(obj => (entered = obj));
59
+ trigger.onEntityLeft.subscribe(obj => (exited = obj));
60
+
61
+ // walk from x=-8 to x=12 (1 unit/step over 20 steps), straight through the trigger's -5..5 span.
62
+ for (let i = 0; i < 20; i++) {
63
+ character.move({ x: 1, y: 0, z: 0 });
64
+ advance(10, () => trigger.checkOverlaps());
65
+ }
66
+
67
+ expect(entered).toBe(character);
68
+ expect(exited).toBe(character);
69
+ });
70
+
71
+ it('fires onEntityEntered for a character controller already spawned inside it', () => {
72
+ const trigger = factory.createTrigger({ shape: 'BOX', dimensions: { x: 10, y: 10, z: 10 } });
73
+ trigger.addToWorld({ physicsWorld: world } as any);
74
+
75
+ const character = factory.createCharacterController(CHAR_OPTIONS, { position: { x: 0, y: 0, z: 0 } });
76
+ character.addToWorld({ physicsWorld: world } as any);
77
+
78
+ let entered: unknown;
79
+ trigger.onEntityEntered.subscribe(obj => (entered = obj));
80
+
81
+ advance(500, () => trigger.checkOverlaps());
82
+
83
+ expect(entered).toBe(character);
84
+ });
85
+ });