@combos-fun/plugin-matterjs 0.0.42 → 0.0.45
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/agent-skill.md +83 -8
- package/combos-plugin.json +1 -1
- package/dist/plugin-matterjs.cjs.js +215 -37
- package/dist/plugin-matterjs.cjs.js.map +1 -1
- package/dist/plugin-matterjs.cjs.prod.js +1 -1
- package/dist/plugin-matterjs.d.ts +49 -8
- package/dist/plugin-matterjs.esm.js +214 -37
- package/dist/plugin-matterjs.esm.js.map +1 -1
- package/package.json +2 -2
package/agent-skill.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
## When to read
|
|
6
6
|
|
|
7
|
-
Read for any 2D physics task: gravity, collisions, kinematic / static bodies, mouse constraints, rigid-body alignment with sprites.
|
|
7
|
+
Read for any 2D physics task: gravity, collisions, kinematic / static bodies, mouse constraints, rigid-body alignment with sprites, custom hulls (vertices / trapezoid / capsule / compound / edge / chain).
|
|
8
8
|
|
|
9
9
|
## Public API
|
|
10
10
|
|
|
@@ -13,24 +13,53 @@ import {
|
|
|
13
13
|
PhysicsSystem,
|
|
14
14
|
Physics,
|
|
15
15
|
PhysicsType,
|
|
16
|
+
type PhysicsParams,
|
|
17
|
+
type PhysicsPartParams,
|
|
18
|
+
type PhysicsVertex,
|
|
16
19
|
type PhysicsSystemParams,
|
|
17
20
|
} from '@combos-fun/plugin-matterjs';
|
|
18
21
|
```
|
|
19
22
|
|
|
20
23
|
### `PhysicsType`
|
|
21
24
|
|
|
22
|
-
|
|
25
|
+
| Value | Matter mapping | Notes |
|
|
26
|
+
|-------|----------------|-------|
|
|
27
|
+
| `RECTANGLE` | `Bodies.rectangle` | Size = `width`/`height`, else `transform.size × scale` |
|
|
28
|
+
| `CIRCLE` | `Bodies.circle` | `radius`; else `min(halfW, halfH)`, at least 1 |
|
|
29
|
+
| `POLYGON` | `Bodies.polygon` | Regular n-gon. `sides` (min 3, default 6) + `radius` |
|
|
30
|
+
| `TRAPEZOID` | `Bodies.trapezoid` | `width`/`height` (same fallback as rectangle) + `slope` (0–0.999, default 0.5) |
|
|
31
|
+
| `FROM_VERTICES` | `Bodies.fromVertices` | Arbitrary polygon. Concave shapes are decomposed via `poly-decomp` |
|
|
32
|
+
| `CAPSULE` | rectangle + 2 circles, then `Body.create({ parts })` | Stadium. `length` is full length including caps; `radius` is cap radius. `length <= 2r` becomes a circle |
|
|
33
|
+
| `COMPOUND` | `Body.create({ parts })` | One rigid body from `parts[]`. Nested `COMPOUND` is rejected |
|
|
34
|
+
| `EDGE` | thin `Bodies.rectangle` | Segment `from` → `to`. Orientation comes from the endpoints (not `bodyOptions.angle`) |
|
|
35
|
+
| `CHAIN` | compound of thin rectangles | Rigid polyline through `vertices`. **Not** a soft rope |
|
|
36
|
+
|
|
37
|
+
`CHAIN` / `EDGE` are static-terrain style colliders. A swinging rope needs multiple bodies + constraints; do not fake that with `CHAIN`.
|
|
38
|
+
|
|
39
|
+
### Coordinates
|
|
40
|
+
|
|
41
|
+
`vertices`, `vertexSets`, `from`, `to`, and each part's `position` are **local** to the GameObject centre. The factory adds the body origin (`bodyParams.position` or the object's transform). Do not pass world-space vertices.
|
|
23
42
|
|
|
24
43
|
### `Physics` Component params
|
|
25
44
|
|
|
26
45
|
| Field | Type | Notes |
|
|
27
46
|
|-------|------|-------|
|
|
28
|
-
| `type` | `PhysicsType?` | Body shape |
|
|
29
|
-
| `bodyOptions` | `object?` | Matter Body options: `isStatic`, `restitution`, `frictionAir`, `density`, `friction`, ... |
|
|
30
|
-
| `position` | `{x, y}?` |
|
|
31
|
-
| `
|
|
47
|
+
| `type` | `PhysicsType?` | Body shape. Required when the object enters the scene |
|
|
48
|
+
| `bodyOptions` | `object?` | Matter Body options: `isStatic`, `restitution`, `frictionAir`, `density`, `friction`, `angle`, ... |
|
|
49
|
+
| `position` | `{x, y}?` | Body origin in world space. If omitted, taken from the GameObject transform |
|
|
50
|
+
| `width` / `height` | `number?` | Rectangle / trapezoid / capsule fallback size |
|
|
51
|
+
| `sides` / `radius` | `number?` | Regular polygon / circle / capsule cap |
|
|
52
|
+
| `slope` | `number?` | Trapezoid only. Clamped to `< 1` |
|
|
53
|
+
| `length` | `number?` | Capsule full length (including caps). Default = transform width |
|
|
54
|
+
| `vertices` | `{x, y}[]?` | `FROM_VERTICES` (one contour) or `CHAIN` (polyline) |
|
|
55
|
+
| `vertexSets` | `{x, y}[][]?` | `FROM_VERTICES` with multiple contours. Takes precedence over `vertices` |
|
|
56
|
+
| `from` / `to` | `{x, y}?` | `EDGE` endpoints, local to the body origin |
|
|
57
|
+
| `thickness` | `number?` | `EDGE` / `CHAIN` segment thickness. Default 4, minimum 1 |
|
|
58
|
+
| `parts` | `PhysicsPartParams[]?` | `COMPOUND` only. Same shape fields as above, plus a local `position` offset. No nested `COMPOUND` |
|
|
32
59
|
| `stopRotation` | `boolean?` | Lock rotation sync to render transform |
|
|
33
60
|
|
|
61
|
+
`PhysicsPartParams` can be `RECTANGLE`, `CIRCLE`, `POLYGON`, `TRAPEZOID`, `FROM_VERTICES`, `CAPSULE`, `EDGE`, or `CHAIN`. Capsule / chain / concave vertex parts are flattened into the parent compound.
|
|
62
|
+
|
|
34
63
|
Runtime fields:
|
|
35
64
|
|
|
36
65
|
- `body: Matter.Body` — set by `PhysicsSystem` after the GameObject enters the scene.
|
|
@@ -41,6 +70,8 @@ Events on the `Physics` instance:
|
|
|
41
70
|
- `'collisionActive'` — same signature
|
|
42
71
|
- `'collisionEnd'` — same signature
|
|
43
72
|
|
|
73
|
+
Compound / capsule / chain collisions arrive on the **parent** `Physics` (the engine walks `body.parent` so part hits still emit).
|
|
74
|
+
|
|
44
75
|
### `PhysicsSystemParams`
|
|
45
76
|
|
|
46
77
|
| Field | Type | Notes |
|
|
@@ -48,7 +79,7 @@ Events on the `Physics` instance:
|
|
|
48
79
|
| `world` | `DeepPartial<IWorldDefinition>` | **Required**. Must include `gravity: { x, y, scale }` (e.g. `{ x: 0, y: 1, scale: 0.001 }`) |
|
|
49
80
|
| `resolution` | `number?` | **Must match `RendererSystem.resolution`** to keep bodies aligned with sprites |
|
|
50
81
|
| `fps` | `number?` | Step rate |
|
|
51
|
-
| `isTest` | `boolean?` | Adds a Pixi debug overlay drawn from Matter renderer |
|
|
82
|
+
| `isTest` | `boolean?` | Adds a Pixi debug overlay drawn from Matter renderer (compound parts are outlined individually) |
|
|
52
83
|
| `mouse` | `{ open, constraint? }?` | Optional Matter mouse constraint |
|
|
53
84
|
| `element` / `canvas` | optional | Debug renderer mount points |
|
|
54
85
|
|
|
@@ -66,16 +97,21 @@ Events on the `Physics` instance:
|
|
|
66
97
|
body's `position` / `rotation` back into the sibling `Transform`.
|
|
67
98
|
- Pause / resume: `PhysicsSystem` automatically stops / starts the Matter
|
|
68
99
|
runner when `Game.pause()` / `Game.resume()` is called.
|
|
100
|
+
- Changing `bodyParams` (deep observer) rebuilds the Matter body.
|
|
69
101
|
|
|
70
102
|
## Common pitfalls
|
|
71
103
|
|
|
72
104
|
| Symptom | Fix |
|
|
73
105
|
|---------|-----|
|
|
74
|
-
| `gravity` complaint at runtime | Provide `world: { gravity: { x
|
|
106
|
+
| `gravity` complaint at runtime | Provide `world: { gravity: { x, y, scale } }` (`scale` is required) |
|
|
75
107
|
| Bodies misaligned with sprites | `PhysicsSystem.resolution` must match `RendererSystem.resolution` |
|
|
76
108
|
| Rotation drift | Use `stopRotation: true` on `Physics` if the visual must stay axis-aligned |
|
|
77
109
|
| Bodies pile up at origin | Set `position: { x, y }` on `Physics` constructor params |
|
|
78
110
|
| Cannot tap through bodies | Disable mouse constraint or `pointer-events: none` on the canvas overlay |
|
|
111
|
+
| `FROM_VERTICES` / `CHAIN` in the wrong place | Vertices are local to the body origin, not world space |
|
|
112
|
+
| Nested `COMPOUND` throws | Flatten into one `parts` list, or use `CAPSULE` / `CHAIN` as a part |
|
|
113
|
+
| Soft rope expected from `CHAIN` | `CHAIN` is a rigid polyline. Soft ropes need constraints, not this type |
|
|
114
|
+
| Collision events missing on capsule / compound | Listen on the parent `Physics`; do not read `pair.bodyA.component` yourself |
|
|
79
115
|
|
|
80
116
|
## Minimal example
|
|
81
117
|
|
|
@@ -98,8 +134,47 @@ ball.addComponent(new Graphics(/* draw a circle */));
|
|
|
98
134
|
ball.addComponent(new Physics({ type: PhysicsType.CIRCLE, radius: 25 }));
|
|
99
135
|
```
|
|
100
136
|
|
|
137
|
+
### Extra shapes
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
new Physics({
|
|
141
|
+
type: PhysicsType.FROM_VERTICES,
|
|
142
|
+
vertices: [{ x: -40, y: 20 }, { x: 40, y: 20 }, { x: 0, y: -40 }],
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
new Physics({ type: PhysicsType.TRAPEZOID, width: 80, height: 40, slope: 0.4 });
|
|
146
|
+
|
|
147
|
+
new Physics({ type: PhysicsType.CAPSULE, length: 80, radius: 16 });
|
|
148
|
+
|
|
149
|
+
new Physics({
|
|
150
|
+
type: PhysicsType.COMPOUND,
|
|
151
|
+
parts: [
|
|
152
|
+
{ type: PhysicsType.RECTANGLE, width: 80, height: 20 },
|
|
153
|
+
{ type: PhysicsType.CIRCLE, radius: 16, position: { x: -40, y: 0 } },
|
|
154
|
+
{ type: PhysicsType.CIRCLE, radius: 16, position: { x: 40, y: 0 } },
|
|
155
|
+
],
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
new Physics({
|
|
159
|
+
type: PhysicsType.EDGE,
|
|
160
|
+
from: { x: -100, y: 0 },
|
|
161
|
+
to: { x: 100, y: 20 },
|
|
162
|
+
thickness: 4,
|
|
163
|
+
bodyOptions: { isStatic: true },
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
new Physics({
|
|
167
|
+
type: PhysicsType.CHAIN,
|
|
168
|
+
vertices: [{ x: -200, y: 0 }, { x: 0, y: 40 }, { x: 200, y: 0 }],
|
|
169
|
+
thickness: 4,
|
|
170
|
+
bodyOptions: { isStatic: true },
|
|
171
|
+
});
|
|
172
|
+
```
|
|
173
|
+
|
|
101
174
|
## Verification
|
|
102
175
|
|
|
103
176
|
- `pnpm --filter @combos-fun/plugin-matterjs run build`
|
|
104
177
|
- Run a 2D example with physics; bodies should fall under gravity, collide,
|
|
105
178
|
and stay aligned with sprites.
|
|
179
|
+
- For new hulls: a static `CHAIN` / `EDGE` should support falling circles;
|
|
180
|
+
a `CAPSULE` / `COMPOUND` should emit `collisionStart` on the parent `Physics`.
|
package/combos-plugin.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"category": "physics",
|
|
5
5
|
"dimension": "2d",
|
|
6
6
|
"isCore": false,
|
|
7
|
-
"keywords": ["matter-js", "2d", "physics", "collision", "rigid-body", "gravity"],
|
|
7
|
+
"keywords": ["matter-js", "2d", "physics", "collision", "rigid-body", "gravity", "capsule", "compound", "polygon"],
|
|
8
8
|
"agentSkill": "./agent-skill.md",
|
|
9
9
|
"requires": ["@combos-fun/engine", "@combos-fun/plugin-renderer"],
|
|
10
10
|
"exports": ["PhysicsSystem", "Physics", "PhysicsType"]
|
|
@@ -3,17 +3,25 @@
|
|
|
3
3
|
var tslib = require('tslib');
|
|
4
4
|
var engine = require('@combos-fun/engine');
|
|
5
5
|
var Matter = require('matter-js');
|
|
6
|
+
var decomp = require('poly-decomp');
|
|
6
7
|
var pixi_js = require('pixi.js');
|
|
7
8
|
|
|
8
9
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
9
10
|
|
|
10
11
|
var Matter__default = /*#__PURE__*/_interopDefault(Matter);
|
|
12
|
+
var decomp__default = /*#__PURE__*/_interopDefault(decomp);
|
|
11
13
|
|
|
12
14
|
exports.PhysicsType = void 0;
|
|
13
15
|
(function (PhysicsType) {
|
|
14
16
|
PhysicsType["RECTANGLE"] = "rectangle";
|
|
15
17
|
PhysicsType["CIRCLE"] = "circle";
|
|
16
18
|
PhysicsType["POLYGON"] = "polygon";
|
|
19
|
+
PhysicsType["TRAPEZOID"] = "trapezoid";
|
|
20
|
+
PhysicsType["FROM_VERTICES"] = "fromVertices";
|
|
21
|
+
PhysicsType["CAPSULE"] = "capsule";
|
|
22
|
+
PhysicsType["COMPOUND"] = "compound";
|
|
23
|
+
PhysicsType["EDGE"] = "edge";
|
|
24
|
+
PhysicsType["CHAIN"] = "chain";
|
|
17
25
|
})(exports.PhysicsType || (exports.PhysicsType = {}));
|
|
18
26
|
class Physics extends engine.Component {
|
|
19
27
|
static { this.componentName = 'Physics'; }
|
|
@@ -36,41 +44,184 @@ class Physics extends engine.Component {
|
|
|
36
44
|
}
|
|
37
45
|
}
|
|
38
46
|
|
|
47
|
+
const DEFAULT_THICKNESS = 4;
|
|
48
|
+
const MIN_SEGMENT = 0.5;
|
|
49
|
+
let decompRegistered = false;
|
|
50
|
+
function ensureDecomp() {
|
|
51
|
+
if (decompRegistered) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const lib = decomp__default.default?.quickDecomp ? decomp__default.default : decomp__default.default?.default;
|
|
55
|
+
if (lib?.quickDecomp) {
|
|
56
|
+
Matter__default.default.Common.setDecomp(lib);
|
|
57
|
+
}
|
|
58
|
+
decompRegistered = true;
|
|
59
|
+
}
|
|
39
60
|
class BodiesFactory {
|
|
40
61
|
constructor() {
|
|
41
62
|
this.Bodies = Matter__default.default.Bodies;
|
|
63
|
+
ensureDecomp();
|
|
42
64
|
}
|
|
43
65
|
create(component) {
|
|
44
|
-
let body = null;
|
|
45
66
|
const { gameObject, bodyParams } = component;
|
|
46
67
|
const coordinate = this.getCoordinate(gameObject);
|
|
47
68
|
const x = bodyParams.position ? bodyParams.position.x : coordinate.x;
|
|
48
69
|
const y = bodyParams.position ? bodyParams.position.y : coordinate.y;
|
|
49
|
-
const
|
|
50
|
-
const
|
|
70
|
+
const transformWidth = gameObject.transform.size.width * gameObject.transform.scale.x;
|
|
71
|
+
const transformHeight = gameObject.transform.size.height * gameObject.transform.scale.y;
|
|
51
72
|
/** Matter.circle approximates with a polygon; NaN/0 radius breaks vertices. */
|
|
52
|
-
const defaultRadius = Math.max(1, Math.min(
|
|
53
|
-
|
|
73
|
+
const defaultRadius = Math.max(1, Math.min(transformWidth, transformHeight) / 2);
|
|
74
|
+
const body = this.createShape(bodyParams, {
|
|
75
|
+
x,
|
|
76
|
+
y,
|
|
77
|
+
transformWidth,
|
|
78
|
+
transformHeight,
|
|
79
|
+
defaultRadius,
|
|
80
|
+
}, false);
|
|
81
|
+
if (!body) {
|
|
82
|
+
throw new Error(`[plugin-matterjs] Failed to create body for type ${String(bodyParams.type)}`);
|
|
83
|
+
}
|
|
84
|
+
return body;
|
|
85
|
+
}
|
|
86
|
+
createShape(params, ctx, isPart) {
|
|
87
|
+
const options = params.bodyOptions;
|
|
88
|
+
switch (params.type) {
|
|
54
89
|
case exports.PhysicsType.RECTANGLE: {
|
|
55
|
-
const width =
|
|
56
|
-
const height =
|
|
57
|
-
|
|
58
|
-
break;
|
|
90
|
+
const width = params.width ?? ctx.transformWidth;
|
|
91
|
+
const height = params.height ?? ctx.transformHeight;
|
|
92
|
+
return this.Bodies.rectangle(ctx.x, ctx.y, width, height, options);
|
|
59
93
|
}
|
|
60
94
|
case exports.PhysicsType.CIRCLE: {
|
|
61
|
-
const radius =
|
|
62
|
-
|
|
63
|
-
break;
|
|
95
|
+
const radius = params.radius ?? ctx.defaultRadius;
|
|
96
|
+
return this.Bodies.circle(ctx.x, ctx.y, radius, options);
|
|
64
97
|
}
|
|
65
98
|
case exports.PhysicsType.POLYGON: {
|
|
66
|
-
const sides = Math.max(3,
|
|
67
|
-
const radius =
|
|
68
|
-
|
|
69
|
-
|
|
99
|
+
const sides = Math.max(3, params.sides ?? 6);
|
|
100
|
+
const radius = params.radius ?? ctx.defaultRadius;
|
|
101
|
+
return this.Bodies.polygon(ctx.x, ctx.y, sides, radius, options);
|
|
102
|
+
}
|
|
103
|
+
case exports.PhysicsType.TRAPEZOID: {
|
|
104
|
+
const width = params.width ?? ctx.transformWidth;
|
|
105
|
+
const height = params.height ?? ctx.transformHeight;
|
|
106
|
+
const slope = Math.min(Math.max(params.slope ?? 0.5, 0), 0.999);
|
|
107
|
+
return this.Bodies.trapezoid(ctx.x, ctx.y, width, height, slope, options);
|
|
70
108
|
}
|
|
109
|
+
case exports.PhysicsType.FROM_VERTICES:
|
|
110
|
+
return this.createFromVertices(params, ctx, options);
|
|
111
|
+
case exports.PhysicsType.CAPSULE:
|
|
112
|
+
return this.createCapsule(params, ctx, options);
|
|
113
|
+
case exports.PhysicsType.EDGE:
|
|
114
|
+
return this.createEdge(params, ctx, options);
|
|
115
|
+
case exports.PhysicsType.CHAIN:
|
|
116
|
+
return this.createChain(params, ctx, options);
|
|
117
|
+
case exports.PhysicsType.COMPOUND:
|
|
118
|
+
if (isPart) {
|
|
119
|
+
throw new Error('[plugin-matterjs] Nested COMPOUND parts are not supported');
|
|
120
|
+
}
|
|
121
|
+
return this.createCompound(params, ctx, options);
|
|
122
|
+
default:
|
|
123
|
+
throw new Error(`[plugin-matterjs] Unsupported PhysicsType: ${String(params.type)}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
createFromVertices(params, ctx, options) {
|
|
127
|
+
const sets = params.vertexSets ?? (params.vertices ? [params.vertices] : []);
|
|
128
|
+
if (!sets.length || sets.some(set => !set || set.length < 3)) {
|
|
129
|
+
throw new Error('[plugin-matterjs] FROM_VERTICES requires vertices (or vertexSets) with at least 3 points each');
|
|
130
|
+
}
|
|
131
|
+
ensureDecomp();
|
|
132
|
+
const body = this.Bodies.fromVertices(ctx.x, ctx.y, sets, options, true);
|
|
133
|
+
if (!body) {
|
|
134
|
+
throw new Error('[plugin-matterjs] FROM_VERTICES produced an empty body');
|
|
71
135
|
}
|
|
72
136
|
return body;
|
|
73
137
|
}
|
|
138
|
+
createCapsule(params, ctx, options) {
|
|
139
|
+
const radius = Math.max(1, params.radius ?? ctx.defaultRadius);
|
|
140
|
+
const length = Math.max(radius * 2, params.length ?? ctx.transformWidth);
|
|
141
|
+
const shaft = length - radius * 2;
|
|
142
|
+
if (shaft < MIN_SEGMENT) {
|
|
143
|
+
return this.Bodies.circle(ctx.x, ctx.y, radius, options);
|
|
144
|
+
}
|
|
145
|
+
const parts = [
|
|
146
|
+
this.Bodies.rectangle(ctx.x, ctx.y, shaft, radius * 2),
|
|
147
|
+
this.Bodies.circle(ctx.x - shaft / 2, ctx.y, radius),
|
|
148
|
+
this.Bodies.circle(ctx.x + shaft / 2, ctx.y, radius),
|
|
149
|
+
];
|
|
150
|
+
return Matter__default.default.Body.create({ ...options, parts });
|
|
151
|
+
}
|
|
152
|
+
createEdge(params, ctx, options) {
|
|
153
|
+
if (!params.from || !params.to) {
|
|
154
|
+
throw new Error('[plugin-matterjs] EDGE requires from and to');
|
|
155
|
+
}
|
|
156
|
+
const x1 = ctx.x + params.from.x;
|
|
157
|
+
const y1 = ctx.y + params.from.y;
|
|
158
|
+
const x2 = ctx.x + params.to.x;
|
|
159
|
+
const y2 = ctx.y + params.to.y;
|
|
160
|
+
const dx = x2 - x1;
|
|
161
|
+
const dy = y2 - y1;
|
|
162
|
+
const len = Math.hypot(dx, dy);
|
|
163
|
+
if (len < MIN_SEGMENT) {
|
|
164
|
+
throw new Error('[plugin-matterjs] EDGE from/to are too close');
|
|
165
|
+
}
|
|
166
|
+
const thickness = Math.max(1, params.thickness ?? DEFAULT_THICKNESS);
|
|
167
|
+
const { angle: _ignored, ...rest } = options ?? {};
|
|
168
|
+
return this.Bodies.rectangle((x1 + x2) / 2, (y1 + y2) / 2, len, thickness, {
|
|
169
|
+
...rest,
|
|
170
|
+
angle: Math.atan2(dy, dx),
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
createChain(params, ctx, options) {
|
|
174
|
+
const verts = params.vertices;
|
|
175
|
+
if (!verts || verts.length < 2) {
|
|
176
|
+
throw new Error('[plugin-matterjs] CHAIN requires vertices with at least 2 points');
|
|
177
|
+
}
|
|
178
|
+
const thickness = Math.max(1, params.thickness ?? DEFAULT_THICKNESS);
|
|
179
|
+
const parts = [];
|
|
180
|
+
for (let i = 0; i < verts.length - 1; i++) {
|
|
181
|
+
const a = verts[i];
|
|
182
|
+
const b = verts[i + 1];
|
|
183
|
+
const x1 = ctx.x + a.x;
|
|
184
|
+
const y1 = ctx.y + a.y;
|
|
185
|
+
const x2 = ctx.x + b.x;
|
|
186
|
+
const y2 = ctx.y + b.y;
|
|
187
|
+
const dx = x2 - x1;
|
|
188
|
+
const dy = y2 - y1;
|
|
189
|
+
const len = Math.hypot(dx, dy);
|
|
190
|
+
if (len < MIN_SEGMENT) {
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
parts.push(this.Bodies.rectangle((x1 + x2) / 2, (y1 + y2) / 2, len, thickness, { angle: Math.atan2(dy, dx) }));
|
|
194
|
+
}
|
|
195
|
+
if (!parts.length) {
|
|
196
|
+
throw new Error('[plugin-matterjs] CHAIN has no usable segments');
|
|
197
|
+
}
|
|
198
|
+
return Matter__default.default.Body.create({ ...options, parts });
|
|
199
|
+
}
|
|
200
|
+
createCompound(params, ctx, options) {
|
|
201
|
+
const partParams = params.parts;
|
|
202
|
+
if (!partParams?.length) {
|
|
203
|
+
throw new Error('[plugin-matterjs] COMPOUND requires a non-empty parts array');
|
|
204
|
+
}
|
|
205
|
+
const parts = [];
|
|
206
|
+
for (let i = 0; i < partParams.length; i++) {
|
|
207
|
+
const part = partParams[i];
|
|
208
|
+
const ox = ctx.x + (part.position?.x ?? 0);
|
|
209
|
+
const oy = ctx.y + (part.position?.y ?? 0);
|
|
210
|
+
const partBody = this.createShape(part, { ...ctx, x: ox, y: oy }, true);
|
|
211
|
+
parts.push(...this.asParts(partBody));
|
|
212
|
+
}
|
|
213
|
+
if (!parts.length) {
|
|
214
|
+
throw new Error('[plugin-matterjs] COMPOUND produced no parts');
|
|
215
|
+
}
|
|
216
|
+
return Matter__default.default.Body.create({ ...options, parts });
|
|
217
|
+
}
|
|
218
|
+
/** Unwrap a compound wrapper so it can be merged into another parent. */
|
|
219
|
+
asParts(body) {
|
|
220
|
+
if (body.parts && body.parts.length > 1) {
|
|
221
|
+
return body.parts.slice(1);
|
|
222
|
+
}
|
|
223
|
+
return [body];
|
|
224
|
+
}
|
|
74
225
|
getCoordinate(gameObject) {
|
|
75
226
|
const x = gameObject.transform.position.x + gameObject.transform.anchor.x * gameObject.parent.transform.size.width;
|
|
76
227
|
const y = gameObject.transform.position.y + gameObject.transform.anchor.y * gameObject.parent.transform.size.height;
|
|
@@ -120,17 +271,20 @@ class MatterPixiDebugRenderer {
|
|
|
120
271
|
if (body.render?.visible === false) {
|
|
121
272
|
continue;
|
|
122
273
|
}
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
g.
|
|
274
|
+
const pieces = body.parts && body.parts.length > 1 ? body.parts.slice(1) : [body];
|
|
275
|
+
for (let p = 0; p < pieces.length; p++) {
|
|
276
|
+
const verts = pieces[p].vertices;
|
|
277
|
+
if (!verts?.length) {
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
const v0 = verts[0];
|
|
281
|
+
g.moveTo(v0.x, v0.y);
|
|
282
|
+
for (let i = 1; i < verts.length; i++) {
|
|
283
|
+
g.lineTo(verts[i].x, verts[i].y);
|
|
284
|
+
}
|
|
285
|
+
g.closePath();
|
|
286
|
+
g.stroke({ width: 1, color: 0x00e676, alpha: 0.9 });
|
|
131
287
|
}
|
|
132
|
-
g.closePath();
|
|
133
|
-
g.stroke({ width: 1, color: 0x00e676, alpha: 0.9 });
|
|
134
288
|
}
|
|
135
289
|
}
|
|
136
290
|
destroy() {
|
|
@@ -143,6 +297,21 @@ class MatterPixiDebugRenderer {
|
|
|
143
297
|
}
|
|
144
298
|
}
|
|
145
299
|
|
|
300
|
+
/** Collision pairs often report a compound part, not the parent body. */
|
|
301
|
+
function resolveLinkedComponent(body) {
|
|
302
|
+
let current = body;
|
|
303
|
+
while (current) {
|
|
304
|
+
const linked = current;
|
|
305
|
+
if (linked.component) {
|
|
306
|
+
return linked.component;
|
|
307
|
+
}
|
|
308
|
+
if (!current.parent || current.parent === current) {
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
311
|
+
current = current.parent;
|
|
312
|
+
}
|
|
313
|
+
return undefined;
|
|
314
|
+
}
|
|
146
315
|
class PhysicsEngine {
|
|
147
316
|
constructor(game, options) {
|
|
148
317
|
this.debugRenderer = null;
|
|
@@ -216,19 +385,13 @@ class PhysicsEngine {
|
|
|
216
385
|
add(component) {
|
|
217
386
|
const body = this.createBodies(component);
|
|
218
387
|
this.World.add(this.engine.world, [body]);
|
|
219
|
-
component
|
|
220
|
-
component.Body = Matter__default.default.Body;
|
|
221
|
-
component.PhysicsEngine = this.engine;
|
|
222
|
-
component.Constraint = this.Constraint;
|
|
223
|
-
component.mouseConstraint = this.mouseConstraint;
|
|
224
|
-
component.World = this.World;
|
|
225
|
-
body.component = component;
|
|
388
|
+
this.linkComponent(component, body);
|
|
226
389
|
}
|
|
227
390
|
change(component) {
|
|
228
391
|
const newBody = this.createBodies(component);
|
|
229
392
|
this.World.remove(this.engine.world, component.body, true);
|
|
230
393
|
this.World.add(this.engine.world, [newBody]);
|
|
231
|
-
component
|
|
394
|
+
this.linkComponent(component, newBody);
|
|
232
395
|
}
|
|
233
396
|
remove(component) {
|
|
234
397
|
this.World.remove(this.engine.world, component.body, true);
|
|
@@ -236,8 +399,20 @@ class PhysicsEngine {
|
|
|
236
399
|
}
|
|
237
400
|
createBodies(params) {
|
|
238
401
|
const body = this.bodiesFactory.create(params);
|
|
402
|
+
if (!body) {
|
|
403
|
+
throw new Error('[plugin-matterjs] Failed to create Matter body');
|
|
404
|
+
}
|
|
239
405
|
return body;
|
|
240
406
|
}
|
|
407
|
+
linkComponent(component, body) {
|
|
408
|
+
component.body = body;
|
|
409
|
+
component.Body = Matter__default.default.Body;
|
|
410
|
+
component.PhysicsEngine = this.engine;
|
|
411
|
+
component.Constraint = this.Constraint;
|
|
412
|
+
component.mouseConstraint = this.mouseConstraint;
|
|
413
|
+
component.World = this.World;
|
|
414
|
+
body.component = component;
|
|
415
|
+
}
|
|
241
416
|
initCollisionEvents() {
|
|
242
417
|
this.collisionEvents.forEach(eventName => {
|
|
243
418
|
Matter__default.default.Events.on(this.engine, eventName, (event) => {
|
|
@@ -245,8 +420,11 @@ class PhysicsEngine {
|
|
|
245
420
|
for (let i = 0; i < pairs.length; i++) {
|
|
246
421
|
const pair = pairs[i];
|
|
247
422
|
const { bodyA, bodyB } = pair;
|
|
248
|
-
const componentA = bodyA
|
|
249
|
-
const componentB = bodyB
|
|
423
|
+
const componentA = resolveLinkedComponent(bodyA);
|
|
424
|
+
const componentB = resolveLinkedComponent(bodyB);
|
|
425
|
+
if (!componentA || !componentB) {
|
|
426
|
+
continue;
|
|
427
|
+
}
|
|
250
428
|
componentA.emit(eventName, componentB.gameObject, componentA.gameObject);
|
|
251
429
|
componentB.emit(eventName, componentA.gameObject, componentB.gameObject);
|
|
252
430
|
}
|
|
@@ -383,7 +561,7 @@ var PhysicsSystem = PhysicsSystem$1;
|
|
|
383
561
|
/** Auto-generated by scripts/build-package.mjs — do not edit. */
|
|
384
562
|
Object.assign(PhysicsSystem, {
|
|
385
563
|
packageName: "@combos-fun/plugin-matterjs",
|
|
386
|
-
packageVersion: "0.0.
|
|
564
|
+
packageVersion: "0.0.45",
|
|
387
565
|
});
|
|
388
566
|
|
|
389
567
|
exports.Physics = Physics;
|