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.
Files changed (30) hide show
  1. package/dist/helpers/index.js +1 -1
  2. package/dist/tools/prefabeditor/EditorTree.js +48 -7
  3. package/dist/tools/prefabeditor/EditorUI.js +2 -2
  4. package/dist/tools/prefabeditor/PrefabEditor.d.ts +1 -0
  5. package/dist/tools/prefabeditor/PrefabEditor.js +4 -3
  6. package/dist/tools/prefabeditor/PrefabRoot.d.ts +2 -3
  7. package/dist/tools/prefabeditor/PrefabRoot.js +9 -3
  8. package/dist/tools/prefabeditor/components/GeometryComponent.js +8 -8
  9. package/dist/tools/prefabeditor/components/Input.d.ts +2 -1
  10. package/dist/tools/prefabeditor/components/Input.js +63 -11
  11. package/dist/tools/prefabeditor/components/MaterialComponent.js +4 -2
  12. package/dist/tools/prefabeditor/components/ModelComponent.js +3 -1
  13. package/dist/tools/prefabeditor/components/PhysicsComponent.d.ts +3 -1
  14. package/dist/tools/prefabeditor/components/PhysicsComponent.js +33 -2
  15. package/dist/tools/prefabeditor/styles.d.ts +1 -4
  16. package/dist/tools/prefabeditor/styles.js +2 -0
  17. package/package.json +7 -7
  18. package/react-three-game-skill/react-three-game/SKILL.md +4 -2
  19. package/react-three-game-skill/react-three-game/rules/ADVANCED_PHYSICS.md +17 -1
  20. package/src/helpers/index.ts +1 -1
  21. package/src/tools/prefabeditor/EditorTree.tsx +92 -17
  22. package/src/tools/prefabeditor/EditorUI.tsx +2 -2
  23. package/src/tools/prefabeditor/PrefabEditor.tsx +24 -15
  24. package/src/tools/prefabeditor/PrefabRoot.tsx +14 -6
  25. package/src/tools/prefabeditor/components/GeometryComponent.tsx +13 -13
  26. package/src/tools/prefabeditor/components/Input.tsx +110 -20
  27. package/src/tools/prefabeditor/components/MaterialComponent.tsx +22 -6
  28. package/src/tools/prefabeditor/components/ModelComponent.tsx +9 -1
  29. package/src/tools/prefabeditor/components/PhysicsComponent.tsx +39 -3
  30. package/src/tools/prefabeditor/styles.ts +3 -1
@@ -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 = "white", texture, repeat = texture ? true : false, repeatCount = [25, 25], physicsType = "fixed", disabled = false, } = options;
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` }), 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: [_jsx("span", { style: {
117
- width: 12,
118
- opacity: 0.6,
119
- marginRight: 4,
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
- visibility: hasChildren ? 'visible' : 'hidden'
122
- }, onClick: (e) => hasChildren && toggleCollapse(e, node.id), children: isCollapsed ? '▶' : '▼' }), !isRoot && _jsx("span", { style: { marginRight: 4, opacity: 0.4 }, children: "\u22EE\u22EE" }), _jsx("span", { style: { overflow: 'hidden', textOverflow: 'ellipsis' }, children: (_a = node.name) !== null && _a !== void 0 ? _a : node.id })] }), !isCollapsed && node.children && node.children.map(child => renderNode(child, depth + 1))] }, node.id));
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: [_jsxs("div", { style: Object.assign(Object.assign({}, tree.panel), { width: collapsed ? 'auto' : 224 }), onClick: () => { setContextMenu(null); setFileMenuOpen(false); }, children: [_jsxs("div", { style: base.header, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 6, cursor: 'pointer' }, onClick: () => setCollapsed(!collapsed), children: [_jsx("span", { children: collapsed ? '▶' : '▼' }), _jsx("span", { children: "Scene" })] }), !collapsed && (_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 4 }, children: [_jsx("button", { style: Object.assign(Object.assign({}, base.btn), { padding: '2px 6px', fontSize: 10, opacity: canUndo ? 1 : 0.4 }), onClick: (e) => { e.stopPropagation(); onUndo === null || onUndo === void 0 ? void 0 : onUndo(); }, disabled: !canUndo, title: "Undo", children: "\u21B6" }), _jsx("button", { style: Object.assign(Object.assign({}, base.btn), { padding: '2px 6px', fontSize: 10, opacity: canRedo ? 1 : 0.4 }), onClick: (e) => { e.stopPropagation(); onRedo === null || onRedo === void 0 ? void 0 : onRedo(); }, disabled: !canRedo, title: "Redo", children: "\u21B7" }), _jsxs("div", { style: { position: 'relative' }, children: [_jsx("button", { style: Object.assign(Object.assign({}, base.btn), { padding: '2px 6px', fontSize: 10 }), onClick: (e) => { e.stopPropagation(); setFileMenuOpen(!fileMenuOpen); }, title: "File", children: "\u22EE" }), fileMenuOpen && (_jsx(FileMenu, { prefabData: prefabData, setPrefabData: setPrefabData, onClose: () => setFileMenuOpen(false) }))] })] }))] }), !collapsed && _jsx("div", { style: tree.scroll, children: renderNode(prefabData.root) })] }), contextMenu && (_jsxs("div", { style: Object.assign(Object.assign({}, menu.container), { top: contextMenu.y, left: contextMenu.x }), onClick: (e) => e.stopPropagation(), onPointerLeave: () => setContextMenu(null), children: [_jsx("button", { style: menu.item, onClick: () => handleAddChild(contextMenu.nodeId), children: "Add Child" }), contextMenu.nodeId !== prefabData.root.id && (_jsxs(_Fragment, { children: [_jsx("button", { style: menu.item, onClick: () => handleDuplicate(contextMenu.nodeId), children: "Duplicate" }), _jsx("button", { style: Object.assign(Object.assign({}, menu.item), menu.danger), onClick: () => handleDelete(contextMenu.nodeId), children: "Delete" })] }))] }))] }));
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: _jsxs(Physics, { debug: editMode, paused: editMode, children: [_jsx("ambientLight", { intensity: 1.5 }), _jsx("gridHelper", { args: [10, 10], position: [0, -1, 0] }), _jsx(PrefabRoot, { ref: prefabRootRef, data: loadedPrefab, editMode: editMode, onPrefabChange: updatePrefab, selectedId: selectedId, onSelect: setSelectedId, basePath: basePath }), children] }) }), _jsx("div", { style: toolbar.panel, children: _jsx("button", { style: base.btn, onClick: () => setEditMode(!editMode), children: editMode ? "▶" : "⏸" }) }), editMode && _jsx(EditorUI, { prefabData: loadedPrefab, setPrefabData: updatePrefab, selectedId: selectedId, setSelectedId: setSelectedId, basePath: basePath, onUndo: undo, onRedo: redo, canUndo: historyIndex > 0, canRedo: historyIndex < history.length - 1 })] });
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, RapierRigidBody | null>;
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: RapierRigidBody | null) => void;
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
- const path = file.startsWith("/")
103
- ? `${basePath}${file}`
104
- : `${basePath}/${file}`;
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, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { FieldRenderer, Input, Label } from "./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: 8 }, children: currentSchema.labels.map((label, i) => {
44
+ return (_jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: 4 }, children: currentSchema.labels.map((label, i) => {
45
45
  var _a;
46
- return (_jsxs("div", { children: [_jsx(Label, { children: label }), _jsx(Input, { value: (_a = currentArgs[i]) !== null && _a !== void 0 ? _a : currentSchema.defaults[i], step: 0.1, onChange: value => {
47
- const next = [...currentArgs];
48
- next[i] = value;
49
- onChangeMultiple({ args: next });
50
- } })] }, label));
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: '100%',
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.6)',
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: 2 }, children: [_jsx("input", { type: "color", style: {
160
- height: 20,
161
- width: 20,
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: 'none',
213
+ border: '1px solid rgba(34, 211, 238, 0.3)',
214
+ borderRadius: 4,
164
215
  cursor: 'pointer',
165
216
  padding: 0,
166
- }, value: value, onChange: e => onChange(e.target.value) }), _jsx("input", { type: "text", style: Object.assign(Object.assign({}, styles.input), { flex: 1 }), value: value, onChange: e => onChange(e.target.value) })] })] }));
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 (_jsxs("div", { children: [_jsx(Label, { children: field.label }), _jsx(Input, { 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));
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 ? 'Hide' : 'Change' }), 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) => {
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 ? 'Hide' : 'Change' }), 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) }))] }));
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;
@@ -99,6 +99,8 @@ export const tree = {
99
99
  scroll: {
100
100
  overflowY: 'auto',
101
101
  padding: 4,
102
+ scrollbarWidth: 'thin',
103
+ scrollbarColor: 'rgba(255,255,255,0.06) transparent',
102
104
  },
103
105
  row: {
104
106
  display: 'flex',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-three-game",
3
- "version": "0.0.51",
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.4.2",
32
+ "@react-three/fiber": "^9.5.0",
33
33
  "@react-three/rapier": "^2.2.0",
34
- "@types/react": "^19.2.6",
34
+ "@types/react": "^19.2.9",
35
35
  "@types/react-dom": "^19.2.3",
36
- "@types/three": "^0.181.0",
36
+ "@types/three": "^0.182.0",
37
37
  "concurrently": "^9.2.1",
38
- "react": "^19.2.0",
39
- "react-dom": "^19.2.0",
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.0.0"
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