@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.
Files changed (234) hide show
  1. package/LICENCE +674 -0
  2. package/README.md +206 -0
  3. package/dist/asset-browser-client.d.ts +24 -0
  4. package/dist/asset-browser-client.d.ts.map +1 -0
  5. package/dist/asset-browser-client.js +60 -0
  6. package/dist/asset-browser-client.js.map +1 -0
  7. package/dist/asset-types.d.ts +33 -0
  8. package/dist/asset-types.d.ts.map +1 -0
  9. package/dist/asset-types.js +2 -0
  10. package/dist/asset-types.js.map +1 -0
  11. package/dist/bootstrap/default-factories.d.ts +38 -0
  12. package/dist/bootstrap/default-factories.d.ts.map +1 -0
  13. package/dist/bootstrap/default-factories.js +268 -0
  14. package/dist/bootstrap/default-factories.js.map +1 -0
  15. package/dist/bootstrap/editor-physics.d.ts +81 -0
  16. package/dist/bootstrap/editor-physics.d.ts.map +1 -0
  17. package/dist/bootstrap/editor-physics.js +512 -0
  18. package/dist/bootstrap/editor-physics.js.map +1 -0
  19. package/dist/bootstrap/geometry-cache.d.ts +5 -0
  20. package/dist/bootstrap/geometry-cache.d.ts.map +1 -0
  21. package/dist/bootstrap/geometry-cache.js +18 -0
  22. package/dist/bootstrap/geometry-cache.js.map +1 -0
  23. package/dist/bootstrap/keyboard-shortcuts.d.ts +4 -0
  24. package/dist/bootstrap/keyboard-shortcuts.d.ts.map +1 -0
  25. package/dist/bootstrap/keyboard-shortcuts.js +43 -0
  26. package/dist/bootstrap/keyboard-shortcuts.js.map +1 -0
  27. package/dist/bootstrap/model-import.d.ts +9 -0
  28. package/dist/bootstrap/model-import.d.ts.map +1 -0
  29. package/dist/bootstrap/model-import.js +55 -0
  30. package/dist/bootstrap/model-import.js.map +1 -0
  31. package/dist/bootstrap/start-editor.d.ts +3 -0
  32. package/dist/bootstrap/start-editor.d.ts.map +1 -0
  33. package/dist/bootstrap/start-editor.js +506 -0
  34. package/dist/bootstrap/start-editor.js.map +1 -0
  35. package/dist/bootstrap/start-player.d.ts +23 -0
  36. package/dist/bootstrap/start-player.d.ts.map +1 -0
  37. package/dist/bootstrap/start-player.js +205 -0
  38. package/dist/bootstrap/start-player.js.map +1 -0
  39. package/dist/bootstrap/types.d.ts +160 -0
  40. package/dist/bootstrap/types.d.ts.map +1 -0
  41. package/dist/bootstrap/types.js +2 -0
  42. package/dist/bootstrap/types.js.map +1 -0
  43. package/dist/camera-frustum-renderer.d.ts +15 -0
  44. package/dist/camera-frustum-renderer.d.ts.map +1 -0
  45. package/dist/camera-frustum-renderer.js +110 -0
  46. package/dist/camera-frustum-renderer.js.map +1 -0
  47. package/dist/camera-presets.d.ts +10 -0
  48. package/dist/camera-presets.d.ts.map +1 -0
  49. package/dist/camera-presets.js +15 -0
  50. package/dist/camera-presets.js.map +1 -0
  51. package/dist/collider-gizmo-renderer.d.ts +13 -0
  52. package/dist/collider-gizmo-renderer.d.ts.map +1 -0
  53. package/dist/collider-gizmo-renderer.js +217 -0
  54. package/dist/collider-gizmo-renderer.js.map +1 -0
  55. package/dist/color-utils.d.ts +5 -0
  56. package/dist/color-utils.d.ts.map +1 -0
  57. package/dist/color-utils.js +13 -0
  58. package/dist/color-utils.js.map +1 -0
  59. package/dist/components/asset-browser-panel.d.ts +14 -0
  60. package/dist/components/asset-browser-panel.d.ts.map +1 -0
  61. package/dist/components/asset-browser-panel.js +247 -0
  62. package/dist/components/asset-browser-panel.js.map +1 -0
  63. package/dist/components/context-menu.d.ts +14 -0
  64. package/dist/components/context-menu.d.ts.map +1 -0
  65. package/dist/components/context-menu.js +48 -0
  66. package/dist/components/context-menu.js.map +1 -0
  67. package/dist/components/editor-shell.d.ts +27 -0
  68. package/dist/components/editor-shell.d.ts.map +1 -0
  69. package/dist/components/editor-shell.js +327 -0
  70. package/dist/components/editor-shell.js.map +1 -0
  71. package/dist/components/fields/boolean-field.d.ts +8 -0
  72. package/dist/components/fields/boolean-field.d.ts.map +1 -0
  73. package/dist/components/fields/boolean-field.js +11 -0
  74. package/dist/components/fields/boolean-field.js.map +1 -0
  75. package/dist/components/fields/color-field.d.ts +8 -0
  76. package/dist/components/fields/color-field.d.ts.map +1 -0
  77. package/dist/components/fields/color-field.js +34 -0
  78. package/dist/components/fields/color-field.js.map +1 -0
  79. package/dist/components/fields/decimal-input.d.ts +13 -0
  80. package/dist/components/fields/decimal-input.d.ts.map +1 -0
  81. package/dist/components/fields/decimal-input.js +49 -0
  82. package/dist/components/fields/decimal-input.js.map +1 -0
  83. package/dist/components/fields/enum-field.d.ts +12 -0
  84. package/dist/components/fields/enum-field.d.ts.map +1 -0
  85. package/dist/components/fields/enum-field.js +20 -0
  86. package/dist/components/fields/enum-field.js.map +1 -0
  87. package/dist/components/fields/game-object-ref-field.d.ts +11 -0
  88. package/dist/components/fields/game-object-ref-field.d.ts.map +1 -0
  89. package/dist/components/fields/game-object-ref-field.js +73 -0
  90. package/dist/components/fields/game-object-ref-field.js.map +1 -0
  91. package/dist/components/fields/material-asset-field.d.ts +10 -0
  92. package/dist/components/fields/material-asset-field.d.ts.map +1 -0
  93. package/dist/components/fields/material-asset-field.js +114 -0
  94. package/dist/components/fields/material-asset-field.js.map +1 -0
  95. package/dist/components/fields/number-field.d.ts +10 -0
  96. package/dist/components/fields/number-field.d.ts.map +1 -0
  97. package/dist/components/fields/number-field.js +21 -0
  98. package/dist/components/fields/number-field.js.map +1 -0
  99. package/dist/components/fields/quat-field.d.ts +8 -0
  100. package/dist/components/fields/quat-field.d.ts.map +1 -0
  101. package/dist/components/fields/quat-field.js +43 -0
  102. package/dist/components/fields/quat-field.js.map +1 -0
  103. package/dist/components/fields/string-field.d.ts +7 -0
  104. package/dist/components/fields/string-field.d.ts.map +1 -0
  105. package/dist/components/fields/string-field.js +20 -0
  106. package/dist/components/fields/string-field.js.map +1 -0
  107. package/dist/components/fields/vec3-field.d.ts +8 -0
  108. package/dist/components/fields/vec3-field.d.ts.map +1 -0
  109. package/dist/components/fields/vec3-field.js +30 -0
  110. package/dist/components/fields/vec3-field.js.map +1 -0
  111. package/dist/components/hierarchy-node.d.ts +18 -0
  112. package/dist/components/hierarchy-node.d.ts.map +1 -0
  113. package/dist/components/hierarchy-node.js +77 -0
  114. package/dist/components/hierarchy-node.js.map +1 -0
  115. package/dist/components/hierarchy-panel.d.ts +14 -0
  116. package/dist/components/hierarchy-panel.d.ts.map +1 -0
  117. package/dist/components/hierarchy-panel.js +228 -0
  118. package/dist/components/hierarchy-panel.js.map +1 -0
  119. package/dist/components/inspector-panel.d.ts +14 -0
  120. package/dist/components/inspector-panel.d.ts.map +1 -0
  121. package/dist/components/inspector-panel.js +288 -0
  122. package/dist/components/inspector-panel.js.map +1 -0
  123. package/dist/components/material-inspector.d.ts +10 -0
  124. package/dist/components/material-inspector.d.ts.map +1 -0
  125. package/dist/components/material-inspector.js +130 -0
  126. package/dist/components/material-inspector.js.map +1 -0
  127. package/dist/components/post-process-panel.d.ts +9 -0
  128. package/dist/components/post-process-panel.d.ts.map +1 -0
  129. package/dist/components/post-process-panel.js +70 -0
  130. package/dist/components/post-process-panel.js.map +1 -0
  131. package/dist/components/project-gate.d.ts +8 -0
  132. package/dist/components/project-gate.d.ts.map +1 -0
  133. package/dist/components/project-gate.js +87 -0
  134. package/dist/components/project-gate.js.map +1 -0
  135. package/dist/components/settings-panel.d.ts +8 -0
  136. package/dist/components/settings-panel.d.ts.map +1 -0
  137. package/dist/components/settings-panel.js +108 -0
  138. package/dist/components/settings-panel.js.map +1 -0
  139. package/dist/components/use-splitter.d.ts +4 -0
  140. package/dist/components/use-splitter.d.ts.map +1 -0
  141. package/dist/components/use-splitter.js +22 -0
  142. package/dist/components/use-splitter.js.map +1 -0
  143. package/dist/editor-mount.d.ts +36 -0
  144. package/dist/editor-mount.d.ts.map +1 -0
  145. package/dist/editor-mount.js +161 -0
  146. package/dist/editor-mount.js.map +1 -0
  147. package/dist/editor-state.d.ts +55 -0
  148. package/dist/editor-state.d.ts.map +1 -0
  149. package/dist/editor-state.js +181 -0
  150. package/dist/editor-state.js.map +1 -0
  151. package/dist/gizmo-meshes.d.ts +9 -0
  152. package/dist/gizmo-meshes.d.ts.map +1 -0
  153. package/dist/gizmo-meshes.js +229 -0
  154. package/dist/gizmo-meshes.js.map +1 -0
  155. package/dist/gizmo-renderer.d.ts +16 -0
  156. package/dist/gizmo-renderer.d.ts.map +1 -0
  157. package/dist/gizmo-renderer.js +77 -0
  158. package/dist/gizmo-renderer.js.map +1 -0
  159. package/dist/gizmo-state.d.ts +25 -0
  160. package/dist/gizmo-state.d.ts.map +1 -0
  161. package/dist/gizmo-state.js +269 -0
  162. package/dist/gizmo-state.js.map +1 -0
  163. package/dist/index.d.ts +33 -0
  164. package/dist/index.d.ts.map +1 -0
  165. package/dist/index.js +22 -0
  166. package/dist/index.js.map +1 -0
  167. package/dist/joint-gizmo-renderer.d.ts +13 -0
  168. package/dist/joint-gizmo-renderer.d.ts.map +1 -0
  169. package/dist/joint-gizmo-renderer.js +133 -0
  170. package/dist/joint-gizmo-renderer.js.map +1 -0
  171. package/dist/material-manager.d.ts +22 -0
  172. package/dist/material-manager.d.ts.map +1 -0
  173. package/dist/material-manager.js +156 -0
  174. package/dist/material-manager.js.map +1 -0
  175. package/dist/object-picker.d.ts +11 -0
  176. package/dist/object-picker.d.ts.map +1 -0
  177. package/dist/object-picker.js +104 -0
  178. package/dist/object-picker.js.map +1 -0
  179. package/dist/orbit-camera.d.ts +38 -0
  180. package/dist/orbit-camera.d.ts.map +1 -0
  181. package/dist/orbit-camera.js +180 -0
  182. package/dist/orbit-camera.js.map +1 -0
  183. package/dist/overlay-renderer.d.ts +23 -0
  184. package/dist/overlay-renderer.d.ts.map +1 -0
  185. package/dist/overlay-renderer.js +95 -0
  186. package/dist/overlay-renderer.js.map +1 -0
  187. package/dist/player-entry.d.ts +6 -0
  188. package/dist/player-entry.d.ts.map +1 -0
  189. package/dist/player-entry.js +4 -0
  190. package/dist/player-entry.js.map +1 -0
  191. package/dist/project-fs.d.ts +28 -0
  192. package/dist/project-fs.d.ts.map +1 -0
  193. package/dist/project-fs.js +258 -0
  194. package/dist/project-fs.js.map +1 -0
  195. package/dist/project-seed.d.ts +3 -0
  196. package/dist/project-seed.d.ts.map +1 -0
  197. package/dist/project-seed.js +35 -0
  198. package/dist/project-seed.js.map +1 -0
  199. package/dist/project-settings.d.ts +29 -0
  200. package/dist/project-settings.d.ts.map +1 -0
  201. package/dist/project-settings.js +69 -0
  202. package/dist/project-settings.js.map +1 -0
  203. package/dist/property-setters.d.ts +4 -0
  204. package/dist/property-setters.d.ts.map +1 -0
  205. package/dist/property-setters.js +58 -0
  206. package/dist/property-setters.js.map +1 -0
  207. package/dist/scene-operations.d.ts +14 -0
  208. package/dist/scene-operations.d.ts.map +1 -0
  209. package/dist/scene-operations.js +195 -0
  210. package/dist/scene-operations.js.map +1 -0
  211. package/dist/scene-snapshot.d.ts +28 -0
  212. package/dist/scene-snapshot.d.ts.map +1 -0
  213. package/dist/scene-snapshot.js +97 -0
  214. package/dist/scene-snapshot.js.map +1 -0
  215. package/dist/script-discovery.d.ts +12 -0
  216. package/dist/script-discovery.d.ts.map +1 -0
  217. package/dist/script-discovery.js +81 -0
  218. package/dist/script-discovery.js.map +1 -0
  219. package/dist/selection-utils.d.ts +4 -0
  220. package/dist/selection-utils.d.ts.map +1 -0
  221. package/dist/selection-utils.js +19 -0
  222. package/dist/selection-utils.js.map +1 -0
  223. package/dist/simple-material-loader.d.ts +17 -0
  224. package/dist/simple-material-loader.d.ts.map +1 -0
  225. package/dist/simple-material-loader.js +85 -0
  226. package/dist/simple-material-loader.js.map +1 -0
  227. package/dist/wireframe-renderer.d.ts +18 -0
  228. package/dist/wireframe-renderer.d.ts.map +1 -0
  229. package/dist/wireframe-renderer.js +106 -0
  230. package/dist/wireframe-renderer.js.map +1 -0
  231. package/package.json +65 -0
  232. package/src/index.ts +48 -0
  233. package/vite-plugin.d.ts +15 -0
  234. 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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=asset-types.js.map
@@ -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