@gg-web-engine/ammo 0.0.1 → 0.0.2
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/README.md +3 -4
- package/package.json +2 -2
- package/src/ammo-debugger.ts +0 -104
- package/src/ammo-utils.ts +0 -3
- package/src/impl/bodies/base-ammo-gg-body.ts +0 -76
- package/src/impl/bodies/gg-3d-body.ts +0 -48
- package/src/impl/bodies/gg-3d-trigger.ts +0 -82
- package/src/impl/gg-3d-body-factory.ts +0 -164
- package/src/impl/gg-3d-body-loader.ts +0 -8
- package/src/impl/gg-3d-physics-world.ts +0 -141
- package/src/impl/gg-3d-raycast-vehicle.ts +0 -97
- package/src/index.ts +0 -6
package/README.md
CHANGED
|
@@ -9,8 +9,7 @@
|
|
|
9
9
|
1) `npm install --save @gg-web-engine/ammo`
|
|
10
10
|
1) Add to your `tsconfig.json` in the record `compilerOptions.paths`:
|
|
11
11
|
```json lines
|
|
12
|
-
"mini-signals": ["./node_modules
|
|
13
|
-
"path": ["./node_modules
|
|
14
|
-
"fs": ["./node_modules
|
|
15
|
-
"ammojs-typed": ["./node_modules/@gg-web-engine/ammo/node_modules/ammojs-typed"]
|
|
12
|
+
"mini-signals": ["./node_modules/mini-signals/index.js"],
|
|
13
|
+
"path": ["./node_modules/path-browserify"],
|
|
14
|
+
"fs": ["./node_modules/fs-web"],
|
|
16
15
|
```
|
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.02",
|
|
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",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"homepage": "https://github.com/AndyGura/gg-web-engine#readme",
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@gg-web-engine/core": "^0.0.
|
|
30
|
+
"@gg-web-engine/core": "^0.0.2",
|
|
31
31
|
"ammojs-typed": "^1.0.6",
|
|
32
32
|
"fs-web": "^1.0.1",
|
|
33
33
|
"mini-signals": "^1.2.0",
|
package/src/ammo-debugger.ts
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import Ammo from 'ammojs-typed';
|
|
2
|
-
import { GgDebugPhysicsDrawer, Point3, Point4 } from '@gg-web-engine/core';
|
|
3
|
-
import { Gg3dPhysicsWorld } from './impl/gg-3d-physics-world';
|
|
4
|
-
|
|
5
|
-
export const DebugBufferSize = 3 * 1000000;
|
|
6
|
-
|
|
7
|
-
export enum AmmoDebugMode {
|
|
8
|
-
NoDebug = 0,
|
|
9
|
-
DrawWireframe = 1,
|
|
10
|
-
DrawAabb = 2,
|
|
11
|
-
DrawFeaturesText = 4,
|
|
12
|
-
DrawContactPoints = 8,
|
|
13
|
-
NoDeactivation = 16,
|
|
14
|
-
NoHelpText = 32,
|
|
15
|
-
DrawText = 64,
|
|
16
|
-
ProfileTimings = 128,
|
|
17
|
-
EnableSatComparison = 256,
|
|
18
|
-
DisableBulletLCP = 512,
|
|
19
|
-
EnableCCD = 1024,
|
|
20
|
-
DrawConstraints = 1 << 11, //2048
|
|
21
|
-
DrawConstraintLimits = 1 << 12, //4096
|
|
22
|
-
FastWireframe = 1 << 13, //8192
|
|
23
|
-
DrawNormals = 1 << 14, //16384
|
|
24
|
-
MAX_DEBUG_DRAW_MODE = 0xffffffff,
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const deserializeVector = function (ammo: typeof Ammo, vec: Ammo.btVector3): Point3 {
|
|
28
|
-
if (!isNaN(+vec)) {
|
|
29
|
-
const heap = ammo.HEAPF32;
|
|
30
|
-
return {
|
|
31
|
-
x: heap[+vec / 4],
|
|
32
|
-
y: heap[(+vec + 4) / 4],
|
|
33
|
-
z: heap[(+vec + 8) / 4],
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
return { x: vec.x(), y: vec.y(), z: vec.z() };
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
export class AmmoDebugger implements Ammo.btIDebugDraw {
|
|
40
|
-
private debugMode: AmmoDebugMode = AmmoDebugMode.DrawWireframe;
|
|
41
|
-
|
|
42
|
-
constructor(private readonly world: Gg3dPhysicsWorld, private readonly drawer: GgDebugPhysicsDrawer<Point3, Point4>) {
|
|
43
|
-
this.ammoInstance = new this.world.ammo.DebugDrawer();
|
|
44
|
-
this.ammoInstance.drawLine = this.drawLine.bind(this);
|
|
45
|
-
this.ammoInstance.drawContactPoint = this.drawContactPoint.bind(this);
|
|
46
|
-
this.ammoInstance.reportErrorWarning = this.reportErrorWarning.bind(this);
|
|
47
|
-
this.ammoInstance.draw3dText = this.draw3dText.bind(this);
|
|
48
|
-
this.ammoInstance.setDebugMode = this.setDebugMode.bind(this);
|
|
49
|
-
this.ammoInstance.getDebugMode = this.getDebugMode.bind(this);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
public readonly ammoInstance: Ammo.btIDebugDraw;
|
|
53
|
-
|
|
54
|
-
draw3dText(location: Ammo.btVector3, textString: string): void {
|
|
55
|
-
console.log('DRAWER DEBUG', 'draw3dtext');
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
drawContactPoint(
|
|
59
|
-
pointOnB: Ammo.btVector3,
|
|
60
|
-
normalOnB: Ammo.btVector3,
|
|
61
|
-
distance: number,
|
|
62
|
-
lifeTime: number,
|
|
63
|
-
color: Ammo.btVector3,
|
|
64
|
-
): void {
|
|
65
|
-
this.drawer.drawContactPoint(
|
|
66
|
-
deserializeVector(this.world.ammo, pointOnB),
|
|
67
|
-
deserializeVector(this.world.ammo, normalOnB),
|
|
68
|
-
deserializeVector(this.world.ammo, color),
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
drawLine(from: Ammo.btVector3, to: Ammo.btVector3, color: Ammo.btVector3): void {
|
|
73
|
-
this.drawer.drawLine(
|
|
74
|
-
deserializeVector(this.world.ammo, from),
|
|
75
|
-
deserializeVector(this.world.ammo, to),
|
|
76
|
-
deserializeVector(this.world.ammo, color),
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
getDebugMode(): number {
|
|
81
|
-
return this.debugMode;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
update(): void {
|
|
85
|
-
this.world.dynamicAmmoWorld?.debugDrawWorld();
|
|
86
|
-
this.drawer.update();
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
setDebugFlags(flags: AmmoDebugMode[]): void {
|
|
90
|
-
let res = 0;
|
|
91
|
-
for (const flag of flags) {
|
|
92
|
-
res = res | flag;
|
|
93
|
-
}
|
|
94
|
-
this.setDebugMode(res);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
setDebugMode(debugMode: number): void {
|
|
98
|
-
this.debugMode = debugMode;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
reportErrorWarning(warningString: string): void {
|
|
102
|
-
console.log('DRAWER DEBUG', 'reportErrorWarning', warningString);
|
|
103
|
-
}
|
|
104
|
-
}
|
package/src/ammo-utils.ts
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { IGg3dBody, Point3, Point4, Gg3dEntity } from '@gg-web-engine/core';
|
|
2
|
-
import { Gg3dPhysicsWorld } from '../gg-3d-physics-world';
|
|
3
|
-
import Ammo from 'ammojs-typed';
|
|
4
|
-
import { ammoId } from '../../ammo-utils';
|
|
5
|
-
|
|
6
|
-
export abstract class BaseAmmoGGBody<T extends Ammo.btCollisionObject> implements IGg3dBody {
|
|
7
|
-
protected static nativeBodyReverseMap: Map<number, IGg3dBody> = new Map<number, IGg3dBody>();
|
|
8
|
-
|
|
9
|
-
protected get ammo(): typeof Ammo {
|
|
10
|
-
return this.world.ammo;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
public get position(): Point3 {
|
|
14
|
-
const origin = this.nativeBody.getWorldTransform().getOrigin();
|
|
15
|
-
return { x: origin.x(), y: origin.y(), z: origin.z() };
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
public set position(value: Point3) {
|
|
19
|
-
const transform = this.nativeBody.getWorldTransform();
|
|
20
|
-
transform.setOrigin(new this.ammo.btVector3(value.x, value.y, value.z));
|
|
21
|
-
this.nativeBody.setWorldTransform(transform);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
public get rotation(): Point4 {
|
|
25
|
-
const quaternion = this.nativeBody.getWorldTransform().getRotation();
|
|
26
|
-
return { x: quaternion.x(), y: quaternion.y(), z: quaternion.z(), w: quaternion.w() };
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
public set rotation(value: Point4) {
|
|
30
|
-
const transform = this.nativeBody.getWorldTransform();
|
|
31
|
-
transform.setRotation(new this.ammo.btQuaternion(value.x, value.y, value.z, value.w));
|
|
32
|
-
this.nativeBody.setWorldTransform(transform);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
public get scale(): Point3 {
|
|
36
|
-
// hmm, is it even possible to be different?
|
|
37
|
-
return { x: 1, y: 1, z: 1 };
|
|
38
|
-
}
|
|
39
|
-
get nativeBody(): T {
|
|
40
|
-
return this._nativeBody;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
set nativeBody(value: T) {
|
|
44
|
-
if (value == this._nativeBody) {
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
BaseAmmoGGBody.nativeBodyReverseMap.delete(ammoId(this._nativeBody));
|
|
48
|
-
this._nativeBody = value;
|
|
49
|
-
BaseAmmoGGBody.nativeBodyReverseMap.set(ammoId(value), this);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
public name: string = '';
|
|
53
|
-
|
|
54
|
-
abstract entity: Gg3dEntity | null;
|
|
55
|
-
|
|
56
|
-
protected constructor(protected readonly world: Gg3dPhysicsWorld, protected _nativeBody: T) {
|
|
57
|
-
BaseAmmoGGBody.nativeBodyReverseMap.set(ammoId(this.nativeBody), this);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
abstract clone(): BaseAmmoGGBody<T>;
|
|
61
|
-
|
|
62
|
-
abstract addToWorld(world: Gg3dPhysicsWorld): void;
|
|
63
|
-
|
|
64
|
-
abstract removeFromWorld(world: Gg3dPhysicsWorld): void;
|
|
65
|
-
|
|
66
|
-
dispose(): void {
|
|
67
|
-
try {
|
|
68
|
-
this.ammo.destroy(this.nativeBody);
|
|
69
|
-
} catch {
|
|
70
|
-
// pass
|
|
71
|
-
}
|
|
72
|
-
BaseAmmoGGBody.nativeBodyReverseMap.delete(ammoId(this.nativeBody));
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
abstract resetMotion(): void;
|
|
76
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { Gg3dPhysicsWorld } from '../gg-3d-physics-world';
|
|
2
|
-
import Ammo from 'ammojs-typed';
|
|
3
|
-
import { BaseAmmoGGBody } from './base-ammo-gg-body';
|
|
4
|
-
import { Gg3dEntity } from '@gg-web-engine/core';
|
|
5
|
-
|
|
6
|
-
export class Gg3dBody extends BaseAmmoGGBody<Ammo.btRigidBody> {
|
|
7
|
-
public entity: Gg3dEntity | null = null;
|
|
8
|
-
|
|
9
|
-
constructor(protected readonly world: Gg3dPhysicsWorld, protected _nativeBody: Ammo.btRigidBody) {
|
|
10
|
-
super(world, _nativeBody);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
clone(): Gg3dBody {
|
|
14
|
-
return this.world.factory.createRigidBodyFromShape(
|
|
15
|
-
this._nativeBody.getCollisionShape(),
|
|
16
|
-
{
|
|
17
|
-
dynamic: !this._nativeBody.isStaticObject(),
|
|
18
|
-
mass: 5, // FIXME how to get mass??
|
|
19
|
-
friction: this._nativeBody.getFriction(),
|
|
20
|
-
restitution: this._nativeBody.getRestitution(),
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
position: this.position,
|
|
24
|
-
rotation: this.rotation,
|
|
25
|
-
},
|
|
26
|
-
);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
addToWorld(world: Gg3dPhysicsWorld): void {
|
|
30
|
-
if (world != this.world) {
|
|
31
|
-
throw new Error('Ammo bodies cannot be shared between different worlds');
|
|
32
|
-
}
|
|
33
|
-
world.dynamicAmmoWorld?.addRigidBody(this.nativeBody);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
removeFromWorld(world: Gg3dPhysicsWorld): void {
|
|
37
|
-
world.dynamicAmmoWorld?.removeRigidBody(this.nativeBody);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
resetMotion(): void {
|
|
41
|
-
const emptyVector = new this.ammo.btVector3();
|
|
42
|
-
this.nativeBody.setLinearVelocity(emptyVector);
|
|
43
|
-
this.nativeBody.setAngularVelocity(emptyVector);
|
|
44
|
-
this.nativeBody.clearForces();
|
|
45
|
-
this.nativeBody.updateInertiaTensor();
|
|
46
|
-
this.ammo.destroy(emptyVector);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { Gg3dTriggerEntity, GgPositionable3dEntity, IGg3dTrigger } from '@gg-web-engine/core';
|
|
2
|
-
import { Gg3dPhysicsWorld } from '../gg-3d-physics-world';
|
|
3
|
-
import { filter, map, Observable, Subject } from 'rxjs';
|
|
4
|
-
import Ammo from 'ammojs-typed';
|
|
5
|
-
import { BaseAmmoGGBody } from './base-ammo-gg-body';
|
|
6
|
-
import { ammoId } from '../../ammo-utils';
|
|
7
|
-
|
|
8
|
-
export class Gg3dTrigger extends BaseAmmoGGBody<Ammo.btPairCachingGhostObject> implements IGg3dTrigger {
|
|
9
|
-
public entity: Gg3dTriggerEntity | null = null;
|
|
10
|
-
|
|
11
|
-
get onEntityEntered(): Observable<GgPositionable3dEntity> {
|
|
12
|
-
return this.onEnter$.pipe(
|
|
13
|
-
map(b => BaseAmmoGGBody.nativeBodyReverseMap.get(b)?.entity),
|
|
14
|
-
filter(x => !!x && x instanceof GgPositionable3dEntity),
|
|
15
|
-
) as Observable<GgPositionable3dEntity>;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
get onEntityLeft(): Observable<GgPositionable3dEntity | null> {
|
|
19
|
-
return this.onLeft$.pipe(
|
|
20
|
-
map(b => BaseAmmoGGBody.nativeBodyReverseMap.get(b)?.entity || null),
|
|
21
|
-
) as Observable<GgPositionable3dEntity | null>;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
constructor(protected readonly world: Gg3dPhysicsWorld, protected _nativeBody: Ammo.btPairCachingGhostObject) {
|
|
25
|
-
super(world, _nativeBody);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
private readonly onEnter$: Subject<number> = new Subject<number>();
|
|
29
|
-
private readonly onLeft$: Subject<number> = new Subject<number>();
|
|
30
|
-
private readonly overlaps: Set<Ammo.btCollisionObject> = new Set<Ammo.btCollisionObject>();
|
|
31
|
-
|
|
32
|
-
checkOverlaps(): void {
|
|
33
|
-
const numOverlappingObjects = this.nativeBody.getNumOverlappingObjects();
|
|
34
|
-
const newOverlaps = new Set(
|
|
35
|
-
new Array(numOverlappingObjects).fill(null).map((_, i) => this.nativeBody.getOverlappingObject(i)),
|
|
36
|
-
);
|
|
37
|
-
for (const overlap of this.overlaps.keys()) {
|
|
38
|
-
if (!newOverlaps.has(overlap)) {
|
|
39
|
-
this.overlaps.delete(overlap);
|
|
40
|
-
this.onLeft$.next(ammoId(overlap));
|
|
41
|
-
} else {
|
|
42
|
-
newOverlaps.delete(overlap);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
for (const newOverlap of newOverlaps) {
|
|
46
|
-
this.overlaps.add(newOverlap);
|
|
47
|
-
this.onEnter$.next(ammoId(newOverlap));
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
clone(): Gg3dTrigger {
|
|
52
|
-
return this.world.factory.createTriggerFromShape(this._nativeBody.getCollisionShape(), {
|
|
53
|
-
position: this.position,
|
|
54
|
-
rotation: this.rotation,
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
addToWorld(world: Gg3dPhysicsWorld) {
|
|
59
|
-
if (world != this.world) {
|
|
60
|
-
throw new Error('Ammo triggers cannot be shared between different worlds');
|
|
61
|
-
}
|
|
62
|
-
world.dynamicAmmoWorld?.addCollisionObject(this.nativeBody);
|
|
63
|
-
this.overlaps.clear();
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
removeFromWorld(world: Gg3dPhysicsWorld): void {
|
|
67
|
-
for (const body of this.overlaps) {
|
|
68
|
-
this.onLeft$.next(ammoId(body));
|
|
69
|
-
}
|
|
70
|
-
this.overlaps.clear();
|
|
71
|
-
world.dynamicAmmoWorld?.removeCollisionObject(this.nativeBody);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
dispose(): void {
|
|
75
|
-
super.dispose();
|
|
76
|
-
this.overlaps.clear();
|
|
77
|
-
this.onEnter$.complete();
|
|
78
|
-
this.onLeft$.complete();
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
resetMotion(): void {}
|
|
82
|
-
}
|
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Body3DOptions,
|
|
3
|
-
BodyShape3DDescriptor,
|
|
4
|
-
IGg3dBodyFactory,
|
|
5
|
-
Point3,
|
|
6
|
-
Point4,
|
|
7
|
-
Shape3DDescriptor,
|
|
8
|
-
} from '@gg-web-engine/core';
|
|
9
|
-
import { Gg3dBody } from './bodies/gg-3d-body';
|
|
10
|
-
import { Gg3dPhysicsWorld } from './gg-3d-physics-world';
|
|
11
|
-
import Ammo from 'ammojs-typed';
|
|
12
|
-
import { Gg3dTrigger } from './bodies/gg-3d-trigger';
|
|
13
|
-
|
|
14
|
-
export class Gg3dBodyFactory implements IGg3dBodyFactory<Gg3dBody, Gg3dTrigger> {
|
|
15
|
-
constructor(private readonly world: Gg3dPhysicsWorld) {}
|
|
16
|
-
|
|
17
|
-
createRigidBody(
|
|
18
|
-
descriptor: BodyShape3DDescriptor,
|
|
19
|
-
transform?: {
|
|
20
|
-
position?: Point3;
|
|
21
|
-
rotation?: Point4;
|
|
22
|
-
},
|
|
23
|
-
): Gg3dBody {
|
|
24
|
-
return this.createRigidBodyFromShape(this.createShape(descriptor.shape), descriptor.body, transform);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
createTrigger(
|
|
28
|
-
descriptor: Shape3DDescriptor,
|
|
29
|
-
transform?: {
|
|
30
|
-
position?: Point3;
|
|
31
|
-
rotation?: Point4;
|
|
32
|
-
},
|
|
33
|
-
): Gg3dTrigger {
|
|
34
|
-
return this.createTriggerFromShape(this.createShape(descriptor), transform);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
private createShape(descriptor: Shape3DDescriptor): Ammo.btCollisionShape {
|
|
38
|
-
switch (descriptor.shape) {
|
|
39
|
-
case 'BOX':
|
|
40
|
-
return new this.world.ammo.btBoxShape(
|
|
41
|
-
new this.world.ammo.btVector3(
|
|
42
|
-
descriptor.dimensions.x / 2,
|
|
43
|
-
descriptor.dimensions.y / 2,
|
|
44
|
-
descriptor.dimensions.z / 2,
|
|
45
|
-
),
|
|
46
|
-
);
|
|
47
|
-
case 'CAPSULE':
|
|
48
|
-
return new this.world.ammo.btCapsuleShapeZ(descriptor.radius, descriptor.centersDistance);
|
|
49
|
-
case 'CYLINDER':
|
|
50
|
-
return new this.world.ammo.btCylinderShapeZ(
|
|
51
|
-
new this.world.ammo.btVector3(descriptor.radius, descriptor.radius, descriptor.height / 2),
|
|
52
|
-
);
|
|
53
|
-
case 'CONE':
|
|
54
|
-
return new this.world.ammo.btConeShapeZ(descriptor.radius, descriptor.height);
|
|
55
|
-
case 'SPHERE':
|
|
56
|
-
return new this.world.ammo.btSphereShape(descriptor.radius);
|
|
57
|
-
case 'COMPOUND':
|
|
58
|
-
const compoundShape: Ammo.btCollisionShape = new this.world.ammo.btCompoundShape();
|
|
59
|
-
for (const item of descriptor.children) {
|
|
60
|
-
const subShape = this.createShape(item.shape);
|
|
61
|
-
if (!subShape) {
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
const subShapeTransform = new this.world.ammo.btTransform();
|
|
65
|
-
const pos = item.position || { x: 0, y: 0, z: 0 };
|
|
66
|
-
const rot = item.rotation || { x: 0, y: 0, z: 0, w: 1 };
|
|
67
|
-
subShapeTransform.setOrigin(new this.world.ammo.btVector3(pos.x, pos.y, pos.z));
|
|
68
|
-
subShapeTransform.setRotation(new this.world.ammo.btQuaternion(rot.x, rot.y, rot.z, rot.w));
|
|
69
|
-
(compoundShape as Ammo.btCompoundShape).addChildShape(subShapeTransform, subShape);
|
|
70
|
-
}
|
|
71
|
-
return compoundShape;
|
|
72
|
-
case 'CONVEX_HULL':
|
|
73
|
-
const shape = new this.world.ammo.btConvexHullShape();
|
|
74
|
-
const tmpVector = new this.world.ammo.btVector3();
|
|
75
|
-
for (const v of descriptor.vertices) {
|
|
76
|
-
tmpVector.setValue(v.x, v.y, v.z);
|
|
77
|
-
shape.addPoint(tmpVector);
|
|
78
|
-
}
|
|
79
|
-
this.world.ammo.destroy(tmpVector);
|
|
80
|
-
shape.recalcLocalAabb();
|
|
81
|
-
return shape;
|
|
82
|
-
case 'MESH':
|
|
83
|
-
const mesh = new this.world.ammo.btTriangleMesh(true, true);
|
|
84
|
-
const tmpVectors: [Ammo.btVector3, Ammo.btVector3, Ammo.btVector3] = [
|
|
85
|
-
new this.world.ammo.btVector3(),
|
|
86
|
-
new this.world.ammo.btVector3(),
|
|
87
|
-
new this.world.ammo.btVector3(),
|
|
88
|
-
];
|
|
89
|
-
for (const f of descriptor.faces) {
|
|
90
|
-
for (let j = 0; j < 3; j++) {
|
|
91
|
-
tmpVectors[j].setValue(
|
|
92
|
-
descriptor.vertices[f[0]].x,
|
|
93
|
-
descriptor.vertices[f[1]].y,
|
|
94
|
-
descriptor.vertices[f[2]].z,
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
mesh.addTriangle(...tmpVectors, true);
|
|
98
|
-
}
|
|
99
|
-
tmpVectors.forEach(v => {
|
|
100
|
-
this.world.ammo.destroy(v);
|
|
101
|
-
});
|
|
102
|
-
return new this.world.ammo.btBvhTriangleMeshShape(mesh, false, true);
|
|
103
|
-
}
|
|
104
|
-
throw new Error(`Shape "${(descriptor as any).shape}" not implemented for Ammo.js`);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
public createRigidBodyFromShape(
|
|
108
|
-
shape: Ammo.btCollisionShape,
|
|
109
|
-
options: Partial<Body3DOptions>,
|
|
110
|
-
transform?: { position?: Point3; rotation?: Point4 },
|
|
111
|
-
): Gg3dBody {
|
|
112
|
-
if (options.dynamic === false) {
|
|
113
|
-
options.mass = 0;
|
|
114
|
-
}
|
|
115
|
-
const pos = transform?.position || { x: 0, y: 0, z: 0 };
|
|
116
|
-
const rot = transform?.rotation || { x: 0, y: 0, z: 0, w: 1 };
|
|
117
|
-
const ammo = this.world.ammo;
|
|
118
|
-
const ammoTransform = new ammo.btTransform();
|
|
119
|
-
ammoTransform.setOrigin(new ammo.btVector3(pos.x, pos.y, pos.z));
|
|
120
|
-
ammoTransform.setRotation(new ammo.btQuaternion(rot.x, rot.y, rot.z, rot.w));
|
|
121
|
-
const motionState = new ammo.btDefaultMotionState(ammoTransform);
|
|
122
|
-
const localInertia = new ammo.btVector3(0, 0, 0);
|
|
123
|
-
shape.calculateLocalInertia(options.mass || 0, localInertia);
|
|
124
|
-
const environmentBodyCI = new ammo.btRigidBodyConstructionInfo(options.mass || 0, motionState, shape, localInertia);
|
|
125
|
-
if (options.friction) {
|
|
126
|
-
environmentBodyCI.set_m_friction(options.friction);
|
|
127
|
-
environmentBodyCI.set_m_rollingFriction(options.friction);
|
|
128
|
-
}
|
|
129
|
-
if (options.restitution) {
|
|
130
|
-
environmentBodyCI.set_m_restitution(options.restitution);
|
|
131
|
-
}
|
|
132
|
-
const body = new this.world.ammo.btRigidBody(environmentBodyCI);
|
|
133
|
-
return new Gg3dBody(this.world, body);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
public createTriggerFromShape(
|
|
137
|
-
shape: Ammo.btCollisionShape,
|
|
138
|
-
transform?: { position?: Point3; rotation?: Point4 },
|
|
139
|
-
): Gg3dTrigger {
|
|
140
|
-
const ghostObject = new this.world.ammo.btPairCachingGhostObject();
|
|
141
|
-
ghostObject.setCollisionShape(shape);
|
|
142
|
-
ghostObject.setCollisionFlags(ghostObject.getCollisionFlags() | 4); // 4 is a CF_NO_CONTACT_RESPONSE collision flag
|
|
143
|
-
ghostObject
|
|
144
|
-
.getWorldTransform()
|
|
145
|
-
.setOrigin(
|
|
146
|
-
new this.world.ammo.btVector3(
|
|
147
|
-
transform?.position?.x || 0,
|
|
148
|
-
transform?.position?.y || 0,
|
|
149
|
-
transform?.position?.z || 0,
|
|
150
|
-
),
|
|
151
|
-
);
|
|
152
|
-
ghostObject
|
|
153
|
-
.getWorldTransform()
|
|
154
|
-
.setRotation(
|
|
155
|
-
new this.world.ammo.btQuaternion(
|
|
156
|
-
transform?.rotation?.x || 0,
|
|
157
|
-
transform?.rotation?.y || 0,
|
|
158
|
-
transform?.rotation?.z || 0,
|
|
159
|
-
transform?.rotation?.w || 1,
|
|
160
|
-
),
|
|
161
|
-
);
|
|
162
|
-
return new Gg3dTrigger(this.world, ghostObject);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import { Gg3dWorld, GgDebugPhysicsDrawer, IGg3dPhysicsWorld, Point3, Point4 } from '@gg-web-engine/core';
|
|
2
|
-
import Ammo, * as AmmoModule from 'ammojs-typed';
|
|
3
|
-
import { Gg3dBodyFactory } from './gg-3d-body-factory';
|
|
4
|
-
import { Gg3dBodyLoader } from './gg-3d-body-loader';
|
|
5
|
-
import { AmmoDebugger, AmmoDebugMode } from '../ammo-debugger';
|
|
6
|
-
|
|
7
|
-
export class Gg3dPhysicsWorld implements IGg3dPhysicsWorld {
|
|
8
|
-
private _factory: Gg3dBodyFactory | null = null;
|
|
9
|
-
public get factory(): Gg3dBodyFactory {
|
|
10
|
-
if (!this._factory) {
|
|
11
|
-
throw new Error('Ammo world not initialized');
|
|
12
|
-
}
|
|
13
|
-
return this._factory;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
private _loader: Gg3dBodyLoader | null = null;
|
|
17
|
-
public get loader(): Gg3dBodyLoader {
|
|
18
|
-
if (!this._loader) {
|
|
19
|
-
throw new Error('Ammo world not initialized');
|
|
20
|
-
}
|
|
21
|
-
return this._loader;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
private _gravity: Point3 = { x: 0, y: 0, z: -9.82 };
|
|
25
|
-
public get gravity(): Point3 {
|
|
26
|
-
return this._gravity;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
public set gravity(value: Point3) {
|
|
30
|
-
this._gravity = value;
|
|
31
|
-
if (this.gravityVector) {
|
|
32
|
-
this.gravityVector.setValue(value.x, value.y, value.z);
|
|
33
|
-
this._dynamicAmmoWorld?.setGravity(this.gravityVector);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
private _timeScale: number = 1;
|
|
38
|
-
public get timeScale(): number {
|
|
39
|
-
return this._timeScale;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
public set timeScale(value: number) {
|
|
43
|
-
this._timeScale = value;
|
|
44
|
-
}
|
|
45
|
-
private _debugger: AmmoDebugger | null = null;
|
|
46
|
-
private _debugDrawer: GgDebugPhysicsDrawer<Point3, Point4> | null = null;
|
|
47
|
-
|
|
48
|
-
get physicsDebugViewActive(): boolean {
|
|
49
|
-
return !!this._debugger;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
private ammoInstance: typeof Ammo | undefined;
|
|
53
|
-
|
|
54
|
-
public get ammo(): typeof Ammo {
|
|
55
|
-
if (this.ammoInstance) {
|
|
56
|
-
return this.ammoInstance;
|
|
57
|
-
}
|
|
58
|
-
throw 'Physics world not initialized!';
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
public get dynamicAmmoWorld(): Ammo.btDiscreteDynamicsWorld | undefined {
|
|
62
|
-
return this._dynamicAmmoWorld;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
private collisionConfiguration: Ammo.btDefaultCollisionConfiguration | undefined;
|
|
66
|
-
private dispatcher: Ammo.btCollisionDispatcher | undefined;
|
|
67
|
-
private ghostPairCallback: Ammo.btGhostPairCallback | undefined;
|
|
68
|
-
private broadphase: Ammo.btBroadphaseInterface | undefined;
|
|
69
|
-
private solver: Ammo.btSequentialImpulseConstraintSolver | undefined;
|
|
70
|
-
private gravityVector: Ammo.btVector3 | undefined;
|
|
71
|
-
private _dynamicAmmoWorld: Ammo.btDiscreteDynamicsWorld | undefined;
|
|
72
|
-
|
|
73
|
-
async init(): Promise<void> {
|
|
74
|
-
this.ammoInstance = await AmmoModule.default.bind(AmmoModule)();
|
|
75
|
-
this.collisionConfiguration = new this.ammo.btDefaultCollisionConfiguration();
|
|
76
|
-
this.dispatcher = new this.ammo.btCollisionDispatcher(this.collisionConfiguration);
|
|
77
|
-
this.broadphase = new this.ammo.btDbvtBroadphase();
|
|
78
|
-
this.ghostPairCallback = new this.ammo.btGhostPairCallback();
|
|
79
|
-
this.solver = new this.ammo.btSequentialImpulseConstraintSolver();
|
|
80
|
-
this.gravityVector = new this.ammo.btVector3(this._gravity.x, this._gravity.y, this._gravity.z);
|
|
81
|
-
|
|
82
|
-
this._dynamicAmmoWorld = new this.ammo.btDiscreteDynamicsWorld(
|
|
83
|
-
this.dispatcher,
|
|
84
|
-
this.broadphase,
|
|
85
|
-
this.solver,
|
|
86
|
-
this.collisionConfiguration,
|
|
87
|
-
);
|
|
88
|
-
this._dynamicAmmoWorld.getPairCache().setInternalGhostPairCallback(this.ghostPairCallback);
|
|
89
|
-
this._dynamicAmmoWorld.setGravity(this.gravityVector);
|
|
90
|
-
this._factory = new Gg3dBodyFactory(this);
|
|
91
|
-
this._loader = new Gg3dBodyLoader(this);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
simulate(delta: number): void {
|
|
95
|
-
this._dynamicAmmoWorld?.stepSimulation((this._timeScale * delta) / 1000, 100, this._timeScale * 0.01);
|
|
96
|
-
if (this._debugger) {
|
|
97
|
-
this._debugger.update();
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
startDebugger(world: Gg3dWorld, drawer: GgDebugPhysicsDrawer<Point3, Point4>): void {
|
|
102
|
-
if (!this._debugger) {
|
|
103
|
-
this._debugger = new AmmoDebugger(this, drawer);
|
|
104
|
-
this.dynamicAmmoWorld?.setDebugDrawer(this._debugger.ammoInstance);
|
|
105
|
-
}
|
|
106
|
-
this._debugger!.setDebugMode(AmmoDebugMode.DrawWireframe);
|
|
107
|
-
this._debugDrawer = drawer;
|
|
108
|
-
this._debugDrawer.addToWorld(world.visualScene);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
stopDebugger(world: Gg3dWorld): void {
|
|
112
|
-
if (this._debugger) {
|
|
113
|
-
this.dynamicAmmoWorld?.setDebugDrawer(null!);
|
|
114
|
-
this.ammo.destroy(this._debugger.ammoInstance);
|
|
115
|
-
this._debugger = null;
|
|
116
|
-
}
|
|
117
|
-
if (this._debugDrawer) {
|
|
118
|
-
this._debugDrawer!.removeFromWorld(world.visualScene);
|
|
119
|
-
this._debugDrawer!.dispose();
|
|
120
|
-
this._debugDrawer = null;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
dispose(): void {
|
|
125
|
-
this.ammo.destroy(this._dynamicAmmoWorld);
|
|
126
|
-
this.ammo.destroy(this.solver);
|
|
127
|
-
this.ammo.destroy(this.broadphase);
|
|
128
|
-
this.ammo.destroy(this.dispatcher);
|
|
129
|
-
this.ammo.destroy(this.collisionConfiguration);
|
|
130
|
-
if (this._debugger) {
|
|
131
|
-
this.ammo.destroy(this._debugger.ammoInstance);
|
|
132
|
-
}
|
|
133
|
-
this._dynamicAmmoWorld =
|
|
134
|
-
this.solver =
|
|
135
|
-
this.broadphase =
|
|
136
|
-
this.dispatcher =
|
|
137
|
-
this.collisionConfiguration =
|
|
138
|
-
this.ammoInstance =
|
|
139
|
-
undefined;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
IGg3dRaycastVehicle,
|
|
3
|
-
WheelOptions,
|
|
4
|
-
SuspensionOptions,
|
|
5
|
-
Point3,
|
|
6
|
-
Point4,
|
|
7
|
-
Gg3dRaycastVehicleEntity,
|
|
8
|
-
} from '@gg-web-engine/core';
|
|
9
|
-
import Ammo from 'ammojs-typed';
|
|
10
|
-
import { Gg3dBody } from './bodies/gg-3d-body';
|
|
11
|
-
import { Gg3dPhysicsWorld } from './gg-3d-physics-world';
|
|
12
|
-
export class Gg3dRaycastVehicle extends Gg3dBody implements IGg3dRaycastVehicle {
|
|
13
|
-
public readonly nativeVehicle: Ammo.btRaycastVehicle;
|
|
14
|
-
public readonly vehicleTuning: Ammo.btVehicleTuning = new this.ammo.btVehicleTuning();
|
|
15
|
-
protected readonly wheelDirectionCS0: Ammo.btVector3 = new this.ammo.btVector3(0, 0, -1);
|
|
16
|
-
protected readonly wheelAxleCS: Ammo.btVector3 = new this.ammo.btVector3(1, 0, 0);
|
|
17
|
-
|
|
18
|
-
public entity: Gg3dRaycastVehicleEntity | null = null;
|
|
19
|
-
|
|
20
|
-
constructor(protected readonly world: Gg3dPhysicsWorld, public chassisBody: Ammo.btRigidBody) {
|
|
21
|
-
super(world, chassisBody);
|
|
22
|
-
this.nativeVehicle = new this.ammo.btRaycastVehicle(
|
|
23
|
-
this.vehicleTuning,
|
|
24
|
-
this.chassisBody,
|
|
25
|
-
new this.ammo.btDefaultVehicleRaycaster(world.dynamicAmmoWorld!),
|
|
26
|
-
);
|
|
27
|
-
this.nativeVehicle.setCoordinateSystem(0, 2, 1);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
get wheelSpeed(): number {
|
|
31
|
-
// FIXME not correct (shows chassis speed in world)
|
|
32
|
-
return this.nativeVehicle.getCurrentSpeedKmHour() / 3.6;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
addToWorld(world: Gg3dPhysicsWorld) {
|
|
36
|
-
if (world != this.world) {
|
|
37
|
-
throw new Error('Ammo raycast vehicle cannot be shared between different worlds');
|
|
38
|
-
}
|
|
39
|
-
// TODO parked cars can be deactivated until we start handling them. Needs explicit activation call
|
|
40
|
-
this.chassisBody.setActivationState(4); // btCollisionObject::DISABLE_DEACTIVATION
|
|
41
|
-
world.dynamicAmmoWorld?.addRigidBody(this.chassisBody);
|
|
42
|
-
this.world.dynamicAmmoWorld!.addAction(this.nativeVehicle);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
addWheel(options: WheelOptions, suspensionOptions: SuspensionOptions): void {
|
|
46
|
-
const wheelInfo = this.nativeVehicle.addWheel(
|
|
47
|
-
new this.ammo.btVector3(options.position.x, options.position.y, options.position.z * 2),
|
|
48
|
-
this.wheelDirectionCS0,
|
|
49
|
-
this.wheelAxleCS,
|
|
50
|
-
suspensionOptions.restLength,
|
|
51
|
-
options.tyre_radius,
|
|
52
|
-
this.vehicleTuning,
|
|
53
|
-
options.isFront,
|
|
54
|
-
);
|
|
55
|
-
wheelInfo.set_m_suspensionStiffness(suspensionOptions.stiffness);
|
|
56
|
-
wheelInfo.set_m_wheelsDampingRelaxation(suspensionOptions.damping);
|
|
57
|
-
wheelInfo.set_m_wheelsDampingCompression(suspensionOptions.compression);
|
|
58
|
-
wheelInfo.set_m_frictionSlip(options.frictionSlip);
|
|
59
|
-
wheelInfo.set_m_rollInfluence(options.rollInfluence);
|
|
60
|
-
wheelInfo.set_m_maxSuspensionTravelCm(options.maxTravel * 100);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
setSteering(wheelIndex: number, steering: number): void {
|
|
64
|
-
this.nativeVehicle.setSteeringValue(steering, wheelIndex);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
applyEngineForce(wheelIndex: number, force: number): void {
|
|
68
|
-
this.nativeVehicle.applyEngineForce(force, wheelIndex);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
applyBrake(wheelIndex: number, force: number): void {
|
|
72
|
-
this.nativeVehicle.setBrake(force, wheelIndex);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
isWheelTouchesGround(wheelIndex: number): boolean {
|
|
76
|
-
return this.nativeVehicle.getWheelInfo(wheelIndex).get_m_raycastInfo().get_m_groundObject() > 0;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
getWheelTransform(wheelIndex: number): { position: Point3; rotation: Point4 } {
|
|
80
|
-
// FIXME when set to `true`, wheels are jittering, but it was not the case in old sandbox app. Check why
|
|
81
|
-
this.nativeVehicle.updateWheelTransform(wheelIndex, false);
|
|
82
|
-
const transform = this.nativeVehicle.getWheelTransformWS(wheelIndex);
|
|
83
|
-
const origin = transform.getOrigin();
|
|
84
|
-
const quaternion = transform.getRotation();
|
|
85
|
-
return {
|
|
86
|
-
position: { x: origin.x(), y: origin.y(), z: origin.z() },
|
|
87
|
-
rotation: { x: quaternion.x(), y: quaternion.y(), z: quaternion.z(), w: quaternion.w() },
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
resetSuspension(): void {
|
|
92
|
-
this.nativeVehicle.resetSuspension();
|
|
93
|
-
for (let i = 0; i < this.nativeVehicle.getNumWheels(); i++) {
|
|
94
|
-
this.nativeVehicle.updateWheelTransform(i, true);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
export * from './impl/bodies/gg-3d-body';
|
|
2
|
-
export * from './impl/bodies/gg-3d-trigger';
|
|
3
|
-
export * from './impl/gg-3d-body-factory';
|
|
4
|
-
export * from './impl/gg-3d-body-loader';
|
|
5
|
-
export * from './impl/gg-3d-physics-world';
|
|
6
|
-
export * from './impl/gg-3d-raycast-vehicle';
|