react-three-game 0.0.41 → 0.0.44

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 (65) hide show
  1. package/README.md +0 -17
  2. package/dist/helpers/SoundManager.d.ts +35 -0
  3. package/dist/helpers/SoundManager.js +93 -0
  4. package/dist/helpers/index.d.ts +35 -0
  5. package/dist/helpers/index.js +44 -0
  6. package/dist/index.d.ts +14 -0
  7. package/dist/index.js +14 -0
  8. package/dist/shared/GameCanvas.d.ts +9 -0
  9. package/dist/shared/GameCanvas.js +47 -0
  10. package/dist/tools/assetviewer/page.d.ts +35 -0
  11. package/dist/tools/assetviewer/page.js +166 -0
  12. package/dist/tools/dragdrop/DragDropLoader.d.ts +9 -0
  13. package/dist/tools/dragdrop/DragDropLoader.js +78 -0
  14. package/dist/tools/dragdrop/modelLoader.d.ts +7 -0
  15. package/dist/tools/dragdrop/modelLoader.js +52 -0
  16. package/dist/tools/dragdrop/page.d.ts +1 -0
  17. package/dist/tools/dragdrop/page.js +11 -0
  18. package/dist/tools/prefabeditor/EditorContext.d.ts +11 -0
  19. package/dist/tools/prefabeditor/EditorContext.js +9 -0
  20. package/dist/tools/prefabeditor/EditorTree.d.ts +12 -0
  21. package/dist/tools/prefabeditor/EditorTree.js +150 -0
  22. package/dist/tools/prefabeditor/EditorUI.d.ts +14 -0
  23. package/dist/tools/prefabeditor/EditorUI.js +71 -0
  24. package/dist/tools/prefabeditor/EventSystem.d.ts +7 -0
  25. package/dist/tools/prefabeditor/EventSystem.js +23 -0
  26. package/dist/tools/prefabeditor/InstanceProvider.d.ts +30 -0
  27. package/dist/tools/prefabeditor/InstanceProvider.js +254 -0
  28. package/dist/tools/prefabeditor/PrefabEditor.d.ts +16 -0
  29. package/dist/tools/prefabeditor/PrefabEditor.js +140 -0
  30. package/dist/tools/prefabeditor/PrefabRoot.d.ts +28 -0
  31. package/dist/tools/prefabeditor/PrefabRoot.js +293 -0
  32. package/dist/tools/prefabeditor/components/ComponentRegistry.d.ts +18 -0
  33. package/dist/tools/prefabeditor/components/ComponentRegistry.js +13 -0
  34. package/dist/tools/prefabeditor/components/DirectionalLightComponent.d.ts +3 -0
  35. package/dist/tools/prefabeditor/components/DirectionalLightComponent.js +78 -0
  36. package/dist/tools/prefabeditor/components/GeometryComponent.d.ts +3 -0
  37. package/dist/tools/prefabeditor/components/GeometryComponent.js +66 -0
  38. package/dist/tools/prefabeditor/components/Input.d.ts +20 -0
  39. package/dist/tools/prefabeditor/components/Input.js +129 -0
  40. package/dist/tools/prefabeditor/components/MaterialComponent.d.ts +3 -0
  41. package/dist/tools/prefabeditor/components/MaterialComponent.js +100 -0
  42. package/dist/tools/prefabeditor/components/ModelComponent.d.ts +3 -0
  43. package/dist/tools/prefabeditor/components/ModelComponent.js +57 -0
  44. package/dist/tools/prefabeditor/components/PhysicsComponent.d.ts +10 -0
  45. package/dist/tools/prefabeditor/components/PhysicsComponent.js +33 -0
  46. package/dist/tools/prefabeditor/components/SpotLightComponent.d.ts +3 -0
  47. package/dist/tools/prefabeditor/components/SpotLightComponent.js +49 -0
  48. package/dist/tools/prefabeditor/components/TransformComponent.d.ts +3 -0
  49. package/dist/tools/prefabeditor/components/TransformComponent.js +42 -0
  50. package/dist/tools/prefabeditor/components/index.d.ts +2 -0
  51. package/dist/tools/prefabeditor/components/index.js +16 -0
  52. package/dist/tools/prefabeditor/page.d.ts +1 -0
  53. package/dist/tools/prefabeditor/page.js +5 -0
  54. package/dist/tools/prefabeditor/styles.d.ts +1809 -0
  55. package/dist/tools/prefabeditor/styles.js +167 -0
  56. package/dist/tools/prefabeditor/types.d.ts +19 -0
  57. package/dist/tools/prefabeditor/types.js +1 -0
  58. package/dist/tools/prefabeditor/utils.d.ts +26 -0
  59. package/dist/tools/prefabeditor/utils.js +131 -0
  60. package/package.json +2 -4
  61. package/src/shared/GameCanvas.tsx +1 -1
  62. package/src/tools/assetviewer/page.tsx +1 -3
  63. package/.claude/settings.local.json +0 -9
  64. package/dist/index.umd.js +0 -438
  65. package/vite.config.ts +0 -34
@@ -0,0 +1,254 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import React, { createContext, useContext, useMemo, useRef, useState, useEffect, useCallback } from "react";
3
+ import { Merged, useHelper } from '@react-three/drei';
4
+ import { InstancedRigidBodies } from "@react-three/rapier";
5
+ import { Mesh, Matrix4, Vector3, Quaternion, Euler, BoxHelper } from "three";
6
+ // Helper functions for comparison
7
+ function arrayEquals(a, b) {
8
+ if (a === b)
9
+ return true;
10
+ if (a.length !== b.length)
11
+ return false;
12
+ for (let i = 0; i < a.length; i++) {
13
+ if (a[i] !== b[i])
14
+ return false;
15
+ }
16
+ return true;
17
+ }
18
+ function instanceEquals(a, b) {
19
+ var _a, _b;
20
+ return a.id === b.id &&
21
+ a.meshPath === b.meshPath &&
22
+ arrayEquals(a.position, b.position) &&
23
+ arrayEquals(a.rotation, b.rotation) &&
24
+ arrayEquals(a.scale, b.scale) &&
25
+ ((_a = a.physics) === null || _a === void 0 ? void 0 : _a.type) === ((_b = b.physics) === null || _b === void 0 ? void 0 : _b.type);
26
+ }
27
+ const GameInstanceContext = createContext(null);
28
+ export function GameInstanceProvider({ children, models, onSelect, registerRef, selectedId, editMode }) {
29
+ const [instances, setInstances] = useState([]);
30
+ const addInstance = useCallback((instance) => {
31
+ setInstances(prev => {
32
+ const idx = prev.findIndex(i => i.id === instance.id);
33
+ if (idx !== -1) {
34
+ // Update existing if changed
35
+ if (instanceEquals(prev[idx], instance)) {
36
+ return prev;
37
+ }
38
+ const copy = [...prev];
39
+ copy[idx] = instance;
40
+ return copy;
41
+ }
42
+ // Add new
43
+ return [...prev, instance];
44
+ });
45
+ }, []);
46
+ const removeInstance = useCallback((id) => {
47
+ setInstances(prev => {
48
+ if (!prev.find(i => i.id === id))
49
+ return prev;
50
+ return prev.filter(i => i.id !== id);
51
+ });
52
+ }, []);
53
+ const hasInstance = useCallback((id) => {
54
+ return instances.some(i => i.id === id);
55
+ }, [instances]);
56
+ // Flatten all model meshes once (models → flat mesh parts)
57
+ // Note: Geometry is cloned with baked transforms for instancing
58
+ const { flatMeshes, modelParts } = useMemo(() => {
59
+ const flatMeshes = {};
60
+ const modelParts = {};
61
+ Object.entries(models).forEach(([modelKey, model]) => {
62
+ model.updateWorldMatrix(false, true);
63
+ const rootInverse = new Matrix4().copy(model.matrixWorld).invert();
64
+ let partIndex = 0;
65
+ model.traverse((obj) => {
66
+ if (obj.isMesh) {
67
+ // Clone geometry and bake relative transform
68
+ const geom = obj.geometry.clone();
69
+ geom.applyMatrix4(obj.matrixWorld.clone().premultiply(rootInverse));
70
+ const partKey = `${modelKey}__${partIndex}`;
71
+ flatMeshes[partKey] = new Mesh(geom, obj.material);
72
+ partIndex++;
73
+ }
74
+ });
75
+ modelParts[modelKey] = partIndex;
76
+ });
77
+ return { flatMeshes, modelParts };
78
+ }, [models]);
79
+ // Cleanup geometries when models change
80
+ useEffect(() => {
81
+ return () => {
82
+ Object.values(flatMeshes).forEach(mesh => mesh.geometry.dispose());
83
+ };
84
+ }, [flatMeshes]);
85
+ // Group instances by meshPath + physics type for batch rendering
86
+ const grouped = useMemo(() => {
87
+ var _a;
88
+ const groups = {};
89
+ for (const inst of instances) {
90
+ const type = ((_a = inst.physics) === null || _a === void 0 ? void 0 : _a.type) || 'none';
91
+ const key = `${inst.meshPath}__${type}`;
92
+ if (!groups[key])
93
+ groups[key] = { physicsType: type, instances: [] };
94
+ groups[key].instances.push(inst);
95
+ }
96
+ return groups;
97
+ }, [instances]);
98
+ return (_jsxs(GameInstanceContext.Provider, { value: {
99
+ addInstance,
100
+ removeInstance,
101
+ instances,
102
+ meshes: flatMeshes,
103
+ modelParts,
104
+ hasInstance
105
+ }, children: [children, Object.entries(grouped).map(([key, group]) => {
106
+ if (group.physicsType === 'none')
107
+ return null;
108
+ const modelKey = group.instances[0].meshPath;
109
+ const partCount = modelParts[modelKey] || 0;
110
+ if (partCount === 0)
111
+ return null;
112
+ return (_jsx(InstancedRigidGroup, { group: group, modelKey: modelKey, partCount: partCount, flatMeshes: flatMeshes, onSelect: onSelect, editMode: editMode }, key));
113
+ }), Object.entries(grouped).map(([key, group]) => {
114
+ if (group.physicsType !== 'none')
115
+ return null;
116
+ const modelKey = group.instances[0].meshPath;
117
+ const partCount = modelParts[modelKey] || 0;
118
+ if (partCount === 0)
119
+ return null;
120
+ // Create mesh subset for this specific model
121
+ const meshesForModel = {};
122
+ for (let i = 0; i < partCount; i++) {
123
+ const partKey = `${modelKey}__${i}`;
124
+ meshesForModel[partKey] = flatMeshes[partKey];
125
+ }
126
+ return (_jsx(Merged, { meshes: meshesForModel, castShadow: true, receiveShadow: true, children: (instancesMap) => (_jsx(NonPhysicsInstancedGroup, { modelKey: modelKey, group: group, partCount: partCount, instancesMap: instancesMap, onSelect: onSelect, registerRef: registerRef, selectedId: selectedId, editMode: editMode })) }, key));
127
+ })] }));
128
+ }
129
+ // Render physics-enabled instances using InstancedRigidBodies
130
+ function InstancedRigidGroup({ group, modelKey, partCount, flatMeshes, onSelect, editMode }) {
131
+ const meshRefs = useRef([]);
132
+ const rigidBodiesRef = useRef(null);
133
+ const instances = useMemo(() => group.instances.map(inst => ({
134
+ key: inst.id,
135
+ position: inst.position,
136
+ rotation: inst.rotation,
137
+ scale: inst.scale,
138
+ })), [group.instances]);
139
+ // Apply scale to visual meshes (InstancedRigidBodies only scales colliders, not visuals)
140
+ useEffect(() => {
141
+ const matrix = new Matrix4();
142
+ const pos = new Vector3();
143
+ const quat = new Quaternion();
144
+ const euler = new Euler();
145
+ const scl = new Vector3();
146
+ meshRefs.current.forEach(mesh => {
147
+ if (!mesh)
148
+ return;
149
+ group.instances.forEach((inst, i) => {
150
+ pos.set(...inst.position);
151
+ euler.set(...inst.rotation);
152
+ quat.setFromEuler(euler);
153
+ scl.set(...inst.scale);
154
+ matrix.compose(pos, quat, scl);
155
+ mesh.setMatrixAt(i, matrix);
156
+ });
157
+ mesh.instanceMatrix.needsUpdate = true;
158
+ });
159
+ // Update rigid body positions when instances change
160
+ if (rigidBodiesRef.current) {
161
+ try {
162
+ group.instances.forEach((inst, i) => {
163
+ var _a;
164
+ const body = (_a = rigidBodiesRef.current) === null || _a === void 0 ? void 0 : _a.at(i);
165
+ if (body && body.setTranslation && body.setRotation) {
166
+ pos.set(...inst.position);
167
+ euler.set(...inst.rotation);
168
+ quat.setFromEuler(euler);
169
+ body.setTranslation(pos, false);
170
+ body.setRotation(quat, false);
171
+ }
172
+ });
173
+ }
174
+ catch (error) {
175
+ // Ignore errors when switching between instanced/non-instanced states
176
+ console.warn('Failed to update rigidbody positions:', error);
177
+ }
178
+ }
179
+ }, [group.instances]);
180
+ const colliders = group.physicsType === 'fixed' ? 'trimesh' : 'hull';
181
+ // Handle click on instanced mesh in edit mode
182
+ const handleClick = (e) => {
183
+ if (!editMode || !onSelect)
184
+ return;
185
+ e.stopPropagation();
186
+ // Get the instance index from the intersection
187
+ const instanceId = e.instanceId;
188
+ if (instanceId !== undefined && group.instances[instanceId]) {
189
+ onSelect(group.instances[instanceId].id);
190
+ }
191
+ };
192
+ // Add key to force remount when instance count changes significantly (helps with cleanup)
193
+ const rigidBodyKey = `rb_${modelKey}_${group.physicsType}_${group.instances.length}`;
194
+ return (_jsx(InstancedRigidBodies, { ref: rigidBodiesRef, instances: instances, colliders: colliders, type: group.physicsType, children: Array.from({ length: partCount }).map((_, i) => {
195
+ const mesh = flatMeshes[`${modelKey}__${i}`];
196
+ if (!mesh)
197
+ return null;
198
+ return (_jsx("instancedMesh", { ref: el => { meshRefs.current[i] = el; }, args: [mesh.geometry, mesh.material, group.instances.length], castShadow: true, receiveShadow: true, frustumCulled: false, onClick: editMode ? handleClick : undefined }, i));
199
+ }) }, rigidBodyKey));
200
+ }
201
+ // Render non-physics instances using Merged (instancing without rigid bodies)
202
+ function NonPhysicsInstancedGroup({ modelKey, group, partCount, instancesMap, onSelect, registerRef, selectedId, editMode }) {
203
+ // Pre-compute which Instance components exist for this model
204
+ const InstanceComponents = useMemo(() => Array.from({ length: partCount }, (_, i) => instancesMap[`${modelKey}__${i}`]).filter(Boolean), [instancesMap, modelKey, partCount]);
205
+ return (_jsx(_Fragment, { children: group.instances.map(inst => (_jsx(InstanceGroupItem, { instance: inst, InstanceComponents: InstanceComponents, onSelect: onSelect, registerRef: registerRef, selectedId: selectedId, editMode: editMode }, inst.id))) }));
206
+ }
207
+ // Individual instance item with its own click state
208
+ function InstanceGroupItem({ instance, InstanceComponents, onSelect, registerRef, selectedId, editMode }) {
209
+ const clickValid = useRef(false);
210
+ const groupRef = useRef(null);
211
+ const isSelected = selectedId === instance.id;
212
+ // Use BoxHelper when object is selected in edit mode
213
+ useHelper(editMode && isSelected ? groupRef : null, BoxHelper, 'cyan');
214
+ useEffect(() => {
215
+ registerRef === null || registerRef === void 0 ? void 0 : registerRef(instance.id, groupRef.current);
216
+ }, [instance.id, registerRef]);
217
+ return (_jsx("group", { ref: groupRef, position: instance.position, rotation: instance.rotation, scale: instance.scale, onPointerDown: (e) => { e.stopPropagation(); clickValid.current = true; }, onPointerMove: () => { clickValid.current = false; }, onPointerUp: (e) => {
218
+ if (clickValid.current) {
219
+ e.stopPropagation();
220
+ onSelect === null || onSelect === void 0 ? void 0 : onSelect(instance.id);
221
+ }
222
+ clickValid.current = false;
223
+ }, children: InstanceComponents.map((Instance, i) => _jsx(Instance, {}, i)) }));
224
+ }
225
+ // Hook to check if an instance exists
226
+ export function useInstanceCheck(id) {
227
+ var _a;
228
+ const ctx = useContext(GameInstanceContext);
229
+ return (_a = ctx === null || ctx === void 0 ? void 0 : ctx.hasInstance(id)) !== null && _a !== void 0 ? _a : false;
230
+ }
231
+ // GameInstance component: registers an instance for batch rendering (renders nothing itself)
232
+ export const GameInstance = React.forwardRef(({ id, modelUrl, position, rotation, scale, physics = undefined, }, ref) => {
233
+ const ctx = useContext(GameInstanceContext);
234
+ const addInstance = ctx === null || ctx === void 0 ? void 0 : ctx.addInstance;
235
+ const removeInstance = ctx === null || ctx === void 0 ? void 0 : ctx.removeInstance;
236
+ const instance = useMemo(() => ({
237
+ id,
238
+ meshPath: modelUrl,
239
+ position,
240
+ rotation,
241
+ scale,
242
+ physics,
243
+ }), [id, modelUrl, JSON.stringify(position), JSON.stringify(rotation), JSON.stringify(scale), JSON.stringify(physics)]);
244
+ useEffect(() => {
245
+ if (!addInstance || !removeInstance)
246
+ return;
247
+ addInstance(instance);
248
+ return () => {
249
+ removeInstance(instance.id);
250
+ };
251
+ }, [addInstance, removeInstance, instance]);
252
+ // No visual rendering - provider handles all instanced visuals
253
+ return null;
254
+ });
@@ -0,0 +1,16 @@
1
+ import { Prefab } from "./types";
2
+ import { PrefabRootRef } from "./PrefabRoot";
3
+ export interface PrefabEditorRef {
4
+ screenshot: () => void;
5
+ exportGLB: () => void;
6
+ prefab: Prefab;
7
+ setPrefab: (prefab: Prefab) => void;
8
+ rootRef: React.RefObject<PrefabRootRef | null>;
9
+ }
10
+ declare const PrefabEditor: import("react").ForwardRefExoticComponent<{
11
+ basePath?: string;
12
+ initialPrefab?: Prefab;
13
+ onPrefabChange?: (prefab: Prefab) => void;
14
+ children?: React.ReactNode;
15
+ } & import("react").RefAttributes<PrefabEditorRef>>;
16
+ export default PrefabEditor;
@@ -0,0 +1,140 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import GameCanvas from "../../shared/GameCanvas";
3
+ import { useState, useRef, useEffect, forwardRef, useImperativeHandle } from "react";
4
+ import PrefabRoot from "./PrefabRoot";
5
+ import { Physics } from "@react-three/rapier";
6
+ import EditorUI from "./EditorUI";
7
+ import { base, toolbar } from "./styles";
8
+ import { EditorContext } from "./EditorContext";
9
+ import { GLTFExporter } from "three/examples/jsm/exporters/GLTFExporter.js";
10
+ const DEFAULT_PREFAB = {
11
+ id: "prefab-default",
12
+ name: "New Prefab",
13
+ root: {
14
+ id: "root",
15
+ components: {
16
+ transform: {
17
+ type: "Transform",
18
+ properties: { position: [0, 0, 0], rotation: [0, 0, 0], scale: [1, 1, 1] }
19
+ }
20
+ }
21
+ }
22
+ };
23
+ const PrefabEditor = forwardRef(({ basePath, initialPrefab, onPrefabChange, children }, ref) => {
24
+ const [editMode, setEditMode] = useState(true);
25
+ const [loadedPrefab, setLoadedPrefab] = useState(initialPrefab !== null && initialPrefab !== void 0 ? initialPrefab : DEFAULT_PREFAB);
26
+ const [selectedId, setSelectedId] = useState(null);
27
+ const [transformMode, setTransformMode] = useState("translate");
28
+ const [snapResolution, setSnapResolution] = useState(0);
29
+ const [history, setHistory] = useState([loadedPrefab]);
30
+ const [historyIndex, setHistoryIndex] = useState(0);
31
+ const throttleRef = useRef(null);
32
+ const lastDataRef = useRef(JSON.stringify(loadedPrefab));
33
+ const prefabRootRef = useRef(null);
34
+ const canvasRef = useRef(null);
35
+ useEffect(() => {
36
+ if (initialPrefab)
37
+ setLoadedPrefab(initialPrefab);
38
+ }, [initialPrefab]);
39
+ const updatePrefab = (newPrefab) => {
40
+ setLoadedPrefab(newPrefab);
41
+ const resolved = typeof newPrefab === 'function' ? newPrefab(loadedPrefab) : newPrefab;
42
+ onPrefabChange === null || onPrefabChange === void 0 ? void 0 : onPrefabChange(resolved);
43
+ };
44
+ const applyHistory = (index) => {
45
+ setHistoryIndex(index);
46
+ lastDataRef.current = JSON.stringify(history[index]);
47
+ setLoadedPrefab(history[index]);
48
+ onPrefabChange === null || onPrefabChange === void 0 ? void 0 : onPrefabChange(history[index]);
49
+ };
50
+ const undo = () => historyIndex > 0 && applyHistory(historyIndex - 1);
51
+ const redo = () => historyIndex < history.length - 1 && applyHistory(historyIndex + 1);
52
+ useEffect(() => {
53
+ const handleKeyDown = (e) => {
54
+ if (!(e.ctrlKey || e.metaKey))
55
+ return;
56
+ if (e.key === 'z' && !e.shiftKey) {
57
+ e.preventDefault();
58
+ undo();
59
+ }
60
+ else if ((e.shiftKey && e.key === 'z') || e.key === 'y') {
61
+ e.preventDefault();
62
+ redo();
63
+ }
64
+ };
65
+ window.addEventListener('keydown', handleKeyDown);
66
+ return () => window.removeEventListener('keydown', handleKeyDown);
67
+ }, [historyIndex, history]);
68
+ useEffect(() => {
69
+ const currentStr = JSON.stringify(loadedPrefab);
70
+ if (currentStr === lastDataRef.current)
71
+ return;
72
+ if (throttleRef.current)
73
+ clearTimeout(throttleRef.current);
74
+ throttleRef.current = setTimeout(() => {
75
+ lastDataRef.current = currentStr;
76
+ setHistory(prev => {
77
+ const newHistory = [...prev.slice(0, historyIndex + 1), loadedPrefab];
78
+ return newHistory.length > 50 ? newHistory.slice(1) : newHistory;
79
+ });
80
+ setHistoryIndex(prev => Math.min(prev + 1, 49));
81
+ }, 500);
82
+ return () => { if (throttleRef.current)
83
+ clearTimeout(throttleRef.current); };
84
+ }, [loadedPrefab]);
85
+ const handleScreenshot = () => {
86
+ const canvas = canvasRef.current;
87
+ if (!canvas)
88
+ return;
89
+ canvas.toBlob((blob) => {
90
+ if (!blob)
91
+ return;
92
+ const url = URL.createObjectURL(blob);
93
+ const a = document.createElement('a');
94
+ a.href = url;
95
+ a.download = `${loadedPrefab.name || 'screenshot'}.png`;
96
+ a.click();
97
+ URL.revokeObjectURL(url);
98
+ });
99
+ };
100
+ const handleExportGLB = () => {
101
+ var _a;
102
+ const sceneRoot = (_a = prefabRootRef.current) === null || _a === void 0 ? void 0 : _a.root;
103
+ if (!sceneRoot)
104
+ return;
105
+ const exporter = new GLTFExporter();
106
+ exporter.parse(sceneRoot, (result) => {
107
+ const blob = new Blob([result], { type: 'application/octet-stream' });
108
+ const url = URL.createObjectURL(blob);
109
+ const a = document.createElement('a');
110
+ a.href = url;
111
+ a.download = `${loadedPrefab.name || 'scene'}.glb`;
112
+ a.click();
113
+ URL.revokeObjectURL(url);
114
+ }, (error) => {
115
+ console.error('Error exporting GLB:', error);
116
+ }, { binary: true });
117
+ };
118
+ useEffect(() => {
119
+ const canvas = document.querySelector('canvas');
120
+ if (canvas)
121
+ canvasRef.current = canvas;
122
+ }, []);
123
+ useImperativeHandle(ref, () => ({
124
+ screenshot: handleScreenshot,
125
+ exportGLB: handleExportGLB,
126
+ prefab: loadedPrefab,
127
+ setPrefab: setLoadedPrefab,
128
+ rootRef: prefabRootRef
129
+ }), [loadedPrefab]);
130
+ return _jsxs(EditorContext.Provider, { value: {
131
+ transformMode,
132
+ setTransformMode,
133
+ snapResolution,
134
+ setSnapResolution,
135
+ onScreenshot: handleScreenshot,
136
+ onExportGLB: handleExportGLB
137
+ }, children: [_jsx(GameCanvas, { children: _jsxs(Physics, { debug: editMode, paused: editMode, children: [_jsx("ambientLight", { intensity: 1.5 }), _jsx("gridHelper", { args: [10, 10], position: [0, -1, 0] }), _jsx(PrefabRoot, { ref: prefabRootRef, data: loadedPrefab, editMode: editMode, onPrefabChange: updatePrefab, selectedId: selectedId, onSelect: setSelectedId, basePath: basePath }), children] }) }), _jsx("div", { style: toolbar.panel, children: _jsx("button", { style: base.btn, onClick: () => setEditMode(!editMode), children: editMode ? "▶" : "⏸" }) }), editMode && _jsx(EditorUI, { prefabData: loadedPrefab, setPrefabData: updatePrefab, selectedId: selectedId, setSelectedId: setSelectedId, basePath: basePath, onUndo: undo, onRedo: redo, canUndo: historyIndex > 0, canRedo: historyIndex < history.length - 1 })] });
138
+ });
139
+ PrefabEditor.displayName = "PrefabEditor";
140
+ export default PrefabEditor;
@@ -0,0 +1,28 @@
1
+ import { Group, Matrix4, Object3D, Texture } from "three";
2
+ import { ThreeEvent } from "@react-three/fiber";
3
+ import { Prefab, GameObject as GameObjectType } from "./types";
4
+ export interface PrefabRootRef {
5
+ root: Group | null;
6
+ }
7
+ export declare const PrefabRoot: import("react").ForwardRefExoticComponent<{
8
+ editMode?: boolean;
9
+ data: Prefab;
10
+ onPrefabChange?: (data: Prefab) => void;
11
+ selectedId?: string | null;
12
+ onSelect?: (id: string | null) => void;
13
+ onClick?: (event: ThreeEvent<PointerEvent>, entity: GameObjectType) => void;
14
+ basePath?: string;
15
+ } & import("react").RefAttributes<PrefabRootRef>>;
16
+ export declare function GameObjectRenderer(props: RendererProps): import("react/jsx-runtime").JSX.Element | null;
17
+ interface RendererProps {
18
+ gameObject: GameObjectType;
19
+ selectedId?: string | null;
20
+ onSelect?: (id: string) => void;
21
+ onClick?: (event: ThreeEvent<PointerEvent>, entity: GameObjectType) => void;
22
+ registerRef: (id: string, obj: Object3D | null) => void;
23
+ loadedModels: Record<string, Object3D>;
24
+ loadedTextures: Record<string, Texture>;
25
+ editMode?: boolean;
26
+ parentMatrix?: Matrix4;
27
+ }
28
+ export default PrefabRoot;