react-three-game 0.0.54 → 0.0.56
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/shared/ContactShadow.d.ts +8 -0
- package/dist/shared/ContactShadow.js +32 -0
- package/dist/tools/assetviewer/page.js +1 -1
- package/dist/tools/dragdrop/DragDropLoader.js +17 -40
- package/dist/tools/dragdrop/modelLoader.d.ts +5 -0
- package/dist/tools/dragdrop/modelLoader.js +39 -0
- package/dist/tools/prefabeditor/EditorTree.js +3 -16
- package/dist/tools/prefabeditor/EditorUI.js +5 -10
- package/dist/tools/prefabeditor/PrefabEditor.js +57 -1
- package/dist/tools/prefabeditor/PrefabRoot.d.ts +2 -0
- package/dist/tools/prefabeditor/PrefabRoot.js +17 -2
- package/dist/tools/prefabeditor/components/Input.js +27 -26
- package/dist/tools/prefabeditor/components/MaterialComponent.js +9 -2
- package/dist/tools/prefabeditor/components/ModelComponent.js +2 -2
- package/dist/tools/prefabeditor/components/PhysicsComponent.js +1 -1
- package/dist/tools/prefabeditor/components/SpotLightComponent.js +3 -0
- package/dist/tools/prefabeditor/components/TransformComponent.js +13 -11
- package/dist/tools/prefabeditor/styles.d.ts +12 -2
- package/dist/tools/prefabeditor/styles.js +63 -30
- package/dist/tools/prefabeditor/utils.d.ts +4 -0
- package/dist/tools/prefabeditor/utils.js +39 -1
- package/package.json +1 -1
- package/react-three-game-skill/react-three-game/rules/LIGHTING.md +6 -0
- package/src/shared/ContactShadow.tsx +74 -0
- package/src/tools/assetviewer/page.tsx +1 -1
- package/src/tools/dragdrop/DragDropLoader.tsx +7 -39
- package/src/tools/dragdrop/modelLoader.ts +36 -0
- package/src/tools/prefabeditor/EditorTree.tsx +4 -15
- package/src/tools/prefabeditor/EditorUI.tsx +5 -10
- package/src/tools/prefabeditor/PrefabEditor.tsx +60 -1
- package/src/tools/prefabeditor/PrefabRoot.tsx +21 -2
- package/src/tools/prefabeditor/components/Input.tsx +27 -26
- package/src/tools/prefabeditor/components/MaterialComponent.tsx +14 -5
- package/src/tools/prefabeditor/components/ModelComponent.tsx +2 -2
- package/src/tools/prefabeditor/components/PhysicsComponent.tsx +1 -1
- package/src/tools/prefabeditor/components/SpotLightComponent.tsx +4 -0
- package/src/tools/prefabeditor/components/TransformComponent.tsx +17 -11
- package/src/tools/prefabeditor/styles.ts +65 -30
- package/src/tools/prefabeditor/utils.ts +41 -1
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
interface ContactShadowProps {
|
|
2
|
+
opacity?: number;
|
|
3
|
+
blur?: number;
|
|
4
|
+
scale?: number;
|
|
5
|
+
yOffset?: number;
|
|
6
|
+
}
|
|
7
|
+
declare const ContactShadow: ({ opacity, blur, scale, yOffset, }: ContactShadowProps) => import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
export default ContactShadow;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
import * as THREE from "three/webgpu";
|
|
5
|
+
import { float, uv, vec3, smoothstep, uniform, length, } from "three/tsl";
|
|
6
|
+
const ContactShadow = ({ opacity = 0.4, blur = 2.5, scale = 1.2, yOffset = 0.05, }) => {
|
|
7
|
+
const material = useMemo(() => {
|
|
8
|
+
const mat = new THREE.MeshBasicNodeMaterial();
|
|
9
|
+
mat.transparent = true;
|
|
10
|
+
mat.depthWrite = false;
|
|
11
|
+
mat.depthTest = true;
|
|
12
|
+
mat.side = THREE.DoubleSide;
|
|
13
|
+
mat.polygonOffset = true;
|
|
14
|
+
mat.polygonOffsetFactor = -1;
|
|
15
|
+
mat.polygonOffsetUnits = -1;
|
|
16
|
+
const uOpacity = uniform(opacity);
|
|
17
|
+
const uBlur = uniform(blur);
|
|
18
|
+
// UVs centered around origin
|
|
19
|
+
const centeredUV = uv().sub(0.5).mul(2.0);
|
|
20
|
+
// IMPORTANT: use functional length(), not .length()
|
|
21
|
+
const dist = length(centeredUV);
|
|
22
|
+
const innerRadius = float(0.0);
|
|
23
|
+
const outerRadius = float(1.0);
|
|
24
|
+
const blurAmount = uBlur.div(10.0);
|
|
25
|
+
const alpha = smoothstep(outerRadius, innerRadius.add(blurAmount), dist).mul(uOpacity);
|
|
26
|
+
mat.colorNode = vec3(0.0, 0.0, 0.0);
|
|
27
|
+
mat.opacityNode = alpha;
|
|
28
|
+
return mat;
|
|
29
|
+
}, [opacity, blur]);
|
|
30
|
+
return (_jsx("mesh", { rotation: [-Math.PI / 2, 0, 0], position: [0, yOffset, 0], material: material, renderOrder: 1, children: _jsx("planeGeometry", { args: [scale, scale] }) }));
|
|
31
|
+
};
|
|
32
|
+
export default ContactShadow;
|
|
@@ -156,7 +156,7 @@ export function SingleSoundViewer({ file, basePath = "" }) {
|
|
|
156
156
|
// Shared Canvas Component - can be used independently in any viewer
|
|
157
157
|
export function SharedCanvas() {
|
|
158
158
|
return (_jsx(Canvas, { shadows: true, dpr: [1, 1.5], camera: { position: [0, 0, 3], fov: 45, near: 0.1, far: 1000 }, style: {
|
|
159
|
-
position: '
|
|
159
|
+
position: 'fixed',
|
|
160
160
|
top: 0,
|
|
161
161
|
left: 0,
|
|
162
162
|
width: '100vw',
|
|
@@ -1,49 +1,26 @@
|
|
|
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, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
11
|
// DragDropLoader.tsx
|
|
3
12
|
import { useEffect } from "react";
|
|
4
|
-
import {
|
|
5
|
-
// Shared file handling logic
|
|
13
|
+
import { parseModelFromFile } from "./modelLoader";
|
|
6
14
|
function handleFiles(files, onModelLoaded) {
|
|
7
|
-
files.forEach((file) => {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
else if (file.name.endsWith(".fbx")) {
|
|
12
|
-
loadFBXFile(file, onModelLoaded);
|
|
13
|
-
}
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
function loadGLTFFile(file, onModelLoaded) {
|
|
17
|
-
const reader = new FileReader();
|
|
18
|
-
reader.onload = (event) => {
|
|
19
|
-
var _a;
|
|
20
|
-
const arrayBuffer = (_a = event.target) === null || _a === void 0 ? void 0 : _a.result;
|
|
21
|
-
if (arrayBuffer) {
|
|
22
|
-
const loader = new GLTFLoader();
|
|
23
|
-
const dracoLoader = new DRACOLoader();
|
|
24
|
-
dracoLoader.setDecoderPath("https://www.gstatic.com/draco/v1/decoders/");
|
|
25
|
-
loader.setDRACOLoader(dracoLoader);
|
|
26
|
-
loader.parse(arrayBuffer, "", (gltf) => {
|
|
27
|
-
onModelLoaded(gltf.scene, file.name);
|
|
28
|
-
}, (error) => {
|
|
29
|
-
console.error("GLTFLoader parse error", error);
|
|
30
|
-
});
|
|
15
|
+
files.forEach((file) => __awaiter(this, void 0, void 0, function* () {
|
|
16
|
+
const result = yield parseModelFromFile(file);
|
|
17
|
+
if (result.success && result.model) {
|
|
18
|
+
onModelLoaded(result.model, file.name);
|
|
31
19
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
function loadFBXFile(file, onModelLoaded) {
|
|
36
|
-
const reader = new FileReader();
|
|
37
|
-
reader.onload = (event) => {
|
|
38
|
-
var _a;
|
|
39
|
-
const arrayBuffer = (_a = event.target) === null || _a === void 0 ? void 0 : _a.result;
|
|
40
|
-
if (arrayBuffer) {
|
|
41
|
-
const loader = new FBXLoader();
|
|
42
|
-
const model = loader.parse(arrayBuffer, "");
|
|
43
|
-
onModelLoaded(model, file.name);
|
|
20
|
+
else {
|
|
21
|
+
console.error("Model parse error:", result.error);
|
|
44
22
|
}
|
|
45
|
-
};
|
|
46
|
-
reader.readAsArrayBuffer(file);
|
|
23
|
+
}));
|
|
47
24
|
}
|
|
48
25
|
export function DragDropLoader({ onModelLoaded }) {
|
|
49
26
|
useEffect(() => {
|
|
@@ -4,4 +4,9 @@ export type ModelLoadResult = {
|
|
|
4
4
|
error?: any;
|
|
5
5
|
};
|
|
6
6
|
export type ProgressCallback = (filename: string, loaded: number, total: number) => void;
|
|
7
|
+
/**
|
|
8
|
+
* Parse a model from a File object (e.g. from drag-drop or file picker).
|
|
9
|
+
* Returns the parsed Three.js Object3D scene.
|
|
10
|
+
*/
|
|
11
|
+
export declare function parseModelFromFile(file: File): Promise<ModelLoadResult>;
|
|
7
12
|
export declare function loadModel(filename: string, onProgress?: ProgressCallback): Promise<ModelLoadResult>;
|
|
@@ -14,6 +14,45 @@ dracoLoader.setDecoderPath("https://www.gstatic.com/draco/v1/decoders/");
|
|
|
14
14
|
const gltfLoader = new GLTFLoader();
|
|
15
15
|
gltfLoader.setDRACOLoader(dracoLoader);
|
|
16
16
|
const fbxLoader = new FBXLoader();
|
|
17
|
+
/**
|
|
18
|
+
* Parse a model from a File object (e.g. from drag-drop or file picker).
|
|
19
|
+
* Returns the parsed Three.js Object3D scene.
|
|
20
|
+
*/
|
|
21
|
+
export function parseModelFromFile(file) {
|
|
22
|
+
return new Promise((resolve) => {
|
|
23
|
+
const reader = new FileReader();
|
|
24
|
+
reader.onload = (event) => {
|
|
25
|
+
var _a;
|
|
26
|
+
const arrayBuffer = (_a = event.target) === null || _a === void 0 ? void 0 : _a.result;
|
|
27
|
+
if (!arrayBuffer) {
|
|
28
|
+
resolve({ success: false, error: new Error('Failed to read file') });
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const name = file.name.toLowerCase();
|
|
32
|
+
if (name.endsWith('.glb') || name.endsWith('.gltf')) {
|
|
33
|
+
gltfLoader.parse(arrayBuffer, '', (gltf) => {
|
|
34
|
+
resolve({ success: true, model: gltf.scene });
|
|
35
|
+
}, (error) => {
|
|
36
|
+
resolve({ success: false, error });
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
else if (name.endsWith('.fbx')) {
|
|
40
|
+
try {
|
|
41
|
+
const model = fbxLoader.parse(arrayBuffer, '');
|
|
42
|
+
resolve({ success: true, model });
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
resolve({ success: false, error });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
resolve({ success: false, error: new Error(`Unsupported file format: ${file.name}`) });
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
reader.onerror = () => resolve({ success: false, error: reader.error });
|
|
53
|
+
reader.readAsArrayBuffer(file);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
17
56
|
export function loadModel(filename, onProgress) {
|
|
18
57
|
return __awaiter(this, void 0, void 0, function* () {
|
|
19
58
|
try {
|
|
@@ -10,7 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
11
11
|
import { useState } from 'react';
|
|
12
12
|
import { getComponent } from './components/ComponentRegistry';
|
|
13
|
-
import { base, tree, menu } from './styles';
|
|
13
|
+
import { base, colors, tree, menu } from './styles';
|
|
14
14
|
import { findNode, findParent, deleteNode, cloneNode, updateNodeById, loadJson, saveJson, regenerateIds } from './utils';
|
|
15
15
|
import { useEditorContext } from './EditorContext';
|
|
16
16
|
export default function EditorTree({ prefabData, setPrefabData, selectedId, setSelectedId, onUndo, onRedo, canUndo, canRedo }) {
|
|
@@ -149,20 +149,7 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
|
|
|
149
149
|
handleToggleDisabled(node.id);
|
|
150
150
|
}, title: node.disabled ? 'Enable' : 'Disable', children: node.disabled ? '◎' : '◉' }))] }), !isCollapsed && node.children && node.children.map(child => renderNode(child, depth + 1))] }, node.id));
|
|
151
151
|
};
|
|
152
|
-
return (_jsxs(_Fragment, { children: [_jsx("style", { children: `
|
|
153
|
-
.tree-scroll::-webkit-scrollbar { width: 8px; height: 8px; }
|
|
154
|
-
.tree-scroll::-webkit-scrollbar-track { background: transparent; }
|
|
155
|
-
.tree-scroll::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.06); border-radius: 8px; }
|
|
156
|
-
` }), _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 && (_jsxs(_Fragment, { children: [_jsx("div", { style: { padding: '4px 4px', borderBottom: '1px solid rgba(255,255,255,0.1)' }, children: _jsx("input", { type: "text", placeholder: "Search nodes...", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), onClick: (e) => e.stopPropagation(), style: {
|
|
157
|
-
width: '100%',
|
|
158
|
-
padding: '4px 8px',
|
|
159
|
-
background: 'rgba(255,255,255,0.05)',
|
|
160
|
-
border: '1px solid rgba(255,255,255,0.1)',
|
|
161
|
-
borderRadius: 3,
|
|
162
|
-
color: 'inherit',
|
|
163
|
-
fontSize: 11,
|
|
164
|
-
outline: 'none',
|
|
165
|
-
} }) }), _jsx("div", { className: "tree-scroll", 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" })] }))] }))] }));
|
|
152
|
+
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 && (_jsxs(_Fragment, { children: [_jsx("div", { style: { padding: '4px 4px', borderBottom: `1px solid ${colors.borderLight}` }, children: _jsx("input", { type: "text", placeholder: "Search nodes...", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), onClick: (e) => e.stopPropagation(), style: Object.assign(Object.assign({}, base.input), { padding: '4px 8px' }) }) }), _jsx("div", { className: "tree-scroll", 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" })] }))] }))] }));
|
|
166
153
|
}
|
|
167
154
|
function FileMenu({ prefabData, setPrefabData, onClose }) {
|
|
168
155
|
const { onScreenshot, onExportGLB } = useEditorContext();
|
|
@@ -187,5 +174,5 @@ function FileMenu({ prefabData, setPrefabData, onClose }) {
|
|
|
187
174
|
}) })));
|
|
188
175
|
onClose();
|
|
189
176
|
});
|
|
190
|
-
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" })] }));
|
|
177
|
+
return (_jsxs("div", { style: Object.assign(Object.assign({}, menu.container), { position: 'absolute', 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" })] }));
|
|
191
178
|
}
|
|
@@ -13,7 +13,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
13
13
|
import { useState, useEffect } from 'react';
|
|
14
14
|
import EditorTree from './EditorTree';
|
|
15
15
|
import { getAllComponents } from './components/ComponentRegistry';
|
|
16
|
-
import { base, inspector } from './styles';
|
|
16
|
+
import { base, colors, inspector, scrollbarCSS, componentCard } from './styles';
|
|
17
17
|
import { findNode, updateNode, deleteNode } from './utils';
|
|
18
18
|
function EditorUI({ prefabData, setPrefabData, selectedId, setSelectedId, basePath, onUndo, onRedo, canUndo, canRedo }) {
|
|
19
19
|
const [collapsed, setCollapsed] = useState(false);
|
|
@@ -29,12 +29,7 @@ function EditorUI({ prefabData, setPrefabData, selectedId, setSelectedId, basePa
|
|
|
29
29
|
setSelectedId(null);
|
|
30
30
|
};
|
|
31
31
|
const selectedNode = selectedId && prefabData ? findNode(prefabData.root, selectedId) : null;
|
|
32
|
-
return _jsxs(_Fragment, { children: [_jsx("style", { children:
|
|
33
|
-
.prefab-scroll::-webkit-scrollbar { width: 8px; height: 8px; }
|
|
34
|
-
.prefab-scroll::-webkit-scrollbar-track { background: transparent; }
|
|
35
|
-
.prefab-scroll::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.06); border-radius: 8px; }
|
|
36
|
-
.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, 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 }) })] });
|
|
32
|
+
return _jsxs(_Fragment, { children: [_jsx("style", { children: scrollbarCSS }), _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, 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
33
|
}
|
|
39
34
|
function NodeInspector({ node, updateNode, deleteNode, basePath }) {
|
|
40
35
|
var _a;
|
|
@@ -47,13 +42,13 @@ function NodeInspector({ node, updateNode, deleteNode, basePath }) {
|
|
|
47
42
|
if (!newAvailable.includes(addType))
|
|
48
43
|
setAddType(newAvailable[0] || "");
|
|
49
44
|
}, [Object.keys(node.components || {}).join(',')]);
|
|
50
|
-
return _jsxs("div", { style: Object.assign(Object.assign({}, inspector.content), { paddingRight: 2 }), className: "prefab-scroll", children: [_jsxs("div", { style: base.section, children: [_jsxs("div", { style: { display: "flex", marginBottom: 8, alignItems: 'center', gap: 8 }, children: [_jsx("div", { style: { fontSize: 10, color:
|
|
45
|
+
return _jsxs("div", { style: Object.assign(Object.assign({}, inspector.content), { paddingRight: 2 }), className: "prefab-scroll", children: [_jsxs("div", { style: base.section, children: [_jsxs("div", { style: { display: "flex", marginBottom: 8, alignItems: 'center', gap: 8 }, children: [_jsx("div", { style: { fontSize: 10, color: colors.textDim, wordBreak: 'break-all', border: `1px solid ${colors.border}`, padding: '2px 6px', borderRadius: 3, flex: 1, fontFamily: 'monospace' }, children: node.id }), _jsx("button", { style: Object.assign(Object.assign({}, base.btn), base.btnDanger), title: "Delete Node", onClick: deleteNode, children: "\u274C" })] }), _jsx("input", { style: base.input, value: (_a = node.name) !== null && _a !== void 0 ? _a : "", placeholder: 'Node name', onChange: e => updateNode(n => (Object.assign(Object.assign({}, n), { name: e.target.value }))) })] }), _jsxs("div", { style: base.section, children: [_jsx("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }, children: _jsx("div", { style: base.label, children: "Components" }) }), node.components && Object.entries(node.components).map(([key, comp]) => {
|
|
51
46
|
if (!comp)
|
|
52
47
|
return null;
|
|
53
48
|
const def = ALL_COMPONENTS[comp.type];
|
|
54
49
|
if (!def)
|
|
55
|
-
return _jsxs("div", { style: { color:
|
|
56
|
-
return (_jsxs("div", { style:
|
|
50
|
+
return _jsxs("div", { style: { color: colors.danger, fontSize: 11 }, children: ["Unknown: ", comp.type] }, key);
|
|
51
|
+
return (_jsxs("div", { style: componentCard.container, children: [_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 4 }, children: [_jsx("div", { style: { fontSize: 11, fontWeight: 500 }, children: key }), _jsx("button", { style: Object.assign(Object.assign({}, base.btn), { padding: '2px 6px' }), title: "Remove Component", onClick: () => updateNode(n => {
|
|
57
52
|
const _a = n.components || {}, _b = key, _ = _a[_b], rest = __rest(_a, [typeof _b === "symbol" ? _b : _b + ""]);
|
|
58
53
|
return Object.assign(Object.assign({}, n), { components: rest });
|
|
59
54
|
}), children: "\u2715" })] }), def.Editor && (_jsx(def.Editor, { component: comp, node: node, onUpdate: (newProps) => updateNode(n => (Object.assign(Object.assign({}, n), { components: Object.assign(Object.assign({}, n.components), { [key]: Object.assign(Object.assign({}, comp), { properties: Object.assign(Object.assign({}, comp.properties), newProps) }) }) }))), basePath: basePath }))] }, key));
|
|
@@ -6,7 +6,8 @@ import { Physics } from "@react-three/rapier";
|
|
|
6
6
|
import EditorUI from "./EditorUI";
|
|
7
7
|
import { base, toolbar } from "./styles";
|
|
8
8
|
import { EditorContext } from "./EditorContext";
|
|
9
|
-
import { exportGLB } from "./utils";
|
|
9
|
+
import { exportGLB, createModelNode, createImageNode } from "./utils";
|
|
10
|
+
import { parseModelFromFile } from "../dragdrop/modelLoader";
|
|
10
11
|
const DEFAULT_PREFAB = {
|
|
11
12
|
id: "prefab-default",
|
|
12
13
|
name: "New Prefab",
|
|
@@ -111,6 +112,61 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, onPr
|
|
|
111
112
|
if (canvas)
|
|
112
113
|
canvasRef.current = canvas;
|
|
113
114
|
}, []);
|
|
115
|
+
// --- Drag & drop files to add nodes ---
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
const IMAGE_EXTS = ['png', 'jpg', 'jpeg', 'webp', 'gif', 'bmp', 'svg'];
|
|
118
|
+
const MODEL_EXTS = ['glb', 'gltf', 'fbx'];
|
|
119
|
+
function handleDragOver(e) {
|
|
120
|
+
e.preventDefault();
|
|
121
|
+
e.stopPropagation();
|
|
122
|
+
}
|
|
123
|
+
function handleDrop(e) {
|
|
124
|
+
var _a;
|
|
125
|
+
e.preventDefault();
|
|
126
|
+
e.stopPropagation();
|
|
127
|
+
const files = ((_a = e.dataTransfer) === null || _a === void 0 ? void 0 : _a.files) ? Array.from(e.dataTransfer.files) : [];
|
|
128
|
+
files.forEach(file => {
|
|
129
|
+
var _a, _b;
|
|
130
|
+
const ext = (_a = file.name.split('.').pop()) === null || _a === void 0 ? void 0 : _a.toLowerCase();
|
|
131
|
+
if (!ext)
|
|
132
|
+
return;
|
|
133
|
+
const baseName = file.name.replace(/\.[^.]+$/, '');
|
|
134
|
+
if (MODEL_EXTS.includes(ext)) {
|
|
135
|
+
const modelPath = `models/${file.name}`;
|
|
136
|
+
const newNode = createModelNode(modelPath, baseName);
|
|
137
|
+
updatePrefab(prev => {
|
|
138
|
+
var _a;
|
|
139
|
+
return (Object.assign(Object.assign({}, prev), { root: Object.assign(Object.assign({}, prev.root), { children: [...((_a = prev.root.children) !== null && _a !== void 0 ? _a : []), newNode] }) }));
|
|
140
|
+
});
|
|
141
|
+
parseModelFromFile(file).then(result => {
|
|
142
|
+
var _a;
|
|
143
|
+
if (result.success && result.model) {
|
|
144
|
+
(_a = prefabRootRef.current) === null || _a === void 0 ? void 0 : _a.injectModel(modelPath, result.model);
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
console.error('Drop parse error:', result.error);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
else if (IMAGE_EXTS.includes(ext)) {
|
|
152
|
+
const texturePath = `textures/${file.name}`;
|
|
153
|
+
const newNode = createImageNode(texturePath, baseName);
|
|
154
|
+
updatePrefab(prev => {
|
|
155
|
+
var _a;
|
|
156
|
+
return (Object.assign(Object.assign({}, prev), { root: Object.assign(Object.assign({}, prev.root), { children: [...((_a = prev.root.children) !== null && _a !== void 0 ? _a : []), newNode] }) }));
|
|
157
|
+
});
|
|
158
|
+
// Inject a blob URL texture so it renders immediately
|
|
159
|
+
(_b = prefabRootRef.current) === null || _b === void 0 ? void 0 : _b.injectTexture(texturePath, file);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
window.addEventListener('dragover', handleDragOver);
|
|
164
|
+
window.addEventListener('drop', handleDrop);
|
|
165
|
+
return () => {
|
|
166
|
+
window.removeEventListener('dragover', handleDragOver);
|
|
167
|
+
window.removeEventListener('drop', handleDrop);
|
|
168
|
+
};
|
|
169
|
+
}, [loadedPrefab]);
|
|
114
170
|
useImperativeHandle(ref, () => ({
|
|
115
171
|
screenshot: handleScreenshot,
|
|
116
172
|
exportGLB: handleExportGLB,
|
|
@@ -4,6 +4,8 @@ import { Prefab, GameObject as GameObjectType } from "./types";
|
|
|
4
4
|
export interface PrefabRootRef {
|
|
5
5
|
root: Group | null;
|
|
6
6
|
rigidBodyRefs: Map<string, any>;
|
|
7
|
+
injectModel: (filename: string, model: Object3D) => void;
|
|
8
|
+
injectTexture: (filename: string, file: File) => void;
|
|
7
9
|
}
|
|
8
10
|
export declare const PrefabRoot: import("react").ForwardRefExoticComponent<{
|
|
9
11
|
editMode?: boolean;
|
|
@@ -33,10 +33,25 @@ export const PrefabRoot = forwardRef(({ editMode, data, onPrefabChange, selected
|
|
|
33
33
|
const rigidBodyRefs = useRef(new Map());
|
|
34
34
|
const [selectedObject, setSelectedObject] = useState(null);
|
|
35
35
|
const rootRef = useRef(null);
|
|
36
|
+
const injectModel = useCallback((filename, model) => {
|
|
37
|
+
setModels(m => (Object.assign(Object.assign({}, m), { [filename]: model })));
|
|
38
|
+
}, []);
|
|
39
|
+
const injectTexture = useCallback((filename, file) => {
|
|
40
|
+
loading.current.add(filename);
|
|
41
|
+
const url = URL.createObjectURL(file);
|
|
42
|
+
const loader = new TextureLoader();
|
|
43
|
+
loader.load(url, tex => {
|
|
44
|
+
tex.colorSpace = SRGBColorSpace;
|
|
45
|
+
setTextures(t => (Object.assign(Object.assign({}, t), { [filename]: tex })));
|
|
46
|
+
URL.revokeObjectURL(url);
|
|
47
|
+
}, undefined, () => URL.revokeObjectURL(url));
|
|
48
|
+
}, []);
|
|
36
49
|
useImperativeHandle(ref, () => ({
|
|
37
50
|
root: rootRef.current,
|
|
38
|
-
rigidBodyRefs: rigidBodyRefs.current
|
|
39
|
-
|
|
51
|
+
rigidBodyRefs: rigidBodyRefs.current,
|
|
52
|
+
injectModel,
|
|
53
|
+
injectTexture
|
|
54
|
+
}), [injectModel, injectTexture]);
|
|
40
55
|
const registerRef = useCallback((id, obj) => {
|
|
41
56
|
objectRefs.current[id] = obj;
|
|
42
57
|
if (id === selectedId)
|
|
@@ -1,28 +1,30 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { colors } from '../styles';
|
|
3
4
|
// ============================================================================
|
|
4
|
-
// Shared Styles
|
|
5
|
+
// Shared Styles (derived from shared color tokens)
|
|
5
6
|
// ============================================================================
|
|
6
|
-
// Shared styles
|
|
7
7
|
const styles = {
|
|
8
8
|
input: {
|
|
9
9
|
width: '80px',
|
|
10
|
-
backgroundColor:
|
|
11
|
-
border:
|
|
12
|
-
padding: '
|
|
13
|
-
fontSize: '
|
|
14
|
-
color:
|
|
10
|
+
backgroundColor: colors.bgInput,
|
|
11
|
+
border: `1px solid ${colors.border}`,
|
|
12
|
+
padding: '3px 6px',
|
|
13
|
+
fontSize: '11px',
|
|
14
|
+
color: colors.text,
|
|
15
15
|
fontFamily: 'monospace',
|
|
16
16
|
outline: 'none',
|
|
17
17
|
textAlign: 'right',
|
|
18
|
+
borderRadius: 3,
|
|
18
19
|
},
|
|
19
20
|
label: {
|
|
20
21
|
display: 'block',
|
|
21
|
-
fontSize: '
|
|
22
|
-
color:
|
|
22
|
+
fontSize: '10px',
|
|
23
|
+
color: colors.textMuted,
|
|
23
24
|
textTransform: 'uppercase',
|
|
24
25
|
letterSpacing: '0.05em',
|
|
25
26
|
marginBottom: 2,
|
|
27
|
+
fontWeight: 500,
|
|
26
28
|
},
|
|
27
29
|
};
|
|
28
30
|
export function Input({ value, onChange, step, min, max, style, label }) {
|
|
@@ -161,23 +163,23 @@ export function Vector3Input({ label, value, onChange, snap }) {
|
|
|
161
163
|
e.target.releasePointerCapture(e.pointerId);
|
|
162
164
|
};
|
|
163
165
|
const axes = [
|
|
164
|
-
{ key: "x", color: '
|
|
165
|
-
{ key: "y", color: '
|
|
166
|
-
{ key: "z", color: '
|
|
166
|
+
{ key: "x", color: '#e06c75', index: 0 },
|
|
167
|
+
{ key: "y", color: '#98c379', index: 1 },
|
|
168
|
+
{ key: "z", color: '#61afef', index: 2 }
|
|
167
169
|
];
|
|
168
170
|
return (_jsxs("div", { style: { marginBottom: 8 }, children: [_jsx("label", { style: Object.assign(Object.assign({}, styles.label), { marginBottom: 4 }), children: label }), _jsx("div", { style: { display: 'flex', gap: 4 }, children: axes.map(({ key, color, index }) => (_jsxs("div", { style: {
|
|
169
171
|
flex: 1,
|
|
170
172
|
display: 'flex',
|
|
171
173
|
alignItems: 'center',
|
|
172
174
|
gap: 4,
|
|
173
|
-
backgroundColor:
|
|
174
|
-
border:
|
|
175
|
-
borderRadius:
|
|
175
|
+
backgroundColor: colors.bgInput,
|
|
176
|
+
border: `1px solid ${colors.border}`,
|
|
177
|
+
borderRadius: 3,
|
|
176
178
|
padding: '4px 6px',
|
|
177
|
-
minHeight:
|
|
179
|
+
minHeight: 28,
|
|
178
180
|
}, children: [_jsx("span", { style: {
|
|
179
|
-
fontSize:
|
|
180
|
-
fontWeight:
|
|
181
|
+
fontSize: 11,
|
|
182
|
+
fontWeight: 600,
|
|
181
183
|
color,
|
|
182
184
|
width: 12,
|
|
183
185
|
cursor: 'ew-resize',
|
|
@@ -186,8 +188,8 @@ export function Vector3Input({ label, value, onChange, snap }) {
|
|
|
186
188
|
flex: 1,
|
|
187
189
|
backgroundColor: 'transparent',
|
|
188
190
|
border: 'none',
|
|
189
|
-
fontSize:
|
|
190
|
-
color:
|
|
191
|
+
fontSize: 11,
|
|
192
|
+
color: colors.text,
|
|
191
193
|
fontFamily: 'monospace',
|
|
192
194
|
outline: 'none',
|
|
193
195
|
width: '100%',
|
|
@@ -209,11 +211,11 @@ export function ColorInput({ label, value, onChange }) {
|
|
|
209
211
|
return (_jsxs("div", { children: [label && _jsx(Label, { children: label }), _jsxs("div", { style: { display: 'flex', gap: 4, justifyContent: 'space-between' }, children: [_jsx("input", { type: "color", style: {
|
|
210
212
|
height: 32,
|
|
211
213
|
width: 48,
|
|
212
|
-
backgroundColor:
|
|
213
|
-
border:
|
|
214
|
-
borderRadius:
|
|
214
|
+
backgroundColor: colors.bgInput,
|
|
215
|
+
border: `1px solid ${colors.border}`,
|
|
216
|
+
borderRadius: 3,
|
|
215
217
|
cursor: 'pointer',
|
|
216
|
-
padding:
|
|
218
|
+
padding: 2,
|
|
217
219
|
flexShrink: 0,
|
|
218
220
|
}, value: value, onChange: e => onChange(e.target.value) }), _jsx("input", { type: "text", style: Object.assign({}, styles.input), value: value, onChange: e => onChange(e.target.value) })] })] }));
|
|
219
221
|
}
|
|
@@ -224,8 +226,7 @@ export function BooleanInput({ label, value, onChange }) {
|
|
|
224
226
|
return (_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [label && _jsx(Label, { children: label }), _jsx("input", { type: "checkbox", style: {
|
|
225
227
|
height: 16,
|
|
226
228
|
width: 16,
|
|
227
|
-
|
|
228
|
-
border: '1px solid rgba(34, 211, 238, 0.3)',
|
|
229
|
+
accentColor: colors.accent,
|
|
229
230
|
cursor: 'pointer',
|
|
230
231
|
}, checked: value, onChange: e => onChange(e.target.checked) })] }));
|
|
231
232
|
}
|
|
@@ -13,6 +13,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
13
13
|
import { SingleTextureViewer, TextureListViewer } from '../../assetviewer/page';
|
|
14
14
|
import { useEffect, useState } from 'react';
|
|
15
15
|
import { FieldRenderer, Input } from './Input';
|
|
16
|
+
import { colors } from '../styles';
|
|
16
17
|
import { useMemo } from 'react';
|
|
17
18
|
import { RepeatWrapping, ClampToEdgeWrapping, SRGBColorSpace, NearestFilter, LinearFilter, NearestMipmapNearestFilter, NearestMipmapLinearFilter, LinearMipmapNearestFilter, LinearMipmapLinearFilter } from 'three';
|
|
18
19
|
function TexturePicker({ value, onChange, basePath }) {
|
|
@@ -24,9 +25,15 @@ function TexturePicker({ value, onChange, basePath }) {
|
|
|
24
25
|
.then(data => setTextureFiles(Array.isArray(data) ? data : data.files || []))
|
|
25
26
|
.catch(console.error);
|
|
26
27
|
}, [basePath]);
|
|
27
|
-
|
|
28
|
+
// Only show 3D preview for server-hosted textures (starting with / or http)
|
|
29
|
+
const canPreview = value && (value.startsWith('/') || value.startsWith('http'));
|
|
30
|
+
return (_jsxs("div", { style: { maxHeight: 128, overflow: 'visible', position: 'relative', display: 'flex', alignItems: 'center' }, children: [canPreview
|
|
31
|
+
? _jsx(SingleTextureViewer, { file: value, basePath: basePath })
|
|
32
|
+
: value
|
|
33
|
+
? _jsx("span", { style: { fontSize: 10, opacity: 0.6, maxWidth: 100, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, children: value })
|
|
34
|
+
: null, _jsx("button", { onClick: () => setShowPicker(!showPicker), style: { padding: '4px 8px', backgroundColor: colors.bgLight, color: 'inherit', fontSize: 10, cursor: 'pointer', border: `1px solid ${colors.border}`, borderRadius: 3, marginTop: 4 }, children: showPicker ? 'Cancel' : 'Change' }), _jsx("button", { onClick: () => {
|
|
28
35
|
onChange(undefined);
|
|
29
|
-
}, style: { padding: '4px 8px', backgroundColor:
|
|
36
|
+
}, style: { padding: '4px 8px', backgroundColor: colors.bgLight, color: 'inherit', fontSize: 10, cursor: 'pointer', border: `1px solid ${colors.border}`, borderRadius: 3, marginTop: 4, marginLeft: 4 }, children: "Clear" }), showPicker && (_jsx("div", { style: { position: 'fixed', right: 60, top: 60, transform: 'translate(-100%,0%)', background: colors.bg, padding: 16, border: `1px solid ${colors.border}`, borderRadius: 4, maxHeight: '80vh', overflowY: 'auto', overflowX: 'hidden', width: 220, zIndex: 1000, boxShadow: '0 4px 16px rgba(0,0,0,0.6)' }, children: _jsx(TextureListViewer, { files: textureFiles, selected: value || undefined, onSelect: (file) => {
|
|
30
37
|
onChange(file);
|
|
31
38
|
setShowPicker(false);
|
|
32
39
|
}, basePath: basePath }) }))] }));
|
|
@@ -16,9 +16,9 @@ function ModelPicker({ value, onChange, basePath, nodeId }) {
|
|
|
16
16
|
onChange(filename);
|
|
17
17
|
setShowPicker(false);
|
|
18
18
|
};
|
|
19
|
-
return (_jsxs("div", { style: { maxHeight: 128,
|
|
19
|
+
return (_jsxs("div", { style: { maxHeight: 128, overflow: 'visible', position: 'relative', display: 'flex', alignItems: 'center' }, children: [_jsx(SingleModelViewer, { file: value ? `/${value}` : undefined, basePath: basePath }), _jsx("button", { onClick: () => setShowPicker(!showPicker), style: { padding: '4px 8px', backgroundColor: '#1f2937', color: 'inherit', fontSize: 10, cursor: 'pointer', border: '1px solid rgba(34, 211, 238, 0.3)', marginTop: 4 }, children: showPicker ? 'Cancel' : 'Change' }), _jsx("button", { onClick: () => {
|
|
20
20
|
onChange(undefined);
|
|
21
|
-
}, style: { padding: '4px 8px', backgroundColor: '#1f2937', color: 'inherit', fontSize: 10, cursor: 'pointer', border: '1px solid rgba(34, 211, 238, 0.3)', marginTop: 4, marginLeft: 4 }, children: "Clear" }), showPicker && (_jsx("div", { style: { position: 'fixed',
|
|
21
|
+
}, style: { padding: '4px 8px', backgroundColor: '#1f2937', color: 'inherit', fontSize: 10, cursor: 'pointer', border: '1px solid rgba(34, 211, 238, 0.3)', marginTop: 4, marginLeft: 4 }, children: "Clear" }), showPicker && (_jsx("div", { style: { position: 'fixed', right: 60, top: 60, transform: 'translate(-100%,0%)', background: 'rgba(0,0,0,0.9)', padding: 16, border: '1px solid rgba(34, 211, 238, 0.3)', maxHeight: '80vh', overflowY: 'auto', overflowX: 'hidden', width: 220, zIndex: 1000 }, children: _jsx(ModelListViewer, { files: modelFiles, selected: value ? `/${value}` : undefined, onSelect: handleModelSelect, basePath: basePath }, nodeId) }))] }));
|
|
22
22
|
}
|
|
23
23
|
function ModelComponentEditor({ component, node, onUpdate, basePath = "" }) {
|
|
24
24
|
const fields = [
|
|
@@ -93,7 +93,7 @@ const physicsFields = [
|
|
|
93
93
|
},
|
|
94
94
|
];
|
|
95
95
|
function PhysicsComponentEditor({ component, onUpdate }) {
|
|
96
|
-
return (_jsx(FieldRenderer, { fields: physicsFields, values: component.properties, onChange:
|
|
96
|
+
return (_jsx(FieldRenderer, { fields: physicsFields, values: component.properties, onChange: onUpdate }));
|
|
97
97
|
}
|
|
98
98
|
function PhysicsComponentView({ properties, children, position, rotation, scale, editMode, nodeId, registerRigidBodyRef }) {
|
|
99
99
|
const { type, colliders, sensor, activeCollisionTypes } = properties, otherProps = __rest(properties, ["type", "colliders", "sensor", "activeCollisionTypes"]);
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useRef, useEffect } from "react";
|
|
3
3
|
import { FieldRenderer } from "./Input";
|
|
4
|
+
import { useHelper } from "@react-three/drei";
|
|
5
|
+
import { SpotLightHelper } from "three";
|
|
4
6
|
const spotLightFields = [
|
|
5
7
|
{ name: 'color', type: 'color', label: 'Color' },
|
|
6
8
|
{ name: 'intensity', type: 'number', label: 'Intensity', step: 0.1, min: 0 },
|
|
@@ -22,6 +24,7 @@ function SpotLightView({ properties, editMode }) {
|
|
|
22
24
|
const castShadow = (_f = properties.castShadow) !== null && _f !== void 0 ? _f : true;
|
|
23
25
|
const spotLightRef = useRef(null);
|
|
24
26
|
const targetRef = useRef(null);
|
|
27
|
+
useHelper(editMode ? spotLightRef : null, SpotLightHelper, color);
|
|
25
28
|
useEffect(() => {
|
|
26
29
|
if (spotLightRef.current && targetRef.current) {
|
|
27
30
|
spotLightRef.current.target = targetRef.current;
|