@gg-web-engine/matter 0.0.77 → 0.0.78
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-rigid-body.component.d.ts +4 -2
- package/dist/components/matter-rigid-body.component.js +32 -8
- package/dist/components/matter-trigger.component.js +3 -1
- package/dist/matter-factory.d.ts +0 -16
- package/dist/matter-factory.js +3 -186
- package/dist/matter-rigid-body-builder.d.ts +24 -0
- package/dist/matter-rigid-body-builder.js +168 -0
- package/package.json +3 -3
- package/test/components/matter-rigid-body-sleep.spec.ts +13 -0
- package/test/components/matter-rigid-body.component.spec.ts +105 -0
- package/test/components/matter-trigger.component.spec.ts +42 -0
|
@@ -7,6 +7,7 @@ export declare class MatterRigidBodyComponent implements IRigidBody2dComponent<M
|
|
|
7
7
|
readonly shape: Shape2DDescriptor;
|
|
8
8
|
readonly bodyType: BodyType;
|
|
9
9
|
readonly ccd: boolean;
|
|
10
|
+
readonly canSleep: boolean;
|
|
10
11
|
get position(): Point2;
|
|
11
12
|
set position(value: Point2);
|
|
12
13
|
get rotation(): number;
|
|
@@ -43,7 +44,7 @@ export declare class MatterRigidBodyComponent implements IRigidBody2dComponent<M
|
|
|
43
44
|
* `notifyCollisionStart`/`notifyCollisionEnd`, called by `MatterWorldComponent`'s single
|
|
44
45
|
* world-wide `collisionStart`/`collisionEnd` listener - not touched directly by anything else. */
|
|
45
46
|
protected readonly currentContacts: Set<MatterRigidBodyComponent>;
|
|
46
|
-
constructor(nativeBody: Body, shape: Shape2DDescriptor, bodyType?: BodyType, ccd?: boolean);
|
|
47
|
+
constructor(nativeBody: Body, shape: Shape2DDescriptor, bodyType?: BodyType, ccd?: boolean, canSleep?: boolean);
|
|
47
48
|
/** @internal called by `MatterWorldComponent`'s global `collisionStart` listener - not part of
|
|
48
49
|
* this component's public API surface. */
|
|
49
50
|
notifyCollisionStart(collisionEvent: CollisionEvent<Point2, MatterRigidBodyComponent>): void;
|
|
@@ -80,7 +81,8 @@ export declare class MatterRigidBodyComponent implements IRigidBody2dComponent<M
|
|
|
80
81
|
*/
|
|
81
82
|
wakeUp(): void;
|
|
82
83
|
/**
|
|
83
|
-
* No-op on a body that reports `isStatic` (see `isSleeping`'s own doc)
|
|
84
|
+
* No-op on a body that reports `isStatic` (see `isSleeping`'s own doc) or was created with
|
|
85
|
+
* `canSleep: false`. Forces sleep immediately,
|
|
84
86
|
* regardless of whether the world's `Matter.Engine` has `enableSleeping` turned on - unlike a
|
|
85
87
|
* body naturally falling asleep from inactivity (which requires that engine flag), an explicit
|
|
86
88
|
* `Sleeping.set(body, true)` call takes effect either way.
|
|
@@ -1,5 +1,17 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
1
12
|
import { BitMask, DebugBody2DSettings, Pnt2, } from '@gg-web-engine/core';
|
|
2
13
|
import { Body, Composite, Sleeping, Vector } from 'matter-js';
|
|
14
|
+
import { buildMatterRigidBody } from '../matter-rigid-body-builder';
|
|
3
15
|
import { Subject } from 'rxjs';
|
|
4
16
|
// FIXME why this needs to be introduced? investigate units in matter.js
|
|
5
17
|
const MATTER_VELOCITY_SCALE = 0.0166667;
|
|
@@ -47,6 +59,7 @@ export class MatterRigidBodyComponent {
|
|
|
47
59
|
friction: this.nativeBody.friction,
|
|
48
60
|
restitution: this.nativeBody.restitution,
|
|
49
61
|
ccd: this.ccd,
|
|
62
|
+
canSleep: this.canSleep,
|
|
50
63
|
ownCollisionGroups: this.ownCollisionGroups,
|
|
51
64
|
interactWithCollisionGroups: this.interactWithCollisionGroups,
|
|
52
65
|
};
|
|
@@ -57,11 +70,12 @@ export class MatterRigidBodyComponent {
|
|
|
57
70
|
get onCollisionEnd() {
|
|
58
71
|
return this.onCollisionEnd$.asObservable();
|
|
59
72
|
}
|
|
60
|
-
constructor(nativeBody, shape, bodyType = 'dynamic', ccd = false) {
|
|
73
|
+
constructor(nativeBody, shape, bodyType = 'dynamic', ccd = false, canSleep = true) {
|
|
61
74
|
this.nativeBody = nativeBody;
|
|
62
75
|
this.shape = shape;
|
|
63
76
|
this.bodyType = bodyType;
|
|
64
77
|
this.ccd = ccd;
|
|
78
|
+
this.canSleep = canSleep;
|
|
65
79
|
this.name = '';
|
|
66
80
|
this.entity = null;
|
|
67
81
|
// matter-js has no native kinematic body - a body requested as `kinematic_pos`/`kinematic_vel`
|
|
@@ -83,6 +97,11 @@ export class MatterRigidBodyComponent {
|
|
|
83
97
|
* world-wide `collisionStart`/`collisionEnd` listener - not touched directly by anything else. */
|
|
84
98
|
this.currentContacts = new Set();
|
|
85
99
|
this.updateCollisionFilter();
|
|
100
|
+
if (!canSleep) {
|
|
101
|
+
// this adapter's engine never sleeps bodies on its own, but an app may turn on
|
|
102
|
+
// `engine.enableSleeping` - an infinite threshold keeps this body awake even then
|
|
103
|
+
nativeBody.sleepThreshold = Infinity;
|
|
104
|
+
}
|
|
86
105
|
}
|
|
87
106
|
/** @internal called by `MatterWorldComponent`'s global `collisionStart` listener - not part of
|
|
88
107
|
* this component's public API surface. */
|
|
@@ -134,11 +153,15 @@ export class MatterRigidBodyComponent {
|
|
|
134
153
|
this.nativeBody.collisionFilter = Object.assign(Object.assign({}, this.nativeBody.collisionFilter), { category: this._ownCGsMask, mask: this._interactWithCGsMask });
|
|
135
154
|
}
|
|
136
155
|
clone() {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
return
|
|
156
|
+
// Rebuilt from the shape and options rather than `Body.create({ ...this.nativeBody })`: a native
|
|
157
|
+
// body references itself (`parts[0]` and `parent` are the body itself), so matter-js's deep
|
|
158
|
+
// option merge recurses forever on it.
|
|
159
|
+
const _a = this.bodyOptions, { mass } = _a, options = __rest(_a, ["mass"]);
|
|
160
|
+
return buildMatterRigidBody({
|
|
161
|
+
shape: this.shape,
|
|
162
|
+
// a static body reports an infinite mass, which isn't a valid option to build one from
|
|
163
|
+
body: this.bodyType === 'dynamic' ? Object.assign(Object.assign({}, options), { mass }) : options,
|
|
164
|
+
}, { position: this.position, rotation: this.rotation });
|
|
142
165
|
}
|
|
143
166
|
addToWorld(world) {
|
|
144
167
|
Composite.add(world.physicsWorld.matterWorld, this.nativeBody);
|
|
@@ -199,13 +222,14 @@ export class MatterRigidBodyComponent {
|
|
|
199
222
|
Sleeping.set(this.nativeBody, false);
|
|
200
223
|
}
|
|
201
224
|
/**
|
|
202
|
-
* No-op on a body that reports `isStatic` (see `isSleeping`'s own doc)
|
|
225
|
+
* No-op on a body that reports `isStatic` (see `isSleeping`'s own doc) or was created with
|
|
226
|
+
* `canSleep: false`. Forces sleep immediately,
|
|
203
227
|
* regardless of whether the world's `Matter.Engine` has `enableSleeping` turned on - unlike a
|
|
204
228
|
* body naturally falling asleep from inactivity (which requires that engine flag), an explicit
|
|
205
229
|
* `Sleeping.set(body, true)` call takes effect either way.
|
|
206
230
|
*/
|
|
207
231
|
sleep() {
|
|
208
|
-
if (this.nativeBody.isStatic) {
|
|
232
|
+
if (this.nativeBody.isStatic || !this.canSleep) {
|
|
209
233
|
return;
|
|
210
234
|
}
|
|
211
235
|
Sleeping.set(this.nativeBody, true);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { map, merge, Subject } from 'rxjs';
|
|
2
2
|
import { Body, Detector, Events, Query } from 'matter-js';
|
|
3
3
|
import { MatterRigidBodyComponent } from './matter-rigid-body.component';
|
|
4
|
+
import { buildMatterTriggerBody } from '../matter-rigid-body-builder';
|
|
4
5
|
import { MatterCharacterControllerComponent } from './matter-character-controller.component';
|
|
5
6
|
import { DebugBody2DSettings } from '@gg-web-engine/core';
|
|
6
7
|
export class MatterTriggerComponent extends MatterRigidBodyComponent {
|
|
@@ -230,7 +231,8 @@ export class MatterTriggerComponent extends MatterRigidBodyComponent {
|
|
|
230
231
|
}
|
|
231
232
|
}
|
|
232
233
|
clone() {
|
|
233
|
-
|
|
234
|
+
// rebuilt from the shape, for the same reason as in `MatterRigidBodyComponent.clone()`
|
|
235
|
+
const clonedBody = buildMatterTriggerBody(this.shape, { position: this.position, rotation: this.rotation });
|
|
234
236
|
const component = new MatterTriggerComponent(clonedBody, this.shape, this.world);
|
|
235
237
|
component.ownCollisionGroups = this.ownCollisionGroups;
|
|
236
238
|
component.interactWithCollisionGroups = this.interactWithCollisionGroups;
|
package/dist/matter-factory.d.ts
CHANGED
|
@@ -19,20 +19,4 @@ export declare class MatterFactory implements IPhysicsBody2dComponentFactory<Mat
|
|
|
19
19
|
position?: Point2;
|
|
20
20
|
rotation?: number;
|
|
21
21
|
}): MatterCharacterControllerComponent;
|
|
22
|
-
private createShapeParts;
|
|
23
|
-
private createShapeBody;
|
|
24
|
-
/**
|
|
25
|
-
* matter-js's own body model only has `isStatic` - no distinct kinematic body type (position-
|
|
26
|
-
* driven, but still pushes/wakes dynamic bodies it moves into) and no continuous collision
|
|
27
|
-
* detection at all, at any level. Both are long-standing, documented upstream limitations, not
|
|
28
|
-
* something this adapter package failed to wire up - see `BodyOptions.kinematic_pos`/
|
|
29
|
-
* `ccd`'s own doc in `packages/core` for what each is supposed to do.
|
|
30
|
-
*
|
|
31
|
-
* `kinematic_pos`/`kinematic_vel` fall back to `isStatic: true` - the closest matter-js has - so
|
|
32
|
-
* a caller gets *a* body rather than a thrown error, but with the exact same caveat as
|
|
33
|
-
* teleporting a `static` body's position by hand (see that doc): resting bodies on top won't be
|
|
34
|
-
* pushed or woken correctly. `ccd: true` is silently accepted as a no-op beyond the warning below -
|
|
35
|
-
* a fast-moving or fast-driven body can still tunnel clean through thin geometry in one step.
|
|
36
|
-
*/
|
|
37
|
-
private transformOptions;
|
|
38
22
|
}
|
package/dist/matter-factory.js
CHANGED
|
@@ -1,203 +1,20 @@
|
|
|
1
|
-
import { Pnt2, warnOnce, } from '@gg-web-engine/core';
|
|
2
|
-
import { MatterRigidBodyComponent } from './components/matter-rigid-body.component';
|
|
3
1
|
import { MatterTriggerComponent } from './components/matter-trigger.component';
|
|
4
2
|
import { MatterCharacterControllerComponent } from './components/matter-character-controller.component';
|
|
5
|
-
import {
|
|
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);
|
|
15
|
-
/**
|
|
16
|
-
* `kinematic_pos`/`kinematic_vel`/`ccd` have no native matter-js equivalent at all - unlike
|
|
17
|
-
* `packages/ammo`/`packages/rapier2d`/`packages/rapier3d`, this package can only warn and fall
|
|
18
|
-
* back rather than actually implement either (see `transformOptions`'s own doc). Warned once per
|
|
19
|
-
* distinct message (via core's `warnOnce`) rather than once per body/tick - an app that spawns many
|
|
20
|
-
* kinematic props (or requests `ccd` on many fast-moving bodies) would otherwise flood the console
|
|
21
|
-
* with an identical warning on every single one, which teaches a developer to ignore the console
|
|
22
|
-
* rather than to fix the one call site that needs it.
|
|
23
|
-
*/
|
|
24
|
-
function warnUnsupportedOnce(message) {
|
|
25
|
-
warnOnce(`[@gg-web-engine/matter] ${message}`);
|
|
26
|
-
}
|
|
3
|
+
import { buildMatterRigidBody, buildMatterTriggerBody } from './matter-rigid-body-builder';
|
|
27
4
|
export class MatterFactory {
|
|
28
5
|
constructor(world) {
|
|
29
6
|
this.world = world;
|
|
30
7
|
}
|
|
31
8
|
createRigidBody(descriptor, transform) {
|
|
32
|
-
|
|
33
|
-
let nativeBody = null;
|
|
34
|
-
switch (descriptor.shape.shape) {
|
|
35
|
-
case 'BOX':
|
|
36
|
-
nativeBody = Bodies.rectangle(0, 0, descriptor.shape.dimensions.x, descriptor.shape.dimensions.y, this.transformOptions(descriptor.body));
|
|
37
|
-
break;
|
|
38
|
-
case 'CIRCLE':
|
|
39
|
-
nativeBody = Bodies.circle(0, 0, descriptor.shape.radius, this.transformOptions(descriptor.body));
|
|
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;
|
|
57
|
-
}
|
|
58
|
-
if (!nativeBody) {
|
|
59
|
-
throw new Error(`Shape "${descriptor.shape}" not implemented for Matter.js`);
|
|
60
|
-
}
|
|
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;
|
|
9
|
+
return buildMatterRigidBody(descriptor, transform);
|
|
86
10
|
}
|
|
87
11
|
createTrigger(descriptor, transform) {
|
|
88
|
-
var _a, _b;
|
|
89
|
-
let nativeBody = null;
|
|
90
|
-
switch (descriptor.shape) {
|
|
91
|
-
case 'BOX':
|
|
92
|
-
nativeBody = Bodies.rectangle(0, 0, descriptor.dimensions.x, descriptor.dimensions.y, { isSensor: true });
|
|
93
|
-
break;
|
|
94
|
-
case 'CIRCLE':
|
|
95
|
-
nativeBody = Bodies.circle(0, 0, descriptor.radius, { isSensor: true });
|
|
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;
|
|
118
|
-
}
|
|
119
|
-
if (!nativeBody) {
|
|
120
|
-
throw new Error(`Shape "${descriptor.shape}" not implemented for Matter.js`);
|
|
121
|
-
}
|
|
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);
|
|
127
12
|
if (!this.world) {
|
|
128
13
|
throw new Error('MatterFactory: World not set. Make sure the factory is created by MatterWorldComponent.');
|
|
129
14
|
}
|
|
130
|
-
return new MatterTriggerComponent(
|
|
15
|
+
return new MatterTriggerComponent(buildMatterTriggerBody(descriptor, transform), descriptor, this.world);
|
|
131
16
|
}
|
|
132
17
|
createCharacterController(options, transform) {
|
|
133
18
|
return new MatterCharacterControllerComponent(this.world, options, transform);
|
|
134
19
|
}
|
|
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
|
-
}
|
|
166
|
-
/**
|
|
167
|
-
* matter-js's own body model only has `isStatic` - no distinct kinematic body type (position-
|
|
168
|
-
* driven, but still pushes/wakes dynamic bodies it moves into) and no continuous collision
|
|
169
|
-
* detection at all, at any level. Both are long-standing, documented upstream limitations, not
|
|
170
|
-
* something this adapter package failed to wire up - see `BodyOptions.kinematic_pos`/
|
|
171
|
-
* `ccd`'s own doc in `packages/core` for what each is supposed to do.
|
|
172
|
-
*
|
|
173
|
-
* `kinematic_pos`/`kinematic_vel` fall back to `isStatic: true` - the closest matter-js has - so
|
|
174
|
-
* a caller gets *a* body rather than a thrown error, but with the exact same caveat as
|
|
175
|
-
* teleporting a `static` body's position by hand (see that doc): resting bodies on top won't be
|
|
176
|
-
* pushed or woken correctly. `ccd: true` is silently accepted as a no-op beyond the warning below -
|
|
177
|
-
* a fast-moving or fast-driven body can still tunnel clean through thin geometry in one step.
|
|
178
|
-
*/
|
|
179
|
-
transformOptions(options) {
|
|
180
|
-
if (options.bodyType === 'kinematic_pos' || options.bodyType === 'kinematic_vel') {
|
|
181
|
-
warnUnsupportedOnce(`bodyType: '${options.bodyType}' has no matter-js equivalent - falling back to a plain static ` +
|
|
182
|
-
"body. Resting bodies won't be pushed or woken when you move it, the same as manually " +
|
|
183
|
-
"teleporting a static body's position. See Body2DOptions.kinematic_pos/kinematic_vel's doc.");
|
|
184
|
-
}
|
|
185
|
-
if (options.ccd) {
|
|
186
|
-
warnUnsupportedOnce('ccd: true has no matter-js equivalent (no continuous collision detection at all) - ignored. ' +
|
|
187
|
-
'A fast-moving or fast-driven body can still tunnel through thin geometry. See ' +
|
|
188
|
-
"Body2DOptions.ccd's doc.");
|
|
189
|
-
}
|
|
190
|
-
const res = {
|
|
191
|
-
isStatic: options.bodyType !== undefined ? options.bodyType != 'dynamic' : !options.mass,
|
|
192
|
-
mass: options.mass,
|
|
193
|
-
restitution: options.restitution,
|
|
194
|
-
friction: options.friction,
|
|
195
|
-
};
|
|
196
|
-
for (const key in res) {
|
|
197
|
-
if (res[key] === undefined) {
|
|
198
|
-
delete res[key];
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
return res;
|
|
202
|
-
}
|
|
203
20
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { BodyShape2DDescriptor, Point2, Shape2DDescriptor } from '@gg-web-engine/core';
|
|
2
|
+
import { Body, IChamferableBodyDefinition } from 'matter-js';
|
|
3
|
+
import { MatterRigidBodyComponent } from './components/matter-rigid-body.component';
|
|
4
|
+
/**
|
|
5
|
+
* Builds a rigid body for `MatterFactory.createRigidBody`. A plain function because building one
|
|
6
|
+
* never needs the world (it's added to one later, in `addToWorld`), which lets
|
|
7
|
+
* `MatterRigidBodyComponent.clone()` rebuild its body from its own shape and options without a
|
|
8
|
+
* factory instance at hand, and without importing the factory module: that module imports
|
|
9
|
+
* `MatterTriggerComponent`, which extends `MatterRigidBodyComponent`, so importing it from the
|
|
10
|
+
* rigid body's own module is a circular import that breaks the `extends`.
|
|
11
|
+
*/
|
|
12
|
+
export declare function buildMatterRigidBody(descriptor: BodyShape2DDescriptor, transform?: {
|
|
13
|
+
position?: Point2;
|
|
14
|
+
rotation?: number;
|
|
15
|
+
}): MatterRigidBodyComponent;
|
|
16
|
+
export declare function createShapeBody(shape: Shape2DDescriptor, options: IChamferableBodyDefinition): Body;
|
|
17
|
+
/**
|
|
18
|
+
* Builds the sensor body of a trigger, for `MatterFactory.createTrigger` and
|
|
19
|
+
* `MatterTriggerComponent.clone()`.
|
|
20
|
+
*/
|
|
21
|
+
export declare function buildMatterTriggerBody(shape: Shape2DDescriptor, transform?: {
|
|
22
|
+
position?: Point2;
|
|
23
|
+
rotation?: number;
|
|
24
|
+
}): Body;
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { Pnt2, warnOnce, } from '@gg-web-engine/core';
|
|
2
|
+
import { Bodies, Body, Common, Vector } from 'matter-js';
|
|
3
|
+
import * as decomp from 'poly-decomp';
|
|
4
|
+
import { MatterRigidBodyComponent } from './components/matter-rigid-body.component';
|
|
5
|
+
/**
|
|
6
|
+
* `Bodies.fromVertices` (used for the `POLYGON` shape below) only actually decomposes a concave
|
|
7
|
+
* vertex set into convex parts when a decomposition library is registered via `Common.setDecomp` -
|
|
8
|
+
* without it, matter-js silently falls back to the convex hull of the given vertices, so a concave
|
|
9
|
+
* `POLYGON` would render/collide as if it were convex. Registering `poly-decomp` here, once, at
|
|
10
|
+
* module load makes `Bodies.fromVertices` actually decompose concave outlines everywhere it's used.
|
|
11
|
+
*/
|
|
12
|
+
Common.setDecomp(decomp);
|
|
13
|
+
/**
|
|
14
|
+
* `kinematic_pos`/`kinematic_vel`/`ccd` have no native matter-js equivalent at all - unlike
|
|
15
|
+
* `packages/ammo`/`packages/rapier2d`/`packages/rapier3d`, this package can only warn and fall
|
|
16
|
+
* back rather than actually implement either (see `transformOptions`'s own doc). Warned once per
|
|
17
|
+
* distinct message (via core's `warnOnce`) rather than once per body/tick - an app that spawns many
|
|
18
|
+
* kinematic props (or requests `ccd` on many fast-moving bodies) would otherwise flood the console
|
|
19
|
+
* with an identical warning on every single one, which teaches a developer to ignore the console
|
|
20
|
+
* rather than to fix the one call site that needs it.
|
|
21
|
+
*/
|
|
22
|
+
function warnUnsupportedOnce(message) {
|
|
23
|
+
warnOnce(`[@gg-web-engine/matter] ${message}`);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Builds a rigid body for `MatterFactory.createRigidBody`. A plain function because building one
|
|
27
|
+
* never needs the world (it's added to one later, in `addToWorld`), which lets
|
|
28
|
+
* `MatterRigidBodyComponent.clone()` rebuild its body from its own shape and options without a
|
|
29
|
+
* factory instance at hand, and without importing the factory module: that module imports
|
|
30
|
+
* `MatterTriggerComponent`, which extends `MatterRigidBodyComponent`, so importing it from the
|
|
31
|
+
* rigid body's own module is a circular import that breaks the `extends`.
|
|
32
|
+
*/
|
|
33
|
+
export function buildMatterRigidBody(descriptor, transform) {
|
|
34
|
+
var _a, _b, _c;
|
|
35
|
+
let nativeBody = null;
|
|
36
|
+
switch (descriptor.shape.shape) {
|
|
37
|
+
case 'BOX':
|
|
38
|
+
nativeBody = Bodies.rectangle(0, 0, descriptor.shape.dimensions.x, descriptor.shape.dimensions.y, transformOptions(descriptor.body));
|
|
39
|
+
break;
|
|
40
|
+
case 'CIRCLE':
|
|
41
|
+
nativeBody = Bodies.circle(0, 0, descriptor.shape.radius, transformOptions(descriptor.body));
|
|
42
|
+
break;
|
|
43
|
+
case 'CAPSULE':
|
|
44
|
+
nativeBody = Bodies.rectangle(0, 0, descriptor.shape.radius * 2, descriptor.shape.centersDistance + descriptor.shape.radius * 2, Object.assign(Object.assign({}, transformOptions(descriptor.body)), { chamfer: {
|
|
45
|
+
radius: descriptor.shape.radius,
|
|
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))], 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))], transformOptions(descriptor.body));
|
|
53
|
+
break;
|
|
54
|
+
case 'COMPOUND':
|
|
55
|
+
nativeBody = createShapeBody(descriptor.shape, transformOptions(descriptor.body));
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
if (!nativeBody) {
|
|
59
|
+
throw new Error(`Shape "${descriptor.shape}" not implemented for Matter.js`);
|
|
60
|
+
}
|
|
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, descriptor.body.canSleep !== false);
|
|
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;
|
|
86
|
+
}
|
|
87
|
+
function createShapeParts(shape, options) {
|
|
88
|
+
switch (shape.shape) {
|
|
89
|
+
case 'BOX':
|
|
90
|
+
return [Bodies.rectangle(0, 0, shape.dimensions.x, shape.dimensions.y, options)];
|
|
91
|
+
case 'CIRCLE':
|
|
92
|
+
return [Bodies.circle(0, 0, shape.radius, options)];
|
|
93
|
+
case 'CAPSULE':
|
|
94
|
+
return [
|
|
95
|
+
Bodies.rectangle(0, 0, shape.radius * 2, shape.centersDistance + shape.radius * 2, Object.assign(Object.assign({}, options), { chamfer: { radius: shape.radius } })),
|
|
96
|
+
];
|
|
97
|
+
case 'CONVEX_HULL':
|
|
98
|
+
return [Bodies.fromVertices(0, 0, [Pnt2.hull(shape.vertices).map(v => Vector.create(v.x, v.y))], options)];
|
|
99
|
+
case 'POLYGON':
|
|
100
|
+
return [Bodies.fromVertices(0, 0, [shape.vertices.map(v => Vector.create(v.x, v.y))], options)];
|
|
101
|
+
case 'COMPOUND': {
|
|
102
|
+
const parts = [];
|
|
103
|
+
for (const { position, rotation, shape: childShape } of shape.children) {
|
|
104
|
+
const childParts = createShapeParts(childShape, options);
|
|
105
|
+
for (const part of childParts) {
|
|
106
|
+
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)));
|
|
107
|
+
Body.setAngle(part, part.angle + (rotation || 0));
|
|
108
|
+
}
|
|
109
|
+
parts.push(...childParts);
|
|
110
|
+
}
|
|
111
|
+
return parts;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
export function createShapeBody(shape, options) {
|
|
116
|
+
return Body.create(Object.assign({ parts: createShapeParts(shape, options) }, options));
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Builds the sensor body of a trigger, for `MatterFactory.createTrigger` and
|
|
120
|
+
* `MatterTriggerComponent.clone()`.
|
|
121
|
+
*/
|
|
122
|
+
export function buildMatterTriggerBody(shape, transform) {
|
|
123
|
+
var _a, _b;
|
|
124
|
+
const options = { isSensor: true };
|
|
125
|
+
const nativeBody = shape.shape === 'COMPOUND' ? createShapeBody(shape, options) : createShapeParts(shape, options)[0];
|
|
126
|
+
// `Body.setPosition`/`Body.setAngle` rather than raw field writes, for the same reason as in
|
|
127
|
+
// `buildMatterRigidBody` above
|
|
128
|
+
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));
|
|
129
|
+
Body.setAngle(nativeBody, (transform === null || transform === void 0 ? void 0 : transform.rotation) || 0);
|
|
130
|
+
return nativeBody;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* matter-js's own body model only has `isStatic` - no distinct kinematic body type (position-
|
|
134
|
+
* driven, but still pushes/wakes dynamic bodies it moves into) and no continuous collision
|
|
135
|
+
* detection at all, at any level. Both are long-standing, documented upstream limitations, not
|
|
136
|
+
* something this adapter package failed to wire up - see `BodyOptions.kinematic_pos`/
|
|
137
|
+
* `ccd`'s own doc in `packages/core` for what each is supposed to do.
|
|
138
|
+
*
|
|
139
|
+
* `kinematic_pos`/`kinematic_vel` fall back to `isStatic: true` - the closest matter-js has - so
|
|
140
|
+
* a caller gets *a* body rather than a thrown error, but with the exact same caveat as
|
|
141
|
+
* teleporting a `static` body's position by hand (see that doc): resting bodies on top won't be
|
|
142
|
+
* pushed or woken correctly. `ccd: true` is silently accepted as a no-op beyond the warning below -
|
|
143
|
+
* a fast-moving or fast-driven body can still tunnel clean through thin geometry in one step.
|
|
144
|
+
*/
|
|
145
|
+
function transformOptions(options) {
|
|
146
|
+
if (options.bodyType === 'kinematic_pos' || options.bodyType === 'kinematic_vel') {
|
|
147
|
+
warnUnsupportedOnce(`bodyType: '${options.bodyType}' has no matter-js equivalent - falling back to a plain static ` +
|
|
148
|
+
"body. Resting bodies won't be pushed or woken when you move it, the same as manually " +
|
|
149
|
+
"teleporting a static body's position. See Body2DOptions.kinematic_pos/kinematic_vel's doc.");
|
|
150
|
+
}
|
|
151
|
+
if (options.ccd) {
|
|
152
|
+
warnUnsupportedOnce('ccd: true has no matter-js equivalent (no continuous collision detection at all) - ignored. ' +
|
|
153
|
+
'A fast-moving or fast-driven body can still tunnel through thin geometry. See ' +
|
|
154
|
+
"Body2DOptions.ccd's doc.");
|
|
155
|
+
}
|
|
156
|
+
const res = {
|
|
157
|
+
isStatic: options.bodyType !== undefined ? options.bodyType != 'dynamic' : !options.mass,
|
|
158
|
+
mass: options.mass,
|
|
159
|
+
restitution: options.restitution,
|
|
160
|
+
friction: options.friction,
|
|
161
|
+
};
|
|
162
|
+
for (const key in res) {
|
|
163
|
+
if (res[key] === undefined) {
|
|
164
|
+
delete res[key];
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return res;
|
|
168
|
+
}
|
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.78",
|
|
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",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"poly-decomp": "^0.3.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
|
-
"@gg-web-engine/core": "0.0.
|
|
33
|
+
"@gg-web-engine/core": "0.0.78",
|
|
34
34
|
"@types/jest": "^30.0.0",
|
|
35
35
|
"@types/matter-js": "0.20.2",
|
|
36
36
|
"@types/node": "^26.4.0",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"typescript": "~6.0.3"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
|
-
"@gg-web-engine/core": "0.0.
|
|
46
|
+
"@gg-web-engine/core": "0.0.78",
|
|
47
47
|
"matter-js": "0.20.0",
|
|
48
48
|
"rxjs": "7.8.2"
|
|
49
49
|
},
|
|
@@ -76,4 +76,17 @@ describe('MatterRigidBodyComponent sleep API', () => {
|
|
|
76
76
|
floor.wakeUp();
|
|
77
77
|
expect(floor.isSleeping).toBe(false);
|
|
78
78
|
});
|
|
79
|
+
|
|
80
|
+
it('a canSleep: false body ignores sleep() and keeps that option through clone()', () => {
|
|
81
|
+
const body = factory.createRigidBody(
|
|
82
|
+
{ shape: { shape: 'CIRCLE', radius: 1 }, body: { bodyType: 'dynamic', mass: 1, canSleep: false } },
|
|
83
|
+
{ position: { x: 0, y: 0 } },
|
|
84
|
+
);
|
|
85
|
+
body.addToWorld({ physicsWorld: world } as any);
|
|
86
|
+
|
|
87
|
+
body.sleep();
|
|
88
|
+
expect(body.isSleeping).toBe(false);
|
|
89
|
+
expect(body.bodyOptions.canSleep).toBe(false);
|
|
90
|
+
expect(body.clone().bodyOptions.canSleep).toBe(false);
|
|
91
|
+
});
|
|
79
92
|
});
|
|
@@ -96,3 +96,108 @@ describe('MatterRigidBodyComponent.rotation', () => {
|
|
|
96
96
|
expect(height).toBeCloseTo(2);
|
|
97
97
|
});
|
|
98
98
|
});
|
|
99
|
+
|
|
100
|
+
describe('MatterRigidBodyComponent.clone', () => {
|
|
101
|
+
let world: MatterWorldComponent;
|
|
102
|
+
let factory: MatterFactory;
|
|
103
|
+
|
|
104
|
+
beforeEach(async () => {
|
|
105
|
+
if (world) {
|
|
106
|
+
world.dispose();
|
|
107
|
+
}
|
|
108
|
+
world = new MatterWorldComponent();
|
|
109
|
+
factory = new MatterFactory(world);
|
|
110
|
+
await world.init();
|
|
111
|
+
world.gravity = Pnt2.O;
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
afterAll(() => {
|
|
115
|
+
world.dispose();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('copies shape, transform and body options into an independent native body', () => {
|
|
119
|
+
const body = factory.createRigidBody(
|
|
120
|
+
{
|
|
121
|
+
shape: { shape: 'BOX', dimensions: { x: 2, y: 1 } },
|
|
122
|
+
body: { bodyType: 'dynamic', mass: 5, friction: 0.3, restitution: 0.7, canSleep: false },
|
|
123
|
+
},
|
|
124
|
+
{ position: { x: 3, y: 4 }, rotation: 0.5 },
|
|
125
|
+
);
|
|
126
|
+
body.ownCollisionGroups = [2];
|
|
127
|
+
body.interactWithCollisionGroups = [3];
|
|
128
|
+
|
|
129
|
+
const clone = body.clone();
|
|
130
|
+
|
|
131
|
+
expect(clone.nativeBody).not.toBe(body.nativeBody);
|
|
132
|
+
expect(clone.shape).toEqual(body.shape);
|
|
133
|
+
expect(clone.position.x).toBeCloseTo(3);
|
|
134
|
+
expect(clone.position.y).toBeCloseTo(4);
|
|
135
|
+
expect(clone.rotation).toBeCloseTo(0.5);
|
|
136
|
+
expect(clone.bodyOptions).toEqual(body.bodyOptions);
|
|
137
|
+
|
|
138
|
+
clone.position = { x: 10, y: 10 };
|
|
139
|
+
expect(body.position.x).toBeCloseTo(3);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('clones a body that is already in a world, and the clone can be added too', () => {
|
|
143
|
+
const body = factory.createRigidBody(
|
|
144
|
+
{ shape: { shape: 'CIRCLE', radius: 1 }, body: { bodyType: 'dynamic', mass: 1 } },
|
|
145
|
+
{ position: { x: 0, y: 0 } },
|
|
146
|
+
);
|
|
147
|
+
body.addToWorld({ physicsWorld: world } as any);
|
|
148
|
+
|
|
149
|
+
const clone = body.clone();
|
|
150
|
+
clone.addToWorld({ physicsWorld: world } as any);
|
|
151
|
+
|
|
152
|
+
expect(world.matterWorld!.bodies).toContain(clone.nativeBody);
|
|
153
|
+
expect(world.matterWorld!.bodies).toContain(body.nativeBody);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('keeps a static body static', () => {
|
|
157
|
+
const floor = factory.createRigidBody(
|
|
158
|
+
{ shape: { shape: 'BOX', dimensions: { x: 10, y: 1 } }, body: { bodyType: 'static' } },
|
|
159
|
+
{ position: { x: 0, y: 5 } },
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
const clone = floor.clone();
|
|
163
|
+
|
|
164
|
+
expect(clone.nativeBody.isStatic).toBe(true);
|
|
165
|
+
expect(clone.bodyOptions.bodyType).toBe('static');
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('clones a compound body', () => {
|
|
169
|
+
const body = factory.createRigidBody(
|
|
170
|
+
{
|
|
171
|
+
shape: {
|
|
172
|
+
shape: 'COMPOUND',
|
|
173
|
+
children: [
|
|
174
|
+
{ shape: { shape: 'BOX', dimensions: { x: 1, y: 1 } }, position: { x: -1, y: 0 } },
|
|
175
|
+
{ shape: { shape: 'CIRCLE', radius: 0.5 }, position: { x: 1, y: 0 } },
|
|
176
|
+
],
|
|
177
|
+
},
|
|
178
|
+
body: { bodyType: 'dynamic', mass: 2 },
|
|
179
|
+
},
|
|
180
|
+
{ position: { x: 0, y: 0 } },
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
const clone = body.clone();
|
|
184
|
+
|
|
185
|
+
expect(clone.nativeBody.parts.length).toBe(body.nativeBody.parts.length);
|
|
186
|
+
expect(clone.bodyOptions.mass).toBeCloseTo(2);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
describe('MatterFactory capsule rigid body', () => {
|
|
191
|
+
it('applies body options to a capsule, like every other shape', async () => {
|
|
192
|
+
const world = new MatterWorldComponent();
|
|
193
|
+
await world.init();
|
|
194
|
+
const capsule = new MatterFactory(world).createRigidBody(
|
|
195
|
+
{ shape: { shape: 'CAPSULE', radius: 0.5, centersDistance: 1 }, body: { bodyType: 'dynamic', mass: 3, friction: 0.2 } },
|
|
196
|
+
{ position: { x: 0, y: 0 } },
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
expect(capsule.nativeBody.mass).toBeCloseTo(3);
|
|
200
|
+
expect(capsule.nativeBody.friction).toBe(0.2);
|
|
201
|
+
world.dispose();
|
|
202
|
+
});
|
|
203
|
+
});
|
|
@@ -250,4 +250,46 @@ describe(`MatterTriggerComponent`, () => {
|
|
|
250
250
|
await Promise.resolve();
|
|
251
251
|
expect(exitRegistered).toBe(true);
|
|
252
252
|
});
|
|
253
|
+
|
|
254
|
+
it(`clones into an independent sensor of the same shape, transform and collision groups`, () => {
|
|
255
|
+
const trigger = factory.createTrigger(
|
|
256
|
+
{ shape: 'BOX', dimensions: { x: 10, y: 4 } },
|
|
257
|
+
{ position: { x: 3, y: 4 }, rotation: 0.5 },
|
|
258
|
+
);
|
|
259
|
+
trigger.ownCollisionGroups = [2];
|
|
260
|
+
trigger.interactWithCollisionGroups = [3];
|
|
261
|
+
trigger.addToWorld({ physicsWorld: world } as any);
|
|
262
|
+
|
|
263
|
+
const clone = trigger.clone();
|
|
264
|
+
|
|
265
|
+
expect(clone.nativeBody).not.toBe(trigger.nativeBody);
|
|
266
|
+
expect(clone.nativeBody.isSensor).toBe(true);
|
|
267
|
+
expect(clone.nativeBody.isStatic).toBe(true);
|
|
268
|
+
expect(clone.shape).toEqual(trigger.shape);
|
|
269
|
+
expect(clone.position.x).toBeCloseTo(3);
|
|
270
|
+
expect(clone.position.y).toBeCloseTo(4);
|
|
271
|
+
expect(clone.rotation).toBeCloseTo(0.5);
|
|
272
|
+
expect(clone.ownCollisionGroups).toEqual([2]);
|
|
273
|
+
expect(clone.interactWithCollisionGroups).toEqual([3]);
|
|
274
|
+
|
|
275
|
+
clone.addToWorld({ physicsWorld: world } as any);
|
|
276
|
+
expect(world.matterWorld!.bodies).toContain(clone.nativeBody);
|
|
277
|
+
clone.position = { x: 10, y: 10 };
|
|
278
|
+
expect(trigger.position.x).toBeCloseTo(3);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it(`clones a compound trigger`, () => {
|
|
282
|
+
const trigger = factory.createTrigger({
|
|
283
|
+
shape: 'COMPOUND',
|
|
284
|
+
children: [
|
|
285
|
+
{ shape: { shape: 'BOX', dimensions: { x: 2, y: 2 } } },
|
|
286
|
+
{ position: { x: 3, y: 0 }, shape: { shape: 'CIRCLE', radius: 1 } },
|
|
287
|
+
],
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
const clone = trigger.clone();
|
|
291
|
+
|
|
292
|
+
expect(clone.nativeBody.parts.length).toBe(trigger.nativeBody.parts.length);
|
|
293
|
+
expect(clone.nativeBody.isSensor).toBe(true);
|
|
294
|
+
});
|
|
253
295
|
});
|