react-three-game 0.0.56 → 0.0.58

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 (64) hide show
  1. package/README.md +16 -3
  2. package/dist/index.d.ts +1 -1
  3. package/dist/index.js +1 -1
  4. package/dist/shared/GameCanvas.js +1 -3
  5. package/dist/tools/assetviewer/page.js +35 -14
  6. package/dist/tools/prefabeditor/Dropdown.d.ts +15 -0
  7. package/dist/tools/prefabeditor/Dropdown.js +82 -0
  8. package/dist/tools/prefabeditor/EditorContext.d.ts +5 -0
  9. package/dist/tools/prefabeditor/EditorTree.js +149 -91
  10. package/dist/tools/prefabeditor/EditorTreeMenus.d.ts +33 -0
  11. package/dist/tools/prefabeditor/EditorTreeMenus.js +136 -0
  12. package/dist/tools/prefabeditor/EditorUI.js +1 -1
  13. package/dist/tools/prefabeditor/PrefabEditor.d.ts +1 -0
  14. package/dist/tools/prefabeditor/PrefabEditor.js +13 -2
  15. package/dist/tools/prefabeditor/PrefabRoot.d.ts +1 -0
  16. package/dist/tools/prefabeditor/PrefabRoot.js +120 -34
  17. package/dist/tools/prefabeditor/components/AmbientLightComponent.js +3 -7
  18. package/dist/tools/prefabeditor/components/CameraComponent.d.ts +3 -0
  19. package/dist/tools/prefabeditor/components/CameraComponent.js +45 -0
  20. package/dist/tools/prefabeditor/components/DirectionalLightComponent.js +50 -24
  21. package/dist/tools/prefabeditor/components/EnvironmentComponent.d.ts +3 -0
  22. package/dist/tools/prefabeditor/components/EnvironmentComponent.js +15 -0
  23. package/dist/tools/prefabeditor/components/GeometryComponent.js +46 -46
  24. package/dist/tools/prefabeditor/components/Input.d.ts +51 -1
  25. package/dist/tools/prefabeditor/components/Input.js +73 -21
  26. package/dist/tools/prefabeditor/components/MaterialComponent.d.ts +16 -2
  27. package/dist/tools/prefabeditor/components/MaterialComponent.js +129 -15
  28. package/dist/tools/prefabeditor/components/ModelComponent.js +44 -3
  29. package/dist/tools/prefabeditor/components/PhysicsComponent.js +16 -81
  30. package/dist/tools/prefabeditor/components/SpotLightComponent.js +36 -23
  31. package/dist/tools/prefabeditor/components/TextComponent.js +7 -53
  32. package/dist/tools/prefabeditor/components/TransformComponent.js +18 -8
  33. package/dist/tools/prefabeditor/components/index.js +5 -1
  34. package/dist/tools/prefabeditor/styles.d.ts +5 -2
  35. package/dist/tools/prefabeditor/styles.js +7 -3
  36. package/dist/tools/prefabeditor/utils.d.ts +4 -3
  37. package/dist/tools/prefabeditor/utils.js +53 -5
  38. package/package.json +1 -1
  39. package/react-three-game-skill/react-three-game/SKILL.md +4 -1
  40. package/src/index.ts +7 -0
  41. package/src/shared/GameCanvas.tsx +0 -3
  42. package/src/tools/assetviewer/page.tsx +77 -45
  43. package/src/tools/prefabeditor/Dropdown.tsx +112 -0
  44. package/src/tools/prefabeditor/EditorContext.tsx +5 -0
  45. package/src/tools/prefabeditor/EditorTree.tsx +242 -178
  46. package/src/tools/prefabeditor/EditorTreeMenus.tsx +307 -0
  47. package/src/tools/prefabeditor/EditorUI.tsx +1 -1
  48. package/src/tools/prefabeditor/PrefabEditor.tsx +17 -4
  49. package/src/tools/prefabeditor/PrefabRoot.tsx +208 -58
  50. package/src/tools/prefabeditor/components/AmbientLightComponent.tsx +5 -11
  51. package/src/tools/prefabeditor/components/CameraComponent.tsx +117 -0
  52. package/src/tools/prefabeditor/components/DirectionalLightComponent.tsx +61 -30
  53. package/src/tools/prefabeditor/components/EnvironmentComponent.tsx +47 -0
  54. package/src/tools/prefabeditor/components/GeometryComponent.tsx +69 -63
  55. package/src/tools/prefabeditor/components/Input.tsx +220 -27
  56. package/src/tools/prefabeditor/components/MaterialComponent.tsx +189 -18
  57. package/src/tools/prefabeditor/components/ModelComponent.tsx +51 -4
  58. package/src/tools/prefabeditor/components/PhysicsComponent.tsx +44 -85
  59. package/src/tools/prefabeditor/components/SpotLightComponent.tsx +52 -27
  60. package/src/tools/prefabeditor/components/TextComponent.tsx +58 -57
  61. package/src/tools/prefabeditor/components/TransformComponent.tsx +61 -9
  62. package/src/tools/prefabeditor/components/index.ts +5 -1
  63. package/src/tools/prefabeditor/styles.ts +7 -3
  64. package/src/tools/prefabeditor/utils.ts +55 -4
@@ -15,24 +15,28 @@ import { getComponent, registerComponent, getNonComposableKeys } from "./compone
15
15
  import components from "./components";
16
16
  import { loadModel } from "../dragdrop/modelLoader";
17
17
  import { GameInstance, GameInstanceProvider, useInstanceCheck } from "./InstanceProvider";
18
- import { updateNode } from "./utils";
18
+ import { focusCameraOnObject, updateNode } from "./utils";
19
19
  import { EditorContext } from "./EditorContext";
20
20
  components.forEach(registerComponent);
21
21
  const IDENTITY = new Matrix4();
22
22
  export const PrefabRoot = forwardRef(({ editMode, data, onPrefabChange, selectedId, onSelect, onClick, basePath = "" }, ref) => {
23
- var _a, _b;
23
+ var _a, _b, _c, _d;
24
24
  // optional editor context
25
25
  const editorContext = useContext(EditorContext);
26
26
  const transformMode = (_a = editorContext === null || editorContext === void 0 ? void 0 : editorContext.transformMode) !== null && _a !== void 0 ? _a : "translate";
27
27
  const snapResolution = (_b = editorContext === null || editorContext === void 0 ? void 0 : editorContext.snapResolution) !== null && _b !== void 0 ? _b : 0;
28
+ const positionSnap = (_c = editorContext === null || editorContext === void 0 ? void 0 : editorContext.positionSnap) !== null && _c !== void 0 ? _c : 0.5;
29
+ const rotationSnap = (_d = editorContext === null || editorContext === void 0 ? void 0 : editorContext.rotationSnap) !== null && _d !== void 0 ? _d : Math.PI / 4;
28
30
  // prefab root state
29
31
  const [models, setModels] = useState({});
30
32
  const [textures, setTextures] = useState({});
31
33
  const loading = useRef(new Set());
34
+ const failedTextures = useRef(new Set());
32
35
  const objectRefs = useRef({});
33
36
  const rigidBodyRefs = useRef(new Map());
34
37
  const [selectedObject, setSelectedObject] = useState(null);
35
38
  const rootRef = useRef(null);
39
+ const controlsRef = useRef(null);
36
40
  const injectModel = useCallback((filename, model) => {
37
41
  setModels(m => (Object.assign(Object.assign({}, m), { [filename]: model })));
38
42
  }, []);
@@ -50,7 +54,15 @@ export const PrefabRoot = forwardRef(({ editMode, data, onPrefabChange, selected
50
54
  root: rootRef.current,
51
55
  rigidBodyRefs: rigidBodyRefs.current,
52
56
  injectModel,
53
- injectTexture
57
+ injectTexture,
58
+ focusNode: (nodeId) => {
59
+ const object = objectRefs.current[nodeId];
60
+ const controls = controlsRef.current;
61
+ const camera = controls === null || controls === void 0 ? void 0 : controls.object;
62
+ if (!object || !controls || !camera)
63
+ return;
64
+ focusCameraOnObject(object, camera, controls.target, () => { var _a; return (_a = controls.update) === null || _a === void 0 ? void 0 : _a.call(controls); });
65
+ }
54
66
  }), [injectModel, injectTexture]);
55
67
  const registerRef = useCallback((id, obj) => {
56
68
  objectRefs.current[id] = obj;
@@ -92,11 +104,13 @@ export const PrefabRoot = forwardRef(({ editMode, data, onPrefabChange, selected
92
104
  const modelsToLoad = new Set();
93
105
  const texturesToLoad = new Set();
94
106
  walk(data.root, node => {
95
- var _a, _b, _c, _d, _e, _f;
107
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
96
108
  ((_c = (_b = (_a = node.components) === null || _a === void 0 ? void 0 : _a.model) === null || _b === void 0 ? void 0 : _b.properties) === null || _c === void 0 ? void 0 : _c.filename) &&
97
109
  modelsToLoad.add(node.components.model.properties.filename);
98
110
  ((_f = (_e = (_d = node.components) === null || _d === void 0 ? void 0 : _d.material) === null || _e === void 0 ? void 0 : _e.properties) === null || _f === void 0 ? void 0 : _f.texture) &&
99
111
  texturesToLoad.add(node.components.material.properties.texture);
112
+ ((_j = (_h = (_g = node.components) === null || _g === void 0 ? void 0 : _g.material) === null || _h === void 0 ? void 0 : _h.properties) === null || _j === void 0 ? void 0 : _j.normalMapTexture) &&
113
+ texturesToLoad.add(node.components.material.properties.normalMapTexture);
100
114
  });
101
115
  modelsToLoad.forEach((file) => __awaiter(void 0, void 0, void 0, function* () {
102
116
  if (models[file] || loading.current.has(file))
@@ -111,7 +125,7 @@ export const PrefabRoot = forwardRef(({ editMode, data, onPrefabChange, selected
111
125
  }));
112
126
  const loader = new TextureLoader();
113
127
  texturesToLoad.forEach(file => {
114
- if (textures[file] || loading.current.has(file))
128
+ if (textures[file] || loading.current.has(file) || failedTextures.current.has(file))
115
129
  return;
116
130
  loading.current.add(file);
117
131
  // Handle full URLs (http/https) or regular paths
@@ -124,12 +138,13 @@ export const PrefabRoot = forwardRef(({ editMode, data, onPrefabChange, selected
124
138
  tex.colorSpace = SRGBColorSpace;
125
139
  setTextures(t => (Object.assign(Object.assign({}, t), { [file]: tex })));
126
140
  }, undefined, (err) => {
127
- console.error(`Failed to load texture: ${path}`, err);
141
+ console.warn(`Failed to load texture: ${path}`, err);
128
142
  loading.current.delete(file);
143
+ failedTextures.current.add(file);
129
144
  });
130
145
  });
131
146
  }, [data, models, textures]);
132
- return (_jsxs("group", { ref: rootRef, children: [_jsx(GameInstanceProvider, { models: models, selectedId: selectedId, editMode: editMode, onSelect: editMode ? onSelect : undefined, registerRef: registerRef, children: _jsx(GameObjectRenderer, { gameObject: data.root, selectedId: selectedId, onSelect: editMode ? onSelect : undefined, onClick: onClick, registerRef: registerRef, registerRigidBodyRef: registerRigidBodyRef, loadedModels: models, loadedTextures: textures, editMode: editMode, parentMatrix: IDENTITY }) }), editMode && (_jsxs(_Fragment, { children: [_jsx(MapControls, { makeDefault: true }), selectedObject && (_jsx(TransformControls, { object: selectedObject, mode: transformMode, space: "local", onObjectChange: onTransformChange, translationSnap: snapResolution > 0 ? snapResolution : undefined, rotationSnap: snapResolution > 0 ? snapResolution : undefined, scaleSnap: snapResolution > 0 ? snapResolution : undefined }, `transform-${snapResolution}`))] }))] }));
147
+ return (_jsxs("group", { ref: rootRef, children: [_jsx(GameInstanceProvider, { models: models, selectedId: selectedId, editMode: editMode, onSelect: editMode ? onSelect : undefined, registerRef: registerRef, children: _jsx(GameObjectRenderer, { gameObject: data.root, selectedId: selectedId, onSelect: editMode ? onSelect : undefined, onClick: onClick, registerRef: registerRef, registerRigidBodyRef: registerRigidBodyRef, loadedModels: models, loadedTextures: textures, editMode: editMode, parentMatrix: IDENTITY }) }), editMode && (_jsxs(_Fragment, { children: [_jsx(MapControls, { ref: controlsRef, makeDefault: true }), selectedObject && (_jsx(TransformControls, { object: selectedObject, mode: transformMode, space: "local", onObjectChange: onTransformChange, translationSnap: positionSnap > 0 ? positionSnap : undefined, rotationSnap: rotationSnap > 0 ? rotationSnap : undefined, scaleSnap: snapResolution > 0 ? snapResolution : undefined }, `transform-${transformMode}-${positionSnap}-${rotationSnap}-${snapResolution}`))] }))] }));
133
148
  });
134
149
  export function GameObjectRenderer(props) {
135
150
  var _a, _b, _c;
@@ -187,7 +202,7 @@ function InstancedNode({ gameObject, parentMatrix = IDENTITY, editMode, register
187
202
  return (_jsx(GameInstance, { id: gameObject.id, modelUrl: (_k = (_j = (_h = gameObject.components) === null || _h === void 0 ? void 0 : _h.model) === null || _j === void 0 ? void 0 : _j.properties) === null || _k === void 0 ? void 0 : _k.filename, position: worldPosition, rotation: worldRotation, scale: worldScale, physics: physicsProps }));
188
203
  }
189
204
  function StandardNode({ gameObject, selectedId, onSelect, onClick, registerRef, registerRigidBodyRef, loadedModels, loadedTextures, editMode, parentMatrix = IDENTITY, }) {
190
- var _a, _b, _c, _d, _e, _f;
205
+ var _a, _b, _c, _d, _e;
191
206
  const groupRef = useRef(null);
192
207
  const helperRef = useRef(null);
193
208
  const clickValid = useRef(false);
@@ -219,7 +234,20 @@ function StandardNode({ gameObject, selectedId, onSelect, onClick, registerRef,
219
234
  const physicsDef = hasPhysics ? getComponent("Physics") : null;
220
235
  const isInstanced = (_e = (_d = (_c = gameObject.components) === null || _c === void 0 ? void 0 : _c.model) === null || _d === void 0 ? void 0 : _d.properties) === null || _e === void 0 ? void 0 : _e.instanced;
221
236
  const physicsKey = `physics_${gameObject.id}_${isInstanced ? 'instanced' : 'standard'}`;
222
- const inner = (_jsxs("group", { onPointerDown: editMode ? onDown : undefined, onPointerMove: editMode ? () => (clickValid.current = false) : undefined, onPointerUp: editMode ? onUp : undefined, children: [renderCoreNode(gameObject, { loadedModels, loadedTextures, editMode, registerRef }, parentMatrix), (_f = gameObject.children) === null || _f === void 0 ? void 0 : _f.map(child => (_jsx(GameObjectRenderer, { gameObject: child, selectedId: selectedId, onSelect: onSelect, onClick: onClick, registerRef: registerRef, registerRigidBodyRef: registerRigidBodyRef, loadedModels: loadedModels, loadedTextures: loadedTextures, editMode: editMode, parentMatrix: world }, child.id)))] }));
237
+ const renderCtx = createRenderContext(loadedModels, loadedTextures, editMode, selectedId, registerRef);
238
+ const childNodes = getChildHostComponents(gameObject).length > 0
239
+ ? renderHostedChildren(gameObject, renderCtx, world)
240
+ : renderSceneChildren(gameObject, world, {
241
+ selectedId,
242
+ onSelect,
243
+ onClick,
244
+ registerRef,
245
+ registerRigidBodyRef,
246
+ loadedModels,
247
+ loadedTextures,
248
+ editMode,
249
+ });
250
+ const inner = (_jsx("group", { onPointerDown: editMode ? onDown : undefined, onPointerMove: editMode ? () => (clickValid.current = false) : undefined, onPointerUp: editMode ? onUp : undefined, children: renderCompositionNode(gameObject, renderCtx, parentMatrix, childNodes) }));
223
251
  if (editMode) {
224
252
  return (_jsxs(_Fragment, { children: [_jsx("group", { ref: groupRef, position: transform.position, rotation: transform.rotation, scale: transform.scale, children: _jsx("mesh", { visible: false, children: _jsx("boxGeometry", { args: [0.01, 0.01, 0.01] }) }) }), _jsx("group", { ref: helperRef, position: transform.position, rotation: transform.rotation, scale: transform.scale, children: inner }), hasPhysics && (physicsDef === null || physicsDef === void 0 ? void 0 : physicsDef.View) ? (_jsx(physicsDef.View, { properties: physics.properties, position: transform.position, rotation: transform.rotation, scale: transform.scale, editMode: editMode, nodeId: gameObject.id, registerRigidBodyRef: registerRigidBodyRef, children: inner }, physicsKey)) : null] }));
225
253
  }
@@ -228,6 +256,28 @@ function StandardNode({ gameObject, selectedId, onSelect, onClick, registerRef,
228
256
  }
229
257
  return (_jsx("group", { ref: groupRef, position: transform.position, rotation: transform.rotation, scale: transform.scale, onPointerDown: onDown, onPointerMove: () => (clickValid.current = false), onPointerUp: onUp, children: inner }));
230
258
  }
259
+ const CHILD_HOST_COMPONENT_TYPES = new Set(["Environment"]);
260
+ function isChildHostType(type) {
261
+ return CHILD_HOST_COMPONENT_TYPES.has(type);
262
+ }
263
+ function getChildHostComponents(gameObject) {
264
+ var _a;
265
+ return Object.entries((_a = gameObject.components) !== null && _a !== void 0 ? _a : {}).flatMap(([key, comp]) => {
266
+ if (!(comp === null || comp === void 0 ? void 0 : comp.type) || !isChildHostType(comp.type))
267
+ return [];
268
+ const def = getComponent(comp.type);
269
+ if (!(def === null || def === void 0 ? void 0 : def.View))
270
+ return [];
271
+ return { key, View: def.View, properties: comp.properties };
272
+ });
273
+ }
274
+ function createRenderContext(loadedModels, loadedTextures, editMode, selectedId, registerRef) {
275
+ return { loadedModels, loadedTextures, editMode, selectedId, registerRef };
276
+ }
277
+ function renderSceneChildren(gameObject, parentMatrix, props) {
278
+ var _a;
279
+ return (_a = gameObject.children) === null || _a === void 0 ? void 0 : _a.map(child => _jsx(GameObjectRenderer, { gameObject: child, selectedId: props.selectedId, onSelect: props.onSelect, onClick: props.onClick, registerRef: props.registerRef, registerRigidBodyRef: props.registerRigidBodyRef, loadedModels: props.loadedModels, loadedTextures: props.loadedTextures, editMode: props.editMode, parentMatrix: parentMatrix }, child.id));
280
+ }
231
281
  function walk(node, fn) {
232
282
  var _a;
233
283
  fn(node);
@@ -270,7 +320,25 @@ function computeParentWorldMatrix(root, targetId) {
270
320
  visit(root, IDENTITY);
271
321
  return result !== null && result !== void 0 ? result : IDENTITY;
272
322
  }
273
- function renderCoreNode(gameObject, ctx, parentMatrix) {
323
+ function renderCompositionSubtree(gameObject, ctx, parentMatrix = IDENTITY) {
324
+ if (!gameObject || gameObject.disabled)
325
+ return null;
326
+ const transform = getNodeTransformProps(gameObject);
327
+ const world = parentMatrix.clone().multiply(compose(gameObject));
328
+ const childNodes = renderHostedChildren(gameObject, ctx, world);
329
+ return (_jsx("group", { position: transform.position, rotation: transform.rotation, scale: transform.scale, children: renderCompositionNode(gameObject, ctx, parentMatrix, childNodes) }, gameObject.id));
330
+ }
331
+ function renderHostedChildren(gameObject, ctx, parentMatrix) {
332
+ var _a;
333
+ return (_a = gameObject.children) === null || _a === void 0 ? void 0 : _a.map(child => renderCompositionSubtree(child, ctx, parentMatrix));
334
+ }
335
+ function renderCompositionNode(gameObject, ctx, parentMatrix, childNodes) {
336
+ const ownContent = renderNodeOwnContent(gameObject, ctx, parentMatrix);
337
+ const siblingContent = renderNodeSiblingComponents(gameObject, ctx, parentMatrix);
338
+ const subtree = _jsxs(_Fragment, { children: [ownContent, siblingContent, childNodes] });
339
+ return wrapWithChildHosts(gameObject, ctx, parentMatrix, subtree);
340
+ }
341
+ function renderNodeOwnContent(gameObject, ctx, parentMatrix) {
274
342
  var _a, _b, _c, _d;
275
343
  const geometry = (_a = gameObject.components) === null || _a === void 0 ? void 0 : _a.geometry;
276
344
  const material = (_b = gameObject.components) === null || _b === void 0 ? void 0 : _b.material;
@@ -284,41 +352,59 @@ function renderCoreNode(gameObject, ctx, parentMatrix) {
284
352
  loadedModels: ctx.loadedModels,
285
353
  loadedTextures: ctx.loadedTextures,
286
354
  editMode: ctx.editMode,
355
+ isSelected: ctx.selectedId === gameObject.id,
356
+ nodeId: gameObject.id,
287
357
  parentMatrix,
288
358
  registerRef: ctx.registerRef,
289
359
  };
290
- const wrappers = [];
291
- const leaves = [];
292
- if (gameObject.components) {
293
- Object.entries(gameObject.components)
294
- .filter(([k]) => !getNonComposableKeys().includes(k))
295
- .forEach(([key, comp]) => {
296
- if (!(comp === null || comp === void 0 ? void 0 : comp.type))
297
- return;
298
- const def = getComponent(comp.type);
299
- if (!(def === null || def === void 0 ? void 0 : def.View))
300
- return;
301
- if (def.View.toString().includes("children")) {
302
- wrappers.push({ key, View: def.View, properties: comp.properties });
303
- }
304
- else {
305
- leaves.push(_jsx(def.View, Object.assign({ properties: comp.properties }, contextProps), key));
306
- }
307
- });
308
- }
309
360
  let core;
310
361
  if (model && (modelDef === null || modelDef === void 0 ? void 0 : modelDef.View)) {
311
- core = (_jsxs(modelDef.View, Object.assign({ properties: model.properties }, contextProps, { children: [material && (materialDef === null || materialDef === void 0 ? void 0 : materialDef.View) && (_jsx(materialDef.View, Object.assign({ properties: material.properties }, contextProps), "material")), leaves] })));
362
+ core = (_jsx(modelDef.View, Object.assign({ properties: model.properties }, contextProps, { children: material && (materialDef === null || materialDef === void 0 ? void 0 : materialDef.View) && (_jsx(materialDef.View, Object.assign({ properties: material.properties }, contextProps), "material")) })));
312
363
  }
313
364
  else if (geometry && (geometryDef === null || geometryDef === void 0 ? void 0 : geometryDef.View)) {
314
- core = (_jsxs("mesh", { castShadow: true, receiveShadow: true, children: [_jsx(geometryDef.View, Object.assign({ properties: geometry.properties }, contextProps)), material && (materialDef === null || materialDef === void 0 ? void 0 : materialDef.View) && (_jsx(materialDef.View, Object.assign({ properties: material.properties }, contextProps), "material")), leaves] }));
365
+ core = (_jsxs("mesh", { castShadow: true, receiveShadow: true, children: [_jsx(geometryDef.View, Object.assign({ properties: geometry.properties }, contextProps)), material && (materialDef === null || materialDef === void 0 ? void 0 : materialDef.View) && (_jsx(materialDef.View, Object.assign({ properties: material.properties }, contextProps), "material"))] }));
315
366
  }
316
367
  else if (text && (textDef === null || textDef === void 0 ? void 0 : textDef.View)) {
317
- core = (_jsxs(_Fragment, { children: [_jsx(textDef.View, Object.assign({ properties: text.properties }, contextProps)), leaves] }));
368
+ core = (_jsx(_Fragment, { children: _jsx(textDef.View, Object.assign({ properties: text.properties }, contextProps)) }));
318
369
  }
319
370
  else {
320
- core = _jsx(_Fragment, { children: leaves });
371
+ core = null;
321
372
  }
322
- return wrappers.reduce((acc, { key, View, properties }) => (_jsx(View, Object.assign({ properties: properties }, contextProps, { children: acc }), key)), core);
373
+ return core;
374
+ }
375
+ function renderNodeSiblingComponents(gameObject, ctx, parentMatrix) {
376
+ var _a;
377
+ const contextProps = {
378
+ loadedModels: ctx.loadedModels,
379
+ loadedTextures: ctx.loadedTextures,
380
+ editMode: ctx.editMode,
381
+ isSelected: ctx.selectedId === gameObject.id,
382
+ nodeId: gameObject.id,
383
+ parentMatrix,
384
+ registerRef: ctx.registerRef,
385
+ };
386
+ return Object.entries((_a = gameObject.components) !== null && _a !== void 0 ? _a : {})
387
+ .filter(([key]) => !getNonComposableKeys().includes(key))
388
+ .flatMap(([key, comp]) => {
389
+ if (!(comp === null || comp === void 0 ? void 0 : comp.type) || isChildHostType(comp.type))
390
+ return [];
391
+ const def = getComponent(comp.type);
392
+ if (!(def === null || def === void 0 ? void 0 : def.View))
393
+ return [];
394
+ return _jsx(def.View, Object.assign({ properties: comp.properties }, contextProps), key);
395
+ });
396
+ }
397
+ function wrapWithChildHosts(gameObject, ctx, parentMatrix, subtree) {
398
+ const contextProps = {
399
+ loadedModels: ctx.loadedModels,
400
+ loadedTextures: ctx.loadedTextures,
401
+ editMode: ctx.editMode,
402
+ isSelected: ctx.selectedId === gameObject.id,
403
+ nodeId: gameObject.id,
404
+ parentMatrix,
405
+ registerRef: ctx.registerRef,
406
+ };
407
+ const childHosts = getChildHostComponents(gameObject);
408
+ return childHosts.reduce((acc, { key, View, properties }) => (_jsx(View, Object.assign({ properties: properties }, contextProps, { children: acc }), key)), subtree);
323
409
  }
324
410
  export default PrefabRoot;
@@ -1,11 +1,7 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { FieldRenderer } from "./Input";
3
- const ambientLightFields = [
4
- { name: 'color', type: 'color', label: 'Color' },
5
- { name: 'intensity', type: 'number', label: 'Intensity', step: 0.1, min: 0 },
6
- ];
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ColorField, FieldGroup, NumberField } from "./Input";
7
3
  function AmbientLightComponentEditor({ component, onUpdate, }) {
8
- return (_jsx(FieldRenderer, { fields: ambientLightFields, values: component.properties, onChange: onUpdate }));
4
+ return (_jsxs(FieldGroup, { children: [_jsx(ColorField, { name: "color", label: "Color", values: component.properties, onChange: onUpdate }), _jsx(NumberField, { name: "intensity", label: "Intensity", values: component.properties, onChange: onUpdate, min: 0, step: 0.1, fallback: 1 })] }));
9
5
  }
10
6
  function AmbientLightComponentView({ properties }) {
11
7
  const { color = '#ffffff', intensity = 1 } = properties;
@@ -0,0 +1,3 @@
1
+ import { Component } from './ComponentRegistry';
2
+ declare const CameraComponent: Component;
3
+ export default CameraComponent;
@@ -0,0 +1,45 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { PerspectiveCamera } from '@react-three/drei';
3
+ import { useEffect, useMemo, useState } from 'react';
4
+ import { CameraHelper } from 'three';
5
+ import { useFrame } from '@react-three/fiber';
6
+ import { FieldGroup, NumberField } from './Input';
7
+ const cameraDefaults = {
8
+ fov: 50,
9
+ near: 0.1,
10
+ zoom: 1,
11
+ far: 1000,
12
+ };
13
+ function CameraComponentEditor({ component, onUpdate }) {
14
+ const values = Object.assign(Object.assign({}, cameraDefaults), component.properties);
15
+ return (_jsxs(FieldGroup, { children: [_jsx(NumberField, { name: "fov", label: "FOV", values: values, onChange: onUpdate, fallback: 50, min: 1, max: 179, step: 1 }), _jsx(NumberField, { name: "near", label: "Near", values: values, onChange: onUpdate, fallback: 0.1, min: 0.001, step: 0.1 }), _jsx(NumberField, { name: "zoom", label: "Zoom", values: values, onChange: onUpdate, fallback: 1, min: 0.01, step: 0.1 }), _jsx(NumberField, { name: "far", label: "Far", values: values, onChange: onUpdate, fallback: 1000, min: 0.1, step: 1 })] }));
16
+ }
17
+ function CameraComponentView({ properties, editMode, isSelected }) {
18
+ const merged = Object.assign(Object.assign({}, cameraDefaults), properties);
19
+ const fov = merged.fov;
20
+ const near = merged.near;
21
+ const zoom = merged.zoom;
22
+ const far = merged.far;
23
+ const [camera, setCamera] = useState(null);
24
+ const cameraHelper = useMemo(() => camera ? new CameraHelper(camera) : null, [camera]);
25
+ useEffect(() => {
26
+ return () => {
27
+ cameraHelper === null || cameraHelper === void 0 ? void 0 : cameraHelper.dispose();
28
+ };
29
+ }, [cameraHelper]);
30
+ useFrame(() => {
31
+ if (camera && cameraHelper && editMode && isSelected) {
32
+ camera.updateProjectionMatrix();
33
+ camera.updateMatrixWorld();
34
+ cameraHelper.update();
35
+ }
36
+ });
37
+ return (_jsxs(_Fragment, { children: [_jsx(PerspectiveCamera, { ref: (instance) => setCamera(instance), makeDefault: !editMode, fov: fov, near: near, zoom: zoom, far: far }), editMode && isSelected && cameraHelper && (_jsx("primitive", { object: cameraHelper })), editMode && !isSelected ? (_jsxs("mesh", { children: [_jsx("boxGeometry", { args: [0.34, 0.22, 0.18] }), _jsx("meshBasicMaterial", { color: "#22d3ee", wireframe: true })] })) : null] }));
38
+ }
39
+ const CameraComponent = {
40
+ name: 'Camera',
41
+ Editor: CameraComponentEditor,
42
+ View: CameraComponentView,
43
+ defaultProperties: cameraDefaults,
44
+ };
45
+ export default CameraComponent;
@@ -1,9 +1,22 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useRef, useEffect } from "react";
2
+ import { useRef, useEffect, useMemo, useState } from "react";
3
3
  import { useFrame } from "@react-three/fiber";
4
- import { Vector3 } from "three";
4
+ import { CameraHelper, Vector3 } from "three";
5
5
  import { FieldRenderer, Input } from "./Input";
6
6
  const smallLabel = { display: 'block', fontSize: '8px', color: 'rgba(34, 211, 238, 0.5)', marginBottom: 2 };
7
+ const directionalLightDefaults = {
8
+ color: '#ffffff',
9
+ intensity: 1,
10
+ castShadow: true,
11
+ shadowMapSize: 1024,
12
+ shadowCameraNear: 0.1,
13
+ shadowCameraFar: 100,
14
+ shadowCameraTop: 30,
15
+ shadowCameraBottom: -30,
16
+ shadowCameraLeft: -30,
17
+ shadowCameraRight: 30,
18
+ targetOffset: [0, -5, 0],
19
+ };
7
20
  const directionalLightFields = [
8
21
  { name: 'color', type: 'color', label: 'Color' },
9
22
  { name: 'intensity', type: 'number', label: 'Intensity', step: 0.1, min: 0 },
@@ -29,39 +42,52 @@ const directionalLightFields = [
29
42
  },
30
43
  ];
31
44
  function DirectionalLightComponentEditor({ component, onUpdate }) {
32
- return (_jsx(FieldRenderer, { fields: directionalLightFields, values: component.properties, onChange: onUpdate }));
45
+ const values = Object.assign(Object.assign({}, directionalLightDefaults), component.properties);
46
+ const fields = values.castShadow
47
+ ? directionalLightFields
48
+ : directionalLightFields.filter(field => field.name !== '_shadowCamera');
49
+ return (_jsx(FieldRenderer, { fields: fields, values: values, onChange: onUpdate }));
33
50
  }
34
- function DirectionalLightView({ properties, editMode }) {
35
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
36
- const color = (_a = properties.color) !== null && _a !== void 0 ? _a : '#ffffff';
37
- const intensity = (_b = properties.intensity) !== null && _b !== void 0 ? _b : 1.0;
38
- const castShadow = (_c = properties.castShadow) !== null && _c !== void 0 ? _c : true;
39
- const shadowMapSize = (_d = properties.shadowMapSize) !== null && _d !== void 0 ? _d : 1024;
40
- const shadowCameraNear = (_e = properties.shadowCameraNear) !== null && _e !== void 0 ? _e : 0.1;
41
- const shadowCameraFar = (_f = properties.shadowCameraFar) !== null && _f !== void 0 ? _f : 100;
42
- const shadowCameraTop = (_g = properties.shadowCameraTop) !== null && _g !== void 0 ? _g : 30;
43
- const shadowCameraBottom = (_h = properties.shadowCameraBottom) !== null && _h !== void 0 ? _h : -30;
44
- const shadowCameraLeft = (_j = properties.shadowCameraLeft) !== null && _j !== void 0 ? _j : -30;
45
- const shadowCameraRight = (_k = properties.shadowCameraRight) !== null && _k !== void 0 ? _k : 30;
46
- const targetOffset = (_l = properties.targetOffset) !== null && _l !== void 0 ? _l : [0, -5, 0];
51
+ function DirectionalLightView({ properties, editMode, isSelected }) {
52
+ const merged = Object.assign(Object.assign({}, directionalLightDefaults), properties);
53
+ const color = merged.color;
54
+ const intensity = merged.intensity;
55
+ const castShadow = merged.castShadow;
56
+ const shadowMapSize = merged.shadowMapSize;
57
+ const shadowCameraNear = merged.shadowCameraNear;
58
+ const shadowCameraFar = merged.shadowCameraFar;
59
+ const shadowCameraTop = merged.shadowCameraTop;
60
+ const shadowCameraBottom = merged.shadowCameraBottom;
61
+ const shadowCameraLeft = merged.shadowCameraLeft;
62
+ const shadowCameraRight = merged.shadowCameraRight;
63
+ const targetOffset = merged.targetOffset;
47
64
  const directionalLightRef = useRef(null);
48
65
  const targetRef = useRef(null);
49
- // Set up light target reference when both refs are ready
66
+ const [shadowCamera, setShadowCamera] = useState(null);
67
+ const shadowCameraHelper = useMemo(() => shadowCamera ? new CameraHelper(shadowCamera) : null, [shadowCamera]);
68
+ useEffect(() => {
69
+ return () => {
70
+ shadowCameraHelper === null || shadowCameraHelper === void 0 ? void 0 : shadowCameraHelper.dispose();
71
+ };
72
+ }, [shadowCameraHelper]);
73
+ // Use a local target object so node transforms rotate the light direction naturally.
50
74
  useEffect(() => {
51
75
  if (directionalLightRef.current && targetRef.current) {
52
76
  directionalLightRef.current.target = targetRef.current;
77
+ setShadowCamera(directionalLightRef.current.shadow.camera);
53
78
  }
54
79
  }, []);
55
- // Update target world position based on light position + offset
56
80
  useFrame(() => {
57
81
  if (!directionalLightRef.current || !targetRef.current)
58
82
  return;
59
- const lightWorldPos = new Vector3();
60
- directionalLightRef.current.getWorldPosition(lightWorldPos);
61
- // Target is positioned relative to light's world position
62
- targetRef.current.position.set(lightWorldPos.x + targetOffset[0], lightWorldPos.y + targetOffset[1], lightWorldPos.z + targetOffset[2]);
83
+ directionalLightRef.current.target.updateMatrixWorld();
84
+ if (shadowCamera && shadowCameraHelper && castShadow) {
85
+ shadowCamera.updateProjectionMatrix();
86
+ shadowCamera.updateMatrixWorld();
87
+ shadowCameraHelper.update();
88
+ }
63
89
  });
64
- return (_jsxs(_Fragment, { children: [_jsx("directionalLight", { ref: directionalLightRef, color: color, intensity: intensity, castShadow: castShadow, "shadow-mapSize-width": shadowMapSize, "shadow-mapSize-height": shadowMapSize, "shadow-camera-near": shadowCameraNear, "shadow-camera-far": shadowCameraFar, "shadow-camera-top": shadowCameraTop, "shadow-camera-bottom": shadowCameraBottom, "shadow-camera-left": shadowCameraLeft, "shadow-camera-right": shadowCameraRight, "shadow-bias": -0.001, "shadow-normalBias": 0.02 }), _jsx("object3D", { ref: targetRef }), editMode && (_jsxs(_Fragment, { children: [_jsxs("mesh", { children: [_jsx("sphereGeometry", { args: [0.3, 8, 6] }), _jsx("meshBasicMaterial", { color: color, wireframe: true })] }), _jsxs("mesh", { position: targetOffset, children: [_jsx("sphereGeometry", { args: [0.2, 8, 6] }), _jsx("meshBasicMaterial", { color: color, wireframe: true, opacity: 0.5, transparent: true })] }), _jsxs("line", { children: [_jsx("bufferGeometry", { onUpdate: (geo) => {
90
+ return (_jsxs(_Fragment, { children: [_jsx("directionalLight", { ref: directionalLightRef, color: color, intensity: intensity, castShadow: castShadow, "shadow-mapSize-width": shadowMapSize, "shadow-mapSize-height": shadowMapSize, "shadow-camera-near": shadowCameraNear, "shadow-camera-far": shadowCameraFar, "shadow-camera-top": shadowCameraTop, "shadow-camera-bottom": shadowCameraBottom, "shadow-camera-left": shadowCameraLeft, "shadow-camera-right": shadowCameraRight, "shadow-bias": -0.001, "shadow-normalBias": 0.02 }), _jsx("object3D", { ref: targetRef, position: targetOffset }), editMode && isSelected && castShadow && shadowCameraHelper && (_jsx("primitive", { object: shadowCameraHelper })), editMode && isSelected && (_jsxs(_Fragment, { children: [_jsxs("mesh", { children: [_jsx("sphereGeometry", { args: [0.3, 8, 6] }), _jsx("meshBasicMaterial", { color: color, wireframe: true })] }), _jsxs("mesh", { position: targetOffset, children: [_jsx("sphereGeometry", { args: [0.2, 8, 6] }), _jsx("meshBasicMaterial", { color: color, wireframe: true, opacity: 0.5, transparent: true })] }), _jsxs("line", { children: [_jsx("bufferGeometry", { onUpdate: (geo) => {
65
91
  const points = [
66
92
  new Vector3(0, 0, 0),
67
93
  new Vector3(targetOffset[0], targetOffset[1], targetOffset[2])
@@ -73,6 +99,6 @@ const DirectionalLightComponent = {
73
99
  name: 'DirectionalLight',
74
100
  Editor: DirectionalLightComponentEditor,
75
101
  View: DirectionalLightView,
76
- defaultProperties: {}
102
+ defaultProperties: directionalLightDefaults
77
103
  };
78
104
  export default DirectionalLightComponent;
@@ -0,0 +1,3 @@
1
+ import { Component } from './ComponentRegistry';
2
+ declare const EnvironmentComponent: Component;
3
+ export default EnvironmentComponent;
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Environment } from '@react-three/drei';
3
+ import { FieldGroup, NumberField } from './Input';
4
+ function EnvironmentView({ properties, children, editMode, loadedTextures, loadedModels, }) {
5
+ const { intensity = 1, resolution = 256 } = properties;
6
+ const assetRevision = `${Object.keys(loadedTextures !== null && loadedTextures !== void 0 ? loadedTextures : {}).sort().join('|')}::${Object.keys(loadedModels !== null && loadedModels !== void 0 ? loadedModels : {}).sort().join('|')}`;
7
+ return (_jsx(Environment, { background: true, environmentIntensity: intensity, resolution: resolution, frames: editMode ? undefined : 1, children: children }, assetRevision));
8
+ }
9
+ const EnvironmentComponent = {
10
+ name: 'Environment',
11
+ Editor: ({ component, onUpdate }) => (_jsxs(FieldGroup, { children: [_jsx(NumberField, { name: "intensity", label: "Intensity", values: component.properties, onChange: onUpdate, min: 0, step: 0.1, fallback: 1 }), _jsx(NumberField, { name: "resolution", label: "Resolution", values: component.properties, onChange: onUpdate, min: 64, step: 64, fallback: 256 })] })),
12
+ View: EnvironmentView,
13
+ defaultProperties: {},
14
+ };
15
+ export default EnvironmentComponent;
@@ -1,67 +1,67 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { FieldRenderer, Input } from "./Input";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { FieldGroup, NumberField, SelectField } from "./Input";
3
3
  const GEOMETRY_ARGS = {
4
4
  box: {
5
- labels: ["Width", "Height", "Depth"],
6
- defaults: [1, 1, 1],
5
+ fields: [
6
+ { name: 'width', label: 'Width', defaultValue: 1, min: 0.01, step: 0.1 },
7
+ { name: 'height', label: 'Height', defaultValue: 1, min: 0.01, step: 0.1 },
8
+ { name: 'depth', label: 'Depth', defaultValue: 1, min: 0.01, step: 0.1 },
9
+ ],
7
10
  },
8
11
  sphere: {
9
- labels: ["Radius", "Width Segments", "Height Segments"],
10
- defaults: [1, 32, 16],
12
+ fields: [
13
+ { name: 'radius', label: 'Radius', defaultValue: 1, min: 0.01, step: 0.1 },
14
+ { name: 'widthSegments', label: 'Width Segments', defaultValue: 32, min: 3, step: 1 },
15
+ { name: 'heightSegments', label: 'Height Segments', defaultValue: 16, min: 2, step: 1 },
16
+ ],
11
17
  },
12
18
  plane: {
13
- labels: ["Width", "Height"],
14
- defaults: [1, 1],
19
+ fields: [
20
+ { name: 'width', label: 'Width', defaultValue: 1, min: 0.01, step: 0.1 },
21
+ { name: 'height', label: 'Height', defaultValue: 1, min: 0.01, step: 0.1 },
22
+ ],
15
23
  },
16
24
  cylinder: {
17
- labels: ["Radius Top", "Radius Bottom", "Height", "Radial Segments"],
18
- defaults: [1, 1, 1, 32],
25
+ fields: [
26
+ { name: 'radiusTop', label: 'Radius Top', defaultValue: 1, min: 0.01, step: 0.1 },
27
+ { name: 'radiusBottom', label: 'Radius Bottom', defaultValue: 1, min: 0.01, step: 0.1 },
28
+ { name: 'height', label: 'Height', defaultValue: 1, min: 0.01, step: 0.1 },
29
+ { name: 'radialSegments', label: 'Radial Segments', defaultValue: 32, min: 3, step: 1 },
30
+ ],
19
31
  },
20
32
  };
33
+ function getDefaultArgs(geometryType) {
34
+ var _a, _b;
35
+ return ((_b = (_a = GEOMETRY_ARGS[geometryType]) === null || _a === void 0 ? void 0 : _a.fields) !== null && _b !== void 0 ? _b : []).map(field => field.defaultValue);
36
+ }
21
37
  function GeometryComponentEditor({ component, onUpdate, }) {
22
- const { geometryType, args = [] } = component.properties;
23
- const schema = GEOMETRY_ARGS[geometryType];
24
- const fields = [
25
- {
26
- name: 'geometryType',
27
- type: 'select',
28
- label: 'Type',
29
- options: [
30
- { value: 'box', label: 'Box' },
31
- { value: 'sphere', label: 'Sphere' },
32
- { value: 'plane', label: 'Plane' },
33
- { value: 'cylinder', label: 'Cylinder' },
34
- ],
35
- },
36
- {
37
- name: 'args',
38
- type: 'custom',
39
- label: '',
40
- render: ({ values, onChangeMultiple }) => {
41
- const currentType = values.geometryType;
42
- const currentSchema = GEOMETRY_ARGS[currentType];
43
- const currentArgs = values.args || currentSchema.defaults;
44
- return (_jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: 4 }, children: currentSchema.labels.map((label, i) => {
45
- var _a;
46
- return (_jsx(Input, { label: label, value: (_a = currentArgs[i]) !== null && _a !== void 0 ? _a : currentSchema.defaults[i], step: 0.1, min: 0.01, onChange: value => {
47
- const next = [...currentArgs];
48
- next[i] = value;
49
- onChangeMultiple({ args: next });
50
- } }, label));
51
- }) }));
52
- },
53
- },
54
- ];
38
+ var _a, _b, _c;
39
+ const geometryType = (_a = component.properties.geometryType) !== null && _a !== void 0 ? _a : 'box';
40
+ const schema = (_b = GEOMETRY_ARGS[geometryType]) !== null && _b !== void 0 ? _b : GEOMETRY_ARGS.box;
41
+ const args = (_c = component.properties.args) !== null && _c !== void 0 ? _c : getDefaultArgs(geometryType);
55
42
  // Handle geometry type change to reset args
56
43
  const handleChange = (newValues) => {
57
44
  if ('geometryType' in newValues && newValues.geometryType !== geometryType) {
58
- onUpdate({ geometryType: newValues.geometryType, args: GEOMETRY_ARGS[newValues.geometryType].defaults });
45
+ onUpdate({ geometryType: newValues.geometryType, args: getDefaultArgs(newValues.geometryType) });
59
46
  }
60
47
  else {
61
48
  onUpdate(newValues);
62
49
  }
63
50
  };
64
- return (_jsx(FieldRenderer, { fields: fields, values: component.properties, onChange: handleChange }));
51
+ const updateArg = (index, value) => {
52
+ const next = [...args];
53
+ next[index] = value;
54
+ onUpdate({ args: next });
55
+ };
56
+ return (_jsxs(FieldGroup, { children: [_jsx(SelectField, { name: "geometryType", label: "Type", values: component.properties, onChange: handleChange, options: [
57
+ { value: 'box', label: 'Box' },
58
+ { value: 'sphere', label: 'Sphere' },
59
+ { value: 'plane', label: 'Plane' },
60
+ { value: 'cylinder', label: 'Cylinder' },
61
+ ] }), schema.fields.map((field, index) => {
62
+ var _a;
63
+ return (_jsx(NumberField, { name: field.name, label: field.label, values: { [field.name]: (_a = args[index]) !== null && _a !== void 0 ? _a : field.defaultValue }, onChange: (next) => updateArg(index, next[field.name]), fallback: field.defaultValue, min: field.min, step: field.step }, field.name));
64
+ })] }));
65
65
  }
66
66
  // View for Geometry component
67
67
  function GeometryComponentView({ properties, children }) {
@@ -87,7 +87,7 @@ const GeometryComponent = {
87
87
  nonComposable: true,
88
88
  defaultProperties: {
89
89
  geometryType: 'box',
90
- args: GEOMETRY_ARGS.box.defaults,
90
+ args: getDefaultArgs('box'),
91
91
  }
92
92
  };
93
93
  export default GeometryComponent;