react-three-game 0.0.89 → 0.0.91

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.
@@ -43,18 +43,18 @@ function NodeInspector({ node, updateNode, deleteNode, basePath }) {
43
43
  const available = allKeys.filter(k => !hasComponent(node, k));
44
44
  const [preferredAddType, setAddType] = useState(available[0] || "");
45
45
  const addType = available.includes(preferredAddType) ? preferredAddType : (available[0] || "");
46
- return _jsxs("div", { style: inspector.content, 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: colors.textDim, wordBreak: 'break-all', border: `1px solid ${colors.border}`, padding: '2px 6px', borderRadius: 3, flex: 1, fontFamily: 'monospace' }, 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]) => {
46
+ return _jsxs("div", { style: inspector.content, children: [_jsxs("div", { style: base.section, children: [_jsxs("div", { style: { display: "flex", marginBottom: 4, alignItems: 'center', gap: 4 }, children: [_jsx("div", { style: { fontSize: 10, color: colors.textDim, wordBreak: 'break-all', background: colors.bgLight, padding: '2px 4px', flex: 1, fontFamily: 'monospace', minHeight: 18, boxSizing: 'border-box' }, children: node.id }), _jsx("button", { style: Object.assign(Object.assign(Object.assign({}, base.btn), base.btnDanger), { minWidth: 22, padding: '2px 4px' }), title: "Delete Node", onClick: deleteNode, children: "\u2715" })] }), _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: 4 }, children: _jsx("div", { style: base.label, children: "Components" }) }), node.components && Object.entries(node.components).map(([key, comp]) => {
47
47
  if (!comp)
48
48
  return null;
49
49
  const def = ALL_COMPONENTS[comp.type];
50
50
  if (!def)
51
51
  return _jsxs("div", { style: { color: colors.danger, fontSize: 11 }, children: ["Unknown: ", comp.type] }, key);
52
- return (_jsxs("div", { style: componentCard.container, 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 => {
52
+ return (_jsxs("div", { style: componentCard.container, children: [_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 3 }, children: [_jsx("div", { style: { fontSize: 11, fontWeight: 500 }, children: key }), _jsx("button", { style: Object.assign(Object.assign({}, base.btn), { padding: '2px 4px', minWidth: 20 }), title: "Remove Component", onClick: () => updateNode(n => {
53
53
  var _a;
54
54
  const _b = (_a = n.components) !== null && _a !== void 0 ? _a : {}, _c = key, _ = _b[_c], rest = __rest(_b, [typeof _c === "symbol" ? _c : _c + ""]);
55
55
  return Object.assign(Object.assign({}, n), { components: rest });
56
56
  }), 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));
57
- })] }), available.length > 0 && (_jsx("div", { children: _jsxs("div", { style: base.row, children: [_jsx("select", { style: Object.assign(Object.assign({}, base.input), { flex: 1 }), value: addType, onChange: e => setAddType(e.target.value), children: available.map(k => _jsx("option", { value: k, children: k }, k)) }), _jsx("button", { style: base.btn, disabled: !addType, onClick: () => {
57
+ })] }), available.length > 0 && (_jsx("div", { children: _jsxs("div", { style: base.row, children: [_jsx("select", { style: Object.assign(Object.assign({}, base.input), { flex: 1, background: colors.bgInput, border: `1px solid ${colors.border}`, minHeight: 22 }), value: addType, onChange: e => setAddType(e.target.value), children: available.map(k => _jsx("option", { value: k, children: k }, k)) }), _jsx("button", { style: base.btn, disabled: !addType, onClick: () => {
58
58
  if (!addType)
59
59
  return;
60
60
  const def = ALL_COMPONENTS[addType];
@@ -370,7 +370,7 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, mode
370
370
  }
371
371
  (_d = canvasProps === null || canvasProps === void 0 ? void 0 : canvasProps.onPointerMissed) === null || _d === void 0 ? void 0 : _d.call(canvasProps, event);
372
372
  }
373
- : canvasProps === null || canvasProps === void 0 ? void 0 : canvasProps.onPointerMissed, children: [physics ? (_jsx(Physics, { 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 }))] }))] }) });
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 }))] }))] }) });
374
374
  });
375
375
  PrefabEditor.displayName = "PrefabEditor";
376
376
  export default PrefabEditor;
@@ -336,8 +336,10 @@ function StandardNode({ nodeId, selectedId, onSelect, onClick, registerRef, load
336
336
  const renderCtx = { loadedModels, editMode, registerRef };
337
337
  const childNodes = _jsx(ChildNodes, { childIds: childIds, parentMatrix: world, selectedId: selectedId, onSelect: onSelect, onClick: onClick, registerRef: registerRef, loadedModels: loadedModels, editMode: editMode });
338
338
  const inner = renderCompositionNode(gameObject, renderCtx, primaryClickHandlers, childNodes);
339
- const physicsInner = editMode ? _jsx("group", { visible: false, children: inner }) : inner;
340
- return (_jsx(EntityRuntimeScope, { nodeId: nodeId, editMode: editMode, isSelected: isSelected, children: editMode ? (_jsxs(_Fragment, { children: [_jsxs("group", Object.assign({ ref: handleEditGroupRef }, groupProps, editClickHandlers, { children: [_jsx("mesh", { visible: false, children: _jsx("boxGeometry", { args: [0.01, 0.01, 0.01] }) }), inner] })), hasPhysics && (physicsDef === null || physicsDef === void 0 ? void 0 : physicsDef.View) ? (_jsx(physicsDef.View, Object.assign({ properties: physics.properties }, transformProps, { children: physicsInner }), physicsKey)) : null] })) : hasPhysics && (physicsDef === null || physicsDef === void 0 ? void 0 : physicsDef.View) ? (_jsx(physicsDef.View, Object.assign({ properties: physics.properties }, transformProps, { children: _jsx("group", Object.assign({ ref: handleGroupRef }, metadataProps, { children: inner })) }), physicsKey)) : (_jsx("group", Object.assign({ ref: handleGroupRef }, groupProps, { children: inner }))) }));
339
+ 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;
342
+ return (_jsx(EntityRuntimeScope, { nodeId: nodeId, editMode: editMode, isSelected: isSelected, children: physicsNode !== null && physicsNode !== void 0 ? physicsNode : standardNode }));
341
343
  }
342
344
  function isRendererHandledComponent(componentType) {
343
345
  return componentType === "Transform"
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { BooleanField, FieldGroup, StringField } from "./Input";
3
+ import { base, ui } from "../styles";
3
4
  const DEFAULT_TRIANGLE_POSITIONS = [
4
5
  0, 0, 0,
5
6
  1, 0, 0,
@@ -37,7 +38,7 @@ function getIndexArray(indices) {
37
38
  return maxIndex > 65535 ? new Uint32Array(indices) : new Uint16Array(indices);
38
39
  }
39
40
  function BufferArrayField({ label, value, fallback, onChange, rows = 4, }) {
40
- return (_jsxs("label", { style: { display: 'grid', gap: 4 }, children: [_jsx("span", { style: { fontSize: '10px', color: '#888', textTransform: 'uppercase', letterSpacing: '0.05em', fontWeight: 500 }, children: label }), _jsx("textarea", { rows: rows, spellCheck: false, defaultValue: toAttributeText(value, fallback), onBlur: (event) => {
41
+ return (_jsxs("label", { style: { display: 'grid', gap: 4 }, children: [_jsx("span", { style: Object.assign(Object.assign({}, base.label), { textTransform: 'uppercase', letterSpacing: '0.05em' }), children: label }), _jsx("textarea", { rows: rows, spellCheck: false, defaultValue: toAttributeText(value, fallback), onBlur: (event) => {
41
42
  try {
42
43
  onChange(parseArrayInput(event.target.value));
43
44
  event.target.setCustomValidity('');
@@ -46,19 +47,7 @@ function BufferArrayField({ label, value, fallback, onChange, rows = 4, }) {
46
47
  event.target.setCustomValidity('Expected a JSON array of numbers');
47
48
  event.target.reportValidity();
48
49
  }
49
- }, style: {
50
- width: '100%',
51
- backgroundColor: '#171717',
52
- border: '1px solid #333',
53
- padding: '6px 8px',
54
- fontSize: '11px',
55
- color: '#eee',
56
- fontFamily: 'monospace',
57
- outline: 'none',
58
- borderRadius: 3,
59
- resize: 'vertical',
60
- boxSizing: 'border-box',
61
- } })] }));
50
+ }, style: Object.assign(Object.assign({}, ui.monoTextInput), { width: '100%', minHeight: rows * 18, padding: '4px 6px', outline: 'none', resize: 'vertical', boxSizing: 'border-box' }) })] }));
62
51
  }
63
52
  function BufferGeometryComponentEditor({ component, onUpdate, }) {
64
53
  var _a;
@@ -1,22 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from "react";
3
- import { colors } from "../styles";
3
+ import { colors, ui } from "../styles";
4
4
  const RESERVED_USER_DATA_KEYS = new Set([
5
5
  'prefabNodeId',
6
6
  'prefabNodeName',
7
7
  ]);
8
- const inputStyle = {
9
- width: '100%',
10
- backgroundColor: colors.bgInput,
11
- border: `1px solid ${colors.border}`,
12
- padding: '6px 8px',
13
- fontSize: '11px',
14
- color: colors.text,
15
- fontFamily: 'monospace',
16
- outline: 'none',
17
- borderRadius: 3,
18
- boxSizing: 'border-box',
19
- };
8
+ const inputStyle = Object.assign(Object.assign({}, ui.monoTextInput), { width: '100%', padding: '4px 6px', fontSize: '11px', outline: 'none', boxSizing: 'border-box' });
20
9
  function isRecord(value) {
21
10
  return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
22
11
  }
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useMemo, useRef, useState } from 'react';
3
- import { colors } from '../styles';
3
+ import { colors, ui } from '../styles';
4
4
  import { useOptionalPrefabStoreApi } from '../prefabStore';
5
5
  // ============================================================================
6
6
  // Shared Styles (derived from shared color tokens)
@@ -10,13 +10,13 @@ const styles = {
10
10
  width: '80px',
11
11
  backgroundColor: colors.bgInput,
12
12
  border: `1px solid ${colors.border}`,
13
- padding: '3px 6px',
13
+ padding: '2px 4px',
14
14
  fontSize: '11px',
15
15
  color: colors.text,
16
16
  fontFamily: 'monospace',
17
17
  outline: 'none',
18
18
  textAlign: 'right',
19
- borderRadius: 3,
19
+ borderRadius: 0,
20
20
  },
21
21
  label: {
22
22
  display: 'block',
@@ -229,9 +229,9 @@ export function Vector3Input({ label, value, onChange, snap, labelExtra }) {
229
229
  gap: 4,
230
230
  backgroundColor: colors.bgInput,
231
231
  border: `1px solid ${colors.border}`,
232
- borderRadius: 3,
233
- padding: '4px 6px',
234
- minHeight: 28,
232
+ borderRadius: 0,
233
+ padding: '2px 4px',
234
+ minHeight: 22,
235
235
  cursor: 'ew-resize',
236
236
  }, onPointerDown: e => startScrub(e, index), onPointerMove: onScrubMove, onPointerUp: endScrub, children: [_jsx("span", { style: {
237
237
  fontSize: 11,
@@ -265,13 +265,13 @@ export function Vector3Input({ label, value, onChange, snap, labelExtra }) {
265
265
  // ============================================================================
266
266
  export function ColorInput({ label, value, onChange }) {
267
267
  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: {
268
- height: 32,
268
+ height: 22,
269
269
  width: 48,
270
270
  backgroundColor: colors.bgInput,
271
271
  border: `1px solid ${colors.border}`,
272
- borderRadius: 3,
272
+ borderRadius: 0,
273
273
  cursor: 'pointer',
274
- padding: 2,
274
+ padding: 1,
275
275
  flexShrink: 0,
276
276
  }, 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) })] })] }));
277
277
  }
@@ -304,20 +304,20 @@ function SearchSuggestionList({ query, options, onSelect, emptyMessage, }) {
304
304
  gap: 4,
305
305
  maxHeight: 160,
306
306
  overflowY: 'auto',
307
- border: `1px solid ${colors.border}`,
308
- borderRadius: 3,
307
+ border: 'none',
308
+ borderRadius: 0,
309
309
  background: colors.bgSurface,
310
- padding: 4,
310
+ padding: 2,
311
311
  }, children: filtered.length === 0 ? (_jsx("div", { style: { fontSize: 11, color: colors.textMuted, padding: '4px 6px' }, children: emptyMessage })) : filtered.map(option => (_jsxs("button", { type: "button", onClick: () => onSelect(option.value), style: {
312
312
  display: 'flex',
313
313
  flexDirection: 'column',
314
314
  alignItems: 'flex-start',
315
315
  gap: 2,
316
- border: `1px solid ${colors.border}`,
317
- borderRadius: 3,
318
- background: colors.bgInput,
316
+ border: 'none',
317
+ borderRadius: 0,
318
+ background: colors.bgSurface,
319
319
  color: colors.text,
320
- padding: '6px 8px',
320
+ padding: '4px 6px',
321
321
  cursor: 'pointer',
322
322
  textAlign: 'left',
323
323
  }, children: [_jsx("span", { style: { fontSize: 11, fontWeight: 500 }, children: option.label }), option.description ? (_jsx("span", { style: { fontSize: 10, color: colors.textMuted, fontFamily: 'monospace' }, children: option.description })) : null] }, option.value))) })] }));
@@ -383,19 +383,7 @@ export function ListEditor({ label, items, renderItem, onAdd, addOptions = [], e
383
383
  setSelectedAddValue((_b = (_a = addOptions[0]) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : '');
384
384
  }
385
385
  }, [addOptions, hasAddSelector, selectedAddValue]);
386
- return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 8 }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between' }, children: [_jsx(Label, { children: label }), _jsxs("div", { style: { display: 'flex', gap: 6, alignItems: 'center' }, children: [hasAddSelector ? (_jsx("div", { style: { minWidth: 140 }, children: _jsx(SelectInput, { value: resolvedAddValue, onChange: setSelectedAddValue, options: canAdd ? addOptions : [{ value: '', label: 'All items added' }] }) })) : null, _jsx("button", { type: "button", onClick: () => onAdd(resolvedAddValue), disabled: !canAddItem, style: {
387
- width: 22,
388
- height: 22,
389
- borderRadius: 3,
390
- border: `1px solid ${canAddItem ? colors.accentBorder : colors.border}`,
391
- background: canAddItem ? colors.accentBg : colors.bgSurface,
392
- color: canAddItem ? colors.accent : colors.textMuted,
393
- cursor: canAddItem ? 'pointer' : 'not-allowed',
394
- fontSize: 14,
395
- lineHeight: 1,
396
- padding: 0,
397
- flexShrink: 0,
398
- }, title: canAddItem ? addButtonTitle : addDisabledTitle, children: "+" })] })] }), items.length === 0 ? (_jsx("div", { style: { fontSize: 11, color: colors.textMuted }, children: emptyMessage })) : null, items.map(renderItem)] }));
386
+ return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 8 }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between' }, children: [_jsx(Label, { children: label }), _jsxs("div", { style: { display: 'flex', gap: 6, alignItems: 'center' }, children: [hasAddSelector ? (_jsx("div", { style: { minWidth: 140 }, children: _jsx(SelectInput, { value: resolvedAddValue, onChange: setSelectedAddValue, options: canAdd ? addOptions : [{ value: '', label: 'All items added' }] }) })) : null, _jsx("button", { type: "button", onClick: () => onAdd(resolvedAddValue), disabled: !canAddItem, style: Object.assign(Object.assign({}, ui.compactActionButton), { width: 22, minWidth: 22, height: 22, border: `1px solid ${canAddItem ? colors.accentBorder : colors.border}`, background: canAddItem ? colors.accentBg : colors.bgSurface, color: canAddItem ? colors.accent : colors.textMuted, cursor: canAddItem ? 'pointer' : 'not-allowed', fontSize: 14, lineHeight: 1 }), title: canAddItem ? addButtonTitle : addDisabledTitle, children: "+" })] })] }), items.length === 0 ? (_jsx("div", { style: { fontSize: 11, color: colors.textMuted }, children: emptyMessage })) : null, items.map(renderItem)] }));
399
387
  }
400
388
  export function NumberField({ name, label, values, onChange, fallback = 0, step, min, max, style, }) {
401
389
  var _a;
@@ -5,7 +5,7 @@ import { BooleanField, FieldGroup, Label, ListEditor, NumberInput, SelectInput,
5
5
  import { useAssetRuntime } from '../assetRuntime';
6
6
  import { EditorContext } from '../PrefabEditor';
7
7
  import { getRepeatAxesFromModelProperties, normalizeRepeatAxes } from '../InstanceProvider';
8
- import { colors } from '../styles';
8
+ import { colors, ui } from '../styles';
9
9
  const AXIS_OPTIONS = [
10
10
  { value: 'x', label: 'X' },
11
11
  { value: 'y', label: 'Y' },
@@ -35,25 +35,7 @@ function RepeatAxisEditor({ axes, onChange, positionSnap, }) {
35
35
  return (_jsx(ListEditor, { label: "Repeat Axes", items: axes, onAdd: addAxis, addOptions: availableAxisOptions, canAdd: availableAxisOptions.length > 0, emptyMessage: "No repeat axes added.", addButtonTitle: "Add repeat axis", addDisabledTitle: "All axes already in use", renderItem: (axisConfig, index) => {
36
36
  const usedByOthers = new Set(axes.filter((_, axisIndex) => axisIndex !== index).map(axis => axis.axis));
37
37
  const axisOptions = AXIS_OPTIONS.filter(option => option.value === axisConfig.axis || !usedByOthers.has(option.value));
38
- return (_jsxs("div", { style: {
39
- display: 'flex',
40
- flexDirection: 'column',
41
- gap: 6,
42
- padding: 8,
43
- border: `1px solid ${colors.border}`,
44
- borderRadius: 4,
45
- background: colors.bgSurface,
46
- }, children: [_jsxs("div", { style: { display: 'flex', gap: 6, alignItems: 'end' }, children: [_jsx("div", { style: { flex: 1, minWidth: 0 }, children: _jsx(SelectInput, { label: "Axis", value: axisConfig.axis, onChange: (axis) => updateAxis(index, { axis: axis }), options: axisOptions }) }), _jsx("button", { type: "button", onClick: () => removeAxis(index), style: {
47
- height: 24,
48
- width: 28,
49
- borderRadius: 3,
50
- border: `1px solid ${colors.border}`,
51
- background: colors.bgInput,
52
- color: colors.text,
53
- cursor: 'pointer',
54
- padding: 0,
55
- flexShrink: 0,
56
- }, title: "Remove repeat axis", children: "\u00D7" })] }), _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 6 }, children: [_jsxs("div", { children: [_jsx(Label, { children: "Count" }), _jsx(NumberInput, { value: axisConfig.count, onChange: (count) => updateAxis(index, { count: Math.max(1, Math.floor(count)) }), step: 1, min: 1, style: { width: '100%', minWidth: 0, boxSizing: 'border-box' } })] }), _jsxs("div", { children: [_jsx(Label, { children: "Offset" }), _jsx(NumberInput, { value: axisConfig.offset, onChange: (offset) => updateAxis(index, { offset: quantize(offset, positionSnap) }), step: positionSnap > 0 ? positionSnap : 0.1, style: { width: '100%', minWidth: 0, boxSizing: 'border-box' } })] })] })] }, `${axisConfig.axis}-${index}`));
38
+ return (_jsxs("div", { style: Object.assign(Object.assign({}, ui.secondaryPanel), { display: 'flex', flexDirection: 'column', gap: 6 }), children: [_jsxs("div", { style: { display: 'flex', gap: 6, alignItems: 'end' }, children: [_jsx("div", { style: { flex: 1, minWidth: 0 }, children: _jsx(SelectInput, { label: "Axis", value: axisConfig.axis, onChange: (axis) => updateAxis(index, { axis: axis }), options: axisOptions }) }), _jsx("button", { type: "button", onClick: () => removeAxis(index), style: Object.assign(Object.assign({}, ui.compactActionButton), { height: 24, background: colors.bgInput }), title: "Remove repeat axis", children: "\u00D7" })] }), _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 6 }, children: [_jsxs("div", { children: [_jsx(Label, { children: "Count" }), _jsx(NumberInput, { value: axisConfig.count, onChange: (count) => updateAxis(index, { count: Math.max(1, Math.floor(count)) }), step: 1, min: 1, style: { width: '100%', minWidth: 0, boxSizing: 'border-box' } })] }), _jsxs("div", { children: [_jsx(Label, { children: "Offset" }), _jsx(NumberInput, { value: axisConfig.offset, onChange: (offset) => updateAxis(index, { offset: quantize(offset, positionSnap) }), step: positionSnap > 0 ? positionSnap : 0.1, style: { width: '100%', minWidth: 0, boxSizing: 'border-box' } })] })] })] }, `${axisConfig.axis}-${index}`));
57
39
  } }));
58
40
  }
59
41
  function ModelComponentEditor({ component, node, onUpdate, basePath = "" }) {
@@ -1,11 +1,15 @@
1
1
  import type { RigidBodyOptions } from "@react-three/rapier";
2
2
  import { Component } from "./ComponentRegistry";
3
3
  type PhysicsColliderType = NonNullable<RigidBodyOptions['colliders']> | 'capsule';
4
+ type ManualColliderShape = 'cuboid' | 'ball' | 'capsule';
4
5
  export type PhysicsProps = Omit<RigidBodyOptions, 'colliders'> & {
5
6
  colliders?: PhysicsColliderType;
7
+ manualColliderShape?: ManualColliderShape;
6
8
  activeCollisionTypes?: 'all' | undefined;
7
9
  linearVelocity?: [number, number, number];
8
10
  angularVelocity?: [number, number, number];
11
+ colliderSize?: [number, number, number];
12
+ colliderRadius?: number;
9
13
  capsuleRadius?: number;
10
14
  capsuleHalfHeight?: number;
11
15
  emitSensorEnterEvent?: boolean;
@@ -10,20 +10,57 @@ var __rest = (this && this.__rest) || function (s, e) {
10
10
  return t;
11
11
  };
12
12
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
13
- import { CapsuleCollider, RigidBody, useRapier } from "@react-three/rapier";
13
+ import { BallCollider, CapsuleCollider, CuboidCollider, RigidBody, useRapier } from "@react-three/rapier";
14
14
  import { useCallback, useEffect, useRef } from 'react';
15
15
  import { useAssetRuntime, useEntityRuntime } from "../assetRuntime";
16
16
  import { gameEvents, getEntityIdFromRigidBody } from "../GameEvents";
17
- import { usePrefabNode } from "../prefabStore";
17
+ import { usePrefabNode, usePrefabStore } from "../prefabStore";
18
18
  import { BooleanField, FieldGroup, NumberField, SelectField, StringField, Vector3Field } from "./Input";
19
- import { getNodeUserData } from "../types";
19
+ import { findComponent, getNodeUserData } from "../types";
20
20
  import { colors } from "../styles";
21
21
  export function isPhysicsProps(v) {
22
22
  return (v === null || v === void 0 ? void 0 : v.type) === "fixed" || (v === null || v === void 0 ? void 0 : v.type) === "dynamic" || (v === null || v === void 0 ? void 0 : v.type) === "kinematicPosition" || (v === null || v === void 0 ? void 0 : v.type) === "kinematicVelocity";
23
23
  }
24
24
  const enabledAxesFallback = [true, true, true];
25
+ const manualColliderShapeFallback = 'cuboid';
26
+ const colliderSizeFallback = [1, 1, 1];
27
+ const colliderRadiusFallback = 0.5;
25
28
  const capsuleRadiusFallback = 0.35;
26
29
  const capsuleHalfHeightFallback = 0.45;
30
+ function isManualColliderShape(value) {
31
+ return value === 'cuboid' || value === 'ball' || value === 'capsule';
32
+ }
33
+ function hasNodeColliderSource(node) {
34
+ return Boolean(findComponent(node, 'Geometry')
35
+ || findComponent(node, 'BufferGeometry')
36
+ || findComponent(node, 'Model'));
37
+ }
38
+ function subtreeHasColliderSource(nodeId, nodesById, childIdsById) {
39
+ var _a;
40
+ if (!nodeId)
41
+ return false;
42
+ const pending = [nodeId];
43
+ while (pending.length > 0) {
44
+ const currentId = pending.pop();
45
+ if (!currentId)
46
+ continue;
47
+ const currentNode = nodesById[currentId];
48
+ if (hasNodeColliderSource(currentNode)) {
49
+ return true;
50
+ }
51
+ pending.push(...((_a = childIdsById[currentId]) !== null && _a !== void 0 ? _a : []));
52
+ }
53
+ return false;
54
+ }
55
+ function renderManualCollider({ shape, sensor, colliderSize, colliderRadius, capsuleRadius, capsuleHalfHeight, }) {
56
+ if (shape === 'ball') {
57
+ return _jsx(BallCollider, { args: [colliderRadius], sensor: sensor });
58
+ }
59
+ if (shape === 'capsule') {
60
+ return _jsx(CapsuleCollider, { args: [capsuleHalfHeight, capsuleRadius], sensor: sensor });
61
+ }
62
+ return _jsx(CuboidCollider, { args: colliderSize.map(value => value / 2), sensor: sensor });
63
+ }
27
64
  function LockedAxisField({ label, name, values, onChange, }) {
28
65
  const enabledAxes = Array.isArray(values[name])
29
66
  ? values[name]
@@ -53,10 +90,11 @@ function LockedAxisField({ label, name, values, onChange, }) {
53
90
  const isLocked = !enabledAxes[index];
54
91
  return (_jsx("button", { type: "button", onClick: () => toggleAxisLock(index), style: {
55
92
  flex: 1,
93
+ minHeight: 22,
56
94
  backgroundColor: isLocked ? colors.dangerBg : colors.bgInput,
57
95
  border: `1px solid ${isLocked ? colors.dangerBorder : colors.border}`,
58
- borderRadius: 3,
59
- padding: '6px 8px',
96
+ borderRadius: 0,
97
+ padding: '2px 6px',
60
98
  color: isLocked ? colors.danger : colors.textMuted,
61
99
  fontSize: '11px',
62
100
  fontFamily: 'monospace',
@@ -64,19 +102,28 @@ function LockedAxisField({ label, name, values, onChange, }) {
64
102
  }, children: axisLabel }, axisLabel));
65
103
  }) })] }));
66
104
  }
67
- function PhysicsComponentEditor({ component, onUpdate }) {
105
+ function PhysicsComponentEditor({ node, component, onUpdate }) {
106
+ const nodeId = node === null || node === void 0 ? void 0 : node.id;
107
+ const hasAutomaticColliderSource = usePrefabStore(state => subtreeHasColliderSource(nodeId, state.nodesById, state.childIdsById));
108
+ const manualColliderShape = isManualColliderShape(component.properties.manualColliderShape)
109
+ ? component.properties.manualColliderShape
110
+ : manualColliderShapeFallback;
68
111
  return (_jsxs(FieldGroup, { children: [_jsx(SelectField, { name: "type", label: "Type", values: component.properties, onChange: onUpdate, options: [
69
112
  { value: 'dynamic', label: 'Dynamic' },
70
113
  { value: 'fixed', label: 'Fixed' },
71
114
  { value: 'kinematicPosition', label: 'Kinematic Position' },
72
115
  { value: 'kinematicVelocity', label: 'Kinematic Velocity' },
73
- ] }), _jsx(SelectField, { name: "colliders", label: "Collider", values: component.properties, onChange: onUpdate, options: [
74
- { value: 'hull', label: 'Hull (convex)' },
75
- { value: 'trimesh', label: 'Trimesh (exact)' },
76
- { value: 'cuboid', label: 'Cuboid (box)' },
77
- { value: 'ball', label: 'Ball (sphere)' },
78
- { value: 'capsule', label: 'Capsule' },
79
- ] }), component.properties.colliders === 'capsule' ? (_jsxs(_Fragment, { children: [_jsx(NumberField, { name: "capsuleRadius", label: "Capsule Radius", values: component.properties, onChange: onUpdate, fallback: capsuleRadiusFallback, min: 0.01, step: 0.01 }), _jsx(NumberField, { name: "capsuleHalfHeight", label: "Capsule Half Height", values: component.properties, onChange: onUpdate, fallback: capsuleHalfHeightFallback, min: 0.01, step: 0.01 })] })) : null, _jsx(NumberField, { name: "mass", label: "Mass", values: component.properties, onChange: onUpdate, fallback: 1, step: 0.1, min: 0 }), _jsx(NumberField, { name: "restitution", label: "Restitution (Bounciness)", values: component.properties, onChange: onUpdate, fallback: 0, min: 0, max: 1, step: 0.1 }), _jsx(NumberField, { name: "friction", label: "Friction", values: component.properties, onChange: onUpdate, fallback: 0.5, min: 0, step: 0.1 }), _jsx(NumberField, { name: "linearDamping", label: "Linear Damping", values: component.properties, onChange: onUpdate, fallback: 0, min: 0, step: 0.1 }), _jsx(NumberField, { name: "angularDamping", label: "Angular Damping", values: component.properties, onChange: onUpdate, fallback: 0, min: 0, step: 0.1 }), _jsx(NumberField, { name: "gravityScale", label: "Gravity Scale", values: component.properties, onChange: onUpdate, fallback: 1, step: 0.1 }), _jsx(Vector3Field, { name: "linearVelocity", label: "Linear Velocity", values: component.properties, onChange: onUpdate, fallback: [0, 0, 0] }), _jsx(Vector3Field, { name: "angularVelocity", label: "Angular Velocity", values: component.properties, onChange: onUpdate, fallback: [0, 0, 0] }), _jsx(LockedAxisField, { label: "Lock Movement", name: "enabledTranslations", values: component.properties, onChange: onUpdate }), _jsx(LockedAxisField, { label: "Lock Rotations", name: "enabledRotations", values: component.properties, onChange: onUpdate }), _jsx(BooleanField, { name: "sensor", label: "Sensor (Trigger Only)", values: component.properties, onChange: onUpdate, fallback: false }), _jsx(BooleanField, { name: "emitCollisionEnterEvent", label: "Emit Collision Enter", values: component.properties, onChange: onUpdate, fallback: false }), component.properties.emitCollisionEnterEvent ? (_jsx(StringField, { name: "collisionEnterEventName", label: "Collision Enter Event", values: component.properties, onChange: onUpdate, placeholder: "target:hit" })) : null, _jsx(BooleanField, { name: "emitCollisionExitEvent", label: "Emit Collision Exit", values: component.properties, onChange: onUpdate, fallback: false }), component.properties.emitCollisionExitEvent ? (_jsx(StringField, { name: "collisionExitEventName", label: "Collision Exit Event", values: component.properties, onChange: onUpdate, placeholder: "target:reset" })) : null, _jsx(BooleanField, { name: "emitSensorEnterEvent", label: "Emit Sensor Enter", values: component.properties, onChange: onUpdate, fallback: false }), component.properties.emitSensorEnterEvent ? (_jsx(StringField, { name: "sensorEnterEventName", label: "Sensor Enter Event", values: component.properties, onChange: onUpdate, placeholder: "sensor:enter" })) : null, _jsx(BooleanField, { name: "emitSensorExitEvent", label: "Emit Sensor Exit", values: component.properties, onChange: onUpdate, fallback: false }), component.properties.emitSensorExitEvent ? (_jsx(StringField, { name: "sensorExitEventName", label: "Sensor Exit Event", values: component.properties, onChange: onUpdate, placeholder: "sensor:exit" })) : null, _jsx(SelectField, { name: "activeCollisionTypes", label: "Collision Detection", values: component.properties, onChange: onUpdate, options: [
116
+ ] }), hasAutomaticColliderSource ? (_jsxs(_Fragment, { children: [_jsx(SelectField, { name: "colliders", label: "Collider", values: component.properties, onChange: onUpdate, options: [
117
+ { value: 'hull', label: 'Hull (convex)' },
118
+ { value: 'trimesh', label: 'Trimesh (exact)' },
119
+ { value: 'cuboid', label: 'Cuboid (box)' },
120
+ { value: 'ball', label: 'Ball (sphere)' },
121
+ { value: 'capsule', label: 'Capsule' },
122
+ ] }), component.properties.colliders === 'capsule' ? (_jsxs(_Fragment, { children: [_jsx(NumberField, { name: "capsuleRadius", label: "Capsule Radius", values: component.properties, onChange: onUpdate, fallback: capsuleRadiusFallback, min: 0.01, step: 0.01 }), _jsx(NumberField, { name: "capsuleHalfHeight", label: "Capsule Half Height", values: component.properties, onChange: onUpdate, fallback: capsuleHalfHeightFallback, min: 0.01, step: 0.01 })] })) : null] })) : (_jsxs(_Fragment, { children: [_jsx(SelectField, { name: "manualColliderShape", label: "Shape", values: Object.assign(Object.assign({}, component.properties), { manualColliderShape }), onChange: onUpdate, options: [
123
+ { value: 'cuboid', label: 'Cuboid (box)' },
124
+ { value: 'ball', label: 'Ball (sphere)' },
125
+ { value: 'capsule', label: 'Capsule' },
126
+ ] }), manualColliderShape === 'cuboid' ? (_jsx(Vector3Field, { name: "colliderSize", label: "Collider Size", values: component.properties, onChange: onUpdate, fallback: colliderSizeFallback })) : null, manualColliderShape === 'ball' ? (_jsx(NumberField, { name: "colliderRadius", label: "Collider Radius", values: component.properties, onChange: onUpdate, fallback: colliderRadiusFallback, min: 0.01, step: 0.01 })) : null, manualColliderShape === 'capsule' ? (_jsxs(_Fragment, { children: [_jsx(NumberField, { name: "capsuleRadius", label: "Capsule Radius", values: component.properties, onChange: onUpdate, fallback: capsuleRadiusFallback, min: 0.01, step: 0.01 }), _jsx(NumberField, { name: "capsuleHalfHeight", label: "Capsule Half Height", values: component.properties, onChange: onUpdate, fallback: capsuleHalfHeightFallback, min: 0.01, step: 0.01 })] })) : null] })), _jsx(NumberField, { name: "mass", label: "Mass", values: component.properties, onChange: onUpdate, fallback: 1, step: 0.1, min: 0 }), _jsx(NumberField, { name: "restitution", label: "Restitution (Bounciness)", values: component.properties, onChange: onUpdate, fallback: 0, min: 0, max: 1, step: 0.1 }), _jsx(NumberField, { name: "friction", label: "Friction", values: component.properties, onChange: onUpdate, fallback: 0.5, min: 0, step: 0.1 }), _jsx(NumberField, { name: "linearDamping", label: "Linear Damping", values: component.properties, onChange: onUpdate, fallback: 0, min: 0, step: 0.1 }), _jsx(NumberField, { name: "angularDamping", label: "Angular Damping", values: component.properties, onChange: onUpdate, fallback: 0, min: 0, step: 0.1 }), _jsx(NumberField, { name: "gravityScale", label: "Gravity Scale", values: component.properties, onChange: onUpdate, fallback: 1, step: 0.1 }), _jsx(Vector3Field, { name: "linearVelocity", label: "Linear Velocity", values: component.properties, onChange: onUpdate, fallback: [0, 0, 0] }), _jsx(Vector3Field, { name: "angularVelocity", label: "Angular Velocity", values: component.properties, onChange: onUpdate, fallback: [0, 0, 0] }), _jsx(LockedAxisField, { label: "Lock Movement", name: "enabledTranslations", values: component.properties, onChange: onUpdate }), _jsx(LockedAxisField, { label: "Lock Rotations", name: "enabledRotations", values: component.properties, onChange: onUpdate }), _jsx(BooleanField, { name: "sensor", label: "Sensor (Trigger Only)", values: component.properties, onChange: onUpdate, fallback: false }), _jsx(BooleanField, { name: "emitCollisionEnterEvent", label: "Emit Collision Enter", values: component.properties, onChange: onUpdate, fallback: false }), component.properties.emitCollisionEnterEvent ? (_jsx(StringField, { name: "collisionEnterEventName", label: "Collision Enter Event", values: component.properties, onChange: onUpdate, placeholder: "target:hit" })) : null, _jsx(BooleanField, { name: "emitCollisionExitEvent", label: "Emit Collision Exit", values: component.properties, onChange: onUpdate, fallback: false }), component.properties.emitCollisionExitEvent ? (_jsx(StringField, { name: "collisionExitEventName", label: "Collision Exit Event", values: component.properties, onChange: onUpdate, placeholder: "target:reset" })) : null, _jsx(BooleanField, { name: "emitSensorEnterEvent", label: "Emit Sensor Enter", values: component.properties, onChange: onUpdate, fallback: false }), component.properties.emitSensorEnterEvent ? (_jsx(StringField, { name: "sensorEnterEventName", label: "Sensor Enter Event", values: component.properties, onChange: onUpdate, placeholder: "sensor:enter" })) : null, _jsx(BooleanField, { name: "emitSensorExitEvent", label: "Emit Sensor Exit", values: component.properties, onChange: onUpdate, fallback: false }), component.properties.emitSensorExitEvent ? (_jsx(StringField, { name: "sensorExitEventName", label: "Sensor Exit Event", values: component.properties, onChange: onUpdate, placeholder: "sensor:exit" })) : null, _jsx(SelectField, { name: "activeCollisionTypes", label: "Collision Detection", values: component.properties, onChange: onUpdate, options: [
80
127
  { value: '', label: 'Default (Dynamic only)' },
81
128
  { value: 'all', label: 'All (includes kinematic & fixed)' },
82
129
  ] })] }));
@@ -86,17 +133,24 @@ function PhysicsComponentView({ properties, children, position, rotation, scale
86
133
  const { registerRigidBodyRef } = useAssetRuntime();
87
134
  const { editMode, nodeId, getObject } = useEntityRuntime();
88
135
  const gameObject = usePrefabNode(nodeId);
136
+ const hasAutomaticColliderSource = usePrefabStore(state => subtreeHasColliderSource(nodeId, state.nodesById, state.childIdsById));
89
137
  const nodeName = (_b = (_a = gameObject === null || gameObject === void 0 ? void 0 : gameObject.name) === null || _a === void 0 ? void 0 : _a.trim()) !== null && _b !== void 0 ? _b : '';
90
138
  const userData = Object.assign(Object.assign({ prefabNodeId: (_c = gameObject === null || gameObject === void 0 ? void 0 : gameObject.id) !== null && _c !== void 0 ? _c : nodeId }, (nodeName ? { prefabNodeName: nodeName } : {})), getNodeUserData(gameObject));
91
- const { type, colliders, sensor, activeCollisionTypes, linearVelocity = [0, 0, 0], angularVelocity = [0, 0, 0], capsuleRadius = capsuleRadiusFallback, capsuleHalfHeight = capsuleHalfHeightFallback, emitSensorEnterEvent = false, sensorEnterEventName, emitSensorExitEvent = false, sensorExitEventName, emitCollisionEnterEvent = false, collisionEnterEventName, emitCollisionExitEvent = false, collisionExitEventName, enabledTranslations = enabledAxesFallback, enabledRotations = enabledAxesFallback } = properties, otherProps = __rest(properties, ["type", "colliders", "sensor", "activeCollisionTypes", "linearVelocity", "angularVelocity", "capsuleRadius", "capsuleHalfHeight", "emitSensorEnterEvent", "sensorEnterEventName", "emitSensorExitEvent", "sensorExitEventName", "emitCollisionEnterEvent", "collisionEnterEventName", "emitCollisionExitEvent", "collisionExitEventName", "enabledTranslations", "enabledRotations"]);
139
+ const { type, colliders, sensor, manualColliderShape = manualColliderShapeFallback, activeCollisionTypes, linearVelocity = [0, 0, 0], angularVelocity = [0, 0, 0], colliderSize = colliderSizeFallback, colliderRadius = colliderRadiusFallback, capsuleRadius = capsuleRadiusFallback, capsuleHalfHeight = capsuleHalfHeightFallback, emitSensorEnterEvent = false, sensorEnterEventName, emitSensorExitEvent = false, sensorExitEventName, emitCollisionEnterEvent = false, collisionEnterEventName, emitCollisionExitEvent = false, collisionExitEventName, enabledTranslations = enabledAxesFallback, enabledRotations = enabledAxesFallback } = properties, otherProps = __rest(properties, ["type", "colliders", "sensor", "manualColliderShape", "activeCollisionTypes", "linearVelocity", "angularVelocity", "colliderSize", "colliderRadius", "capsuleRadius", "capsuleHalfHeight", "emitSensorEnterEvent", "sensorEnterEventName", "emitSensorExitEvent", "sensorExitEventName", "emitCollisionEnterEvent", "collisionEnterEventName", "emitCollisionExitEvent", "collisionExitEventName", "enabledTranslations", "enabledRotations"]);
92
140
  const colliderType = colliders || (type === 'fixed' ? 'trimesh' : 'hull');
93
- const usesManualCapsuleCollider = colliderType === 'capsule';
141
+ const resolvedManualColliderShape = isManualColliderShape(manualColliderShape)
142
+ ? manualColliderShape
143
+ : manualColliderShapeFallback;
144
+ const usesAutomaticColliderSource = hasAutomaticColliderSource && colliderType !== 'capsule';
145
+ const manualColliderShapeToRender = hasAutomaticColliderSource
146
+ ? 'capsule'
147
+ : resolvedManualColliderShape;
94
148
  const rigidBodyRef = useRef(null);
95
149
  const linearVelocityKey = linearVelocity.join(',');
96
150
  const angularVelocityKey = angularVelocity.join(',');
97
151
  const rbKey = editMode
98
- ? `${type || 'dynamic'}_${colliderType}_${capsuleRadius}_${capsuleHalfHeight}_${position === null || position === void 0 ? void 0 : position.join(',')}_${rotation === null || rotation === void 0 ? void 0 : rotation.join(',')}`
99
- : `${type || 'dynamic'}_${colliderType}_${capsuleRadius}_${capsuleHalfHeight}`;
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}`;
100
154
  const handleRigidBodyRef = useCallback((rigidBody) => {
101
155
  rigidBodyRef.current = rigidBody;
102
156
  if (!nodeId)
@@ -184,13 +238,25 @@ function PhysicsComponentView({ properties, children, position, rotation, scale
184
238
  return;
185
239
  dispatchPhysicsEvent(collisionExitEventName, payload);
186
240
  }, [collisionExitEventName, dispatchPhysicsEvent, emitCollisionExitEvent]);
187
- const rigidBodyProps = Object.assign({ ref: handleRigidBodyRef, type, colliders: usesManualCapsuleCollider ? false : colliderType, position,
188
- rotation,
189
- scale,
190
- sensor,
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,
191
249
  enabledTranslations,
192
250
  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);
193
- return (_jsxs(RigidBody, Object.assign({}, rigidBodyProps, { children: [usesManualCapsuleCollider ? _jsx(CapsuleCollider, { args: [capsuleHalfHeight, capsuleRadius], sensor: sensor }) : null, children] }), rbKey));
251
+ const rigidBodyContent = (_jsxs(_Fragment, { children: [!usesAutomaticColliderSource ? renderManualCollider({
252
+ shape: manualColliderShapeToRender,
253
+ sensor,
254
+ colliderSize,
255
+ colliderRadius,
256
+ capsuleRadius,
257
+ capsuleHalfHeight,
258
+ }) : null, children] }));
259
+ return (_jsx(RigidBody, Object.assign({}, rigidBodyProps, { children: editMode ? (_jsx("group", Object.assign({}, editModeTransformProps, { children: rigidBodyContent }))) : rigidBodyContent }), rbKey));
194
260
  }
195
261
  const PhysicsComponent = {
196
262
  name: 'Physics',
@@ -199,6 +265,9 @@ const PhysicsComponent = {
199
265
  defaultProperties: {
200
266
  type: 'dynamic',
201
267
  colliders: 'hull',
268
+ manualColliderShape: manualColliderShapeFallback,
269
+ colliderSize: colliderSizeFallback,
270
+ colliderRadius: colliderRadiusFallback,
202
271
  capsuleRadius: capsuleRadiusFallback,
203
272
  capsuleHalfHeight: capsuleHalfHeightFallback,
204
273
  linearVelocity: [0, 0, 0],
@@ -5,7 +5,7 @@ import { SoundPicker } from '../../assetviewer/page';
5
5
  import { useAssetRuntime, useEntityRuntime } from '../assetRuntime';
6
6
  import { gameEvents } from '../GameEvents';
7
7
  import { BooleanField, FieldGroup, FieldRenderer, ListEditor, NumberField, SelectField, StringField } from './Input';
8
- import { colors } from '../styles';
8
+ import { colors, ui } from '../styles';
9
9
  import { AudioListener } from 'three';
10
10
  const CLIP_MODE_OPTIONS = [
11
11
  { value: 'single', label: 'Single Clip' },
@@ -116,25 +116,7 @@ function SoundComponentEditor({ component, onUpdate, basePath = '' }) {
116
116
  type: 'select',
117
117
  options: CLIP_MODE_OPTIONS.map(option => ({ value: option.value, label: option.label })),
118
118
  },
119
- ], values: component.properties, onChange: onUpdate }), _jsx(ListEditor, { label: "Clips", items: clips, onAdd: addClip, emptyMessage: "No clips added.", addButtonTitle: "Add clip", addDisabledTitle: "Add clip", renderItem: (clip, index) => (_jsxs("div", { style: {
120
- display: 'flex',
121
- gap: 6,
122
- alignItems: 'end',
123
- padding: 8,
124
- border: `1px solid ${colors.border}`,
125
- borderRadius: 4,
126
- background: colors.bgSurface,
127
- }, children: [_jsx("div", { style: { flex: 1, minWidth: 0 }, children: _jsx(SoundPicker, { value: clip || undefined, onChange: (nextPath) => updateClip(index, nextPath !== null && nextPath !== void 0 ? nextPath : ''), basePath: basePath }) }), _jsx("button", { type: "button", onClick: () => removeClip(index), style: {
128
- height: 24,
129
- width: 28,
130
- borderRadius: 3,
131
- border: `1px solid ${colors.border}`,
132
- background: colors.bgInput,
133
- color: colors.text,
134
- cursor: 'pointer',
135
- padding: 0,
136
- flexShrink: 0,
137
- }, title: "Remove clip", children: "\u00D7" })] }, `${clip}-${index}`)) }), _jsx(BooleanField, { name: "positional", label: "Positional", values: component.properties, onChange: onUpdate, fallback: false }), positional ? (_jsxs(_Fragment, { children: [_jsx(NumberField, { name: "refDistance", label: "Ref Distance", values: component.properties, onChange: onUpdate, fallback: 1, min: 0.01, step: 0.1 }), _jsx(NumberField, { name: "maxDistance", label: "Max Distance", values: component.properties, onChange: onUpdate, fallback: 24, min: 0.01, step: 0.1 }), _jsx(NumberField, { name: "rolloffFactor", label: "Rolloff", values: component.properties, onChange: onUpdate, fallback: 1, min: 0, step: 0.1 }), _jsx(SelectField, { name: "distanceModel", label: "Distance Model", values: component.properties, onChange: onUpdate, fallback: "inverse", options: [
119
+ ], values: component.properties, onChange: onUpdate }), _jsx(ListEditor, { label: "Clips", items: clips, onAdd: addClip, emptyMessage: "No clips added.", addButtonTitle: "Add clip", addDisabledTitle: "Add clip", renderItem: (clip, index) => (_jsxs("div", { style: Object.assign(Object.assign({}, ui.secondaryPanel), { display: 'flex', gap: 6, alignItems: 'end' }), children: [_jsx("div", { style: { flex: 1, minWidth: 0 }, children: _jsx(SoundPicker, { value: clip || undefined, onChange: (nextPath) => updateClip(index, nextPath !== null && nextPath !== void 0 ? nextPath : ''), basePath: basePath }) }), _jsx("button", { type: "button", onClick: () => removeClip(index), style: Object.assign(Object.assign({}, ui.compactActionButton), { height: 24, background: colors.bgInput }), title: "Remove clip", children: "\u00D7" })] }, `${clip}-${index}`)) }), _jsx(BooleanField, { name: "positional", label: "Positional", values: component.properties, onChange: onUpdate, fallback: false }), positional ? (_jsxs(_Fragment, { children: [_jsx(NumberField, { name: "refDistance", label: "Ref Distance", values: component.properties, onChange: onUpdate, fallback: 1, min: 0.01, step: 0.1 }), _jsx(NumberField, { name: "maxDistance", label: "Max Distance", values: component.properties, onChange: onUpdate, fallback: 24, min: 0.01, step: 0.1 }), _jsx(NumberField, { name: "rolloffFactor", label: "Rolloff", values: component.properties, onChange: onUpdate, fallback: 1, min: 0, step: 0.1 }), _jsx(SelectField, { name: "distanceModel", label: "Distance Model", values: component.properties, onChange: onUpdate, fallback: "inverse", options: [
138
120
  { value: 'inverse', label: 'Inverse' },
139
121
  { value: 'linear', label: 'Linear' },
140
122
  { value: 'exponential', label: 'Exponential' },
@@ -3,15 +3,16 @@ import { Label, Vector3Input } from "./Input";
3
3
  import { useEditorContext } from "../PrefabEditor";
4
4
  import { colors } from "../styles";
5
5
  const buttonStyle = {
6
- padding: '4px 8px',
6
+ padding: '2px 6px',
7
7
  background: colors.bgSurface,
8
8
  color: colors.text,
9
9
  border: `1px solid ${colors.border}`,
10
- borderRadius: 3,
10
+ borderRadius: 0,
11
11
  cursor: 'pointer',
12
12
  font: 'inherit',
13
13
  fontSize: 11,
14
14
  flex: 1,
15
+ minHeight: 22,
15
16
  };
16
17
  function TransformModeSelector({ transformMode, setTransformMode }) {
17
18
  return (_jsxs("div", { style: { marginBottom: 8 }, children: [_jsx(Label, { children: "Transform Mode" }), _jsx("div", { style: { display: 'flex', gap: 6 }, children: ["translate", "rotate", "scale"].map(mode => {
@@ -29,7 +30,7 @@ const snapLockBtnStyle = {
29
30
  background: 'none',
30
31
  border: 'none',
31
32
  cursor: 'pointer',
32
- padding: '0 2px',
33
+ padding: 0,
33
34
  fontSize: 12,
34
35
  lineHeight: 1,
35
36
  color: colors.textMuted,
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useState } from 'react';
3
- import { colors } from '../styles';
3
+ import { base, colors, ui } from '../styles';
4
4
  import { FieldGroup, FieldRow, NumberInput } from './Input';
5
5
  export function mergeWithDefaults(defaults, properties) {
6
6
  const merged = Object.assign({}, defaults);
@@ -15,15 +15,7 @@ export function mergeWithDefaults(defaults, properties) {
15
15
  return merged;
16
16
  }
17
17
  export function LightSection({ title, children }) {
18
- return (_jsxs("div", { style: {
19
- display: 'flex',
20
- flexDirection: 'column',
21
- gap: 8,
22
- padding: '8px 10px',
23
- border: `1px solid ${colors.border}`,
24
- borderRadius: 6,
25
- background: colors.bgSurface,
26
- }, children: [_jsx("div", { style: {
18
+ return (_jsxs("div", { style: Object.assign(Object.assign({}, ui.secondaryPanel), { display: 'flex', flexDirection: 'column', gap: 8, padding: 6 }), children: [_jsx("div", { style: {
27
19
  fontSize: 10,
28
20
  textTransform: 'uppercase',
29
21
  letterSpacing: '0.08em',
@@ -51,14 +43,5 @@ export function ShadowBiasField({ name, label, values, onChange, fallback = 0, }
51
43
  var _a;
52
44
  const value = (_a = values[name]) !== null && _a !== void 0 ? _a : fallback;
53
45
  const [step, setStep] = useState(() => getBiasStep(value));
54
- return (_jsx(FieldRow, { label: label, children: _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 6 }, children: [_jsx(NumberInput, { value: value, onChange: nextValue => onChange({ [name]: nextValue }), step: step, min: -0.1, max: 0.1, style: { width: 92 } }), _jsx("select", { value: step.toString(), onChange: event => setStep(Number(event.target.value)), style: {
55
- width: 78,
56
- backgroundColor: colors.bgInput,
57
- border: `1px solid ${colors.border}`,
58
- color: colors.text,
59
- borderRadius: 3,
60
- fontSize: 11,
61
- padding: '3px 6px',
62
- fontFamily: 'monospace',
63
- }, title: "Bias scrub step", children: shadowBiasSteps.map(option => (_jsx("option", { value: option, children: formatBiasStep(option) }, option))) })] }) }));
46
+ return (_jsx(FieldRow, { label: label, children: _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 6 }, children: [_jsx(NumberInput, { value: value, onChange: nextValue => onChange({ [name]: nextValue }), step: step, min: -0.1, max: 0.1, style: { width: 92 } }), _jsx("select", { value: step.toString(), onChange: event => setStep(Number(event.target.value)), style: Object.assign(Object.assign({}, base.input), { width: 78, fontSize: 11, fontFamily: 'monospace' }), title: "Bias scrub step", children: shadowBiasSteps.map(option => (_jsx("option", { value: option, children: formatBiasStep(option) }, option))) })] }) }));
64
47
  }
@@ -34,6 +34,11 @@ interface ToolbarStyles {
34
34
  interface ComponentCardStyles {
35
35
  container: Style;
36
36
  }
37
+ interface UtilityStyles {
38
+ secondaryPanel: Style;
39
+ compactActionButton: Style;
40
+ monoTextInput: Style;
41
+ }
37
42
  export declare const colors: {
38
43
  bg: string;
39
44
  bgSurface: string;
@@ -64,4 +69,5 @@ export declare const tree: TreeStyles;
64
69
  export declare const menu: MenuStyles;
65
70
  export declare const toolbar: ToolbarStyles;
66
71
  export declare const componentCard: ComponentCardStyles;
72
+ export declare const ui: UtilityStyles;
67
73
  export {};
@@ -1,24 +1,24 @@
1
1
  export const colors = {
2
- bg: '#1e1e1e',
3
- bgSurface: '#252526',
4
- bgLight: '#2d2d2d',
5
- bgHover: '#2a2d2e',
6
- bgInput: '#1a1a1a',
7
- border: '#3c3c3c',
8
- borderLight: '#333333',
9
- borderFaint: '#2a2a2a',
10
- text: '#cccccc',
11
- textMuted: '#999999',
12
- textDim: '#666666',
13
- accent: '#4c9eff',
14
- accentBg: 'rgba(76, 158, 255, 0.12)',
15
- accentBorder: 'rgba(76, 158, 255, 0.4)',
16
- danger: '#f44747',
17
- dangerBg: 'rgba(244, 71, 71, 0.12)',
18
- dangerBorder: 'rgba(244, 71, 71, 0.35)',
2
+ bg: '#f3f3f3',
3
+ bgSurface: '#d7d7d7',
4
+ bgLight: '#fafafa',
5
+ bgHover: '#e6e6e6',
6
+ bgInput: '#f5f5f5',
7
+ border: '#6f6f6f',
8
+ borderLight: '#9a9a9a',
9
+ borderFaint: '#b8b8b8',
10
+ text: '#2f2f2f',
11
+ textMuted: '#5f5f5f',
12
+ textDim: '#7f7f7f',
13
+ accent: '#1e6f89',
14
+ accentBg: '#a9dded',
15
+ accentBorder: '#5e5e5e',
16
+ danger: '#9c3232',
17
+ dangerBg: '#efcaca',
18
+ dangerBorder: '#6f6f6f',
19
19
  };
20
20
  export const fonts = {
21
- family: 'system-ui, -apple-system, sans-serif',
21
+ family: 'Tahoma, Verdana, sans-serif',
22
22
  size: 11,
23
23
  sizeSm: 10,
24
24
  };
@@ -28,44 +28,48 @@ export const base = {
28
28
  background: colors.bg,
29
29
  color: colors.text,
30
30
  border: `1px solid ${colors.border}`,
31
- borderRadius: 4,
32
31
  fontFamily: fonts.family,
33
32
  fontSize: fonts.size,
34
- boxShadow: '0 2px 8px rgba(0,0,0,0.4)',
33
+ borderRadius: 0,
34
+ boxShadow: 'none',
35
35
  },
36
36
  header: {
37
- padding: '7px 10px',
37
+ padding: '3px 6px',
38
38
  display: 'flex',
39
39
  alignItems: 'center',
40
40
  justifyContent: 'space-between',
41
41
  cursor: 'pointer',
42
42
  background: colors.bgLight,
43
- borderBottom: `1px solid ${colors.borderLight}`,
43
+ borderBottom: `1px solid ${colors.border}`,
44
44
  fontSize: fonts.size,
45
- fontWeight: 600,
46
- textTransform: 'uppercase',
47
- letterSpacing: 0.8,
45
+ fontWeight: 400,
48
46
  color: colors.text,
47
+ minHeight: 22,
48
+ boxSizing: 'border-box',
49
49
  },
50
50
  input: {
51
51
  width: '100%',
52
52
  background: colors.bgInput,
53
53
  border: `1px solid ${colors.border}`,
54
- borderRadius: 3,
55
- padding: '5px 8px',
54
+ borderRadius: 0,
55
+ padding: '2px 4px',
56
56
  color: colors.text,
57
57
  fontSize: fonts.size,
58
58
  outline: 'none',
59
+ minHeight: 22,
60
+ boxSizing: 'border-box',
59
61
  },
60
62
  btn: {
61
- background: colors.bgLight,
63
+ background: colors.bgSurface,
62
64
  border: `1px solid ${colors.border}`,
63
- borderRadius: 3,
64
- padding: '4px 8px',
65
+ borderRadius: 0,
66
+ padding: '2px 6px',
65
67
  color: colors.text,
66
68
  fontSize: fonts.size,
67
69
  cursor: 'pointer',
68
70
  outline: 'none',
71
+ minHeight: 22,
72
+ boxSizing: 'border-box',
69
73
  },
70
74
  btnDanger: {
71
75
  background: colors.dangerBg,
@@ -75,25 +79,22 @@ export const base = {
75
79
  label: {
76
80
  fontSize: fonts.sizeSm,
77
81
  color: colors.textMuted,
78
- marginBottom: 4,
79
- textTransform: 'uppercase',
80
- letterSpacing: 0.5,
81
- fontWeight: 500,
82
+ marginBottom: 2,
83
+ fontWeight: 400,
82
84
  },
83
85
  row: {
84
86
  display: 'flex',
85
- gap: 6,
87
+ gap: 4,
86
88
  },
87
89
  section: {
88
- paddingBottom: 8,
89
- borderBottom: `1px solid ${colors.borderLight}`,
90
+ paddingBottom: 4,
90
91
  },
91
92
  };
92
93
  // Specific panel styles
93
94
  export const inspector = {
94
- panel: Object.assign(Object.assign({}, base.panel), { position: 'absolute', top: 8, right: 8, zIndex: 20, width: 260 }),
95
+ panel: Object.assign(Object.assign({}, base.panel), { position: 'absolute', top: 8, right: 8, zIndex: 20, width: 300 }),
95
96
  content: {
96
- padding: 8,
97
+ padding: 6,
97
98
  maxHeight: '80vh',
98
99
  overflowY: 'auto',
99
100
  overflowX: 'hidden',
@@ -102,37 +103,37 @@ export const inspector = {
102
103
  boxSizing: 'border-box',
103
104
  display: 'flex',
104
105
  flexDirection: 'column',
105
- gap: 8,
106
+ gap: 4,
106
107
  },
107
108
  };
108
109
  export const tree = {
109
110
  panel: Object.assign(Object.assign({}, base.panel), { maxHeight: '85vh', display: 'flex', flexDirection: 'column', userSelect: 'none' }),
110
111
  scroll: {
111
112
  overflowY: 'auto',
112
- padding: 4,
113
+ padding: 2,
113
114
  scrollbarWidth: 'thin',
114
115
  scrollbarColor: `${colors.bgLight} transparent`,
115
116
  },
116
117
  row: {
117
118
  display: 'flex',
118
119
  alignItems: 'center',
119
- padding: '3px 6px',
120
+ padding: '2px 4px',
120
121
  borderBottomWidth: 1,
121
122
  borderBottomStyle: 'solid',
122
123
  borderBottomColor: colors.borderFaint,
123
124
  cursor: 'pointer',
124
125
  whiteSpace: 'nowrap',
125
- borderRadius: 2,
126
126
  },
127
127
  selected: {
128
128
  background: colors.accentBg,
129
129
  borderBottomColor: colors.accentBorder,
130
+ boxShadow: 'none',
130
131
  },
131
132
  iconButton: {
132
133
  background: 'none',
133
134
  border: 'none',
134
135
  cursor: 'pointer',
135
- padding: '0 4px',
136
+ padding: '0 2px',
136
137
  fontSize: 14,
137
138
  opacity: 0.7,
138
139
  color: 'inherit',
@@ -146,15 +147,15 @@ export const menu = {
146
147
  width: 'max-content',
147
148
  maxWidth: 'min(240px, calc(100vw - 16px))',
148
149
  background: colors.bgSurface,
149
- border: `1px solid ${colors.border}`,
150
- borderRadius: 4,
150
+ border: 'none',
151
151
  overflow: 'hidden',
152
- boxShadow: '0 4px 16px rgba(0,0,0,0.6)',
152
+ borderRadius: 0,
153
+ boxShadow: 'none',
153
154
  },
154
155
  item: {
155
156
  width: '100%',
156
157
  textAlign: 'left',
157
- padding: '7px 12px',
158
+ padding: '4px 8px',
158
159
  background: 'transparent',
159
160
  border: 'none',
160
161
  color: colors.text,
@@ -171,17 +172,17 @@ export const toolbar = {
171
172
  panel: {
172
173
  position: 'absolute',
173
174
  top: 8,
174
- left: '240px',
175
+ left: '232px',
175
176
  display: 'flex',
176
- gap: 6,
177
- padding: '4px 6px',
177
+ gap: 4,
178
+ padding: '2px 4px',
178
179
  background: colors.bg,
179
180
  border: `1px solid ${colors.border}`,
180
- borderRadius: 4,
181
181
  color: colors.text,
182
182
  fontFamily: fonts.family,
183
183
  fontSize: fonts.size,
184
- boxShadow: '0 2px 8px rgba(0,0,0,0.4)',
184
+ borderRadius: 0,
185
+ boxShadow: 'none',
185
186
  },
186
187
  divider: {
187
188
  width: 1,
@@ -195,10 +196,22 @@ export const toolbar = {
195
196
  // Reusable component card style for inspector sections
196
197
  export const componentCard = {
197
198
  container: {
198
- marginBottom: 8,
199
- backgroundColor: colors.bgSurface,
200
- padding: 8,
201
- borderRadius: 4,
199
+ marginBottom: 4,
200
+ backgroundColor: colors.bg,
201
+ padding: 4,
202
+ border: `1px solid ${colors.border}`,
203
+ borderRadius: 0,
204
+ boxShadow: 'none',
205
+ },
206
+ };
207
+ export const ui = {
208
+ secondaryPanel: {
209
+ background: colors.bgSurface,
202
210
  border: `1px solid ${colors.border}`,
211
+ borderRadius: 0,
212
+ padding: 4,
213
+ boxSizing: 'border-box',
203
214
  },
215
+ compactActionButton: Object.assign(Object.assign({}, base.btn), { width: 28, minWidth: 28, padding: 0, flexShrink: 0 }),
216
+ monoTextInput: Object.assign(Object.assign({}, base.input), { fontFamily: 'monospace' }),
204
217
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-three-game",
3
- "version": "0.0.89",
3
+ "version": "0.0.91",
4
4
  "description": "high performance 3D game engine built in React",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",