@gg-web-engine/rapier3d 0.0.56 → 0.0.58
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/components/rapier-3d-raycast-vehicle.component.d.ts +4 -4
- package/dist/components/rapier-3d-rigid-body.component.d.ts +9 -9
- package/dist/components/rapier-3d-rigid-body.component.js +6 -12
- package/dist/components/rapier-3d-trigger.component.d.ts +7 -4
- package/dist/components/rapier-3d-trigger.component.js +33 -5
- package/dist/components/rapier-3d-world.component.d.ts +3 -1
- package/dist/components/rapier-3d-world.component.js +45 -1
- package/dist/rapier-3d-factory.js +1 -1
- package/dist/types.d.ts +3 -0
- package/package.json +3 -3
- package/test/components/rapier-3d-trigger.component.spec.ts +38 -37
- package/test/components/rapier-3d-world.component.spec.ts +296 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { IRaycastVehicleComponent, Point3, Point4, SuspensionOptions, WheelOptions } from '@gg-web-engine/core';
|
|
2
2
|
import { DynamicRayCastVehicleController } from '@dimforge/rapier3d-compat';
|
|
3
3
|
import { Rapier3dRigidBodyComponent } from './rapier-3d-rigid-body.component';
|
|
4
4
|
import { Rapier3dWorldComponent } from './rapier-3d-world.component';
|
|
5
|
-
import { Rapier3dPhysicsTypeDocRepo } from '../types';
|
|
5
|
+
import { Rapier3dGgWorld, Rapier3dPhysicsTypeDocRepo } from '../types';
|
|
6
6
|
export declare class Rapier3dRaycastVehicleComponent extends Rapier3dRigidBodyComponent implements IRaycastVehicleComponent<Rapier3dPhysicsTypeDocRepo> {
|
|
7
7
|
protected readonly world: Rapier3dWorldComponent;
|
|
8
8
|
private chassisBody;
|
|
@@ -11,8 +11,8 @@ export declare class Rapier3dRaycastVehicleComponent extends Rapier3dRigidBodyCo
|
|
|
11
11
|
private wheelDescr;
|
|
12
12
|
constructor(world: Rapier3dWorldComponent, chassisBody: Rapier3dRigidBodyComponent);
|
|
13
13
|
get wheelSpeed(): number;
|
|
14
|
-
addToWorld(world:
|
|
15
|
-
removeFromWorld(world:
|
|
14
|
+
addToWorld(world: Rapier3dGgWorld): void;
|
|
15
|
+
removeFromWorld(world: Rapier3dGgWorld): void;
|
|
16
16
|
addWheel(options: WheelOptions, suspensionOptions: SuspensionOptions): void;
|
|
17
17
|
setSteering(wheelIndex: number, steering: number): void;
|
|
18
18
|
applyEngineForce(wheelIndex: number, force: number): void;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Body3DOptions, CollisionGroup, DebugBody3DSettings, Entity3d,
|
|
1
|
+
import { Body3DOptions, CollisionGroup, DebugBody3DSettings, Entity3d, IRigidBody3dComponent, Point3, Point4, Shape3DDescriptor } from '@gg-web-engine/core';
|
|
2
2
|
import { Collider, ColliderDesc, RigidBody, RigidBodyDesc } from '@dimforge/rapier3d-compat';
|
|
3
3
|
import { Rapier3dWorldComponent } from './rapier-3d-world.component';
|
|
4
|
-
import { Rapier3dPhysicsTypeDocRepo } from '../types';
|
|
4
|
+
import { Rapier3dGgWorld, Rapier3dPhysicsTypeDocRepo } from '../types';
|
|
5
5
|
import { InteractionGroups } from '@dimforge/rapier3d-compat/geometry/interaction_groups';
|
|
6
6
|
export declare class Rapier3dRigidBodyComponent implements IRigidBody3dComponent<Rapier3dPhysicsTypeDocRepo> {
|
|
7
7
|
protected readonly world: Rapier3dWorldComponent;
|
|
@@ -18,7 +18,7 @@ export declare class Rapier3dRigidBodyComponent implements IRigidBody3dComponent
|
|
|
18
18
|
set linearVelocity(value: Point3);
|
|
19
19
|
get angularVelocity(): Point3;
|
|
20
20
|
set angularVelocity(value: Point3);
|
|
21
|
-
|
|
21
|
+
readonly debugBodySettings: DebugBody3DSettings;
|
|
22
22
|
protected _nativeBody: RigidBody | null;
|
|
23
23
|
protected _nativeBodyColliders: Collider[] | null;
|
|
24
24
|
get nativeBody(): RigidBody | null;
|
|
@@ -32,13 +32,13 @@ export declare class Rapier3dRigidBodyComponent implements IRigidBody3dComponent
|
|
|
32
32
|
];
|
|
33
33
|
constructor(world: Rapier3dWorldComponent, _colliderDescr: ColliderDesc[], shape: Shape3DDescriptor, _bodyDescr: RigidBodyDesc, _colliderOptions: Omit<Omit<Body3DOptions, 'dynamic'>, 'mass'>);
|
|
34
34
|
protected collisionGroups: InteractionGroups;
|
|
35
|
-
get interactWithCollisionGroups(): CollisionGroup
|
|
36
|
-
set interactWithCollisionGroups(value: CollisionGroup
|
|
37
|
-
get ownCollisionGroups(): CollisionGroup
|
|
38
|
-
set ownCollisionGroups(value: CollisionGroup
|
|
35
|
+
get interactWithCollisionGroups(): ReadonlyArray<CollisionGroup>;
|
|
36
|
+
set interactWithCollisionGroups(value: ReadonlyArray<CollisionGroup> | 'all');
|
|
37
|
+
get ownCollisionGroups(): ReadonlyArray<CollisionGroup>;
|
|
38
|
+
set ownCollisionGroups(value: ReadonlyArray<CollisionGroup> | 'all');
|
|
39
39
|
clone(): Rapier3dRigidBodyComponent;
|
|
40
|
-
addToWorld(world:
|
|
41
|
-
removeFromWorld(world:
|
|
40
|
+
addToWorld(world: Rapier3dGgWorld): void;
|
|
41
|
+
removeFromWorld(world: Rapier3dGgWorld): void;
|
|
42
42
|
resetMotion(): void;
|
|
43
43
|
dispose(): void;
|
|
44
44
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BitMask, Pnt3, Qtrn, } from '@gg-web-engine/core';
|
|
1
|
+
import { BitMask, DebugBody3DSettings, Pnt3, Qtrn, } from '@gg-web-engine/core';
|
|
2
2
|
import { ColliderDesc, Quaternion, RigidBodyDesc, Vector3 } from '@dimforge/rapier3d-compat';
|
|
3
3
|
export class Rapier3dRigidBodyComponent {
|
|
4
4
|
get position() {
|
|
@@ -41,15 +41,6 @@ export class Rapier3dRigidBodyComponent {
|
|
|
41
41
|
this.nativeBody.setAngvel(new Vector3(value.x, value.y, value.z), false);
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
|
-
get debugBodySettings() {
|
|
45
|
-
var _a;
|
|
46
|
-
if (this._bodyDescr.mass > 0) {
|
|
47
|
-
return { shape: this.shape, type: 'RIGID_DYNAMIC', sleeping: !!((_a = this._nativeBody) === null || _a === void 0 ? void 0 : _a.isSleeping()) };
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
return { shape: this.shape, type: 'RIGID_STATIC' };
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
44
|
get nativeBody() {
|
|
54
45
|
return this._nativeBody;
|
|
55
46
|
}
|
|
@@ -85,12 +76,15 @@ export class Rapier3dRigidBodyComponent {
|
|
|
85
76
|
this._bodyDescr = _bodyDescr;
|
|
86
77
|
this._colliderOptions = _colliderOptions;
|
|
87
78
|
this.entity = null;
|
|
79
|
+
this.debugBodySettings = new DebugBody3DSettings(this._bodyDescr.mass > 0
|
|
80
|
+
? { type: 'RIGID_DYNAMIC', sleeping: () => { var _a; return !!((_a = this._nativeBody) === null || _a === void 0 ? void 0 : _a.isSleeping()); } }
|
|
81
|
+
: { type: 'RIGID_STATIC' }, this.shape);
|
|
88
82
|
this._nativeBody = null;
|
|
89
83
|
this._nativeBodyColliders = null;
|
|
90
84
|
this.name = '';
|
|
91
85
|
this.collisionGroups = BitMask.full(32);
|
|
92
|
-
this.ownCollisionGroups = (_colliderOptions === null || _colliderOptions === void 0 ? void 0 : _colliderOptions.ownCollisionGroups) ||
|
|
93
|
-
this.interactWithCollisionGroups = (_colliderOptions === null || _colliderOptions === void 0 ? void 0 : _colliderOptions.interactWithCollisionGroups) ||
|
|
86
|
+
this.ownCollisionGroups = (_colliderOptions === null || _colliderOptions === void 0 ? void 0 : _colliderOptions.ownCollisionGroups) || [world.mainCollisionGroup];
|
|
87
|
+
this.interactWithCollisionGroups = (_colliderOptions === null || _colliderOptions === void 0 ? void 0 : _colliderOptions.interactWithCollisionGroups) || [world.mainCollisionGroup];
|
|
94
88
|
}
|
|
95
89
|
get interactWithCollisionGroups() {
|
|
96
90
|
return BitMask.unpack(this.collisionGroups, 16);
|
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
import { Observable, Subject } from 'rxjs';
|
|
2
2
|
import { ColliderDesc, RigidBodyDesc } from '@dimforge/rapier3d-compat';
|
|
3
3
|
import { Rapier3dRigidBodyComponent } from './rapier-3d-rigid-body.component';
|
|
4
|
-
import { DebugBody3DSettings,
|
|
4
|
+
import { DebugBody3DSettings, ITrigger3dComponent, Shape3DDescriptor } from '@gg-web-engine/core';
|
|
5
5
|
import { Rapier3dWorldComponent } from './rapier-3d-world.component';
|
|
6
|
-
import { Rapier3dPhysicsTypeDocRepo } from '../types';
|
|
6
|
+
import { Rapier3dGgWorld, Rapier3dPhysicsTypeDocRepo } from '../types';
|
|
7
7
|
export declare class Rapier3dTriggerComponent extends Rapier3dRigidBodyComponent implements ITrigger3dComponent<Rapier3dPhysicsTypeDocRepo> {
|
|
8
8
|
protected readonly world: Rapier3dWorldComponent;
|
|
9
9
|
protected _colliderDescr: ColliderDesc[];
|
|
10
10
|
readonly shape: Shape3DDescriptor;
|
|
11
11
|
protected _bodyDescr: RigidBodyDesc;
|
|
12
|
+
readonly debugBodySettings: DebugBody3DSettings;
|
|
12
13
|
get onEntityEntered(): Observable<Rapier3dRigidBodyComponent>;
|
|
13
14
|
get onEntityLeft(): Observable<Rapier3dRigidBodyComponent>;
|
|
15
|
+
protected readonly overlaps: Set<Rapier3dRigidBodyComponent>;
|
|
14
16
|
protected readonly onEnter$: Subject<Rapier3dRigidBodyComponent>;
|
|
15
17
|
protected readonly onLeft$: Subject<Rapier3dRigidBodyComponent>;
|
|
16
|
-
get debugBodySettings(): DebugBody3DSettings;
|
|
17
18
|
constructor(world: Rapier3dWorldComponent, _colliderDescr: ColliderDesc[], shape: Shape3DDescriptor, _bodyDescr: RigidBodyDesc);
|
|
18
|
-
addToWorld(world:
|
|
19
|
+
addToWorld(world: Rapier3dGgWorld): void;
|
|
20
|
+
removeFromWorld(world: Rapier3dGgWorld): void;
|
|
19
21
|
checkOverlaps(): void;
|
|
20
22
|
clone(): Rapier3dTriggerComponent;
|
|
23
|
+
dispose(): void;
|
|
21
24
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Subject } from 'rxjs';
|
|
2
2
|
import { Rapier3dRigidBodyComponent } from './rapier-3d-rigid-body.component';
|
|
3
|
+
import { DebugBody3DSettings } from '@gg-web-engine/core';
|
|
3
4
|
export class Rapier3dTriggerComponent extends Rapier3dRigidBodyComponent {
|
|
4
5
|
get onEntityEntered() {
|
|
5
6
|
return this.onEnter$.asObservable();
|
|
@@ -7,15 +8,14 @@ export class Rapier3dTriggerComponent extends Rapier3dRigidBodyComponent {
|
|
|
7
8
|
get onEntityLeft() {
|
|
8
9
|
return this.onLeft$.asObservable();
|
|
9
10
|
}
|
|
10
|
-
get debugBodySettings() {
|
|
11
|
-
return { shape: this.shape, type: 'TRIGGER' };
|
|
12
|
-
}
|
|
13
11
|
constructor(world, _colliderDescr, shape, _bodyDescr) {
|
|
14
12
|
super(world, _colliderDescr, shape, _bodyDescr, null);
|
|
15
13
|
this.world = world;
|
|
16
14
|
this._colliderDescr = _colliderDescr;
|
|
17
15
|
this.shape = shape;
|
|
18
16
|
this._bodyDescr = _bodyDescr;
|
|
17
|
+
this.debugBodySettings = new DebugBody3DSettings({ type: 'TRIGGER', activated: () => this.overlaps.size > 0 }, this.shape);
|
|
18
|
+
this.overlaps = new Set();
|
|
19
19
|
this.onEnter$ = new Subject();
|
|
20
20
|
this.onLeft$ = new Subject();
|
|
21
21
|
}
|
|
@@ -23,11 +23,19 @@ export class Rapier3dTriggerComponent extends Rapier3dRigidBodyComponent {
|
|
|
23
23
|
if (world.physicsWorld != this.world) {
|
|
24
24
|
throw new Error('Rapier3D bodies cannot be shared between different worlds');
|
|
25
25
|
}
|
|
26
|
+
this.overlaps.clear();
|
|
26
27
|
this._nativeBody = this.world.nativeWorld.createRigidBody(this._bodyDescr);
|
|
27
28
|
this._nativeBodyColliders = this._colliderDescr.map(c => this.world.nativeWorld.createCollider(c, this._nativeBody));
|
|
28
29
|
this.world.handleIdEntityMap.set(this._nativeBody.handle, this);
|
|
29
30
|
this.world.added$.next(this);
|
|
30
31
|
}
|
|
32
|
+
removeFromWorld(world) {
|
|
33
|
+
for (const body of this.overlaps) {
|
|
34
|
+
this.onLeft$.next(body);
|
|
35
|
+
}
|
|
36
|
+
this.overlaps.clear();
|
|
37
|
+
super.removeFromWorld(world);
|
|
38
|
+
}
|
|
31
39
|
checkOverlaps() {
|
|
32
40
|
this.world.eventQueue.drainCollisionEvents((h1, h2, started) => {
|
|
33
41
|
var _a, _b;
|
|
@@ -40,11 +48,31 @@ export class Rapier3dTriggerComponent extends Rapier3dRigidBodyComponent {
|
|
|
40
48
|
}
|
|
41
49
|
if (!otherBody)
|
|
42
50
|
return;
|
|
43
|
-
(started
|
|
51
|
+
if (started) {
|
|
52
|
+
this.overlaps.add(otherBody);
|
|
53
|
+
this.onEnter$.next(otherBody);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
this.overlaps.delete(otherBody);
|
|
57
|
+
this.onLeft$.next(otherBody);
|
|
58
|
+
}
|
|
44
59
|
});
|
|
60
|
+
for (const body of this.overlaps.keys()) {
|
|
61
|
+
if (!body.nativeBody) {
|
|
62
|
+
this.overlaps.delete(body);
|
|
63
|
+
this.onLeft$.next(body);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
45
66
|
}
|
|
46
67
|
clone() {
|
|
47
68
|
const [colliderDescr, sd, bd] = super.factoryProps;
|
|
48
|
-
|
|
69
|
+
let component = new Rapier3dTriggerComponent(this.world, colliderDescr, sd, bd);
|
|
70
|
+
component.ownCollisionGroups = this.ownCollisionGroups;
|
|
71
|
+
component.interactWithCollisionGroups = this.interactWithCollisionGroups;
|
|
72
|
+
return component;
|
|
73
|
+
}
|
|
74
|
+
dispose() {
|
|
75
|
+
this.overlaps.clear();
|
|
76
|
+
super.dispose();
|
|
49
77
|
}
|
|
50
78
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CollisionGroup, IPhysicsWorld3dComponent, Point3 } from '@gg-web-engine/core';
|
|
1
|
+
import { CollisionGroup, IPhysicsWorld3dComponent, Point3, RaycastOptions, RaycastResult } from '@gg-web-engine/core';
|
|
2
2
|
import { EventQueue, World } from '@dimforge/rapier3d-compat';
|
|
3
3
|
import { Rapier3dRigidBodyComponent } from './rapier-3d-rigid-body.component';
|
|
4
4
|
import { Rapier3dFactory } from '../rapier-3d-factory';
|
|
@@ -16,6 +16,7 @@ export declare class Rapier3dWorldComponent implements IPhysicsWorld3dComponent<
|
|
|
16
16
|
private _gravity;
|
|
17
17
|
get gravity(): Point3;
|
|
18
18
|
set gravity(value: Point3);
|
|
19
|
+
readonly mainCollisionGroup: CollisionGroup;
|
|
19
20
|
protected _nativeWorld: World | null;
|
|
20
21
|
get nativeWorld(): World;
|
|
21
22
|
private _eventQueue;
|
|
@@ -27,5 +28,6 @@ export declare class Rapier3dWorldComponent implements IPhysicsWorld3dComponent<
|
|
|
27
28
|
protected lockedCollisionGroups: number[];
|
|
28
29
|
registerCollisionGroup(): CollisionGroup;
|
|
29
30
|
deregisterCollisionGroup(group: CollisionGroup): void;
|
|
31
|
+
raycast(options: RaycastOptions<Point3>): RaycastResult<Point3, Rapier3dRigidBodyComponent>;
|
|
30
32
|
dispose(): void;
|
|
31
33
|
}
|
|
@@ -7,6 +7,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
|
+
import { BitMask, Pnt3, } from '@gg-web-engine/core';
|
|
10
11
|
import { EventQueue, init, Vector3, World } from '@dimforge/rapier3d-compat';
|
|
11
12
|
import { Rapier3dFactory } from '../rapier-3d-factory';
|
|
12
13
|
import { Rapier3dLoader } from '../rapier-3d-loader';
|
|
@@ -54,6 +55,7 @@ export class Rapier3dWorldComponent {
|
|
|
54
55
|
this.removed$ = new Subject();
|
|
55
56
|
this.children = [];
|
|
56
57
|
this._gravity = { x: 0, y: 0, z: -9.82 };
|
|
58
|
+
this.mainCollisionGroup = 0;
|
|
57
59
|
this._nativeWorld = null;
|
|
58
60
|
this._eventQueue = null;
|
|
59
61
|
this.handleIdEntityMap = new Map();
|
|
@@ -76,7 +78,7 @@ export class Rapier3dWorldComponent {
|
|
|
76
78
|
(_a = this._nativeWorld) === null || _a === void 0 ? void 0 : _a.step(this.eventQueue);
|
|
77
79
|
}
|
|
78
80
|
registerCollisionGroup() {
|
|
79
|
-
for (let i =
|
|
81
|
+
for (let i = 1; i < 16; i++) {
|
|
80
82
|
if (!this.lockedCollisionGroups.includes(i)) {
|
|
81
83
|
this.lockedCollisionGroups.push(i);
|
|
82
84
|
return i;
|
|
@@ -87,6 +89,48 @@ export class Rapier3dWorldComponent {
|
|
|
87
89
|
deregisterCollisionGroup(group) {
|
|
88
90
|
this.lockedCollisionGroups = this.lockedCollisionGroups.filter(x => x !== group);
|
|
89
91
|
}
|
|
92
|
+
raycast(options) {
|
|
93
|
+
if (!this._nativeWorld) {
|
|
94
|
+
return { hasHit: false };
|
|
95
|
+
}
|
|
96
|
+
const origin = options.from;
|
|
97
|
+
let direction = Pnt3.sub(options.to, options.from);
|
|
98
|
+
const rayLength = Pnt3.len(direction);
|
|
99
|
+
direction = Pnt3.norm(direction);
|
|
100
|
+
const ray = {
|
|
101
|
+
origin: new Vector3(...Pnt3.spr(origin)),
|
|
102
|
+
dir: new Vector3(...Pnt3.spr(direction)),
|
|
103
|
+
pointAt: (t) => {
|
|
104
|
+
return new Vector3(origin.x + direction.x * t, origin.y + direction.y * t, origin.z + direction.z * t);
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
const mask = (groups) => {
|
|
108
|
+
return groups ? BitMask.pack(groups, 16) : BitMask.full(16);
|
|
109
|
+
};
|
|
110
|
+
const hit = this._nativeWorld.castRay(ray, rayLength, true, undefined, (mask(options.collisionFilterGroups) << 16) | mask(options.collisionFilterMask));
|
|
111
|
+
if (!hit) {
|
|
112
|
+
return { hasHit: false };
|
|
113
|
+
}
|
|
114
|
+
const result = {
|
|
115
|
+
hasHit: true,
|
|
116
|
+
};
|
|
117
|
+
const collider = hit.collider;
|
|
118
|
+
if (collider) {
|
|
119
|
+
const rigidBody = collider.parent();
|
|
120
|
+
if (!rigidBody) {
|
|
121
|
+
return { hasHit: false };
|
|
122
|
+
}
|
|
123
|
+
result.hitBody = this.handleIdEntityMap.get(rigidBody.handle);
|
|
124
|
+
result.hitDistance = hit.timeOfImpact;
|
|
125
|
+
result.hitPoint = Pnt3.add(origin, Pnt3.scalarMult(direction, hit.timeOfImpact));
|
|
126
|
+
const rayIntersection = hit.collider.castRayAndGetNormal(ray, hit.timeOfImpact, true);
|
|
127
|
+
result.hitNormal = Pnt3.clone((rayIntersection === null || rayIntersection === void 0 ? void 0 : rayIntersection.normal) || Pnt3.O);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
result.hasHit = false;
|
|
131
|
+
}
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
90
134
|
dispose() {
|
|
91
135
|
var _a;
|
|
92
136
|
(_a = this._nativeWorld) === null || _a === void 0 ? void 0 : _a.free();
|
|
@@ -7,7 +7,7 @@ export class Rapier3dFactory {
|
|
|
7
7
|
this.world = world;
|
|
8
8
|
}
|
|
9
9
|
createRigidBody(descriptor, transform) {
|
|
10
|
-
return new Rapier3dRigidBodyComponent(this.world, this.createColliderDescr(descriptor.shape), descriptor.shape, this.createRigidBodyDescr(descriptor.body, transform), Object.assign({ friction: 0.5, restitution: 0.1, ownCollisionGroups:
|
|
10
|
+
return new Rapier3dRigidBodyComponent(this.world, this.createColliderDescr(descriptor.shape), descriptor.shape, this.createRigidBodyDescr(descriptor.body, transform), Object.assign({ friction: 0.5, restitution: 0.1, ownCollisionGroups: [this.world.mainCollisionGroup], interactWithCollisionGroups: [this.world.mainCollisionGroup] }, descriptor.body));
|
|
11
11
|
}
|
|
12
12
|
createTrigger(descriptor, transform) {
|
|
13
13
|
const colliderDescr = this.createColliderDescr(descriptor);
|
package/dist/types.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ import { Rapier3dFactory } from './rapier-3d-factory';
|
|
|
2
2
|
import { Rapier3dLoader } from './rapier-3d-loader';
|
|
3
3
|
import { Rapier3dRigidBodyComponent } from './components/rapier-3d-rigid-body.component';
|
|
4
4
|
import { Rapier3dTriggerComponent } from './components/rapier-3d-trigger.component';
|
|
5
|
+
import { Gg3dWorld, Gg3dWorldSceneTypeDocPPatch, Gg3dWorldTypeDocPPatch } from '@gg-web-engine/core';
|
|
6
|
+
import { Rapier3dWorldComponent } from './components/rapier-3d-world.component';
|
|
5
7
|
export type Rapier3dPhysicsTypeDocRepo = {
|
|
6
8
|
factory: Rapier3dFactory;
|
|
7
9
|
loader: Rapier3dLoader;
|
|
@@ -9,3 +11,4 @@ export type Rapier3dPhysicsTypeDocRepo = {
|
|
|
9
11
|
trigger: Rapier3dTriggerComponent;
|
|
10
12
|
raycastVehicle: never;
|
|
11
13
|
};
|
|
14
|
+
export type Rapier3dGgWorld = Gg3dWorld<Gg3dWorldTypeDocPPatch<Rapier3dPhysicsTypeDocRepo>, Gg3dWorldSceneTypeDocPPatch<Rapier3dPhysicsTypeDocRepo, Rapier3dWorldComponent>>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gg-web-engine/rapier3d",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.58",
|
|
4
4
|
"description": "An attempt to create open source game engine for browser",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"homepage": "https://github.com/AndyGura/gg-web-engine#readme",
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@dimforge/rapier3d-compat": "0.0.0-50223ff-20240729",
|
|
31
|
-
"@gg-web-engine/core": "0.0.
|
|
31
|
+
"@gg-web-engine/core": "0.0.58",
|
|
32
32
|
"@types/jest": "^29.5.12",
|
|
33
33
|
"jest": "^29.7.0",
|
|
34
34
|
"jest-environment-jsdom": "^29.7.0",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
41
|
"@dimforge/rapier3d-compat": "0.0.0-50223ff-20240729",
|
|
42
|
-
"@gg-web-engine/core": "0.0.
|
|
42
|
+
"@gg-web-engine/core": "0.0.58",
|
|
43
43
|
"rxjs": "7.8.1"
|
|
44
44
|
},
|
|
45
45
|
"jest": {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Rapier3dFactory, Rapier3dWorldComponent } from '../../src';
|
|
2
1
|
import { Pnt3 } from '@gg-web-engine/core';
|
|
2
|
+
import { Rapier3dFactory, Rapier3dWorldComponent } from '../../src';
|
|
3
3
|
|
|
4
4
|
describe(`Rapier3dTriggerComponent`, () => {
|
|
5
5
|
|
|
@@ -85,40 +85,41 @@ describe(`Rapier3dTriggerComponent`, () => {
|
|
|
85
85
|
expect(enterRegistered).toBe(true);
|
|
86
86
|
});
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
88
|
+
it(`should fire end of object intersection if trigger removed`, async () => {
|
|
89
|
+
const trigger = factory.createTrigger({ shape: 'BOX', dimensions: { x: 10, y: 10, z: 10 } });
|
|
90
|
+
trigger.addToWorld({ physicsWorld: world } as any);
|
|
91
|
+
const ball = factory.createRigidBody({
|
|
92
|
+
shape: { shape: 'SPHERE', radius: 1 },
|
|
93
|
+
body: { dynamic: true, mass: 1 },
|
|
94
|
+
}, { position: { x: 0, y: 0, z: 0 } });
|
|
95
|
+
ball.addToWorld({ physicsWorld: world } as any);
|
|
96
|
+
let exitRegistered = false;
|
|
97
|
+
trigger.onEntityLeft.subscribe(((obj) => {
|
|
98
|
+
exitRegistered = obj === ball;
|
|
99
|
+
}));
|
|
100
|
+
world.simulate(1);
|
|
101
|
+
trigger.checkOverlaps();
|
|
102
|
+
trigger.removeFromWorld({ physicsWorld: world } as any);
|
|
103
|
+
expect(exitRegistered).toBe(true);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it(`should fire end of object intersection if object removed`, async () => {
|
|
107
|
+
const trigger = factory.createTrigger({ shape: 'BOX', dimensions: { x: 10, y: 10, z: 10 } });
|
|
108
|
+
trigger.addToWorld({ physicsWorld: world } as any);
|
|
109
|
+
const ball = factory.createRigidBody({
|
|
110
|
+
shape: { shape: 'SPHERE', radius: 1 },
|
|
111
|
+
body: { dynamic: true, mass: 1 },
|
|
112
|
+
}, { position: { x: 0, y: 0, z: 0 } });
|
|
113
|
+
ball.addToWorld({ physicsWorld: world } as any);
|
|
114
|
+
let exitRegistered = false;
|
|
115
|
+
trigger.onEntityLeft.subscribe(((obj) => {
|
|
116
|
+
exitRegistered = obj === ball;
|
|
117
|
+
}));
|
|
118
|
+
world.simulate(1);
|
|
119
|
+
trigger.checkOverlaps();
|
|
120
|
+
ball.removeFromWorld({ physicsWorld: world } as any);
|
|
121
|
+
world.simulate(1);
|
|
122
|
+
trigger.checkOverlaps();
|
|
123
|
+
expect(exitRegistered).toBe(true);
|
|
124
|
+
});
|
|
124
125
|
});
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { Point3, RaycastOptions } from '@gg-web-engine/core';
|
|
2
|
+
import { Rapier3dWorldComponent } from '../../src';
|
|
3
|
+
|
|
4
|
+
describe('Rapier3dWorldComponent', () => {
|
|
5
|
+
let world: Rapier3dWorldComponent;
|
|
6
|
+
|
|
7
|
+
beforeEach(async () => {
|
|
8
|
+
if (world) {
|
|
9
|
+
world.dispose();
|
|
10
|
+
}
|
|
11
|
+
world = new Rapier3dWorldComponent();
|
|
12
|
+
await world.init();
|
|
13
|
+
world.gravity = { x: 0, y: 0, z: 0 }; // Set gravity to zero for predictable physics
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterAll(() => {
|
|
17
|
+
world.dispose();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe('Rigid bodies', () => {
|
|
21
|
+
|
|
22
|
+
it('should simulate inertial motion of rigid body', () => {
|
|
23
|
+
|
|
24
|
+
const ball = world.factory.createRigidBody({
|
|
25
|
+
shape: { shape: 'SPHERE', radius: 1 },
|
|
26
|
+
body: { dynamic: true, mass: 5 },
|
|
27
|
+
}, { position: { x: -5, y: 0, z: 0 } });
|
|
28
|
+
ball.addToWorld({ physicsWorld: world } as any);
|
|
29
|
+
ball.linearVelocity = { x: 1, y: 0, z: 0 };
|
|
30
|
+
|
|
31
|
+
// simulate for 6 seconds
|
|
32
|
+
for (let i = 0; i < 100; i++) {
|
|
33
|
+
world.simulate(60);
|
|
34
|
+
}
|
|
35
|
+
expect(ball.position.x).toBeCloseTo(1);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should simulate collision of two rigid bodies', () => {
|
|
39
|
+
|
|
40
|
+
const ball0 = world.factory.createRigidBody({
|
|
41
|
+
shape: { shape: 'SPHERE', radius: 1 },
|
|
42
|
+
body: { dynamic: true, mass: 5 },
|
|
43
|
+
}, { position: { x: -5, y: 0, z: 0 } });
|
|
44
|
+
ball0.addToWorld({ physicsWorld: world } as any);
|
|
45
|
+
ball0.linearVelocity = { x: 1, y: 0, z: 0 };
|
|
46
|
+
|
|
47
|
+
const ball1 = world.factory.createRigidBody({
|
|
48
|
+
shape: { shape: 'SPHERE', radius: 1 },
|
|
49
|
+
body: { dynamic: true, mass: 5 },
|
|
50
|
+
}, { position: { x: 5, y: 0, z: 0 } });
|
|
51
|
+
ball1.addToWorld({ physicsWorld: world } as any);
|
|
52
|
+
ball1.linearVelocity = { x: -1, y: 0, z: 0 };
|
|
53
|
+
|
|
54
|
+
// simulate for 6 seconds
|
|
55
|
+
for (let i = 0; i < 100; i++) {
|
|
56
|
+
world.simulate(60);
|
|
57
|
+
}
|
|
58
|
+
expect(ball0.position.x).toBeLessThan(0);
|
|
59
|
+
expect(ball1.position.x).toBeGreaterThan(0);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should not simulate collision of two rigid bodies with different collision groups', () => {
|
|
63
|
+
const cg0 = world.registerCollisionGroup();
|
|
64
|
+
const ball0 = world.factory.createRigidBody({
|
|
65
|
+
shape: { shape: 'SPHERE', radius: 1 },
|
|
66
|
+
body: { dynamic: true, mass: 5 },
|
|
67
|
+
}, { position: { x: -5, y: 0, z: 0 } });
|
|
68
|
+
ball0.addToWorld({ physicsWorld: world } as any);
|
|
69
|
+
ball0.linearVelocity = { x: 1, y: 0, z: 0 };
|
|
70
|
+
ball0.ownCollisionGroups = ball0.interactWithCollisionGroups = [cg0];
|
|
71
|
+
|
|
72
|
+
const cg1 = world.registerCollisionGroup();
|
|
73
|
+
const ball1 = world.factory.createRigidBody({
|
|
74
|
+
shape: { shape: 'SPHERE', radius: 1 },
|
|
75
|
+
body: { dynamic: true, mass: 5 },
|
|
76
|
+
}, { position: { x: 5, y: 0, z: 0 } });
|
|
77
|
+
ball1.addToWorld({ physicsWorld: world } as any);
|
|
78
|
+
ball1.linearVelocity = { x: -1, y: 0, z: 0 };
|
|
79
|
+
ball1.ownCollisionGroups = ball1.interactWithCollisionGroups = [cg1];
|
|
80
|
+
|
|
81
|
+
// simulate for 6 seconds
|
|
82
|
+
for (let i = 0; i < 100; i++) {
|
|
83
|
+
world.simulate(60);
|
|
84
|
+
}
|
|
85
|
+
expect(ball0.position.x).toBeCloseTo(1);
|
|
86
|
+
expect(ball1.position.x).toBeCloseTo(-1);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('Raycast', () => {
|
|
91
|
+
|
|
92
|
+
it('should return no hit when ray does not intersect any object', () => {
|
|
93
|
+
// Create a box far away from the ray
|
|
94
|
+
const box = world.factory.createRigidBody({
|
|
95
|
+
shape: { shape: 'BOX', dimensions: { x: 1, y: 1, z: 1 } },
|
|
96
|
+
body: { dynamic: false, mass: 0 },
|
|
97
|
+
}, { position: { x: 10, y: 10, z: 10 } });
|
|
98
|
+
box.addToWorld({ physicsWorld: world } as any);
|
|
99
|
+
|
|
100
|
+
world.simulate(1);
|
|
101
|
+
|
|
102
|
+
// Cast a ray that doesn't hit anything
|
|
103
|
+
const raycastOptions: RaycastOptions<Point3> = {
|
|
104
|
+
from: { x: 0, y: 0, z: 0 },
|
|
105
|
+
to: { x: 0, y: 0, z: -10 },
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const result = world.raycast(raycastOptions);
|
|
109
|
+
expect(result.hasHit).toBe(false);
|
|
110
|
+
expect(result.hitPoint).toBeUndefined();
|
|
111
|
+
expect(result.hitNormal).toBeUndefined();
|
|
112
|
+
expect(result.hitDistance).toBeUndefined();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('should detect hit when ray intersects an object', () => {
|
|
116
|
+
// Create a box in the path of the ray
|
|
117
|
+
const box = world.factory.createRigidBody({
|
|
118
|
+
shape: { shape: 'BOX', dimensions: { x: 2, y: 2, z: 2 } },
|
|
119
|
+
body: { dynamic: false, mass: 0 },
|
|
120
|
+
}, { position: { x: 0, y: 0, z: -5 } });
|
|
121
|
+
box.addToWorld({ physicsWorld: world } as any);
|
|
122
|
+
|
|
123
|
+
world.simulate(1);
|
|
124
|
+
|
|
125
|
+
// Cast a ray that hits the box
|
|
126
|
+
const raycastOptions: RaycastOptions<Point3> = {
|
|
127
|
+
from: { x: 0, y: 0, z: 0 },
|
|
128
|
+
to: { x: 0, y: 0, z: -10 },
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const result = world.raycast(raycastOptions);
|
|
132
|
+
expect(result.hasHit).toBe(true);
|
|
133
|
+
expect(result.hitBody).toBe(box);
|
|
134
|
+
expect(result.hitPoint).toBeDefined();
|
|
135
|
+
expect(result.hitNormal).toBeDefined();
|
|
136
|
+
expect(result.hitDistance).toBeDefined();
|
|
137
|
+
|
|
138
|
+
// The hit should be at approximately z = -4 (box at z = -5 with size 2)
|
|
139
|
+
expect(result.hitPoint!.z).toBeCloseTo(-4, 0.1);
|
|
140
|
+
|
|
141
|
+
// The normal should point up (towards the ray origin)
|
|
142
|
+
expect(result.hitNormal!.z).toBeGreaterThan(0);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('should respect collision filtering', () => {
|
|
146
|
+
// Register collision groups
|
|
147
|
+
const group1 = world.registerCollisionGroup();
|
|
148
|
+
const group2 = world.registerCollisionGroup();
|
|
149
|
+
|
|
150
|
+
// Create a box that only belongs to group1
|
|
151
|
+
const box1 = world.factory.createRigidBody({
|
|
152
|
+
shape: { shape: 'BOX', dimensions: { x: 2, y: 2, z: 2 } },
|
|
153
|
+
body: {
|
|
154
|
+
dynamic: false,
|
|
155
|
+
mass: 0,
|
|
156
|
+
ownCollisionGroups: [group1],
|
|
157
|
+
interactWithCollisionGroups: [group1, group2],
|
|
158
|
+
},
|
|
159
|
+
}, { position: { x: 0, y: 0, z: -5 } });
|
|
160
|
+
box1.addToWorld({ physicsWorld: world } as any);
|
|
161
|
+
|
|
162
|
+
world.simulate(1);
|
|
163
|
+
|
|
164
|
+
// Ray that only checks against group2 should not hit
|
|
165
|
+
const rayOptions1: RaycastOptions<Point3> = {
|
|
166
|
+
from: { x: 0, y: 0, z: 0 },
|
|
167
|
+
to: { x: 0, y: 0, z: -10 },
|
|
168
|
+
collisionFilterGroups: [group2],
|
|
169
|
+
collisionFilterMask: [group2],
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const result1 = world.raycast(rayOptions1);
|
|
173
|
+
expect(result1.hasHit).toBe(false);
|
|
174
|
+
|
|
175
|
+
// Ray that checks against group1 should hit
|
|
176
|
+
const rayOptions2: RaycastOptions<Point3> = {
|
|
177
|
+
from: { x: 0, y: 0, z: 0 },
|
|
178
|
+
to: { x: 0, y: 0, z: -10 },
|
|
179
|
+
collisionFilterGroups: [group1],
|
|
180
|
+
collisionFilterMask: [group1],
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const result2 = world.raycast(rayOptions2);
|
|
184
|
+
expect(result2.hasHit).toBe(true);
|
|
185
|
+
expect(result2.hitBody).toBe(box1);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('should calculate hit distance correctly', () => {
|
|
189
|
+
// Create a box at a known distance
|
|
190
|
+
const box = world.factory.createRigidBody({
|
|
191
|
+
shape: { shape: 'BOX', dimensions: { x: 2, y: 2, z: 2 } },
|
|
192
|
+
body: { dynamic: false, mass: 0 },
|
|
193
|
+
}, { position: { x: 0, y: 0, z: -5 } });
|
|
194
|
+
box.addToWorld({ physicsWorld: world } as any);
|
|
195
|
+
|
|
196
|
+
world.simulate(1);
|
|
197
|
+
|
|
198
|
+
// Cast a ray from origin to z = -10
|
|
199
|
+
const raycastOptions: RaycastOptions<Point3> = {
|
|
200
|
+
from: { x: 0, y: 0, z: 0 },
|
|
201
|
+
to: { x: 0, y: 0, z: -10 },
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const result = world.raycast(raycastOptions);
|
|
205
|
+
expect(result.hasHit).toBe(true);
|
|
206
|
+
|
|
207
|
+
// The hit distance should be approximately 4 units
|
|
208
|
+
// (from origin to the edge of the box at z = -4)
|
|
209
|
+
expect(result.hitDistance).toBeCloseTo(4, 0.1);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('should handle array of collision groups correctly', () => {
|
|
213
|
+
// Register collision groups
|
|
214
|
+
const group1 = world.registerCollisionGroup();
|
|
215
|
+
const group2 = world.registerCollisionGroup();
|
|
216
|
+
|
|
217
|
+
// Create a box that belongs to group1
|
|
218
|
+
const box = world.factory.createRigidBody({
|
|
219
|
+
shape: { shape: 'BOX', dimensions: { x: 2, y: 2, z: 2 } },
|
|
220
|
+
body: {
|
|
221
|
+
dynamic: false,
|
|
222
|
+
mass: 0,
|
|
223
|
+
ownCollisionGroups: [group1],
|
|
224
|
+
interactWithCollisionGroups: [group1, group2],
|
|
225
|
+
},
|
|
226
|
+
}, { position: { x: 0, y: 0, z: -5 } });
|
|
227
|
+
box.addToWorld({ physicsWorld: world } as any);
|
|
228
|
+
|
|
229
|
+
world.simulate(1);
|
|
230
|
+
|
|
231
|
+
// Ray that checks against both groups should hit
|
|
232
|
+
const rayOptions: RaycastOptions<Point3> = {
|
|
233
|
+
from: { x: 0, y: 0, z: 0 },
|
|
234
|
+
to: { x: 0, y: 0, z: -10 },
|
|
235
|
+
collisionFilterGroups: [group1, group2],
|
|
236
|
+
collisionFilterMask: [group1, group2],
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const result = world.raycast(rayOptions);
|
|
240
|
+
expect(result.hasHit).toBe(true);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('should return correct hit body', () => {
|
|
244
|
+
// Create two boxes at different positions
|
|
245
|
+
const box1 = world.factory.createRigidBody({
|
|
246
|
+
shape: { shape: 'BOX', dimensions: { x: 1, y: 1, z: 1 } },
|
|
247
|
+
body: { dynamic: false, mass: 0 },
|
|
248
|
+
}, { position: { x: 0, y: 0, z: -3 } });
|
|
249
|
+
box1.addToWorld({ physicsWorld: world } as any);
|
|
250
|
+
|
|
251
|
+
const box2 = world.factory.createRigidBody({
|
|
252
|
+
shape: { shape: 'BOX', dimensions: { x: 1, y: 1, z: 1 } },
|
|
253
|
+
body: { dynamic: false, mass: 0 },
|
|
254
|
+
}, { position: { x: 0, y: 0, z: -7 } });
|
|
255
|
+
box2.addToWorld({ physicsWorld: world } as any);
|
|
256
|
+
|
|
257
|
+
world.simulate(1);
|
|
258
|
+
|
|
259
|
+
// Cast a ray that should hit box1 first
|
|
260
|
+
const raycastOptions: RaycastOptions<Point3> = {
|
|
261
|
+
from: { x: 0, y: 0, z: 0 },
|
|
262
|
+
to: { x: 0, y: 0, z: -10 },
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const result = world.raycast(raycastOptions);
|
|
266
|
+
expect(result.hasHit).toBe(true);
|
|
267
|
+
expect(result.hitBody).toBe(box1);
|
|
268
|
+
expect(result.hitPoint!.z).toBeCloseTo(-2.5, 0.1); // box1 edge is at z = -2.5
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it('should handle edge case with ray starting inside an object', () => {
|
|
272
|
+
// Create a box
|
|
273
|
+
const box = world.factory.createRigidBody({
|
|
274
|
+
shape: { shape: 'BOX', dimensions: { x: 4, y: 4, z: 4 } },
|
|
275
|
+
body: { dynamic: false, mass: 0 },
|
|
276
|
+
}, { position: { x: 0, y: 0, z: 0 } });
|
|
277
|
+
box.addToWorld({ physicsWorld: world } as any);
|
|
278
|
+
|
|
279
|
+
world.simulate(1);
|
|
280
|
+
|
|
281
|
+
// Cast a ray from inside the box
|
|
282
|
+
const raycastOptions: RaycastOptions<Point3> = {
|
|
283
|
+
from: { x: 0, y: 0, z: 0 }, // Center of the box
|
|
284
|
+
to: { x: 0, y: 0, z: -10 },
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
const result = world.raycast(raycastOptions);
|
|
288
|
+
|
|
289
|
+
// Behavior may vary depending on physics engine implementation
|
|
290
|
+
// Some engines might not detect hits when starting inside an object
|
|
291
|
+
// Others might detect the exit point
|
|
292
|
+
// We just verify the method doesn't crash and returns a valid result
|
|
293
|
+
expect(result).toBeDefined();
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
});
|