react-three-game 0.0.37 → 0.0.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +5 -3
- package/dist/index.js +5 -5
- package/dist/tools/prefabeditor/EditorContext.d.ts +11 -0
- package/dist/tools/prefabeditor/EditorContext.js +9 -0
- package/dist/tools/prefabeditor/EditorTree.d.ts +1 -3
- package/dist/tools/prefabeditor/EditorTree.js +38 -3
- package/dist/tools/prefabeditor/EditorUI.d.ts +1 -5
- package/dist/tools/prefabeditor/EditorUI.js +4 -2
- package/dist/tools/prefabeditor/ExportHelper.d.ts +7 -0
- package/dist/tools/prefabeditor/ExportHelper.js +55 -0
- package/dist/tools/prefabeditor/PrefabEditor.d.ts +10 -2
- package/dist/tools/prefabeditor/PrefabEditor.js +60 -53
- package/dist/tools/prefabeditor/PrefabRoot.d.ts +4 -2
- package/dist/tools/prefabeditor/PrefabRoot.js +18 -41
- package/dist/tools/prefabeditor/components/Input.d.ts +2 -1
- package/dist/tools/prefabeditor/components/Input.js +9 -3
- package/dist/tools/prefabeditor/components/TransformComponent.js +11 -3
- package/dist/tools/prefabeditor/utils.d.ts +7 -1
- package/dist/tools/prefabeditor/utils.js +41 -0
- package/package.json +1 -1
- package/src/index.ts +12 -12
- package/src/tools/prefabeditor/EditorContext.tsx +20 -0
- package/src/tools/prefabeditor/EditorTree.tsx +83 -22
- package/src/tools/prefabeditor/EditorUI.tsx +2 -10
- package/src/tools/prefabeditor/PrefabEditor.tsx +79 -50
- package/src/tools/prefabeditor/PrefabRoot.tsx +26 -64
- package/src/tools/prefabeditor/components/Input.tsx +11 -3
- package/src/tools/prefabeditor/components/TransformComponent.tsx +25 -4
- package/src/tools/prefabeditor/utils.ts +43 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
export { default as GameCanvas } from './shared/GameCanvas';
|
|
2
2
|
export { default as PrefabEditor } from './tools/prefabeditor/PrefabEditor';
|
|
3
|
+
export type { PrefabEditorRef } from './tools/prefabeditor/PrefabEditor';
|
|
3
4
|
export { default as PrefabRoot } from './tools/prefabeditor/PrefabRoot';
|
|
4
|
-
export {
|
|
5
|
-
export { TextureListViewer, ModelListViewer, SoundListViewer, SharedCanvas, } from './tools/assetviewer/page';
|
|
5
|
+
export type { PrefabRootRef } from './tools/prefabeditor/PrefabRoot';
|
|
6
6
|
export { registerComponent } from './tools/prefabeditor/components/ComponentRegistry';
|
|
7
7
|
export type { Component } from './tools/prefabeditor/components/ComponentRegistry';
|
|
8
|
+
export type { Prefab, GameObject, ComponentData } from './tools/prefabeditor/types';
|
|
8
9
|
export * as editorStyles from './tools/prefabeditor/styles';
|
|
9
10
|
export * from './tools/prefabeditor/utils';
|
|
11
|
+
export { DragDropLoader } from './tools/dragdrop/DragDropLoader';
|
|
12
|
+
export { TextureListViewer, ModelListViewer, SoundListViewer, SharedCanvas, } from './tools/assetviewer/page';
|
|
10
13
|
export * from './helpers';
|
|
11
|
-
export type { Prefab, GameObject, ComponentData } from './tools/prefabeditor/types';
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
// Components
|
|
1
|
+
// Core Components
|
|
2
2
|
export { default as GameCanvas } from './shared/GameCanvas';
|
|
3
|
+
// Prefab Editor
|
|
3
4
|
export { default as PrefabEditor } from './tools/prefabeditor/PrefabEditor';
|
|
4
5
|
export { default as PrefabRoot } from './tools/prefabeditor/PrefabRoot';
|
|
5
|
-
export { DragDropLoader } from './tools/dragdrop/DragDropLoader';
|
|
6
|
-
export { TextureListViewer, ModelListViewer, SoundListViewer, SharedCanvas, } from './tools/assetviewer/page';
|
|
7
|
-
// Component Registry
|
|
8
6
|
export { registerComponent } from './tools/prefabeditor/components/ComponentRegistry';
|
|
9
|
-
// Editor Styles & Utils
|
|
10
7
|
export * as editorStyles from './tools/prefabeditor/styles';
|
|
11
8
|
export * from './tools/prefabeditor/utils';
|
|
9
|
+
// Asset Tools
|
|
10
|
+
export { DragDropLoader } from './tools/dragdrop/DragDropLoader';
|
|
11
|
+
export { TextureListViewer, ModelListViewer, SoundListViewer, SharedCanvas, } from './tools/assetviewer/page';
|
|
12
12
|
// Helpers
|
|
13
13
|
export * from './helpers';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
interface EditorContextType {
|
|
2
|
+
transformMode: "translate" | "rotate" | "scale";
|
|
3
|
+
setTransformMode: (mode: "translate" | "rotate" | "scale") => void;
|
|
4
|
+
snapResolution: number;
|
|
5
|
+
setSnapResolution: (resolution: number) => void;
|
|
6
|
+
onScreenshot?: () => void;
|
|
7
|
+
onExportGLB?: () => void;
|
|
8
|
+
}
|
|
9
|
+
export declare const EditorContext: import("react").Context<EditorContextType | null>;
|
|
10
|
+
export declare function useEditorContext(): EditorContextType;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
2
|
+
export const EditorContext = createContext(null);
|
|
3
|
+
export function useEditorContext() {
|
|
4
|
+
const context = useContext(EditorContext);
|
|
5
|
+
if (!context) {
|
|
6
|
+
throw new Error("useEditorContext must be used within EditorContext.Provider");
|
|
7
|
+
}
|
|
8
|
+
return context;
|
|
9
|
+
}
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { Dispatch, SetStateAction } from 'react';
|
|
2
2
|
import { Prefab } from "./types";
|
|
3
|
-
export default function EditorTree({ prefabData, setPrefabData, selectedId, setSelectedId,
|
|
3
|
+
export default function EditorTree({ prefabData, setPrefabData, selectedId, setSelectedId, onUndo, onRedo, canUndo, canRedo }: {
|
|
4
4
|
prefabData?: Prefab;
|
|
5
5
|
setPrefabData?: Dispatch<SetStateAction<Prefab>>;
|
|
6
6
|
selectedId: string | null;
|
|
7
7
|
setSelectedId: Dispatch<SetStateAction<string | null>>;
|
|
8
|
-
onSave?: () => void;
|
|
9
|
-
onLoad?: () => void;
|
|
10
8
|
onUndo?: () => void;
|
|
11
9
|
onRedo?: () => void;
|
|
12
10
|
canUndo?: boolean;
|
|
@@ -1,9 +1,19 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
1
10
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
11
|
import { useState } from 'react';
|
|
3
12
|
import { getComponent } from './components/ComponentRegistry';
|
|
4
13
|
import { base, tree, menu } from './styles';
|
|
5
|
-
import { findNode, findParent, deleteNode, cloneNode, updateNodeById } from './utils';
|
|
6
|
-
|
|
14
|
+
import { findNode, findParent, deleteNode, cloneNode, updateNodeById, loadJson, saveJson, regenerateIds } from './utils';
|
|
15
|
+
import { useEditorContext } from './EditorContext';
|
|
16
|
+
export default function EditorTree({ prefabData, setPrefabData, selectedId, setSelectedId, onUndo, onRedo, canUndo, canRedo }) {
|
|
7
17
|
const [contextMenu, setContextMenu] = useState(null);
|
|
8
18
|
const [draggedId, setDraggedId] = useState(null);
|
|
9
19
|
const [collapsedIds, setCollapsedIds] = useState(new Set());
|
|
@@ -111,5 +121,30 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
|
|
|
111
121
|
visibility: hasChildren ? 'visible' : 'hidden'
|
|
112
122
|
}, onClick: (e) => hasChildren && toggleCollapse(e, node.id), children: isCollapsed ? '▶' : '▼' }), !isRoot && _jsx("span", { style: { marginRight: 4, opacity: 0.4 }, children: "\u22EE\u22EE" }), _jsx("span", { style: { overflow: 'hidden', textOverflow: 'ellipsis' }, children: (_a = node.name) !== null && _a !== void 0 ? _a : node.id })] }), !isCollapsed && node.children && node.children.map(child => renderNode(child, depth + 1))] }, node.id));
|
|
113
123
|
};
|
|
114
|
-
return (_jsxs(_Fragment, { children: [_jsxs("div", { style: Object.assign(Object.assign({}, tree.panel), { width: collapsed ? 'auto' : 224 }), onClick: () => { setContextMenu(null); setFileMenuOpen(false); }, children: [_jsxs("div", { style: base.header, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 6, cursor: 'pointer' }, onClick: () => setCollapsed(!collapsed), children: [_jsx("span", { children: collapsed ? '▶' : '▼' }), _jsx("span", { children: "Scene" })] }), !collapsed && (_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 4 }, children: [_jsx("button", { style: Object.assign(Object.assign({}, base.btn), { padding: '2px 6px', fontSize: 10, opacity: canUndo ? 1 : 0.4 }), onClick: (e) => { e.stopPropagation(); onUndo === null || onUndo === void 0 ? void 0 : onUndo(); }, disabled: !canUndo, title: "Undo", children: "\u21B6" }), _jsx("button", { style: Object.assign(Object.assign({}, base.btn), { padding: '2px 6px', fontSize: 10, opacity: canRedo ? 1 : 0.4 }), onClick: (e) => { e.stopPropagation(); onRedo === null || onRedo === void 0 ? void 0 : onRedo(); }, disabled: !canRedo, title: "Redo", children: "\u21B7" }), _jsxs("div", { style: { position: 'relative' }, children: [_jsx("button", { style: Object.assign(Object.assign({}, base.btn), { padding: '2px 6px', fontSize: 10 }), onClick: (e) => { e.stopPropagation(); setFileMenuOpen(!fileMenuOpen); }, title: "File", children: "\u22EE" }), fileMenuOpen && (
|
|
124
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { style: Object.assign(Object.assign({}, tree.panel), { width: collapsed ? 'auto' : 224 }), onClick: () => { setContextMenu(null); setFileMenuOpen(false); }, children: [_jsxs("div", { style: base.header, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 6, cursor: 'pointer' }, onClick: () => setCollapsed(!collapsed), children: [_jsx("span", { children: collapsed ? '▶' : '▼' }), _jsx("span", { children: "Scene" })] }), !collapsed && (_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 4 }, children: [_jsx("button", { style: Object.assign(Object.assign({}, base.btn), { padding: '2px 6px', fontSize: 10, opacity: canUndo ? 1 : 0.4 }), onClick: (e) => { e.stopPropagation(); onUndo === null || onUndo === void 0 ? void 0 : onUndo(); }, disabled: !canUndo, title: "Undo", children: "\u21B6" }), _jsx("button", { style: Object.assign(Object.assign({}, base.btn), { padding: '2px 6px', fontSize: 10, opacity: canRedo ? 1 : 0.4 }), onClick: (e) => { e.stopPropagation(); onRedo === null || onRedo === void 0 ? void 0 : onRedo(); }, disabled: !canRedo, title: "Redo", children: "\u21B7" }), _jsxs("div", { style: { position: 'relative' }, children: [_jsx("button", { style: Object.assign(Object.assign({}, base.btn), { padding: '2px 6px', fontSize: 10 }), onClick: (e) => { e.stopPropagation(); setFileMenuOpen(!fileMenuOpen); }, title: "File", children: "\u22EE" }), fileMenuOpen && (_jsx(FileMenu, { prefabData: prefabData, setPrefabData: setPrefabData, onClose: () => setFileMenuOpen(false) }))] })] }))] }), !collapsed && _jsx("div", { style: tree.scroll, children: renderNode(prefabData.root) })] }), contextMenu && (_jsxs("div", { style: Object.assign(Object.assign({}, menu.container), { top: contextMenu.y, left: contextMenu.x }), onClick: (e) => e.stopPropagation(), onPointerLeave: () => setContextMenu(null), children: [_jsx("button", { style: menu.item, onClick: () => handleAddChild(contextMenu.nodeId), children: "Add Child" }), contextMenu.nodeId !== prefabData.root.id && (_jsxs(_Fragment, { children: [_jsx("button", { style: menu.item, onClick: () => handleDuplicate(contextMenu.nodeId), children: "Duplicate" }), _jsx("button", { style: Object.assign(Object.assign({}, menu.item), menu.danger), onClick: () => handleDelete(contextMenu.nodeId), children: "Delete" })] }))] }))] }));
|
|
125
|
+
}
|
|
126
|
+
function FileMenu({ prefabData, setPrefabData, onClose }) {
|
|
127
|
+
const { onScreenshot, onExportGLB } = useEditorContext();
|
|
128
|
+
const handleLoad = () => __awaiter(this, void 0, void 0, function* () {
|
|
129
|
+
const loadedPrefab = yield loadJson();
|
|
130
|
+
if (!loadedPrefab)
|
|
131
|
+
return;
|
|
132
|
+
setPrefabData(loadedPrefab);
|
|
133
|
+
onClose();
|
|
134
|
+
});
|
|
135
|
+
const handleSave = () => {
|
|
136
|
+
saveJson(prefabData, "prefab");
|
|
137
|
+
onClose();
|
|
138
|
+
};
|
|
139
|
+
const handleLoadIntoScene = () => __awaiter(this, void 0, void 0, function* () {
|
|
140
|
+
const loadedPrefab = yield loadJson();
|
|
141
|
+
if (!loadedPrefab)
|
|
142
|
+
return;
|
|
143
|
+
setPrefabData(prev => (Object.assign(Object.assign({}, prev), { root: updateNodeById(prev.root, prev.root.id, root => {
|
|
144
|
+
var _a;
|
|
145
|
+
return (Object.assign(Object.assign({}, root), { children: [...((_a = root.children) !== null && _a !== void 0 ? _a : []), regenerateIds(loadedPrefab.root)] }));
|
|
146
|
+
}) })));
|
|
147
|
+
onClose();
|
|
148
|
+
});
|
|
149
|
+
return (_jsxs("div", { style: Object.assign(Object.assign({}, menu.container), { top: 28, right: 0 }), onClick: (e) => e.stopPropagation(), children: [_jsx("button", { style: menu.item, onClick: handleLoad, children: "\uD83D\uDCE5 Load Prefab JSON" }), _jsx("button", { style: menu.item, onClick: handleSave, children: "\uD83D\uDCBE Save Prefab JSON" }), _jsx("button", { style: menu.item, onClick: handleLoadIntoScene, children: "\uD83D\uDCC2 Load into Scene" }), _jsx("button", { style: menu.item, onClick: () => { onScreenshot === null || onScreenshot === void 0 ? void 0 : onScreenshot(); onClose(); }, children: "\uD83D\uDCF8 Screenshot" }), _jsx("button", { style: menu.item, onClick: () => { onExportGLB === null || onExportGLB === void 0 ? void 0 : onExportGLB(); onClose(); }, children: "\uD83D\uDCE6 Export GLB" })] }));
|
|
115
150
|
}
|
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
import { Dispatch, SetStateAction } from 'react';
|
|
2
2
|
import { Prefab } from "./types";
|
|
3
|
-
declare function EditorUI({ prefabData, setPrefabData, selectedId, setSelectedId,
|
|
3
|
+
declare function EditorUI({ prefabData, setPrefabData, selectedId, setSelectedId, basePath, onUndo, onRedo, canUndo, canRedo }: {
|
|
4
4
|
prefabData?: Prefab;
|
|
5
5
|
setPrefabData?: Dispatch<SetStateAction<Prefab>>;
|
|
6
6
|
selectedId: string | null;
|
|
7
7
|
setSelectedId: Dispatch<SetStateAction<string | null>>;
|
|
8
|
-
transformMode: "translate" | "rotate" | "scale";
|
|
9
|
-
setTransformMode: (m: "translate" | "rotate" | "scale") => void;
|
|
10
8
|
basePath?: string;
|
|
11
|
-
onSave?: () => void;
|
|
12
|
-
onLoad?: () => void;
|
|
13
9
|
onUndo?: () => void;
|
|
14
10
|
onRedo?: () => void;
|
|
15
11
|
canUndo?: boolean;
|
|
@@ -15,8 +15,10 @@ import EditorTree from './EditorTree';
|
|
|
15
15
|
import { getAllComponents } from './components/ComponentRegistry';
|
|
16
16
|
import { base, inspector } from './styles';
|
|
17
17
|
import { findNode, updateNode, deleteNode } from './utils';
|
|
18
|
-
|
|
18
|
+
import { useEditorContext } from './EditorContext';
|
|
19
|
+
function EditorUI({ prefabData, setPrefabData, selectedId, setSelectedId, basePath, onUndo, onRedo, canUndo, canRedo }) {
|
|
19
20
|
const [collapsed, setCollapsed] = useState(false);
|
|
21
|
+
const { transformMode, setTransformMode } = useEditorContext();
|
|
20
22
|
const updateNodeHandler = (updater) => {
|
|
21
23
|
if (!prefabData || !setPrefabData || !selectedId)
|
|
22
24
|
return;
|
|
@@ -34,7 +36,7 @@ function EditorUI({ prefabData, setPrefabData, selectedId, setSelectedId, transf
|
|
|
34
36
|
.prefab-scroll::-webkit-scrollbar-track { background: transparent; }
|
|
35
37
|
.prefab-scroll::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.06); border-radius: 8px; }
|
|
36
38
|
.prefab-scroll { scrollbar-width: thin; scrollbar-color: rgba(255,255,255,0.06) transparent; }
|
|
37
|
-
` }), _jsxs("div", { style: inspector.panel, children: [_jsxs("div", { style: base.header, onClick: () => setCollapsed(!collapsed), children: [_jsx("span", { children: "Inspector" }), _jsx("span", { children: collapsed ? '◀' : '▼' })] }), !collapsed && selectedNode && (_jsx(NodeInspector, { node: selectedNode, updateNode: updateNodeHandler, deleteNode: deleteNodeHandler, transformMode: transformMode, setTransformMode: setTransformMode, basePath: basePath }))] }), _jsx("div", { style: { position: 'absolute', top: 8, left: 8, zIndex: 20 }, children: _jsx(EditorTree, { prefabData: prefabData, setPrefabData: setPrefabData, selectedId: selectedId, setSelectedId: setSelectedId,
|
|
39
|
+
` }), _jsxs("div", { style: inspector.panel, children: [_jsxs("div", { style: base.header, onClick: () => setCollapsed(!collapsed), children: [_jsx("span", { children: "Inspector" }), _jsx("span", { children: collapsed ? '◀' : '▼' })] }), !collapsed && selectedNode && (_jsx(NodeInspector, { node: selectedNode, updateNode: updateNodeHandler, deleteNode: deleteNodeHandler, transformMode: transformMode, setTransformMode: setTransformMode, basePath: basePath }))] }), _jsx("div", { style: { position: 'absolute', top: 8, left: 8, zIndex: 20 }, children: _jsx(EditorTree, { prefabData: prefabData, setPrefabData: setPrefabData, selectedId: selectedId, setSelectedId: setSelectedId, onUndo: onUndo, onRedo: onRedo, canUndo: canUndo, canRedo: canRedo }) })] });
|
|
38
40
|
}
|
|
39
41
|
function NodeInspector({ node, updateNode, deleteNode, transformMode, setTransformMode, basePath }) {
|
|
40
42
|
var _a;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const SCREENSHOT_EVENT = "prefab-editor-screenshot";
|
|
2
|
+
export declare const EXPORT_GLB_EVENT = "prefab-editor-export-glb";
|
|
3
|
+
export declare function ExportHelper({ prefabName }: {
|
|
4
|
+
prefabName: string;
|
|
5
|
+
}): null;
|
|
6
|
+
export declare function triggerScreenshot(): void;
|
|
7
|
+
export declare function triggerExportGLB(): void;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
import { useThree } from "@react-three/fiber";
|
|
3
|
+
import { GLTFExporter } from "three/examples/jsm/exporters/GLTFExporter.js";
|
|
4
|
+
// Custom events for triggering exports
|
|
5
|
+
export const SCREENSHOT_EVENT = "prefab-editor-screenshot";
|
|
6
|
+
export const EXPORT_GLB_EVENT = "prefab-editor-export-glb";
|
|
7
|
+
export function ExportHelper({ prefabName }) {
|
|
8
|
+
const { gl, scene } = useThree();
|
|
9
|
+
const sceneRef = useRef(scene);
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
sceneRef.current = scene;
|
|
12
|
+
}, [scene]);
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
const handleScreenshot = () => {
|
|
15
|
+
const canvas = gl.domElement;
|
|
16
|
+
canvas.toBlob((blob) => {
|
|
17
|
+
if (!blob)
|
|
18
|
+
return;
|
|
19
|
+
const url = URL.createObjectURL(blob);
|
|
20
|
+
const a = document.createElement('a');
|
|
21
|
+
a.href = url;
|
|
22
|
+
a.download = `${prefabName || 'screenshot'}.png`;
|
|
23
|
+
a.click();
|
|
24
|
+
URL.revokeObjectURL(url);
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
const handleExportGLB = () => {
|
|
28
|
+
const exporter = new GLTFExporter();
|
|
29
|
+
exporter.parse(sceneRef.current, (result) => {
|
|
30
|
+
const blob = new Blob([result], { type: 'application/octet-stream' });
|
|
31
|
+
const url = URL.createObjectURL(blob);
|
|
32
|
+
const a = document.createElement('a');
|
|
33
|
+
a.href = url;
|
|
34
|
+
a.download = `${prefabName || 'scene'}.glb`;
|
|
35
|
+
a.click();
|
|
36
|
+
URL.revokeObjectURL(url);
|
|
37
|
+
}, (error) => {
|
|
38
|
+
console.error('Error exporting GLB:', error);
|
|
39
|
+
}, { binary: true });
|
|
40
|
+
};
|
|
41
|
+
window.addEventListener(SCREENSHOT_EVENT, handleScreenshot);
|
|
42
|
+
window.addEventListener(EXPORT_GLB_EVENT, handleExportGLB);
|
|
43
|
+
return () => {
|
|
44
|
+
window.removeEventListener(SCREENSHOT_EVENT, handleScreenshot);
|
|
45
|
+
window.removeEventListener(EXPORT_GLB_EVENT, handleExportGLB);
|
|
46
|
+
};
|
|
47
|
+
}, [gl, prefabName]);
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
export function triggerScreenshot() {
|
|
51
|
+
window.dispatchEvent(new Event(SCREENSHOT_EVENT));
|
|
52
|
+
}
|
|
53
|
+
export function triggerExportGLB() {
|
|
54
|
+
window.dispatchEvent(new Event(EXPORT_GLB_EVENT));
|
|
55
|
+
}
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import { Prefab } from "./types";
|
|
2
|
-
|
|
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<{
|
|
3
11
|
basePath?: string;
|
|
4
12
|
initialPrefab?: Prefab;
|
|
5
13
|
onPrefabChange?: (prefab: Prefab) => void;
|
|
6
14
|
children?: React.ReactNode;
|
|
7
|
-
}
|
|
15
|
+
} & import("react").RefAttributes<PrefabEditorRef>>;
|
|
8
16
|
export default PrefabEditor;
|
|
@@ -1,20 +1,13 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
12
3
|
import GameCanvas from "../../shared/GameCanvas";
|
|
13
|
-
import { useState, useRef, useEffect } from "react";
|
|
4
|
+
import { useState, useRef, useEffect, forwardRef, useImperativeHandle } from "react";
|
|
14
5
|
import PrefabRoot from "./PrefabRoot";
|
|
15
6
|
import { Physics } from "@react-three/rapier";
|
|
16
7
|
import EditorUI from "./EditorUI";
|
|
17
8
|
import { base, toolbar } from "./styles";
|
|
9
|
+
import { EditorContext } from "./EditorContext";
|
|
10
|
+
import { GLTFExporter } from "three/examples/jsm/exporters/GLTFExporter.js";
|
|
18
11
|
const DEFAULT_PREFAB = {
|
|
19
12
|
id: "prefab-default",
|
|
20
13
|
name: "New Prefab",
|
|
@@ -28,15 +21,18 @@ const DEFAULT_PREFAB = {
|
|
|
28
21
|
}
|
|
29
22
|
}
|
|
30
23
|
};
|
|
31
|
-
const PrefabEditor = ({ basePath, initialPrefab, onPrefabChange, children }) => {
|
|
24
|
+
const PrefabEditor = forwardRef(({ basePath, initialPrefab, onPrefabChange, children }, ref) => {
|
|
32
25
|
const [editMode, setEditMode] = useState(true);
|
|
33
26
|
const [loadedPrefab, setLoadedPrefab] = useState(initialPrefab !== null && initialPrefab !== void 0 ? initialPrefab : DEFAULT_PREFAB);
|
|
34
27
|
const [selectedId, setSelectedId] = useState(null);
|
|
35
28
|
const [transformMode, setTransformMode] = useState("translate");
|
|
29
|
+
const [snapResolution, setSnapResolution] = useState(0);
|
|
36
30
|
const [history, setHistory] = useState([loadedPrefab]);
|
|
37
31
|
const [historyIndex, setHistoryIndex] = useState(0);
|
|
38
32
|
const throttleRef = useRef(null);
|
|
39
33
|
const lastDataRef = useRef(JSON.stringify(loadedPrefab));
|
|
34
|
+
const prefabRootRef = useRef(null);
|
|
35
|
+
const canvasRef = useRef(null);
|
|
40
36
|
useEffect(() => {
|
|
41
37
|
if (initialPrefab)
|
|
42
38
|
setLoadedPrefab(initialPrefab);
|
|
@@ -87,48 +83,59 @@ const PrefabEditor = ({ basePath, initialPrefab, onPrefabChange, children }) =>
|
|
|
87
83
|
return () => { if (throttleRef.current)
|
|
88
84
|
clearTimeout(throttleRef.current); };
|
|
89
85
|
}, [loadedPrefab]);
|
|
90
|
-
const
|
|
91
|
-
const
|
|
92
|
-
if (
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
a.click();
|
|
107
|
-
};
|
|
108
|
-
const loadJson = () => new Promise(resolve => {
|
|
109
|
-
const input = document.createElement('input');
|
|
110
|
-
input.type = 'file';
|
|
111
|
-
input.accept = '.json,application/json';
|
|
112
|
-
input.onchange = e => {
|
|
86
|
+
const handleScreenshot = () => {
|
|
87
|
+
const canvas = canvasRef.current;
|
|
88
|
+
if (!canvas)
|
|
89
|
+
return;
|
|
90
|
+
canvas.toBlob((blob) => {
|
|
91
|
+
if (!blob)
|
|
92
|
+
return;
|
|
93
|
+
const url = URL.createObjectURL(blob);
|
|
94
|
+
const a = document.createElement('a');
|
|
95
|
+
a.href = url;
|
|
96
|
+
a.download = `${loadedPrefab.name || 'screenshot'}.png`;
|
|
97
|
+
a.click();
|
|
98
|
+
URL.revokeObjectURL(url);
|
|
99
|
+
});
|
|
100
|
+
};
|
|
101
|
+
const handleExportGLB = () => {
|
|
113
102
|
var _a;
|
|
114
|
-
const
|
|
115
|
-
if (!
|
|
116
|
-
return
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
};
|
|
130
|
-
reader.readAsText(file);
|
|
103
|
+
const sceneRoot = (_a = prefabRootRef.current) === null || _a === void 0 ? void 0 : _a.root;
|
|
104
|
+
if (!sceneRoot)
|
|
105
|
+
return;
|
|
106
|
+
const exporter = new GLTFExporter();
|
|
107
|
+
exporter.parse(sceneRoot, (result) => {
|
|
108
|
+
const blob = new Blob([result], { type: 'application/octet-stream' });
|
|
109
|
+
const url = URL.createObjectURL(blob);
|
|
110
|
+
const a = document.createElement('a');
|
|
111
|
+
a.href = url;
|
|
112
|
+
a.download = `${loadedPrefab.name || 'scene'}.glb`;
|
|
113
|
+
a.click();
|
|
114
|
+
URL.revokeObjectURL(url);
|
|
115
|
+
}, (error) => {
|
|
116
|
+
console.error('Error exporting GLB:', error);
|
|
117
|
+
}, { binary: true });
|
|
131
118
|
};
|
|
132
|
-
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
const canvas = document.querySelector('canvas');
|
|
121
|
+
if (canvas)
|
|
122
|
+
canvasRef.current = canvas;
|
|
123
|
+
}, []);
|
|
124
|
+
useImperativeHandle(ref, () => ({
|
|
125
|
+
screenshot: handleScreenshot,
|
|
126
|
+
exportGLB: handleExportGLB,
|
|
127
|
+
prefab: loadedPrefab,
|
|
128
|
+
setPrefab: setLoadedPrefab,
|
|
129
|
+
rootRef: prefabRootRef
|
|
130
|
+
}), [loadedPrefab]);
|
|
131
|
+
return _jsxs(EditorContext.Provider, { value: {
|
|
132
|
+
transformMode,
|
|
133
|
+
setTransformMode,
|
|
134
|
+
snapResolution,
|
|
135
|
+
setSnapResolution,
|
|
136
|
+
onScreenshot: handleScreenshot,
|
|
137
|
+
onExportGLB: handleExportGLB
|
|
138
|
+
}, 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 })] });
|
|
133
139
|
});
|
|
140
|
+
PrefabEditor.displayName = "PrefabEditor";
|
|
134
141
|
export default PrefabEditor;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { Group, Matrix4, Object3D, Texture } from "three";
|
|
2
2
|
import { ThreeEvent } from "@react-three/fiber";
|
|
3
3
|
import { Prefab, GameObject as GameObjectType } from "./types";
|
|
4
|
+
export interface PrefabRootRef {
|
|
5
|
+
root: Group | null;
|
|
6
|
+
}
|
|
4
7
|
export declare const PrefabRoot: import("react").ForwardRefExoticComponent<{
|
|
5
8
|
editMode?: boolean;
|
|
6
9
|
data: Prefab;
|
|
@@ -8,9 +11,8 @@ export declare const PrefabRoot: import("react").ForwardRefExoticComponent<{
|
|
|
8
11
|
selectedId?: string | null;
|
|
9
12
|
onSelect?: (id: string | null) => void;
|
|
10
13
|
onClick?: (event: ThreeEvent<PointerEvent>, entity: GameObjectType) => void;
|
|
11
|
-
transformMode?: "translate" | "rotate" | "scale";
|
|
12
14
|
basePath?: string;
|
|
13
|
-
} & import("react").RefAttributes<
|
|
15
|
+
} & import("react").RefAttributes<PrefabRootRef>>;
|
|
14
16
|
export declare function GameObjectRenderer(props: RendererProps): import("react/jsx-runtime").JSX.Element | null;
|
|
15
17
|
interface RendererProps {
|
|
16
18
|
gameObject: GameObjectType;
|
|
@@ -10,50 +10,50 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
};
|
|
11
11
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
12
12
|
import { MapControls, TransformControls, useHelper } from "@react-three/drei";
|
|
13
|
-
import { forwardRef, useCallback, useEffect, useRef, useState
|
|
13
|
+
import { forwardRef, useCallback, useContext, useEffect, useImperativeHandle, useRef, useState } from "react";
|
|
14
14
|
import { BoxHelper, Euler, Matrix4, Quaternion, SRGBColorSpace, TextureLoader, Vector3, } from "three";
|
|
15
15
|
import { getComponent, registerComponent } from "./components/ComponentRegistry";
|
|
16
16
|
import components from "./components";
|
|
17
17
|
import { loadModel } from "../dragdrop/modelLoader";
|
|
18
18
|
import { GameInstance, GameInstanceProvider, useInstanceCheck } from "./InstanceProvider";
|
|
19
19
|
import { updateNode } from "./utils";
|
|
20
|
-
|
|
21
|
-
/* Setup */
|
|
22
|
-
/* -------------------------------------------------- */
|
|
20
|
+
import { EditorContext } from "./EditorContext";
|
|
23
21
|
components.forEach(registerComponent);
|
|
24
22
|
const IDENTITY = new Matrix4();
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
export const PrefabRoot = forwardRef(({ editMode, data, onPrefabChange, selectedId, onSelect, onClick, basePath = "" }, ref) => {
|
|
24
|
+
var _a, _b;
|
|
25
|
+
// optional editor context
|
|
26
|
+
const editorContext = useContext(EditorContext);
|
|
27
|
+
const transformMode = (_a = editorContext === null || editorContext === void 0 ? void 0 : editorContext.transformMode) !== null && _a !== void 0 ? _a : "translate";
|
|
28
|
+
const snapResolution = (_b = editorContext === null || editorContext === void 0 ? void 0 : editorContext.snapResolution) !== null && _b !== void 0 ? _b : 0;
|
|
29
|
+
// prefab root state
|
|
29
30
|
const [models, setModels] = useState({});
|
|
30
31
|
const [textures, setTextures] = useState({});
|
|
31
32
|
const loading = useRef(new Set());
|
|
32
33
|
const objectRefs = useRef({});
|
|
33
34
|
const [selectedObject, setSelectedObject] = useState(null);
|
|
35
|
+
const rootRef = useRef(null);
|
|
36
|
+
useImperativeHandle(ref, () => ({
|
|
37
|
+
root: rootRef.current
|
|
38
|
+
}), []);
|
|
34
39
|
const registerRef = useCallback((id, obj) => {
|
|
35
40
|
objectRefs.current[id] = obj;
|
|
36
41
|
if (id === selectedId)
|
|
37
42
|
setSelectedObject(obj);
|
|
38
43
|
}, [selectedId]);
|
|
39
|
-
// Suppress TransformControls scene graph warnings during transitions
|
|
40
44
|
useEffect(() => {
|
|
41
45
|
const originalError = console.error;
|
|
42
46
|
console.error = (...args) => {
|
|
43
|
-
if (typeof args[0] === 'string' && args[0].includes('TransformControls') && args[0].includes('scene graph'))
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
47
|
+
if (typeof args[0] === 'string' && args[0].includes('TransformControls') && args[0].includes('scene graph'))
|
|
48
|
+
return;
|
|
46
49
|
originalError.apply(console, args);
|
|
47
50
|
};
|
|
48
|
-
return () => {
|
|
49
|
-
console.error = originalError;
|
|
50
|
-
};
|
|
51
|
+
return () => { console.error = originalError; };
|
|
51
52
|
}, []);
|
|
52
53
|
useEffect(() => {
|
|
53
54
|
var _a;
|
|
54
55
|
setSelectedObject(selectedId ? (_a = objectRefs.current[selectedId]) !== null && _a !== void 0 ? _a : null : null);
|
|
55
56
|
}, [selectedId]);
|
|
56
|
-
/* ---------------- Transform writeback ---------------- */
|
|
57
57
|
const onTransformChange = () => {
|
|
58
58
|
if (!selectedId || !onPrefabChange)
|
|
59
59
|
return;
|
|
@@ -69,7 +69,6 @@ export const PrefabRoot = forwardRef(({ editMode, data, onPrefabChange, selected
|
|
|
69
69
|
} }) })));
|
|
70
70
|
onPrefabChange(Object.assign(Object.assign({}, data), { root }));
|
|
71
71
|
};
|
|
72
|
-
/* ---------------- Asset loading ---------------- */
|
|
73
72
|
useEffect(() => {
|
|
74
73
|
const modelsToLoad = new Set();
|
|
75
74
|
const texturesToLoad = new Set();
|
|
@@ -105,12 +104,8 @@ export const PrefabRoot = forwardRef(({ editMode, data, onPrefabChange, selected
|
|
|
105
104
|
});
|
|
106
105
|
});
|
|
107
106
|
}, [data, models, textures]);
|
|
108
|
-
|
|
109
|
-
return (_jsxs("group", { ref: ref, 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, 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 }))] }))] }));
|
|
107
|
+
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, 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}`))] }))] }));
|
|
110
108
|
});
|
|
111
|
-
/* -------------------------------------------------- */
|
|
112
|
-
/* Renderer Switch */
|
|
113
|
-
/* -------------------------------------------------- */
|
|
114
109
|
export function GameObjectRenderer(props) {
|
|
115
110
|
var _a, _b, _c;
|
|
116
111
|
const node = props.gameObject;
|
|
@@ -120,16 +115,13 @@ export function GameObjectRenderer(props) {
|
|
|
120
115
|
const prevInstancedRef = useRef(undefined);
|
|
121
116
|
const [isTransitioning, setIsTransitioning] = useState(false);
|
|
122
117
|
useEffect(() => {
|
|
123
|
-
// Detect instanced mode change
|
|
124
118
|
if (prevInstancedRef.current !== undefined && prevInstancedRef.current !== isInstanced) {
|
|
125
119
|
setIsTransitioning(true);
|
|
126
|
-
// Wait for cleanup, then allow new mode to render
|
|
127
120
|
const timer = setTimeout(() => setIsTransitioning(false), 100);
|
|
128
121
|
return () => clearTimeout(timer);
|
|
129
122
|
}
|
|
130
123
|
prevInstancedRef.current = isInstanced;
|
|
131
124
|
}, [isInstanced]);
|
|
132
|
-
// Don't render during transition to avoid physics conflicts
|
|
133
125
|
if (isTransitioning)
|
|
134
126
|
return null;
|
|
135
127
|
const key = `${node.id}_${isInstanced ? 'instanced' : 'standard'}`;
|
|
@@ -137,9 +129,6 @@ export function GameObjectRenderer(props) {
|
|
|
137
129
|
? _jsx(InstancedNode, Object.assign({}, props), key)
|
|
138
130
|
: _jsx(StandardNode, Object.assign({}, props), key);
|
|
139
131
|
}
|
|
140
|
-
/* -------------------------------------------------- */
|
|
141
|
-
/* InstancedNode (terminal) */
|
|
142
|
-
/* -------------------------------------------------- */
|
|
143
132
|
function isPhysicsProps(v) {
|
|
144
133
|
return (v === null || v === void 0 ? void 0 : v.type) === "fixed" || (v === null || v === void 0 ? void 0 : v.type) === "dynamic";
|
|
145
134
|
}
|
|
@@ -147,7 +136,6 @@ function InstancedNode({ gameObject, parentMatrix = IDENTITY, editMode, register
|
|
|
147
136
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
148
137
|
const world = parentMatrix.clone().multiply(compose(gameObject));
|
|
149
138
|
const { position: worldPosition, rotation: worldRotation, scale: worldScale } = decompose(world);
|
|
150
|
-
// Get local transform for proxy group (used by transform controls)
|
|
151
139
|
const localTransform = getNodeTransformProps(gameObject);
|
|
152
140
|
const physicsProps = isPhysicsProps((_b = (_a = gameObject.components) === null || _a === void 0 ? void 0 : _a.physics) === null || _b === void 0 ? void 0 : _b.properties)
|
|
153
141
|
? (_d = (_c = gameObject.components) === null || _c === void 0 ? void 0 : _c.physics) === null || _d === void 0 ? void 0 : _d.properties
|
|
@@ -161,8 +149,6 @@ function InstancedNode({ gameObject, parentMatrix = IDENTITY, editMode, register
|
|
|
161
149
|
}
|
|
162
150
|
}, [gameObject.id, registerRef, editMode]);
|
|
163
151
|
const modelUrl = (_g = (_f = (_e = gameObject.components) === null || _e === void 0 ? void 0 : _e.model) === null || _f === void 0 ? void 0 : _f.properties) === null || _g === void 0 ? void 0 : _g.filename;
|
|
164
|
-
// In edit mode, create a proxy group at the same position for transform controls
|
|
165
|
-
// The GameInstance still needs the actual position so it renders correctly
|
|
166
152
|
if (editMode) {
|
|
167
153
|
return (_jsxs(_Fragment, { children: [_jsx("group", { ref: groupRef, position: localTransform.position, rotation: localTransform.rotation, scale: localTransform.scale, onPointerDown: (e) => { e.stopPropagation(); clickValid.current = true; }, onPointerMove: () => { clickValid.current = false; }, onPointerUp: (e) => {
|
|
168
154
|
if (clickValid.current) {
|
|
@@ -175,18 +161,13 @@ function InstancedNode({ gameObject, parentMatrix = IDENTITY, editMode, register
|
|
|
175
161
|
}
|
|
176
162
|
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 }));
|
|
177
163
|
}
|
|
178
|
-
/* -------------------------------------------------- */
|
|
179
|
-
/* StandardNode */
|
|
180
|
-
/* -------------------------------------------------- */
|
|
181
164
|
function StandardNode({ gameObject, selectedId, onSelect, onClick, registerRef, loadedModels, loadedTextures, editMode, parentMatrix = IDENTITY, }) {
|
|
182
165
|
var _a, _b, _c, _d, _e, _f;
|
|
183
166
|
const groupRef = useRef(null);
|
|
184
167
|
const helperRef = useRef(null);
|
|
185
168
|
const clickValid = useRef(false);
|
|
186
169
|
const isSelected = selectedId === gameObject.id;
|
|
187
|
-
// Check if this object still exists as an instance (to prevent physics overlap)
|
|
188
170
|
const stillInstanced = useInstanceCheck(gameObject.id);
|
|
189
|
-
// Use helperRef for BoxHelper (shows actual content bounds at correct position)
|
|
190
171
|
useHelper(editMode && isSelected ? helperRef : null, BoxHelper, "cyan");
|
|
191
172
|
useEffect(() => {
|
|
192
173
|
registerRef(gameObject.id, groupRef.current);
|
|
@@ -210,16 +191,13 @@ function StandardNode({ gameObject, selectedId, onSelect, onClick, registerRef,
|
|
|
210
191
|
loadedModels[gameObject.components.model.properties.filename];
|
|
211
192
|
const hasPhysics = physics && ready && !stillInstanced;
|
|
212
193
|
const transform = getNodeTransformProps(gameObject);
|
|
213
|
-
// Prepare physics wrapper if needed
|
|
214
194
|
const physicsDef = hasPhysics ? getComponent("Physics") : null;
|
|
215
195
|
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;
|
|
216
196
|
const physicsKey = `physics_${gameObject.id}_${isInstanced ? 'instanced' : 'standard'}`;
|
|
217
|
-
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, {
|
|
218
|
-
// In edit mode, use proxy group pattern
|
|
197
|
+
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, loadedModels: loadedModels, loadedTextures: loadedTextures, editMode: editMode, parentMatrix: world }, child.id)))] }));
|
|
219
198
|
if (editMode) {
|
|
220
199
|
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, children: inner }, physicsKey)) : null] }));
|
|
221
200
|
}
|
|
222
|
-
// In play mode, apply transform directly to content
|
|
223
201
|
if (hasPhysics && (physicsDef === null || physicsDef === void 0 ? void 0 : physicsDef.View)) {
|
|
224
202
|
return (_jsx(physicsDef.View, { properties: physics.properties, position: transform.position, rotation: transform.rotation, scale: transform.scale, editMode: editMode, children: inner }, physicsKey));
|
|
225
203
|
}
|
|
@@ -293,7 +271,6 @@ function renderCoreNode(gameObject, ctx, parentMatrix) {
|
|
|
293
271
|
const def = getComponent(comp.type);
|
|
294
272
|
if (!(def === null || def === void 0 ? void 0 : def.View))
|
|
295
273
|
return;
|
|
296
|
-
// crude but works with your existing component API
|
|
297
274
|
if (def.View.toString().includes("children")) {
|
|
298
275
|
wrappers.push({ key, View: def.View, properties: comp.properties });
|
|
299
276
|
}
|