@certe/atmos-editor 0.1.0
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/LICENCE +674 -0
- package/README.md +206 -0
- package/dist/asset-browser-client.d.ts +24 -0
- package/dist/asset-browser-client.d.ts.map +1 -0
- package/dist/asset-browser-client.js +60 -0
- package/dist/asset-browser-client.js.map +1 -0
- package/dist/asset-types.d.ts +33 -0
- package/dist/asset-types.d.ts.map +1 -0
- package/dist/asset-types.js +2 -0
- package/dist/asset-types.js.map +1 -0
- package/dist/bootstrap/default-factories.d.ts +38 -0
- package/dist/bootstrap/default-factories.d.ts.map +1 -0
- package/dist/bootstrap/default-factories.js +268 -0
- package/dist/bootstrap/default-factories.js.map +1 -0
- package/dist/bootstrap/editor-physics.d.ts +81 -0
- package/dist/bootstrap/editor-physics.d.ts.map +1 -0
- package/dist/bootstrap/editor-physics.js +512 -0
- package/dist/bootstrap/editor-physics.js.map +1 -0
- package/dist/bootstrap/geometry-cache.d.ts +5 -0
- package/dist/bootstrap/geometry-cache.d.ts.map +1 -0
- package/dist/bootstrap/geometry-cache.js +18 -0
- package/dist/bootstrap/geometry-cache.js.map +1 -0
- package/dist/bootstrap/keyboard-shortcuts.d.ts +4 -0
- package/dist/bootstrap/keyboard-shortcuts.d.ts.map +1 -0
- package/dist/bootstrap/keyboard-shortcuts.js +43 -0
- package/dist/bootstrap/keyboard-shortcuts.js.map +1 -0
- package/dist/bootstrap/model-import.d.ts +9 -0
- package/dist/bootstrap/model-import.d.ts.map +1 -0
- package/dist/bootstrap/model-import.js +55 -0
- package/dist/bootstrap/model-import.js.map +1 -0
- package/dist/bootstrap/start-editor.d.ts +3 -0
- package/dist/bootstrap/start-editor.d.ts.map +1 -0
- package/dist/bootstrap/start-editor.js +506 -0
- package/dist/bootstrap/start-editor.js.map +1 -0
- package/dist/bootstrap/start-player.d.ts +23 -0
- package/dist/bootstrap/start-player.d.ts.map +1 -0
- package/dist/bootstrap/start-player.js +205 -0
- package/dist/bootstrap/start-player.js.map +1 -0
- package/dist/bootstrap/types.d.ts +160 -0
- package/dist/bootstrap/types.d.ts.map +1 -0
- package/dist/bootstrap/types.js +2 -0
- package/dist/bootstrap/types.js.map +1 -0
- package/dist/camera-frustum-renderer.d.ts +15 -0
- package/dist/camera-frustum-renderer.d.ts.map +1 -0
- package/dist/camera-frustum-renderer.js +110 -0
- package/dist/camera-frustum-renderer.js.map +1 -0
- package/dist/camera-presets.d.ts +10 -0
- package/dist/camera-presets.d.ts.map +1 -0
- package/dist/camera-presets.js +15 -0
- package/dist/camera-presets.js.map +1 -0
- package/dist/collider-gizmo-renderer.d.ts +13 -0
- package/dist/collider-gizmo-renderer.d.ts.map +1 -0
- package/dist/collider-gizmo-renderer.js +217 -0
- package/dist/collider-gizmo-renderer.js.map +1 -0
- package/dist/color-utils.d.ts +5 -0
- package/dist/color-utils.d.ts.map +1 -0
- package/dist/color-utils.js +13 -0
- package/dist/color-utils.js.map +1 -0
- package/dist/components/asset-browser-panel.d.ts +14 -0
- package/dist/components/asset-browser-panel.d.ts.map +1 -0
- package/dist/components/asset-browser-panel.js +247 -0
- package/dist/components/asset-browser-panel.js.map +1 -0
- package/dist/components/context-menu.d.ts +14 -0
- package/dist/components/context-menu.d.ts.map +1 -0
- package/dist/components/context-menu.js +48 -0
- package/dist/components/context-menu.js.map +1 -0
- package/dist/components/editor-shell.d.ts +27 -0
- package/dist/components/editor-shell.d.ts.map +1 -0
- package/dist/components/editor-shell.js +327 -0
- package/dist/components/editor-shell.js.map +1 -0
- package/dist/components/fields/boolean-field.d.ts +8 -0
- package/dist/components/fields/boolean-field.d.ts.map +1 -0
- package/dist/components/fields/boolean-field.js +11 -0
- package/dist/components/fields/boolean-field.js.map +1 -0
- package/dist/components/fields/color-field.d.ts +8 -0
- package/dist/components/fields/color-field.d.ts.map +1 -0
- package/dist/components/fields/color-field.js +34 -0
- package/dist/components/fields/color-field.js.map +1 -0
- package/dist/components/fields/decimal-input.d.ts +13 -0
- package/dist/components/fields/decimal-input.d.ts.map +1 -0
- package/dist/components/fields/decimal-input.js +49 -0
- package/dist/components/fields/decimal-input.js.map +1 -0
- package/dist/components/fields/enum-field.d.ts +12 -0
- package/dist/components/fields/enum-field.d.ts.map +1 -0
- package/dist/components/fields/enum-field.js +20 -0
- package/dist/components/fields/enum-field.js.map +1 -0
- package/dist/components/fields/game-object-ref-field.d.ts +11 -0
- package/dist/components/fields/game-object-ref-field.d.ts.map +1 -0
- package/dist/components/fields/game-object-ref-field.js +73 -0
- package/dist/components/fields/game-object-ref-field.js.map +1 -0
- package/dist/components/fields/material-asset-field.d.ts +10 -0
- package/dist/components/fields/material-asset-field.d.ts.map +1 -0
- package/dist/components/fields/material-asset-field.js +114 -0
- package/dist/components/fields/material-asset-field.js.map +1 -0
- package/dist/components/fields/number-field.d.ts +10 -0
- package/dist/components/fields/number-field.d.ts.map +1 -0
- package/dist/components/fields/number-field.js +21 -0
- package/dist/components/fields/number-field.js.map +1 -0
- package/dist/components/fields/quat-field.d.ts +8 -0
- package/dist/components/fields/quat-field.d.ts.map +1 -0
- package/dist/components/fields/quat-field.js +43 -0
- package/dist/components/fields/quat-field.js.map +1 -0
- package/dist/components/fields/string-field.d.ts +7 -0
- package/dist/components/fields/string-field.d.ts.map +1 -0
- package/dist/components/fields/string-field.js +20 -0
- package/dist/components/fields/string-field.js.map +1 -0
- package/dist/components/fields/vec3-field.d.ts +8 -0
- package/dist/components/fields/vec3-field.d.ts.map +1 -0
- package/dist/components/fields/vec3-field.js +30 -0
- package/dist/components/fields/vec3-field.js.map +1 -0
- package/dist/components/hierarchy-node.d.ts +18 -0
- package/dist/components/hierarchy-node.d.ts.map +1 -0
- package/dist/components/hierarchy-node.js +77 -0
- package/dist/components/hierarchy-node.js.map +1 -0
- package/dist/components/hierarchy-panel.d.ts +14 -0
- package/dist/components/hierarchy-panel.d.ts.map +1 -0
- package/dist/components/hierarchy-panel.js +228 -0
- package/dist/components/hierarchy-panel.js.map +1 -0
- package/dist/components/inspector-panel.d.ts +14 -0
- package/dist/components/inspector-panel.d.ts.map +1 -0
- package/dist/components/inspector-panel.js +288 -0
- package/dist/components/inspector-panel.js.map +1 -0
- package/dist/components/material-inspector.d.ts +10 -0
- package/dist/components/material-inspector.d.ts.map +1 -0
- package/dist/components/material-inspector.js +130 -0
- package/dist/components/material-inspector.js.map +1 -0
- package/dist/components/post-process-panel.d.ts +9 -0
- package/dist/components/post-process-panel.d.ts.map +1 -0
- package/dist/components/post-process-panel.js +70 -0
- package/dist/components/post-process-panel.js.map +1 -0
- package/dist/components/project-gate.d.ts +8 -0
- package/dist/components/project-gate.d.ts.map +1 -0
- package/dist/components/project-gate.js +87 -0
- package/dist/components/project-gate.js.map +1 -0
- package/dist/components/settings-panel.d.ts +8 -0
- package/dist/components/settings-panel.d.ts.map +1 -0
- package/dist/components/settings-panel.js +108 -0
- package/dist/components/settings-panel.js.map +1 -0
- package/dist/components/use-splitter.d.ts +4 -0
- package/dist/components/use-splitter.d.ts.map +1 -0
- package/dist/components/use-splitter.js +22 -0
- package/dist/components/use-splitter.js.map +1 -0
- package/dist/editor-mount.d.ts +36 -0
- package/dist/editor-mount.d.ts.map +1 -0
- package/dist/editor-mount.js +161 -0
- package/dist/editor-mount.js.map +1 -0
- package/dist/editor-state.d.ts +55 -0
- package/dist/editor-state.d.ts.map +1 -0
- package/dist/editor-state.js +181 -0
- package/dist/editor-state.js.map +1 -0
- package/dist/gizmo-meshes.d.ts +9 -0
- package/dist/gizmo-meshes.d.ts.map +1 -0
- package/dist/gizmo-meshes.js +229 -0
- package/dist/gizmo-meshes.js.map +1 -0
- package/dist/gizmo-renderer.d.ts +16 -0
- package/dist/gizmo-renderer.d.ts.map +1 -0
- package/dist/gizmo-renderer.js +77 -0
- package/dist/gizmo-renderer.js.map +1 -0
- package/dist/gizmo-state.d.ts +25 -0
- package/dist/gizmo-state.d.ts.map +1 -0
- package/dist/gizmo-state.js +269 -0
- package/dist/gizmo-state.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/joint-gizmo-renderer.d.ts +13 -0
- package/dist/joint-gizmo-renderer.d.ts.map +1 -0
- package/dist/joint-gizmo-renderer.js +133 -0
- package/dist/joint-gizmo-renderer.js.map +1 -0
- package/dist/material-manager.d.ts +22 -0
- package/dist/material-manager.d.ts.map +1 -0
- package/dist/material-manager.js +156 -0
- package/dist/material-manager.js.map +1 -0
- package/dist/object-picker.d.ts +11 -0
- package/dist/object-picker.d.ts.map +1 -0
- package/dist/object-picker.js +104 -0
- package/dist/object-picker.js.map +1 -0
- package/dist/orbit-camera.d.ts +38 -0
- package/dist/orbit-camera.d.ts.map +1 -0
- package/dist/orbit-camera.js +180 -0
- package/dist/orbit-camera.js.map +1 -0
- package/dist/overlay-renderer.d.ts +23 -0
- package/dist/overlay-renderer.d.ts.map +1 -0
- package/dist/overlay-renderer.js +95 -0
- package/dist/overlay-renderer.js.map +1 -0
- package/dist/player-entry.d.ts +6 -0
- package/dist/player-entry.d.ts.map +1 -0
- package/dist/player-entry.js +4 -0
- package/dist/player-entry.js.map +1 -0
- package/dist/project-fs.d.ts +28 -0
- package/dist/project-fs.d.ts.map +1 -0
- package/dist/project-fs.js +258 -0
- package/dist/project-fs.js.map +1 -0
- package/dist/project-seed.d.ts +3 -0
- package/dist/project-seed.d.ts.map +1 -0
- package/dist/project-seed.js +35 -0
- package/dist/project-seed.js.map +1 -0
- package/dist/project-settings.d.ts +29 -0
- package/dist/project-settings.d.ts.map +1 -0
- package/dist/project-settings.js +69 -0
- package/dist/project-settings.js.map +1 -0
- package/dist/property-setters.d.ts +4 -0
- package/dist/property-setters.d.ts.map +1 -0
- package/dist/property-setters.js +58 -0
- package/dist/property-setters.js.map +1 -0
- package/dist/scene-operations.d.ts +14 -0
- package/dist/scene-operations.d.ts.map +1 -0
- package/dist/scene-operations.js +195 -0
- package/dist/scene-operations.js.map +1 -0
- package/dist/scene-snapshot.d.ts +28 -0
- package/dist/scene-snapshot.d.ts.map +1 -0
- package/dist/scene-snapshot.js +97 -0
- package/dist/scene-snapshot.js.map +1 -0
- package/dist/script-discovery.d.ts +12 -0
- package/dist/script-discovery.d.ts.map +1 -0
- package/dist/script-discovery.js +81 -0
- package/dist/script-discovery.js.map +1 -0
- package/dist/selection-utils.d.ts +4 -0
- package/dist/selection-utils.d.ts.map +1 -0
- package/dist/selection-utils.js +19 -0
- package/dist/selection-utils.js.map +1 -0
- package/dist/simple-material-loader.d.ts +17 -0
- package/dist/simple-material-loader.d.ts.map +1 -0
- package/dist/simple-material-loader.js +85 -0
- package/dist/simple-material-loader.js.map +1 -0
- package/dist/wireframe-renderer.d.ts +18 -0
- package/dist/wireframe-renderer.d.ts.map +1 -0
- package/dist/wireframe-renderer.js +106 -0
- package/dist/wireframe-renderer.js.map +1 -0
- package/package.json +65 -0
- package/src/index.ts +48 -0
- package/vite-plugin.d.ts +15 -0
- package/vite-plugin.mjs +395 -0
package/README.md
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# @certe/atmos-editor
|
|
2
|
+
|
|
3
|
+
Unity-style editor for the Atmos Engine, built with React. Provides scene hierarchy, inspector, gizmos, object picking, orbit camera, project file I/O, and material asset management — all running in the browser.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Getting Started
|
|
8
|
+
|
|
9
|
+
### 1. Create a new project
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
mkdir my-game && cd my-game
|
|
13
|
+
npm init -y
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
### 2. Install dependencies
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @certe/atmos-editor @certe/atmos-core @certe/atmos-math @certe/atmos-renderer @certe/atmos-physics
|
|
20
|
+
npm install -D vite @vitejs/plugin-react
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### 3. Configure Vite
|
|
24
|
+
|
|
25
|
+
Create `vite.config.ts`:
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { defineConfig } from 'vite';
|
|
29
|
+
import react from '@vitejs/plugin-react';
|
|
30
|
+
import { atmosPlugin } from '@certe/atmos-editor/vite';
|
|
31
|
+
|
|
32
|
+
export default defineConfig({
|
|
33
|
+
plugins: [react(), atmosPlugin()],
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 4. Create the entry point
|
|
38
|
+
|
|
39
|
+
Create `src/main.ts`:
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import { startEditor, createEditorPhysics } from '@certe/atmos-editor';
|
|
43
|
+
|
|
44
|
+
await startEditor({
|
|
45
|
+
physics: await createEditorPhysics(),
|
|
46
|
+
scriptModules: import.meta.glob('./scripts/*.ts', { eager: true }),
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### 5. Run the editor
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npx vite
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The editor opens with a WebGPU viewport, scene hierarchy, inspector, and gizmo tools. Place game scripts in `src/scripts/` and they'll be automatically discovered.
|
|
57
|
+
|
|
58
|
+
### 6. Build a standalone game
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
npx vite build
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
This produces a `dist/` folder with a player-only build — no editor UI, no React. The build entry is generated automatically by `atmosPlugin`.
|
|
65
|
+
|
|
66
|
+
To preview the build:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npx vite preview
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Project Structure
|
|
75
|
+
|
|
76
|
+
A typical Atmos project looks like this:
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
my-game/
|
|
80
|
+
src/
|
|
81
|
+
main.ts # Editor entry point
|
|
82
|
+
scripts/ # Game scripts (auto-discovered)
|
|
83
|
+
player-controller.ts
|
|
84
|
+
enemy-ai.ts
|
|
85
|
+
scenes/
|
|
86
|
+
main.scene.json # Saved from editor
|
|
87
|
+
materials/
|
|
88
|
+
metal.mat.json # Material assets
|
|
89
|
+
textures/
|
|
90
|
+
diffuse.jpg
|
|
91
|
+
project-settings.json # Editor settings (defaultScene, physics)
|
|
92
|
+
vite.config.ts
|
|
93
|
+
package.json
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Custom HTML
|
|
99
|
+
|
|
100
|
+
You can provide your own `index.html` with a game UI overlay:
|
|
101
|
+
|
|
102
|
+
```html
|
|
103
|
+
<!DOCTYPE html>
|
|
104
|
+
<html>
|
|
105
|
+
<head>
|
|
106
|
+
<style>
|
|
107
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
108
|
+
html, body { width: 100%; height: 100%; overflow: hidden; }
|
|
109
|
+
#atmos-container { position: relative; width: 100%; height: 100%; }
|
|
110
|
+
#atmos-canvas { width: 100%; height: 100%; display: block; }
|
|
111
|
+
#atmos-ui { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; }
|
|
112
|
+
#atmos-ui * { pointer-events: auto; }
|
|
113
|
+
</style>
|
|
114
|
+
</head>
|
|
115
|
+
<body>
|
|
116
|
+
<div id="atmos-container">
|
|
117
|
+
<canvas id="atmos-canvas"></canvas>
|
|
118
|
+
<div id="atmos-ui">
|
|
119
|
+
<!-- Your game UI here (health bar, minimap, etc.) -->
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
</body>
|
|
123
|
+
</html>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The `#atmos-ui` overlay renders on top of the canvas in both editor and build modes. If no `index.html` is provided, one is generated automatically.
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## EditorState
|
|
131
|
+
|
|
132
|
+
Central state machine with event-driven updates:
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
editorState.select(gameObject); // emits 'selectionChanged'
|
|
136
|
+
editorState.setGizmoMode('rotate'); // emits 'gizmoModeChanged'
|
|
137
|
+
editorState.togglePause(); // emits 'pauseChanged'
|
|
138
|
+
editorState.setScene(newScene); // emits 'sceneChanged'
|
|
139
|
+
|
|
140
|
+
editorState.on('selectionChanged', () => { /* update UI */ });
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Events: `selectionChanged` · `sceneChanged` · `pauseChanged` · `gizmoModeChanged` · `snapChanged` · `inspectorChanged` · `sceneRestored` · `assetsChanged` · `projectChanged` · `materialSelected` · `wireframeChanged`
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## UI Panels
|
|
148
|
+
|
|
149
|
+
| Panel | Description |
|
|
150
|
+
|---|---|
|
|
151
|
+
| **Hierarchy** | Tree view with drag-and-drop reparenting, search/filter, context menu (create primitives, lights, duplicate, delete) |
|
|
152
|
+
| **Inspector** | Transform fields, component list with enable/disable/remove, add component button, dynamic property fields |
|
|
153
|
+
| **Asset Browser** | File tree from project directory, double-click to load models |
|
|
154
|
+
| **Post-Process** | Exposure, SSAO, Bloom, Vignette controls |
|
|
155
|
+
| **Material Inspector** | Edit material properties when a `.mat.json` is selected |
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Editor Controls
|
|
160
|
+
|
|
161
|
+
- **Left click** — select object
|
|
162
|
+
- **Drag** — orbit camera
|
|
163
|
+
- **Shift+Drag** — pan camera
|
|
164
|
+
- **Scroll** — zoom
|
|
165
|
+
- **W** — translate gizmo
|
|
166
|
+
- **E** — rotate gizmo
|
|
167
|
+
- **R** — scale gizmo
|
|
168
|
+
- Camera presets: Front, Back, Left, Right, Top, Bottom
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Vite Plugin Options
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
atmosPlugin({
|
|
176
|
+
include: ['src'], // Directories to scan for asset browser
|
|
177
|
+
exclude: ['temp'], // Directories to exclude
|
|
178
|
+
entry: 'src/main.ts', // Entry file (default)
|
|
179
|
+
})
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## startEditor() Options
|
|
185
|
+
|
|
186
|
+
| Option | Description |
|
|
187
|
+
|---|---|
|
|
188
|
+
| `canvas` / `container` | Custom DOM elements |
|
|
189
|
+
| `physics` | Physics plugin (Rapier integration) |
|
|
190
|
+
| `setupScene` | Callback for custom scene initialization |
|
|
191
|
+
| `primitiveFactory` | Custom primitive creation logic |
|
|
192
|
+
| `componentFactory` | Custom component creation logic |
|
|
193
|
+
| `deserializeContext` | Custom deserialization hooks |
|
|
194
|
+
| `showAssetBrowser` | Toggle asset browser panel |
|
|
195
|
+
| `scriptModules` | `import.meta.glob()` result for script discovery |
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Dependencies
|
|
200
|
+
|
|
201
|
+
- `@certe/atmos-core` — Component, GameObject, Scene, serialization
|
|
202
|
+
- `@certe/atmos-renderer` — RenderSystem, Camera, Material, lights, pipelines
|
|
203
|
+
- `@certe/atmos-math` — Vec3, Mat4, Quat, Ray
|
|
204
|
+
- `@certe/atmos-assets` — glTF model loading
|
|
205
|
+
- `@certe/atmos-animation` — AnimationMixer registration
|
|
206
|
+
- `react` / `react-dom` — Editor UI (not included in game builds)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { AssetEntry } from './asset-types.js';
|
|
2
|
+
type AssetTreeListener = () => void;
|
|
3
|
+
/**
|
|
4
|
+
* Browser-side client that fetches the project file tree from the
|
|
5
|
+
* Vite dev server /__atmos_assets endpoint and subscribes to HMR
|
|
6
|
+
* events for live updates when files change on disk.
|
|
7
|
+
*/
|
|
8
|
+
export declare class AssetBrowserClient {
|
|
9
|
+
private _entries;
|
|
10
|
+
private _root;
|
|
11
|
+
private readonly _listeners;
|
|
12
|
+
private _disposed;
|
|
13
|
+
get entries(): AssetEntry[];
|
|
14
|
+
get root(): string;
|
|
15
|
+
/** Fetch initial tree and subscribe to HMR file-change events */
|
|
16
|
+
init(): Promise<void>;
|
|
17
|
+
/** Register a listener called whenever the file tree updates. Returns unsubscribe fn. */
|
|
18
|
+
onChange(fn: AssetTreeListener): () => void;
|
|
19
|
+
dispose(): void;
|
|
20
|
+
private _fetchTree;
|
|
21
|
+
private _notify;
|
|
22
|
+
}
|
|
23
|
+
export {};
|
|
24
|
+
//# sourceMappingURL=asset-browser-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asset-browser-client.d.ts","sourceRoot":"","sources":["../src/asset-browser-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAuC,MAAM,kBAAkB,CAAC;AAExF,KAAK,iBAAiB,GAAG,MAAM,IAAI,CAAC;AAEpC;;;;GAIG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAoB;IACpC,OAAO,CAAC,KAAK,CAAM;IACnB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAgC;IAC3D,OAAO,CAAC,SAAS,CAAS;IAE1B,IAAI,OAAO,IAAI,UAAU,EAAE,CAE1B;IACD,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,iEAAiE;IAC3D,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAa3B,yFAAyF;IACzF,QAAQ,CAAC,EAAE,EAAE,iBAAiB,GAAG,MAAM,IAAI;IAK3C,OAAO,IAAI,IAAI;YAKD,UAAU;IAgBxB,OAAO,CAAC,OAAO;CAGhB"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-side client that fetches the project file tree from the
|
|
3
|
+
* Vite dev server /__atmos_assets endpoint and subscribes to HMR
|
|
4
|
+
* events for live updates when files change on disk.
|
|
5
|
+
*/
|
|
6
|
+
export class AssetBrowserClient {
|
|
7
|
+
_entries = [];
|
|
8
|
+
_root = '';
|
|
9
|
+
_listeners = new Set();
|
|
10
|
+
_disposed = false;
|
|
11
|
+
get entries() {
|
|
12
|
+
return this._entries;
|
|
13
|
+
}
|
|
14
|
+
get root() {
|
|
15
|
+
return this._root;
|
|
16
|
+
}
|
|
17
|
+
/** Fetch initial tree and subscribe to HMR file-change events */
|
|
18
|
+
async init() {
|
|
19
|
+
await this._fetchTree();
|
|
20
|
+
// Subscribe to Vite HMR custom events when available
|
|
21
|
+
const meta = import.meta;
|
|
22
|
+
if (meta.hot) {
|
|
23
|
+
meta.hot.on('atmos:asset-change', (_event) => {
|
|
24
|
+
if (this._disposed)
|
|
25
|
+
return;
|
|
26
|
+
this._fetchTree();
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/** Register a listener called whenever the file tree updates. Returns unsubscribe fn. */
|
|
31
|
+
onChange(fn) {
|
|
32
|
+
this._listeners.add(fn);
|
|
33
|
+
return () => this._listeners.delete(fn);
|
|
34
|
+
}
|
|
35
|
+
dispose() {
|
|
36
|
+
this._disposed = true;
|
|
37
|
+
this._listeners.clear();
|
|
38
|
+
}
|
|
39
|
+
async _fetchTree() {
|
|
40
|
+
try {
|
|
41
|
+
const res = await fetch('/__atmos_assets');
|
|
42
|
+
if (!res.ok) {
|
|
43
|
+
console.warn('[AssetBrowser] Asset listing endpoint not available');
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const data = await res.json();
|
|
47
|
+
this._root = data.root;
|
|
48
|
+
this._entries = data.entries;
|
|
49
|
+
this._notify();
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// Endpoint not available (e.g. production build) — ignore
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
_notify() {
|
|
56
|
+
for (const fn of this._listeners)
|
|
57
|
+
fn();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=asset-browser-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asset-browser-client.js","sourceRoot":"","sources":["../src/asset-browser-client.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,MAAM,OAAO,kBAAkB;IACrB,QAAQ,GAAiB,EAAE,CAAC;IAC5B,KAAK,GAAG,EAAE,CAAC;IACF,UAAU,GAAG,IAAI,GAAG,EAAqB,CAAC;IACnD,SAAS,GAAG,KAAK,CAAC;IAE1B,IAAI,OAAO;QACT,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;IACD,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED,iEAAiE;IACjE,KAAK,CAAC,IAAI;QACR,MAAM,IAAI,CAAC,UAAU,EAAE,CAAC;QAExB,qDAAqD;QACrD,MAAM,IAAI,GAAG,MAAM,CAAC,IAA8F,CAAC;QACnH,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,oBAAoB,EAAE,CAAC,MAAwB,EAAE,EAAE;gBAC7D,IAAI,IAAI,CAAC,SAAS;oBAAE,OAAO;gBAC3B,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,CAAC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,yFAAyF;IACzF,QAAQ,CAAC,EAAqB;QAC5B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACxB,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC1C,CAAC;IAED,OAAO;QACL,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QACtB,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;IAC1B,CAAC;IAEO,KAAK,CAAC,UAAU;QACtB,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,iBAAiB,CAAC,CAAC;YAC3C,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;gBACZ,OAAO,CAAC,IAAI,CAAC,qDAAqD,CAAC,CAAC;gBACpE,OAAO;YACT,CAAC;YACD,MAAM,IAAI,GAAsB,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;YACjD,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC;YACvB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC;YAC7B,IAAI,CAAC,OAAO,EAAE,CAAC;QACjB,CAAC;QAAC,MAAM,CAAC;YACP,0DAA0D;QAC5D,CAAC;IACH,CAAC;IAEO,OAAO;QACb,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,UAAU;YAAE,EAAE,EAAE,CAAC;IACzC,CAAC;CACF"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Component } from '@certe/atmos-core';
|
|
2
|
+
/** A single file or folder in the project asset tree */
|
|
3
|
+
export interface AssetEntry {
|
|
4
|
+
/** Relative path from project root, e.g. "src/scripts/Rotator.ts" */
|
|
5
|
+
path: string;
|
|
6
|
+
/** File name, e.g. "Rotator.ts" */
|
|
7
|
+
name: string;
|
|
8
|
+
kind: 'file' | 'directory';
|
|
9
|
+
/** Extension without dot, e.g. "ts". Empty string for directories. */
|
|
10
|
+
extension: string;
|
|
11
|
+
/** Child entries (directories only) */
|
|
12
|
+
children?: AssetEntry[];
|
|
13
|
+
}
|
|
14
|
+
/** Response shape from the Vite plugin /__atmos_assets endpoint */
|
|
15
|
+
export interface AssetListResponse {
|
|
16
|
+
root: string;
|
|
17
|
+
entries: AssetEntry[];
|
|
18
|
+
}
|
|
19
|
+
/** HMR event payload for file changes */
|
|
20
|
+
export interface AssetChangeEvent {
|
|
21
|
+
kind: 'add' | 'change' | 'unlink';
|
|
22
|
+
path: string;
|
|
23
|
+
}
|
|
24
|
+
/** A discovered script that can be attached to GameObjects */
|
|
25
|
+
export interface ScriptAsset {
|
|
26
|
+
/** Relative path to the .ts file */
|
|
27
|
+
path: string;
|
|
28
|
+
/** Display name (class name) */
|
|
29
|
+
name: string;
|
|
30
|
+
/** The Component constructor */
|
|
31
|
+
ctor: new () => Component;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=asset-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asset-types.d.ts","sourceRoot":"","sources":["../src/asset-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAEnD,wDAAwD;AACxD,MAAM,WAAW,UAAU;IACzB,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC;IACb,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,sEAAsE;IACtE,SAAS,EAAE,MAAM,CAAC;IAClB,uCAAuC;IACvC,QAAQ,CAAC,EAAE,UAAU,EAAE,CAAC;CACzB;AAED,mEAAmE;AACnE,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,UAAU,EAAE,CAAC;CACvB;AAED,yCAAyC;AACzC,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,KAAK,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAClC,IAAI,EAAE,MAAM,CAAC;CACd;AAED,8DAA8D;AAC9D,MAAM,WAAW,WAAW;IAC1B,oCAAoC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,gCAAgC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,gCAAgC;IAChC,IAAI,EAAE,UAAU,SAAS,CAAC;CAC3B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asset-types.js","sourceRoot":"","sources":["../src/asset-types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { GameObject } from '@certe/atmos-core';
|
|
2
|
+
import type { Component, DeserializeContext } from '@certe/atmos-core';
|
|
3
|
+
import type { MeshRendererContext } from '@certe/atmos-renderer';
|
|
4
|
+
import type { ModelAsset } from '@certe/atmos-assets';
|
|
5
|
+
import type { PrimitiveType } from '../editor-mount.js';
|
|
6
|
+
import type { EditorState } from '../editor-state.js';
|
|
7
|
+
import type { MaterialManager } from '../material-manager.js';
|
|
8
|
+
import type { EditorPhysicsPlugin } from './types.js';
|
|
9
|
+
import type { Mesh } from '@certe/atmos-renderer';
|
|
10
|
+
import type { MeshRecord } from './geometry-cache.js';
|
|
11
|
+
export interface FactoryDeps {
|
|
12
|
+
rendererCtx: MeshRendererContext;
|
|
13
|
+
meshes: MeshRecord;
|
|
14
|
+
physics: EditorPhysicsPlugin | undefined;
|
|
15
|
+
/** Lazy ref — set after mountEditor returns, before any callback fires. */
|
|
16
|
+
editorState: {
|
|
17
|
+
current: EditorState | null;
|
|
18
|
+
};
|
|
19
|
+
materialManager: {
|
|
20
|
+
current: MaterialManager | null;
|
|
21
|
+
};
|
|
22
|
+
loadModelMesh?: (source: string) => Promise<{
|
|
23
|
+
mesh: Mesh;
|
|
24
|
+
skinned: boolean;
|
|
25
|
+
skinIndex?: number;
|
|
26
|
+
} | null>;
|
|
27
|
+
loadModelData?: (source: string) => Promise<{
|
|
28
|
+
mesh: Mesh;
|
|
29
|
+
asset: ModelAsset;
|
|
30
|
+
meshIndex: number;
|
|
31
|
+
} | null>;
|
|
32
|
+
}
|
|
33
|
+
export declare function createDefaultPrimitiveFactory(deps: FactoryDeps): (type: PrimitiveType, name: string) => GameObject;
|
|
34
|
+
export declare function createDefaultComponentFactory(deps: FactoryDeps): (ctor: new () => Component, go: GameObject) => void;
|
|
35
|
+
export declare function createDefaultComponentFilter(deps: FactoryDeps): (ctor: new () => Component, go: GameObject) => string | null;
|
|
36
|
+
export declare function createDefaultComponentRemover(deps: FactoryDeps): (comp: Component, go: GameObject) => void;
|
|
37
|
+
export declare function createDefaultDeserializeContext(deps: FactoryDeps): DeserializeContext;
|
|
38
|
+
//# sourceMappingURL=default-factories.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"default-factories.d.ts","sourceRoot":"","sources":["../../src/bootstrap/default-factories.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAkD,MAAM,mBAAmB,CAAC;AAC/F,OAAO,KAAK,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvE,OAAO,KAAK,EAAE,mBAAmB,EAA0B,MAAM,uBAAuB,CAAC;AASzF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAKtD,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,mBAAmB,CAAC;IACjC,MAAM,EAAE,UAAU,CAAC;IACnB,OAAO,EAAE,mBAAmB,GAAG,SAAS,CAAC;IACzC,2EAA2E;IAC3E,WAAW,EAAE;QAAE,OAAO,EAAE,WAAW,GAAG,IAAI,CAAA;KAAE,CAAC;IAC7C,eAAe,EAAE;QAAE,OAAO,EAAE,eAAe,GAAG,IAAI,CAAA;KAAE,CAAC;IACrD,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC;QAC1C,IAAI,EAAE,IAAI,CAAC;QAAC,OAAO,EAAE,OAAO,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;KAClD,GAAG,IAAI,CAAC,CAAC;IACV,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC;QAC1C,IAAI,EAAE,IAAI,CAAC;QAAC,KAAK,EAAE,UAAU,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;KAClD,GAAG,IAAI,CAAC,CAAC;CACX;AAID,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,WAAW,IACrD,MAAM,aAAa,EAAE,MAAM,MAAM,KAAG,UAAU,CA6BvD;AAID,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,WAAW,IACrD,MAAM,UAAU,SAAS,EAAE,IAAI,UAAU,KAAG,IAAI,CAyBzD;AAID,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,WAAW,IACpD,MAAM,UAAU,SAAS,EAAE,IAAI,UAAU,KAAG,MAAM,GAAG,IAAI,CAGlE;AAID,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,WAAW,IACrD,MAAM,SAAS,EAAE,IAAI,UAAU,KAAG,IAAI,CAI/C;AAID,wBAAgB,+BAA+B,CAAC,IAAI,EAAE,WAAW,GAAG,kBAAkB,CA+IrF"}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { GameObject, getAllRegisteredComponents, applyComponentData } from '@certe/atmos-core';
|
|
2
|
+
import { MeshRenderer, SkinnedMeshRenderer, Camera, DirectionalLight, PointLight, SpotLight, createMaterial, } from '@certe/atmos-renderer';
|
|
3
|
+
import { AnimationMixer, AnimationHandler, createSkeleton, createAnimationClip, } from '@certe/atmos-animation';
|
|
4
|
+
const DEFAULT_MAT = { albedo: [0.7, 0.7, 0.7, 1], metallic: 0.0, roughness: 0.5 };
|
|
5
|
+
const DEFAULT_MAT_PATH = 'materials/default.mat.json';
|
|
6
|
+
// ---- Primitive factory ---- //
|
|
7
|
+
export function createDefaultPrimitiveFactory(deps) {
|
|
8
|
+
return (type, name) => {
|
|
9
|
+
const go = new GameObject(name);
|
|
10
|
+
if (type === 'camera') {
|
|
11
|
+
const cam = go.addComponent(Camera);
|
|
12
|
+
const scene = deps.editorState.current?.scene;
|
|
13
|
+
if (scene && !Camera.getMain(scene))
|
|
14
|
+
cam.isMainCamera = true;
|
|
15
|
+
}
|
|
16
|
+
else if (type === 'directionalLight') {
|
|
17
|
+
go.addComponent(DirectionalLight);
|
|
18
|
+
}
|
|
19
|
+
else if (type === 'pointLight') {
|
|
20
|
+
go.addComponent(PointLight);
|
|
21
|
+
}
|
|
22
|
+
else if (type === 'spotLight') {
|
|
23
|
+
go.addComponent(SpotLight);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
const mr = go.addComponent(MeshRenderer);
|
|
27
|
+
const mat = createMaterial(DEFAULT_MAT);
|
|
28
|
+
mr.init(deps.rendererCtx, deps.meshes[type], mat);
|
|
29
|
+
mr.meshSource = `primitive:${type}`;
|
|
30
|
+
mr.materialSource = DEFAULT_MAT_PATH;
|
|
31
|
+
// Async: load shared material from manager
|
|
32
|
+
const mm = deps.materialManager.current;
|
|
33
|
+
if (mm) {
|
|
34
|
+
mm.getMaterial(DEFAULT_MAT_PATH).then((m) => {
|
|
35
|
+
mr.material = m;
|
|
36
|
+
mr.materialBindGroup = null;
|
|
37
|
+
}).catch(() => { });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return go;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
// ---- Component factory ---- //
|
|
44
|
+
export function createDefaultComponentFactory(deps) {
|
|
45
|
+
return (ctor, go) => {
|
|
46
|
+
// Delegate to physics plugin first (RigidBody, Collider, Joint)
|
|
47
|
+
if (deps.physics?.handleAddComponent(ctor, go))
|
|
48
|
+
return;
|
|
49
|
+
if (ctor === MeshRenderer) {
|
|
50
|
+
const mr = go.addComponent(MeshRenderer);
|
|
51
|
+
const mat = createMaterial(DEFAULT_MAT);
|
|
52
|
+
mr.init(deps.rendererCtx, deps.meshes.cube, mat);
|
|
53
|
+
mr.meshSource = 'primitive:cube';
|
|
54
|
+
mr.materialSource = DEFAULT_MAT_PATH;
|
|
55
|
+
const mm = deps.materialManager.current;
|
|
56
|
+
if (mm) {
|
|
57
|
+
mm.getMaterial(DEFAULT_MAT_PATH).then((m) => {
|
|
58
|
+
mr.material = m;
|
|
59
|
+
mr.materialBindGroup = null;
|
|
60
|
+
}).catch(() => { });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
else if (ctor === Camera) {
|
|
64
|
+
const cam = go.addComponent(Camera);
|
|
65
|
+
const scene = deps.editorState.current?.scene;
|
|
66
|
+
if (scene && !Camera.getMain(scene))
|
|
67
|
+
cam.isMainCamera = true;
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
go.addComponent(ctor);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
// ---- Component filter ---- //
|
|
75
|
+
export function createDefaultComponentFilter(deps) {
|
|
76
|
+
return (ctor, go) => {
|
|
77
|
+
return deps.physics?.canAddComponent(ctor, go) ?? null;
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
// ---- Component remover ---- //
|
|
81
|
+
export function createDefaultComponentRemover(deps) {
|
|
82
|
+
return (comp, go) => {
|
|
83
|
+
if (deps.physics?.handleRemoveComponent(comp, go))
|
|
84
|
+
return;
|
|
85
|
+
go.removeComponent(comp);
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
// ---- Deserialize context ---- //
|
|
89
|
+
export function createDefaultDeserializeContext(deps) {
|
|
90
|
+
let nameToCtors = null;
|
|
91
|
+
const getCtorMap = () => {
|
|
92
|
+
if (!nameToCtors) {
|
|
93
|
+
nameToCtors = new Map();
|
|
94
|
+
for (const [ctor, def] of getAllRegisteredComponents()) {
|
|
95
|
+
nameToCtors.set(def.name, ctor);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return nameToCtors;
|
|
99
|
+
};
|
|
100
|
+
const deferredOps = [];
|
|
101
|
+
const asyncLoads = [];
|
|
102
|
+
return {
|
|
103
|
+
onComponent(go, type, data) {
|
|
104
|
+
// Delegate physics types to plugin
|
|
105
|
+
if (deps.physics?.handleDeserialize(go, type, data, deferredOps))
|
|
106
|
+
return;
|
|
107
|
+
switch (type) {
|
|
108
|
+
case 'MeshRenderer': {
|
|
109
|
+
const mr = go.addComponent(MeshRenderer);
|
|
110
|
+
const mat = createMaterial(DEFAULT_MAT);
|
|
111
|
+
const source = data['meshSource'] ?? 'primitive:cube';
|
|
112
|
+
// Resolve mesh: primitive or placeholder for model
|
|
113
|
+
const primName = source.startsWith('primitive:') ? source.slice(10) : 'cube';
|
|
114
|
+
const mesh = deps.meshes[primName] ?? deps.meshes.cube;
|
|
115
|
+
mr.init(deps.rendererCtx, mesh, mat);
|
|
116
|
+
mr.meshSource = source;
|
|
117
|
+
const rawMat = data['materialSource'];
|
|
118
|
+
const matPath = rawMat?.endsWith('.mat.json') ? rawMat : DEFAULT_MAT_PATH;
|
|
119
|
+
mr.materialSource = matPath;
|
|
120
|
+
applyComponentData(mr, data);
|
|
121
|
+
// Queue async model mesh load if source is model:*
|
|
122
|
+
if (source.startsWith('model:') && deps.loadModelMesh) {
|
|
123
|
+
asyncLoads.push(deps.loadModelMesh(source).then((result) => {
|
|
124
|
+
if (result)
|
|
125
|
+
mr.mesh = result.mesh;
|
|
126
|
+
}).catch(() => { }));
|
|
127
|
+
}
|
|
128
|
+
// Queue async material load
|
|
129
|
+
const mm = deps.materialManager.current;
|
|
130
|
+
if (mm && matPath) {
|
|
131
|
+
asyncLoads.push(mm.getMaterial(matPath).then((m) => {
|
|
132
|
+
mr.material = m;
|
|
133
|
+
mr.materialBindGroup = null;
|
|
134
|
+
}).catch(() => { }));
|
|
135
|
+
}
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
case 'SkinnedMeshRenderer': {
|
|
139
|
+
const source = data['meshSource'] ?? '';
|
|
140
|
+
if (source.startsWith('model:') && deps.loadModelData) {
|
|
141
|
+
asyncLoads.push(deps.loadModelData(source).then((result) => {
|
|
142
|
+
if (!result)
|
|
143
|
+
return;
|
|
144
|
+
const { mesh, asset, meshIndex } = result;
|
|
145
|
+
const modelMesh = asset.meshes[meshIndex];
|
|
146
|
+
if (!modelMesh?.skinned)
|
|
147
|
+
return;
|
|
148
|
+
const skinIdx = modelMesh.skinIndex ?? 0;
|
|
149
|
+
const skin = asset.skins[skinIdx];
|
|
150
|
+
if (!skin)
|
|
151
|
+
return;
|
|
152
|
+
// Resolve material
|
|
153
|
+
const rawMat = data['materialSource'];
|
|
154
|
+
const matPath = rawMat?.endsWith('.mat.json') ? rawMat : DEFAULT_MAT_PATH;
|
|
155
|
+
const mat = createMaterial(DEFAULT_MAT);
|
|
156
|
+
const smr = go.addComponent(SkinnedMeshRenderer);
|
|
157
|
+
smr.init(deps.rendererCtx, mesh, skin.jointNodeIndices.length, mat);
|
|
158
|
+
smr.meshSource = source;
|
|
159
|
+
smr.materialSource = matPath;
|
|
160
|
+
applyComponentData(smr, data);
|
|
161
|
+
// Set up skeleton + AnimationMixer (mirrors model-instantiate setupSkinning)
|
|
162
|
+
setupSkeletonFromSkin(go, asset, skin, skinIdx);
|
|
163
|
+
// Add AnimationHandler on root to aggregate child mixers
|
|
164
|
+
const root = findRoot(go);
|
|
165
|
+
if (!root.getComponent(AnimationHandler)) {
|
|
166
|
+
const handler = root.addComponent(AnimationHandler);
|
|
167
|
+
const mixer = go.getComponent(AnimationMixer);
|
|
168
|
+
if (mixer?.initialClip)
|
|
169
|
+
handler.initialClip = mixer.initialClip;
|
|
170
|
+
}
|
|
171
|
+
// Async material load
|
|
172
|
+
const mm = deps.materialManager.current;
|
|
173
|
+
if (mm && matPath) {
|
|
174
|
+
mm.getMaterial(matPath).then((m) => {
|
|
175
|
+
smr.material = m;
|
|
176
|
+
smr.materialBindGroup = null;
|
|
177
|
+
}).catch(() => { });
|
|
178
|
+
}
|
|
179
|
+
}).catch(() => { }));
|
|
180
|
+
}
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
case 'Camera': {
|
|
184
|
+
const cam = go.addComponent(Camera);
|
|
185
|
+
applyComponentData(cam, data);
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
case 'DirectionalLight': {
|
|
189
|
+
const dl = go.addComponent(DirectionalLight);
|
|
190
|
+
applyComponentData(dl, data);
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
case 'PointLight': {
|
|
194
|
+
const pl = go.addComponent(PointLight);
|
|
195
|
+
applyComponentData(pl, data);
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
case 'SpotLight': {
|
|
199
|
+
const sl = go.addComponent(SpotLight);
|
|
200
|
+
applyComponentData(sl, data);
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
default: {
|
|
204
|
+
const ctor = getCtorMap().get(type);
|
|
205
|
+
if (ctor) {
|
|
206
|
+
const comp = go.addComponent(ctor);
|
|
207
|
+
applyComponentData(comp, data);
|
|
208
|
+
}
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
async onComplete() {
|
|
214
|
+
if (deps.physics)
|
|
215
|
+
deps.physics.flushDeferred(deferredOps);
|
|
216
|
+
deferredOps.length = 0;
|
|
217
|
+
await Promise.all(asyncLoads);
|
|
218
|
+
asyncLoads.length = 0;
|
|
219
|
+
nameToCtors = null; // invalidate so next deserialize picks up newly registered scripts
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
function findRoot(go) {
|
|
224
|
+
let current = go;
|
|
225
|
+
while (current.parent)
|
|
226
|
+
current = current.parent;
|
|
227
|
+
return current;
|
|
228
|
+
}
|
|
229
|
+
function setupSkeletonFromSkin(go, asset, skin, _skinIdx) {
|
|
230
|
+
const joints = skin.jointParents.map((parentIdx, i) => ({
|
|
231
|
+
name: skin.jointNames[i] ?? `joint_${i}`,
|
|
232
|
+
parentIndex: parentIdx,
|
|
233
|
+
}));
|
|
234
|
+
const skeleton = createSkeleton(joints, skin.inverseBindMatrices, skin.restT, skin.restR, skin.restS);
|
|
235
|
+
const mixer = go.addComponent(AnimationMixer);
|
|
236
|
+
mixer.skeleton = skeleton;
|
|
237
|
+
// Build node→joint mapping for animation track remapping
|
|
238
|
+
const nodeToJoint = new Map();
|
|
239
|
+
for (let ji = 0; ji < skin.jointNodeIndices.length; ji++) {
|
|
240
|
+
nodeToJoint.set(skin.jointNodeIndices[ji], ji);
|
|
241
|
+
}
|
|
242
|
+
for (const anim of asset.animations) {
|
|
243
|
+
const tracks = [];
|
|
244
|
+
for (const track of anim.tracks) {
|
|
245
|
+
const jointIndex = nodeToJoint.get(track.targetNode);
|
|
246
|
+
if (jointIndex === undefined)
|
|
247
|
+
continue;
|
|
248
|
+
tracks.push({
|
|
249
|
+
jointIndex,
|
|
250
|
+
channel: track.path,
|
|
251
|
+
interpolation: track.interpolation,
|
|
252
|
+
times: track.times,
|
|
253
|
+
values: track.values,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
if (tracks.length > 0) {
|
|
257
|
+
const clip = createAnimationClip(anim.name, tracks);
|
|
258
|
+
mixer.addClip(clip);
|
|
259
|
+
if (!mixer.initialClip) {
|
|
260
|
+
mixer.initialClip = clip.name;
|
|
261
|
+
}
|
|
262
|
+
if (mixer.layers.length === 0) {
|
|
263
|
+
mixer.play(clip);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
//# sourceMappingURL=default-factories.js.map
|