@combos-fun/plugin-renderer-3d 0.0.44 → 0.0.46
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 +75 -145
- package/dist/plugin-renderer-3d.cjs.js +3 -17
- package/dist/plugin-renderer-3d.cjs.js.map +1 -1
- package/dist/plugin-renderer-3d.cjs.prod.js +1 -1
- package/dist/plugin-renderer-3d.d.ts +2 -4
- package/dist/plugin-renderer-3d.esm.js +4 -17
- package/dist/plugin-renderer-3d.esm.js.map +1 -1
- package/package.json +3 -3
package/agent-skill.md
CHANGED
|
@@ -1,33 +1,39 @@
|
|
|
1
1
|
# `@combos-fun/plugin-renderer-3d` — Agent notes
|
|
2
2
|
|
|
3
|
-
3D rendering foundation for Combos Fun
|
|
3
|
+
3D rendering foundation for Combos Fun, backed by Three.js (`three` ^0.172). Read this before any `plugin-renderer-3d-*` leaf skill; shared setup, resource, pose, and stale-async rules live here.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 3D drawable picker
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
- Procedural primitive, including a texture-mapped primitive → `Graphics3D`
|
|
8
|
+
- One image on one plane → `Img3D`
|
|
9
|
+
- GLB / glTF / FBX / OBJ / STL / DAE / PLY asset → `Model3D`
|
|
10
|
+
- Repeated UV texture on a plane → `TilingSprite3D`
|
|
11
|
+
- Spritesheet animation on a plane → `SpriteAnimation3D`
|
|
12
|
+
- Replace or override materials on an existing visual → sibling `Material3D`
|
|
8
13
|
|
|
9
14
|
## Public API
|
|
10
15
|
|
|
16
|
+
The current package entry exports these runtime values:
|
|
17
|
+
|
|
11
18
|
```ts
|
|
12
19
|
import {
|
|
13
|
-
Renderer3DSystem,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
tagObject3D,
|
|
26
|
-
requireNamedResource,
|
|
27
|
-
textureFromNamedImage,
|
|
20
|
+
Renderer3DSystem, ThreeContext, Renderer3D, Renderer3DManager,
|
|
21
|
+
Transform3D, Transform3DSystem,
|
|
22
|
+
Render3D, Render3DSystem,
|
|
23
|
+
Camera3D, Camera3DSystem,
|
|
24
|
+
Light3D, Light3DSystem,
|
|
25
|
+
COMBOS_GAME_OBJECT_ID, COMBOS_TRANSFORM3D,
|
|
26
|
+
tagObject3D, gameObjectIdFromObject3D, gameObjectIdFromIntersection,
|
|
27
|
+
isTransform3DRoot,
|
|
28
|
+
requireNamedResource, textureFromNamedImage, textureFromResourceImage,
|
|
29
|
+
is3DSceneParent, has3DChildGameObjects,
|
|
30
|
+
readPose, poseKeyOf, copyPose, isIdentityPose, seedTransform3D,
|
|
31
|
+
VisualPoseBridge,
|
|
28
32
|
} from '@combos-fun/plugin-renderer-3d';
|
|
29
33
|
```
|
|
30
34
|
|
|
35
|
+
Type-only exports are `Renderer3DSystemParams`, `ThreeContextParams`, `FogParams`, `HemisphereLightParams`, `ToneMappingKind`, `Transform3DLike`, `Transform3DParams`, `Render3DParams`, `Camera3DParams`, `Light3DParams`, `Light3DKind`, and `PoseLike`.
|
|
36
|
+
|
|
31
37
|
### `Renderer3DSystem`
|
|
32
38
|
|
|
33
39
|
`systemName = 'Renderer3DSystem'`. Init params:
|
|
@@ -41,95 +47,21 @@ import {
|
|
|
41
47
|
| `antialias` | `boolean` | `true` |
|
|
42
48
|
| `backgroundColor` | `number` | `0x000000` |
|
|
43
49
|
| `backgroundAlpha` | `number` | `1` |
|
|
44
|
-
| `fog` | `
|
|
45
|
-
| `hemisphereLight` | `
|
|
50
|
+
| `fog` | `boolean \| { color?, near?, far? }` | `false`; object defaults: background color / `8` / `40` |
|
|
51
|
+
| `hemisphereLight` | `boolean \| { skyColor?, groundColor?, intensity? }` | on; `0xffffff` / `0x444444` / `0.35` |
|
|
46
52
|
| `shadows` | `boolean` | `false` |
|
|
47
53
|
| `toneMapping` | `'none' \| 'aces' \| 'linear' \| 'reinhard' \| 'cineon'` | `'aces'` |
|
|
48
54
|
| `toneMappingExposure` | `number` | `1` |
|
|
49
55
|
| `ambientIntensity` | `number` | `0.6` |
|
|
50
56
|
| `directionalIntensity` | `number` | `0.8` |
|
|
51
57
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
### Built-in scene defaults
|
|
55
|
-
|
|
56
|
-
`ThreeContext` automatically creates:
|
|
57
|
-
|
|
58
|
-
- `PerspectiveCamera` (FOV 75, z=5)
|
|
59
|
-
- `AmbientLight` (`0xffffff`, intensity `0.6`)
|
|
60
|
-
- `DirectionalLight` (`0xffffff`, intensity `0.8`, position `(5, 10, 7.5)`)
|
|
61
|
-
- `HemisphereLight` (disable with `hemisphereLight: false`)
|
|
62
|
-
- `ACESFilmicToneMapping` (set `toneMapping: 'none'` to turn off)
|
|
63
|
-
- Optional `Fog` / shadow maps when those params are set
|
|
64
|
-
- `WebGLRenderer`, `Clock`
|
|
65
|
-
|
|
66
|
-
`Graphics3D` / `Model3D` / `Img3D` meshes receive `castShadow` / `receiveShadow` only when `shadows: true`. `Model3D` traverses the loaded group and stamps every mesh.
|
|
67
|
-
|
|
68
|
-
### Transform3D (pose + empty parents)
|
|
58
|
+
Pass `canvas` to reuse an element. With no `canvas`, `container` appends the newly created renderer canvas; with neither, the canvas is created but not mounted. Defaults also include a perspective camera (FOV `75`, near `0.1`, far `1000`, z=`5`), ambient light, directional light at `(5, 10, 7.5)`, hemisphere light, `WebGLRenderer`, and `Clock`.
|
|
69
59
|
|
|
70
|
-
|
|
60
|
+
## Shared contracts for every leaf plugin
|
|
71
61
|
|
|
72
|
-
|
|
62
|
+
### System order
|
|
73
63
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
`Physics3D.update()` writes pose back onto sibling `Transform3D` (and other `position*` components).
|
|
77
|
-
|
|
78
|
-
### Render3D / Camera3D / Light3D
|
|
79
|
-
|
|
80
|
-
| Component | System | Role |
|
|
81
|
-
|-----------|--------|------|
|
|
82
|
-
| `Render3D` | `Render3DSystem` | `visible`, `opacity` (2D `alpha`), `renderOrder` (2D `zIndex`), `sortableChildren` |
|
|
83
|
-
| `Camera3D` | `Camera3DSystem` | `fov` / `near` / `far` / `active` / optional `lookAt`. Pose is `Transform3D`. First camera without a pose is placed at `z=5`. No `Camera3D` → keep the default `PerspectiveCamera`. |
|
|
84
|
-
| `Light3D` | `Light3DSystem` | `ambient` / `directional` / `hemisphere` / `point` / `spot`. **The first `Light3D` removes the built-in default lights.** |
|
|
85
|
-
|
|
86
|
-
Register those systems after `Renderer3DSystem`. `Transform3DSystem` is optional (the renderer already creates roots) but recommended so pose stays in the Transform3D system.
|
|
87
|
-
|
|
88
|
-
### `Renderer3D` base class
|
|
89
|
-
|
|
90
|
-
Extend this for any custom 3D rendering plugin:
|
|
91
|
-
|
|
92
|
-
```ts
|
|
93
|
-
class MyRenderer3D extends Renderer3D {
|
|
94
|
-
init() {
|
|
95
|
-
this.rendererSystem = this.game.getSystem(Renderer3DSystem);
|
|
96
|
-
this.rendererSystem.rendererManager.register(this);
|
|
97
|
-
}
|
|
98
|
-
componentChanged(changed) {
|
|
99
|
-
const scene = this.threeContext.scene;
|
|
100
|
-
/* create / update / dispose THREE.Object3D under scene */
|
|
101
|
-
tagObject3D(object3d, changed.gameObject.id);
|
|
102
|
-
}
|
|
103
|
-
rendererUpdate(gameObject) {
|
|
104
|
-
/* per-frame sync */
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
Always call `tagObject3D(object, gameObject.id)` so `plugin-renderer-3d-event` can raycast the mesh. Attach visuals with `this.threeContext.attachVisual(id, object, gameObject)` so they parent under the `Transform3D` Group.
|
|
110
|
-
|
|
111
|
-
### Named resources (same as 2D)
|
|
112
|
-
|
|
113
|
-
3D `Img3D` / `Model3D` / `TilingSprite3D` / `SpriteAnimation3D` take an engine **resource name**. Register with `resource.addResource` (`RESOURCE_TYPE.IMAGE` / `MODEL` / `SPRITE_ANIMATION`). Put CDN URLs in `src`, not on the component. A missing name throws; there is no `http` / `./` / `/` fallback.
|
|
114
|
-
|
|
115
|
-
Use `requireNamedResource(name)` and `textureFromNamedImage(name)` from this package. 3D image systems read `data.image` (and `data.json` for spritesheets) so they do not overwrite 2D Pixi `registerInstance(IMAGE)`.
|
|
116
|
-
|
|
117
|
-
### Async loading pattern
|
|
118
|
-
|
|
119
|
-
For 3D plugins that load assets (`Img3D`, `Model3D`, etc.) use the inherited helpers to drop stale work when a component is removed mid-load:
|
|
120
|
-
|
|
121
|
-
```ts
|
|
122
|
-
const asyncId = this.increaseAsyncId(gameObject.id);
|
|
123
|
-
const data = await loadSomething();
|
|
124
|
-
if (!this.validateAsyncId(gameObject.id, asyncId)) return; // stale
|
|
125
|
-
/* attach data */
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
This prevents memory leaks and double-add bugs when the same `GameObject` is re-used quickly.
|
|
129
|
-
|
|
130
|
-
## Required setup
|
|
131
|
-
|
|
132
|
-
`Renderer3DSystem` **must** be the first system added before any 3D sub-system that calls `getSystem(Renderer3DSystem)` in `init`. For taps, also register `Event3DSystem` from `@combos-fun/plugin-renderer-3d-event`.
|
|
64
|
+
`Renderer3DSystem` must precede every 3D leaf system because each leaf registers with its `rendererManager` during `init`. Add `Transform3DSystem`, `Render3DSystem`, camera/light systems, drawable systems, then overlays such as `Material3DSystem`; add `Event3DSystem` for picking. `Transform3DSystem` is optional for root creation because `Renderer3DSystem` also ensures roots, but it remains the normal pose system.
|
|
133
65
|
|
|
134
66
|
```ts
|
|
135
67
|
new Game({
|
|
@@ -141,68 +73,66 @@ new Game({
|
|
|
141
73
|
new Light3DSystem(),
|
|
142
74
|
new Graphics3DSystem(),
|
|
143
75
|
new Img3DSystem(),
|
|
76
|
+
new Material3DSystem(),
|
|
144
77
|
new Event3DSystem(),
|
|
145
|
-
// Physics3DSystem + Physics3DConstraintSystem from @combos-fun/plugin-cannon
|
|
146
78
|
],
|
|
147
79
|
});
|
|
148
80
|
```
|
|
149
81
|
|
|
150
|
-
|
|
82
|
+
### Named resources
|
|
151
83
|
|
|
152
|
-
|
|
153
|
-
- Three.js `Object3D`s should be attached with `attachVisual` / `attachObject3D`, not by calling `scene.add` in a plugin.
|
|
84
|
+
`resource` fields contain an engine resource **name**, not a URL or path. Register the appropriate `RESOURCE_TYPE` with `resource.addResource`; URLs belong in `src`. There is no URL fallback. `requireNamedResource` rejects empty or unknown names. Asset components currently skip ADD when their own `resource` is empty; image helpers build a Three `Texture` from `data.image`, leaving the 2D Pixi resource instance alone.
|
|
154
85
|
|
|
155
|
-
|
|
86
|
+
### Pose and hierarchy
|
|
156
87
|
|
|
157
|
-
|
|
158
|
-
|---------|-----|
|
|
159
|
-
| Blank canvas | Add `Renderer3DSystem`; ensure `autoStart: true` or call `game.start()` |
|
|
160
|
-
| Nothing draws | Add the matching 3D sub-system (`Graphics3DSystem`, `Model3DSystem`, etc.) before adding components |
|
|
161
|
-
| Object loaded but not visible | Object likely loaded at origin — set `Transform3D.position*` (or sibling visual `position*`, which seeds `Transform3D`), or check camera distance (default `z=5`) |
|
|
162
|
-
| Empty parent / hand slot has no child | Add `Transform3DSystem` or rely on `Renderer3DSystem` auto-`Transform3D`; `addChild` the slot then the item |
|
|
163
|
-
| Custom lights + still have default sun | First `Light3D` removes defaults — add `Light3DSystem` |
|
|
164
|
-
| Want 2D-like hide / fade / draw order | `Render3D` + `Render3DSystem` (`visible` / `opacity` / `renderOrder`) |
|
|
165
|
-
| `getSystem` undefined | Use class reference `game.getSystem(Renderer3DSystem)` |
|
|
166
|
-
| Memory leak after fast remove | Use `increaseAsyncId` / `validateAsyncId` to drop stale async work |
|
|
167
|
-
| CORS errors loading 3D assets | Host on same origin or CORS-enabled CDN; URLs live in `addResource` `src` |
|
|
168
|
-
| `tap` never fires | Use `Event3D` + `Event3DSystem`, not 2D `Event` |
|
|
169
|
-
| Instanced primitives not pickable | `gameObjectIdFromIntersection` reads `instanceId`; official `Graphics3DSystem` fills `userData.combosInstanceGameObjectIds` |
|
|
170
|
-
|
|
171
|
-
## Minimal example
|
|
88
|
+
`Transform3D` is the pose source of truth (`position*`, `rotation*`, `scale*`, plus computed world position/rotation). A Three `Group` root is created per non-Scene GameObject, so empty parents and local child transforms work.
|
|
172
89
|
|
|
173
|
-
|
|
174
|
-
import { Game } from '@combos-fun/engine';
|
|
175
|
-
import { Renderer3DSystem } from '@combos-fun/plugin-renderer-3d';
|
|
90
|
+
Leaf visual pose fields are compatibility inputs. On first bridge sync, a non-identity visual pose seeds an identity sibling `Transform3D`; the visual object is then reset to identity under that root. Prefer writing `Transform3D` after setup. `attachVisual` tags the visual and parents it under the root; use it instead of `scene.add`.
|
|
176
91
|
|
|
177
|
-
|
|
178
|
-
systems: [
|
|
179
|
-
new Renderer3DSystem({
|
|
180
|
-
canvas: document.querySelector('#canvas')!,
|
|
181
|
-
width: 750,
|
|
182
|
-
height: 1334,
|
|
183
|
-
}),
|
|
184
|
-
],
|
|
185
|
-
});
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
This alone shows an empty 3D scene with default lighting. Add `Graphics3DSystem` etc. and matching components to render anything.
|
|
92
|
+
### Async lifecycle
|
|
189
93
|
|
|
190
|
-
|
|
94
|
+
Asset-loading systems use a per-GameObject async id to reject stale work after
|
|
95
|
+
removal, replacement, or resource changes:
|
|
191
96
|
|
|
192
97
|
```ts
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const hand = new GameObject('hand');
|
|
197
|
-
hand.addComponent(new Transform3D({ positionX: 0.4, positionY: 0.3 }));
|
|
198
|
-
const sword = new GameObject('sword');
|
|
199
|
-
sword.addComponent(new Graphics3D({ shape: 'box', width: 0.1, height: 0.8, depth: 0.1, color: 0xcccccc }));
|
|
200
|
-
hero.addChild(hand);
|
|
201
|
-
hand.addChild(sword);
|
|
98
|
+
const asyncId = this.increaseAsyncId(gameObject.id);
|
|
99
|
+
const asset = await loadSomething();
|
|
100
|
+
if (!this.validateAsyncId(gameObject.id, asyncId)) return;
|
|
202
101
|
```
|
|
203
102
|
|
|
103
|
+
Dispose assets created by rejected work. `Model3DSystem` is an exception: it
|
|
104
|
+
validates after attachment, so its skill documents the stale-clone risk.
|
|
105
|
+
|
|
106
|
+
## Core components
|
|
107
|
+
|
|
108
|
+
| Component | Defaults and behavior |
|
|
109
|
+
|-----------|-----------------------|
|
|
110
|
+
| `Render3D` | `visible: true`, `opacity: 1`, `renderOrder: 0`, `sortableChildren: false`. Every renderer update, `opacity` is written to **every mesh material under this GameObject's registered root Object3D subtree** (including nested child visuals), so it overrides visual/`Material3D` opacity for that frame. |
|
|
111
|
+
| `Camera3D` | Controls `fov`, `near`, `far`, `active`; `lookAt` is boolean with `lookAtX/Y/Z`. Without one, the fallback camera remains active. |
|
|
112
|
+
| `Light3D` | `type` defaults to `'directional'`; supports color/intensity/shadows and type-specific fields. The first `Light3D` removes all built-in default lights. |
|
|
113
|
+
|
|
114
|
+
When `shadows: true`, official drawable systems mark their meshes `castShadow` and `receiveShadow`; `Model3D` traverses the loaded clone.
|
|
115
|
+
|
|
116
|
+
## Custom renderer plugins
|
|
117
|
+
|
|
118
|
+
Extend `Renderer3D`, register it with `Renderer3DSystem.rendererManager`, handle observer changes in `componentChanged`, and do frame sync in `rendererUpdate`. `attachVisual(id, object, gameObject)` both tags and parents a visual. Use `tagObject3D` yourself only when bypassing that helper.
|
|
119
|
+
|
|
120
|
+
The engine owns the loop: `Renderer3DSystem.update()` applies `Transform3D` and invokes leaf `rendererUpdate`; `lateUpdate()` advances animation mixers and renders the frame.
|
|
121
|
+
|
|
122
|
+
## Common pitfalls
|
|
123
|
+
|
|
124
|
+
| Symptom | Fix |
|
|
125
|
+
|---------|-----|
|
|
126
|
+
| Blank canvas | Add `Renderer3DSystem`; mount its canvas; ensure the game starts |
|
|
127
|
+
| Nothing draws | Add the matching leaf system before adding its component |
|
|
128
|
+
| Loaded object is invisible | Check `Transform3D`, camera distance, front/back side, lights, and resource errors |
|
|
129
|
+
| `getSystem` is undefined | Use `game.getSystem(Renderer3DSystem)` or exact string `'Renderer3DSystem'` |
|
|
130
|
+
| Fast remove/re-add leaks or double-adds | Follow the async-id invalidation pattern |
|
|
131
|
+
| CORS failure | Serve assets from the same origin or a CORS-enabled URL registered in `src` |
|
|
132
|
+
|
|
133
|
+
Key 2D/3D differences: use `Render3D.opacity` rather than `Render.alpha`, `Event3D`/`Event3DSystem` rather than `Event`/`EventSystem`, and Cannon (`plugin-cannon`) rather than Matter for 3D physics.
|
|
134
|
+
|
|
204
135
|
## Verification
|
|
205
136
|
|
|
206
137
|
- `pnpm --filter @combos-fun/plugin-renderer-3d run build`
|
|
207
|
-
- Run a 3D example
|
|
208
|
-
errors and missing assets.
|
|
138
|
+
- Run a 3D example and inspect WebGL/resource errors in the browser console.
|
|
@@ -6,7 +6,6 @@ var three = require('three');
|
|
|
6
6
|
var inspectorDecorator = require('@combos-fun/inspector-decorator');
|
|
7
7
|
|
|
8
8
|
const COMBOS_GAME_OBJECT_ID = 'combosGameObjectId';
|
|
9
|
-
const COMBOS_INSTANCE_GAME_OBJECT_IDS = 'combosInstanceGameObjectIds';
|
|
10
9
|
const COMBOS_TRANSFORM3D = 'combosTransform3D';
|
|
11
10
|
function isTransform3DRoot(object) {
|
|
12
11
|
return !!object?.userData?.[COMBOS_TRANSFORM3D];
|
|
@@ -28,21 +27,9 @@ function gameObjectIdFromObject3D(object) {
|
|
|
28
27
|
}
|
|
29
28
|
return undefined;
|
|
30
29
|
}
|
|
31
|
-
/** Resolve a raycast hit
|
|
30
|
+
/** Resolve a raycast hit to a GameObject id. */
|
|
32
31
|
function gameObjectIdFromIntersection(intersection) {
|
|
33
|
-
|
|
34
|
-
return undefined;
|
|
35
|
-
if (typeof intersection.instanceId === 'number') {
|
|
36
|
-
let current = intersection.object;
|
|
37
|
-
while (current) {
|
|
38
|
-
const ids = current.userData?.[COMBOS_INSTANCE_GAME_OBJECT_IDS];
|
|
39
|
-
if (Array.isArray(ids) && typeof ids[intersection.instanceId] === 'number') {
|
|
40
|
-
return ids[intersection.instanceId];
|
|
41
|
-
}
|
|
42
|
-
current = current.parent;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
return gameObjectIdFromObject3D(intersection.object);
|
|
32
|
+
return gameObjectIdFromObject3D(intersection?.object);
|
|
46
33
|
}
|
|
47
34
|
|
|
48
35
|
/** True when this GameObject is a scene root (no 3D parent). */
|
|
@@ -684,7 +671,7 @@ var Renderer3DSystem = Renderer3DSystem$1;
|
|
|
684
671
|
/** Auto-generated by scripts/build-package.mjs — do not edit. */
|
|
685
672
|
Object.assign(Renderer3DSystem, {
|
|
686
673
|
packageName: "@combos-fun/plugin-renderer-3d",
|
|
687
|
-
packageVersion: "0.0.
|
|
674
|
+
packageVersion: "0.0.46",
|
|
688
675
|
});
|
|
689
676
|
|
|
690
677
|
class Renderer3D extends engine.System {
|
|
@@ -1225,7 +1212,6 @@ async function textureFromNamedImage(name) {
|
|
|
1225
1212
|
}
|
|
1226
1213
|
|
|
1227
1214
|
exports.COMBOS_GAME_OBJECT_ID = COMBOS_GAME_OBJECT_ID;
|
|
1228
|
-
exports.COMBOS_INSTANCE_GAME_OBJECT_IDS = COMBOS_INSTANCE_GAME_OBJECT_IDS;
|
|
1229
1215
|
exports.COMBOS_TRANSFORM3D = COMBOS_TRANSFORM3D;
|
|
1230
1216
|
exports.Camera3D = Camera3D;
|
|
1231
1217
|
exports.Camera3DSystem = Camera3DSystem_default;
|