@gg-web-engine/ammo 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/ammo-body.component.d.ts +9 -9
- package/dist/components/ammo-body.component.js +2 -1
- package/dist/components/ammo-raycast-vehicle.component.d.ts +8 -8
- package/dist/components/ammo-raycast-vehicle.component.js +8 -4
- package/dist/components/ammo-rigid-body.component.d.ts +5 -5
- package/dist/components/ammo-rigid-body.component.js +4 -8
- package/dist/components/ammo-trigger.component.d.ts +6 -6
- package/dist/components/ammo-trigger.component.js +3 -4
- package/dist/components/ammo-world.component.d.ts +3 -1
- package/dist/components/ammo-world.component.js +44 -1
- package/dist/types.d.ts +3 -0
- package/package.json +3 -3
- package/test/components/ammo-trigger.component.spec.ts +3 -0
- package/test/components/ammo-world.component.spec.ts +296 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { CollisionGroup,
|
|
1
|
+
import { CollisionGroup, IEntity, Point3, Point4, Shape3DDescriptor } from '@gg-web-engine/core';
|
|
2
2
|
import { AmmoWorldComponent } from './ammo-world.component';
|
|
3
3
|
import Ammo from '../ammo.js/ammo';
|
|
4
|
-
import {
|
|
4
|
+
import { AmmoGgWorld } from '../types';
|
|
5
5
|
export declare abstract class AmmoBodyComponent<T extends Ammo.btCollisionObject> {
|
|
6
6
|
protected readonly world: AmmoWorldComponent;
|
|
7
7
|
protected _nativeBody: T;
|
|
8
8
|
readonly shape: Shape3DDescriptor;
|
|
9
|
-
|
|
9
|
+
static nativeBodyReverseMap: Map<number, AmmoBodyComponent<any>>;
|
|
10
10
|
get position(): Point3;
|
|
11
11
|
set position(value: Point3);
|
|
12
12
|
get rotation(): Point4;
|
|
@@ -18,14 +18,14 @@ export declare abstract class AmmoBodyComponent<T extends Ammo.btCollisionObject
|
|
|
18
18
|
protected addedToWorld: boolean;
|
|
19
19
|
protected _interactWithCGsMask: number;
|
|
20
20
|
protected _ownCGsMask: number;
|
|
21
|
-
get interactWithCollisionGroups(): CollisionGroup
|
|
21
|
+
get interactWithCollisionGroups(): ReadonlyArray<CollisionGroup>;
|
|
22
22
|
abstract refreshCG(): void;
|
|
23
|
-
set interactWithCollisionGroups(value: CollisionGroup
|
|
24
|
-
get ownCollisionGroups(): CollisionGroup
|
|
25
|
-
set ownCollisionGroups(value: CollisionGroup
|
|
23
|
+
set interactWithCollisionGroups(value: ReadonlyArray<CollisionGroup> | 'all');
|
|
24
|
+
get ownCollisionGroups(): ReadonlyArray<CollisionGroup>;
|
|
25
|
+
set ownCollisionGroups(value: ReadonlyArray<CollisionGroup> | 'all');
|
|
26
26
|
protected constructor(world: AmmoWorldComponent, _nativeBody: T, shape: Shape3DDescriptor);
|
|
27
27
|
abstract clone(): AmmoBodyComponent<T>;
|
|
28
|
-
addToWorld(world:
|
|
29
|
-
removeFromWorld(world:
|
|
28
|
+
addToWorld(world: AmmoGgWorld): void;
|
|
29
|
+
removeFromWorld(world: AmmoGgWorld): void;
|
|
30
30
|
dispose(): void;
|
|
31
31
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BitMask
|
|
1
|
+
import { BitMask } from '@gg-web-engine/core';
|
|
2
2
|
import Ammo from '../ammo.js/ammo';
|
|
3
3
|
export class AmmoBodyComponent {
|
|
4
4
|
get position() {
|
|
@@ -75,6 +75,7 @@ export class AmmoBodyComponent {
|
|
|
75
75
|
this._interactWithCGsMask = BitMask.full(16);
|
|
76
76
|
this._ownCGsMask = BitMask.full(16);
|
|
77
77
|
AmmoBodyComponent.nativeBodyReverseMap.set(Ammo.getPointer(this.nativeBody), this);
|
|
78
|
+
this.ownCollisionGroups = this.interactWithCollisionGroups = [world.mainCollisionGroup];
|
|
78
79
|
}
|
|
79
80
|
addToWorld(world) {
|
|
80
81
|
if (world.physicsWorld != this.world) {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { CollisionGroup,
|
|
1
|
+
import { CollisionGroup, IRaycastVehicleComponent, Point3, Point4, RaycastVehicle3dEntity, SuspensionOptions, WheelOptions } from '@gg-web-engine/core';
|
|
2
2
|
import Ammo from '../ammo.js/ammo';
|
|
3
3
|
import { AmmoRigidBodyComponent } from './ammo-rigid-body.component';
|
|
4
4
|
import { AmmoWorldComponent } from './ammo-world.component';
|
|
5
|
-
import { AmmoPhysicsTypeDocRepo } from '../types';
|
|
5
|
+
import { AmmoGgWorld, AmmoPhysicsTypeDocRepo } from '../types';
|
|
6
6
|
export declare class AmmoRaycastVehicleComponent extends AmmoRigidBodyComponent implements IRaycastVehicleComponent<AmmoPhysicsTypeDocRepo> {
|
|
7
7
|
protected readonly world: AmmoWorldComponent;
|
|
8
8
|
chassisBody: AmmoRigidBodyComponent;
|
|
@@ -12,15 +12,15 @@ export declare class AmmoRaycastVehicleComponent extends AmmoRigidBodyComponent
|
|
|
12
12
|
protected readonly wheelAxleCS: Ammo.btVector3;
|
|
13
13
|
entity: RaycastVehicle3dEntity | null;
|
|
14
14
|
protected readonly raycaster: Ammo.btDefaultVehicleRaycaster;
|
|
15
|
-
get interactWithCollisionGroups(): CollisionGroup
|
|
16
|
-
set interactWithCollisionGroups(value: CollisionGroup
|
|
17
|
-
get ownCollisionGroups(): CollisionGroup
|
|
18
|
-
set ownCollisionGroups(value: CollisionGroup
|
|
15
|
+
get interactWithCollisionGroups(): ReadonlyArray<CollisionGroup>;
|
|
16
|
+
set interactWithCollisionGroups(value: ReadonlyArray<CollisionGroup> | 'all');
|
|
17
|
+
get ownCollisionGroups(): ReadonlyArray<CollisionGroup>;
|
|
18
|
+
set ownCollisionGroups(value: ReadonlyArray<CollisionGroup> | 'all');
|
|
19
19
|
refreshCG(): void;
|
|
20
20
|
constructor(world: AmmoWorldComponent, chassisBody: AmmoRigidBodyComponent);
|
|
21
21
|
get wheelSpeed(): number;
|
|
22
|
-
addToWorld(world:
|
|
23
|
-
removeFromWorld(world:
|
|
22
|
+
addToWorld(world: AmmoGgWorld): void;
|
|
23
|
+
removeFromWorld(world: AmmoGgWorld): void;
|
|
24
24
|
addWheel(options: WheelOptions, suspensionOptions: SuspensionOptions): void;
|
|
25
25
|
setSteering(wheelIndex: number, steering: number): void;
|
|
26
26
|
applyEngineForce(wheelIndex: number, force: number): void;
|
|
@@ -6,15 +6,19 @@ export class AmmoRaycastVehicleComponent extends AmmoRigidBodyComponent {
|
|
|
6
6
|
return this.chassisBody.interactWithCollisionGroups;
|
|
7
7
|
}
|
|
8
8
|
set interactWithCollisionGroups(value) {
|
|
9
|
-
this.chassisBody
|
|
10
|
-
|
|
9
|
+
if (this.chassisBody) {
|
|
10
|
+
this.chassisBody.interactWithCollisionGroups = value;
|
|
11
|
+
this.raycaster.set_m_collisionFilterMask(BitMask.pack(this.chassisBody.interactWithCollisionGroups, 16));
|
|
12
|
+
}
|
|
11
13
|
}
|
|
12
14
|
get ownCollisionGroups() {
|
|
13
15
|
return this.chassisBody.ownCollisionGroups;
|
|
14
16
|
}
|
|
15
17
|
set ownCollisionGroups(value) {
|
|
16
|
-
this.chassisBody
|
|
17
|
-
|
|
18
|
+
if (this.chassisBody) {
|
|
19
|
+
this.chassisBody.ownCollisionGroups = value;
|
|
20
|
+
this.raycaster.set_m_collisionFilterGroup(BitMask.pack(this.chassisBody.ownCollisionGroups, 16));
|
|
21
|
+
}
|
|
18
22
|
}
|
|
19
23
|
refreshCG() {
|
|
20
24
|
this.chassisBody.refreshCG();
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { AmmoWorldComponent } from './ammo-world.component';
|
|
2
2
|
import Ammo from '../ammo.js/ammo';
|
|
3
3
|
import { AmmoBodyComponent } from './ammo-body.component';
|
|
4
|
-
import { DebugBody3DSettings, Entity3d,
|
|
5
|
-
import { AmmoPhysicsTypeDocRepo } from '../types';
|
|
4
|
+
import { DebugBody3DSettings, Entity3d, IRigidBody3dComponent, Point3, Shape3DDescriptor } from '@gg-web-engine/core';
|
|
5
|
+
import { AmmoGgWorld, AmmoPhysicsTypeDocRepo } from '../types';
|
|
6
6
|
export declare class AmmoRigidBodyComponent extends AmmoBodyComponent<Ammo.btRigidBody> implements IRigidBody3dComponent<AmmoPhysicsTypeDocRepo> {
|
|
7
7
|
protected readonly world: AmmoWorldComponent;
|
|
8
8
|
protected _nativeBody: Ammo.btRigidBody;
|
|
@@ -12,11 +12,11 @@ export declare class AmmoRigidBodyComponent extends AmmoBodyComponent<Ammo.btRig
|
|
|
12
12
|
set linearVelocity(value: Point3);
|
|
13
13
|
get angularVelocity(): Point3;
|
|
14
14
|
set angularVelocity(value: Point3);
|
|
15
|
-
|
|
15
|
+
readonly debugBodySettings: DebugBody3DSettings;
|
|
16
16
|
constructor(world: AmmoWorldComponent, _nativeBody: Ammo.btRigidBody, shape: Shape3DDescriptor);
|
|
17
17
|
clone(): AmmoRigidBodyComponent;
|
|
18
|
-
addToWorld(world:
|
|
19
|
-
removeFromWorld(world:
|
|
18
|
+
addToWorld(world: AmmoGgWorld): void;
|
|
19
|
+
removeFromWorld(world: AmmoGgWorld): void;
|
|
20
20
|
refreshCG(): void;
|
|
21
21
|
resetMotion(): void;
|
|
22
22
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import Ammo from '../ammo.js/ammo';
|
|
2
2
|
import { AmmoBodyComponent } from './ammo-body.component';
|
|
3
|
+
import { DebugBody3DSettings } from '@gg-web-engine/core';
|
|
3
4
|
import { first } from 'rxjs';
|
|
4
5
|
export class AmmoRigidBodyComponent extends AmmoBodyComponent {
|
|
5
6
|
get linearVelocity() {
|
|
@@ -16,20 +17,15 @@ export class AmmoRigidBodyComponent extends AmmoBodyComponent {
|
|
|
16
17
|
set angularVelocity(value) {
|
|
17
18
|
this.nativeBody.setAngularVelocity(new Ammo.btVector3(value.x, value.y, value.z));
|
|
18
19
|
}
|
|
19
|
-
get debugBodySettings() {
|
|
20
|
-
if (this._nativeBody.isStaticOrKinematicObject()) {
|
|
21
|
-
return { shape: this.shape, type: 'RIGID_STATIC' };
|
|
22
|
-
}
|
|
23
|
-
else {
|
|
24
|
-
return { shape: this.shape, type: 'RIGID_DYNAMIC', sleeping: !this._nativeBody.isActive() };
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
20
|
constructor(world, _nativeBody, shape) {
|
|
28
21
|
super(world, _nativeBody, shape);
|
|
29
22
|
this.world = world;
|
|
30
23
|
this._nativeBody = _nativeBody;
|
|
31
24
|
this.shape = shape;
|
|
32
25
|
this.entity = null;
|
|
26
|
+
this.debugBodySettings = new DebugBody3DSettings(this._nativeBody.isStaticOrKinematicObject()
|
|
27
|
+
? { type: 'RIGID_STATIC' }
|
|
28
|
+
: { type: 'RIGID_DYNAMIC', sleeping: () => !this._nativeBody.isActive() }, this.shape);
|
|
33
29
|
}
|
|
34
30
|
clone() {
|
|
35
31
|
return this.world.factory.createRigidBodyFromShape(this._nativeBody.getCollisionShape(), this.shape, {
|
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
import { DebugBody3DSettings,
|
|
1
|
+
import { DebugBody3DSettings, IEntity, ITrigger3dComponent, Shape3DDescriptor } from '@gg-web-engine/core';
|
|
2
2
|
import { AmmoWorldComponent } from './ammo-world.component';
|
|
3
3
|
import { Observable, Subject } from 'rxjs';
|
|
4
4
|
import Ammo from '../ammo.js/ammo';
|
|
5
5
|
import { AmmoBodyComponent } from './ammo-body.component';
|
|
6
6
|
import { AmmoRigidBodyComponent } from './ammo-rigid-body.component';
|
|
7
|
-
import { AmmoPhysicsTypeDocRepo } from '../types';
|
|
7
|
+
import { AmmoGgWorld, AmmoPhysicsTypeDocRepo } from '../types';
|
|
8
8
|
export declare class AmmoTriggerComponent extends AmmoBodyComponent<Ammo.btPairCachingGhostObject> implements ITrigger3dComponent<AmmoPhysicsTypeDocRepo> {
|
|
9
9
|
protected readonly world: AmmoWorldComponent;
|
|
10
10
|
protected _nativeBody: Ammo.btPairCachingGhostObject;
|
|
11
11
|
readonly shape: Shape3DDescriptor;
|
|
12
12
|
entity: IEntity | null;
|
|
13
|
-
|
|
13
|
+
readonly debugBodySettings: DebugBody3DSettings;
|
|
14
14
|
get onEntityEntered(): Observable<AmmoRigidBodyComponent>;
|
|
15
15
|
get onEntityLeft(): Observable<AmmoRigidBodyComponent | null>;
|
|
16
16
|
constructor(world: AmmoWorldComponent, _nativeBody: Ammo.btPairCachingGhostObject, shape: Shape3DDescriptor);
|
|
17
|
+
protected readonly overlaps: Set<Ammo.btCollisionObject>;
|
|
17
18
|
protected readonly onEnter$: Subject<number>;
|
|
18
19
|
protected readonly onLeft$: Subject<number>;
|
|
19
|
-
protected readonly overlaps: Set<Ammo.btCollisionObject>;
|
|
20
20
|
checkOverlaps(): void;
|
|
21
21
|
clone(): AmmoTriggerComponent;
|
|
22
|
-
addToWorld(world:
|
|
23
|
-
removeFromWorld(world:
|
|
22
|
+
addToWorld(world: AmmoGgWorld): void;
|
|
23
|
+
removeFromWorld(world: AmmoGgWorld): void;
|
|
24
24
|
refreshCG(): void;
|
|
25
25
|
dispose(): void;
|
|
26
26
|
}
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
+
import { DebugBody3DSettings } from '@gg-web-engine/core';
|
|
1
2
|
import { filter, map, Subject } from 'rxjs';
|
|
2
3
|
import Ammo from '../ammo.js/ammo';
|
|
3
4
|
import { AmmoBodyComponent } from './ammo-body.component';
|
|
4
5
|
export class AmmoTriggerComponent extends AmmoBodyComponent {
|
|
5
|
-
get debugBodySettings() {
|
|
6
|
-
return { shape: this.shape, type: 'TRIGGER' };
|
|
7
|
-
}
|
|
8
6
|
get onEntityEntered() {
|
|
9
7
|
return this.onEnter$.pipe(map(b => AmmoBodyComponent.nativeBodyReverseMap.get(b)), filter(x => !!x));
|
|
10
8
|
}
|
|
@@ -17,9 +15,10 @@ export class AmmoTriggerComponent extends AmmoBodyComponent {
|
|
|
17
15
|
this._nativeBody = _nativeBody;
|
|
18
16
|
this.shape = shape;
|
|
19
17
|
this.entity = null;
|
|
18
|
+
this.debugBodySettings = new DebugBody3DSettings({ type: 'TRIGGER', activated: () => this.overlaps.size > 0 }, this.shape);
|
|
19
|
+
this.overlaps = new Set();
|
|
20
20
|
this.onEnter$ = new Subject();
|
|
21
21
|
this.onLeft$ = new Subject();
|
|
22
|
-
this.overlaps = new Set();
|
|
23
22
|
}
|
|
24
23
|
checkOverlaps() {
|
|
25
24
|
const numOverlappingObjects = this.nativeBody.getNumOverlappingObjects();
|
|
@@ -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 { Subject } from 'rxjs';
|
|
3
3
|
import Ammo from '../ammo.js/ammo';
|
|
4
4
|
import { AmmoFactory } from '../ammo-factory';
|
|
@@ -18,6 +18,7 @@ export declare class AmmoWorldComponent implements IPhysicsWorld3dComponent<Ammo
|
|
|
18
18
|
private _gravity;
|
|
19
19
|
get gravity(): Point3;
|
|
20
20
|
set gravity(value: Point3);
|
|
21
|
+
readonly mainCollisionGroup: CollisionGroup;
|
|
21
22
|
maxSubSteps?: number;
|
|
22
23
|
fixedTimeStep?: number;
|
|
23
24
|
get dynamicAmmoWorld(): Ammo.btDiscreteDynamicsWorld | undefined;
|
|
@@ -34,5 +35,6 @@ export declare class AmmoWorldComponent implements IPhysicsWorld3dComponent<Ammo
|
|
|
34
35
|
protected lockedCollisionGroups: number[];
|
|
35
36
|
registerCollisionGroup(): CollisionGroup;
|
|
36
37
|
deregisterCollisionGroup(group: CollisionGroup): void;
|
|
38
|
+
raycast(options: RaycastOptions<Point3>): RaycastResult<Point3, AmmoRigidBodyComponent | AmmoTriggerComponent>;
|
|
37
39
|
dispose(): void;
|
|
38
40
|
}
|
|
@@ -7,10 +7,12 @@ 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, } from '@gg-web-engine/core';
|
|
10
11
|
import { Subject } from 'rxjs';
|
|
11
12
|
import Ammo from '../ammo.js/ammo';
|
|
12
13
|
import { AmmoFactory } from '../ammo-factory';
|
|
13
14
|
import { AmmoLoader } from '../ammo-loader';
|
|
15
|
+
import { AmmoBodyComponent } from './ammo-body.component';
|
|
14
16
|
export class AmmoWorldComponent {
|
|
15
17
|
get factory() {
|
|
16
18
|
if (!this._factory) {
|
|
@@ -46,6 +48,7 @@ export class AmmoWorldComponent {
|
|
|
46
48
|
this.children = [];
|
|
47
49
|
this._loader = null;
|
|
48
50
|
this._gravity = { x: 0, y: 0, z: -9.82 };
|
|
51
|
+
this.mainCollisionGroup = 0;
|
|
49
52
|
this.maxSubSteps = 100;
|
|
50
53
|
this.fixedTimeStep = 0.01;
|
|
51
54
|
this.lockedCollisionGroups = [];
|
|
@@ -77,7 +80,7 @@ export class AmmoWorldComponent {
|
|
|
77
80
|
this.afterTick$.next();
|
|
78
81
|
}
|
|
79
82
|
registerCollisionGroup() {
|
|
80
|
-
for (let i =
|
|
83
|
+
for (let i = 1; i < 16; i++) {
|
|
81
84
|
if (!this.lockedCollisionGroups.includes(i)) {
|
|
82
85
|
this.lockedCollisionGroups.push(i);
|
|
83
86
|
return i;
|
|
@@ -88,6 +91,46 @@ export class AmmoWorldComponent {
|
|
|
88
91
|
deregisterCollisionGroup(group) {
|
|
89
92
|
this.lockedCollisionGroups = this.lockedCollisionGroups.filter(x => x !== group);
|
|
90
93
|
}
|
|
94
|
+
raycast(options) {
|
|
95
|
+
if (!this._dynamicAmmoWorld) {
|
|
96
|
+
return { hasHit: false };
|
|
97
|
+
}
|
|
98
|
+
const from = new Ammo.btVector3(options.from.x, options.from.y, options.from.z);
|
|
99
|
+
const to = new Ammo.btVector3(options.to.x, options.to.y, options.to.z);
|
|
100
|
+
const rayCallback = new Ammo.ClosestRayResultCallback(from, to);
|
|
101
|
+
if (options.collisionFilterGroups) {
|
|
102
|
+
rayCallback.set_m_collisionFilterGroup(BitMask.pack(options.collisionFilterGroups, 16));
|
|
103
|
+
}
|
|
104
|
+
if (options.collisionFilterMask !== undefined) {
|
|
105
|
+
rayCallback.set_m_collisionFilterMask(BitMask.pack(options.collisionFilterMask, 16));
|
|
106
|
+
}
|
|
107
|
+
this._dynamicAmmoWorld.rayTest(from, to, rayCallback);
|
|
108
|
+
const hasHit = rayCallback.hasHit();
|
|
109
|
+
const result = { hasHit };
|
|
110
|
+
if (hasHit) {
|
|
111
|
+
result.hitBody = AmmoBodyComponent.nativeBodyReverseMap.get(Ammo.getPointer(rayCallback.get_m_collisionObject()));
|
|
112
|
+
const hitPointAmmo = rayCallback.get_m_hitPointWorld();
|
|
113
|
+
result.hitPoint = {
|
|
114
|
+
x: hitPointAmmo.x(),
|
|
115
|
+
y: hitPointAmmo.y(),
|
|
116
|
+
z: hitPointAmmo.z(),
|
|
117
|
+
};
|
|
118
|
+
const hitNormalAmmo = rayCallback.get_m_hitNormalWorld();
|
|
119
|
+
result.hitNormal = {
|
|
120
|
+
x: hitNormalAmmo.x(),
|
|
121
|
+
y: hitNormalAmmo.y(),
|
|
122
|
+
z: hitNormalAmmo.z(),
|
|
123
|
+
};
|
|
124
|
+
const dx = result.hitPoint.x - options.from.x;
|
|
125
|
+
const dy = result.hitPoint.y - options.from.y;
|
|
126
|
+
const dz = result.hitPoint.z - options.from.z;
|
|
127
|
+
result.hitDistance = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
128
|
+
}
|
|
129
|
+
Ammo.destroy(from);
|
|
130
|
+
Ammo.destroy(to);
|
|
131
|
+
Ammo.destroy(rayCallback);
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
91
134
|
dispose() {
|
|
92
135
|
this.afterTick$.complete();
|
|
93
136
|
Ammo.destroy(this._dynamicAmmoWorld);
|
package/dist/types.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { AmmoTriggerComponent } from './components/ammo-trigger.component';
|
|
|
3
3
|
import { AmmoRaycastVehicleComponent } from './components/ammo-raycast-vehicle.component';
|
|
4
4
|
import { AmmoFactory } from './ammo-factory';
|
|
5
5
|
import { AmmoLoader } from './ammo-loader';
|
|
6
|
+
import { Gg3dWorld, Gg3dWorldSceneTypeDocPPatch, Gg3dWorldTypeDocPPatch } from '@gg-web-engine/core';
|
|
7
|
+
import { AmmoWorldComponent } from './components/ammo-world.component';
|
|
6
8
|
export type AmmoPhysicsTypeDocRepo = {
|
|
7
9
|
factory: AmmoFactory;
|
|
8
10
|
loader: AmmoLoader;
|
|
@@ -10,3 +12,4 @@ export type AmmoPhysicsTypeDocRepo = {
|
|
|
10
12
|
trigger: AmmoTriggerComponent;
|
|
11
13
|
raycastVehicle: AmmoRaycastVehicleComponent;
|
|
12
14
|
};
|
|
15
|
+
export type AmmoGgWorld = Gg3dWorld<Gg3dWorldTypeDocPPatch<AmmoPhysicsTypeDocRepo>, Gg3dWorldSceneTypeDocPPatch<AmmoPhysicsTypeDocRepo, AmmoWorldComponent>>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gg-web-engine/ammo",
|
|
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",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"dependencies": {
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@gg-web-engine/core": "0.0.
|
|
37
|
+
"@gg-web-engine/core": "0.0.58",
|
|
38
38
|
"@types/jest": "^29.5.12",
|
|
39
39
|
"jest": "^29.7.0",
|
|
40
40
|
"jest-environment-jsdom": "^29.7.0",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"typescript": "~5.5.4"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
|
-
"@gg-web-engine/core": "0.0.
|
|
48
|
+
"@gg-web-engine/core": "0.0.58",
|
|
49
49
|
"mini-signals": "2.0.0",
|
|
50
50
|
"rxjs": "7.8.1"
|
|
51
51
|
},
|
|
@@ -97,6 +97,7 @@ describe(`AmmoTriggerComponent`, () => {
|
|
|
97
97
|
trigger.onEntityLeft.subscribe(((obj) => {
|
|
98
98
|
exitRegistered = obj === ball;
|
|
99
99
|
}));
|
|
100
|
+
world.simulate(1);
|
|
100
101
|
trigger.checkOverlaps();
|
|
101
102
|
trigger.removeFromWorld({ physicsWorld: world } as any);
|
|
102
103
|
expect(exitRegistered).toBe(true);
|
|
@@ -114,8 +115,10 @@ describe(`AmmoTriggerComponent`, () => {
|
|
|
114
115
|
trigger.onEntityLeft.subscribe(((obj) => {
|
|
115
116
|
exitRegistered = obj === ball;
|
|
116
117
|
}));
|
|
118
|
+
world.simulate(1);
|
|
117
119
|
trigger.checkOverlaps();
|
|
118
120
|
ball.removeFromWorld({ physicsWorld: world } as any);
|
|
121
|
+
world.simulate(1);
|
|
119
122
|
trigger.checkOverlaps();
|
|
120
123
|
expect(exitRegistered).toBe(true);
|
|
121
124
|
});
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { Point3, RaycastOptions } from '@gg-web-engine/core';
|
|
2
|
+
import { AmmoWorldComponent } from '../../src';
|
|
3
|
+
|
|
4
|
+
describe('AmmoWorldComponent', () => {
|
|
5
|
+
let world: AmmoWorldComponent;
|
|
6
|
+
|
|
7
|
+
beforeEach(async () => {
|
|
8
|
+
if (world) {
|
|
9
|
+
world.dispose();
|
|
10
|
+
}
|
|
11
|
+
world = new AmmoWorldComponent();
|
|
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
|
+
});
|