react-three-game 0.0.91 → 0.0.92

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.
@@ -12,6 +12,34 @@ const styles = {
12
12
  textLight: { color: '#f9fafb' },
13
13
  iconLarge: { fontSize: 20 }
14
14
  };
15
+ const assetViewerColors = {
16
+ panelBg: '#111827',
17
+ controlBg: '#1f2937',
18
+ text: '#f9fafb',
19
+ border: 'rgba(255,255,255,0.12)',
20
+ accentBorder: 'rgba(34, 211, 238, 0.3)',
21
+ };
22
+ const assetPickerPopupBaseStyle = {
23
+ background: assetViewerColors.panelBg,
24
+ border: `1px solid ${assetViewerColors.border}`,
25
+ borderRadius: 0,
26
+ boxShadow: '0 4px 16px rgba(0,0,0,0.6)',
27
+ };
28
+ const assetPickerButtonBaseStyle = {
29
+ backgroundColor: assetViewerColors.controlBg,
30
+ color: 'inherit',
31
+ fontSize: 10,
32
+ cursor: 'pointer',
33
+ border: `1px solid ${assetViewerColors.border}`,
34
+ borderRadius: 0,
35
+ };
36
+ const assetPickerWideButtonStyle = Object.assign(Object.assign({}, assetPickerButtonBaseStyle), { width: '100%', padding: '6px 8px' });
37
+ const assetPickerSmallButtonStyle = Object.assign(Object.assign({}, assetPickerButtonBaseStyle), { padding: '4px 8px' });
38
+ const assetPickerEmptyPreviewStyle = {
39
+ backgroundColor: assetViewerColors.controlBg,
40
+ border: `1px dashed ${assetViewerColors.border}`,
41
+ borderRadius: 0,
42
+ };
15
43
  function getItemsInPath(files, currentPath) {
16
44
  // Remove the leading category folder (e.g., /textures/, /models/, /sounds/)
17
45
  const filesWithoutCategory = files.map(file => {
@@ -39,8 +67,8 @@ function FolderTile({ name, onClick }) {
39
67
  return (_jsxs("div", { onClick: onClick, style: {
40
68
  maxWidth: 60,
41
69
  aspectRatio: '1 / 1',
42
- backgroundColor: '#1f2937', /* gray-800 */
43
- color: '#f9fafb',
70
+ backgroundColor: assetViewerColors.controlBg,
71
+ color: assetViewerColors.text,
44
72
  cursor: 'pointer',
45
73
  display: 'flex',
46
74
  flexDirection: 'column',
@@ -73,7 +101,7 @@ function AssetListViewer({ files, selected, onSelect, renderCard }) {
73
101
  const pathParts = currentPath.split('/').filter(Boolean);
74
102
  pathParts.pop();
75
103
  setCurrentPath(pathParts.join('/'));
76
- }, style: { marginBottom: 4, padding: '4px 8px', backgroundColor: '#1f2937', color: 'inherit', fontSize: 12, cursor: 'pointer', border: 'none' }, children: "\u2190 Back" })), _jsxs("div", { style: { display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 4 }, children: [folders.map((folder) => (_jsx(FolderTile, { name: folder, onClick: () => setCurrentPath(currentPath ? `${currentPath}/${folder}` : folder) }, folder))), filesInCurrentPath.map((file) => (_jsx("div", { children: renderCard(file, onSelect) }, file)))] })] }));
104
+ }, style: Object.assign(Object.assign({}, assetPickerSmallButtonStyle), { marginBottom: 4, fontSize: 12, border: 'none' }), children: "\u2190 Back" })), _jsxs("div", { style: { display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 4 }, children: [folders.map((folder) => (_jsx(FolderTile, { name: folder, onClick: () => setCurrentPath(currentPath ? `${currentPath}/${folder}` : folder) }, folder))), filesInCurrentPath.map((file) => (_jsx("div", { children: renderCard(file, onSelect) }, file)))] })] }));
77
105
  }
78
106
  export function TextureListViewer({ files, selected, onSelect, basePath = "" }) {
79
107
  return (_jsxs("div", { style: { position: 'relative', width: '100%', height: '100%' }, children: [_jsx("div", { style: { width: '100%', height: '100%', overflowY: 'auto', overflowX: 'hidden', paddingRight: 4 }, children: _jsx(AssetListViewer, { files: files, selected: selected, onSelect: onSelect, renderCard: (file, onSelectHandler) => (_jsx(TextureCard, { file: file, basePath: basePath, onSelect: onSelectHandler })) }) }), _jsx(SharedCanvas, {})] }));
@@ -172,8 +200,8 @@ function AssetPicker({ value, onChange, basePath, manifestFolder, preview, rende
172
200
  const fitsLeft = preferredLeft >= 8;
173
201
  const left = fitsLeft ? preferredLeft : Math.min(fallbackLeft, window.innerWidth - PICKER_POPUP_WIDTH - 8);
174
202
  const top = Math.min(Math.max(8, rect.top), window.innerHeight - PICKER_POPUP_HEIGHT - 8);
175
- setResolvedPopupStyle(Object.assign({ position: 'fixed', left,
176
- top, padding: 12, width: PICKER_POPUP_WIDTH, height: PICKER_POPUP_HEIGHT, overflow: 'hidden', zIndex: 1000, boxShadow: '0 4px 16px rgba(0,0,0,0.6)', background: '#111827', border: '1px solid rgba(255,255,255,0.12)', borderRadius: 6 }, popupStyle));
203
+ setResolvedPopupStyle(Object.assign(Object.assign({ position: 'fixed', left,
204
+ top, padding: 12, width: PICKER_POPUP_WIDTH, height: PICKER_POPUP_HEIGHT, overflow: 'hidden', zIndex: 1000 }, assetPickerPopupBaseStyle), popupStyle));
177
205
  };
178
206
  updatePosition();
179
207
  window.addEventListener('resize', updatePosition);
@@ -194,23 +222,23 @@ function AssetPicker({ value, onChange, basePath, manifestFolder, preview, rende
194
222
  }) }), document.body)] }));
195
223
  }
196
224
  export function TexturePicker({ value, onChange, basePath = "" }) {
197
- return (_jsx(AssetPicker, { value: value, onChange: onChange, basePath: basePath, manifestFolder: "textures", rootStyle: { maxHeight: 128, overflow: 'visible', position: 'relative', display: 'flex', alignItems: 'center' }, changeButtonStyle: { padding: '4px 8px', backgroundColor: '#1f2937', color: 'inherit', fontSize: 10, cursor: 'pointer', border: '1px solid rgba(255,255,255,0.12)', borderRadius: 3, marginTop: 4 }, clearButtonStyle: { padding: '4px 8px', backgroundColor: '#1f2937', color: 'inherit', fontSize: 10, cursor: 'pointer', border: '1px solid rgba(255,255,255,0.12)', borderRadius: 3, marginTop: 4, marginLeft: 4 }, preview: _jsx(SingleTextureViewer, { file: value, basePath: basePath }), renderList: ({ files, value: selectedValue, onSelect, basePath: currentBasePath }) => (_jsx(TextureListViewer, { files: files, selected: selectedValue || undefined, onSelect: onSelect, basePath: currentBasePath })) }));
225
+ return (_jsx(AssetPicker, { value: value, onChange: onChange, basePath: basePath, manifestFolder: "textures", rootStyle: { maxHeight: 128, overflow: 'visible', position: 'relative', display: 'flex', alignItems: 'center' }, changeButtonStyle: Object.assign(Object.assign({}, assetPickerSmallButtonStyle), { marginTop: 4 }), clearButtonStyle: Object.assign(Object.assign({}, assetPickerSmallButtonStyle), { marginTop: 4, marginLeft: 4 }), preview: _jsx(SingleTextureViewer, { file: value, basePath: basePath }), renderList: ({ files, value: selectedValue, onSelect, basePath: currentBasePath }) => (_jsx(TextureListViewer, { files: files, selected: selectedValue || undefined, onSelect: onSelect, basePath: currentBasePath })) }));
198
226
  }
199
227
  export function ModelPicker({ value, onChange, basePath = "", pickerKey }) {
200
- return (_jsx(AssetPicker, { value: value, onChange: onChange, basePath: basePath, manifestFolder: "models", rootStyle: { maxHeight: 160, overflow: 'visible', position: 'relative', display: 'flex', gap: 8, alignItems: 'center', justifyContent: 'center' }, controlsStyle: { display: 'flex', flexDirection: 'column', gap: 6, flex: '0 0 84px', minWidth: 84, justifyContent: 'flex-end' }, changeButtonStyle: { width: '100%', padding: '6px 8px', backgroundColor: '#1f2937', color: 'inherit', fontSize: 10, cursor: 'pointer', border: '1px solid rgba(34, 211, 238, 0.3)' }, clearButtonStyle: { width: '100%', padding: '6px 8px', backgroundColor: '#1f2937', color: 'inherit', fontSize: 10, cursor: 'pointer', border: '1px solid rgba(34, 211, 238, 0.3)' }, popupStyle: { background: 'rgba(0,0,0,0.9)', border: '1px solid rgba(34, 211, 238, 0.3)' }, preview: _jsx("div", { style: { flex: '0 0 auto' }, children: _jsx(SingleModelViewer, { file: value ? `/${value}` : undefined, basePath: basePath }) }), renderList: ({ files, value: selectedValue, onSelect, basePath: currentBasePath }) => (_jsx(ModelListViewer, { files: files, selected: selectedValue ? `/${selectedValue}` : undefined, onSelect: (file) => onSelect(file.startsWith('/') ? file.slice(1) : file), basePath: currentBasePath }, pickerKey)) }));
228
+ return (_jsx(AssetPicker, { value: value, onChange: onChange, basePath: basePath, manifestFolder: "models", rootStyle: { maxHeight: 160, overflow: 'visible', position: 'relative', display: 'flex', gap: 8, alignItems: 'center', justifyContent: 'center' }, controlsStyle: { display: 'flex', flexDirection: 'column', gap: 6, flex: '0 0 84px', minWidth: 84, justifyContent: 'flex-end' }, changeButtonStyle: Object.assign(Object.assign({}, assetPickerWideButtonStyle), { border: `1px solid ${assetViewerColors.accentBorder}` }), clearButtonStyle: Object.assign(Object.assign({}, assetPickerWideButtonStyle), { border: `1px solid ${assetViewerColors.accentBorder}` }), popupStyle: { background: 'rgba(0,0,0,0.9)', border: `1px solid ${assetViewerColors.accentBorder}` }, preview: _jsx("div", { style: { flex: '0 0 auto' }, children: _jsx(SingleModelViewer, { file: value ? `/${value}` : undefined, basePath: basePath }) }), renderList: ({ files, value: selectedValue, onSelect, basePath: currentBasePath }) => (_jsx(ModelListViewer, { files: files, selected: selectedValue ? `/${selectedValue}` : undefined, onSelect: (file) => onSelect(file.startsWith('/') ? file.slice(1) : file), basePath: currentBasePath }, pickerKey)) }));
201
229
  }
202
230
  export function SoundPicker({ value, onChange, basePath = "" }) {
203
- return (_jsx(AssetPicker, { value: value, onChange: onChange, basePath: basePath, manifestFolder: "sound", rootStyle: { maxHeight: 76, overflow: 'visible', position: 'relative', display: 'flex', gap: 8, alignItems: 'center', justifyContent: 'center' }, controlsStyle: { display: 'flex', flexDirection: 'column', gap: 6, flex: '0 0 84px', minWidth: 84, justifyContent: 'flex-end' }, changeButtonStyle: { width: '100%', padding: '6px 8px', backgroundColor: '#1f2937', color: 'inherit', fontSize: 10, cursor: 'pointer', border: '1px solid rgba(255,255,255,0.12)', borderRadius: 3 }, clearButtonStyle: { width: '100%', padding: '6px 8px', backgroundColor: '#1f2937', color: 'inherit', fontSize: 10, cursor: 'pointer', border: '1px solid rgba(255,255,255,0.12)', borderRadius: 3 }, preview: _jsx("div", { style: { flex: '0 0 auto', minWidth: 84 }, children: value ? _jsx(SingleSoundViewer, { file: value, basePath: basePath }) : _jsx("div", { style: { width: 84, height: 60, backgroundColor: '#1f2937', border: '1px dashed rgba(255,255,255,0.12)', borderRadius: 4 } }) }), renderList: ({ files, value: selectedValue, onSelect, basePath: currentBasePath }) => (_jsx(SoundListViewer, { files: files, selected: selectedValue || undefined, onSelect: onSelect, basePath: currentBasePath })) }));
231
+ return (_jsx(AssetPicker, { value: value, onChange: onChange, basePath: basePath, manifestFolder: "sound", rootStyle: { maxHeight: 76, overflow: 'visible', position: 'relative', display: 'flex', gap: 8, alignItems: 'center', justifyContent: 'center' }, controlsStyle: { display: 'flex', flexDirection: 'column', gap: 6, flex: '0 0 84px', minWidth: 84, justifyContent: 'flex-end' }, changeButtonStyle: assetPickerWideButtonStyle, clearButtonStyle: assetPickerWideButtonStyle, preview: _jsx("div", { style: { flex: '0 0 auto', minWidth: 84 }, children: value ? _jsx(SingleSoundViewer, { file: value, basePath: basePath }) : _jsx("div", { style: Object.assign({ width: 84, height: 60 }, assetPickerEmptyPreviewStyle) }) }), renderList: ({ files, value: selectedValue, onSelect, basePath: currentBasePath }) => (_jsx(SoundListViewer, { files: files, selected: selectedValue || undefined, onSelect: onSelect, basePath: currentBasePath })) }));
204
232
  }
205
233
  // Single Asset Viewer Components - display only one selected asset
206
234
  export function SingleTextureViewer({ file, basePath = "" }) {
207
235
  if (!file)
208
- return _jsx("div", { style: { width: 60, aspectRatio: '1 / 1', backgroundColor: '#1f2937', border: '1px dashed rgba(255,255,255,0.12)' } });
236
+ return _jsx("div", { style: Object.assign({ width: 60, aspectRatio: '1 / 1' }, assetPickerEmptyPreviewStyle) });
209
237
  return (_jsxs(_Fragment, { children: [_jsx(TextureCard, { file: file, basePath: basePath, onSelect: () => { } }), _jsx(SharedCanvas, {})] }));
210
238
  }
211
239
  export function SingleModelViewer({ file, basePath = "" }) {
212
240
  if (!file)
213
- return _jsx("div", { style: { width: 112, aspectRatio: '1 / 1', backgroundColor: '#1f2937', border: '1px dashed rgba(255,255,255,0.12)' } });
241
+ return _jsx("div", { style: Object.assign({ width: 112, aspectRatio: '1 / 1' }, assetPickerEmptyPreviewStyle) });
214
242
  return (_jsxs(_Fragment, { children: [_jsx(ModelCard, { file: file, basePath: basePath, onSelect: () => { }, size: 112 }), _jsx(SharedCanvas, {})] }));
215
243
  }
216
244
  export function SingleSoundViewer({ file, basePath = "" }) {
@@ -8,9 +8,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
11
- import { MapControls, TransformControls } from "@react-three/drei";
11
+ import { MapControls, TransformControls, useHelper } from "@react-three/drei";
12
12
  import GameCanvas from "../../shared/GameCanvas";
13
- import { useCallback, useEffect, useReducer, useRef, useState, forwardRef, useImperativeHandle, createContext, useContext } from "react";
13
+ import { useCallback, useEffect, useLayoutEffect, useRef, useState, forwardRef, useImperativeHandle, createContext, useContext } from "react";
14
+ import { BoxHelper } from "three";
14
15
  import { findComponentEntry } from "./types";
15
16
  import PrefabRoot from "./PrefabRoot";
16
17
  import { Physics } from "@react-three/rapier";
@@ -31,6 +32,12 @@ function isObjectAttachedToRoot(root, object) {
31
32
  }
32
33
  return false;
33
34
  }
35
+ function SelectionHelper({ object }) {
36
+ const objectRef = useRef(null);
37
+ objectRef.current = object;
38
+ useHelper(object ? objectRef : null, BoxHelper, "cyan");
39
+ return null;
40
+ }
34
41
  export var PrefabEditorMode;
35
42
  (function (PrefabEditorMode) {
36
43
  PrefabEditorMode["Edit"] = "edit";
@@ -52,6 +59,7 @@ const DEFAULT_PREFAB = {
52
59
  root: createNode('Root', {}, { id: 'root' })
53
60
  };
54
61
  const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, mode: initialMode = PrefabEditorMode.Edit, onChange, showUI = true, enableWindowDrop = true, canvasProps, uiPlugins, children }, ref) => {
62
+ var _a, _b;
55
63
  const [mode, setMode] = useState(initialMode);
56
64
  const [selectedId, setSelectedId] = useState(null);
57
65
  const [transformMode, setTransformMode] = useState("translate");
@@ -64,21 +72,17 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, mode
64
72
  const [historyIndex, setHistoryIndex] = useState(0);
65
73
  const changeOriginRef = useRef(null);
66
74
  const historyIndexRef = useRef(0);
67
- const [, bumpSelectedObjectVersion] = useReducer((value) => value + 1, 0);
68
75
  const prefabRootRef = useRef(null);
69
76
  const canvasRef = useRef(null);
70
77
  const controlsRef = useRef(null);
78
+ const transformControlsRef = useRef(null);
79
+ const transformProxyRef = useRef(null);
71
80
  const onChangeRef = useRef(onChange);
72
81
  const isEditMode = mode === PrefabEditorMode.Edit;
73
82
  const getPrefab = useCallback(() => denormalizePrefab(prefabStore.getState()), [prefabStore]);
74
83
  const getRootObject = useCallback(() => { var _a, _b; return (_b = (_a = prefabRootRef.current) === null || _a === void 0 ? void 0 : _a.root) !== null && _b !== void 0 ? _b : null; }, []);
75
84
  const getObject = useCallback((nodeId) => { var _a, _b; return (_b = (_a = prefabRootRef.current) === null || _a === void 0 ? void 0 : _a.getObject(nodeId)) !== null && _b !== void 0 ? _b : null; }, []);
76
85
  const getRigidBody = useCallback((nodeId) => { var _a, _b; return (_b = (_a = prefabRootRef.current) === null || _a === void 0 ? void 0 : _a.getRigidBody(nodeId)) !== null && _b !== void 0 ? _b : null; }, []);
77
- const handleObjectRefChange = useCallback((nodeId) => {
78
- if (nodeId !== selectedId)
79
- return;
80
- bumpSelectedObjectVersion();
81
- }, [selectedId]);
82
86
  onChangeRef.current = onChange;
83
87
  const setSelection = useCallback((nodeId) => {
84
88
  const nextNode = nodeId ? prefabStore.getState().nodesById[nodeId] : null;
@@ -104,7 +108,9 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, mode
104
108
  updateMode(initialMode);
105
109
  }, [initialMode, updateMode]);
106
110
  const loadPrefab = useCallback((prefab, options) => {
111
+ var _a;
107
112
  changeOriginRef.current = (options === null || options === void 0 ? void 0 : options.notifyChange) === false ? "replace-silent" : "replace";
113
+ (_a = transformControlsRef.current) === null || _a === void 0 ? void 0 : _a.detach();
108
114
  prefabStore.getState().replacePrefab(prefab);
109
115
  if (options === null || options === void 0 ? void 0 : options.resetHistory) {
110
116
  setSelectedId(null);
@@ -171,10 +177,23 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, mode
171
177
  });
172
178
  return () => unsubscribe();
173
179
  }, [prefabStore, selectedId]);
180
+ const selectedNode = selectedId ? (_a = prefabStore.getState().nodesById[selectedId]) !== null && _a !== void 0 ? _a : null : null;
174
181
  const selectedObject = selectedId ? getObject(selectedId) : null;
175
- const transformObject = isObjectAttachedToRoot(getRootObject(), selectedObject)
176
- ? selectedObject
182
+ const selectedHasPhysics = Object.values((_b = selectedNode === null || selectedNode === void 0 ? void 0 : selectedNode.components) !== null && _b !== void 0 ? _b : {}).some(component => (component === null || component === void 0 ? void 0 : component.type) === "Physics");
183
+ const transformObject = isEditMode && (selectedHasPhysics ? transformProxyRef.current : selectedObject)
184
+ && isObjectAttachedToRoot(getRootObject(), selectedObject)
185
+ ? (selectedHasPhysics ? transformProxyRef.current : selectedObject)
177
186
  : null;
187
+ useLayoutEffect(() => {
188
+ if (!isEditMode || !selectedHasPhysics || !selectedObject || !transformProxyRef.current) {
189
+ return;
190
+ }
191
+ selectedObject.updateMatrixWorld(true);
192
+ transformProxyRef.current.matrixAutoUpdate = true;
193
+ selectedObject.matrixWorld.decompose(transformProxyRef.current.position, transformProxyRef.current.quaternion, transformProxyRef.current.scale);
194
+ transformProxyRef.current.updateMatrix();
195
+ transformProxyRef.current.updateMatrixWorld(true);
196
+ }, [isEditMode, selectedHasPhysics, selectedId, selectedObject]);
178
197
  const addNode = useCallback((node, options) => {
179
198
  var _a;
180
199
  const { addChild, rootId } = prefabStore.getState();
@@ -202,7 +221,9 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, mode
202
221
  return node;
203
222
  }, [addNode]);
204
223
  const applyHistory = (index) => {
224
+ var _a;
205
225
  changeOriginRef.current = "history";
226
+ (_a = transformControlsRef.current) === null || _a === void 0 ? void 0 : _a.detach();
206
227
  prefabStore.getState().replacePrefab(history[index]);
207
228
  historyIndexRef.current = index;
208
229
  setHistoryIndex(index);
@@ -274,7 +295,7 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, mode
274
295
  const handleTransformChange = () => {
275
296
  if (!selectedId)
276
297
  return;
277
- const object = getObject(selectedId);
298
+ const object = selectedHasPhysics ? transformProxyRef.current : getObject(selectedId);
278
299
  if (!object)
279
300
  return;
280
301
  const parentWorld = computeParentWorldMatrix(prefabStore.getState(), selectedId);
@@ -341,7 +362,7 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, mode
341
362
  addModel,
342
363
  addTexture
343
364
  }), [addModel, addNode, addTexture, clearSelection, getObject, getPrefab, getRigidBody, getRootObject, handleExportGLB, handleExportGLBData, handleScreenshot, loadPrefab, prefabStore]);
344
- const content = (_jsxs(_Fragment, { children: [isEditMode ? _jsx("gridHelper", { args: [10, 10], position: [0, -1, 0] }) : null, _jsx(PrefabRoot, { ref: prefabRootRef, store: prefabStore, editMode: isEditMode, selectedId: selectedId, onSelect: setSelection, onObjectRefChange: handleObjectRefChange, basePath: basePath }), children] }));
365
+ const content = (_jsxs(_Fragment, { children: [isEditMode ? _jsx("gridHelper", { args: [10, 10], position: [0, -1, 0] }) : null, _jsx(PrefabRoot, { ref: prefabRootRef, store: prefabStore, editMode: isEditMode, selectedId: selectedId, onSelect: setSelection, basePath: basePath }), children] }));
345
366
  const handleCanvasCreated = useCallback((state) => {
346
367
  var _a;
347
368
  canvasRef.current = state.gl.domElement;
@@ -370,7 +391,7 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, mode
370
391
  }
371
392
  (_d = canvasProps === null || canvasProps === void 0 ? void 0 : canvasProps.onPointerMissed) === null || _d === void 0 ? void 0 : _d.call(canvasProps, event);
372
393
  }
373
- : canvasProps === null || canvasProps === void 0 ? void 0 : canvasProps.onPointerMissed, children: [physics ? (_jsx(Physics, { colliders: false, debug: isEditMode, paused: isEditMode, children: content })) : content, isEditMode && (_jsxs(_Fragment, { children: [_jsx(MapControls, { ref: controlsRef, enableDamping: false, makeDefault: true }), transformObject && (_jsx(TransformControls, { object: transformObject, mode: transformMode, space: "local", onObjectChange: handleTransformChange, translationSnap: positionSnap > 0 ? positionSnap : undefined, rotationSnap: rotationSnap > 0 ? rotationSnap : undefined, scaleSnap: scaleSnap > 0 ? scaleSnap : undefined }, `transform-${selectedId}-${transformMode}-${positionSnap}-${rotationSnap}-${scaleSnap}`))] }))] })), showUI && (_jsxs(_Fragment, { children: [_jsxs("div", { style: toolbar.panel, children: [_jsx("button", { style: base.btn, onClick: toggleMode, children: isEditMode ? "▶" : "⏸" }), uiPlugins] }), isEditMode && (_jsx(EditorUI, { selectedId: selectedId, setSelectedId: setSelection, getPrefab: getPrefab, onReplacePrefab: (prefab) => loadPrefab(prefab, { resetHistory: true }), onImportPrefab: importPrefab, basePath: basePath, onUndo: undo, onRedo: redo, canUndo: historyIndex > 0, canRedo: historyIndex < history.length - 1 }))] }))] }) });
394
+ : canvasProps === null || canvasProps === void 0 ? void 0 : canvasProps.onPointerMissed, children: [physics ? (_jsx(Physics, { colliders: false, debug: isEditMode, paused: isEditMode, children: content })) : content, _jsx("group", { ref: transformProxyRef, visible: false }), isEditMode ? _jsx(SelectionHelper, { object: transformObject }) : null, isEditMode && (_jsxs(_Fragment, { children: [_jsx(MapControls, { ref: controlsRef, enableDamping: false, makeDefault: true }), transformObject && (_jsx(TransformControls, { ref: transformControlsRef, object: transformObject, mode: transformMode, space: "local", onObjectChange: handleTransformChange, translationSnap: positionSnap > 0 ? positionSnap : undefined, rotationSnap: rotationSnap > 0 ? rotationSnap : undefined, scaleSnap: scaleSnap > 0 ? scaleSnap : undefined }, `transform-${selectedId}-${transformMode}-${positionSnap}-${rotationSnap}-${scaleSnap}`))] }))] })), showUI && (_jsxs(_Fragment, { children: [_jsxs("div", { style: toolbar.panel, children: [_jsx("button", { style: base.btn, onClick: toggleMode, children: isEditMode ? "▶" : "⏸" }), uiPlugins] }), isEditMode && (_jsx(EditorUI, { selectedId: selectedId, setSelectedId: setSelection, getPrefab: getPrefab, onReplacePrefab: (prefab) => loadPrefab(prefab, { resetHistory: true }), onImportPrefab: importPrefab, basePath: basePath, onUndo: undo, onRedo: redo, canUndo: historyIndex > 0, canRedo: historyIndex < history.length - 1 }))] }))] }) });
374
395
  });
375
396
  PrefabEditor.displayName = "PrefabEditor";
376
397
  export default PrefabEditor;
@@ -18,7 +18,6 @@ export interface PrefabRootProps {
18
18
  selectedId?: string | null;
19
19
  onSelect?: (id: string | null) => void;
20
20
  onClick?: (event: ThreeEvent<PointerEvent>, entity: GameObjectType) => void;
21
- onObjectRefChange?: (id: string, object: Object3D | null) => void;
22
21
  basePath?: string;
23
22
  }
24
23
  export declare const PrefabRoot: import("react").ForwardRefExoticComponent<PrefabRootProps & import("react").RefAttributes<PrefabRootRef>>;
@@ -10,9 +10,8 @@ var __rest = (this && this.__rest) || function (s, e) {
10
10
  return t;
11
11
  };
12
12
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
13
- import { useHelper } from "@react-three/drei";
14
13
  import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
15
- import { BoxHelper, Euler, Matrix4, } from "three";
14
+ import { Euler, Matrix4, } from "three";
16
15
  import { useStore } from "zustand";
17
16
  import { useClickValid } from "./useClickValid";
18
17
  import { findComponent, getNodeUserData } from "./types";
@@ -69,7 +68,7 @@ function getNodeMetadataProps(node) {
69
68
  userData: Object.assign(Object.assign({ prefabNodeId: node.id }, (nodeName ? { prefabNodeName: nodeName } : {})), getNodeUserData(node)),
70
69
  };
71
70
  }
72
- export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSelect, onClick, onObjectRefChange, basePath = "" }, ref) => {
71
+ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSelect, onClick, basePath = "" }, ref) => {
73
72
  var _a;
74
73
  const [models, setModels] = useState({});
75
74
  const [textures, setTextures] = useState({});
@@ -120,8 +119,7 @@ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSel
120
119
  }), [getObject]);
121
120
  const registerRef = useCallback((id, obj) => {
122
121
  objectRefs.current[id] = obj;
123
- onObjectRefChange === null || onObjectRefChange === void 0 ? void 0 : onObjectRefChange(id, obj);
124
- }, [onObjectRefChange]);
122
+ }, []);
125
123
  const registerRigidBodyRef = useCallback((id, rb) => {
126
124
  rigidBodyRefs.current.set(id, rb);
127
125
  }, []);
@@ -293,18 +291,10 @@ function StandardNode({ nodeId, selectedId, onSelect, onClick, registerRef, load
293
291
  const clickEventName = getNodeClickEventName(gameObject);
294
292
  const metadataProps = getNodeMetadataProps(gameObject);
295
293
  const groupRef = useRef(null);
296
- const helperRef = useRef(null);
297
294
  const handleGroupRef = useCallback((object) => {
298
295
  groupRef.current = object;
299
296
  registerRef(nodeId, object);
300
297
  }, [nodeId, registerRef]);
301
- const handleHelperRef = useCallback((object) => {
302
- helperRef.current = object;
303
- }, []);
304
- const handleEditGroupRef = useCallback((object) => {
305
- handleGroupRef(object);
306
- handleHelperRef(object);
307
- }, [handleGroupRef, handleHelperRef]);
308
298
  const editClickHandlers = useClickValid(!!editMode && !isLocked, (event) => {
309
299
  onSelect === null || onSelect === void 0 ? void 0 : onSelect(nodeId);
310
300
  onClick === null || onClick === void 0 ? void 0 : onClick(event, gameObject);
@@ -318,7 +308,6 @@ function StandardNode({ nodeId, selectedId, onSelect, onClick, registerRef, load
318
308
  },
319
309
  }
320
310
  : undefined;
321
- useHelper(editMode && isSelected ? helperRef : null, BoxHelper, "cyan");
322
311
  const world = parentMatrix.clone().multiply(compose(gameObject));
323
312
  const physics = findComponent(gameObject, "Physics");
324
313
  const ready = isNodeReady(gameObject, loadedModels);
@@ -337,8 +326,8 @@ function StandardNode({ nodeId, selectedId, onSelect, onClick, registerRef, load
337
326
  const childNodes = _jsx(ChildNodes, { childIds: childIds, parentMatrix: world, selectedId: selectedId, onSelect: onSelect, onClick: onClick, registerRef: registerRef, loadedModels: loadedModels, editMode: editMode });
338
327
  const inner = renderCompositionNode(gameObject, renderCtx, primaryClickHandlers, childNodes);
339
328
  const editAnchor = editMode ? (_jsx("mesh", { visible: false, children: _jsx("boxGeometry", { args: [0.01, 0.01, 0.01] }) })) : null;
340
- const standardNode = (_jsxs("group", Object.assign({ ref: editMode ? handleEditGroupRef : handleGroupRef }, groupProps, (editMode ? editClickHandlers : undefined), { children: [editAnchor, inner] })));
341
- const physicsNode = hasPhysics && (physicsDef === null || physicsDef === void 0 ? void 0 : physicsDef.View) ? (_jsx(physicsDef.View, Object.assign({ properties: physics.properties }, transformProps, { children: _jsxs("group", Object.assign({ ref: editMode ? handleEditGroupRef : handleGroupRef }, metadataProps, (editMode ? editClickHandlers : undefined), { children: [editAnchor, inner] })) }), physicsKey)) : null;
329
+ const standardNode = (_jsxs("group", Object.assign({ ref: handleGroupRef }, groupProps, (editMode ? editClickHandlers : undefined), { children: [editAnchor, inner] })));
330
+ const physicsNode = hasPhysics && (physicsDef === null || physicsDef === void 0 ? void 0 : physicsDef.View) ? (_jsx(physicsDef.View, Object.assign({ properties: physics.properties }, transformProps, { children: _jsxs("group", Object.assign({ ref: handleGroupRef }, metadataProps, (editMode ? editClickHandlers : undefined), { children: [editAnchor, inner] })) }), physicsKey)) : null;
342
331
  return (_jsx(EntityRuntimeScope, { nodeId: nodeId, editMode: editMode, isSelected: isSelected, children: physicsNode !== null && physicsNode !== void 0 ? physicsNode : standardNode }));
343
332
  }
344
333
  function isRendererHandledComponent(componentType) {
@@ -43,7 +43,7 @@ const frustumInputStyle = {
43
43
  const centerLockButtonStyle = {
44
44
  width: 34,
45
45
  height: 34,
46
- borderRadius: 999,
46
+ borderRadius: 0,
47
47
  border: `1px solid ${colors.border}`,
48
48
  background: colors.bgInput,
49
49
  color: colors.textMuted,
@@ -11,7 +11,7 @@ var __rest = (this && this.__rest) || function (s, e) {
11
11
  };
12
12
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
13
13
  import { BallCollider, CapsuleCollider, CuboidCollider, RigidBody, useRapier } from "@react-three/rapier";
14
- import { useCallback, useEffect, useRef } from 'react';
14
+ import { useCallback, useEffect, useRef, useState } from 'react';
15
15
  import { useAssetRuntime, useEntityRuntime } from "../assetRuntime";
16
16
  import { gameEvents, getEntityIdFromRigidBody } from "../GameEvents";
17
17
  import { usePrefabNode, usePrefabStore } from "../prefabStore";
@@ -27,6 +27,7 @@ const colliderSizeFallback = [1, 1, 1];
27
27
  const colliderRadiusFallback = 0.5;
28
28
  const capsuleRadiusFallback = 0.35;
29
29
  const capsuleHalfHeightFallback = 0.45;
30
+ const EDIT_MODE_DEBUG_REFRESH_THROTTLE_MS = 120;
30
31
  function isManualColliderShape(value) {
31
32
  return value === 'cuboid' || value === 'ball' || value === 'capsule';
32
33
  }
@@ -146,11 +147,12 @@ function PhysicsComponentView({ properties, children, position, rotation, scale
146
147
  ? 'capsule'
147
148
  : resolvedManualColliderShape;
148
149
  const rigidBodyRef = useRef(null);
150
+ const [editRefreshVersion, setEditRefreshVersion] = useState(0);
151
+ const lastEditRefreshAtRef = useRef(0);
149
152
  const linearVelocityKey = linearVelocity.join(',');
150
153
  const angularVelocityKey = angularVelocity.join(',');
151
- const rbKey = editMode
152
- ? `${type || 'dynamic'}_${colliderType}_${resolvedManualColliderShape}_${colliderSize.join(',')}_${colliderRadius}_${capsuleRadius}_${capsuleHalfHeight}_${position === null || position === void 0 ? void 0 : position.join(',')}_${rotation === null || rotation === void 0 ? void 0 : rotation.join(',')}`
153
- : `${type || 'dynamic'}_${colliderType}_${resolvedManualColliderShape}_${colliderSize.join(',')}_${colliderRadius}_${capsuleRadius}_${capsuleHalfHeight}`;
154
+ const transformSignature = `${position === null || position === void 0 ? void 0 : position.join(',')}_${rotation === null || rotation === void 0 ? void 0 : rotation.join(',')}_${scale === null || scale === void 0 ? void 0 : scale.join(',')}`;
155
+ const rbKey = `${type || 'dynamic'}_${colliderType}_${resolvedManualColliderShape}_${colliderSize.join(',')}_${colliderRadius}_${capsuleRadius}_${capsuleHalfHeight}_${editMode ? editRefreshVersion : 0}`;
154
156
  const handleRigidBodyRef = useCallback((rigidBody) => {
155
157
  rigidBodyRef.current = rigidBody;
156
158
  if (!nodeId)
@@ -179,6 +181,23 @@ function PhysicsComponentView({ properties, children, position, rotation, scale
179
181
  }
180
182
  }
181
183
  }, [activeCollisionTypes, rapier, type, colliders]);
184
+ useEffect(() => {
185
+ if (!editMode) {
186
+ return;
187
+ }
188
+ const now = Date.now();
189
+ const delay = Math.max(0, EDIT_MODE_DEBUG_REFRESH_THROTTLE_MS - (now - lastEditRefreshAtRef.current));
190
+ if (delay === 0) {
191
+ lastEditRefreshAtRef.current = now;
192
+ setEditRefreshVersion(version => version + 1);
193
+ return;
194
+ }
195
+ const timeoutId = setTimeout(() => {
196
+ lastEditRefreshAtRef.current = Date.now();
197
+ setEditRefreshVersion(version => version + 1);
198
+ }, delay);
199
+ return () => clearTimeout(timeoutId);
200
+ }, [editMode, transformSignature]);
182
201
  // Seed authored velocities when the body instance changes or the authored values change.
183
202
  useEffect(() => {
184
203
  if (!rigidBodyRef.current)
@@ -238,14 +257,10 @@ function PhysicsComponentView({ properties, children, position, rotation, scale
238
257
  return;
239
258
  dispatchPhysicsEvent(collisionExitEventName, payload);
240
259
  }, [collisionExitEventName, dispatchPhysicsEvent, emitCollisionExitEvent]);
241
- const editModeTransformProps = editMode
242
- ? {
243
- position,
244
- rotation,
245
- scale,
246
- }
247
- : undefined;
248
- const rigidBodyProps = Object.assign({ ref: handleRigidBodyRef, type, colliders: usesAutomaticColliderSource ? colliderType : false, position: editMode ? undefined : position, rotation: editMode ? undefined : rotation, scale: editMode ? undefined : scale, sensor,
260
+ const rigidBodyProps = Object.assign({ ref: handleRigidBodyRef, type, colliders: usesAutomaticColliderSource ? colliderType : false, position,
261
+ rotation,
262
+ scale,
263
+ sensor,
249
264
  enabledTranslations,
250
265
  enabledRotations, name: nodeName, userData: Object.assign({ entityId: nodeId }, userData), onIntersectionEnter: emitSensorEnterEvent ? handleIntersectionEnter : undefined, onIntersectionExit: emitSensorExitEvent ? handleIntersectionExit : undefined, onCollisionEnter: emitCollisionEnterEvent ? handleCollisionEnter : undefined, onCollisionExit: emitCollisionExitEvent ? handleCollisionExit : undefined }, otherProps);
251
266
  const rigidBodyContent = (_jsxs(_Fragment, { children: [!usesAutomaticColliderSource ? renderManualCollider({
@@ -256,7 +271,7 @@ function PhysicsComponentView({ properties, children, position, rotation, scale
256
271
  capsuleRadius,
257
272
  capsuleHalfHeight,
258
273
  }) : null, children] }));
259
- return (_jsx(RigidBody, Object.assign({}, rigidBodyProps, { children: editMode ? (_jsx("group", Object.assign({}, editModeTransformProps, { children: rigidBodyContent }))) : rigidBodyContent }), rbKey));
274
+ return (_jsx(RigidBody, Object.assign({}, rigidBodyProps, { children: rigidBodyContent }), rbKey));
260
275
  }
261
276
  const PhysicsComponent = {
262
277
  name: 'Physics',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-three-game",
3
- "version": "0.0.91",
3
+ "version": "0.0.92",
4
4
  "description": "high performance 3D game engine built in React",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",