react-three-game 0.0.108 → 0.0.109

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 (36) hide show
  1. package/README.md +5 -5
  2. package/dist/editor.d.ts +22 -0
  3. package/dist/editor.js +15 -0
  4. package/dist/plugins/crashcat/CrashcatPhysicsComponent.js +1 -1
  5. package/dist/plugins/crashcat/CrashcatRuntime.js +1 -1
  6. package/dist/tools/prefabeditor/EditorContext.d.ts +36 -0
  7. package/dist/tools/prefabeditor/EditorContext.js +17 -0
  8. package/dist/tools/prefabeditor/EditorTree.js +1 -1
  9. package/dist/tools/prefabeditor/EditorTreeMenus.js +1 -1
  10. package/dist/tools/prefabeditor/EditorUI.js +1 -1
  11. package/dist/tools/prefabeditor/PrefabEditor.d.ts +4 -37
  12. package/dist/tools/prefabeditor/PrefabEditor.js +15 -30
  13. package/dist/tools/prefabeditor/PrefabRoot.d.ts +3 -27
  14. package/dist/tools/prefabeditor/PrefabRoot.js +79 -60
  15. package/dist/tools/prefabeditor/SceneContext.d.ts +28 -0
  16. package/dist/tools/prefabeditor/SceneContext.js +14 -0
  17. package/dist/tools/prefabeditor/assetRuntime.d.ts +4 -0
  18. package/dist/tools/prefabeditor/components/ComponentRegistry.d.ts +1 -1
  19. package/dist/tools/prefabeditor/components/ModelComponent.js +1 -1
  20. package/dist/tools/prefabeditor/components/PrefabRefComponent.js +4 -4
  21. package/dist/tools/prefabeditor/components/TransformComponent.js +1 -1
  22. package/dist/tools/prefabeditor/components/index.d.ts +1 -0
  23. package/dist/tools/prefabeditor/components/index.js +8 -0
  24. package/dist/tools/prefabeditor/components/runtime.d.ts +4 -0
  25. package/dist/tools/prefabeditor/components/runtime.js +372 -0
  26. package/dist/tools/prefabeditor/prefabStore.d.ts +1 -1
  27. package/dist/tools/prefabeditor/prefabStore.js +5 -1
  28. package/dist/tools/prefabeditor/runtimeUtils.d.ts +10 -0
  29. package/dist/tools/prefabeditor/runtimeUtils.js +30 -0
  30. package/dist/tools/prefabeditor/utils.d.ts +1 -9
  31. package/dist/tools/prefabeditor/utils.js +3 -28
  32. package/dist/viewer.d.ts +22 -0
  33. package/dist/viewer.js +14 -0
  34. package/package.json +10 -8
  35. package/dist/index.d.ts +0 -40
  36. package/dist/index.js +0 -32
package/README.md CHANGED
@@ -29,7 +29,7 @@ npm install react-three-game @react-three/drei @react-three/fiber three
29
29
  Here is a minimal example that renders a prefab inside a normal R3F app:
30
30
 
31
31
  ```tsx
32
- import { GameCanvas, PrefabRoot, ground } from "react-three-game";
32
+ import { GameCanvas, PrefabRoot, ground } from "react-three-game/viewer";
33
33
 
34
34
  const prefab = {
35
35
  id: "starter-scene",
@@ -80,7 +80,7 @@ This example renders a simple authored prefab with a ground plane and mesh conte
80
80
  In addition to the runtime renderer, there is a visual editor for authoring prefabs.
81
81
 
82
82
  ```tsx
83
- import { PrefabEditor } from "react-three-game";
83
+ import { PrefabEditor } from "react-three-game/editor";
84
84
 
85
85
  export default function App() {
86
86
  return <PrefabEditor initialPrefab={prefab} onChange={console.log} />;
@@ -164,7 +164,7 @@ Use the editor or root ref for scene-native object access, and the `Scene` mutat
164
164
 
165
165
  ```tsx
166
166
  import { useEffect, useRef } from "react";
167
- import { PrefabEditor, type PrefabEditorRef } from "react-three-game";
167
+ import { PrefabEditor, type PrefabEditorRef } from "react-three-game/editor";
168
168
 
169
169
  function RaiseBall() {
170
170
  const editorRef = useRef<PrefabEditorRef>(null);
@@ -199,7 +199,7 @@ ball?.rotateY(0.5);
199
199
  For runtime integrations that need to react to authored scene changes, subscribe through the prefab store:
200
200
 
201
201
  ```tsx
202
- import { usePrefabStoreApi } from "react-three-game";
202
+ import { usePrefabStoreApi } from "react-three-game/editor";
203
203
 
204
204
  const store = usePrefabStoreApi();
205
205
  const stop = store.subscribe(
@@ -214,7 +214,7 @@ For runtime-owned imperative state, register node-local handles instead of reach
214
214
 
215
215
  ```tsx
216
216
  import { useEffect } from "react";
217
- import { useAssetRuntime, useNode, useNodeHandle } from "react-three-game";
217
+ import { useAssetRuntime, useNode, useNodeHandle } from "react-three-game/viewer";
218
218
 
219
219
  function SpinnerView({ children }: { children?: React.ReactNode }) {
220
220
  const { nodeId } = useNode();
@@ -0,0 +1,22 @@
1
+ import { registerBuiltinComponents } from "./tools/prefabeditor/components";
2
+ import "./viewer";
3
+ export { registerBuiltinComponents };
4
+ export * from "./viewer";
5
+ export { default as PrefabEditor } from "./tools/prefabeditor/PrefabEditor";
6
+ export type { PrefabEditorProps, PrefabEditorRef } from "./tools/prefabeditor/PrefabEditor";
7
+ export { useEditorContext, useEditorRef } from "./tools/prefabeditor/EditorContext";
8
+ export type { EditorContextType } from "./tools/prefabeditor/EditorContext";
9
+ export { usePrefabStore, usePrefabStoreApi } from "./tools/prefabeditor/prefabStore";
10
+ export type { PrefabStoreApi, PrefabStoreState } from "./tools/prefabeditor/prefabStore";
11
+ export { FieldRenderer, FieldGroup, ListEditor, Label, Vector3Input, Vector3Field, NumberField, ColorInput, ColorField, StringInput, StringField, BooleanInput, BooleanField, SelectInput, SelectField, } from "./tools/prefabeditor/components/Input";
12
+ export { loadJson, saveJson, exportGLB, exportGLBData, regenerateIds, computeParentWorldMatrix, } from "./tools/prefabeditor/utils";
13
+ export type { ExportGLBOptions } from "./tools/prefabeditor/utils";
14
+ export { decomposeModelToPrefabNodes } from "./tools/prefabeditor/modelPrefab";
15
+ export type { DecomposeModelOptions } from "./tools/prefabeditor/modelPrefab";
16
+ export type { FieldDefinition, FieldType } from "./tools/prefabeditor/components/Input";
17
+ export { MaterialOverridesProvider, useMaterialOverrides } from "./tools/prefabeditor/components/MaterialComponent";
18
+ export type { MaterialOverrides } from "./tools/prefabeditor/components/MaterialComponent";
19
+ export { float, positionLocal, sin, time, uniform, vec3, } from "three/tsl";
20
+ export { loadFiles } from "./tools/dragdrop/DragDropLoader";
21
+ export type { AssetLoadOptions } from "./tools/dragdrop/DragDropLoader";
22
+ export { ModelListViewer, SoundListViewer, ModelPicker, SoundPicker, TextureListViewer, TexturePicker, SingleModelViewer, SingleSoundViewer, SingleTextureViewer, SharedCanvas, } from "./tools/assetviewer/page";
package/dist/editor.js ADDED
@@ -0,0 +1,15 @@
1
+ import { registerBuiltinComponents } from "./tools/prefabeditor/components";
2
+ import "./viewer";
3
+ registerBuiltinComponents();
4
+ export { registerBuiltinComponents };
5
+ export * from "./viewer";
6
+ export { default as PrefabEditor } from "./tools/prefabeditor/PrefabEditor";
7
+ export { useEditorContext, useEditorRef } from "./tools/prefabeditor/EditorContext";
8
+ export { usePrefabStore, usePrefabStoreApi } from "./tools/prefabeditor/prefabStore";
9
+ export { FieldRenderer, FieldGroup, ListEditor, Label, Vector3Input, Vector3Field, NumberField, ColorInput, ColorField, StringInput, StringField, BooleanInput, BooleanField, SelectInput, SelectField, } from "./tools/prefabeditor/components/Input";
10
+ export { loadJson, saveJson, exportGLB, exportGLBData, regenerateIds, computeParentWorldMatrix, } from "./tools/prefabeditor/utils";
11
+ export { decomposeModelToPrefabNodes } from "./tools/prefabeditor/modelPrefab";
12
+ export { MaterialOverridesProvider, useMaterialOverrides } from "./tools/prefabeditor/components/MaterialComponent";
13
+ export { float, positionLocal, sin, time, uniform, vec3, } from "three/tsl";
14
+ export { loadFiles } from "./tools/dragdrop/DragDropLoader";
15
+ export { ModelListViewer, SoundListViewer, ModelPicker, SoundPicker, TextureListViewer, TexturePicker, SingleModelViewer, SingleSoundViewer, SingleTextureViewer, SharedCanvas, } from "./tools/assetviewer/page";
@@ -5,7 +5,7 @@ import { useEffect, useMemo, useRef } from "react";
5
5
  import { BooleanField, FieldRenderer, StringField, Vector3Field, } from "../../tools/prefabeditor/components/Input";
6
6
  import { useAssetRuntime, useNode } from "../../tools/prefabeditor/assetRuntime";
7
7
  import { usePrefabStoreApi } from "../../tools/prefabeditor/prefabStore";
8
- import { PrefabEditorMode, useScene } from "../../tools/prefabeditor/PrefabRoot";
8
+ import { PrefabEditorMode, useScene } from "../../tools/prefabeditor/SceneContext";
9
9
  import { box, capsule, convexHull, MotionQuality, MotionType, rigidBody, sphere, triangleMesh, } from "crashcat";
10
10
  import { Matrix4, Quaternion, Vector3 } from "three";
11
11
  import { useCrashcat } from "./CrashcatRuntime";
@@ -5,7 +5,7 @@ import { addBroadphaseLayer, addObjectLayer, createWorld, createWorldSettings, e
5
5
  import { debugRenderer } from "crashcat/three";
6
6
  import { useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
7
7
  import { gameEvents } from "../../tools/prefabeditor/GameEvents";
8
- import { PrefabEditorMode, useScene } from "../../tools/prefabeditor/PrefabRoot";
8
+ import { PrefabEditorMode, useScene } from "../../tools/prefabeditor/SceneContext";
9
9
  const SLEEP_TIME_BEFORE_REST = 0.1;
10
10
  const SLEEP_POINT_VELOCITY_THRESHOLD = 0.06;
11
11
  const MAX_PHYSICS_STEP_DELTA = 1 / 60;
@@ -0,0 +1,36 @@
1
+ import type { PrefabEditorMode, Scene } from "./SceneContext";
2
+ import type { Prefab } from "./types";
3
+ import type { ExportGLBOptions } from "./utils";
4
+ export interface PrefabEditorRef extends Scene {
5
+ save: () => Prefab;
6
+ load: (prefab: Prefab, options?: {
7
+ resetHistory?: boolean;
8
+ notifyChange?: boolean;
9
+ }) => void;
10
+ undo: () => void;
11
+ redo: () => void;
12
+ screenshot: () => void;
13
+ exportGLB: (options?: ExportGLBOptions) => Promise<ArrayBuffer | undefined>;
14
+ exportGLBData: () => Promise<ArrayBuffer | undefined>;
15
+ clearSelection: () => Promise<void>;
16
+ }
17
+ export interface EditorContextType {
18
+ mode: PrefabEditorMode;
19
+ basePath: string;
20
+ setMode: (mode: PrefabEditorMode) => void;
21
+ transformMode: "translate" | "rotate" | "scale";
22
+ setTransformMode: (mode: "translate" | "rotate" | "scale") => void;
23
+ scaleSnap: number;
24
+ setScaleSnap: (resolution: number) => void;
25
+ positionSnap: number;
26
+ setPositionSnap: (resolution: number) => void;
27
+ rotationSnap: number;
28
+ setRotationSnap: (resolution: number) => void;
29
+ onFocusNode?: (nodeId: string) => void;
30
+ onScreenshot?: () => void;
31
+ onExportGLB?: () => void;
32
+ }
33
+ export declare const EditorContext: import("react").Context<EditorContextType | null>;
34
+ export declare const EditorRefContext: import("react").Context<PrefabEditorRef | null>;
35
+ export declare function useEditorContext(): EditorContextType;
36
+ export declare function useEditorRef(): PrefabEditorRef;
@@ -0,0 +1,17 @@
1
+ import { createContext, useContext } from "react";
2
+ export const EditorContext = createContext(null);
3
+ export const EditorRefContext = createContext(null);
4
+ export function useEditorContext() {
5
+ const context = useContext(EditorContext);
6
+ if (!context) {
7
+ throw new Error("useEditorContext must be used within EditorContext.Provider");
8
+ }
9
+ return context;
10
+ }
11
+ export function useEditorRef() {
12
+ const editorRef = useContext(EditorRefContext);
13
+ if (!editorRef) {
14
+ throw new Error("useEditorRef must be used within PrefabEditor");
15
+ }
16
+ return editorRef;
17
+ }
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { memo, useCallback, useState } from 'react';
3
3
  import { base, colors, tree } from './styles';
4
- import { useEditorContext, useEditorRef } from './PrefabEditor';
4
+ import { useEditorContext, useEditorRef } from './EditorContext';
5
5
  import { Dropdown } from './Dropdown';
6
6
  import { FileMenu, TreeContextMenu, TreeNodeMenu } from './EditorTreeMenus';
7
7
  import { createEmptyNode, createPackedPrefabNode } from './prefab';
@@ -12,7 +12,7 @@ import { useEffect, useRef, useState } from 'react';
12
12
  import { createPortal } from 'react-dom';
13
13
  import { createEmptyPrefab } from './prefab';
14
14
  import { menu } from './styles';
15
- import { useEditorContext } from './PrefabEditor';
15
+ import { useEditorContext } from './EditorContext';
16
16
  import { loadJson, loadJsonFile, saveJson, withBasePath } from './utils';
17
17
  function MenuPanel({ children, style, }) {
18
18
  return (_jsx("div", { style: Object.assign(Object.assign(Object.assign({}, menu.container), { position: 'static' }), style), onClick: (e) => e.stopPropagation(), children: children }));
@@ -14,7 +14,7 @@ import { useState } from 'react';
14
14
  import EditorTree from './EditorTree';
15
15
  import { canAddComponentToNode, getAllComponentDefs, getNextComponentKey } from './components/ComponentRegistry';
16
16
  import { createComponentData } from './prefab';
17
- import { useEditorRef } from './PrefabEditor';
17
+ import { useEditorRef } from './EditorContext';
18
18
  import { base, colors, inspector, componentCard } from './styles';
19
19
  import { usePrefabStore } from './prefabStore';
20
20
  function EditorUI({ selectedId, setSelectedId, getPrefab, onReplacePrefab, onImportPrefab, basePath, onUndo, onRedo, canUndo, canRedo }) {
@@ -1,44 +1,11 @@
1
- import GameCanvas from "../../shared/GameCanvas";
2
1
  import type { Prefab } from "./types";
3
- import { PrefabEditorMode, type Scene } from "./PrefabRoot";
4
- import type { ExportGLBOptions } from "./utils";
2
+ import { GameCanvas, PrefabEditorMode } from "../../viewer";
3
+ import { type PrefabEditorRef } from "./EditorContext";
5
4
  export { isExternalPath as isAbsoluteAssetPath } from "./utils";
6
5
  export declare function resolvePrefabAssetPath(basePath: string, file: string): string;
7
6
  export declare function getPrefabAssetRef(assetRef: string, folder: "models" | "textures" | "sound"): string;
8
- export interface PrefabEditorRef extends Scene {
9
- save: () => Prefab;
10
- load: (prefab: Prefab, options?: {
11
- resetHistory?: boolean;
12
- notifyChange?: boolean;
13
- }) => void;
14
- undo: () => void;
15
- redo: () => void;
16
- screenshot: () => void;
17
- exportGLB: (options?: ExportGLBOptions) => Promise<ArrayBuffer | undefined>;
18
- exportGLBData: () => Promise<ArrayBuffer | undefined>;
19
- clearSelection: () => Promise<void>;
20
- }
21
- export type { PrefabNode } from "./PrefabRoot";
22
- export interface EditorContextType {
23
- mode: PrefabEditorMode;
24
- basePath: string;
25
- setMode: (mode: PrefabEditorMode) => void;
26
- transformMode: "translate" | "rotate" | "scale";
27
- setTransformMode: (mode: "translate" | "rotate" | "scale") => void;
28
- scaleSnap: number;
29
- setScaleSnap: (resolution: number) => void;
30
- positionSnap: number;
31
- setPositionSnap: (resolution: number) => void;
32
- rotationSnap: number;
33
- setRotationSnap: (resolution: number) => void;
34
- onFocusNode?: (nodeId: string) => void;
35
- onScreenshot?: () => void;
36
- onExportGLB?: () => void;
37
- }
38
- export declare const EditorContext: import("react").Context<EditorContextType | null>;
39
- export declare const EditorRefContext: import("react").Context<PrefabEditorRef | null>;
40
- export declare function useEditorContext(): EditorContextType;
41
- export declare function useEditorRef(): PrefabEditorRef;
7
+ export type { EditorContextType, PrefabEditorRef } from "./EditorContext";
8
+ export { EditorContext, EditorRefContext, useEditorContext, useEditorRef } from "./EditorContext";
42
9
  export interface PrefabEditorProps {
43
10
  basePath?: string;
44
11
  initialPrefab?: Prefab;
@@ -9,18 +9,18 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  };
10
10
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
11
11
  import { MapControls, TransformControls, useHelper } from "@react-three/drei";
12
- import GameCanvas from "../../shared/GameCanvas";
13
- import { useCallback, useEffect, useMemo, useRef, useState, forwardRef, useImperativeHandle, createContext, useContext } from "react";
12
+ import { useCallback, useEffect, useMemo, useRef, useState, forwardRef, useImperativeHandle } from "react";
14
13
  import { BoxHelper } from "three";
15
14
  import { findComponentEntry } from "./types";
16
- import { PrefabEditorMode, PrefabRoot } from "./PrefabRoot";
15
+ import { GameCanvas, PrefabRoot, PrefabEditorMode, SceneContext, createImageNode, createModelNode, denormalizePrefab } from "../../viewer";
17
16
  import EditorUI from "./EditorUI";
18
17
  import { base, toolbar } from "./styles";
19
18
  import { computeParentWorldMatrix, decompose, exportGLB as exportGLBFile, exportGLBData, focusCameraOnObject, isExternalPath, regenerateIds, withBasePath } from "./utils";
20
19
  import { loadDroppedAssets } from "../dragdrop";
21
- import { denormalizePrefab, createImageNode, createModelNode, createNode } from './prefab';
20
+ import { createNode } from './prefab';
22
21
  import { createPrefabStore, PrefabStoreProvider } from "./prefabStore";
23
22
  import { decomposeModelToPrefabNodes, hasCollisionMeshConventions } from "./modelPrefab";
23
+ import { EditorContext, EditorRefContext } from "./EditorContext";
24
24
  function isObjectAttachedToRoot(root, object) {
25
25
  if (!root || !object)
26
26
  return false;
@@ -48,22 +48,7 @@ function SelectionHelper({ object }) {
48
48
  useHelper(helperTarget, BoxHelper, "cyan");
49
49
  return null;
50
50
  }
51
- export const EditorContext = createContext(null);
52
- export const EditorRefContext = createContext(null);
53
- export function useEditorContext() {
54
- const context = useContext(EditorContext);
55
- if (!context) {
56
- throw new Error("useEditorContext must be used within EditorContext.Provider");
57
- }
58
- return context;
59
- }
60
- export function useEditorRef() {
61
- const editorRef = useContext(EditorRefContext);
62
- if (!editorRef) {
63
- throw new Error("useEditorRef must be used within PrefabEditor");
64
- }
65
- return editorRef;
66
- }
51
+ export { EditorContext, EditorRefContext, useEditorContext, useEditorRef } from "./EditorContext";
67
52
  const MAX_HISTORY_LENGTH = 50;
68
53
  const HISTORY_DEBOUNCE_MS = 500;
69
54
  const DEFAULT_PREFAB = {
@@ -404,7 +389,7 @@ const PrefabEditor = forwardRef(({ basePath = "", initialPrefab, mode: initialMo
404
389
  canvasRef.current = state.gl.domElement;
405
390
  (_a = canvasProps === null || canvasProps === void 0 ? void 0 : canvasProps.onCreated) === null || _a === void 0 ? void 0 : _a.call(canvasProps, state);
406
391
  }, [canvasProps]);
407
- return _jsx(PrefabStoreProvider, { store: prefabStore, children: _jsx(EditorRefContext.Provider, { value: editorRefValue, children: _jsxs(EditorContext.Provider, { value: {
392
+ return _jsx(PrefabStoreProvider, { store: prefabStore, children: _jsx(EditorRefContext.Provider, { value: editorRefValue, children: _jsx(EditorContext.Provider, { value: {
408
393
  mode,
409
394
  basePath,
410
395
  setMode: updateMode,
@@ -419,16 +404,16 @@ const PrefabEditor = forwardRef(({ basePath = "", initialPrefab, mode: initialMo
419
404
  onFocusNode: isEditMode ? handleFocusNode : undefined,
420
405
  onScreenshot: handleScreenshot,
421
406
  onExportGLB: handleExportGLB
422
- }, children: [_jsxs(GameCanvas, Object.assign({ camera: { position: [0, 5, 15] } }, canvasProps, { onCreated: handleCanvasCreated, onPointerMissed: isEditMode
423
- ? (event) => {
424
- var _a, _b, _c, _d;
425
- const button = (_c = (_a = event.button) !== null && _a !== void 0 ? _a : (_b = event.sourceEvent) === null || _b === void 0 ? void 0 : _b.button) !== null && _c !== void 0 ? _c : 0;
426
- if (button === 0 && selectedId) {
427
- setSelection(null);
407
+ }, children: _jsxs(SceneContext.Provider, { value: sceneValue, children: [_jsxs(GameCanvas, Object.assign({ camera: { position: [0, 5, 15] } }, canvasProps, { onCreated: handleCanvasCreated, onPointerMissed: isEditMode
408
+ ? (event) => {
409
+ var _a, _b, _c, _d;
410
+ const button = (_c = (_a = event.button) !== null && _a !== void 0 ? _a : (_b = event.sourceEvent) === null || _b === void 0 ? void 0 : _b.button) !== null && _c !== void 0 ? _c : 0;
411
+ if (button === 0 && selectedId) {
412
+ setSelection(null);
413
+ }
414
+ (_d = canvasProps === null || canvasProps === void 0 ? void 0 : canvasProps.onPointerMissed) === null || _d === void 0 ? void 0 : _d.call(canvasProps, event);
428
415
  }
429
- (_d = canvasProps === null || canvasProps === void 0 ? void 0 : canvasProps.onPointerMissed) === null || _d === void 0 ? void 0 : _d.call(canvasProps, event);
430
- }
431
- : canvasProps === null || canvasProps === void 0 ? void 0 : canvasProps.onPointerMissed, children: [content, isEditMode ? _jsx(SelectionHelper, { object: transformObject }) : null, isEditMode && (_jsxs(_Fragment, { children: [_jsx(MapControls, { ref: controlsRef, enableDamping: false, makeDefault: true }), transformObject && (_jsx(TransformControls, { ref: transformControlsRef, object: transformObject, mode: transformMode, space: transformMode === "translate" ? "world" : "local", onObjectChange: handleTransformChange, translationSnap: positionSnap > 0 ? positionSnap : undefined, rotationSnap: rotationSnap > 0 ? rotationSnap : undefined, scaleSnap: scaleSnap > 0 ? scaleSnap : undefined }, `transform-${selectedId}-${transformMode}-${positionSnap}-${rotationSnap}-${scaleSnap}`))] }))] })), showUI && (_jsxs(_Fragment, { children: [_jsxs("div", { style: toolbar.panel, children: [_jsx("button", { type: "button", style: base.btn, onClick: toggleMode, children: isEditMode ? "▶" : "⏸" }), uiPlugins] }), isEditMode && (_jsx(EditorUI, { selectedId: selectedId, setSelectedId: setSelection, getPrefab: getPrefab, onReplacePrefab: (prefab) => loadPrefab(prefab, { resetHistory: true }), onImportPrefab: importPrefab, basePath: basePath, onUndo: undo, onRedo: redo, canUndo: historyIndex > 0, canRedo: historyIndex < history.length - 1 }))] }))] }) }) });
416
+ : canvasProps === null || canvasProps === void 0 ? void 0 : canvasProps.onPointerMissed, children: [content, isEditMode ? _jsx(SelectionHelper, { object: transformObject }) : null, isEditMode && (_jsxs(_Fragment, { children: [_jsx(MapControls, { ref: controlsRef, enableDamping: false, makeDefault: true }), transformObject && (_jsx(TransformControls, { ref: transformControlsRef, object: transformObject, mode: transformMode, space: transformMode === "translate" ? "world" : "local", onObjectChange: handleTransformChange, translationSnap: positionSnap > 0 ? positionSnap : undefined, rotationSnap: rotationSnap > 0 ? rotationSnap : undefined, scaleSnap: scaleSnap > 0 ? scaleSnap : undefined }, `transform-${selectedId}-${transformMode}-${positionSnap}-${rotationSnap}-${scaleSnap}`))] }))] })), showUI && (_jsxs(_Fragment, { children: [_jsxs("div", { style: toolbar.panel, children: [_jsx("button", { type: "button", style: base.btn, onClick: toggleMode, children: isEditMode ? "▶" : "⏸" }), uiPlugins] }), isEditMode && (_jsx(EditorUI, { selectedId: selectedId, setSelectedId: setSelection, getPrefab: getPrefab, onReplacePrefab: (prefab) => loadPrefab(prefab, { resetHistory: true }), onImportPrefab: importPrefab, basePath: basePath, onUndo: undo, onRedo: redo, canUndo: historyIndex > 0, canRedo: historyIndex < history.length - 1 }))] }))] }) }) }) });
432
417
  });
433
418
  PrefabEditor.displayName = "PrefabEditor";
434
419
  export default PrefabEditor;
@@ -1,35 +1,11 @@
1
1
  import { Matrix4 } from "three";
2
- import type { Object3D, Texture } from "three";
2
+ import type { Object3D } from "three";
3
3
  import { type ThreeEvent } from "@react-three/fiber";
4
4
  import type { GameObject as GameObjectType, Prefab } from "./types";
5
5
  import type { LoadedModels } from "../dragdrop";
6
6
  import type { PrefabStoreApi } from "./prefabStore";
7
- export declare enum PrefabEditorMode {
8
- Edit = "edit",
9
- Play = "play"
10
- }
11
- export type PrefabNode = Omit<GameObjectType, "children">;
12
- export interface Scene {
13
- root: Object3D | null;
14
- mode: PrefabEditorMode;
15
- basePath: string;
16
- get(id: string): GameObjectType | null;
17
- getObject(id: string): Object3D | null;
18
- getHandle<T = unknown>(id: string, kind: string): T | null;
19
- getModel(path: string): Object3D | null;
20
- add(node: GameObjectType, parentId?: string): GameObjectType;
21
- update(id: string, fn: (node: PrefabNode) => PrefabNode): void;
22
- replaceNode(id: string, node: GameObjectType): void;
23
- remove(id: string): void;
24
- duplicate(id: string): string | null;
25
- move(draggedId: string, targetId: string, position: "before" | "inside"): void;
26
- replace(prefab: Prefab): void;
27
- addModel(path: string, model: Object3D): void;
28
- addTexture(path: string, texture: Texture): void;
29
- addSound(path: string, sound: AudioBuffer): void;
30
- }
31
- export declare const SceneContext: import("react").Context<Scene | null>;
32
- export declare function useScene(): Scene;
7
+ import { type Scene } from "./SceneContext";
8
+ export type { Scene };
33
9
  export interface PrefabRootProps {
34
10
  editMode?: boolean;
35
11
  data?: Prefab;
@@ -19,26 +19,22 @@ var __rest = (this && this.__rest) || function (s, e) {
19
19
  return t;
20
20
  };
21
21
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
22
- import { createContext, forwardRef, useCallback, useContext, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
22
+ import { forwardRef, useCallback, useContext, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
23
23
  import { Euler, Matrix4 } from "three";
24
24
  import { useThree } from "@react-three/fiber";
25
25
  import { useStore } from "zustand";
26
26
  import { useClickValid } from "./useClickValid";
27
27
  import { findComponent, getNodeUserData } from "./types";
28
- import { getComponentDef, registerComponent } from "./components/ComponentRegistry";
29
- import { builtinComponents } from "./components";
28
+ import { getComponentDef } from "./components/ComponentRegistry";
30
29
  import { loadModel, loadSound, loadTexture } from "../dragdrop";
31
30
  import { GameInstance, GameInstanceProvider, getRepeatAxesFromModelProperties } from "./InstanceProvider";
32
- import { composeTransform, decompose, withBasePath } from "./utils";
31
+ import { composeTransform, decompose, withBasePath } from "./runtimeUtils";
33
32
  import { createPrefabStore, PrefabStoreProvider, usePrefabChildIds, usePrefabNode, usePrefabRootId } from "./prefabStore";
34
33
  import { AssetRuntimeContext, NodeScope } from "./assetRuntime";
35
34
  import { gameEvents } from "./GameEvents";
36
35
  import { sound as soundManager } from "../../helpers/SoundManager";
37
- builtinComponents.forEach(registerComponent);
36
+ import { PrefabEditorMode, SceneContext } from "./SceneContext";
38
37
  const IDENTITY = new Matrix4();
39
- const EMPTY_MODELS = {};
40
- const EMPTY_TEXTURES = {};
41
- const EMPTY_SOUNDS = {};
42
38
  const EMPTY_NODE_COMPONENTS = {
43
39
  geometry: undefined,
44
40
  materials: [],
@@ -76,28 +72,14 @@ function getNodeMetadataProps(node) {
76
72
  userData: Object.assign(Object.assign({ prefabNodeId: node.id }, (nodeName ? { prefabNodeName: nodeName } : {})), getNodeUserData(node)),
77
73
  };
78
74
  }
79
- export var PrefabEditorMode;
80
- (function (PrefabEditorMode) {
81
- PrefabEditorMode["Edit"] = "edit";
82
- PrefabEditorMode["Play"] = "play";
83
- })(PrefabEditorMode || (PrefabEditorMode = {}));
84
- export const SceneContext = createContext(null);
85
- export function useScene() {
86
- const scene = useContext(SceneContext);
87
- if (!scene) {
88
- throw new Error("useScene must be used within a PrefabRoot or PrefabEditor scene provider");
89
- }
90
- return scene;
91
- }
92
75
  export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSelect, onClick, onEditNodeClick, basePath = "", children }, ref) => {
76
+ const parentScene = useContext(SceneContext);
77
+ const parentAssetRuntime = useContext(AssetRuntimeContext);
93
78
  const renderer = useThree(state => state.gl);
94
79
  const camera = useThree(state => state.camera);
95
80
  const [models, setModels] = useState({});
96
81
  const [textures, setTextures] = useState({});
97
82
  const [sounds, setSounds] = useState({});
98
- const [injectedModels, setInjectedModels] = useState(EMPTY_MODELS);
99
- const [injectedTextures, setInjectedTextures] = useState(EMPTY_TEXTURES);
100
- const [injectedSounds, setInjectedSounds] = useState(EMPTY_SOUNDS);
101
83
  const loading = useRef(new Set());
102
84
  const failedModels = useRef(new Set());
103
85
  const failedTextures = useRef(new Set());
@@ -119,19 +101,18 @@ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSel
119
101
  const usesOwnedStore = resolvedStore === ownedStore;
120
102
  const rootId = useStore(resolvedStore, state => state.rootId);
121
103
  const assetRefCounts = useStore(resolvedStore, state => state.assetRefCounts);
122
- const availableModels = useMemo(() => (Object.assign(Object.assign({}, models), injectedModels)), [models, injectedModels]);
123
- const getModel = useCallback((path) => { var _a; return (_a = availableModels[path]) !== null && _a !== void 0 ? _a : null; }, [availableModels]);
124
- const getTexture = useCallback((path) => { var _a, _b; return (_b = (_a = injectedTextures[path]) !== null && _a !== void 0 ? _a : textures[path]) !== null && _b !== void 0 ? _b : null; }, [injectedTextures, textures]);
125
- const getSound = useCallback((path) => { var _a, _b; return (_b = (_a = injectedSounds[path]) !== null && _a !== void 0 ? _a : sounds[path]) !== null && _b !== void 0 ? _b : null; }, [injectedSounds, sounds]);
126
- const assetRevision = useMemo(() => `${Object.keys(textures).concat(Object.keys(injectedTextures)).sort().join('|')}::${Object.keys(availableModels).sort().join('|')}`, [availableModels, injectedTextures, textures]);
104
+ const getModel = useCallback((path) => { var _a, _b; return (_b = (_a = models[path]) !== null && _a !== void 0 ? _a : parentAssetRuntime === null || parentAssetRuntime === void 0 ? void 0 : parentAssetRuntime.getModel(path)) !== null && _b !== void 0 ? _b : null; }, [models, parentAssetRuntime]);
105
+ const getTexture = useCallback((path) => { var _a, _b; return (_b = (_a = textures[path]) !== null && _a !== void 0 ? _a : parentAssetRuntime === null || parentAssetRuntime === void 0 ? void 0 : parentAssetRuntime.getTexture(path)) !== null && _b !== void 0 ? _b : null; }, [parentAssetRuntime, textures]);
106
+ const getSound = useCallback((path) => { var _a, _b; return (_b = (_a = sounds[path]) !== null && _a !== void 0 ? _a : parentAssetRuntime === null || parentAssetRuntime === void 0 ? void 0 : parentAssetRuntime.getSound(path)) !== null && _b !== void 0 ? _b : null; }, [parentAssetRuntime, sounds]);
107
+ const assetRevision = useMemo(() => { var _a; return `${(_a = parentAssetRuntime === null || parentAssetRuntime === void 0 ? void 0 : parentAssetRuntime.getAssetRevision()) !== null && _a !== void 0 ? _a : ""}::${Object.keys(textures).sort().join('|')}::${Object.keys(models).sort().join('|')}`; }, [models, parentAssetRuntime, textures]);
127
108
  const getObject = useCallback((id) => {
128
- var _a;
129
- return (_a = objectRefs.current[id]) !== null && _a !== void 0 ? _a : null;
130
- }, []);
131
- const getHandle = useCallback((id, kind) => {
132
109
  var _a, _b;
133
- return (_b = (_a = nodeHandles.current.get(id)) === null || _a === void 0 ? void 0 : _a.get(kind)) !== null && _b !== void 0 ? _b : null;
134
- }, []);
110
+ return (_b = (_a = objectRefs.current[id]) !== null && _a !== void 0 ? _a : parentAssetRuntime === null || parentAssetRuntime === void 0 ? void 0 : parentAssetRuntime.getObject(id)) !== null && _b !== void 0 ? _b : null;
111
+ }, [parentAssetRuntime]);
112
+ const getHandle = useCallback((id, kind) => {
113
+ var _a, _b, _c;
114
+ return (_c = (_b = (_a = nodeHandles.current.get(id)) === null || _a === void 0 ? void 0 : _a.get(kind)) !== null && _b !== void 0 ? _b : parentAssetRuntime === null || parentAssetRuntime === void 0 ? void 0 : parentAssetRuntime.getHandle(id, kind)) !== null && _c !== void 0 ? _c : null;
115
+ }, [parentAssetRuntime]);
135
116
  const getNode = useCallback((nodeId) => {
136
117
  var _a;
137
118
  return (_a = resolvedStore.getState().nodesById[nodeId]) !== null && _a !== void 0 ? _a : null;
@@ -139,6 +120,7 @@ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSel
139
120
  const registerHandle = useCallback((id, kind, handle) => {
140
121
  const current = nodeHandles.current.get(id);
141
122
  if (handle == null) {
123
+ parentAssetRuntime === null || parentAssetRuntime === void 0 ? void 0 : parentAssetRuntime.registerHandle(id, kind, null);
142
124
  if (!current)
143
125
  return;
144
126
  current.delete(kind);
@@ -149,10 +131,34 @@ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSel
149
131
  }
150
132
  if (current) {
151
133
  current.set(kind, handle);
134
+ parentAssetRuntime === null || parentAssetRuntime === void 0 ? void 0 : parentAssetRuntime.registerHandle(id, kind, handle);
152
135
  return;
153
136
  }
154
137
  nodeHandles.current.set(id, new Map([[kind, handle]]));
155
- }, []);
138
+ parentAssetRuntime === null || parentAssetRuntime === void 0 ? void 0 : parentAssetRuntime.registerHandle(id, kind, handle);
139
+ }, [parentAssetRuntime]);
140
+ const registerObject = useCallback((id, obj) => {
141
+ if (obj) {
142
+ objectRefs.current[id] = obj;
143
+ }
144
+ else {
145
+ delete objectRefs.current[id];
146
+ }
147
+ parentAssetRuntime === null || parentAssetRuntime === void 0 ? void 0 : parentAssetRuntime.registerObject(id, obj);
148
+ }, [parentAssetRuntime]);
149
+ const registerModel = useCallback((path, model) => {
150
+ parentAssetRuntime === null || parentAssetRuntime === void 0 ? void 0 : parentAssetRuntime.registerModel(path, model);
151
+ setModels(prev => prev[path] === model ? prev : Object.assign(Object.assign({}, prev), { [path]: model }));
152
+ }, [parentAssetRuntime]);
153
+ const registerTexture = useCallback((path, texture) => {
154
+ parentAssetRuntime === null || parentAssetRuntime === void 0 ? void 0 : parentAssetRuntime.registerTexture(path, texture);
155
+ setTextures(prev => prev[path] === texture ? prev : Object.assign(Object.assign({}, prev), { [path]: texture }));
156
+ }, [parentAssetRuntime]);
157
+ const registerSound = useCallback((path, sound) => {
158
+ parentAssetRuntime === null || parentAssetRuntime === void 0 ? void 0 : parentAssetRuntime.registerSound(path, sound);
159
+ soundManager.setBuffer(path, sound);
160
+ setSounds(prev => prev[path] === sound ? prev : Object.assign(Object.assign({}, prev), { [path]: sound }));
161
+ }, [parentAssetRuntime]);
156
162
  const sceneValue = useMemo(() => ({
157
163
  get root() {
158
164
  var _a;
@@ -182,27 +188,21 @@ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSel
182
188
  void precompileModel(model, renderer, camera).then(() => {
183
189
  if (injectedModelVersions.current[path] !== version)
184
190
  return;
185
- setInjectedModels(prev => (Object.assign(Object.assign({}, prev), { [path]: model })));
191
+ registerModel(path, model);
186
192
  });
187
193
  },
188
- addTexture: (path, texture) => setInjectedTextures(prev => (Object.assign(Object.assign({}, prev), { [path]: texture }))),
189
- addSound: (path, sound) => {
190
- soundManager.setBuffer(path, sound);
191
- setInjectedSounds(prev => (Object.assign(Object.assign({}, prev), { [path]: sound })));
192
- },
193
- }), [basePath, camera, editMode, getHandle, getModel, getNode, getObject, renderer, resolvedStore, rootId]);
194
+ addTexture: registerTexture,
195
+ addSound: registerSound,
196
+ }), [basePath, camera, editMode, getHandle, getModel, getNode, getObject, registerModel, registerSound, registerTexture, renderer, resolvedStore, rootId]);
194
197
  useImperativeHandle(ref, () => sceneValue, [sceneValue]);
195
- const registerRef = useCallback((id, obj) => {
196
- objectRefs.current[id] = obj;
197
- }, []);
198
198
  useEffect(() => {
199
199
  if (usesOwnedStore && data) {
200
200
  resolvedStore.getState().replacePrefab(data);
201
201
  }
202
202
  }, [data, resolvedStore, usesOwnedStore]);
203
203
  useEffect(() => {
204
- const loadAsset = (file, loaded, injected, failed, loader) => {
205
- if (loaded[file] || injected[file] || loading.current.has(file) || failed.has(file))
204
+ const loadAsset = (file, loaded, failed, loader) => {
205
+ if (loaded[file] || loading.current.has(file) || failed.has(file))
206
206
  return;
207
207
  loading.current.add(file);
208
208
  void loader(withBasePath(basePath, file)).then(result => {
@@ -218,45 +218,63 @@ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSel
218
218
  const type = entry.slice(0, separator);
219
219
  const file = entry.slice(separator + 1);
220
220
  if (type === 'model') {
221
- loadAsset(file, models, injectedModels, failedModels.current, path => loadModel(path).then((result) => __awaiter(void 0, void 0, void 0, function* () {
221
+ const inheritedModel = models[file] ? null : parentAssetRuntime === null || parentAssetRuntime === void 0 ? void 0 : parentAssetRuntime.getModel(file);
222
+ if (inheritedModel) {
223
+ registerModel(file, inheritedModel);
224
+ return;
225
+ }
226
+ loadAsset(file, models, failedModels.current, path => loadModel(path).then((result) => __awaiter(void 0, void 0, void 0, function* () {
222
227
  const loadedModel = result.model;
223
228
  if (result.success && loadedModel) {
224
229
  yield precompileModel(loadedModel, renderer, camera);
225
- setModels(currentModels => (Object.assign(Object.assign({}, currentModels), { [file]: loadedModel })));
230
+ registerModel(file, loadedModel);
226
231
  }
227
232
  return result;
228
233
  })));
229
234
  }
230
235
  else if (type === 'texture') {
231
- loadAsset(file, textures, injectedTextures, failedTextures.current, path => loadTexture(path).then(result => {
236
+ const inheritedTexture = textures[file] ? null : parentAssetRuntime === null || parentAssetRuntime === void 0 ? void 0 : parentAssetRuntime.getTexture(file);
237
+ if (inheritedTexture) {
238
+ registerTexture(file, inheritedTexture);
239
+ return;
240
+ }
241
+ loadAsset(file, textures, failedTextures.current, path => loadTexture(path).then(result => {
232
242
  const loadedTexture = result.texture;
233
243
  if (result.success && loadedTexture) {
234
- setTextures(currentTextures => (Object.assign(Object.assign({}, currentTextures), { [file]: loadedTexture })));
244
+ registerTexture(file, loadedTexture);
235
245
  }
236
246
  return result;
237
247
  }));
238
248
  }
239
249
  else if (type === 'sound') {
240
- loadAsset(file, sounds, injectedSounds, failedSounds.current, path => loadSound(path).then(result => {
250
+ const inheritedSound = sounds[file] ? null : parentAssetRuntime === null || parentAssetRuntime === void 0 ? void 0 : parentAssetRuntime.getSound(file);
251
+ if (inheritedSound) {
252
+ registerSound(file, inheritedSound);
253
+ return;
254
+ }
255
+ loadAsset(file, sounds, failedSounds.current, path => loadSound(path).then(result => {
241
256
  const loadedSound = result.sound;
242
257
  if (result.success && loadedSound) {
243
- soundManager.setBuffer(file, loadedSound);
244
- setSounds(currentSounds => (Object.assign(Object.assign({}, currentSounds), { [file]: loadedSound })));
258
+ registerSound(file, loadedSound);
245
259
  }
246
260
  return result;
247
261
  }));
248
262
  }
249
263
  });
250
- }, [assetRefCounts, basePath, camera, injectedModels, injectedSounds, injectedTextures, models, renderer, sounds, textures]);
264
+ }, [assetRefCounts, basePath, camera, models, parentAssetRuntime, registerModel, registerSound, registerTexture, renderer, sounds, textures]);
251
265
  const assetRuntime = useMemo(() => ({
266
+ registerObject,
252
267
  registerHandle,
268
+ registerModel,
269
+ registerTexture,
270
+ registerSound,
253
271
  getHandle,
254
272
  getObject,
255
273
  getModel,
256
274
  getTexture,
257
275
  getSound,
258
276
  getAssetRevision: () => assetRevision,
259
- }), [registerHandle, getHandle, getObject, getModel, getTexture, getSound, assetRevision]);
277
+ }), [registerObject, registerHandle, registerModel, registerTexture, registerSound, getHandle, getObject, getModel, getTexture, getSound, assetRevision]);
260
278
  const handleNodeClick = useCallback((event, nodeId, fallbackObject) => {
261
279
  const node = resolvedStore.getState().nodesById[nodeId];
262
280
  if (!node)
@@ -265,9 +283,10 @@ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSel
265
283
  emitNodePointerEvent(clickEventName, event, nodeId, node, fallbackObject);
266
284
  onClick === null || onClick === void 0 ? void 0 : onClick(event, node);
267
285
  }, [onClick, resolvedStore]);
268
- const content = (_jsxs(GameInstanceProvider, { models: availableModels, selectedId: selectedId, editMode: editMode, onSelect: editMode ? onSelect : undefined, onClick: editMode ? undefined : handleNodeClick, registerRef: registerRef, children: [_jsx(StoreRootNode, { selectedId: selectedId, onSelect: editMode ? onSelect : undefined, onClick: editMode ? undefined : handleNodeClick, onEditNodeClick: editMode ? onEditNodeClick : undefined, registerRef: registerRef, loadedModels: availableModels, editMode: editMode, parentMatrix: IDENTITY, basePath: basePath }), children] }));
269
- const runtimeContent = (_jsx(SceneContext.Provider, { value: sceneValue, children: _jsx(AssetRuntimeContext.Provider, { value: assetRuntime, children: content }) }));
270
- return _jsx(PrefabStoreProvider, { store: resolvedStore, children: runtimeContent });
286
+ const content = (_jsxs(GameInstanceProvider, { models: models, selectedId: selectedId, editMode: editMode, onSelect: editMode ? onSelect : undefined, onClick: editMode ? undefined : handleNodeClick, registerRef: registerObject, children: [_jsx(StoreRootNode, { selectedId: selectedId, onSelect: editMode ? onSelect : undefined, onClick: editMode ? undefined : handleNodeClick, onEditNodeClick: editMode ? onEditNodeClick : undefined, registerRef: registerObject, loadedModels: models, editMode: editMode, parentMatrix: IDENTITY, basePath: basePath }), children] }));
287
+ const runtimeContent = parentAssetRuntime ? content : (_jsx(AssetRuntimeContext.Provider, { value: assetRuntime, children: content }));
288
+ const sceneContent = parentScene ? runtimeContent : (_jsx(SceneContext.Provider, { value: sceneValue, children: runtimeContent }));
289
+ return _jsx(PrefabStoreProvider, { store: resolvedStore, children: sceneContent });
271
290
  });
272
291
  function StoreRootNode(props) {
273
292
  const rootId = usePrefabRootId();