@combos-fun/plugin-cannon 0.0.42 → 0.0.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/agent-skill.md +79 -16
- package/combos-plugin.json +8 -2
- package/dist/plugin-cannon.cjs.js +694 -76
- package/dist/plugin-cannon.cjs.js.map +1 -1
- package/dist/plugin-cannon.cjs.prod.js +1 -1
- package/dist/plugin-cannon.d.ts +170 -33
- package/dist/plugin-cannon.esm.js +693 -77
- package/dist/plugin-cannon.esm.js.map +1 -1
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
@combos-fun/plugin-cannon — part of the Combos Fun engine monorepo.
|
|
4
4
|
|
|
5
|
-
Keywords: `cannon-es`, `3d`, `physics`, `collision`, `rigid-body`, `gravity`.
|
|
5
|
+
Keywords: `cannon-es`, `3d`, `physics`, `collision`, `rigid-body`, `gravity`, `trimesh`, `compound`.
|
|
6
6
|
|
|
7
7
|
## Documentation
|
|
8
8
|
|
package/agent-skill.md
CHANGED
|
@@ -6,7 +6,7 @@ Pattern mirrors `plugin-matterjs` (2D): Component stores params, System observes
|
|
|
6
6
|
|
|
7
7
|
## When to read
|
|
8
8
|
|
|
9
|
-
Read for any 3D physics task: gravity, collisions, rigid bodies, ground planes, cylinders /
|
|
9
|
+
Read for any 3D physics task: gravity, collisions, rigid bodies, ground planes, cylinders / cones / capsules / tori, body-mesh sync, GLB colliders.
|
|
10
10
|
|
|
11
11
|
## Public API
|
|
12
12
|
|
|
@@ -15,7 +15,12 @@ import {
|
|
|
15
15
|
Physics3DSystem,
|
|
16
16
|
Physics3D,
|
|
17
17
|
Physics3DType,
|
|
18
|
+
Physics3DConstraint,
|
|
19
|
+
Physics3DConstraintSystem,
|
|
18
20
|
type Physics3DParams,
|
|
21
|
+
type Physics3DPartParams,
|
|
22
|
+
type Physics3DCollider,
|
|
23
|
+
type Physics3DConstraintParams,
|
|
19
24
|
type Physics3DSystemParams,
|
|
20
25
|
} from '@combos-fun/plugin-cannon';
|
|
21
26
|
```
|
|
@@ -24,29 +29,54 @@ import {
|
|
|
24
29
|
|
|
25
30
|
| Value | Shape | Notes |
|
|
26
31
|
|-------|-------|-------|
|
|
27
|
-
| `BOX` | `CANNON.Box` | Half-extents from `width
|
|
32
|
+
| `BOX` | `CANNON.Box` | Half-extents from `width` / `height` / `depth` |
|
|
28
33
|
| `SPHERE` | `CANNON.Sphere` | `radius` |
|
|
29
|
-
| `CYLINDER` | `CANNON.Cylinder` | `radiusTop`, `radiusBottom`, `height`, `segments` |
|
|
30
|
-
| `
|
|
34
|
+
| `CYLINDER` | `CANNON.Cylinder` | `radiusTop`, `radiusBottom`, `height`, `segments` (Y axis) |
|
|
35
|
+
| `CONE` | `CANNON.Cylinder(≈0, radius, height)` | Matches `Graphics3D` cone |
|
|
36
|
+
| `PLANE` | `CANNON.Plane` or thin box | Infinite unless `finite: true` |
|
|
37
|
+
| `TORUS` | Compound spheres | 8–12 spheres; tube = `tube` or `0.4 × radius` |
|
|
38
|
+
| `CAPSULE` | Cylinder + 2 spheres | `height` is total height |
|
|
39
|
+
| `COMPOUND` | `Body.addShape` list | From `parts` or sibling `Graphics3D.parts` |
|
|
40
|
+
|
|
41
|
+
If `type` is omitted: use `parts` → `COMPOUND`, else sibling `Graphics3D.shape`, else `BOX`. Missing size fields fall back to the sibling `Graphics3D`.
|
|
42
|
+
|
|
43
|
+
### `Physics3DCollider` (models)
|
|
44
|
+
|
|
45
|
+
| Value | When | Notes |
|
|
46
|
+
|-------|------|-------|
|
|
47
|
+
| `manual` | default without `Model3D` | Use `type` / `parts` |
|
|
48
|
+
| `aabb` | **default when a sibling `Model3D` exists and `type` / `parts` are omitted** | `Box3` after load, includes `scale*` |
|
|
49
|
+
| `hull` | convex props | Convex hull of sampled vertices; holes fill in |
|
|
50
|
+
| `trimesh` | static scenery only | Triangle mesh; `mass` is forced to `0` |
|
|
51
|
+
|
|
52
|
+
Mesh colliders wait until `Model3D` has attached a `Group`. Changing `scale*` rebuilds the body. Skinned animation stays on the bind-pose mesh. Do not use `trimesh` on two dynamic models that must hit each other.
|
|
31
53
|
|
|
32
54
|
### `Physics3DParams`
|
|
33
55
|
|
|
34
56
|
| Field | Type | Default | Notes |
|
|
35
57
|
|-------|------|---------|-------|
|
|
36
|
-
| `type` | `Physics3DType` |
|
|
37
|
-
| `mass` | `number` | `1` | `0` = static; `PLANE` always
|
|
58
|
+
| `type` | `Physics3DType` | inferred | |
|
|
59
|
+
| `mass` | `number` | `1` | `0` = static; infinite `PLANE` always 0; `trimesh` always 0 |
|
|
38
60
|
| `bodyOptions` | `object` | `{}` | `friction`, `restitution`, `linearDamping`, `angularDamping`, `fixedRotation` |
|
|
39
|
-
| `position` | `{x?, y?, z?}` | `0
|
|
40
|
-
| `rotation` | `{x?, y?, z?}` | `0
|
|
41
|
-
| `width`/`height`/`depth` | `number` | `1` | Box
|
|
42
|
-
| `radius` | `number` | `0.5` | Sphere / cylinder
|
|
43
|
-
| `radiusTop`/`radiusBottom` | `number` | `radius` | Cylinder
|
|
44
|
-
| `segments` | `number` | `
|
|
61
|
+
| `position` | `{x?, y?, z?}` | sibling pose or `0` | |
|
|
62
|
+
| `rotation` | `{x?, y?, z?}` | sibling pose or `0` | Euler → quaternion |
|
|
63
|
+
| `width`/`height`/`depth` | `number` | sibling or `1` | Box / finite plane |
|
|
64
|
+
| `radius` | `number` | sibling or `0.5` | Sphere / cylinder / cone / torus / capsule |
|
|
65
|
+
| `radiusTop`/`radiusBottom` | `number` | `radius` | Cylinder taper |
|
|
66
|
+
| `segments` | `number` | `12` | |
|
|
67
|
+
| `tube` | `number` | `0.4 × radius` | Torus tube |
|
|
68
|
+
| `finite` | `boolean` | `false` | `PLANE` → thin box (`width` × `height` × 0.02) in XY |
|
|
69
|
+
| `collider` | `Physics3DCollider` | see above | |
|
|
70
|
+
| `parts` | `Physics3DPartParams[]` | sibling `Graphics3D.parts` | Local offsets; no nested `COMPOUND` |
|
|
45
71
|
| `stopRotation` | `boolean` | `false` | Lock rotation sync |
|
|
46
72
|
|
|
73
|
+
`PLANE` stays **infinite** unless `finite: true`. A 10×10 visual ground that should drop you at the edge is a thin `BOX` or `finite: true`. Infinite ground still needs `rotation: { x: -Math.PI/2 }`.
|
|
74
|
+
|
|
47
75
|
Synced public fields (read each `update`):
|
|
48
76
|
|
|
49
|
-
`positionX`, `positionY`, `positionZ`, `rotationX`, `rotationY`, `rotationZ`. The system writes these back to **all sibling components** on the same `GameObject` that have those fields (duck-type check)
|
|
77
|
+
`positionX`, `positionY`, `positionZ`, `rotationX`, `rotationY`, `rotationZ`. The system writes these back to **all sibling components** on the same `GameObject` that have those fields (duck-type check), including `Transform3D`. So `Physics3D` + `Transform3D` / `Graphics3D` / `Model3D` on the same node stay in sync without custom glue code.
|
|
78
|
+
|
|
79
|
+
If the visual is parented (`addChild`), Cannon stays in world space and `Physics3D` converts the body pose to **local** before writing siblings. A moving car with physics + visual-only wheels as children does not need physics on the wheels.
|
|
50
80
|
|
|
51
81
|
`body: CANNON.Body` is injected by the engine after the body is created.
|
|
52
82
|
|
|
@@ -66,15 +96,33 @@ Listen on the `Physics3D` instance:
|
|
|
66
96
|
|
|
67
97
|
```ts
|
|
68
98
|
physics3d.on('collisionStart', (otherGameObject, selfGameObject) => { ... });
|
|
99
|
+
physics3d.on('collisionActive', (otherGameObject, selfGameObject) => { ... });
|
|
69
100
|
physics3d.on('collisionEnd', (otherGameObject, selfGameObject) => { ... });
|
|
70
101
|
```
|
|
71
102
|
|
|
103
|
+
`collisionActive` fires every `world.step` while the pair is still overlapping. Compound / torus / capsule hits still emit on this parent `Physics3D`.
|
|
104
|
+
|
|
105
|
+
### `Physics3DConstraint`
|
|
106
|
+
|
|
107
|
+
Put `Physics3DConstraint` on the same GameObject as `Physics3D`. `target` is the **other GameObject's `name`**. Both bodies must exist (pending until they do).
|
|
108
|
+
|
|
109
|
+
| `type` | Cannon constraint |
|
|
110
|
+
|--------|-------------------|
|
|
111
|
+
| `hinge` | `HingeConstraint` (`pivotA*` / `pivotB*` / `axisA*` / `axisB*`, optional `motor` / `motorSpeed`) |
|
|
112
|
+
| `point` | `PointToPointConstraint` |
|
|
113
|
+
| `lock` | `LockConstraint` |
|
|
114
|
+
| `distance` | `DistanceConstraint` (`distance`) |
|
|
115
|
+
|
|
116
|
+
Add `Physics3DConstraintSystem` after `Physics3DSystem`.
|
|
117
|
+
|
|
72
118
|
## Required setup
|
|
73
119
|
|
|
74
120
|
- Add `Renderer3DSystem` (from `plugin-renderer-3d`) **before**
|
|
75
121
|
`Physics3DSystem`.
|
|
122
|
+
- Add `Physics3DConstraintSystem` after `Physics3DSystem` when using joints.
|
|
76
123
|
- Body creation is **lazy**: it happens only when `Physics3D` is added
|
|
77
124
|
AND the `GameObject` has a parent (is in the scene hierarchy).
|
|
125
|
+
- Mesh colliders are **lazier**: they wait for `Model3D` to finish loading.
|
|
78
126
|
|
|
79
127
|
## Runtime behaviour
|
|
80
128
|
|
|
@@ -88,9 +136,13 @@ physics3d.on('collisionEnd', (otherGameObject, selfGameObject) => { ... });
|
|
|
88
136
|
|
|
89
137
|
| Symptom | Fix |
|
|
90
138
|
|---------|-----|
|
|
91
|
-
| Bodies fall forever | `PLANE`
|
|
92
|
-
|
|
|
93
|
-
|
|
|
139
|
+
| Bodies fall forever | Infinite `PLANE` needs `rotation: { x: -Math.PI/2 }` and a sane Y |
|
|
140
|
+
| Walk off a 10×10 visual plane and still stand | That plane is infinite; set `finite: true` or use a `BOX` |
|
|
141
|
+
| Mesh not following body | `Physics3D` and the visual on the **same** `GameObject` |
|
|
142
|
+
| GLB uses a 1×1×1 box | Default without `Model3D` is manual `BOX`. With `Model3D`, default is `aabb` after load |
|
|
143
|
+
| Doorway blocked on a GLB house | `collider: 'trimesh'` and `mass: 0` (forced). `aabb` / `hull` fill holes |
|
|
144
|
+
| Dynamic trimesh jitter | Don't; use `aabb` / `hull` for moving objects |
|
|
145
|
+
| Body exists but no collision | `Physics3DSystem` after `Renderer3DSystem`; object is in the scene hierarchy |
|
|
94
146
|
| Continuous low-amplitude jitter on a stack | Increase `solver.iterations` or set `allowSleep: true` |
|
|
95
147
|
| Want axis-aligned visual on rotating body | `stopRotation: true` |
|
|
96
148
|
|
|
@@ -107,6 +159,7 @@ const game = new Game({
|
|
|
107
159
|
new Renderer3DSystem({ canvas, width: 750, height: 1334 }),
|
|
108
160
|
new Graphics3DSystem(),
|
|
109
161
|
new Physics3DSystem({ gravity: { x: 0, y: -9.82, z: 0 } }),
|
|
162
|
+
new Physics3DConstraintSystem(),
|
|
110
163
|
],
|
|
111
164
|
onSystemsBootstrapComplete: (g) => {
|
|
112
165
|
const scene = g.scene;
|
|
@@ -130,6 +183,16 @@ const game = new Game({
|
|
|
130
183
|
});
|
|
131
184
|
```
|
|
132
185
|
|
|
186
|
+
Model collider:
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
hero.addComponent(new Model3D({ resource: 'hero', positionY: 0 }));
|
|
190
|
+
hero.addComponent(new Physics3D({ mass: 1 })); // aabb after the GLB loads
|
|
191
|
+
|
|
192
|
+
building.addComponent(new Model3D({ resource: 'house' }));
|
|
193
|
+
building.addComponent(new Physics3D({ collider: 'trimesh', mass: 0 }));
|
|
194
|
+
```
|
|
195
|
+
|
|
133
196
|
## Verification
|
|
134
197
|
|
|
135
198
|
- `pnpm --filter @combos-fun/plugin-cannon run build`
|
package/combos-plugin.json
CHANGED
|
@@ -4,8 +4,14 @@
|
|
|
4
4
|
"category": "physics",
|
|
5
5
|
"dimension": "3d",
|
|
6
6
|
"isCore": false,
|
|
7
|
-
"keywords": ["cannon-es", "3d", "physics", "collision", "rigid-body", "gravity"],
|
|
7
|
+
"keywords": ["cannon-es", "3d", "physics", "collision", "rigid-body", "gravity", "trimesh", "compound", "constraint", "hinge"],
|
|
8
8
|
"agentSkill": "./agent-skill.md",
|
|
9
9
|
"requires": ["@combos-fun/engine", "@combos-fun/plugin-renderer-3d"],
|
|
10
|
-
"exports": [
|
|
10
|
+
"exports": [
|
|
11
|
+
"Physics3DSystem",
|
|
12
|
+
"Physics3D",
|
|
13
|
+
"Physics3DType",
|
|
14
|
+
"Physics3DConstraint",
|
|
15
|
+
"Physics3DConstraintSystem"
|
|
16
|
+
]
|
|
11
17
|
}
|