react-three-game 0.0.107 → 0.0.108
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/README.md +7 -0
- package/dist/plugins/crashcat/CrashcatPhysicsComponent.js +74 -46
- package/dist/plugins/crashcat/CrashcatRagdoll.d.ts +58 -0
- package/dist/plugins/crashcat/CrashcatRagdoll.js +410 -0
- package/dist/plugins/crashcat/CrashcatRuntime.js +18 -14
- package/dist/plugins/crashcat/index.d.ts +1 -0
- package/dist/plugins/crashcat/index.js +1 -0
- package/dist/tools/assetviewer/page.js +4 -4
- package/dist/tools/prefabeditor/EditorTree.js +5 -2
- package/dist/tools/prefabeditor/EditorTreeMenus.d.ts +2 -1
- package/dist/tools/prefabeditor/EditorTreeMenus.js +17 -5
- package/dist/tools/prefabeditor/GameEvents.d.ts +1 -0
- package/dist/tools/prefabeditor/PrefabEditor.d.ts +2 -1
- package/dist/tools/prefabeditor/PrefabEditor.js +26 -13
- package/dist/tools/prefabeditor/PrefabRoot.d.ts +3 -1
- package/dist/tools/prefabeditor/PrefabRoot.js +61 -25
- package/dist/tools/prefabeditor/components/ComponentRegistry.d.ts +15 -0
- package/dist/tools/prefabeditor/components/PrefabRefComponent.d.ts +3 -0
- package/dist/tools/prefabeditor/components/PrefabRefComponent.js +72 -0
- package/dist/tools/prefabeditor/components/TextComponent.js +8 -5
- package/dist/tools/prefabeditor/components/index.js +2 -0
- package/dist/tools/prefabeditor/modelPrefab.d.ts +3 -0
- package/dist/tools/prefabeditor/modelPrefab.js +44 -11
- package/dist/tools/prefabeditor/prefab.d.ts +5 -4
- package/dist/tools/prefabeditor/prefab.js +47 -29
- package/dist/tools/prefabeditor/utils.d.ts +8 -1
- package/dist/tools/prefabeditor/utils.js +74 -22
- package/package.json +13 -13
|
@@ -33,11 +33,14 @@ function setCrashcatApi(api) {
|
|
|
33
33
|
listener();
|
|
34
34
|
});
|
|
35
35
|
}
|
|
36
|
-
function
|
|
37
|
-
const
|
|
38
|
-
if (!
|
|
36
|
+
function emitPhysicsEvent(eventType, eventName, sourceNodeId, targetNodeId, collisionNormal) {
|
|
37
|
+
const alias = eventName === null || eventName === void 0 ? void 0 : eventName.trim();
|
|
38
|
+
if (!gameEvents.hasListeners(eventType) && (!alias || !gameEvents.hasListeners(alias)))
|
|
39
39
|
return;
|
|
40
|
-
|
|
40
|
+
const payload = Object.assign({ sourceEntityId: sourceNodeId, sourceNodeId, targetEntityId: targetNodeId, targetNodeId }, (collisionNormal ? { collisionNormal } : {}));
|
|
41
|
+
gameEvents.emit(eventType, payload);
|
|
42
|
+
if (alias)
|
|
43
|
+
gameEvents.emit(alias, payload);
|
|
41
44
|
}
|
|
42
45
|
function createDebugState() {
|
|
43
46
|
const options = debugRenderer.createDefaultOptions();
|
|
@@ -71,26 +74,27 @@ export function CrashcatRuntime({ debug = false, children }) {
|
|
|
71
74
|
const [debugState] = useState(() => debug ? createDebugState() : null);
|
|
72
75
|
const listener = useMemo(() => ({
|
|
73
76
|
onContactAdded: (bodyA, bodyB, manifold) => {
|
|
74
|
-
var _a, _b;
|
|
77
|
+
var _a, _b, _c, _d, _e, _f;
|
|
75
78
|
const metaA = getBodyMeta(bodyByIdRef.current, bodyA);
|
|
76
79
|
const metaB = getBodyMeta(bodyByIdRef.current, bodyB);
|
|
77
80
|
const n = manifold === null || manifold === void 0 ? void 0 : manifold.worldSpaceNormal;
|
|
78
81
|
const nA = n ? [n[0], n[1], n[2]] : undefined;
|
|
79
82
|
const nB = n ? [-n[0], -n[1], -n[2]] : undefined;
|
|
80
|
-
if (metaA
|
|
81
|
-
|
|
82
|
-
if (metaB
|
|
83
|
-
|
|
83
|
+
if (metaA)
|
|
84
|
+
emitPhysicsEvent(metaA.sensor ? "sensor:enter" : "collision:enter", metaA.sensor ? (_a = metaA.events) === null || _a === void 0 ? void 0 : _a.sensorEnter : (_b = metaA.events) === null || _b === void 0 ? void 0 : _b.collisionEnter, metaA.nodeId, (_c = metaB === null || metaB === void 0 ? void 0 : metaB.nodeId) !== null && _c !== void 0 ? _c : null, nA);
|
|
85
|
+
if (metaB)
|
|
86
|
+
emitPhysicsEvent(metaB.sensor ? "sensor:enter" : "collision:enter", metaB.sensor ? (_d = metaB.events) === null || _d === void 0 ? void 0 : _d.sensorEnter : (_e = metaB.events) === null || _e === void 0 ? void 0 : _e.collisionEnter, metaB.nodeId, (_f = metaA === null || metaA === void 0 ? void 0 : metaA.nodeId) !== null && _f !== void 0 ? _f : null, nB);
|
|
84
87
|
},
|
|
85
88
|
onContactRemoved: (idA, idB) => {
|
|
86
|
-
var _a, _b;
|
|
89
|
+
var _a, _b, _c, _d, _e, _f;
|
|
87
90
|
const metaA = bodyByIdRef.current.get(Number(idA));
|
|
88
91
|
const metaB = bodyByIdRef.current.get(Number(idB));
|
|
89
|
-
if (metaA
|
|
90
|
-
|
|
91
|
-
if (metaB
|
|
92
|
-
|
|
92
|
+
if (metaA)
|
|
93
|
+
emitPhysicsEvent(metaA.sensor ? "sensor:exit" : "collision:exit", metaA.sensor ? (_a = metaA.events) === null || _a === void 0 ? void 0 : _a.sensorExit : (_b = metaA.events) === null || _b === void 0 ? void 0 : _b.collisionExit, metaA.nodeId, (_c = metaB === null || metaB === void 0 ? void 0 : metaB.nodeId) !== null && _c !== void 0 ? _c : null);
|
|
94
|
+
if (metaB)
|
|
95
|
+
emitPhysicsEvent(metaB.sensor ? "sensor:exit" : "collision:exit", metaB.sensor ? (_d = metaB.events) === null || _d === void 0 ? void 0 : _d.sensorExit : (_e = metaB.events) === null || _e === void 0 ? void 0 : _e.collisionExit, metaB.nodeId, (_f = metaA === null || metaA === void 0 ? void 0 : metaA.nodeId) !== null && _f !== void 0 ? _f : null);
|
|
93
96
|
},
|
|
97
|
+
onBodyPairValidate: (bodyA, bodyB) => !rigidBody.bodiesShareConstraint(bodyA, bodyB),
|
|
94
98
|
}), []);
|
|
95
99
|
useEffect(() => {
|
|
96
100
|
ensureCrashcatRegistered();
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
export { CrashcatRuntime, useCrashcat, type BodyMeta, type CrashcatApi, type CrashcatEventConfig, } from "./CrashcatRuntime";
|
|
2
2
|
export { default as CrashcatPhysicsComponent, default, } from "./CrashcatPhysicsComponent";
|
|
3
|
+
export { CrashcatRagdoll, default as CrashcatRagdollComponent, RagdollBodyPart, createRagdollSettings, createStaticBoxBody, type CrashcatRagdollProps, type RagdollSettings, } from "./CrashcatRagdoll";
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
export { CrashcatRuntime, useCrashcat, } from "./CrashcatRuntime";
|
|
2
2
|
export { default as CrashcatPhysicsComponent, default, } from "./CrashcatPhysicsComponent";
|
|
3
|
+
export { CrashcatRagdoll, default as CrashcatRagdollComponent, RagdollBodyPart, createRagdollSettings, createStaticBoxBody, } from "./CrashcatRagdoll";
|
|
@@ -5,7 +5,7 @@ import { Suspense, useEffect, useLayoutEffect, useState, useRef } from "react";
|
|
|
5
5
|
import { createPortal } from 'react-dom';
|
|
6
6
|
import { Mesh, TextureLoader } from "three";
|
|
7
7
|
import { loadModel } from "../dragdrop/modelLoader";
|
|
8
|
-
import {
|
|
8
|
+
import { withBasePath } from "../prefabeditor/utils";
|
|
9
9
|
import { base, colors, fonts } from "../prefabeditor/styles";
|
|
10
10
|
const styles = {
|
|
11
11
|
errorIcon: { color: colors.danger, fontSize: 12 },
|
|
@@ -126,7 +126,7 @@ function TextureCard({ file, onSelect, basePath = "" }) {
|
|
|
126
126
|
const [isHovered, setIsHovered] = useState(false);
|
|
127
127
|
const [error, setError] = useState(false);
|
|
128
128
|
const { ref, isInView } = useInView();
|
|
129
|
-
const fullPath =
|
|
129
|
+
const fullPath = withBasePath(basePath, file);
|
|
130
130
|
const fileName = file.split('/').pop();
|
|
131
131
|
if (error) {
|
|
132
132
|
return (_jsxs("div", { ref: ref, style: Object.assign(Object.assign({ maxWidth: 60, aspectRatio: '1 / 1' }, assetTileStyle), { backgroundColor: assetViewerColors.errorBg }), onClick: () => onSelect(file), title: `Could not load ${file}`, children: [_jsx("div", { style: { flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center' }, children: _jsx("div", { style: styles.errorIcon, children: "\u2717" }) }), _jsx("div", { style: styles.bottomLabel, children: fileName })] }));
|
|
@@ -170,7 +170,7 @@ export function ModelListViewer({ files, selected, onSelect, basePath = "" }) {
|
|
|
170
170
|
function ModelCard({ file, onSelect, basePath = "", size = 60, }) {
|
|
171
171
|
const [error, setError] = useState(false);
|
|
172
172
|
const { ref, isInView } = useInView();
|
|
173
|
-
const fullPath =
|
|
173
|
+
const fullPath = withBasePath(basePath, file);
|
|
174
174
|
if (error) {
|
|
175
175
|
return (_jsx("div", { ref: ref, style: { aspectRatio: '1 / 1', backgroundColor: assetViewerColors.errorBg, cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', border: `1px solid ${colors.dangerBorder}` }, onClick: () => onSelect(file), children: _jsx("div", { style: styles.errorIcon, children: "\u2717" }) }));
|
|
176
176
|
}
|
|
@@ -215,7 +215,7 @@ export function SoundListViewer({ files, selected, onSelect, basePath = "" }) {
|
|
|
215
215
|
}
|
|
216
216
|
function SoundCard({ file, onSelect, basePath = "" }) {
|
|
217
217
|
const fileName = file.split('/').pop() || '';
|
|
218
|
-
const fullPath =
|
|
218
|
+
const fullPath = withBasePath(basePath, file);
|
|
219
219
|
return (_jsxs("div", { onClick: () => onSelect(file), style: Object.assign(Object.assign({ aspectRatio: '1 / 1' }, assetTileStyle), { alignItems: 'center', justifyContent: 'center' }), children: [_jsx("div", { style: styles.iconLarge, children: "\uD83D\uDD0A" }), _jsx("div", { style: { color: colors.text, fontSize: fonts.size, padding: '0 4px', marginTop: 4, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', textAlign: 'center', width: '100%' }, children: fileName })] }));
|
|
220
220
|
}
|
|
221
221
|
const PICKER_POPUP_WIDTH = 260;
|
|
@@ -4,7 +4,7 @@ import { base, colors, tree } from './styles';
|
|
|
4
4
|
import { useEditorContext, useEditorRef } from './PrefabEditor';
|
|
5
5
|
import { Dropdown } from './Dropdown';
|
|
6
6
|
import { FileMenu, TreeContextMenu, TreeNodeMenu } from './EditorTreeMenus';
|
|
7
|
-
import { createEmptyNode } from './prefab';
|
|
7
|
+
import { createEmptyNode, createPackedPrefabNode } from './prefab';
|
|
8
8
|
import { usePrefabChildIds, usePrefabNode, usePrefabRootId, usePrefabStore, usePrefabStoreApi } from './prefabStore';
|
|
9
9
|
export default function EditorTree({ selectedId, setSelectedId, getPrefab, onReplacePrefab, onImportPrefab, onUndo, onRedo, canUndo, canRedo }) {
|
|
10
10
|
const { onFocusNode } = useEditorContext();
|
|
@@ -30,6 +30,9 @@ export default function EditorTree({ selectedId, setSelectedId, getPrefab, onRep
|
|
|
30
30
|
editor.add(newNode, parentId);
|
|
31
31
|
setSelectedId(newNode.id);
|
|
32
32
|
};
|
|
33
|
+
const handleImportPackedPrefab = (url) => {
|
|
34
|
+
editor.add(createPackedPrefabNode(url), rootId);
|
|
35
|
+
};
|
|
33
36
|
const handleDuplicate = (nodeId) => {
|
|
34
37
|
if (nodeId === rootId)
|
|
35
38
|
return;
|
|
@@ -103,7 +106,7 @@ export default function EditorTree({ selectedId, setSelectedId, getPrefab, onRep
|
|
|
103
106
|
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: "Prefab" })] }), !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("button", { ref: ref, title: "Menu", style: Object.assign(Object.assign({}, base.btn), { padding: '2px 6px', fontSize: 10 }), onClick: (e) => {
|
|
104
107
|
e.stopPropagation();
|
|
105
108
|
toggle();
|
|
106
|
-
}, children: "\u22EE" })), children: (close) => (_jsx(FileMenu, { getPrefab: getPrefab, onReplacePrefab: onReplacePrefab, onImportPrefab: onImportPrefab, 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", { style: tree.scroll, children: _jsx(TreeNode, { nodeId: rootId, depth: 0, rootId: rootId, visibleIds: visibleIds, collapsedIds: collapsedIds, dropTarget: dropTarget, selectedNodeId: selectedId, onToggleCollapse: toggleCollapse, onOpenContextMenu: openContextMenu, onDragStart: handleDragStart, onDragOver: handleDragOver, onDragLeave: handleDragLeave, onDrop: handleDrop, onDragEnd: () => { setDraggedId(null); setDropTarget(null); }, renderTreeNodeMenu: renderTreeNodeMenu, onToggleDisabled: handleToggleDisabled, setSelectedId: setSelectedId }) })] }))] }), _jsx(TreeContextMenu, { contextMenu: contextMenu, onClose: closeContextMenu, children: (nodeId, close) => renderTreeNodeMenu(nodeId, nodeId === rootId, close) })] }));
|
|
109
|
+
}, children: "\u22EE" })), children: (close) => (_jsx(FileMenu, { getPrefab: getPrefab, onReplacePrefab: onReplacePrefab, onImportPrefab: onImportPrefab, onImportPackedPrefab: handleImportPackedPrefab, 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", { style: tree.scroll, children: _jsx(TreeNode, { nodeId: rootId, depth: 0, rootId: rootId, visibleIds: visibleIds, collapsedIds: collapsedIds, dropTarget: dropTarget, selectedNodeId: selectedId, onToggleCollapse: toggleCollapse, onOpenContextMenu: openContextMenu, onDragStart: handleDragStart, onDragOver: handleDragOver, onDragLeave: handleDragLeave, onDrop: handleDrop, onDragEnd: () => { setDraggedId(null); setDropTarget(null); }, renderTreeNodeMenu: renderTreeNodeMenu, onToggleDisabled: handleToggleDisabled, setSelectedId: setSelectedId }) })] }))] }), _jsx(TreeContextMenu, { contextMenu: contextMenu, onClose: closeContextMenu, children: (nodeId, close) => renderTreeNodeMenu(nodeId, nodeId === rootId, close) })] }));
|
|
107
110
|
}
|
|
108
111
|
const TreeNode = memo(function TreeNode({ nodeId, depth, rootId, visibleIds, collapsedIds, dropTarget, selectedNodeId, onToggleCollapse, onOpenContextMenu, onDragStart, onDragOver, onDragLeave, onDrop, onDragEnd, renderTreeNodeMenu, onToggleDisabled, setSelectedId, }) {
|
|
109
112
|
var _a;
|
|
@@ -20,9 +20,10 @@ export declare function TreeContextMenu({ contextMenu, onClose, children, }: {
|
|
|
20
20
|
onClose: () => void;
|
|
21
21
|
children: (nodeId: string, onClose: () => void) => React.ReactNode;
|
|
22
22
|
}): import("react").ReactPortal | null;
|
|
23
|
-
export declare function FileMenu({ getPrefab, onReplacePrefab, onImportPrefab, onClose }: {
|
|
23
|
+
export declare function FileMenu({ getPrefab, onReplacePrefab, onImportPrefab, onImportPackedPrefab, onClose }: {
|
|
24
24
|
getPrefab: () => Prefab;
|
|
25
25
|
onReplacePrefab: (prefab: Prefab) => void;
|
|
26
26
|
onImportPrefab: (prefab: Prefab) => void;
|
|
27
|
+
onImportPackedPrefab: (url: string) => void;
|
|
27
28
|
onClose: () => void;
|
|
28
29
|
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -13,7 +13,7 @@ import { createPortal } from 'react-dom';
|
|
|
13
13
|
import { createEmptyPrefab } from './prefab';
|
|
14
14
|
import { menu } from './styles';
|
|
15
15
|
import { useEditorContext } from './PrefabEditor';
|
|
16
|
-
import { loadJson, saveJson } from './utils';
|
|
16
|
+
import { loadJson, loadJsonFile, saveJson, withBasePath } from './utils';
|
|
17
17
|
function MenuPanel({ children, style, }) {
|
|
18
18
|
return (_jsx("div", { style: Object.assign(Object.assign(Object.assign({}, menu.container), { position: 'static' }), style), onClick: (e) => e.stopPropagation(), children: children }));
|
|
19
19
|
}
|
|
@@ -80,8 +80,8 @@ export function TreeContextMenu({ contextMenu, onClose, children, }) {
|
|
|
80
80
|
zIndex: 1000,
|
|
81
81
|
}, onMouseLeave: onClose, onContextMenu: (e) => e.preventDefault(), children: children(contextMenu.nodeId, onClose) }), document.body);
|
|
82
82
|
}
|
|
83
|
-
export function FileMenu({ getPrefab, onReplacePrefab, onImportPrefab, onClose }) {
|
|
84
|
-
const { onScreenshot, onExportGLB } = useEditorContext();
|
|
83
|
+
export function FileMenu({ getPrefab, onReplacePrefab, onImportPrefab, onImportPackedPrefab, onClose }) {
|
|
84
|
+
const { basePath, onScreenshot, onExportGLB } = useEditorContext();
|
|
85
85
|
const handleNew = () => {
|
|
86
86
|
onReplacePrefab(createEmptyPrefab());
|
|
87
87
|
onClose();
|
|
@@ -98,10 +98,22 @@ export function FileMenu({ getPrefab, onReplacePrefab, onImportPrefab, onClose }
|
|
|
98
98
|
onClose();
|
|
99
99
|
};
|
|
100
100
|
const handleImport = () => __awaiter(this, void 0, void 0, function* () {
|
|
101
|
-
const loaded = yield
|
|
101
|
+
const loaded = yield loadJsonFile();
|
|
102
102
|
if (!loaded)
|
|
103
103
|
return;
|
|
104
|
-
|
|
104
|
+
try {
|
|
105
|
+
const manifest = yield fetch(withBasePath(basePath, '/prefabs/manifest.json')).then(r => r.json());
|
|
106
|
+
const matched = manifest.find(entry => entry.endsWith(`/${loaded.filename}`) || entry === `/${loaded.filename}`);
|
|
107
|
+
if (matched) {
|
|
108
|
+
onImportPackedPrefab(matched);
|
|
109
|
+
onClose();
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch (_a) {
|
|
114
|
+
// manifest not available, fall through to full import
|
|
115
|
+
}
|
|
116
|
+
onImportPrefab(loaded.prefab);
|
|
105
117
|
onClose();
|
|
106
118
|
});
|
|
107
119
|
return (_jsxs(MenuPanel, { style: { overflow: 'visible' }, children: [_jsxs(MenuSubmenu, { label: "File", children: [_jsx(MenuItemButton, { onClick: handleNew, children: "New Prefab" }), _jsx(MenuItemButton, { onClick: handleOpen, children: "Open Prefab" }), _jsx(MenuItemButton, { onClick: handleImport, children: "Import Prefab" }), _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" })] })] }));
|
|
@@ -2,7 +2,7 @@ import GameCanvas from "../../shared/GameCanvas";
|
|
|
2
2
|
import type { Prefab } from "./types";
|
|
3
3
|
import { PrefabEditorMode, type Scene } from "./PrefabRoot";
|
|
4
4
|
import type { ExportGLBOptions } from "./utils";
|
|
5
|
-
export
|
|
5
|
+
export { isExternalPath as isAbsoluteAssetPath } from "./utils";
|
|
6
6
|
export declare function resolvePrefabAssetPath(basePath: string, file: string): string;
|
|
7
7
|
export declare function getPrefabAssetRef(assetRef: string, folder: "models" | "textures" | "sound"): string;
|
|
8
8
|
export interface PrefabEditorRef extends Scene {
|
|
@@ -21,6 +21,7 @@ export interface PrefabEditorRef extends Scene {
|
|
|
21
21
|
export type { PrefabNode } from "./PrefabRoot";
|
|
22
22
|
export interface EditorContextType {
|
|
23
23
|
mode: PrefabEditorMode;
|
|
24
|
+
basePath: string;
|
|
24
25
|
setMode: (mode: PrefabEditorMode) => void;
|
|
25
26
|
transformMode: "translate" | "rotate" | "scale";
|
|
26
27
|
setTransformMode: (mode: "translate" | "rotate" | "scale") => void;
|
|
@@ -16,10 +16,11 @@ import { findComponentEntry } from "./types";
|
|
|
16
16
|
import { PrefabEditorMode, PrefabRoot } from "./PrefabRoot";
|
|
17
17
|
import EditorUI from "./EditorUI";
|
|
18
18
|
import { base, toolbar } from "./styles";
|
|
19
|
-
import { computeParentWorldMatrix, decompose, exportGLB as exportGLBFile, exportGLBData, focusCameraOnObject, regenerateIds } from "./utils";
|
|
19
|
+
import { computeParentWorldMatrix, decompose, exportGLB as exportGLBFile, exportGLBData, focusCameraOnObject, isExternalPath, regenerateIds, withBasePath } from "./utils";
|
|
20
20
|
import { loadDroppedAssets } from "../dragdrop";
|
|
21
21
|
import { denormalizePrefab, createImageNode, createModelNode, createNode } from './prefab';
|
|
22
22
|
import { createPrefabStore, PrefabStoreProvider } from "./prefabStore";
|
|
23
|
+
import { decomposeModelToPrefabNodes, hasCollisionMeshConventions } from "./modelPrefab";
|
|
23
24
|
function isObjectAttachedToRoot(root, object) {
|
|
24
25
|
if (!root || !object)
|
|
25
26
|
return false;
|
|
@@ -31,18 +32,12 @@ function isObjectAttachedToRoot(root, object) {
|
|
|
31
32
|
}
|
|
32
33
|
return false;
|
|
33
34
|
}
|
|
34
|
-
export
|
|
35
|
-
return (path.startsWith("data:") ||
|
|
36
|
-
path.startsWith("http://") ||
|
|
37
|
-
path.startsWith("https://"));
|
|
38
|
-
}
|
|
35
|
+
export { isExternalPath as isAbsoluteAssetPath } from "./utils";
|
|
39
36
|
export function resolvePrefabAssetPath(basePath, file) {
|
|
40
|
-
|
|
41
|
-
return file;
|
|
42
|
-
return file.startsWith("/") ? `${basePath}${file}` : `${basePath}/${file}`;
|
|
37
|
+
return withBasePath(basePath, file);
|
|
43
38
|
}
|
|
44
39
|
export function getPrefabAssetRef(assetRef, folder) {
|
|
45
|
-
return
|
|
40
|
+
return isExternalPath(assetRef) ? assetRef : `${folder}/${assetRef}`;
|
|
46
41
|
}
|
|
47
42
|
function SelectionHelper({ object }) {
|
|
48
43
|
const objectRef = useRef(null);
|
|
@@ -76,7 +71,7 @@ const DEFAULT_PREFAB = {
|
|
|
76
71
|
name: "New Prefab",
|
|
77
72
|
root: createNode('Root', {}, { id: 'root' })
|
|
78
73
|
};
|
|
79
|
-
const PrefabEditor = forwardRef(({ basePath, initialPrefab, mode: initialMode = PrefabEditorMode.Edit, onChange, showUI = true, enableWindowDrop = true, canvasProps, uiPlugins, children }, ref) => {
|
|
74
|
+
const PrefabEditor = forwardRef(({ basePath = "", initialPrefab, mode: initialMode = PrefabEditorMode.Edit, onChange, showUI = true, enableWindowDrop = true, canvasProps, uiPlugins, children }, ref) => {
|
|
80
75
|
const [mode, setMode] = useState(initialMode);
|
|
81
76
|
const [selectedId, setSelectedId] = useState(null);
|
|
82
77
|
const [transformMode, setTransformMode] = useState("translate");
|
|
@@ -344,7 +339,23 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, mode: initialMode =
|
|
|
344
339
|
onModelLoaded: (model, filename, file) => {
|
|
345
340
|
const path = getPrefabAssetRef(filename, 'models');
|
|
346
341
|
scene === null || scene === void 0 ? void 0 : scene.addModel(path, model);
|
|
347
|
-
|
|
342
|
+
const modelName = file.name.replace(/\.[^.]+$/, '');
|
|
343
|
+
const modelIdPrefix = modelName.replace(/[^\w-]+/g, '-') || 'model';
|
|
344
|
+
if (hasCollisionMeshConventions(model)) {
|
|
345
|
+
const textureRefs = new Map();
|
|
346
|
+
const decomposed = decomposeModelToPrefabNodes(model, {
|
|
347
|
+
idPrefix: modelIdPrefix,
|
|
348
|
+
getTexturePath: (texture, usage) => {
|
|
349
|
+
const key = `embedded/${modelIdPrefix}/${usage}/${texture.uuid}`;
|
|
350
|
+
textureRefs.set(key, texture);
|
|
351
|
+
return key;
|
|
352
|
+
},
|
|
353
|
+
});
|
|
354
|
+
textureRefs.forEach((texture, path) => scene === null || scene === void 0 ? void 0 : scene.addTexture(path, texture));
|
|
355
|
+
add(Object.assign(Object.assign({}, decomposed), { name: modelName || decomposed.name }));
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
add(createModelNode(path, modelName));
|
|
348
359
|
},
|
|
349
360
|
onTextureLoaded: (texture, filename, file) => {
|
|
350
361
|
const path = getPrefabAssetRef(filename, 'textures');
|
|
@@ -368,6 +379,7 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, mode: initialMode =
|
|
|
368
379
|
return getRoot();
|
|
369
380
|
},
|
|
370
381
|
mode,
|
|
382
|
+
basePath,
|
|
371
383
|
get: getNode,
|
|
372
384
|
getObject,
|
|
373
385
|
getHandle,
|
|
@@ -382,7 +394,7 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, mode: initialMode =
|
|
|
382
394
|
addModel: (path, model) => { var _a; return (_a = prefabRootRef.current) === null || _a === void 0 ? void 0 : _a.addModel(path, model); },
|
|
383
395
|
addTexture: (path, texture) => { var _a; return (_a = prefabRootRef.current) === null || _a === void 0 ? void 0 : _a.addTexture(path, texture); },
|
|
384
396
|
addSound: (path, sound) => { var _a; return (_a = prefabRootRef.current) === null || _a === void 0 ? void 0 : _a.addSound(path, sound); },
|
|
385
|
-
}), [add, duplicate, getHandle, getModel, getNode, getObject, getRoot, mode, move, remove, replace, replaceNode, update]);
|
|
397
|
+
}), [add, basePath, duplicate, getHandle, getModel, getNode, getObject, getRoot, mode, move, remove, replace, replaceNode, update]);
|
|
386
398
|
const editorRefValue = useMemo(() => (Object.assign(Object.assign({}, sceneValue), { save: getPrefab, load: loadPrefab, undo,
|
|
387
399
|
redo, screenshot: handleScreenshot, exportGLB: handleExportGLB, exportGLBData: handleExportGLBData, clearSelection })), [clearSelection, getPrefab, handleExportGLB, handleExportGLBData, handleScreenshot, loadPrefab, redo, sceneValue, undo]);
|
|
388
400
|
useImperativeHandle(ref, () => editorRefValue, [editorRefValue]);
|
|
@@ -394,6 +406,7 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, mode: initialMode =
|
|
|
394
406
|
}, [canvasProps]);
|
|
395
407
|
return _jsx(PrefabStoreProvider, { store: prefabStore, children: _jsx(EditorRefContext.Provider, { value: editorRefValue, children: _jsxs(EditorContext.Provider, { value: {
|
|
396
408
|
mode,
|
|
409
|
+
basePath,
|
|
397
410
|
setMode: updateMode,
|
|
398
411
|
transformMode,
|
|
399
412
|
setTransformMode,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Matrix4 } from "three";
|
|
2
2
|
import type { Object3D, Texture } from "three";
|
|
3
|
-
import type
|
|
3
|
+
import { type ThreeEvent } from "@react-three/fiber";
|
|
4
4
|
import type { GameObject as GameObjectType, Prefab } from "./types";
|
|
5
5
|
import type { LoadedModels } from "../dragdrop";
|
|
6
6
|
import type { PrefabStoreApi } from "./prefabStore";
|
|
@@ -12,6 +12,7 @@ export type PrefabNode = Omit<GameObjectType, "children">;
|
|
|
12
12
|
export interface Scene {
|
|
13
13
|
root: Object3D | null;
|
|
14
14
|
mode: PrefabEditorMode;
|
|
15
|
+
basePath: string;
|
|
15
16
|
get(id: string): GameObjectType | null;
|
|
16
17
|
getObject(id: string): Object3D | null;
|
|
17
18
|
getHandle<T = unknown>(id: string, kind: string): T | null;
|
|
@@ -53,5 +54,6 @@ interface RendererProps {
|
|
|
53
54
|
editMode?: boolean;
|
|
54
55
|
parentMatrix?: Matrix4;
|
|
55
56
|
isVisible?: boolean;
|
|
57
|
+
basePath?: string;
|
|
56
58
|
}
|
|
57
59
|
export default PrefabRoot;
|
|
@@ -1,3 +1,12 @@
|
|
|
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
|
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
11
|
var t = {};
|
|
3
12
|
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
@@ -12,6 +21,7 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
12
21
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
13
22
|
import { createContext, forwardRef, useCallback, useContext, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
|
|
14
23
|
import { Euler, Matrix4 } from "three";
|
|
24
|
+
import { useThree } from "@react-three/fiber";
|
|
15
25
|
import { useStore } from "zustand";
|
|
16
26
|
import { useClickValid } from "./useClickValid";
|
|
17
27
|
import { findComponent, getNodeUserData } from "./types";
|
|
@@ -19,7 +29,7 @@ import { getComponentDef, registerComponent } from "./components/ComponentRegist
|
|
|
19
29
|
import { builtinComponents } from "./components";
|
|
20
30
|
import { loadModel, loadSound, loadTexture } from "../dragdrop";
|
|
21
31
|
import { GameInstance, GameInstanceProvider, getRepeatAxesFromModelProperties } from "./InstanceProvider";
|
|
22
|
-
import { composeTransform, decompose } from "./utils";
|
|
32
|
+
import { composeTransform, decompose, withBasePath } from "./utils";
|
|
23
33
|
import { createPrefabStore, PrefabStoreProvider, usePrefabChildIds, usePrefabNode, usePrefabRootId } from "./prefabStore";
|
|
24
34
|
import { AssetRuntimeContext, NodeScope } from "./assetRuntime";
|
|
25
35
|
import { gameEvents } from "./GameEvents";
|
|
@@ -37,13 +47,19 @@ const EMPTY_NODE_COMPONENTS = {
|
|
|
37
47
|
clickEventName: null,
|
|
38
48
|
composition: [],
|
|
39
49
|
};
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
50
|
+
function precompileModel(model, renderer, camera) {
|
|
51
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
52
|
+
try {
|
|
53
|
+
if (typeof renderer.compileAsync === "function") {
|
|
54
|
+
yield renderer.compileAsync(model, camera);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
renderer.compile(model, camera);
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
console.warn("Failed to precompile model before adding it to the scene", error);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
47
63
|
}
|
|
48
64
|
/** Check if a model component's assets are loaded. */
|
|
49
65
|
function isNodeReady(model, loadedModels) {
|
|
@@ -74,6 +90,8 @@ export function useScene() {
|
|
|
74
90
|
return scene;
|
|
75
91
|
}
|
|
76
92
|
export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSelect, onClick, onEditNodeClick, basePath = "", children }, ref) => {
|
|
93
|
+
const renderer = useThree(state => state.gl);
|
|
94
|
+
const camera = useThree(state => state.camera);
|
|
77
95
|
const [models, setModels] = useState({});
|
|
78
96
|
const [textures, setTextures] = useState({});
|
|
79
97
|
const [sounds, setSounds] = useState({});
|
|
@@ -84,6 +102,7 @@ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSel
|
|
|
84
102
|
const failedModels = useRef(new Set());
|
|
85
103
|
const failedTextures = useRef(new Set());
|
|
86
104
|
const failedSounds = useRef(new Set());
|
|
105
|
+
const injectedModelVersions = useRef({});
|
|
87
106
|
const objectRefs = useRef({});
|
|
88
107
|
const nodeHandles = useRef(new Map());
|
|
89
108
|
const [ownedStore] = useState(() => {
|
|
@@ -140,6 +159,7 @@ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSel
|
|
|
140
159
|
return (_a = objectRefs.current[rootId]) !== null && _a !== void 0 ? _a : null;
|
|
141
160
|
},
|
|
142
161
|
mode: editMode ? PrefabEditorMode.Edit : PrefabEditorMode.Play,
|
|
162
|
+
basePath,
|
|
143
163
|
get: getNode,
|
|
144
164
|
getObject,
|
|
145
165
|
getHandle,
|
|
@@ -155,13 +175,22 @@ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSel
|
|
|
155
175
|
duplicate: (id) => resolvedStore.getState().duplicateNode(id),
|
|
156
176
|
move: (draggedId, targetId, position) => resolvedStore.getState().moveNode(draggedId, targetId, position),
|
|
157
177
|
replace: (prefab) => resolvedStore.getState().replacePrefab(prefab),
|
|
158
|
-
addModel: (path, model) =>
|
|
178
|
+
addModel: (path, model) => {
|
|
179
|
+
var _a;
|
|
180
|
+
const version = ((_a = injectedModelVersions.current[path]) !== null && _a !== void 0 ? _a : 0) + 1;
|
|
181
|
+
injectedModelVersions.current[path] = version;
|
|
182
|
+
void precompileModel(model, renderer, camera).then(() => {
|
|
183
|
+
if (injectedModelVersions.current[path] !== version)
|
|
184
|
+
return;
|
|
185
|
+
setInjectedModels(prev => (Object.assign(Object.assign({}, prev), { [path]: model })));
|
|
186
|
+
});
|
|
187
|
+
},
|
|
159
188
|
addTexture: (path, texture) => setInjectedTextures(prev => (Object.assign(Object.assign({}, prev), { [path]: texture }))),
|
|
160
189
|
addSound: (path, sound) => {
|
|
161
190
|
soundManager.setBuffer(path, sound);
|
|
162
191
|
setInjectedSounds(prev => (Object.assign(Object.assign({}, prev), { [path]: sound })));
|
|
163
192
|
},
|
|
164
|
-
}), [editMode, getHandle, getModel, getNode, getObject, resolvedStore, rootId]);
|
|
193
|
+
}), [basePath, camera, editMode, getHandle, getModel, getNode, getObject, renderer, resolvedStore, rootId]);
|
|
165
194
|
useImperativeHandle(ref, () => sceneValue, [sceneValue]);
|
|
166
195
|
const registerRef = useCallback((id, obj) => {
|
|
167
196
|
objectRefs.current[id] = obj;
|
|
@@ -176,7 +205,7 @@ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSel
|
|
|
176
205
|
if (loaded[file] || injected[file] || loading.current.has(file) || failed.has(file))
|
|
177
206
|
return;
|
|
178
207
|
loading.current.add(file);
|
|
179
|
-
void loader(
|
|
208
|
+
void loader(withBasePath(basePath, file)).then(result => {
|
|
180
209
|
loading.current.delete(file);
|
|
181
210
|
if (!result.success) {
|
|
182
211
|
console.warn(`Failed to load asset: ${file}`, result.error);
|
|
@@ -189,13 +218,14 @@ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSel
|
|
|
189
218
|
const type = entry.slice(0, separator);
|
|
190
219
|
const file = entry.slice(separator + 1);
|
|
191
220
|
if (type === 'model') {
|
|
192
|
-
loadAsset(file, models, injectedModels, failedModels.current, path => loadModel(path).then(result => {
|
|
221
|
+
loadAsset(file, models, injectedModels, failedModels.current, path => loadModel(path).then((result) => __awaiter(void 0, void 0, void 0, function* () {
|
|
193
222
|
const loadedModel = result.model;
|
|
194
223
|
if (result.success && loadedModel) {
|
|
224
|
+
yield precompileModel(loadedModel, renderer, camera);
|
|
195
225
|
setModels(currentModels => (Object.assign(Object.assign({}, currentModels), { [file]: loadedModel })));
|
|
196
226
|
}
|
|
197
227
|
return result;
|
|
198
|
-
}));
|
|
228
|
+
})));
|
|
199
229
|
}
|
|
200
230
|
else if (type === 'texture') {
|
|
201
231
|
loadAsset(file, textures, injectedTextures, failedTextures.current, path => loadTexture(path).then(result => {
|
|
@@ -217,7 +247,7 @@ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSel
|
|
|
217
247
|
}));
|
|
218
248
|
}
|
|
219
249
|
});
|
|
220
|
-
}, [assetRefCounts, basePath, injectedModels, injectedSounds, injectedTextures, models, sounds, textures]);
|
|
250
|
+
}, [assetRefCounts, basePath, camera, injectedModels, injectedSounds, injectedTextures, models, renderer, sounds, textures]);
|
|
221
251
|
const assetRuntime = useMemo(() => ({
|
|
222
252
|
registerHandle,
|
|
223
253
|
getHandle,
|
|
@@ -235,7 +265,7 @@ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSel
|
|
|
235
265
|
emitNodePointerEvent(clickEventName, event, nodeId, node, fallbackObject);
|
|
236
266
|
onClick === null || onClick === void 0 ? void 0 : onClick(event, node);
|
|
237
267
|
}, [onClick, resolvedStore]);
|
|
238
|
-
const content = (_jsxs(GameInstanceProvider, { models: availableModels, selectedId: selectedId, editMode: editMode, onSelect: editMode ? onSelect : undefined, onClick: editMode ? undefined : handleNodeClick, registerRef: registerRef, children: [_jsx(StoreRootNode, { selectedId: selectedId, onSelect: editMode ? onSelect : undefined, onClick: editMode ? undefined : handleNodeClick, onEditNodeClick: editMode ? onEditNodeClick : undefined, registerRef: registerRef, loadedModels: availableModels, editMode: editMode, parentMatrix: IDENTITY }), children] }));
|
|
268
|
+
const content = (_jsxs(GameInstanceProvider, { models: availableModels, selectedId: selectedId, editMode: editMode, onSelect: editMode ? onSelect : undefined, onClick: editMode ? undefined : handleNodeClick, registerRef: registerRef, children: [_jsx(StoreRootNode, { selectedId: selectedId, onSelect: editMode ? onSelect : undefined, onClick: editMode ? undefined : handleNodeClick, onEditNodeClick: editMode ? onEditNodeClick : undefined, registerRef: registerRef, loadedModels: availableModels, editMode: editMode, parentMatrix: IDENTITY, basePath: basePath }), children] }));
|
|
239
269
|
const runtimeContent = (_jsx(SceneContext.Provider, { value: sceneValue, children: _jsx(AssetRuntimeContext.Provider, { value: assetRuntime, children: content }) }));
|
|
240
270
|
return _jsx(PrefabStoreProvider, { store: resolvedStore, children: runtimeContent });
|
|
241
271
|
});
|
|
@@ -303,10 +333,7 @@ function analyzeNodeComponents(node) {
|
|
|
303
333
|
}
|
|
304
334
|
function emitNodePointerEvent(eventName, event, nodeId, node, fallbackObject) {
|
|
305
335
|
var _a;
|
|
306
|
-
const
|
|
307
|
-
if (!trimmedEventName)
|
|
308
|
-
return;
|
|
309
|
-
gameEvents.emit(trimmedEventName, {
|
|
336
|
+
const payload = {
|
|
310
337
|
sourceEntityId: nodeId,
|
|
311
338
|
sourceNodeId: nodeId,
|
|
312
339
|
nodeId,
|
|
@@ -319,7 +346,12 @@ function emitNodePointerEvent(eventName, event, nodeId, node, fallbackObject) {
|
|
|
319
346
|
metaKey: event.nativeEvent.metaKey,
|
|
320
347
|
shiftKey: event.nativeEvent.shiftKey,
|
|
321
348
|
r3fEvent: event,
|
|
322
|
-
}
|
|
349
|
+
};
|
|
350
|
+
gameEvents.emit('click', payload);
|
|
351
|
+
const trimmedEventName = eventName === null || eventName === void 0 ? void 0 : eventName.trim();
|
|
352
|
+
if (!trimmedEventName)
|
|
353
|
+
return;
|
|
354
|
+
gameEvents.emit(trimmedEventName, payload);
|
|
323
355
|
}
|
|
324
356
|
export function GameObjectRenderer(props) {
|
|
325
357
|
var _a, _b;
|
|
@@ -374,7 +406,7 @@ function InstancedNode({ nodeId, parentMatrix = IDENTITY, editMode, registerRef,
|
|
|
374
406
|
}
|
|
375
407
|
return _jsx(_Fragment, { children: renderedInstances });
|
|
376
408
|
}
|
|
377
|
-
function StandardNode({ nodeId, selectedId, onSelect, onClick, onEditNodeClick, registerRef, loadedModels, editMode, parentMatrix = IDENTITY, isVisible = true, }) {
|
|
409
|
+
function StandardNode({ nodeId, selectedId, onSelect, onClick, onEditNodeClick, registerRef, loadedModels, editMode, parentMatrix = IDENTITY, isVisible = true, basePath = "", }) {
|
|
378
410
|
const gameObject = usePrefabNode(nodeId);
|
|
379
411
|
const childIds = usePrefabChildIds(nodeId);
|
|
380
412
|
const analyzedComponents = useMemo(() => gameObject ? analyzeNodeComponents(gameObject) : EMPTY_NODE_COMPONENTS, [gameObject]);
|
|
@@ -410,9 +442,13 @@ function StandardNode({ nodeId, selectedId, onSelect, onClick, onEditNodeClick,
|
|
|
410
442
|
rotation: transform.rotation,
|
|
411
443
|
scale: transform.scale,
|
|
412
444
|
};
|
|
445
|
+
const worldTransform = decompose(world);
|
|
413
446
|
const groupProps = Object.assign(Object.assign({}, metadataProps), transformProps);
|
|
414
|
-
const childNodes = _jsx(ChildNodes, { childIds: childIds, parentMatrix: world, selectedId: selectedId, onSelect: onSelect, onClick: onClick, onEditNodeClick: onEditNodeClick, registerRef: registerRef, loadedModels: loadedModels, editMode: editMode, isVisible: nodeVisible });
|
|
415
|
-
const
|
|
447
|
+
const childNodes = _jsx(ChildNodes, { childIds: childIds, parentMatrix: world, selectedId: selectedId, onSelect: onSelect, onClick: onClick, onEditNodeClick: onEditNodeClick, registerRef: registerRef, loadedModels: loadedModels, editMode: editMode, isVisible: nodeVisible, basePath: basePath });
|
|
448
|
+
const nodeInteractionHandlers = editMode ? editClickHandlers : primaryClickHandlers;
|
|
449
|
+
const componentRuntimeProps = Object.assign(Object.assign({ editMode,
|
|
450
|
+
nodeInteractionHandlers }, transformProps), { worldPosition: worldTransform.position });
|
|
451
|
+
const inner = renderNodeContent(analyzedComponents, loadedModels, primaryClickHandlers, childNodes, basePath, componentRuntimeProps);
|
|
416
452
|
const editAnchor = editMode ? (_jsx("mesh", { visible: false, children: _jsx("boxGeometry", { args: [0.01, 0.01, 0.01] }) })) : null;
|
|
417
453
|
const standardNode = (_jsxs("group", Object.assign({ ref: handleGroupRef }, groupProps, { visible: nodeVisible }, (editMode ? editClickHandlers : undefined), { children: [editAnchor, inner] })));
|
|
418
454
|
return (_jsx(NodeScope, { nodeId: nodeId, editMode: editMode, isSelected: isSelected, children: standardNode }));
|
|
@@ -485,7 +521,7 @@ function getNodeTransformProps(node) {
|
|
|
485
521
|
scale: (_d = t === null || t === void 0 ? void 0 : t.scale) !== null && _d !== void 0 ? _d : [1, 1, 1],
|
|
486
522
|
};
|
|
487
523
|
}
|
|
488
|
-
function renderNodeContent(analyzedComponents, loadedModels, primaryClickHandlers, childNodes) {
|
|
524
|
+
function renderNodeContent(analyzedComponents, loadedModels, primaryClickHandlers, childNodes, basePath = "", componentRuntimeProps) {
|
|
489
525
|
var _a, _b, _c, _d, _e, _f;
|
|
490
526
|
const geometry = analyzedComponents.geometry;
|
|
491
527
|
const models = analyzedComponents.models;
|
|
@@ -550,7 +586,7 @@ function renderNodeContent(analyzedComponents, loadedModels, primaryClickHandler
|
|
|
550
586
|
}
|
|
551
587
|
let content = _jsxs(_Fragment, { children: [primaryContent, contentChildren] });
|
|
552
588
|
for (const { key, View, properties } of analyzedComponents.composition) {
|
|
553
|
-
content = (_jsx(View, { properties: properties, children: content }, key));
|
|
589
|
+
content = (_jsx(View, Object.assign({ properties: properties, basePath: basePath }, componentRuntimeProps, { children: content }), key));
|
|
554
590
|
}
|
|
555
591
|
return content;
|
|
556
592
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { FC } from "react";
|
|
2
|
+
import type { ThreeEvent } from "@react-three/fiber";
|
|
2
3
|
import type { ComponentData, GameObject } from "../types";
|
|
3
4
|
export type AssetRef = {
|
|
4
5
|
type: "model" | "texture" | "sound";
|
|
@@ -12,13 +13,27 @@ export interface ComponentViewProps<P = Record<string, unknown>> {
|
|
|
12
13
|
properties: P;
|
|
13
14
|
/** Children to render for components that wrap the current subtree. */
|
|
14
15
|
children?: React.ReactNode;
|
|
16
|
+
/** Whether this node is currently rendered in editor mode. */
|
|
17
|
+
editMode?: boolean;
|
|
18
|
+
/** Node-level pointer/click handlers for custom components that render their own pickable objects. */
|
|
19
|
+
nodeInteractionHandlers?: NodeInteractionHandlers;
|
|
15
20
|
/** Current node local position for wrapper components. */
|
|
16
21
|
position?: [number, number, number];
|
|
17
22
|
/** Current node local rotation in radians for wrapper components. */
|
|
18
23
|
rotation?: [number, number, number];
|
|
19
24
|
/** Current node local scale for wrapper components. */
|
|
20
25
|
scale?: [number, number, number];
|
|
26
|
+
/** Current node world position. Components that create world-space resources should prefer this. */
|
|
27
|
+
worldPosition?: [number, number, number];
|
|
28
|
+
/** Public asset URL prefix, such as a Next.js basePath. */
|
|
29
|
+
basePath?: string;
|
|
21
30
|
}
|
|
31
|
+
export type NodeInteractionHandlers = {
|
|
32
|
+
onClick?: (event: ThreeEvent<PointerEvent>) => void;
|
|
33
|
+
onPointerDown?: (event: ThreeEvent<PointerEvent>) => void;
|
|
34
|
+
onPointerMove?: (event: ThreeEvent<PointerEvent>) => void;
|
|
35
|
+
onPointerUp?: (event: ThreeEvent<PointerEvent>) => void;
|
|
36
|
+
};
|
|
22
37
|
export interface Component {
|
|
23
38
|
name: string;
|
|
24
39
|
/** Set when this component occupies a single slot on a node. Use a string to share a slot across component types. */
|