react-three-game 0.0.57 → 0.0.59
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/.github/copilot-instructions.md +1 -1
- package/README.md +59 -35
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/tools/assetviewer/page.js +1 -1
- package/dist/tools/dragdrop/DragDropLoader.d.ts +19 -6
- package/dist/tools/dragdrop/DragDropLoader.js +77 -40
- package/dist/tools/dragdrop/index.d.ts +4 -0
- package/dist/tools/dragdrop/index.js +2 -0
- package/dist/tools/dragdrop/modelLoader.d.ts +5 -6
- package/dist/tools/dragdrop/modelLoader.js +62 -49
- package/dist/tools/dragdrop/page.js +3 -3
- package/dist/tools/prefabeditor/EditorTree.js +24 -48
- package/dist/tools/prefabeditor/EditorTreeMenus.d.ts +33 -0
- package/dist/tools/prefabeditor/EditorTreeMenus.js +136 -0
- package/dist/tools/prefabeditor/PrefabEditor.js +1 -1
- package/dist/tools/prefabeditor/PrefabRoot.js +5 -3
- package/dist/tools/prefabeditor/components/CameraComponent.js +32 -12
- package/dist/tools/prefabeditor/components/DirectionalLightComponent.js +49 -23
- package/dist/tools/prefabeditor/components/MaterialComponent.d.ts +8 -0
- package/dist/tools/prefabeditor/components/MaterialComponent.js +11 -5
- package/dist/tools/prefabeditor/components/SpotLightComponent.js +34 -13
- package/package.json +2 -2
- package/react-three-game-skill/react-three-game/SKILL.md +63 -5
- package/react-three-game-skill/react-three-game/rules/ADVANCED_PHYSICS.md +7 -5
- package/src/index.ts +1 -1
- package/src/tools/assetviewer/page.tsx +1 -1
- package/src/tools/dragdrop/DragDropLoader.tsx +118 -55
- package/src/tools/dragdrop/index.ts +4 -0
- package/src/tools/dragdrop/modelLoader.ts +95 -50
- package/src/tools/dragdrop/page.tsx +7 -4
- package/src/tools/prefabeditor/EditorTree.tsx +56 -125
- package/src/tools/prefabeditor/EditorTreeMenus.tsx +307 -0
- package/src/tools/prefabeditor/PrefabEditor.tsx +1 -1
- package/src/tools/prefabeditor/PrefabRoot.tsx +6 -3
- package/src/tools/prefabeditor/components/CameraComponent.tsx +51 -14
- package/src/tools/prefabeditor/components/DirectionalLightComponent.tsx +59 -28
- package/src/tools/prefabeditor/components/MaterialComponent.tsx +18 -9
- package/src/tools/prefabeditor/components/SpotLightComponent.tsx +49 -18
|
@@ -1,19 +1,11 @@
|
|
|
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
|
-
};
|
|
10
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
11
2
|
import { useState } from 'react';
|
|
12
3
|
import { getComponent } from './components/ComponentRegistry';
|
|
13
|
-
import { base, colors, tree
|
|
14
|
-
import { findNode, findParent, deleteNode, cloneNode, updateNodeById
|
|
4
|
+
import { base, colors, tree } from './styles';
|
|
5
|
+
import { findNode, findParent, deleteNode, cloneNode, updateNodeById } from './utils';
|
|
15
6
|
import { useEditorContext } from './EditorContext';
|
|
16
7
|
import { Dropdown } from './Dropdown';
|
|
8
|
+
import { FileMenu, MenuTriggerButton, TreeContextMenu, TreeNodeMenu } from './EditorTreeMenus';
|
|
17
9
|
function moveNode(root, draggedId, targetId, position) {
|
|
18
10
|
const draggedNode = findNode(root, draggedId);
|
|
19
11
|
const oldParent = findParent(root, draggedId);
|
|
@@ -79,6 +71,7 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
|
|
|
79
71
|
const [collapsedIds, setCollapsedIds] = useState(new Set());
|
|
80
72
|
const [collapsed, setCollapsed] = useState(false);
|
|
81
73
|
const [searchQuery, setSearchQuery] = useState('');
|
|
74
|
+
const [contextMenu, setContextMenu] = useState(null);
|
|
82
75
|
if (!prefabData || !setPrefabData)
|
|
83
76
|
return null;
|
|
84
77
|
const toggleCollapse = (e, id) => {
|
|
@@ -128,6 +121,16 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
|
|
|
128
121
|
const handleToggleDisabled = (nodeId) => {
|
|
129
122
|
setPrefabData(prev => (Object.assign(Object.assign({}, prev), { root: updateNodeById(prev.root, nodeId, node => (Object.assign(Object.assign({}, node), { disabled: !node.disabled }))) })));
|
|
130
123
|
};
|
|
124
|
+
const closeContextMenu = () => setContextMenu(null);
|
|
125
|
+
const openContextMenu = (nodeId, x, y) => {
|
|
126
|
+
setSelectedId(nodeId);
|
|
127
|
+
setContextMenu({ nodeId, x, y });
|
|
128
|
+
};
|
|
129
|
+
const handleFocus = (nodeId) => {
|
|
130
|
+
setSelectedId(nodeId);
|
|
131
|
+
onFocusNode === null || onFocusNode === void 0 ? void 0 : onFocusNode(nodeId);
|
|
132
|
+
};
|
|
133
|
+
const renderTreeNodeMenu = (nodeId, isRoot, onClose) => (_jsx(TreeNodeMenu, { isRoot: isRoot, nodeId: nodeId, onAddChild: handleAddChild, onFocus: handleFocus, onDuplicate: isRoot ? undefined : handleDuplicate, onDelete: isRoot ? undefined : handleDelete, onClose: onClose }));
|
|
131
134
|
const handleDragStart = (e, id) => {
|
|
132
135
|
if (id === prefabData.root.id)
|
|
133
136
|
return e.preventDefault();
|
|
@@ -190,13 +193,17 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
|
|
|
190
193
|
const isDropTarget = (dropTarget === null || dropTarget === void 0 ? void 0 : dropTarget.id) === node.id;
|
|
191
194
|
const showDropBefore = isDropTarget && (dropTarget === null || dropTarget === void 0 ? void 0 : dropTarget.position) === 'before';
|
|
192
195
|
const showDropInside = isDropTarget && (dropTarget === null || dropTarget === void 0 ? void 0 : dropTarget.position) === 'inside';
|
|
193
|
-
return (_jsxs("div", { children: [_jsxs("div", { style: Object.assign(Object.assign(Object.assign({}, tree.row), (isSelected ? tree.selected : {})), { paddingLeft: `${depth * 12 + 6}px`, opacity: node.disabled ? 0.4 : 1, display: 'flex', alignItems: 'center', justifyContent: 'space-between', borderTop: showDropBefore ? `2px solid ${colors.accent}` : undefined, boxShadow: showDropInside ? `inset 0 0 0 1px ${colors.accentBorder}` : undefined }), draggable: !isRoot, onClick: (e) => { e.stopPropagation(); setSelectedId(node.id); },
|
|
196
|
+
return (_jsxs("div", { children: [_jsxs("div", { style: Object.assign(Object.assign(Object.assign({}, tree.row), (isSelected ? tree.selected : {})), { paddingLeft: `${depth * 12 + 6}px`, opacity: node.disabled ? 0.4 : 1, display: 'flex', alignItems: 'center', justifyContent: 'space-between', borderTop: showDropBefore ? `2px solid ${colors.accent}` : undefined, boxShadow: showDropInside ? `inset 0 0 0 1px ${colors.accentBorder}` : undefined }), draggable: !isRoot, onClick: (e) => { e.stopPropagation(); setSelectedId(node.id); }, onContextMenu: (e) => {
|
|
197
|
+
e.preventDefault();
|
|
198
|
+
e.stopPropagation();
|
|
199
|
+
openContextMenu(node.id, e.clientX, e.clientY);
|
|
200
|
+
}, onDragStart: (e) => handleDragStart(e, node.id), onDragEnd: () => { setDraggedId(null); setDropTarget(null); }, onDragOver: (e) => handleDragOver(e, node.id, isRoot), onDragLeave: (e) => handleDragLeave(e, node.id), onDrop: (e) => handleDrop(e, node.id, isRoot), children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', flex: 1, minWidth: 0 }, children: [_jsx("span", { style: {
|
|
194
201
|
width: 12,
|
|
195
202
|
opacity: 0.6,
|
|
196
203
|
marginRight: 4,
|
|
197
204
|
cursor: 'pointer',
|
|
198
205
|
visibility: hasChildren ? 'visible' : 'hidden'
|
|
199
|
-
}, 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 })] }), !isRoot && (_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 2 }, children: [_jsx(Dropdown, { placement: "bottom-end", trigger: ({ ref, toggle }) => (_jsx(
|
|
206
|
+
}, 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 })] }), !isRoot && (_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 2 }, children: [_jsx(Dropdown, { placement: "bottom-end", trigger: ({ ref, toggle }) => (_jsx(MenuTriggerButton, { buttonRef: ref, onToggle: toggle, title: "Node Actions", style: {
|
|
200
207
|
background: 'none',
|
|
201
208
|
border: 'none',
|
|
202
209
|
cursor: 'pointer',
|
|
@@ -204,10 +211,7 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
|
|
|
204
211
|
fontSize: 14,
|
|
205
212
|
opacity: 0.7,
|
|
206
213
|
color: 'inherit',
|
|
207
|
-
},
|
|
208
|
-
e.stopPropagation();
|
|
209
|
-
toggle();
|
|
210
|
-
}, title: "Node Actions", children: "\u22EF" })), children: (close) => (_jsxs("div", { style: Object.assign(Object.assign({}, menu.container), { position: 'static' }), onClick: (e) => e.stopPropagation(), children: [_jsx("button", { style: menu.item, onClick: () => { handleAddChild(node.id); close(); }, children: "Add Child" }), _jsx("button", { style: menu.item, onClick: () => { setSelectedId(node.id); onFocusNode === null || onFocusNode === void 0 ? void 0 : onFocusNode(node.id); close(); }, children: "Focus Camera" }), _jsx("button", { style: menu.item, onClick: () => { handleDuplicate(node.id); close(); }, children: "Duplicate" }), _jsx("button", { style: Object.assign(Object.assign({}, menu.item), menu.danger), onClick: () => { handleDelete(node.id); close(); }, children: "Delete" })] })) }), _jsx("button", { style: {
|
|
214
|
+
}, children: "\u22EF" })), children: (close) => renderTreeNodeMenu(node.id, false, close) }), _jsx("button", { style: {
|
|
211
215
|
background: 'none',
|
|
212
216
|
border: 'none',
|
|
213
217
|
cursor: 'pointer',
|
|
@@ -218,7 +222,7 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
|
|
|
218
222
|
}, onClick: (e) => {
|
|
219
223
|
e.stopPropagation();
|
|
220
224
|
handleToggleDisabled(node.id);
|
|
221
|
-
}, title: node.disabled ? 'Enable' : 'Disable', children: node.disabled ? '◎' : '◉' })] })), isRoot && (_jsx(Dropdown, { placement: "bottom-end", trigger: ({ ref, toggle }) => (_jsx(
|
|
225
|
+
}, title: node.disabled ? 'Enable' : 'Disable', children: node.disabled ? '◎' : '◉' })] })), isRoot && (_jsx(Dropdown, { placement: "bottom-end", trigger: ({ ref, toggle }) => (_jsx(MenuTriggerButton, { buttonRef: ref, onToggle: toggle, title: "Scene Actions", style: {
|
|
222
226
|
background: 'none',
|
|
223
227
|
border: 'none',
|
|
224
228
|
cursor: 'pointer',
|
|
@@ -226,35 +230,7 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
|
|
|
226
230
|
fontSize: 14,
|
|
227
231
|
opacity: 0.7,
|
|
228
232
|
color: 'inherit',
|
|
229
|
-
},
|
|
230
|
-
e.stopPropagation();
|
|
231
|
-
toggle();
|
|
232
|
-
}, title: "Scene Actions", children: "\u22EF" })), children: (close) => (_jsxs("div", { style: Object.assign(Object.assign({}, menu.container), { position: 'static' }), onClick: (e) => e.stopPropagation(), children: [_jsx("button", { style: menu.item, onClick: () => { handleAddChild(node.id); close(); }, children: "Add Child" }), _jsx("button", { style: menu.item, onClick: () => { setSelectedId(node.id); onFocusNode === null || onFocusNode === void 0 ? void 0 : onFocusNode(node.id); close(); }, children: "Focus Camera" })] })) }))] }), !isCollapsed && node.children && node.children.map(child => renderNode(child, depth + 1))] }, node.id));
|
|
233
|
+
}, children: "\u22EF" })), children: (close) => renderTreeNodeMenu(node.id, true, close) }))] }), !isCollapsed && node.children && node.children.map(child => renderNode(child, depth + 1))] }, node.id));
|
|
233
234
|
};
|
|
234
|
-
return (
|
|
235
|
-
}
|
|
236
|
-
function FileMenu({ prefabData, setPrefabData, onClose }) {
|
|
237
|
-
const { onScreenshot, onExportGLB } = useEditorContext();
|
|
238
|
-
const handleLoad = () => __awaiter(this, void 0, void 0, function* () {
|
|
239
|
-
const loadedPrefab = yield loadJson();
|
|
240
|
-
if (!loadedPrefab)
|
|
241
|
-
return;
|
|
242
|
-
setPrefabData(loadedPrefab);
|
|
243
|
-
onClose();
|
|
244
|
-
});
|
|
245
|
-
const handleSave = () => {
|
|
246
|
-
saveJson(prefabData, "prefab");
|
|
247
|
-
onClose();
|
|
248
|
-
};
|
|
249
|
-
const handleLoadIntoScene = () => __awaiter(this, void 0, void 0, function* () {
|
|
250
|
-
const loadedPrefab = yield loadJson();
|
|
251
|
-
if (!loadedPrefab)
|
|
252
|
-
return;
|
|
253
|
-
setPrefabData(prev => (Object.assign(Object.assign({}, prev), { root: updateNodeById(prev.root, prev.root.id, root => {
|
|
254
|
-
var _a;
|
|
255
|
-
return (Object.assign(Object.assign({}, root), { children: [...((_a = root.children) !== null && _a !== void 0 ? _a : []), regenerateIds(loadedPrefab.root)] }));
|
|
256
|
-
}) })));
|
|
257
|
-
onClose();
|
|
258
|
-
});
|
|
259
|
-
return (_jsxs("div", { style: Object.assign(Object.assign({}, menu.container), { position: 'static' }), 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" })] }));
|
|
235
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { style: Object.assign(Object.assign({}, tree.panel), { width: collapsed ? 'auto' : 224 }), 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" }), _jsx(Dropdown, { placement: "bottom-end", trigger: ({ ref, toggle }) => (_jsx(MenuTriggerButton, { buttonRef: ref, onToggle: toggle, title: "Menu", style: Object.assign(Object.assign({}, base.btn), { padding: '2px 6px', fontSize: 10 }), children: "\u22EE" })), children: (close) => (_jsx(FileMenu, { prefabData: prefabData, setPrefabData: setPrefabData, onClose: close })) })] }))] }), !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) })] }))] }), _jsx(TreeContextMenu, { contextMenu: contextMenu, onClose: closeContextMenu, children: (nodeId, close) => renderTreeNodeMenu(nodeId, nodeId === prefabData.root.id, close) })] }));
|
|
260
236
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Dispatch, SetStateAction } from 'react';
|
|
2
|
+
import { Prefab } from './types';
|
|
3
|
+
export type TreeContextMenuState = {
|
|
4
|
+
nodeId: string;
|
|
5
|
+
x: number;
|
|
6
|
+
y: number;
|
|
7
|
+
} | null;
|
|
8
|
+
export declare function MenuTriggerButton({ buttonRef, onToggle, title, style, children, }: {
|
|
9
|
+
buttonRef: React.RefObject<HTMLButtonElement | null>;
|
|
10
|
+
onToggle: () => void;
|
|
11
|
+
title: string;
|
|
12
|
+
style: React.CSSProperties;
|
|
13
|
+
children: React.ReactNode;
|
|
14
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
export declare function TreeNodeMenu({ isRoot, nodeId, onAddChild, onFocus, onDuplicate, onDelete, onClose, }: {
|
|
16
|
+
isRoot: boolean;
|
|
17
|
+
nodeId: string;
|
|
18
|
+
onAddChild: (parentId: string) => void;
|
|
19
|
+
onFocus: (nodeId: string) => void;
|
|
20
|
+
onDuplicate?: (nodeId: string) => void;
|
|
21
|
+
onDelete?: (nodeId: string) => void;
|
|
22
|
+
onClose: () => void;
|
|
23
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
24
|
+
export declare function TreeContextMenu({ contextMenu, onClose, children, }: {
|
|
25
|
+
contextMenu: TreeContextMenuState;
|
|
26
|
+
onClose: () => void;
|
|
27
|
+
children: (nodeId: string, onClose: () => void) => React.ReactNode;
|
|
28
|
+
}): import("react").ReactPortal | null;
|
|
29
|
+
export declare function FileMenu({ prefabData, setPrefabData, onClose }: {
|
|
30
|
+
prefabData: Prefab;
|
|
31
|
+
setPrefabData: Dispatch<SetStateAction<Prefab>>;
|
|
32
|
+
onClose: () => void;
|
|
33
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,136 @@
|
|
|
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
|
+
};
|
|
10
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
11
|
+
import { useEffect, useRef, useState } from 'react';
|
|
12
|
+
import { createPortal } from 'react-dom';
|
|
13
|
+
import { menu } from './styles';
|
|
14
|
+
import { useEditorContext } from './EditorContext';
|
|
15
|
+
import { getComponent } from './components/ComponentRegistry';
|
|
16
|
+
import { loadJson, saveJson, regenerateIds, updateNodeById } from './utils';
|
|
17
|
+
function createEmptyPrefab() {
|
|
18
|
+
var _a;
|
|
19
|
+
return {
|
|
20
|
+
id: crypto.randomUUID(),
|
|
21
|
+
name: 'New Scene',
|
|
22
|
+
root: {
|
|
23
|
+
id: crypto.randomUUID(),
|
|
24
|
+
name: 'Scene',
|
|
25
|
+
components: {
|
|
26
|
+
transform: {
|
|
27
|
+
type: 'Transform',
|
|
28
|
+
properties: Object.assign({}, (_a = getComponent('Transform')) === null || _a === void 0 ? void 0 : _a.defaultProperties)
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
children: []
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function MenuPanel({ children, style, }) {
|
|
36
|
+
return (_jsx("div", { style: Object.assign(Object.assign(Object.assign({}, menu.container), { position: 'static' }), style), onClick: (e) => e.stopPropagation(), children: children }));
|
|
37
|
+
}
|
|
38
|
+
function MenuItemButton({ children, onClick, danger = false, style, }) {
|
|
39
|
+
return (_jsx("button", { style: danger ? Object.assign(Object.assign(Object.assign({}, menu.item), menu.danger), style) : Object.assign(Object.assign({}, menu.item), style), onClick: onClick, children: children }));
|
|
40
|
+
}
|
|
41
|
+
function MenuSubmenu({ label, children, }) {
|
|
42
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
43
|
+
return (_jsxs("div", { style: { position: 'relative' }, onMouseEnter: () => setIsOpen(true), onMouseLeave: () => setIsOpen(false), children: [_jsxs(MenuItemButton, { onClick: () => setIsOpen(open => !open), style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12 }, children: [_jsx("span", { children: label }), _jsx("span", { "aria-hidden": "true", children: "\u203A" })] }), isOpen && (_jsx("div", { style: {
|
|
44
|
+
position: 'absolute',
|
|
45
|
+
top: 0,
|
|
46
|
+
left: '100%',
|
|
47
|
+
zIndex: 1,
|
|
48
|
+
}, children: _jsx(MenuPanel, { children: children }) }))] }));
|
|
49
|
+
}
|
|
50
|
+
export function MenuTriggerButton({ buttonRef, onToggle, title, style, children, }) {
|
|
51
|
+
return (_jsx("button", { ref: buttonRef, style: style, onClick: (e) => {
|
|
52
|
+
e.stopPropagation();
|
|
53
|
+
onToggle();
|
|
54
|
+
}, title: title, children: children }));
|
|
55
|
+
}
|
|
56
|
+
export function TreeNodeMenu({ isRoot, nodeId, onAddChild, onFocus, onDuplicate, onDelete, onClose, }) {
|
|
57
|
+
return (_jsxs(MenuPanel, { children: [_jsx(MenuItemButton, { onClick: () => { onAddChild(nodeId); onClose(); }, children: "Add Child" }), _jsx(MenuItemButton, { onClick: () => { onFocus(nodeId); onClose(); }, children: "Focus Camera" }), !isRoot && onDuplicate && (_jsx(MenuItemButton, { onClick: () => { onDuplicate(nodeId); onClose(); }, children: "Duplicate" })), !isRoot && onDelete && (_jsx(MenuItemButton, { danger: true, onClick: () => { onDelete(nodeId); onClose(); }, children: "Delete" }))] }));
|
|
58
|
+
}
|
|
59
|
+
export function TreeContextMenu({ contextMenu, onClose, children, }) {
|
|
60
|
+
var _a, _b;
|
|
61
|
+
const panelRef = useRef(null);
|
|
62
|
+
const [position, setPosition] = useState(null);
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
if (!contextMenu)
|
|
65
|
+
return;
|
|
66
|
+
const handlePointerDown = (event) => {
|
|
67
|
+
var _a;
|
|
68
|
+
const target = event.target;
|
|
69
|
+
if (!target)
|
|
70
|
+
return;
|
|
71
|
+
if ((_a = panelRef.current) === null || _a === void 0 ? void 0 : _a.contains(target))
|
|
72
|
+
return;
|
|
73
|
+
onClose();
|
|
74
|
+
};
|
|
75
|
+
const handleKeyDown = (event) => {
|
|
76
|
+
if (event.key === 'Escape')
|
|
77
|
+
onClose();
|
|
78
|
+
};
|
|
79
|
+
document.addEventListener('pointerdown', handlePointerDown);
|
|
80
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
81
|
+
return () => {
|
|
82
|
+
document.removeEventListener('pointerdown', handlePointerDown);
|
|
83
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
84
|
+
};
|
|
85
|
+
}, [contextMenu, onClose]);
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
if (!contextMenu || !panelRef.current || typeof window === 'undefined')
|
|
88
|
+
return;
|
|
89
|
+
const panelRect = panelRef.current.getBoundingClientRect();
|
|
90
|
+
const left = Math.max(8, Math.min(contextMenu.x, window.innerWidth - panelRect.width - 8));
|
|
91
|
+
const top = Math.max(8, Math.min(contextMenu.y, window.innerHeight - panelRect.height - 8));
|
|
92
|
+
setPosition({ left, top });
|
|
93
|
+
}, [contextMenu]);
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
if (!contextMenu) {
|
|
96
|
+
setPosition(null);
|
|
97
|
+
}
|
|
98
|
+
}, [contextMenu]);
|
|
99
|
+
if (!contextMenu || typeof document === 'undefined')
|
|
100
|
+
return null;
|
|
101
|
+
return createPortal(_jsx("div", { ref: panelRef, style: {
|
|
102
|
+
position: 'fixed',
|
|
103
|
+
left: (_a = position === null || position === void 0 ? void 0 : position.left) !== null && _a !== void 0 ? _a : contextMenu.x,
|
|
104
|
+
top: (_b = position === null || position === void 0 ? void 0 : position.top) !== null && _b !== void 0 ? _b : contextMenu.y,
|
|
105
|
+
zIndex: 1000,
|
|
106
|
+
}, onMouseLeave: onClose, onContextMenu: (e) => e.preventDefault(), children: children(contextMenu.nodeId, onClose) }), document.body);
|
|
107
|
+
}
|
|
108
|
+
export function FileMenu({ prefabData, setPrefabData, onClose }) {
|
|
109
|
+
const { onScreenshot, onExportGLB } = useEditorContext();
|
|
110
|
+
const handleNewScene = () => {
|
|
111
|
+
setPrefabData(createEmptyPrefab());
|
|
112
|
+
onClose();
|
|
113
|
+
};
|
|
114
|
+
const handleNewSceneFromPrefab = () => __awaiter(this, void 0, void 0, function* () {
|
|
115
|
+
const loadedPrefab = yield loadJson();
|
|
116
|
+
if (!loadedPrefab)
|
|
117
|
+
return;
|
|
118
|
+
setPrefabData(loadedPrefab);
|
|
119
|
+
onClose();
|
|
120
|
+
});
|
|
121
|
+
const handleSave = () => {
|
|
122
|
+
saveJson(prefabData, 'prefab');
|
|
123
|
+
onClose();
|
|
124
|
+
};
|
|
125
|
+
const handleLoadIntoScene = () => __awaiter(this, void 0, void 0, function* () {
|
|
126
|
+
const loadedPrefab = yield loadJson();
|
|
127
|
+
if (!loadedPrefab)
|
|
128
|
+
return;
|
|
129
|
+
setPrefabData(prev => (Object.assign(Object.assign({}, prev), { root: updateNodeById(prev.root, prev.root.id, root => {
|
|
130
|
+
var _a;
|
|
131
|
+
return (Object.assign(Object.assign({}, root), { children: [...((_a = root.children) !== null && _a !== void 0 ? _a : []), regenerateIds(loadedPrefab.root)] }));
|
|
132
|
+
}) })));
|
|
133
|
+
onClose();
|
|
134
|
+
});
|
|
135
|
+
return (_jsxs(MenuPanel, { style: { overflow: 'visible' }, children: [_jsxs(MenuSubmenu, { label: "File", children: [_jsx(MenuItemButton, { onClick: handleNewScene, children: "New Scene" }), _jsx(MenuItemButton, { onClick: handleNewSceneFromPrefab, children: "New Scene from Prefab" }), _jsx(MenuItemButton, { onClick: handleLoadIntoScene, children: "Load Prefab into Scene" }), _jsx(MenuItemButton, { onClick: handleSave, children: "Save Prefab" })] }), _jsxs(MenuSubmenu, { label: "Export", children: [_jsx(MenuItemButton, { onClick: () => { onExportGLB === null || onExportGLB === void 0 ? void 0 : onExportGLB(); onClose(); }, children: "GLB" }), _jsx(MenuItemButton, { onClick: () => { onScreenshot === null || onScreenshot === void 0 ? void 0 : onScreenshot(); onClose(); }, children: "PNG" })] })] }));
|
|
136
|
+
}
|
|
@@ -7,7 +7,7 @@ import EditorUI from "./EditorUI";
|
|
|
7
7
|
import { base, toolbar } from "./styles";
|
|
8
8
|
import { EditorContext } from "./EditorContext";
|
|
9
9
|
import { exportGLB, createModelNode, createImageNode } from "./utils";
|
|
10
|
-
import { parseModelFromFile } from "../dragdrop
|
|
10
|
+
import { parseModelFromFile } from "../dragdrop";
|
|
11
11
|
const DEFAULT_PREFAB = {
|
|
12
12
|
id: "prefab-default",
|
|
13
13
|
name: "New Prefab",
|
|
@@ -13,7 +13,7 @@ import { forwardRef, useCallback, useContext, useEffect, useImperativeHandle, us
|
|
|
13
13
|
import { BoxHelper, Euler, Matrix4, Quaternion, SRGBColorSpace, TextureLoader, Vector3, } from "three";
|
|
14
14
|
import { getComponent, registerComponent, getNonComposableKeys } from "./components/ComponentRegistry";
|
|
15
15
|
import components from "./components";
|
|
16
|
-
import { loadModel } from "../dragdrop
|
|
16
|
+
import { loadModel } from "../dragdrop";
|
|
17
17
|
import { GameInstance, GameInstanceProvider, useInstanceCheck } from "./InstanceProvider";
|
|
18
18
|
import { focusCameraOnObject, updateNode } from "./utils";
|
|
19
19
|
import { EditorContext } from "./EditorContext";
|
|
@@ -120,8 +120,10 @@ export const PrefabRoot = forwardRef(({ editMode, data, onPrefabChange, selected
|
|
|
120
120
|
? `${basePath}${file}`
|
|
121
121
|
: `${basePath}/${file}`;
|
|
122
122
|
const res = yield loadModel(path);
|
|
123
|
-
|
|
124
|
-
|
|
123
|
+
const model = res.model;
|
|
124
|
+
if (res.success && model) {
|
|
125
|
+
setModels(m => (Object.assign(Object.assign({}, m), { [file]: model })));
|
|
126
|
+
}
|
|
125
127
|
}));
|
|
126
128
|
const loader = new TextureLoader();
|
|
127
129
|
texturesToLoad.forEach(file => {
|
|
@@ -1,25 +1,45 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { PerspectiveCamera
|
|
3
|
-
import {
|
|
2
|
+
import { PerspectiveCamera } from '@react-three/drei';
|
|
3
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
4
4
|
import { CameraHelper } from 'three';
|
|
5
|
+
import { useFrame } from '@react-three/fiber';
|
|
5
6
|
import { FieldGroup, NumberField } from './Input';
|
|
7
|
+
const cameraDefaults = {
|
|
8
|
+
fov: 50,
|
|
9
|
+
near: 0.1,
|
|
10
|
+
zoom: 1,
|
|
11
|
+
far: 1000,
|
|
12
|
+
};
|
|
6
13
|
function CameraComponentEditor({ component, onUpdate }) {
|
|
7
|
-
|
|
14
|
+
const values = Object.assign(Object.assign({}, cameraDefaults), component.properties);
|
|
15
|
+
return (_jsxs(FieldGroup, { children: [_jsx(NumberField, { name: "fov", label: "FOV", values: values, onChange: onUpdate, fallback: 50, min: 1, max: 179, step: 1 }), _jsx(NumberField, { name: "near", label: "Near", values: values, onChange: onUpdate, fallback: 0.1, min: 0.001, step: 0.1 }), _jsx(NumberField, { name: "zoom", label: "Zoom", values: values, onChange: onUpdate, fallback: 1, min: 0.01, step: 0.1 }), _jsx(NumberField, { name: "far", label: "Far", values: values, onChange: onUpdate, fallback: 1000, min: 0.1, step: 1 })] }));
|
|
8
16
|
}
|
|
9
17
|
function CameraComponentView({ properties, editMode, isSelected }) {
|
|
10
|
-
|
|
11
|
-
const fov =
|
|
12
|
-
const near =
|
|
13
|
-
const zoom =
|
|
14
|
-
const far =
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
const merged = Object.assign(Object.assign({}, cameraDefaults), properties);
|
|
19
|
+
const fov = merged.fov;
|
|
20
|
+
const near = merged.near;
|
|
21
|
+
const zoom = merged.zoom;
|
|
22
|
+
const far = merged.far;
|
|
23
|
+
const [camera, setCamera] = useState(null);
|
|
24
|
+
const cameraHelper = useMemo(() => camera ? new CameraHelper(camera) : null, [camera]);
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
return () => {
|
|
27
|
+
cameraHelper === null || cameraHelper === void 0 ? void 0 : cameraHelper.dispose();
|
|
28
|
+
};
|
|
29
|
+
}, [cameraHelper]);
|
|
30
|
+
useFrame(() => {
|
|
31
|
+
if (camera && cameraHelper && editMode && isSelected) {
|
|
32
|
+
camera.updateProjectionMatrix();
|
|
33
|
+
camera.updateMatrixWorld();
|
|
34
|
+
cameraHelper.update();
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
return (_jsxs(_Fragment, { children: [_jsx(PerspectiveCamera, { ref: (instance) => setCamera(instance), makeDefault: !editMode, fov: fov, near: near, zoom: zoom, far: far }), editMode && isSelected && cameraHelper && (_jsx("primitive", { object: cameraHelper })), editMode && !isSelected ? (_jsxs("mesh", { children: [_jsx("boxGeometry", { args: [0.34, 0.22, 0.18] }), _jsx("meshBasicMaterial", { color: "#22d3ee", wireframe: true })] })) : null] }));
|
|
18
38
|
}
|
|
19
39
|
const CameraComponent = {
|
|
20
40
|
name: 'Camera',
|
|
21
41
|
Editor: CameraComponentEditor,
|
|
22
42
|
View: CameraComponentView,
|
|
23
|
-
defaultProperties:
|
|
43
|
+
defaultProperties: cameraDefaults,
|
|
24
44
|
};
|
|
25
45
|
export default CameraComponent;
|
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useRef, useEffect } from "react";
|
|
2
|
+
import { useRef, useEffect, useMemo, useState } from "react";
|
|
3
3
|
import { useFrame } from "@react-three/fiber";
|
|
4
|
-
import { Vector3 } from "three";
|
|
4
|
+
import { CameraHelper, Vector3 } from "three";
|
|
5
5
|
import { FieldRenderer, Input } from "./Input";
|
|
6
6
|
const smallLabel = { display: 'block', fontSize: '8px', color: 'rgba(34, 211, 238, 0.5)', marginBottom: 2 };
|
|
7
|
+
const directionalLightDefaults = {
|
|
8
|
+
color: '#ffffff',
|
|
9
|
+
intensity: 1,
|
|
10
|
+
castShadow: true,
|
|
11
|
+
shadowMapSize: 1024,
|
|
12
|
+
shadowCameraNear: 0.1,
|
|
13
|
+
shadowCameraFar: 100,
|
|
14
|
+
shadowCameraTop: 30,
|
|
15
|
+
shadowCameraBottom: -30,
|
|
16
|
+
shadowCameraLeft: -30,
|
|
17
|
+
shadowCameraRight: 30,
|
|
18
|
+
targetOffset: [0, -5, 0],
|
|
19
|
+
};
|
|
7
20
|
const directionalLightFields = [
|
|
8
21
|
{ name: 'color', type: 'color', label: 'Color' },
|
|
9
22
|
{ name: 'intensity', type: 'number', label: 'Intensity', step: 0.1, min: 0 },
|
|
@@ -29,39 +42,52 @@ const directionalLightFields = [
|
|
|
29
42
|
},
|
|
30
43
|
];
|
|
31
44
|
function DirectionalLightComponentEditor({ component, onUpdate }) {
|
|
32
|
-
|
|
45
|
+
const values = Object.assign(Object.assign({}, directionalLightDefaults), component.properties);
|
|
46
|
+
const fields = values.castShadow
|
|
47
|
+
? directionalLightFields
|
|
48
|
+
: directionalLightFields.filter(field => field.name !== '_shadowCamera');
|
|
49
|
+
return (_jsx(FieldRenderer, { fields: fields, values: values, onChange: onUpdate }));
|
|
33
50
|
}
|
|
34
51
|
function DirectionalLightView({ properties, editMode, isSelected }) {
|
|
35
|
-
|
|
36
|
-
const color =
|
|
37
|
-
const intensity =
|
|
38
|
-
const castShadow =
|
|
39
|
-
const shadowMapSize =
|
|
40
|
-
const shadowCameraNear =
|
|
41
|
-
const shadowCameraFar =
|
|
42
|
-
const shadowCameraTop =
|
|
43
|
-
const shadowCameraBottom =
|
|
44
|
-
const shadowCameraLeft =
|
|
45
|
-
const shadowCameraRight =
|
|
46
|
-
const targetOffset =
|
|
52
|
+
const merged = Object.assign(Object.assign({}, directionalLightDefaults), properties);
|
|
53
|
+
const color = merged.color;
|
|
54
|
+
const intensity = merged.intensity;
|
|
55
|
+
const castShadow = merged.castShadow;
|
|
56
|
+
const shadowMapSize = merged.shadowMapSize;
|
|
57
|
+
const shadowCameraNear = merged.shadowCameraNear;
|
|
58
|
+
const shadowCameraFar = merged.shadowCameraFar;
|
|
59
|
+
const shadowCameraTop = merged.shadowCameraTop;
|
|
60
|
+
const shadowCameraBottom = merged.shadowCameraBottom;
|
|
61
|
+
const shadowCameraLeft = merged.shadowCameraLeft;
|
|
62
|
+
const shadowCameraRight = merged.shadowCameraRight;
|
|
63
|
+
const targetOffset = merged.targetOffset;
|
|
47
64
|
const directionalLightRef = useRef(null);
|
|
48
65
|
const targetRef = useRef(null);
|
|
49
|
-
|
|
66
|
+
const [shadowCamera, setShadowCamera] = useState(null);
|
|
67
|
+
const shadowCameraHelper = useMemo(() => shadowCamera ? new CameraHelper(shadowCamera) : null, [shadowCamera]);
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
return () => {
|
|
70
|
+
shadowCameraHelper === null || shadowCameraHelper === void 0 ? void 0 : shadowCameraHelper.dispose();
|
|
71
|
+
};
|
|
72
|
+
}, [shadowCameraHelper]);
|
|
73
|
+
// Use a local target object so node transforms rotate the light direction naturally.
|
|
50
74
|
useEffect(() => {
|
|
51
75
|
if (directionalLightRef.current && targetRef.current) {
|
|
52
76
|
directionalLightRef.current.target = targetRef.current;
|
|
77
|
+
setShadowCamera(directionalLightRef.current.shadow.camera);
|
|
53
78
|
}
|
|
54
79
|
}, []);
|
|
55
|
-
// Update target world position based on light position + offset
|
|
56
80
|
useFrame(() => {
|
|
57
81
|
if (!directionalLightRef.current || !targetRef.current)
|
|
58
82
|
return;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
83
|
+
directionalLightRef.current.target.updateMatrixWorld();
|
|
84
|
+
if (shadowCamera && shadowCameraHelper && castShadow) {
|
|
85
|
+
shadowCamera.updateProjectionMatrix();
|
|
86
|
+
shadowCamera.updateMatrixWorld();
|
|
87
|
+
shadowCameraHelper.update();
|
|
88
|
+
}
|
|
63
89
|
});
|
|
64
|
-
return (_jsxs(_Fragment, { children: [_jsx("directionalLight", { ref: directionalLightRef, color: color, intensity: intensity, castShadow: castShadow, "shadow-mapSize-width": shadowMapSize, "shadow-mapSize-height": shadowMapSize, "shadow-camera-near": shadowCameraNear, "shadow-camera-far": shadowCameraFar, "shadow-camera-top": shadowCameraTop, "shadow-camera-bottom": shadowCameraBottom, "shadow-camera-left": shadowCameraLeft, "shadow-camera-right": shadowCameraRight, "shadow-bias": -0.001, "shadow-normalBias": 0.02 }), _jsx("object3D", { ref: targetRef }), editMode && isSelected && (_jsxs(_Fragment, { children: [_jsxs("mesh", { children: [_jsx("sphereGeometry", { args: [0.3, 8, 6] }), _jsx("meshBasicMaterial", { color: color, wireframe: true })] }), _jsxs("mesh", { position: targetOffset, children: [_jsx("sphereGeometry", { args: [0.2, 8, 6] }), _jsx("meshBasicMaterial", { color: color, wireframe: true, opacity: 0.5, transparent: true })] }), _jsxs("line", { children: [_jsx("bufferGeometry", { onUpdate: (geo) => {
|
|
90
|
+
return (_jsxs(_Fragment, { children: [_jsx("directionalLight", { ref: directionalLightRef, color: color, intensity: intensity, castShadow: castShadow, "shadow-mapSize-width": shadowMapSize, "shadow-mapSize-height": shadowMapSize, "shadow-camera-near": shadowCameraNear, "shadow-camera-far": shadowCameraFar, "shadow-camera-top": shadowCameraTop, "shadow-camera-bottom": shadowCameraBottom, "shadow-camera-left": shadowCameraLeft, "shadow-camera-right": shadowCameraRight, "shadow-bias": -0.001, "shadow-normalBias": 0.02 }), _jsx("object3D", { ref: targetRef, position: targetOffset }), editMode && isSelected && castShadow && shadowCameraHelper && (_jsx("primitive", { object: shadowCameraHelper })), editMode && isSelected && (_jsxs(_Fragment, { children: [_jsxs("mesh", { children: [_jsx("sphereGeometry", { args: [0.3, 8, 6] }), _jsx("meshBasicMaterial", { color: color, wireframe: true })] }), _jsxs("mesh", { position: targetOffset, children: [_jsx("sphereGeometry", { args: [0.2, 8, 6] }), _jsx("meshBasicMaterial", { color: color, wireframe: true, opacity: 0.5, transparent: true })] }), _jsxs("line", { children: [_jsx("bufferGeometry", { onUpdate: (geo) => {
|
|
65
91
|
const points = [
|
|
66
92
|
new Vector3(0, 0, 0),
|
|
67
93
|
new Vector3(targetOffset[0], targetOffset[1], targetOffset[2])
|
|
@@ -73,6 +99,6 @@ const DirectionalLightComponent = {
|
|
|
73
99
|
name: 'DirectionalLight',
|
|
74
100
|
Editor: DirectionalLightComponentEditor,
|
|
75
101
|
View: DirectionalLightView,
|
|
76
|
-
defaultProperties:
|
|
102
|
+
defaultProperties: directionalLightDefaults
|
|
77
103
|
};
|
|
78
104
|
export default DirectionalLightComponent;
|
|
@@ -1,5 +1,13 @@
|
|
|
1
|
+
import type { ThreeElement } from '@react-three/fiber';
|
|
1
2
|
import { Component } from './ComponentRegistry';
|
|
3
|
+
import { MeshBasicNodeMaterial, MeshStandardNodeMaterial } from 'three/webgpu';
|
|
2
4
|
import { MeshBasicMaterialProperties, MeshStandardMaterialProperties } from 'three';
|
|
5
|
+
declare module '@react-three/fiber' {
|
|
6
|
+
interface ThreeElements {
|
|
7
|
+
meshBasicNodeMaterial: ThreeElement<typeof MeshBasicNodeMaterial>;
|
|
8
|
+
meshStandardNodeMaterial: ThreeElement<typeof MeshStandardNodeMaterial>;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
3
11
|
export interface MaterialProps extends Omit<MeshStandardMaterialProperties & MeshBasicMaterialProperties, 'args' | 'normalScale'> {
|
|
4
12
|
materialType?: 'standard' | 'basic';
|
|
5
13
|
transmission?: number;
|
|
@@ -11,14 +11,20 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
11
11
|
};
|
|
12
12
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
13
13
|
import { SingleTextureViewer, TextureListViewer } from '../../assetviewer/page';
|
|
14
|
+
import { extend } from '@react-three/fiber';
|
|
14
15
|
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|
15
16
|
import { createPortal } from 'react-dom';
|
|
16
17
|
import { FieldRenderer, Input } from './Input';
|
|
17
18
|
import { colors } from '../styles';
|
|
18
19
|
import { useMemo } from 'react';
|
|
20
|
+
import { MeshBasicNodeMaterial, MeshStandardNodeMaterial } from 'three/webgpu';
|
|
19
21
|
import { RepeatWrapping, ClampToEdgeWrapping, SRGBColorSpace, LinearSRGBColorSpace, Vector2, NearestFilter, LinearFilter, NearestMipmapNearestFilter, NearestMipmapLinearFilter, LinearMipmapNearestFilter, LinearMipmapLinearFilter, FrontSide, BackSide, DoubleSide, } from 'three';
|
|
20
22
|
const PICKER_POPUP_WIDTH = 260;
|
|
21
23
|
const PICKER_POPUP_HEIGHT = 360;
|
|
24
|
+
extend({
|
|
25
|
+
MeshBasicNodeMaterial,
|
|
26
|
+
MeshStandardNodeMaterial,
|
|
27
|
+
});
|
|
22
28
|
function TexturePicker({ value, onChange, basePath }) {
|
|
23
29
|
const [textureFiles, setTextureFiles] = useState([]);
|
|
24
30
|
const [showPicker, setShowPicker] = useState(false);
|
|
@@ -193,7 +199,7 @@ function MaterialComponentView({ properties, loadedTextures }) {
|
|
|
193
199
|
const normalMapTexture = normalMapTextureName && loadedTextures ? loadedTextures[normalMapTextureName] : undefined;
|
|
194
200
|
const materialSource = properties !== null && properties !== void 0 ? properties : {};
|
|
195
201
|
// Destructure all material props and separate custom texture handling props
|
|
196
|
-
const { texture: _texture, repeat: _repeat, repeatCount: _repeatCount, generateMipmaps: _generateMipmaps, minFilter: _minFilter, magFilter: _magFilter, map: _map, materialType: _materialType, normalMapTexture: _normalMapTexture, normalScale: _normalScale, normalMap: _normalMap, side: sideProp
|
|
202
|
+
const { texture: _texture, repeat: _repeat, repeatCount: _repeatCount, generateMipmaps: _generateMipmaps, minFilter: _minFilter, magFilter: _magFilter, map: _map, materialType: _materialType, normalMapTexture: _normalMapTexture, normalScale: _normalScale, normalMap: _normalMap, side: sideProp } = materialSource, materialProps = __rest(materialSource, ["texture", "repeat", "repeatCount", "generateMipmaps", "minFilter", "magFilter", "map", "materialType", "normalMapTexture", "normalScale", "normalMap", "side"]);
|
|
197
203
|
const sideMap = { FrontSide, BackSide, DoubleSide };
|
|
198
204
|
const resolvedSide = sideProp ? ((_b = sideMap[sideProp]) !== null && _b !== void 0 ? _b : FrontSide) : FrontSide;
|
|
199
205
|
const minFilterMap = {
|
|
@@ -244,14 +250,14 @@ function MaterialComponentView({ properties, loadedTextures }) {
|
|
|
244
250
|
return new Vector2((_a = normalScaleProp === null || normalScaleProp === void 0 ? void 0 : normalScaleProp[0]) !== null && _a !== void 0 ? _a : 1, (_b = normalScaleProp === null || normalScaleProp === void 0 ? void 0 : normalScaleProp[1]) !== null && _b !== void 0 ? _b : 1);
|
|
245
251
|
}, [finalNormalMap, normalScaleProp === null || normalScaleProp === void 0 ? void 0 : normalScaleProp[0], normalScaleProp === null || normalScaleProp === void 0 ? void 0 : normalScaleProp[1]]);
|
|
246
252
|
if (!properties) {
|
|
247
|
-
return _jsx("
|
|
253
|
+
return _jsx("meshStandardNodeMaterial", { color: "red", wireframe: true });
|
|
248
254
|
}
|
|
249
|
-
const materialKey = (_c = finalTexture === null || finalTexture === void 0 ? void 0 : finalTexture.uuid) !== null && _c !== void 0 ? _c : 'no-texture'
|
|
255
|
+
const materialKey = `${(_c = finalTexture === null || finalTexture === void 0 ? void 0 : finalTexture.uuid) !== null && _c !== void 0 ? _c : 'no-texture'}:${materialProps.transparent ? 'transparent' : 'opaque'}`;
|
|
250
256
|
const sharedProps = Object.assign({ map: finalTexture, side: resolvedSide }, materialProps);
|
|
251
257
|
if (materialType === 'basic') {
|
|
252
|
-
return _jsx("
|
|
258
|
+
return _jsx("meshBasicNodeMaterial", Object.assign({}, sharedProps), materialKey);
|
|
253
259
|
}
|
|
254
|
-
return (_jsx("
|
|
260
|
+
return (_jsx("meshStandardNodeMaterial", Object.assign({}, sharedProps, { normalMap: finalNormalMap, normalScale: normalScaleVec }), materialKey));
|
|
255
261
|
}
|
|
256
262
|
const MaterialComponent = {
|
|
257
263
|
name: 'Material',
|