@gg-web-engine/matter 0.0.72 → 0.0.74
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +43 -2
- package/dist/components/matter-rigid-body.component.js +68 -5
- package/dist/components/matter-trigger.component.d.ts +66 -4
- package/dist/components/matter-trigger.component.js +139 -4
- package/dist/components/matter-world.component.d.ts +44 -5
- package/dist/components/matter-world.component.js +131 -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-sleep.spec.ts +79 -0
- package/test/components/matter-rigid-body.component.spec.ts +98 -0
- package/test/components/matter-trigger.component.spec.ts +135 -13
- package/test/components/matter-world.component.spec.ts +44 -9
|
@@ -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;
|
|
@@ -79,6 +118,18 @@ export class MatterWorldComponent {
|
|
|
79
118
|
this.handleCollisionStart = this.handleCollisionStart.bind(this);
|
|
80
119
|
this.handleCollisionEnd = this.handleCollisionEnd.bind(this);
|
|
81
120
|
}
|
|
121
|
+
/**
|
|
122
|
+
* `Engine.create` is never given `enableSleeping: true` here, matching matter-js's own default -
|
|
123
|
+
* so a body this world creates never *naturally* falls asleep from prolonged inactivity, no matter
|
|
124
|
+
* how long it rests. `IRigidBodyComponent.sleep()`/`wakeUp()` still work regardless of this flag
|
|
125
|
+
* (`Matter.Sleeping.set` writes a body's `isSleeping` flag directly, and `Engine.update`'s own
|
|
126
|
+
* per-body integration skip checks that flag unconditionally, not gated behind
|
|
127
|
+
* `engine.enableSleeping` - only the *automatic* fall-asleep-from-rest/wake-on-collision behavior
|
|
128
|
+
* is gated behind that flag) - only automatic, inactivity-driven sleeping is unavailable under the
|
|
129
|
+
* current setup. A future change enabling it should re-check
|
|
130
|
+
* `gg-engine-physics-adapter-matter`'s own note on this before assuming every existing
|
|
131
|
+
* position/velocity setter still behaves the same way once bodies can sleep on their own.
|
|
132
|
+
*/
|
|
82
133
|
init() {
|
|
83
134
|
return __awaiter(this, void 0, void 0, function* () {
|
|
84
135
|
this.matterEngine_ = Engine.create({
|
|
@@ -89,7 +140,11 @@ export class MatterWorldComponent {
|
|
|
89
140
|
});
|
|
90
141
|
}
|
|
91
142
|
findRigidBody(nativeBody) {
|
|
92
|
-
|
|
143
|
+
// a character controller's phantom body is never added to `Composite`/`engine.world` (see its
|
|
144
|
+
// own doc), so it can never actually be `pair.bodyA`/`pair.bodyB` here - this narrows the lookup's
|
|
145
|
+
// type back down since `handleIdEntityMap` itself now also tracks that component class.
|
|
146
|
+
const comp = this.handleIdEntityMap.get(nativeBody.id);
|
|
147
|
+
return comp instanceof MatterRigidBodyComponent ? comp : undefined;
|
|
93
148
|
}
|
|
94
149
|
/**
|
|
95
150
|
* Wires `IRigidBody2dComponent.onCollisionStart` for every non-sensor pair in the world, off
|
|
@@ -173,8 +228,79 @@ export class MatterWorldComponent {
|
|
|
173
228
|
Engine.update(this.matterEngine, delta, this.lastDelta > 0 ? delta / this.lastDelta : 1);
|
|
174
229
|
this.lastDelta = delta;
|
|
175
230
|
}
|
|
231
|
+
/**
|
|
232
|
+
* matter-js has no native raycast query. Built on a true parametric ray-vs-polygon intersection
|
|
233
|
+
* (`segmentIntersection`/`bodyPolygons`), not `Matter.Query.ray` - that helper is only a thin
|
|
234
|
+
* wrapper over `Query.collides` (a full-geometry SAT test of a synthetic, very thin rectangle body
|
|
235
|
+
* against candidates), which reports an approximate overlap contact point, not a true "where does
|
|
236
|
+
* the ray segment first cross this body's boundary" point; verified via this adapter's own
|
|
237
|
+
* regression suite (`MatterWorldComponent.spec.ts`'s `Raycast` block expects a precise hit point at
|
|
238
|
+
* a known box edge) that the SAT-approximated version produces a visibly wrong point.
|
|
239
|
+
*
|
|
240
|
+
* `Query.collides`/`Collision.collides` test raw geometry only and know nothing about
|
|
241
|
+
* `collisionFilter` (the same limitation `MatterCharacterControllerComponent.collectObstacles`'s
|
|
242
|
+
* own doc describes) - candidates are pre-filtered here via `Detector.canCollide` itself, called
|
|
243
|
+
* directly against a synthetic filter built from `options.collisionFilterGroups`/
|
|
244
|
+
* `collisionFilterMask`. Sensor bodies (triggers) are excluded from candidates entirely, mirroring
|
|
245
|
+
* `collectObstacles`'s own exclusion - a trigger never physically blocks anything, so it shouldn't
|
|
246
|
+
* register as a raycast hit either.
|
|
247
|
+
*
|
|
248
|
+
* `hitNormal` is derived from whichever polygon edge the closest intersection landed on (rotated
|
|
249
|
+
* 90°, sign chosen to point back towards `options.from`) - not from a matter-js collision object at
|
|
250
|
+
* all, sidestepping the sign-convention pitfall `handleCollisionStart`'s own doc describes for
|
|
251
|
+
* `pair.collision.normal`.
|
|
252
|
+
*/
|
|
176
253
|
raycast(options) {
|
|
177
|
-
|
|
254
|
+
var _a;
|
|
255
|
+
const matterWorld = this.matterWorld;
|
|
256
|
+
const direction = Pnt2.sub(options.to, options.from);
|
|
257
|
+
const rayLength = Pnt2.len(direction);
|
|
258
|
+
if (!matterWorld || rayLength === 0) {
|
|
259
|
+
return { hasHit: false };
|
|
260
|
+
}
|
|
261
|
+
const groupsMask = options.collisionFilterGroups
|
|
262
|
+
? BitMask.pack(options.collisionFilterGroups, 16)
|
|
263
|
+
: BitMask.full(16);
|
|
264
|
+
const maskMask = options.collisionFilterMask ? BitMask.pack(options.collisionFilterMask, 16) : BitMask.full(16);
|
|
265
|
+
const rayFilter = { category: groupsMask, mask: maskMask, group: 0 };
|
|
266
|
+
const candidates = Composite.allBodies(matterWorld).filter(b => !b.isSensor && Detector.canCollide(rayFilter, b.collisionFilter));
|
|
267
|
+
let closestT = Infinity;
|
|
268
|
+
let closestPoint = null;
|
|
269
|
+
let closestNormal = null;
|
|
270
|
+
let closestBody = null;
|
|
271
|
+
for (const body of candidates) {
|
|
272
|
+
for (const polygon of bodyPolygons(body)) {
|
|
273
|
+
for (let i = 0; i < polygon.length; i++) {
|
|
274
|
+
const a = polygon[i];
|
|
275
|
+
const b = polygon[(i + 1) % polygon.length];
|
|
276
|
+
const hit = segmentIntersection(options.from, options.to, a, b);
|
|
277
|
+
if (!hit || hit.t >= closestT) {
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
closestT = hit.t;
|
|
281
|
+
closestPoint = hit.point;
|
|
282
|
+
closestBody = body;
|
|
283
|
+
const edge = Pnt2.sub(b, a);
|
|
284
|
+
const normal = Pnt2.norm({ x: -edge.y, y: edge.x });
|
|
285
|
+
closestNormal = Pnt2.dot(normal, Pnt2.sub(options.from, hit.point)) >= 0 ? normal : Pnt2.neg(normal);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
if (!closestBody) {
|
|
290
|
+
return { hasHit: false };
|
|
291
|
+
}
|
|
292
|
+
// A compound body's hit part is not the parent body `handleIdEntityMap` is keyed by (see
|
|
293
|
+
// `MatterFactory`'s compound-shape flattening) - `.parent` (self-referential for a non-compound
|
|
294
|
+
// body) resolves back to it.
|
|
295
|
+
const parentBody = (_a = closestBody.parent) !== null && _a !== void 0 ? _a : closestBody;
|
|
296
|
+
const resolved = this.handleIdEntityMap.get(parentBody.id);
|
|
297
|
+
return {
|
|
298
|
+
hasHit: true,
|
|
299
|
+
hitBody: resolved instanceof MatterRigidBodyComponent ? resolved : undefined,
|
|
300
|
+
hitDistance: closestT * rayLength,
|
|
301
|
+
hitPoint: closestPoint,
|
|
302
|
+
hitNormal: closestNormal,
|
|
303
|
+
};
|
|
178
304
|
}
|
|
179
305
|
dispose() {
|
|
180
306
|
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>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gg-web-engine/matter",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.74",
|
|
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,11 +27,12 @@
|
|
|
27
27
|
},
|
|
28
28
|
"homepage": "https://github.com/AndyGura/gg-web-engine#readme",
|
|
29
29
|
"dependencies": {
|
|
30
|
+
"poly-decomp": "^0.3.0"
|
|
30
31
|
},
|
|
31
32
|
"devDependencies": {
|
|
32
|
-
"@
|
|
33
|
-
"@gg-web-engine/core": "0.0.72",
|
|
33
|
+
"@gg-web-engine/core": "0.0.74",
|
|
34
34
|
"@types/jest": "^30.0.0",
|
|
35
|
+
"@types/matter-js": "0.20.2",
|
|
35
36
|
"@types/node": "^26.4.0",
|
|
36
37
|
"jest": "^30.5.0",
|
|
37
38
|
"jest-environment-jsdom": "^30.5.0",
|
|
@@ -42,7 +43,7 @@
|
|
|
42
43
|
"typescript": "~6.0.3"
|
|
43
44
|
},
|
|
44
45
|
"peerDependencies": {
|
|
45
|
-
"@gg-web-engine/core": "0.0.
|
|
46
|
+
"@gg-web-engine/core": "0.0.74",
|
|
46
47
|
"matter-js": "0.20.0",
|
|
47
48
|
"rxjs": "7.8.2"
|
|
48
49
|
},
|