react-three-game 0.0.51 → 0.0.53
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/helpers/index.js +1 -1
- package/dist/tools/prefabeditor/EditorTree.js +48 -7
- package/dist/tools/prefabeditor/EditorUI.js +2 -2
- package/dist/tools/prefabeditor/PrefabEditor.d.ts +1 -0
- package/dist/tools/prefabeditor/PrefabEditor.js +4 -3
- package/dist/tools/prefabeditor/PrefabRoot.d.ts +2 -3
- package/dist/tools/prefabeditor/PrefabRoot.js +9 -3
- package/dist/tools/prefabeditor/components/GeometryComponent.js +8 -8
- package/dist/tools/prefabeditor/components/Input.d.ts +2 -1
- package/dist/tools/prefabeditor/components/Input.js +63 -11
- package/dist/tools/prefabeditor/components/MaterialComponent.js +4 -2
- package/dist/tools/prefabeditor/components/ModelComponent.js +3 -1
- package/dist/tools/prefabeditor/components/PhysicsComponent.d.ts +3 -1
- package/dist/tools/prefabeditor/components/PhysicsComponent.js +33 -2
- package/dist/tools/prefabeditor/styles.d.ts +1 -4
- package/dist/tools/prefabeditor/styles.js +2 -0
- package/package.json +7 -7
- package/react-three-game-skill/react-three-game/SKILL.md +4 -2
- package/react-three-game-skill/react-three-game/rules/ADVANCED_PHYSICS.md +17 -1
- package/src/helpers/index.ts +1 -1
- package/src/tools/prefabeditor/EditorTree.tsx +92 -17
- package/src/tools/prefabeditor/EditorUI.tsx +2 -2
- package/src/tools/prefabeditor/PrefabEditor.tsx +24 -15
- package/src/tools/prefabeditor/PrefabRoot.tsx +14 -6
- package/src/tools/prefabeditor/components/GeometryComponent.tsx +13 -13
- package/src/tools/prefabeditor/components/Input.tsx +110 -20
- package/src/tools/prefabeditor/components/MaterialComponent.tsx +22 -6
- package/src/tools/prefabeditor/components/ModelComponent.tsx +9 -1
- package/src/tools/prefabeditor/components/PhysicsComponent.tsx +39 -3
- package/src/tools/prefabeditor/styles.ts +3 -1
package/dist/helpers/index.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* - Physics (fixed by default)
|
|
9
9
|
*/
|
|
10
10
|
export function ground(options = {}) {
|
|
11
|
-
const { id = "ground", size = 50, position = [0, 0, 0], rotation = [-Math.PI / 2, 0, 0], scale = [1, 1, 1], color = "
|
|
11
|
+
const { id = "ground", size = 50, position = [0, 0, 0], rotation = [-Math.PI / 2, 0, 0], scale = [1, 1, 1], color = "#eeeeee", texture, repeat = texture ? true : false, repeatCount = [25, 25], physicsType = "fixed", disabled = false, } = options;
|
|
12
12
|
return {
|
|
13
13
|
id,
|
|
14
14
|
disabled,
|
|
@@ -19,6 +19,7 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
|
|
|
19
19
|
const [collapsedIds, setCollapsedIds] = useState(new Set());
|
|
20
20
|
const [collapsed, setCollapsed] = useState(false);
|
|
21
21
|
const [fileMenuOpen, setFileMenuOpen] = useState(false);
|
|
22
|
+
const [searchQuery, setSearchQuery] = useState('');
|
|
22
23
|
if (!prefabData || !setPrefabData)
|
|
23
24
|
return null;
|
|
24
25
|
const handleContextMenu = (e, nodeId) => {
|
|
@@ -73,6 +74,10 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
|
|
|
73
74
|
setSelectedId(null);
|
|
74
75
|
setContextMenu(null);
|
|
75
76
|
};
|
|
77
|
+
const handleToggleDisabled = (nodeId) => {
|
|
78
|
+
setPrefabData(prev => (Object.assign(Object.assign({}, prev), { root: updateNodeById(prev.root, nodeId, node => (Object.assign(Object.assign({}, node), { disabled: !node.disabled }))) })));
|
|
79
|
+
setContextMenu(null);
|
|
80
|
+
};
|
|
76
81
|
const handleDragStart = (e, id) => {
|
|
77
82
|
if (id === prefabData.root.id)
|
|
78
83
|
return e.preventDefault();
|
|
@@ -105,23 +110,59 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
|
|
|
105
110
|
});
|
|
106
111
|
setDraggedId(null);
|
|
107
112
|
};
|
|
113
|
+
const matchesSearch = (node, query) => {
|
|
114
|
+
var _a, _b, _c;
|
|
115
|
+
if (!query)
|
|
116
|
+
return true;
|
|
117
|
+
const lowerQuery = query.toLowerCase();
|
|
118
|
+
const nodeName = ((_a = node.name) !== null && _a !== void 0 ? _a : node.id).toLowerCase();
|
|
119
|
+
if (nodeName.includes(lowerQuery))
|
|
120
|
+
return true;
|
|
121
|
+
return (_c = (_b = node.children) === null || _b === void 0 ? void 0 : _b.some(child => matchesSearch(child, query))) !== null && _c !== void 0 ? _c : false;
|
|
122
|
+
};
|
|
108
123
|
const renderNode = (node, depth = 0) => {
|
|
109
124
|
var _a;
|
|
110
125
|
if (!node)
|
|
111
126
|
return null;
|
|
127
|
+
if (!matchesSearch(node, searchQuery))
|
|
128
|
+
return null;
|
|
112
129
|
const isSelected = node.id === selectedId;
|
|
113
130
|
const isCollapsed = collapsedIds.has(node.id);
|
|
114
131
|
const hasChildren = node.children && node.children.length > 0;
|
|
115
132
|
const isRoot = node.id === prefabData.root.id;
|
|
116
|
-
return (_jsxs("div", { children: [_jsxs("div", { style: Object.assign(Object.assign(Object.assign({}, tree.row), (isSelected ? tree.selected : {})), { paddingLeft: `${depth * 12 + 6}px
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
133
|
+
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' }), draggable: !isRoot, onClick: (e) => { e.stopPropagation(); setSelectedId(node.id); }, onContextMenu: (e) => handleContextMenu(e, node.id), onDragStart: (e) => handleDragStart(e, node.id), onDragEnd: () => setDraggedId(null), onDragOver: (e) => handleDragOver(e, node.id), onDrop: (e) => handleDrop(e, node.id), children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', flex: 1, minWidth: 0 }, children: [_jsx("span", { style: {
|
|
134
|
+
width: 12,
|
|
135
|
+
opacity: 0.6,
|
|
136
|
+
marginRight: 4,
|
|
137
|
+
cursor: 'pointer',
|
|
138
|
+
visibility: hasChildren ? 'visible' : 'hidden'
|
|
139
|
+
}, 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 && (_jsx("button", { style: {
|
|
140
|
+
background: 'none',
|
|
141
|
+
border: 'none',
|
|
120
142
|
cursor: 'pointer',
|
|
121
|
-
|
|
122
|
-
|
|
143
|
+
padding: '0 4px',
|
|
144
|
+
fontSize: 14,
|
|
145
|
+
opacity: node.disabled ? 0.5 : 0.7,
|
|
146
|
+
color: 'inherit',
|
|
147
|
+
}, onClick: (e) => {
|
|
148
|
+
e.stopPropagation();
|
|
149
|
+
handleToggleDisabled(node.id);
|
|
150
|
+
}, title: node.disabled ? 'Enable' : 'Disable', children: node.disabled ? '◎' : '◉' }))] }), !isCollapsed && node.children && node.children.map(child => renderNode(child, depth + 1))] }, node.id));
|
|
123
151
|
};
|
|
124
|
-
return (_jsxs(_Fragment, { children: [
|
|
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 6px', 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" })] }))] }))] }));
|
|
125
166
|
}
|
|
126
167
|
function FileMenu({ prefabData, setPrefabData, onClose }) {
|
|
127
168
|
const { onScreenshot, onExportGLB } = useEditorContext();
|
|
@@ -47,13 +47,13 @@ function NodeInspector({ node, updateNode, deleteNode, basePath }) {
|
|
|
47
47
|
if (!newAvailable.includes(addType))
|
|
48
48
|
setAddType(newAvailable[0] || "");
|
|
49
49
|
}, [Object.keys(node.components || {}).join(',')]);
|
|
50
|
-
return _jsxs("div", { style: inspector.content, 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: '#888', wordBreak: 'break-all', border: '1px solid rgba(255,255,255,0.1)', padding: '2px 4px', borderRadius: 4, flex: 1 }, 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]) => {
|
|
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: '#888', wordBreak: 'break-all', border: '1px solid rgba(255,255,255,0.1)', padding: '2px 4px', borderRadius: 4, flex: 1 }, 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
51
|
if (!comp)
|
|
52
52
|
return null;
|
|
53
53
|
const def = ALL_COMPONENTS[comp.type];
|
|
54
54
|
if (!def)
|
|
55
55
|
return _jsxs("div", { style: { color: '#ff8888', fontSize: 11 }, children: ["Unknown: ", comp.type] }, key);
|
|
56
|
-
return (_jsxs("div", { style: { marginBottom: 8 }, 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 => {
|
|
56
|
+
return (_jsxs("div", { style: { marginBottom: 8, backgroundColor: 'rgba(255, 255, 255, 0.1)', padding: 8, borderRadius: 4 }, 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
57
|
const _a = n.components || {}, _b = key, _ = _a[_b], rest = __rest(_a, [typeof _b === "symbol" ? _b : _b + ""]);
|
|
58
58
|
return Object.assign(Object.assign({}, n), { components: rest });
|
|
59
59
|
}), 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));
|
|
@@ -10,6 +10,7 @@ export interface PrefabEditorRef {
|
|
|
10
10
|
declare const PrefabEditor: import("react").ForwardRefExoticComponent<{
|
|
11
11
|
basePath?: string;
|
|
12
12
|
initialPrefab?: Prefab;
|
|
13
|
+
physics?: boolean;
|
|
13
14
|
onPrefabChange?: (prefab: Prefab) => void;
|
|
14
15
|
children?: React.ReactNode;
|
|
15
16
|
} & import("react").RefAttributes<PrefabEditorRef>>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import GameCanvas from "../../shared/GameCanvas";
|
|
3
3
|
import { useState, useRef, useEffect, forwardRef, useImperativeHandle } from "react";
|
|
4
4
|
import PrefabRoot from "./PrefabRoot";
|
|
@@ -20,7 +20,7 @@ const DEFAULT_PREFAB = {
|
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
};
|
|
23
|
-
const PrefabEditor = forwardRef(({ basePath, initialPrefab, onPrefabChange, children }, ref) => {
|
|
23
|
+
const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, onPrefabChange, children }, ref) => {
|
|
24
24
|
const [editMode, setEditMode] = useState(true);
|
|
25
25
|
const [loadedPrefab, setLoadedPrefab] = useState(initialPrefab !== null && initialPrefab !== void 0 ? initialPrefab : DEFAULT_PREFAB);
|
|
26
26
|
const [selectedId, setSelectedId] = useState(null);
|
|
@@ -118,6 +118,7 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, onPrefabChange, chil
|
|
|
118
118
|
setPrefab: setLoadedPrefab,
|
|
119
119
|
rootRef: prefabRootRef
|
|
120
120
|
}), [loadedPrefab]);
|
|
121
|
+
const content = (_jsxs(_Fragment, { children: [_jsx("ambientLight", { intensity: 1.5 }), _jsx("gridHelper", { args: [10, 10], position: [0, -1, 0] }), _jsx(PrefabRoot, { ref: prefabRootRef, data: loadedPrefab, editMode: editMode, onPrefabChange: updatePrefab, selectedId: selectedId, onSelect: setSelectedId, basePath: basePath }), children] }));
|
|
121
122
|
return _jsxs(EditorContext.Provider, { value: {
|
|
122
123
|
transformMode,
|
|
123
124
|
setTransformMode,
|
|
@@ -125,7 +126,7 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, onPrefabChange, chil
|
|
|
125
126
|
setSnapResolution,
|
|
126
127
|
onScreenshot: handleScreenshot,
|
|
127
128
|
onExportGLB: handleExportGLB
|
|
128
|
-
}, children: [_jsx(GameCanvas, { children:
|
|
129
|
+
}, children: [_jsx(GameCanvas, { children: physics ? (_jsx(Physics, { debug: editMode, paused: editMode, children: content })) : content }), _jsx("div", { style: toolbar.panel, children: _jsx("button", { style: base.btn, onClick: () => setEditMode(!editMode), children: editMode ? "▶" : "⏸" }) }), editMode && _jsx(EditorUI, { prefabData: loadedPrefab, setPrefabData: updatePrefab, selectedId: selectedId, setSelectedId: setSelectedId, basePath: basePath, onUndo: undo, onRedo: redo, canUndo: historyIndex > 0, canRedo: historyIndex < history.length - 1 })] });
|
|
129
130
|
});
|
|
130
131
|
PrefabEditor.displayName = "PrefabEditor";
|
|
131
132
|
export default PrefabEditor;
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { Group, Matrix4, Object3D, Texture } from "three";
|
|
2
2
|
import { ThreeEvent } from "@react-three/fiber";
|
|
3
3
|
import { Prefab, GameObject as GameObjectType } from "./types";
|
|
4
|
-
import type { RapierRigidBody } from "@react-three/rapier";
|
|
5
4
|
export interface PrefabRootRef {
|
|
6
5
|
root: Group | null;
|
|
7
|
-
rigidBodyRefs: Map<string,
|
|
6
|
+
rigidBodyRefs: Map<string, any>;
|
|
8
7
|
}
|
|
9
8
|
export declare const PrefabRoot: import("react").ForwardRefExoticComponent<{
|
|
10
9
|
editMode?: boolean;
|
|
@@ -22,7 +21,7 @@ interface RendererProps {
|
|
|
22
21
|
onSelect?: (id: string) => void;
|
|
23
22
|
onClick?: (event: ThreeEvent<PointerEvent>, entity: GameObjectType) => void;
|
|
24
23
|
registerRef: (id: string, obj: Object3D | null) => void;
|
|
25
|
-
registerRigidBodyRef: (id: string, rb:
|
|
24
|
+
registerRigidBodyRef: (id: string, rb: any) => void;
|
|
26
25
|
loadedModels: Record<string, Object3D>;
|
|
27
26
|
loadedTextures: Record<string, Texture>;
|
|
28
27
|
editMode?: boolean;
|
|
@@ -99,12 +99,18 @@ export const PrefabRoot = forwardRef(({ editMode, data, onPrefabChange, selected
|
|
|
99
99
|
if (textures[file] || loading.current.has(file))
|
|
100
100
|
return;
|
|
101
101
|
loading.current.add(file);
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
102
|
+
// Handle full URLs (http/https) or regular paths
|
|
103
|
+
const path = file.startsWith("http://") || file.startsWith("https://")
|
|
104
|
+
? file
|
|
105
|
+
: file.startsWith("/")
|
|
106
|
+
? `${basePath}${file}`
|
|
107
|
+
: `${basePath}/${file}`;
|
|
105
108
|
loader.load(path, tex => {
|
|
106
109
|
tex.colorSpace = SRGBColorSpace;
|
|
107
110
|
setTextures(t => (Object.assign(Object.assign({}, t), { [file]: tex })));
|
|
111
|
+
}, undefined, (err) => {
|
|
112
|
+
console.error(`Failed to load texture: ${path}`, err);
|
|
113
|
+
loading.current.delete(file);
|
|
108
114
|
});
|
|
109
115
|
});
|
|
110
116
|
}, [data, models, textures]);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { jsx as _jsx
|
|
2
|
-
import { FieldRenderer, Input
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { FieldRenderer, Input } from "./Input";
|
|
3
3
|
const GEOMETRY_ARGS = {
|
|
4
4
|
box: {
|
|
5
5
|
labels: ["Width", "Height", "Depth"],
|
|
@@ -41,13 +41,13 @@ function GeometryComponentEditor({ component, onUpdate, }) {
|
|
|
41
41
|
const currentType = values.geometryType;
|
|
42
42
|
const currentSchema = GEOMETRY_ARGS[currentType];
|
|
43
43
|
const currentArgs = values.args || currentSchema.defaults;
|
|
44
|
-
return (_jsx("div", { style: { display: 'flex', flexDirection: 'column', gap:
|
|
44
|
+
return (_jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: 4 }, children: currentSchema.labels.map((label, i) => {
|
|
45
45
|
var _a;
|
|
46
|
-
return (
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
return (_jsx(Input, { label: label, value: (_a = currentArgs[i]) !== null && _a !== void 0 ? _a : currentSchema.defaults[i], step: 0.1, min: 0.01, onChange: value => {
|
|
47
|
+
const next = [...currentArgs];
|
|
48
|
+
next[i] = value;
|
|
49
|
+
onChangeMultiple({ args: next });
|
|
50
|
+
} }, label));
|
|
51
51
|
}) }));
|
|
52
52
|
},
|
|
53
53
|
},
|
|
@@ -52,8 +52,9 @@ interface InputProps {
|
|
|
52
52
|
min?: number;
|
|
53
53
|
max?: number;
|
|
54
54
|
style?: React.CSSProperties;
|
|
55
|
+
label?: string;
|
|
55
56
|
}
|
|
56
|
-
export declare function Input({ value, onChange, step, min, max, style }: InputProps): import("react/jsx-runtime").JSX.Element;
|
|
57
|
+
export declare function Input({ value, onChange, step, min, max, style, label }: InputProps): import("react/jsx-runtime").JSX.Element;
|
|
57
58
|
export declare function Label({ children }: {
|
|
58
59
|
children: React.ReactNode;
|
|
59
60
|
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -6,7 +6,7 @@ import { useEffect, useRef, useState } from 'react';
|
|
|
6
6
|
// Shared styles
|
|
7
7
|
const styles = {
|
|
8
8
|
input: {
|
|
9
|
-
width: '
|
|
9
|
+
width: '80px',
|
|
10
10
|
backgroundColor: 'rgba(0, 0, 0, 0.4)',
|
|
11
11
|
border: '1px solid rgba(34, 211, 238, 0.3)',
|
|
12
12
|
padding: '2px 4px',
|
|
@@ -14,17 +14,18 @@ const styles = {
|
|
|
14
14
|
color: 'rgba(165, 243, 252, 1)',
|
|
15
15
|
fontFamily: 'monospace',
|
|
16
16
|
outline: 'none',
|
|
17
|
+
textAlign: 'right',
|
|
17
18
|
},
|
|
18
19
|
label: {
|
|
19
20
|
display: 'block',
|
|
20
21
|
fontSize: '9px',
|
|
21
|
-
color: 'rgba(34, 211, 238, 0.
|
|
22
|
+
color: 'rgba(34, 211, 238, 0.9)',
|
|
22
23
|
textTransform: 'uppercase',
|
|
23
24
|
letterSpacing: '0.05em',
|
|
24
25
|
marginBottom: 2,
|
|
25
26
|
},
|
|
26
27
|
};
|
|
27
|
-
export function Input({ value, onChange, step, min, max, style }) {
|
|
28
|
+
export function Input({ value, onChange, step, min, max, style, label }) {
|
|
28
29
|
const [draft, setDraft] = useState(() => value.toString());
|
|
29
30
|
useEffect(() => {
|
|
30
31
|
setDraft(value.toString());
|
|
@@ -43,6 +44,55 @@ export function Input({ value, onChange, step, min, max, style }) {
|
|
|
43
44
|
setDraft(value.toString());
|
|
44
45
|
}
|
|
45
46
|
};
|
|
47
|
+
const dragState = useRef(null);
|
|
48
|
+
const startScrub = (e) => {
|
|
49
|
+
if (!label)
|
|
50
|
+
return;
|
|
51
|
+
e.preventDefault();
|
|
52
|
+
dragState.current = {
|
|
53
|
+
startX: e.clientX,
|
|
54
|
+
startValue: value
|
|
55
|
+
};
|
|
56
|
+
e.target.setPointerCapture(e.pointerId);
|
|
57
|
+
document.body.style.cursor = "ew-resize";
|
|
58
|
+
};
|
|
59
|
+
const onScrubMove = (e) => {
|
|
60
|
+
if (!dragState.current)
|
|
61
|
+
return;
|
|
62
|
+
const { startX, startValue } = dragState.current;
|
|
63
|
+
const dx = e.clientX - startX;
|
|
64
|
+
let speed = 0.02;
|
|
65
|
+
if (e.shiftKey)
|
|
66
|
+
speed *= 0.1; // fine
|
|
67
|
+
if (e.altKey)
|
|
68
|
+
speed *= 5; // coarse
|
|
69
|
+
let nextValue = startValue + dx * speed;
|
|
70
|
+
// Apply min/max constraints
|
|
71
|
+
if (min !== undefined && nextValue < min)
|
|
72
|
+
nextValue = min;
|
|
73
|
+
if (max !== undefined && nextValue > max)
|
|
74
|
+
nextValue = max;
|
|
75
|
+
setDraft(nextValue.toFixed(3));
|
|
76
|
+
onChange(nextValue);
|
|
77
|
+
};
|
|
78
|
+
const endScrub = (e) => {
|
|
79
|
+
if (!dragState.current)
|
|
80
|
+
return;
|
|
81
|
+
dragState.current = null;
|
|
82
|
+
document.body.style.cursor = "";
|
|
83
|
+
e.target.releasePointerCapture(e.pointerId);
|
|
84
|
+
};
|
|
85
|
+
if (label) {
|
|
86
|
+
return (_jsxs("div", { style: {
|
|
87
|
+
display: 'flex',
|
|
88
|
+
alignItems: 'center',
|
|
89
|
+
justifyContent: 'space-between',
|
|
90
|
+
}, children: [_jsx("span", { style: Object.assign(Object.assign({}, styles.label), { marginBottom: 0, cursor: 'ew-resize', userSelect: 'none', flex: '0 0 auto', minWidth: 20 }), onPointerDown: startScrub, onPointerMove: onScrubMove, onPointerUp: endScrub, children: label }), _jsx("input", { type: "text", value: draft, onChange: handleChange, onBlur: handleBlur, onKeyDown: e => {
|
|
91
|
+
if (e.key === 'Enter') {
|
|
92
|
+
e.target.blur();
|
|
93
|
+
}
|
|
94
|
+
}, step: step, min: min, max: max, style: Object.assign(Object.assign({}, styles.input), style) })] }));
|
|
95
|
+
}
|
|
46
96
|
return (_jsx("input", { type: "text", value: draft, onChange: handleChange, onBlur: handleBlur, onKeyDown: e => {
|
|
47
97
|
if (e.key === 'Enter') {
|
|
48
98
|
e.target.blur();
|
|
@@ -156,20 +206,22 @@ export function Vector3Input({ label, value, onChange, snap }) {
|
|
|
156
206
|
// Additional Input Components
|
|
157
207
|
// ============================================================================
|
|
158
208
|
export function ColorInput({ label, value, onChange }) {
|
|
159
|
-
return (_jsxs("div", { children: [label && _jsx(Label, { children: label }), _jsxs("div", { style: { display: 'flex', gap:
|
|
160
|
-
height:
|
|
161
|
-
width:
|
|
209
|
+
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
|
+
height: 32,
|
|
211
|
+
width: 48,
|
|
162
212
|
backgroundColor: 'transparent',
|
|
163
|
-
border: '
|
|
213
|
+
border: '1px solid rgba(34, 211, 238, 0.3)',
|
|
214
|
+
borderRadius: 4,
|
|
164
215
|
cursor: 'pointer',
|
|
165
216
|
padding: 0,
|
|
166
|
-
|
|
217
|
+
flexShrink: 0,
|
|
218
|
+
}, 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) })] })] }));
|
|
167
219
|
}
|
|
168
220
|
export function StringInput({ label, value, onChange, placeholder }) {
|
|
169
221
|
return (_jsxs("div", { children: [label && _jsx(Label, { children: label }), _jsx("input", { type: "text", style: styles.input, value: value, onChange: e => onChange(e.target.value), placeholder: placeholder })] }));
|
|
170
222
|
}
|
|
171
223
|
export function BooleanInput({ label, value, onChange }) {
|
|
172
|
-
return (_jsxs("div", { children: [label && _jsx(Label, { children: label }), _jsx("input", { type: "checkbox", style: {
|
|
224
|
+
return (_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [label && _jsx(Label, { children: label }), _jsx("input", { type: "checkbox", style: {
|
|
173
225
|
height: 16,
|
|
174
226
|
width: 16,
|
|
175
227
|
backgroundColor: 'rgba(0, 0, 0, 0.4)',
|
|
@@ -178,7 +230,7 @@ export function BooleanInput({ label, value, onChange }) {
|
|
|
178
230
|
}, checked: value, onChange: e => onChange(e.target.checked) })] }));
|
|
179
231
|
}
|
|
180
232
|
export function SelectInput({ label, value, onChange, options }) {
|
|
181
|
-
return (_jsxs("div", { children: [label && _jsx(Label, { children: label }), _jsx("select", { style: styles.input, value: value, onChange: e => onChange(e.target.value), children: options.map(opt => (_jsx("option", { value: opt.value, children: opt.label }, opt.value))) })] }));
|
|
233
|
+
return (_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [label && _jsx(Label, { children: label }), _jsx("select", { style: styles.input, value: value, onChange: e => onChange(e.target.value), children: options.map(opt => (_jsx("option", { value: opt.value, children: opt.label }, opt.value))) })] }));
|
|
182
234
|
}
|
|
183
235
|
export function FieldRenderer({ fields, values, onChange }) {
|
|
184
236
|
const updateField = (name, value) => {
|
|
@@ -191,7 +243,7 @@ export function FieldRenderer({ fields, values, onChange }) {
|
|
|
191
243
|
case 'vector3':
|
|
192
244
|
return (_jsx(Vector3Input, { label: field.label, value: value !== null && value !== void 0 ? value : [0, 0, 0], onChange: v => updateField(field.name, v), snap: field.snap }, field.name));
|
|
193
245
|
case 'number':
|
|
194
|
-
return (
|
|
246
|
+
return (_jsx(Input, { label: field.label, value: value !== null && value !== void 0 ? value : 0, onChange: v => updateField(field.name, v), min: field.min, max: field.max, step: field.step }, field.name));
|
|
195
247
|
case 'string':
|
|
196
248
|
return (_jsx(StringInput, { label: field.label, value: value !== null && value !== void 0 ? value : '', onChange: v => updateField(field.name, v), placeholder: field.placeholder }, field.name));
|
|
197
249
|
case 'color':
|
|
@@ -25,7 +25,9 @@ function TexturePicker({ value, onChange, basePath }) {
|
|
|
25
25
|
.then(data => setTextureFiles(Array.isArray(data) ? data : data.files || []))
|
|
26
26
|
.catch(console.error);
|
|
27
27
|
}, [basePath]);
|
|
28
|
-
return (_jsxs("div", { style: { maxHeight: 128, overflowY: 'auto', position: 'relative', display: 'flex', alignItems: 'center' }, children: [_jsx(SingleTextureViewer, { file: 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 ? '
|
|
28
|
+
return (_jsxs("div", { style: { maxHeight: 128, overflowY: 'auto', position: 'relative', display: 'flex', alignItems: 'center' }, children: [_jsx(SingleTextureViewer, { file: 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: () => {
|
|
29
|
+
onChange(undefined);
|
|
30
|
+
}, 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', left: '-10px', top: '50%', transform: 'translate(-100%, -50%)', background: 'rgba(0,0,0,0.9)', padding: 16, border: '1px solid rgba(34, 211, 238, 0.3)', maxHeight: '80vh', overflowY: 'auto', zIndex: 1000 }, children: _jsx(TextureListViewer, { files: textureFiles, selected: value || undefined, onSelect: (file) => {
|
|
29
31
|
onChange(file);
|
|
30
32
|
setShowPicker(false);
|
|
31
33
|
}, basePath: basePath }) }))] }));
|
|
@@ -58,7 +60,7 @@ function MaterialComponentEditor({ component, onUpdate, basePath = "" }) {
|
|
|
58
60
|
label: 'Repeat (X, Y)',
|
|
59
61
|
render: ({ value, onChange }) => {
|
|
60
62
|
var _a, _b;
|
|
61
|
-
return (_jsxs("div", { style: { display: 'flex', gap: 2 }, children: [_jsx(Input, { value: (_a = value === null || value === void 0 ? void 0 : value[0]) !== null && _a !== void 0 ? _a : 1, onChange: v => { var _a; return onChange([v, (_a = value === null || value === void 0 ? void 0 : value[1]) !== null && _a !== void 0 ? _a : 1]); } }), _jsx(Input, { value: (_b = value === null || value === void 0 ? void 0 : value[1]) !== null && _b !== void 0 ? _b : 1, onChange: v => { var _a; return onChange([(_a = value === null || value === void 0 ? void 0 : value[0]) !== null && _a !== void 0 ? _a : 1, v]); } })] }));
|
|
63
|
+
return (_jsxs("div", { style: { display: 'flex', gap: 2 }, children: [_jsx(Input, { label: "X", value: (_a = value === null || value === void 0 ? void 0 : value[0]) !== null && _a !== void 0 ? _a : 1, onChange: v => { var _a; return onChange([v, (_a = value === null || value === void 0 ? void 0 : value[1]) !== null && _a !== void 0 ? _a : 1]); }, min: 0.01, max: 100, step: 0.1 }), _jsx(Input, { label: "Y", value: (_b = value === null || value === void 0 ? void 0 : value[1]) !== null && _b !== void 0 ? _b : 1, onChange: v => { var _a; return onChange([(_a = value === null || value === void 0 ? void 0 : value[0]) !== null && _a !== void 0 ? _a : 1, v]); }, min: 0.01, max: 100, step: 0.1 })] }));
|
|
62
64
|
},
|
|
63
65
|
}] : []),
|
|
64
66
|
{ name: 'generateMipmaps', type: 'boolean', label: 'Generate Mipmaps' },
|
|
@@ -17,7 +17,9 @@ function ModelPicker({ value, onChange, basePath, nodeId }) {
|
|
|
17
17
|
onChange(filename);
|
|
18
18
|
setShowPicker(false);
|
|
19
19
|
};
|
|
20
|
-
return (_jsxs("div", { style: { maxHeight: 128, overflowY: 'auto', 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 ? '
|
|
20
|
+
return (_jsxs("div", { style: { maxHeight: 128, overflowY: 'auto', 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: () => {
|
|
21
|
+
onChange(undefined);
|
|
22
|
+
}, 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', left: '-10px', top: '50%', transform: 'translate(-100%, -50%)', background: 'rgba(0,0,0,0.9)', padding: 16, border: '1px solid rgba(34, 211, 238, 0.3)', maxHeight: '80vh', overflowY: 'auto', zIndex: 1000 }, children: _jsx(ModelListViewer, { files: modelFiles, selected: value ? `/${value}` : undefined, onSelect: handleModelSelect, basePath: basePath }, nodeId) }))] }));
|
|
21
23
|
}
|
|
22
24
|
function ModelComponentEditor({ component, node, onUpdate, basePath = "" }) {
|
|
23
25
|
const fields = [
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { RigidBodyOptions } from "@react-three/rapier";
|
|
2
2
|
import { Component } from "./ComponentRegistry";
|
|
3
|
-
export type PhysicsProps = RigidBodyOptions
|
|
3
|
+
export type PhysicsProps = RigidBodyOptions & {
|
|
4
|
+
activeCollisionTypes?: 'all' | undefined;
|
|
5
|
+
};
|
|
4
6
|
declare const PhysicsComponent: Component;
|
|
5
7
|
export default PhysicsComponent;
|
|
@@ -10,7 +10,7 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
10
10
|
return t;
|
|
11
11
|
};
|
|
12
12
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
13
|
-
import { RigidBody } from "@react-three/rapier";
|
|
13
|
+
import { RigidBody, useRapier } from "@react-three/rapier";
|
|
14
14
|
import { useRef, useEffect, useCallback } from 'react';
|
|
15
15
|
import { FieldRenderer } from "./Input";
|
|
16
16
|
import { gameEvents, getEntityIdFromRigidBody } from "../GameEvents";
|
|
@@ -82,14 +82,32 @@ const physicsFields = [
|
|
|
82
82
|
type: 'boolean',
|
|
83
83
|
label: 'Sensor (Trigger Only)',
|
|
84
84
|
},
|
|
85
|
+
{
|
|
86
|
+
name: 'activeCollisionTypes',
|
|
87
|
+
type: 'select',
|
|
88
|
+
label: 'Collision Detection',
|
|
89
|
+
options: [
|
|
90
|
+
{ value: '', label: 'Default (Dynamic only)' },
|
|
91
|
+
{ value: 'all', label: 'All (includes kinematic & fixed)' },
|
|
92
|
+
],
|
|
93
|
+
},
|
|
85
94
|
];
|
|
86
95
|
function PhysicsComponentEditor({ component, onUpdate }) {
|
|
87
96
|
return (_jsx(FieldRenderer, { fields: physicsFields, values: component.properties, onChange: (props) => onUpdate(Object.assign(Object.assign({}, component), { properties: Object.assign(Object.assign({}, component.properties), props) })) }));
|
|
88
97
|
}
|
|
89
98
|
function PhysicsComponentView({ properties, children, position, rotation, scale, editMode, nodeId, registerRigidBodyRef }) {
|
|
90
|
-
const { type, colliders, sensor } = properties, otherProps = __rest(properties, ["type", "colliders", "sensor"]);
|
|
99
|
+
const { type, colliders, sensor, activeCollisionTypes } = properties, otherProps = __rest(properties, ["type", "colliders", "sensor", "activeCollisionTypes"]);
|
|
91
100
|
const colliderType = colliders || (type === 'fixed' ? 'trimesh' : 'hull');
|
|
92
101
|
const rigidBodyRef = useRef(null);
|
|
102
|
+
// Try to get rapier context - will be null if not inside <Physics>
|
|
103
|
+
let rapier = null;
|
|
104
|
+
try {
|
|
105
|
+
const rapierContext = useRapier();
|
|
106
|
+
rapier = rapierContext.rapier;
|
|
107
|
+
}
|
|
108
|
+
catch (e) {
|
|
109
|
+
// Not inside Physics context - that's ok, just won't have rapier features
|
|
110
|
+
}
|
|
93
111
|
// Register RigidBody ref when it's available
|
|
94
112
|
useEffect(() => {
|
|
95
113
|
if (nodeId && registerRigidBodyRef && rigidBodyRef.current) {
|
|
@@ -101,6 +119,19 @@ function PhysicsComponentView({ properties, children, position, rotation, scale,
|
|
|
101
119
|
}
|
|
102
120
|
};
|
|
103
121
|
}, [nodeId, registerRigidBodyRef]);
|
|
122
|
+
// Configure active collision types for kinematic/sensor bodies
|
|
123
|
+
useEffect(() => {
|
|
124
|
+
if (activeCollisionTypes === 'all' && rigidBodyRef.current && rapier) {
|
|
125
|
+
const rb = rigidBodyRef.current;
|
|
126
|
+
// Apply to all colliders on this rigid body
|
|
127
|
+
for (let i = 0; i < rb.numColliders(); i++) {
|
|
128
|
+
const collider = rb.collider(i);
|
|
129
|
+
collider.setActiveCollisionTypes(rapier.ActiveCollisionTypes.DEFAULT |
|
|
130
|
+
rapier.ActiveCollisionTypes.KINEMATIC_FIXED |
|
|
131
|
+
rapier.ActiveCollisionTypes.KINEMATIC_KINEMATIC);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}, [activeCollisionTypes, rapier, type, colliders]);
|
|
104
135
|
// Event handlers for physics interactions
|
|
105
136
|
const handleIntersectionEnter = useCallback((payload) => {
|
|
106
137
|
if (!nodeId)
|
|
@@ -1755,10 +1755,7 @@ export declare const tree: {
|
|
|
1755
1755
|
colorRendering?: import("csstype").Property.ColorRendering | undefined;
|
|
1756
1756
|
glyphOrientationVertical?: import("csstype").Property.GlyphOrientationVertical | undefined;
|
|
1757
1757
|
};
|
|
1758
|
-
scroll:
|
|
1759
|
-
overflowY: "auto";
|
|
1760
|
-
padding: number;
|
|
1761
|
-
};
|
|
1758
|
+
scroll: React.CSSProperties;
|
|
1762
1759
|
row: React.CSSProperties;
|
|
1763
1760
|
selected: {
|
|
1764
1761
|
background: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-three-game",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.53",
|
|
4
4
|
"description": "Batteries included React Three Fiber game engine",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -29,19 +29,19 @@
|
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@react-three/drei": "^10.7.7",
|
|
32
|
-
"@react-three/fiber": "^9.
|
|
32
|
+
"@react-three/fiber": "^9.5.0",
|
|
33
33
|
"@react-three/rapier": "^2.2.0",
|
|
34
|
-
"@types/react": "^19.2.
|
|
34
|
+
"@types/react": "^19.2.9",
|
|
35
35
|
"@types/react-dom": "^19.2.3",
|
|
36
|
-
"@types/three": "^0.
|
|
36
|
+
"@types/three": "^0.182.0",
|
|
37
37
|
"concurrently": "^9.2.1",
|
|
38
|
-
"react": "^19.2.
|
|
39
|
-
"react-dom": "^19.2.
|
|
38
|
+
"react": "^19.2.4",
|
|
39
|
+
"react-dom": "^19.2.4",
|
|
40
40
|
"three": "^0.182.0",
|
|
41
41
|
"typescript": "^5.9.3",
|
|
42
42
|
"vite": "^7.3.1"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"react-error-boundary": "^6.
|
|
45
|
+
"react-error-boundary": "^6.1.0"
|
|
46
46
|
}
|
|
47
47
|
}
|
|
@@ -22,7 +22,7 @@ Agents can programmatically generate 3D assets:
|
|
|
22
22
|
```tsx
|
|
23
23
|
import { useRef, useEffect } from 'react';
|
|
24
24
|
import { PrefabEditor, exportGLBData } from 'react-three-game';
|
|
25
|
-
import type { PrefabEditorRef } from 'react-three-game'
|
|
25
|
+
import type { PrefabEditorRef } from 'react-three-game'
|
|
26
26
|
|
|
27
27
|
const jsonPrefab = {
|
|
28
28
|
root: {
|
|
@@ -116,7 +116,7 @@ Scenes are defined as JSON prefabs with a root node containing children:
|
|
|
116
116
|
| Transform | `Transform` | `position: [x,y,z]`, `rotation: [x,y,z]` (radians), `scale: [x,y,z]` |
|
|
117
117
|
| Geometry | `Geometry` | `geometryType`: box/sphere/plane/cylinder, `args`: dimension array |
|
|
118
118
|
| Material | `Material` | `color`, `texture?`, `metalness?`, `roughness?`, `repeat?`, `repeatCount?` |
|
|
119
|
-
| Physics | `Physics` | `type`: dynamic/fixed/kinematicPosition/kinematicVelocity, `mass?`, `restitution?`, `friction?`, `linearDamping?`, `angularDamping?`, `gravityScale?`, plus any Rapier RigidBody props - [See advanced physics guide](./rules/ADVANCED_PHYSICS.md) |
|
|
119
|
+
| Physics | `Physics` | `type`: dynamic/fixed/kinematicPosition/kinematicVelocity, `mass?`, `restitution?`, `friction?`, `linearDamping?`, `angularDamping?`, `gravityScale?`, `sensor?`, `activeCollisionTypes?: 'all'` (enable kinematic/fixed collision detection), plus any Rapier RigidBody props - [See advanced physics guide](./rules/ADVANCED_PHYSICS.md) |
|
|
120
120
|
| Model | `Model` | `filename` (GLB/FBX path), `instanced?` for GPU batching |
|
|
121
121
|
| SpotLight | `SpotLight` | `color`, `intensity`, `angle`, `penumbra`, `distance?`, `castShadow?` |
|
|
122
122
|
| DirectionalLight | `DirectionalLight` | `color`, `intensity`, `castShadow?`, `targetOffset?: [x,y,z]` |
|
|
@@ -414,6 +414,8 @@ Physics components automatically emit these events:
|
|
|
414
414
|
| `collision:enter` | A collision starts | `{ sourceEntityId, targetEntityId, targetRigidBody }` |
|
|
415
415
|
| `collision:exit` | A collision ends | `{ sourceEntityId, targetEntityId, targetRigidBody }` |
|
|
416
416
|
|
|
417
|
+
**Collision filtering**: By default, kinematic/fixed bodies don't detect each other. For kinematic sensors or projectiles to detect walls/floors, add `"activeCollisionTypes": "all"` to the Physics properties.
|
|
418
|
+
|
|
417
419
|
See [Advanced Physics](./rules/ADVANCED_PHYSICS.md) for sensor setup and collision handling patterns.
|
|
418
420
|
|
|
419
421
|
### TypeScript: Typed Custom Events
|