@gg-web-engine/matter 0.0.71 → 0.0.73
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/matter-character-controller.component.d.ts +191 -0
- package/dist/components/matter-character-controller.component.js +498 -0
- package/dist/components/matter-rigid-body.component.d.ts +17 -2
- package/dist/components/matter-rigid-body.component.js +27 -2
- package/dist/components/matter-trigger.component.d.ts +46 -4
- package/dist/components/matter-trigger.component.js +74 -4
- package/dist/components/matter-world.component.d.ts +32 -5
- package/dist/components/matter-world.component.js +119 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/matter-factory.d.ts +8 -1
- package/dist/matter-factory.js +116 -11
- package/dist/types.d.ts +2 -0
- package/package.json +5 -4
- package/test/components/matter-character-controller.component.spec.ts +298 -0
- package/test/components/matter-rigid-body-collision.spec.ts +27 -13
- package/test/components/matter-rigid-body.component.spec.ts +58 -0
- package/test/components/matter-trigger.component.spec.ts +41 -13
- package/test/components/matter-world.component.spec.ts +44 -9
|
@@ -1,19 +1,47 @@
|
|
|
1
1
|
import { Observable, Subject } from 'rxjs';
|
|
2
2
|
import { Body } from 'matter-js';
|
|
3
3
|
import { MatterRigidBodyComponent } from './matter-rigid-body.component';
|
|
4
|
+
import { MatterCharacterControllerComponent } from './matter-character-controller.component';
|
|
4
5
|
import { DebugBody2DSettings, ITrigger2dComponent, Shape2DDescriptor } from '@gg-web-engine/core';
|
|
5
6
|
import { MatterWorldComponent } from './matter-world.component';
|
|
6
7
|
import { MatterGgWorld, MatterPhysicsTypeDocRepo } from '../types';
|
|
7
8
|
export declare class MatterTriggerComponent extends MatterRigidBodyComponent implements ITrigger2dComponent<MatterPhysicsTypeDocRepo> {
|
|
8
9
|
readonly shape: Shape2DDescriptor;
|
|
9
10
|
protected readonly world: MatterWorldComponent;
|
|
10
|
-
get onEntityEntered(): Observable<MatterRigidBodyComponent>;
|
|
11
|
-
get onEntityLeft(): Observable<MatterRigidBodyComponent>;
|
|
12
|
-
protected readonly onEnter$: Subject<MatterRigidBodyComponent>;
|
|
13
|
-
protected readonly onLeft$: Subject<MatterRigidBodyComponent>;
|
|
11
|
+
get onEntityEntered(): Observable<MatterRigidBodyComponent | MatterCharacterControllerComponent>;
|
|
12
|
+
get onEntityLeft(): Observable<MatterRigidBodyComponent | MatterCharacterControllerComponent>;
|
|
13
|
+
protected readonly onEnter$: Subject<MatterRigidBodyComponent | MatterCharacterControllerComponent>;
|
|
14
|
+
protected readonly onLeft$: Subject<MatterRigidBodyComponent | MatterCharacterControllerComponent>;
|
|
15
|
+
/** Character controllers currently overlapping this trigger, as of the last `checkOverlaps()`
|
|
16
|
+
* poll - see that method's own doc for why this needs its own separate polling mechanism instead
|
|
17
|
+
* of the native `collisionStart`/`collisionEnd` events `handleCollisionStart`/`handleCollisionEnd`
|
|
18
|
+
* below rely on for ordinary rigid bodies. */
|
|
19
|
+
protected currentCharacterOverlaps: Set<MatterCharacterControllerComponent>;
|
|
14
20
|
readonly debugBodySettings: DebugBody2DSettings;
|
|
15
21
|
protected intersectionsAmount: number;
|
|
16
22
|
protected currentOverlaps: Set<MatterRigidBodyComponent>;
|
|
23
|
+
/** `Composite.remove` (what `removeFromWorld` calls) never fires a native `collisionEnd` for the
|
|
24
|
+
* body it removes - matter-js simply stops considering that body's pairs on the next step, it
|
|
25
|
+
* doesn't retroactively report the pairs that were active at removal time. Without this, a body
|
|
26
|
+
* removed from the world while still overlapping this trigger would leave `currentOverlaps`
|
|
27
|
+
* (and, for a character, `currentCharacterOverlaps`) permanently stale and `onEntityLeft` would
|
|
28
|
+
* never fire for it. `world.physicsWorld.removed$` fires for every component removal regardless
|
|
29
|
+
* of overlap state, so this only actually acts when the removed component is one this trigger was
|
|
30
|
+
* still tracking as an overlap.
|
|
31
|
+
*
|
|
32
|
+
* The stale entry is dropped synchronously, but `onLeft$` itself is only ever emitted on a
|
|
33
|
+
* microtask from this reaction, never inline. `removed$` fires from inside whatever call stack
|
|
34
|
+
* performed the removal - which can be another component's own in-progress lifecycle operation:
|
|
35
|
+
* `CharacterController2dEntity.recreateCapsule()` (crouch/stand) removes the *old*
|
|
36
|
+
* `characterController` with `dispose: true` before it has assigned the new one, so an `onLeft$`
|
|
37
|
+
* subscriber running inline there (app code driven by `Trigger2dEntity`, e.g. a kill volume
|
|
38
|
+
* resetting `.position` on the character that just left it) would write through the old,
|
|
39
|
+
* already-removed `MatterCharacterControllerComponent` - `Body.setPosition` on a body no longer
|
|
40
|
+
* queried by anything - and the write is silently lost, with the replacement capsule then spawning
|
|
41
|
+
* at the stale position. Deferring the emission keeps it within the same JS turn (well before the
|
|
42
|
+
* next `Engine.update`) but strictly after every synchronous call stack in flight, including the
|
|
43
|
+
* removal's own, has unwound. */
|
|
44
|
+
private removedSub?;
|
|
17
45
|
private handleCollisionStart;
|
|
18
46
|
private handleCollisionEnd;
|
|
19
47
|
constructor(nativeBody: Body, shape: Shape2DDescriptor, world: MatterWorldComponent);
|
|
@@ -23,6 +51,20 @@ export declare class MatterTriggerComponent extends MatterRigidBodyComponent imp
|
|
|
23
51
|
* `onCollisionStart$`/`onCollisionEnd$` completion (via `super.dispose()`) - this trigger's own
|
|
24
52
|
* enter/exit subjects are a separate pair this subclass owns and must complete itself. */
|
|
25
53
|
dispose(): void;
|
|
54
|
+
/**
|
|
55
|
+
* Regular rigid-body overlaps are handled entirely by `handleCollisionStart`/`handleCollisionEnd`
|
|
56
|
+
* above, off matter's own native `collisionStart`/`collisionEnd` engine events - so this used to be
|
|
57
|
+
* a pure no-op for matter-js. A `MatterCharacterControllerComponent`'s own phantom body is
|
|
58
|
+
* deliberately never added to `Composite`/`engine.world` at all (see that class's own doc), so no
|
|
59
|
+
* native collision pair - and thus no native event - can ever involve it. Since `checkOverlaps()` is
|
|
60
|
+
* already called once per tick by `Trigger2dEntity` regardless of backend, this is the natural place
|
|
61
|
+
* to add the poll this needs instead of inventing a second, differently-shaped mechanism: every
|
|
62
|
+
* `MatterCharacterControllerComponent` currently in the world (`world.children`, which - unlike
|
|
63
|
+
* matter's own `Composite` - already tracks it) is tested against this trigger's own body via
|
|
64
|
+
* `Query.collides`, diffed against `currentCharacterOverlaps` to fire `onEntityEntered`/
|
|
65
|
+
* `onEntityLeft` exactly on the enter/exit transitions, the same as the native-event path does for
|
|
66
|
+
* ordinary bodies.
|
|
67
|
+
*/
|
|
26
68
|
checkOverlaps(): void;
|
|
27
69
|
clone(): MatterTriggerComponent;
|
|
28
70
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { map, merge, Subject } from 'rxjs';
|
|
2
|
-
import { Body, Events } from 'matter-js';
|
|
2
|
+
import { Body, Detector, Events, Query } from 'matter-js';
|
|
3
3
|
import { MatterRigidBodyComponent } from './matter-rigid-body.component';
|
|
4
|
+
import { MatterCharacterControllerComponent } from './matter-character-controller.component';
|
|
4
5
|
import { DebugBody2DSettings } from '@gg-web-engine/core';
|
|
5
6
|
export class MatterTriggerComponent extends MatterRigidBodyComponent {
|
|
6
7
|
get onEntityEntered() {
|
|
@@ -20,7 +21,8 @@ export class MatterTriggerComponent extends MatterRigidBodyComponent {
|
|
|
20
21
|
}
|
|
21
22
|
if (body) {
|
|
22
23
|
let comp = this.world.children.find(c => c.nativeBody === body);
|
|
23
|
-
if (comp) {
|
|
24
|
+
if (comp instanceof MatterRigidBodyComponent) {
|
|
25
|
+
this.currentOverlaps.add(comp);
|
|
24
26
|
this.onEnter$.next(comp);
|
|
25
27
|
}
|
|
26
28
|
}
|
|
@@ -37,7 +39,8 @@ export class MatterTriggerComponent extends MatterRigidBodyComponent {
|
|
|
37
39
|
}
|
|
38
40
|
if (body) {
|
|
39
41
|
let comp = this.world.children.find(c => c.nativeBody === body);
|
|
40
|
-
if (comp) {
|
|
42
|
+
if (comp instanceof MatterRigidBodyComponent) {
|
|
43
|
+
this.currentOverlaps.delete(comp);
|
|
41
44
|
this.onLeft$.next(comp);
|
|
42
45
|
}
|
|
43
46
|
}
|
|
@@ -49,6 +52,11 @@ export class MatterTriggerComponent extends MatterRigidBodyComponent {
|
|
|
49
52
|
this.world = world;
|
|
50
53
|
this.onEnter$ = new Subject();
|
|
51
54
|
this.onLeft$ = new Subject();
|
|
55
|
+
/** Character controllers currently overlapping this trigger, as of the last `checkOverlaps()`
|
|
56
|
+
* poll - see that method's own doc for why this needs its own separate polling mechanism instead
|
|
57
|
+
* of the native `collisionStart`/`collisionEnd` events `handleCollisionStart`/`handleCollisionEnd`
|
|
58
|
+
* below rely on for ordinary rigid bodies. */
|
|
59
|
+
this.currentCharacterOverlaps = new Set();
|
|
52
60
|
this.debugBodySettings = new DebugBody2DSettings({ type: 'TRIGGER', activated: () => this.intersectionsAmount > 0 }, this.shape);
|
|
53
61
|
this.intersectionsAmount = 0;
|
|
54
62
|
this.currentOverlaps = new Set();
|
|
@@ -73,14 +81,33 @@ export class MatterTriggerComponent extends MatterRigidBodyComponent {
|
|
|
73
81
|
super.addToWorld(world);
|
|
74
82
|
Events.on(world.physicsWorld.matterEngine, 'collisionStart', this.handleCollisionStart);
|
|
75
83
|
Events.on(world.physicsWorld.matterEngine, 'collisionEnd', this.handleCollisionEnd);
|
|
84
|
+
this.removedSub = world.physicsWorld.removed$.subscribe(c => {
|
|
85
|
+
if (c === this) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (c instanceof MatterCharacterControllerComponent) {
|
|
89
|
+
if (this.currentCharacterOverlaps.delete(c)) {
|
|
90
|
+
queueMicrotask(() => this.onLeft$.next(c));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
else if (c instanceof MatterRigidBodyComponent && this.currentOverlaps.delete(c)) {
|
|
94
|
+
queueMicrotask(() => this.onLeft$.next(c));
|
|
95
|
+
}
|
|
96
|
+
});
|
|
76
97
|
}
|
|
77
98
|
removeFromWorld(world, dispose) {
|
|
99
|
+
var _a;
|
|
78
100
|
Events.off(world.physicsWorld.matterEngine, 'collisionStart', this.handleCollisionStart);
|
|
79
101
|
Events.off(world.physicsWorld.matterEngine, 'collisionEnd', this.handleCollisionEnd);
|
|
102
|
+
(_a = this.removedSub) === null || _a === void 0 ? void 0 : _a.unsubscribe();
|
|
80
103
|
for (const body of this.currentOverlaps) {
|
|
81
104
|
this.onLeft$.next(body);
|
|
82
105
|
}
|
|
83
106
|
this.currentOverlaps.clear();
|
|
107
|
+
for (const character of this.currentCharacterOverlaps) {
|
|
108
|
+
this.onLeft$.next(character);
|
|
109
|
+
}
|
|
110
|
+
this.currentCharacterOverlaps.clear();
|
|
84
111
|
super.removeFromWorld(world, dispose);
|
|
85
112
|
}
|
|
86
113
|
/** Completes `onEnter$`/`onLeft$` on top of `MatterRigidBodyComponent.dispose()`'s own
|
|
@@ -91,8 +118,51 @@ export class MatterTriggerComponent extends MatterRigidBodyComponent {
|
|
|
91
118
|
this.onLeft$.complete();
|
|
92
119
|
super.dispose();
|
|
93
120
|
}
|
|
121
|
+
/**
|
|
122
|
+
* Regular rigid-body overlaps are handled entirely by `handleCollisionStart`/`handleCollisionEnd`
|
|
123
|
+
* above, off matter's own native `collisionStart`/`collisionEnd` engine events - so this used to be
|
|
124
|
+
* a pure no-op for matter-js. A `MatterCharacterControllerComponent`'s own phantom body is
|
|
125
|
+
* deliberately never added to `Composite`/`engine.world` at all (see that class's own doc), so no
|
|
126
|
+
* native collision pair - and thus no native event - can ever involve it. Since `checkOverlaps()` is
|
|
127
|
+
* already called once per tick by `Trigger2dEntity` regardless of backend, this is the natural place
|
|
128
|
+
* to add the poll this needs instead of inventing a second, differently-shaped mechanism: every
|
|
129
|
+
* `MatterCharacterControllerComponent` currently in the world (`world.children`, which - unlike
|
|
130
|
+
* matter's own `Composite` - already tracks it) is tested against this trigger's own body via
|
|
131
|
+
* `Query.collides`, diffed against `currentCharacterOverlaps` to fire `onEntityEntered`/
|
|
132
|
+
* `onEntityLeft` exactly on the enter/exit transitions, the same as the native-event path does for
|
|
133
|
+
* ordinary bodies.
|
|
134
|
+
*/
|
|
94
135
|
checkOverlaps() {
|
|
95
|
-
//
|
|
136
|
+
// `Query.collides` tests raw geometry only and knows nothing about `collisionFilter` (see
|
|
137
|
+
// `MatterCharacterControllerComponent.collectObstacles`'s own doc on this same gap) - call
|
|
138
|
+
// matter's own `Detector.canCollide` directly so a character controller whose collision groups
|
|
139
|
+
// wouldn't ordinarily interact with this trigger isn't falsely reported entering it just because
|
|
140
|
+
// this poll bypasses the broadphase that would otherwise exclude it.
|
|
141
|
+
const characters = this.world.children.filter((c) => c instanceof MatterCharacterControllerComponent &&
|
|
142
|
+
Detector.canCollide(this.nativeBody.collisionFilter, c.nativeBody.collisionFilter));
|
|
143
|
+
const stillOverlapping = new Set();
|
|
144
|
+
if (characters.length > 0) {
|
|
145
|
+
const collisions = Query.collides(this.nativeBody, characters.map(c => c.nativeBody));
|
|
146
|
+
for (const collision of collisions) {
|
|
147
|
+
const otherNative = collision.parentA === this.nativeBody ? collision.parentB : collision.parentA;
|
|
148
|
+
const comp = characters.find(c => c.nativeBody === otherNative);
|
|
149
|
+
if (comp) {
|
|
150
|
+
stillOverlapping.add(comp);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
for (const comp of stillOverlapping) {
|
|
155
|
+
if (!this.currentCharacterOverlaps.has(comp)) {
|
|
156
|
+
this.currentCharacterOverlaps.add(comp);
|
|
157
|
+
this.onEnter$.next(comp);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
for (const comp of this.currentCharacterOverlaps) {
|
|
161
|
+
if (!stillOverlapping.has(comp)) {
|
|
162
|
+
this.currentCharacterOverlaps.delete(comp);
|
|
163
|
+
this.onLeft$.next(comp);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
96
166
|
}
|
|
97
167
|
clone() {
|
|
98
168
|
const clonedBody = Body.create(Object.assign(Object.assign({}, this.nativeBody), { isSensor: true, collisionFilter: Object.assign({}, this.nativeBody.collisionFilter) }));
|
|
@@ -5,19 +5,23 @@ import { MatterPhysicsTypeDocRepo } from '../types';
|
|
|
5
5
|
import { Subject } from 'rxjs';
|
|
6
6
|
import { MatterRigidBodyComponent } from './matter-rigid-body.component';
|
|
7
7
|
import { MatterTriggerComponent } from './matter-trigger.component';
|
|
8
|
+
import { MatterCharacterControllerComponent } from './matter-character-controller.component';
|
|
9
|
+
type MatterWorldChild = MatterRigidBodyComponent | MatterTriggerComponent | MatterCharacterControllerComponent;
|
|
8
10
|
export declare class MatterWorldComponent implements IPhysicsWorld2dComponent<MatterPhysicsTypeDocRepo> {
|
|
9
11
|
protected matterEngine_: Engine | null;
|
|
10
12
|
get matterEngine(): Engine | null;
|
|
11
13
|
get matterWorld(): World | null;
|
|
12
14
|
readonly factory: MatterFactory;
|
|
13
|
-
readonly added$: Subject<
|
|
14
|
-
readonly removed$: Subject<
|
|
15
|
-
readonly children:
|
|
15
|
+
readonly added$: Subject<MatterWorldChild>;
|
|
16
|
+
readonly removed$: Subject<MatterWorldChild>;
|
|
17
|
+
readonly children: MatterWorldChild[];
|
|
16
18
|
/** Mirrors the rapier packages' `handleIdEntityMap` pattern: `Body.id` (matter-js's own
|
|
17
19
|
* globally-unique numeric id, assigned once per body via `Body.nextId` and stable for its whole
|
|
18
20
|
* lifetime) to component, kept in sync alongside `children` so `findRigidBody` - called once per
|
|
19
|
-
* collision pair, per step - is an O(1) lookup instead of an O(n) `Array.find` scan.
|
|
20
|
-
|
|
21
|
+
* collision pair, per step - is an O(1) lookup instead of an O(n) `Array.find` scan. Also used by
|
|
22
|
+
* `MatterCharacterControllerComponent.pushDynamicBodies` to resolve a native body it just bumped
|
|
23
|
+
* into back to its owning component. */
|
|
24
|
+
readonly handleIdEntityMap: Map<number, MatterWorldChild>;
|
|
21
25
|
private _gravity;
|
|
22
26
|
get gravity(): Point2;
|
|
23
27
|
set gravity(value: Point2);
|
|
@@ -54,6 +58,29 @@ export declare class MatterWorldComponent implements IPhysicsWorld2dComponent<Ma
|
|
|
54
58
|
deregisterCollisionGroup(group: CollisionGroup): void;
|
|
55
59
|
private lastDelta;
|
|
56
60
|
simulate(delta: number): void;
|
|
61
|
+
/**
|
|
62
|
+
* matter-js has no native raycast query. Built on a true parametric ray-vs-polygon intersection
|
|
63
|
+
* (`segmentIntersection`/`bodyPolygons`), not `Matter.Query.ray` - that helper is only a thin
|
|
64
|
+
* wrapper over `Query.collides` (a full-geometry SAT test of a synthetic, very thin rectangle body
|
|
65
|
+
* against candidates), which reports an approximate overlap contact point, not a true "where does
|
|
66
|
+
* the ray segment first cross this body's boundary" point; verified via this adapter's own
|
|
67
|
+
* regression suite (`MatterWorldComponent.spec.ts`'s `Raycast` block expects a precise hit point at
|
|
68
|
+
* a known box edge) that the SAT-approximated version produces a visibly wrong point.
|
|
69
|
+
*
|
|
70
|
+
* `Query.collides`/`Collision.collides` test raw geometry only and know nothing about
|
|
71
|
+
* `collisionFilter` (the same limitation `MatterCharacterControllerComponent.collectObstacles`'s
|
|
72
|
+
* own doc describes) - candidates are pre-filtered here via `Detector.canCollide` itself, called
|
|
73
|
+
* directly against a synthetic filter built from `options.collisionFilterGroups`/
|
|
74
|
+
* `collisionFilterMask`. Sensor bodies (triggers) are excluded from candidates entirely, mirroring
|
|
75
|
+
* `collectObstacles`'s own exclusion - a trigger never physically blocks anything, so it shouldn't
|
|
76
|
+
* register as a raycast hit either.
|
|
77
|
+
*
|
|
78
|
+
* `hitNormal` is derived from whichever polygon edge the closest intersection landed on (rotated
|
|
79
|
+
* 90°, sign chosen to point back towards `options.from`) - not from a matter-js collision object at
|
|
80
|
+
* all, sidestepping the sign-convention pitfall `handleCollisionStart`'s own doc describes for
|
|
81
|
+
* `pair.collision.normal`.
|
|
82
|
+
*/
|
|
57
83
|
raycast(options: RaycastOptions<Point2>): RaycastResult<Point2, MatterRigidBodyComponent>;
|
|
58
84
|
dispose(): void;
|
|
59
85
|
}
|
|
86
|
+
export {};
|
|
@@ -7,10 +7,11 @@ 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 { Pnt2, } from '@gg-web-engine/core';
|
|
11
|
-
import { Engine, Events } from 'matter-js';
|
|
10
|
+
import { BitMask, Pnt2, } from '@gg-web-engine/core';
|
|
11
|
+
import { Composite, Detector, Engine, Events } from 'matter-js';
|
|
12
12
|
import { MatterFactory } from '../matter-factory';
|
|
13
13
|
import { Subject } from 'rxjs';
|
|
14
|
+
import { MatterRigidBodyComponent } from './matter-rigid-body.component';
|
|
14
15
|
/**
|
|
15
16
|
* Averages matter-js's collision support points into a single representative contact point for
|
|
16
17
|
* `CollisionEvent.position`. `collision.supports` is *always* a fixed-length-2 array
|
|
@@ -34,6 +35,42 @@ function averageSupports(collision) {
|
|
|
34
35
|
}
|
|
35
36
|
return Pnt2.scalarMult(sum, 1 / supports.length);
|
|
36
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Standard 2D segment-vs-segment intersection (parametric form): `p1->p2` is the ray segment,
|
|
40
|
+
* `p3->p4` one polygon edge. Returns the intersection's fractional position along `p1->p2`
|
|
41
|
+
* (`t`, always in `[0,1]` when a result is returned) and the world-space point itself, or `null`
|
|
42
|
+
* for parallel/non-intersecting/out-of-range segments. Used by `MatterWorldComponent.raycast`
|
|
43
|
+
* (see its own doc for why) instead of matter-js's own `Query.ray`/`Collision.collides`, which
|
|
44
|
+
* only ever reports an approximate SAT contact point, not a true point where the ray segment
|
|
45
|
+
* crosses a candidate's actual boundary.
|
|
46
|
+
*/
|
|
47
|
+
function segmentIntersection(p1, p2, p3, p4) {
|
|
48
|
+
const d1 = Pnt2.sub(p2, p1);
|
|
49
|
+
const d2 = Pnt2.sub(p4, p3);
|
|
50
|
+
const denom = d1.x * d2.y - d1.y * d2.x;
|
|
51
|
+
if (Math.abs(denom) < 1e-10) {
|
|
52
|
+
return null; // parallel (or degenerate) - no single intersection point
|
|
53
|
+
}
|
|
54
|
+
const diff = Pnt2.sub(p3, p1);
|
|
55
|
+
const t = (diff.x * d2.y - diff.y * d2.x) / denom;
|
|
56
|
+
const u = (diff.x * d1.y - diff.y * d1.x) / denom;
|
|
57
|
+
if (t < 0 || t > 1 || u < 0 || u > 1) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
return { t, point: Pnt2.add(p1, Pnt2.scalarMult(d1, t)) };
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Every polygon (in matter-js's sense) making up `body`'s actual collision boundary, each as a
|
|
64
|
+
* closed loop of world-space vertices - `body` itself for a simple (non-compound) body, or every
|
|
65
|
+
* part but the synthetic index-0 "container" part for a compound one (mirrors `Query.collides`'s
|
|
66
|
+
* own `partsAStart = partsALength === 1 ? 0 : 1` convention). A circle is included via matter-js's
|
|
67
|
+
* own many-sided-polygon approximation (`Bodies.circle` is built on `Bodies.polygon`) - close
|
|
68
|
+
* enough for a raycast headroom check, not a true circle intersection.
|
|
69
|
+
*/
|
|
70
|
+
function bodyPolygons(body) {
|
|
71
|
+
const parts = body.parts.length === 1 ? [body] : body.parts.slice(1);
|
|
72
|
+
return parts.map(part => part.vertices);
|
|
73
|
+
}
|
|
37
74
|
// TODO probably should be configurable in world
|
|
38
75
|
const MATTER_WORLD_SCALE = 0.0001;
|
|
39
76
|
export class MatterWorldComponent {
|
|
@@ -61,7 +98,9 @@ export class MatterWorldComponent {
|
|
|
61
98
|
/** Mirrors the rapier packages' `handleIdEntityMap` pattern: `Body.id` (matter-js's own
|
|
62
99
|
* globally-unique numeric id, assigned once per body via `Body.nextId` and stable for its whole
|
|
63
100
|
* lifetime) to component, kept in sync alongside `children` so `findRigidBody` - called once per
|
|
64
|
-
* collision pair, per step - is an O(1) lookup instead of an O(n) `Array.find` scan.
|
|
101
|
+
* collision pair, per step - is an O(1) lookup instead of an O(n) `Array.find` scan. Also used by
|
|
102
|
+
* `MatterCharacterControllerComponent.pushDynamicBodies` to resolve a native body it just bumped
|
|
103
|
+
* into back to its owning component. */
|
|
65
104
|
this.handleIdEntityMap = new Map();
|
|
66
105
|
this._gravity = { x: 0, y: 9.82 };
|
|
67
106
|
this.mainCollisionGroup = 0;
|
|
@@ -89,7 +128,11 @@ export class MatterWorldComponent {
|
|
|
89
128
|
});
|
|
90
129
|
}
|
|
91
130
|
findRigidBody(nativeBody) {
|
|
92
|
-
|
|
131
|
+
// a character controller's phantom body is never added to `Composite`/`engine.world` (see its
|
|
132
|
+
// own doc), so it can never actually be `pair.bodyA`/`pair.bodyB` here - this narrows the lookup's
|
|
133
|
+
// type back down since `handleIdEntityMap` itself now also tracks that component class.
|
|
134
|
+
const comp = this.handleIdEntityMap.get(nativeBody.id);
|
|
135
|
+
return comp instanceof MatterRigidBodyComponent ? comp : undefined;
|
|
93
136
|
}
|
|
94
137
|
/**
|
|
95
138
|
* Wires `IRigidBody2dComponent.onCollisionStart` for every non-sensor pair in the world, off
|
|
@@ -173,8 +216,79 @@ export class MatterWorldComponent {
|
|
|
173
216
|
Engine.update(this.matterEngine, delta, this.lastDelta > 0 ? delta / this.lastDelta : 1);
|
|
174
217
|
this.lastDelta = delta;
|
|
175
218
|
}
|
|
219
|
+
/**
|
|
220
|
+
* matter-js has no native raycast query. Built on a true parametric ray-vs-polygon intersection
|
|
221
|
+
* (`segmentIntersection`/`bodyPolygons`), not `Matter.Query.ray` - that helper is only a thin
|
|
222
|
+
* wrapper over `Query.collides` (a full-geometry SAT test of a synthetic, very thin rectangle body
|
|
223
|
+
* against candidates), which reports an approximate overlap contact point, not a true "where does
|
|
224
|
+
* the ray segment first cross this body's boundary" point; verified via this adapter's own
|
|
225
|
+
* regression suite (`MatterWorldComponent.spec.ts`'s `Raycast` block expects a precise hit point at
|
|
226
|
+
* a known box edge) that the SAT-approximated version produces a visibly wrong point.
|
|
227
|
+
*
|
|
228
|
+
* `Query.collides`/`Collision.collides` test raw geometry only and know nothing about
|
|
229
|
+
* `collisionFilter` (the same limitation `MatterCharacterControllerComponent.collectObstacles`'s
|
|
230
|
+
* own doc describes) - candidates are pre-filtered here via `Detector.canCollide` itself, called
|
|
231
|
+
* directly against a synthetic filter built from `options.collisionFilterGroups`/
|
|
232
|
+
* `collisionFilterMask`. Sensor bodies (triggers) are excluded from candidates entirely, mirroring
|
|
233
|
+
* `collectObstacles`'s own exclusion - a trigger never physically blocks anything, so it shouldn't
|
|
234
|
+
* register as a raycast hit either.
|
|
235
|
+
*
|
|
236
|
+
* `hitNormal` is derived from whichever polygon edge the closest intersection landed on (rotated
|
|
237
|
+
* 90°, sign chosen to point back towards `options.from`) - not from a matter-js collision object at
|
|
238
|
+
* all, sidestepping the sign-convention pitfall `handleCollisionStart`'s own doc describes for
|
|
239
|
+
* `pair.collision.normal`.
|
|
240
|
+
*/
|
|
176
241
|
raycast(options) {
|
|
177
|
-
|
|
242
|
+
var _a;
|
|
243
|
+
const matterWorld = this.matterWorld;
|
|
244
|
+
const direction = Pnt2.sub(options.to, options.from);
|
|
245
|
+
const rayLength = Pnt2.len(direction);
|
|
246
|
+
if (!matterWorld || rayLength === 0) {
|
|
247
|
+
return { hasHit: false };
|
|
248
|
+
}
|
|
249
|
+
const groupsMask = options.collisionFilterGroups
|
|
250
|
+
? BitMask.pack(options.collisionFilterGroups, 16)
|
|
251
|
+
: BitMask.full(16);
|
|
252
|
+
const maskMask = options.collisionFilterMask ? BitMask.pack(options.collisionFilterMask, 16) : BitMask.full(16);
|
|
253
|
+
const rayFilter = { category: groupsMask, mask: maskMask, group: 0 };
|
|
254
|
+
const candidates = Composite.allBodies(matterWorld).filter(b => !b.isSensor && Detector.canCollide(rayFilter, b.collisionFilter));
|
|
255
|
+
let closestT = Infinity;
|
|
256
|
+
let closestPoint = null;
|
|
257
|
+
let closestNormal = null;
|
|
258
|
+
let closestBody = null;
|
|
259
|
+
for (const body of candidates) {
|
|
260
|
+
for (const polygon of bodyPolygons(body)) {
|
|
261
|
+
for (let i = 0; i < polygon.length; i++) {
|
|
262
|
+
const a = polygon[i];
|
|
263
|
+
const b = polygon[(i + 1) % polygon.length];
|
|
264
|
+
const hit = segmentIntersection(options.from, options.to, a, b);
|
|
265
|
+
if (!hit || hit.t >= closestT) {
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
closestT = hit.t;
|
|
269
|
+
closestPoint = hit.point;
|
|
270
|
+
closestBody = body;
|
|
271
|
+
const edge = Pnt2.sub(b, a);
|
|
272
|
+
const normal = Pnt2.norm({ x: -edge.y, y: edge.x });
|
|
273
|
+
closestNormal = Pnt2.dot(normal, Pnt2.sub(options.from, hit.point)) >= 0 ? normal : Pnt2.neg(normal);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (!closestBody) {
|
|
278
|
+
return { hasHit: false };
|
|
279
|
+
}
|
|
280
|
+
// A compound body's hit part is not the parent body `handleIdEntityMap` is keyed by (see
|
|
281
|
+
// `MatterFactory`'s compound-shape flattening) - `.parent` (self-referential for a non-compound
|
|
282
|
+
// body) resolves back to it.
|
|
283
|
+
const parentBody = (_a = closestBody.parent) !== null && _a !== void 0 ? _a : closestBody;
|
|
284
|
+
const resolved = this.handleIdEntityMap.get(parentBody.id);
|
|
285
|
+
return {
|
|
286
|
+
hasHit: true,
|
|
287
|
+
hitBody: resolved instanceof MatterRigidBodyComponent ? resolved : undefined,
|
|
288
|
+
hitDistance: closestT * rayLength,
|
|
289
|
+
hitPoint: closestPoint,
|
|
290
|
+
hitNormal: closestNormal,
|
|
291
|
+
};
|
|
178
292
|
}
|
|
179
293
|
dispose() {
|
|
180
294
|
Events.off(this.matterEngine, 'collisionStart', this.handleCollisionStart);
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/matter-factory.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { BodyShape2DDescriptor, IPhysicsBody2dComponentFactory, Point2, Shape2DDescriptor } from '@gg-web-engine/core';
|
|
1
|
+
import { BodyShape2DDescriptor, CharacterController2dOptions, IPhysicsBody2dComponentFactory, Point2, Shape2DDescriptor } from '@gg-web-engine/core';
|
|
2
2
|
import { MatterRigidBodyComponent } from './components/matter-rigid-body.component';
|
|
3
3
|
import { MatterTriggerComponent } from './components/matter-trigger.component';
|
|
4
|
+
import { MatterCharacterControllerComponent } from './components/matter-character-controller.component';
|
|
4
5
|
import { MatterWorldComponent } from './components/matter-world.component';
|
|
5
6
|
import { MatterPhysicsTypeDocRepo } from './types';
|
|
6
7
|
export declare class MatterFactory implements IPhysicsBody2dComponentFactory<MatterPhysicsTypeDocRepo> {
|
|
@@ -14,6 +15,12 @@ export declare class MatterFactory implements IPhysicsBody2dComponentFactory<Mat
|
|
|
14
15
|
position?: Point2;
|
|
15
16
|
rotation?: number;
|
|
16
17
|
}): MatterTriggerComponent;
|
|
18
|
+
createCharacterController(options: CharacterController2dOptions, transform?: {
|
|
19
|
+
position?: Point2;
|
|
20
|
+
rotation?: number;
|
|
21
|
+
}): MatterCharacterControllerComponent;
|
|
22
|
+
private createShapeParts;
|
|
23
|
+
private createShapeBody;
|
|
17
24
|
/**
|
|
18
25
|
* matter-js's own body model only has `isStatic` - no distinct kinematic body type (position-
|
|
19
26
|
* driven, but still pushes/wakes dynamic bodies it moves into) and no continuous collision
|
package/dist/matter-factory.js
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
|
-
import { warnOnce, } from '@gg-web-engine/core';
|
|
1
|
+
import { Pnt2, warnOnce, } from '@gg-web-engine/core';
|
|
2
2
|
import { MatterRigidBodyComponent } from './components/matter-rigid-body.component';
|
|
3
3
|
import { MatterTriggerComponent } from './components/matter-trigger.component';
|
|
4
|
-
import {
|
|
4
|
+
import { MatterCharacterControllerComponent } from './components/matter-character-controller.component';
|
|
5
|
+
import { Bodies, Body, Common, Vector } from 'matter-js';
|
|
6
|
+
import * as decomp from 'poly-decomp';
|
|
7
|
+
/**
|
|
8
|
+
* `Bodies.fromVertices` (used for the `POLYGON` shape below) only actually decomposes a concave
|
|
9
|
+
* vertex set into convex parts when a decomposition library is registered via `Common.setDecomp` -
|
|
10
|
+
* without it, matter-js silently falls back to the convex hull of the given vertices, so a concave
|
|
11
|
+
* `POLYGON` would render/collide as if it were convex. Registering `poly-decomp` here, once, at
|
|
12
|
+
* module load makes `Bodies.fromVertices` actually decompose concave outlines everywhere it's used.
|
|
13
|
+
*/
|
|
14
|
+
Common.setDecomp(decomp);
|
|
5
15
|
/**
|
|
6
16
|
* `kinematic_pos`/`kinematic_vel`/`ccd` have no native matter-js equivalent at all - unlike
|
|
7
17
|
* `packages/ammo`/`packages/rapier2d`/`packages/rapier3d`, this package can only warn and fall
|
|
@@ -19,45 +29,140 @@ export class MatterFactory {
|
|
|
19
29
|
this.world = world;
|
|
20
30
|
}
|
|
21
31
|
createRigidBody(descriptor, transform) {
|
|
22
|
-
var _a, _b;
|
|
32
|
+
var _a, _b, _c;
|
|
23
33
|
let nativeBody = null;
|
|
24
34
|
switch (descriptor.shape.shape) {
|
|
25
|
-
case '
|
|
35
|
+
case 'BOX':
|
|
26
36
|
nativeBody = Bodies.rectangle(0, 0, descriptor.shape.dimensions.x, descriptor.shape.dimensions.y, this.transformOptions(descriptor.body));
|
|
27
37
|
break;
|
|
28
38
|
case 'CIRCLE':
|
|
29
39
|
nativeBody = Bodies.circle(0, 0, descriptor.shape.radius, this.transformOptions(descriptor.body));
|
|
30
40
|
break;
|
|
41
|
+
case 'CAPSULE':
|
|
42
|
+
nativeBody = Bodies.rectangle(0, 0, descriptor.shape.radius * 2, descriptor.shape.centersDistance + descriptor.shape.radius * 2, {
|
|
43
|
+
chamfer: {
|
|
44
|
+
radius: descriptor.shape.radius,
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
break;
|
|
48
|
+
case 'CONVEX_HULL':
|
|
49
|
+
nativeBody = Bodies.fromVertices(0, 0, [Pnt2.hull(descriptor.shape.vertices).map(v => Vector.create(v.x, v.y))], this.transformOptions(descriptor.body));
|
|
50
|
+
break;
|
|
51
|
+
case 'POLYGON':
|
|
52
|
+
nativeBody = Bodies.fromVertices(0, 0, [descriptor.shape.vertices.map(v => Vector.create(v.x, v.y))], this.transformOptions(descriptor.body));
|
|
53
|
+
break;
|
|
54
|
+
case 'COMPOUND':
|
|
55
|
+
nativeBody = this.createShapeBody(descriptor.shape, this.transformOptions(descriptor.body));
|
|
56
|
+
break;
|
|
31
57
|
}
|
|
32
58
|
if (!nativeBody) {
|
|
33
59
|
throw new Error(`Shape "${descriptor.shape}" not implemented for Matter.js`);
|
|
34
60
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
61
|
+
// Must go through `Body.setPosition`/`Body.setAngle` (not a raw `nativeBody.position = ...`
|
|
62
|
+
// field assignment) - see `gg-engine-physics-adapter-matter`'s own note on this: a body's
|
|
63
|
+
// `vertices`/`bounds` (what every actual collision query reads) are only ever translated to
|
|
64
|
+
// match `.position` at creation time, using whatever position was passed into
|
|
65
|
+
// `Bodies.rectangle`/`Bodies.circle`/`Bodies.fromVertices` itself (always `(0, 0)` here) - a
|
|
66
|
+
// later raw field write changes what `.position` *reports* without moving the real collision
|
|
67
|
+
// geometry at all, permanently desyncing the two until something else (ordinary simulation,
|
|
68
|
+
// which this character-controller-adjacent code path can't rely on) happens to correct it.
|
|
69
|
+
Body.setPosition(nativeBody, Vector.create(((_a = transform === null || transform === void 0 ? void 0 : transform.position) === null || _a === void 0 ? void 0 : _a.x) || 0, ((_b = transform === null || transform === void 0 ? void 0 : transform.position) === null || _b === void 0 ? void 0 : _b.y) || 0));
|
|
70
|
+
Body.setAngle(nativeBody, (transform === null || transform === void 0 ? void 0 : transform.rotation) || 0);
|
|
71
|
+
const bodyType = (_c = descriptor.body.bodyType) !== null && _c !== void 0 ? _c : (descriptor.body.mass ? 'dynamic' : 'static');
|
|
72
|
+
const component = new MatterRigidBodyComponent(nativeBody, descriptor.shape, bodyType, !!descriptor.body.ccd);
|
|
73
|
+
// `transformOptions` (used to build `nativeBody` above) only ever reads
|
|
74
|
+
// `bodyType`/`mass`/`restitution`/`friction` - `ownCollisionGroups`/`interactWithCollisionGroups`
|
|
75
|
+
// must be applied through the component's own setters afterward (same as
|
|
76
|
+
// `MatterCharacterControllerComponent`'s constructor already does for a character), or a
|
|
77
|
+
// configured collision group is silently dropped in favor of the "all groups" default every
|
|
78
|
+
// `MatterRigidBodyComponent` otherwise starts with.
|
|
79
|
+
if (descriptor.body.ownCollisionGroups !== undefined) {
|
|
80
|
+
component.ownCollisionGroups = descriptor.body.ownCollisionGroups;
|
|
81
|
+
}
|
|
82
|
+
if (descriptor.body.interactWithCollisionGroups !== undefined) {
|
|
83
|
+
component.interactWithCollisionGroups = descriptor.body.interactWithCollisionGroups;
|
|
84
|
+
}
|
|
85
|
+
return component;
|
|
38
86
|
}
|
|
39
87
|
createTrigger(descriptor, transform) {
|
|
40
88
|
var _a, _b;
|
|
41
89
|
let nativeBody = null;
|
|
42
90
|
switch (descriptor.shape) {
|
|
43
|
-
case '
|
|
91
|
+
case 'BOX':
|
|
44
92
|
nativeBody = Bodies.rectangle(0, 0, descriptor.dimensions.x, descriptor.dimensions.y, { isSensor: true });
|
|
45
93
|
break;
|
|
46
94
|
case 'CIRCLE':
|
|
47
95
|
nativeBody = Bodies.circle(0, 0, descriptor.radius, { isSensor: true });
|
|
48
96
|
break;
|
|
97
|
+
case 'CAPSULE':
|
|
98
|
+
nativeBody = Bodies.rectangle(0, 0, descriptor.radius * 2, descriptor.centersDistance + descriptor.radius * 2, {
|
|
99
|
+
isSensor: true,
|
|
100
|
+
chamfer: {
|
|
101
|
+
radius: descriptor.radius,
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
break;
|
|
105
|
+
case 'CONVEX_HULL':
|
|
106
|
+
nativeBody = Bodies.fromVertices(0, 0, [Pnt2.hull(descriptor.vertices).map(v => Vector.create(v.x, v.y))], {
|
|
107
|
+
isSensor: true,
|
|
108
|
+
});
|
|
109
|
+
break;
|
|
110
|
+
case 'POLYGON':
|
|
111
|
+
nativeBody = Bodies.fromVertices(0, 0, [descriptor.vertices.map(v => Vector.create(v.x, v.y))], {
|
|
112
|
+
isSensor: true,
|
|
113
|
+
});
|
|
114
|
+
break;
|
|
115
|
+
case 'COMPOUND':
|
|
116
|
+
nativeBody = this.createShapeBody(descriptor, { isSensor: true });
|
|
117
|
+
break;
|
|
49
118
|
}
|
|
50
119
|
if (!nativeBody) {
|
|
51
120
|
throw new Error(`Shape "${descriptor.shape}" not implemented for Matter.js`);
|
|
52
121
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
122
|
+
// See the identical fix (and its doc) in `createRigidBody` above - a trigger's collision
|
|
123
|
+
// geometry needs the same real `Body.setPosition`/`Body.setAngle` treatment, not a raw
|
|
124
|
+
// `.position.x`/`.position.y`/`.angle` field write.
|
|
125
|
+
Body.setPosition(nativeBody, Vector.create(((_a = transform === null || transform === void 0 ? void 0 : transform.position) === null || _a === void 0 ? void 0 : _a.x) || 0, ((_b = transform === null || transform === void 0 ? void 0 : transform.position) === null || _b === void 0 ? void 0 : _b.y) || 0));
|
|
126
|
+
Body.setAngle(nativeBody, (transform === null || transform === void 0 ? void 0 : transform.rotation) || 0);
|
|
56
127
|
if (!this.world) {
|
|
57
128
|
throw new Error('MatterFactory: World not set. Make sure the factory is created by MatterWorldComponent.');
|
|
58
129
|
}
|
|
59
130
|
return new MatterTriggerComponent(nativeBody, descriptor, this.world);
|
|
60
131
|
}
|
|
132
|
+
createCharacterController(options, transform) {
|
|
133
|
+
return new MatterCharacterControllerComponent(this.world, options, transform);
|
|
134
|
+
}
|
|
135
|
+
createShapeParts(shape, options) {
|
|
136
|
+
switch (shape.shape) {
|
|
137
|
+
case 'BOX':
|
|
138
|
+
return [Bodies.rectangle(0, 0, shape.dimensions.x, shape.dimensions.y, options)];
|
|
139
|
+
case 'CIRCLE':
|
|
140
|
+
return [Bodies.circle(0, 0, shape.radius, options)];
|
|
141
|
+
case 'CAPSULE':
|
|
142
|
+
return [
|
|
143
|
+
Bodies.rectangle(0, 0, shape.radius * 2, shape.centersDistance + shape.radius * 2, Object.assign(Object.assign({}, options), { chamfer: { radius: shape.radius } })),
|
|
144
|
+
];
|
|
145
|
+
case 'CONVEX_HULL':
|
|
146
|
+
return [Bodies.fromVertices(0, 0, [Pnt2.hull(shape.vertices).map(v => Vector.create(v.x, v.y))], options)];
|
|
147
|
+
case 'POLYGON':
|
|
148
|
+
return [Bodies.fromVertices(0, 0, [shape.vertices.map(v => Vector.create(v.x, v.y))], options)];
|
|
149
|
+
case 'COMPOUND': {
|
|
150
|
+
const parts = [];
|
|
151
|
+
for (const { position, rotation, shape: childShape } of shape.children) {
|
|
152
|
+
const childParts = this.createShapeParts(childShape, options);
|
|
153
|
+
for (const part of childParts) {
|
|
154
|
+
Body.setPosition(part, Vector.add(Vector.rotate(part.position, rotation || 0), Vector.create((position === null || position === void 0 ? void 0 : position.x) || 0, (position === null || position === void 0 ? void 0 : position.y) || 0)));
|
|
155
|
+
Body.setAngle(part, part.angle + (rotation || 0));
|
|
156
|
+
}
|
|
157
|
+
parts.push(...childParts);
|
|
158
|
+
}
|
|
159
|
+
return parts;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
createShapeBody(shape, options) {
|
|
164
|
+
return Body.create(Object.assign({ parts: this.createShapeParts(shape, options) }, options));
|
|
165
|
+
}
|
|
61
166
|
/**
|
|
62
167
|
* matter-js's own body model only has `isStatic` - no distinct kinematic body type (position-
|
|
63
168
|
* driven, but still pushes/wakes dynamic bodies it moves into) and no continuous collision
|
package/dist/types.d.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { MatterFactory } from './matter-factory';
|
|
2
2
|
import { MatterRigidBodyComponent } from './components/matter-rigid-body.component';
|
|
3
3
|
import { MatterTriggerComponent } from './components/matter-trigger.component';
|
|
4
|
+
import { MatterCharacterControllerComponent } from './components/matter-character-controller.component';
|
|
4
5
|
import { Gg2dWorld, Gg2dWorldSceneTypeDocPPatch, Gg2dWorldTypeDocPPatch } from '@gg-web-engine/core';
|
|
5
6
|
import { MatterWorldComponent } from './components/matter-world.component';
|
|
6
7
|
export type MatterPhysicsTypeDocRepo = {
|
|
7
8
|
factory: MatterFactory;
|
|
8
9
|
rigidBody: MatterRigidBodyComponent;
|
|
9
10
|
trigger: MatterTriggerComponent;
|
|
11
|
+
characterController: MatterCharacterControllerComponent;
|
|
10
12
|
};
|
|
11
13
|
export type MatterTypeDoc = Gg2dWorldTypeDocPPatch<MatterPhysicsTypeDocRepo>;
|
|
12
14
|
export type MatterSceneTypeDoc = Gg2dWorldSceneTypeDocPPatch<MatterPhysicsTypeDocRepo, MatterWorldComponent>;
|