react-three-game 0.0.11 → 0.0.13

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.
@@ -136,7 +136,7 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
136
136
  const hasChildren = node.children && node.children.length > 0;
137
137
  return (_jsxs("div", { className: "select-none", children: [_jsxs("div", { className: `flex items-center py-0.5 px-1 cursor-pointer border-b border-cyan-500/10 ${isSelected ? 'bg-cyan-500/30 hover:bg-cyan-500/40 border-cyan-400/30' : 'hover:bg-cyan-500/10'}`, style: { paddingLeft: `${depth * 8 + 4}px` }, onClick: (e) => { e.stopPropagation(); setSelectedId(node.id); }, onContextMenu: (e) => handleContextMenu(e, node.id), draggable: node.id !== prefabData.root.id, onDragStart: (e) => handleDragStart(e, node.id), onDragOver: (e) => handleDragOver(e, node.id), onDrop: (e) => handleDrop(e, node.id), children: [_jsx("span", { className: `mr-0.5 w-3 text-center text-cyan-400/50 hover:text-cyan-400 cursor-pointer text-[8px] ${hasChildren ? '' : 'invisible'}`, onClick: (e) => hasChildren && toggleCollapse(e, node.id), children: isCollapsed ? '▶' : '▼' }), _jsx("span", { className: "text-[10px] truncate font-mono text-cyan-300", children: node.id })] }), !isCollapsed && node.children && (_jsx("div", { children: node.children.map(child => renderNode(child, depth + 1)) }))] }, node.id));
138
138
  };
139
- return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "bg-black/70 backdrop-blur-sm text-white border border-cyan-500/30 max-h-[85vh] overflow-y-auto flex flex-col", style: { width: isTreeCollapsed ? 'auto' : '14rem' }, onClick: closeContextMenu, children: [_jsxs("div", { className: "px-1.5 py-1 font-mono text-[10px] bg-cyan-500/10 border-b border-cyan-500/30 sticky top-0 uppercase tracking-wider text-cyan-400/80 cursor-pointer hover:bg-cyan-500/20 flex items-center justify-between", onClick: (e) => { e.stopPropagation(); setIsTreeCollapsed(!isTreeCollapsed); }, children: [_jsx("span", { children: "Prefab Graph" }), _jsx("span", { className: "text-[8px]", children: isTreeCollapsed ? '▶' : '◀' })] }), !isTreeCollapsed && (_jsx("div", { className: "flex-1 py-0.5", children: renderNode(prefabData.root) }))] }), contextMenu && (_jsxs("div", { className: "fixed bg-black/90 backdrop-blur-sm border border-cyan-500/40 z-50 min-w-[100px]", style: { top: contextMenu.y, left: contextMenu.x }, onClick: (e) => e.stopPropagation(), children: [_jsx("button", { className: "w-full text-left px-2 py-1 hover:bg-cyan-500/20 text-[10px] text-cyan-300 font-mono border-b border-cyan-500/20", onClick: () => handleAddChild(contextMenu.nodeId), onPointerLeave: closeContextMenu, children: "Add Child" }), contextMenu.nodeId !== prefabData.root.id && (_jsxs(_Fragment, { children: [_jsx("button", { className: "w-full text-left px-2 py-1 hover:bg-cyan-500/20 text-[10px] text-cyan-300 font-mono border-b border-cyan-500/20", onClick: () => handleDuplicate(contextMenu.nodeId), children: "Duplicate" }), _jsx("button", { className: "w-full text-left px-2 py-1 hover:bg-red-500/20 text-[10px] text-red-400 font-mono", onClick: () => handleDelete(contextMenu.nodeId), children: "Delete" })] }))] }))] }));
139
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "bg-black/70 backdrop-blur-sm text-white border border-cyan-500/30 max-h-[85vh] overflow-y-auto flex flex-col", style: { width: isTreeCollapsed ? 'auto' : '14rem' }, onClick: closeContextMenu, children: [_jsxs("div", { className: "px-1.5 py-1 font-mono text-[10px] bg-cyan-500/10 border-b border-cyan-500/30 sticky top-0 uppercase tracking-wider text-cyan-400/80 cursor-pointer hover:bg-cyan-500/20 flex items-center justify-between", onClick: (e) => { e.stopPropagation(); setIsTreeCollapsed(!isTreeCollapsed); }, children: [_jsx("span", { children: "Prefab Graph" }), _jsx("span", { className: "text-[8px]", children: isTreeCollapsed ? '▶' : '◀' })] }), !isTreeCollapsed && (_jsx("div", { className: "flex-1 py-0.5", children: renderNode(prefabData.root) }))] }), contextMenu && (_jsxs("div", { className: "fixed bg-black/90 backdrop-blur-sm border border-cyan-500/40 z-50 min-w-[100px]", style: { top: contextMenu.y, left: contextMenu.x }, onClick: (e) => e.stopPropagation(), onPointerLeave: closeContextMenu, children: [_jsx("button", { className: "w-full text-left px-2 py-1 hover:bg-cyan-500/20 text-[10px] text-cyan-300 font-mono border-b border-cyan-500/20", onClick: () => handleAddChild(contextMenu.nodeId), children: "Add Child" }), contextMenu.nodeId !== prefabData.root.id && (_jsxs(_Fragment, { children: [_jsx("button", { className: "w-full text-left px-2 py-1 hover:bg-cyan-500/20 text-[10px] text-cyan-300 font-mono border-b border-cyan-500/20", onClick: () => handleDuplicate(contextMenu.nodeId), children: "Duplicate" }), _jsx("button", { className: "w-full text-left px-2 py-1 hover:bg-red-500/20 text-[10px] text-red-400 font-mono", onClick: () => handleDelete(contextMenu.nodeId), children: "Delete" })] }))] }))] }));
140
140
  }
141
141
  // --- Helpers ---
142
142
  function findNode(root, id) {
@@ -211,10 +211,12 @@ function renderCoreNode(gameObject, ctx, parentMatrix) {
211
211
  };
212
212
  const allComponentViews = gameObject.components
213
213
  ? Object.entries(gameObject.components)
214
- .filter(([key]) => key !== 'geometry' && key !== 'material' && key !== 'model')
214
+ .filter(([key]) => key !== 'geometry' && key !== 'material' && key !== 'model' && key !== 'transform' && key !== 'physics')
215
215
  .map(([key, comp]) => {
216
- const def = getComponent(key);
217
- if (!def || !def.View || !comp)
216
+ if (!comp || !comp.type)
217
+ return null;
218
+ const def = getComponent(comp.type);
219
+ if (!def || !def.View)
218
220
  return null;
219
221
  return _jsx(def.View, Object.assign({ properties: comp.properties }, contextProps), key);
220
222
  })
@@ -1,19 +1,33 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  function SpotLightComponentEditor({ component, onUpdate }) {
3
- return _jsxs("div", { className: "flex flex-col", children: [_jsxs("div", { className: "mb-1", children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5", children: "Color" }), _jsxs("div", { className: "flex gap-0.5", children: [_jsx("input", { type: "color", className: "h-5 w-5 bg-transparent border-none cursor-pointer", value: component.properties.color, onChange: e => onUpdate({ 'color': e.target.value }) }), _jsx("input", { type: "text", className: "flex-1 bg-black/40 border border-cyan-500/30 px-1 py-0.5 text-[10px] text-cyan-300 font-mono focus:outline-none focus:border-cyan-400/50", value: component.properties.color, onChange: e => onUpdate({ 'color': e.target.value }) })] })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5", children: "Intensity" }), _jsx("input", { type: "number", step: "0.1", className: "w-full bg-black/40 border border-cyan-500/30 px-1 py-0.5 text-[10px] text-cyan-300 font-mono focus:outline-none focus:border-cyan-400/50", value: component.properties.intensity, onChange: e => onUpdate({ 'intensity': parseFloat(e.target.value) }) })] })] });
3
+ var _a, _b, _c, _d, _e, _f;
4
+ // Provide default values to prevent NaN
5
+ const props = {
6
+ color: (_a = component.properties.color) !== null && _a !== void 0 ? _a : '#ffffff',
7
+ intensity: (_b = component.properties.intensity) !== null && _b !== void 0 ? _b : 1.0,
8
+ angle: (_c = component.properties.angle) !== null && _c !== void 0 ? _c : Math.PI / 6,
9
+ penumbra: (_d = component.properties.penumbra) !== null && _d !== void 0 ? _d : 0.5,
10
+ distance: (_e = component.properties.distance) !== null && _e !== void 0 ? _e : 100,
11
+ castShadow: (_f = component.properties.castShadow) !== null && _f !== void 0 ? _f : true
12
+ };
13
+ return _jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5", children: "Color" }), _jsxs("div", { className: "flex gap-0.5", children: [_jsx("input", { type: "color", className: "h-5 w-5 bg-transparent border-none cursor-pointer", value: props.color, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'color': e.target.value })) }), _jsx("input", { type: "text", className: "flex-1 bg-black/40 border border-cyan-500/30 px-1 py-0.5 text-[10px] text-cyan-300 font-mono focus:outline-none focus:border-cyan-400/50", value: props.color, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'color': e.target.value })) })] })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5", children: "Intensity" }), _jsx("input", { type: "number", step: "0.1", className: "w-full bg-black/40 border border-cyan-500/30 px-1 py-0.5 text-[10px] text-cyan-300 font-mono focus:outline-none focus:border-cyan-400/50", value: props.intensity, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'intensity': parseFloat(e.target.value) })) })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5", children: "Angle" }), _jsx("input", { type: "number", step: "0.1", min: "0", max: Math.PI, className: "w-full bg-black/40 border border-cyan-500/30 px-1 py-0.5 text-[10px] text-cyan-300 font-mono focus:outline-none focus:border-cyan-400/50", value: props.angle, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'angle': parseFloat(e.target.value) })) })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5", children: "Penumbra" }), _jsx("input", { type: "number", step: "0.1", min: "0", max: "1", className: "w-full bg-black/40 border border-cyan-500/30 px-1 py-0.5 text-[10px] text-cyan-300 font-mono focus:outline-none focus:border-cyan-400/50", value: props.penumbra, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'penumbra': parseFloat(e.target.value) })) })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5", children: "Distance" }), _jsx("input", { type: "number", step: "1", min: "0", className: "w-full bg-black/40 border border-cyan-500/30 px-1 py-0.5 text-[10px] text-cyan-300 font-mono focus:outline-none focus:border-cyan-400/50", value: props.distance, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'distance': parseFloat(e.target.value) })) })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5", children: "Cast Shadow" }), _jsx("input", { type: "checkbox", className: "h-4 w-4 bg-black/40 border border-cyan-500/30 cursor-pointer", checked: props.castShadow, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'castShadow': e.target.checked })) })] })] });
4
14
  }
5
15
  // The view component for SpotLight
6
16
  function SpotLightView({ properties }) {
7
- // You can expand this with more spotlight properties as needed
8
- return _jsx("spotLight", { color: properties.color, intensity: properties.intensity });
17
+ var _a, _b, _c, _d, _e, _f;
18
+ // Provide defaults in case properties are missing
19
+ const color = (_a = properties.color) !== null && _a !== void 0 ? _a : '#ffffff';
20
+ const intensity = (_b = properties.intensity) !== null && _b !== void 0 ? _b : 1.0;
21
+ const angle = (_c = properties.angle) !== null && _c !== void 0 ? _c : Math.PI / 6;
22
+ const penumbra = (_d = properties.penumbra) !== null && _d !== void 0 ? _d : 0.5;
23
+ const distance = (_e = properties.distance) !== null && _e !== void 0 ? _e : 100;
24
+ const castShadow = (_f = properties.castShadow) !== null && _f !== void 0 ? _f : true;
25
+ return (_jsx(_Fragment, { children: _jsx("spotLight", { color: color, intensity: intensity, angle: angle, penumbra: penumbra, distance: distance, castShadow: castShadow, "target-position": [0, 0, 0], position: [0, 0, 0] }) }));
9
26
  }
10
27
  const SpotLightComponent = {
11
28
  name: 'SpotLight',
12
29
  Editor: SpotLightComponentEditor,
13
30
  View: SpotLightView,
14
- defaultProperties: {
15
- color: '#ffffff',
16
- intensity: 1.0
17
- }
31
+ defaultProperties: {}
18
32
  };
19
33
  export default SpotLightComponent;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-three-game",
3
- "version": "0.0.11",
3
+ "version": "0.0.13",
4
4
  "description": "Batteries included React Three Fiber game engine",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
@@ -206,11 +206,11 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
206
206
  className="fixed bg-black/90 backdrop-blur-sm border border-cyan-500/40 z-50 min-w-[100px]"
207
207
  style={{ top: contextMenu.y, left: contextMenu.x }}
208
208
  onClick={(e) => e.stopPropagation()}
209
+ onPointerLeave={closeContextMenu}
209
210
  >
210
211
  <button
211
212
  className="w-full text-left px-2 py-1 hover:bg-cyan-500/20 text-[10px] text-cyan-300 font-mono border-b border-cyan-500/20"
212
213
  onClick={() => handleAddChild(contextMenu.nodeId)}
213
- onPointerLeave={closeContextMenu}
214
214
  >
215
215
  Add Child
216
216
  </button>
@@ -340,10 +340,11 @@ function renderCoreNode(gameObject: GameObjectType, ctx: any, parentMatrix: Matr
340
340
  };
341
341
  const allComponentViews = gameObject.components
342
342
  ? Object.entries(gameObject.components)
343
- .filter(([key]) => key !== 'geometry' && key !== 'material' && key !== 'model')
343
+ .filter(([key]) => key !== 'geometry' && key !== 'material' && key !== 'model' && key !== 'transform' && key !== 'physics')
344
344
  .map(([key, comp]) => {
345
- const def = getComponent(key);
346
- if (!def || !def.View || !comp) return null;
345
+ if (!comp || !comp.type) return null;
346
+ const def = getComponent(comp.type);
347
+ if (!def || !def.View) return null;
347
348
  return <def.View key={key} properties={comp.properties} {...contextProps} />;
348
349
  })
349
350
  : null;
@@ -1,22 +1,33 @@
1
1
 
2
2
  import { Component } from "./ComponentRegistry";
3
+ import { useRef } from "react";
3
4
 
4
5
  function SpotLightComponentEditor({ component, onUpdate }: { component: any; onUpdate: (newComp: any) => void }) {
5
- return <div className="flex flex-col">
6
- <div className="mb-1">
6
+ // Provide default values to prevent NaN
7
+ const props = {
8
+ color: component.properties.color ?? '#ffffff',
9
+ intensity: component.properties.intensity ?? 1.0,
10
+ angle: component.properties.angle ?? Math.PI / 6,
11
+ penumbra: component.properties.penumbra ?? 0.5,
12
+ distance: component.properties.distance ?? 100,
13
+ castShadow: component.properties.castShadow ?? true
14
+ };
15
+
16
+ return <div className="flex flex-col gap-2">
17
+ <div>
7
18
  <label className="block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5">Color</label>
8
19
  <div className="flex gap-0.5">
9
20
  <input
10
21
  type="color"
11
22
  className="h-5 w-5 bg-transparent border-none cursor-pointer"
12
- value={component.properties.color}
13
- onChange={e => onUpdate({ 'color': e.target.value })}
23
+ value={props.color}
24
+ onChange={e => onUpdate({ ...component.properties, 'color': e.target.value })}
14
25
  />
15
26
  <input
16
27
  type="text"
17
28
  className="flex-1 bg-black/40 border border-cyan-500/30 px-1 py-0.5 text-[10px] text-cyan-300 font-mono focus:outline-none focus:border-cyan-400/50"
18
- value={component.properties.color}
19
- onChange={e => onUpdate({ 'color': e.target.value })}
29
+ value={props.color}
30
+ onChange={e => onUpdate({ ...component.properties, 'color': e.target.value })}
20
31
  />
21
32
  </div>
22
33
  </div>
@@ -26,8 +37,52 @@ function SpotLightComponentEditor({ component, onUpdate }: { component: any; onU
26
37
  type="number"
27
38
  step="0.1"
28
39
  className="w-full bg-black/40 border border-cyan-500/30 px-1 py-0.5 text-[10px] text-cyan-300 font-mono focus:outline-none focus:border-cyan-400/50"
29
- value={component.properties.intensity}
30
- onChange={e => onUpdate({ 'intensity': parseFloat(e.target.value) })}
40
+ value={props.intensity}
41
+ onChange={e => onUpdate({ ...component.properties, 'intensity': parseFloat(e.target.value) })}
42
+ />
43
+ </div>
44
+ <div>
45
+ <label className="block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5">Angle</label>
46
+ <input
47
+ type="number"
48
+ step="0.1"
49
+ min="0"
50
+ max={Math.PI}
51
+ className="w-full bg-black/40 border border-cyan-500/30 px-1 py-0.5 text-[10px] text-cyan-300 font-mono focus:outline-none focus:border-cyan-400/50"
52
+ value={props.angle}
53
+ onChange={e => onUpdate({ ...component.properties, 'angle': parseFloat(e.target.value) })}
54
+ />
55
+ </div>
56
+ <div>
57
+ <label className="block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5">Penumbra</label>
58
+ <input
59
+ type="number"
60
+ step="0.1"
61
+ min="0"
62
+ max="1"
63
+ className="w-full bg-black/40 border border-cyan-500/30 px-1 py-0.5 text-[10px] text-cyan-300 font-mono focus:outline-none focus:border-cyan-400/50"
64
+ value={props.penumbra}
65
+ onChange={e => onUpdate({ ...component.properties, 'penumbra': parseFloat(e.target.value) })}
66
+ />
67
+ </div>
68
+ <div>
69
+ <label className="block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5">Distance</label>
70
+ <input
71
+ type="number"
72
+ step="1"
73
+ min="0"
74
+ className="w-full bg-black/40 border border-cyan-500/30 px-1 py-0.5 text-[10px] text-cyan-300 font-mono focus:outline-none focus:border-cyan-400/50"
75
+ value={props.distance}
76
+ onChange={e => onUpdate({ ...component.properties, 'distance': parseFloat(e.target.value) })}
77
+ />
78
+ </div>
79
+ <div>
80
+ <label className="block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5">Cast Shadow</label>
81
+ <input
82
+ type="checkbox"
83
+ className="h-4 w-4 bg-black/40 border border-cyan-500/30 cursor-pointer"
84
+ checked={props.castShadow}
85
+ onChange={e => onUpdate({ ...component.properties, 'castShadow': e.target.checked })}
31
86
  />
32
87
  </div>
33
88
  </div>;
@@ -36,18 +91,35 @@ function SpotLightComponentEditor({ component, onUpdate }: { component: any; onU
36
91
 
37
92
  // The view component for SpotLight
38
93
  function SpotLightView({ properties }: { properties: any }) {
39
- // You can expand this with more spotlight properties as needed
40
- return <spotLight color={properties.color} intensity={properties.intensity} />;
94
+ // Provide defaults in case properties are missing
95
+ const color = properties.color ?? '#ffffff';
96
+ const intensity = properties.intensity ?? 1.0;
97
+ const angle = properties.angle ?? Math.PI / 6;
98
+ const penumbra = properties.penumbra ?? 0.5;
99
+ const distance = properties.distance ?? 100;
100
+ const castShadow = properties.castShadow ?? true;
101
+
102
+ return (
103
+ <>
104
+ <spotLight
105
+ color={color}
106
+ intensity={intensity}
107
+ angle={angle}
108
+ penumbra={penumbra}
109
+ distance={distance}
110
+ castShadow={castShadow}
111
+ target-position={[0, 0, 0]}
112
+ position={[0, 0, 0]}
113
+ />
114
+ </>
115
+ );
41
116
  }
42
117
 
43
118
  const SpotLightComponent: Component = {
44
119
  name: 'SpotLight',
45
120
  Editor: SpotLightComponentEditor,
46
121
  View: SpotLightView,
47
- defaultProperties: {
48
- color: '#ffffff',
49
- intensity: 1.0
50
- }
122
+ defaultProperties: {}
51
123
  };
52
124
 
53
125
  export default SpotLightComponent;