react-three-game 0.0.48 → 0.0.50

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.
@@ -15,7 +15,6 @@ interface Prefab { id?: string; name?: string; root: GameObject; }
15
15
  interface GameObject {
16
16
  id: string; // Use crypto.randomUUID() for new nodes
17
17
  disabled?: boolean;
18
- hidden?: boolean;
19
18
  components?: Record<string, { type: string; properties: any }>;
20
19
  children?: GameObject[];
21
20
  }
@@ -38,11 +37,17 @@ const MyComponent: Component = {
38
37
  | File | Purpose |
39
38
  |------|---------|
40
39
  | `src/index.ts` | All public exports - add new features here |
41
- | `src/tools/prefabeditor/PrefabRoot.tsx` | Recursive scene renderer, world matrix math |
42
- | `src/tools/prefabeditor/PrefabEditor.tsx` | Edit/play mode, physics pause, JSON import/export |
40
+ | `src/tools/prefabeditor/PrefabRoot.tsx` | Pure renderer - renders prefab as Three.js objects for R3F integration |
41
+ | `src/tools/prefabeditor/PrefabEditor.tsx` | Managed scene with editor UI and play/pause controls for physics |
43
42
  | `src/tools/prefabeditor/utils.ts` | Tree helpers: `findNode`, `updateNode`, `deleteNode`, `cloneNode` |
44
43
  | `src/shared/GameCanvas.tsx` | WebGPU renderer setup (use `MeshStandardNodeMaterial`) |
45
44
 
45
+ ## Usage Modes
46
+
47
+ **GameCanvas + PrefabRoot**: Pure renderer for embedding prefab data in standard R3F applications. Minimal wrapper - just renders the prefab as Three.js objects. Requires manual `<Physics>` setup. Physics always active. Use this to integrate prefabs into larger R3F scenes.
48
+
49
+ **PrefabEditor**: Managed scene with editor UI and play/pause controls for physics. Full authoring tool for level design and prototyping. Includes canvas, physics, transform gizmos, and inspector. Physics only runs in play mode. Can pass R3F components as children.
50
+
46
51
  ## Critical Patterns
47
52
 
48
53
  ### Tree Manipulation (Immutable)
package/README.md CHANGED
@@ -14,7 +14,13 @@ npm i react-three-game @react-three/fiber @react-three/rapier three
14
14
  npx skills add https://github.com/prnthh/react-three-game-skill
15
15
  ```
16
16
 
17
- ## Usage
17
+ ## Usage Modes
18
+
19
+ **GameCanvas + PrefabRoot**: Pure renderer for embedding prefab data in standard R3F applications. Minimal wrapper - just renders the prefab as Three.js objects. Requires manual `<Physics>` setup. Physics always active. Use this to integrate prefabs into larger R3F scenes.
20
+
21
+ **PrefabEditor**: Managed scene with editor UI and play/pause controls for physics. Full authoring tool for level design and prototyping. Includes canvas, physics, transform gizmos, and inspector. Physics only runs in play mode. Can pass R3F components as children.
22
+
23
+ ## Basic Usage
18
24
 
19
25
  ```jsx
20
26
  import { Physics } from '@react-three/rapier';
@@ -57,7 +63,6 @@ import { GameCanvas, PrefabRoot } from 'react-three-game';
57
63
  interface GameObject {
58
64
  id: string;
59
65
  disabled?: boolean;
60
- hidden?: boolean;
61
66
  components?: Record<string, { type: string; properties: any }>;
62
67
  children?: GameObject[];
63
68
  }
@@ -70,7 +75,7 @@ interface GameObject {
70
75
  | Transform | `position`, `rotation`, `scale` — all `[x,y,z]` arrays, rotation in radians |
71
76
  | Geometry | `geometryType`: box/sphere/plane/cylinder, `args`: dimension array |
72
77
  | Material | `color`, `texture?`, `metalness?`, `roughness?` |
73
- | Physics | `type`: dynamic/fixed |
78
+ | Physics | `type`: dynamic/fixed/kinematicPosition/kinematicVelocity, `mass?`, `restitution?` (bounciness), `friction?`, plus any Rapier props |
74
79
  | Model | `filename` (GLB/FBX path), `instanced?` for GPU batching |
75
80
  | SpotLight | `color`, `intensity`, `angle`, `penumbra` |
76
81
 
@@ -133,14 +138,21 @@ The `FieldRenderer` component auto-generates editor UI from a field schema:
133
138
  }
134
139
  ```
135
140
 
136
- ## Visual Editor
141
+ ## Prefab Editor
137
142
 
138
143
  ```jsx
139
144
  import { PrefabEditor } from 'react-three-game';
145
+
146
+ // Standalone editor
140
147
  <PrefabEditor initialPrefab={sceneData} onPrefabChange={setSceneData} />
148
+
149
+ // With custom R3F components
150
+ <PrefabEditor initialPrefab={sceneData}>
151
+ <CustomComponent />
152
+ </PrefabEditor>
141
153
  ```
142
154
 
143
- Keys: **T**ranslate / **R**otate / **S**cale. Drag tree nodes to reparent. Import/export JSON.
155
+ Keys: **T**ranslate / **R**otate / **S**cale. Drag tree nodes to reparent. Import/export JSON. Physics only runs in play mode.
144
156
 
145
157
  ## Internals
146
158
 
@@ -151,9 +163,13 @@ Keys: **T**ranslate / **R**otate / **S**cale. Drag tree nodes to reparent. Impor
151
163
  ## Tree Utilities
152
164
 
153
165
  ```typescript
154
- import { findNode, updateNode, deleteNode, cloneNode } from 'react-three-game';
166
+ import { findNode, updateNode, updateNodeById, deleteNode, cloneNode, exportGLBData } from 'react-three-game';
155
167
 
156
- const updated = updateNode(root, nodeId, n => ({ ...n, disabled: true }));
168
+ const node = findNode(root, nodeId);
169
+ const updated = updateNode(root, nodeId, n => ({ ...n, disabled: true })); // or updateNodeById
170
+ const afterDelete = deleteNode(root, nodeId);
171
+ const cloned = cloneNode(node);
172
+ const glbData = await exportGLBData(sceneRoot); // export scene to GLB ArrayBuffer
157
173
  ```
158
174
 
159
175
  ## Development
@@ -18,8 +18,6 @@ export interface GroundOptions {
18
18
  repeatCount?: [number, number];
19
19
  /** Physics body type. Defaults to "fixed". */
20
20
  physicsType?: "fixed" | "dynamic" | "kinematic";
21
- /** Set true to hide the node. */
22
- hidden?: boolean;
23
21
  /** Set true to disable the node. */
24
22
  disabled?: boolean;
25
23
  }
@@ -8,11 +8,10 @@
8
8
  * - Physics (fixed by default)
9
9
  */
10
10
  export function ground(options = {}) {
11
- const { id = "ground", size = 50, position = [0, 0, 0], rotation = [-Math.PI / 2, 0, 0], scale = [1, 1, 1], color = "white", texture, repeat = texture ? true : false, repeatCount = [25, 25], physicsType = "fixed", hidden = false, disabled = false, } = options;
11
+ const { id = "ground", size = 50, position = [0, 0, 0], rotation = [-Math.PI / 2, 0, 0], scale = [1, 1, 1], color = "white", texture, repeat = texture ? true : false, repeatCount = [25, 25], physicsType = "fixed", disabled = false, } = options;
12
12
  return {
13
13
  id,
14
14
  disabled,
15
- hidden,
16
15
  components: {
17
16
  transform: {
18
17
  type: "Transform",
@@ -1,8 +1,10 @@
1
1
  import { Group, Matrix4, Object3D, Texture } from "three";
2
2
  import { ThreeEvent } from "@react-three/fiber";
3
3
  import { Prefab, GameObject as GameObjectType } from "./types";
4
+ import type { RapierRigidBody } from "@react-three/rapier";
4
5
  export interface PrefabRootRef {
5
6
  root: Group | null;
7
+ rigidBodyRefs: Map<string, RapierRigidBody | null>;
6
8
  }
7
9
  export declare const PrefabRoot: import("react").ForwardRefExoticComponent<{
8
10
  editMode?: boolean;
@@ -20,6 +22,7 @@ interface RendererProps {
20
22
  onSelect?: (id: string) => void;
21
23
  onClick?: (event: ThreeEvent<PointerEvent>, entity: GameObjectType) => void;
22
24
  registerRef: (id: string, obj: Object3D | null) => void;
25
+ registerRigidBodyRef: (id: string, rb: RapierRigidBody | null) => void;
23
26
  loadedModels: Record<string, Object3D>;
24
27
  loadedTextures: Record<string, Texture>;
25
28
  editMode?: boolean;
@@ -30,16 +30,21 @@ export const PrefabRoot = forwardRef(({ editMode, data, onPrefabChange, selected
30
30
  const [textures, setTextures] = useState({});
31
31
  const loading = useRef(new Set());
32
32
  const objectRefs = useRef({});
33
+ const rigidBodyRefs = useRef(new Map());
33
34
  const [selectedObject, setSelectedObject] = useState(null);
34
35
  const rootRef = useRef(null);
35
36
  useImperativeHandle(ref, () => ({
36
- root: rootRef.current
37
+ root: rootRef.current,
38
+ rigidBodyRefs: rigidBodyRefs.current
37
39
  }), []);
38
40
  const registerRef = useCallback((id, obj) => {
39
41
  objectRefs.current[id] = obj;
40
42
  if (id === selectedId)
41
43
  setSelectedObject(obj);
42
44
  }, [selectedId]);
45
+ const registerRigidBodyRef = useCallback((id, rb) => {
46
+ rigidBodyRefs.current.set(id, rb);
47
+ }, []);
43
48
  useEffect(() => {
44
49
  const originalError = console.error;
45
50
  console.error = (...args) => {
@@ -103,12 +108,12 @@ export const PrefabRoot = forwardRef(({ editMode, data, onPrefabChange, selected
103
108
  });
104
109
  });
105
110
  }, [data, models, textures]);
106
- return (_jsxs("group", { ref: rootRef, children: [_jsx(GameInstanceProvider, { models: models, selectedId: selectedId, editMode: editMode, onSelect: editMode ? onSelect : undefined, registerRef: registerRef, children: _jsx(GameObjectRenderer, { gameObject: data.root, selectedId: selectedId, onSelect: editMode ? onSelect : undefined, onClick: onClick, registerRef: registerRef, loadedModels: models, loadedTextures: textures, editMode: editMode, parentMatrix: IDENTITY }) }), editMode && (_jsxs(_Fragment, { children: [_jsx(MapControls, { makeDefault: true }), selectedObject && (_jsx(TransformControls, { object: selectedObject, mode: transformMode, space: "local", onObjectChange: onTransformChange, translationSnap: snapResolution > 0 ? snapResolution : undefined, rotationSnap: snapResolution > 0 ? snapResolution : undefined, scaleSnap: snapResolution > 0 ? snapResolution : undefined }, `transform-${snapResolution}`))] }))] }));
111
+ return (_jsxs("group", { ref: rootRef, children: [_jsx(GameInstanceProvider, { models: models, selectedId: selectedId, editMode: editMode, onSelect: editMode ? onSelect : undefined, registerRef: registerRef, children: _jsx(GameObjectRenderer, { gameObject: data.root, selectedId: selectedId, onSelect: editMode ? onSelect : undefined, onClick: onClick, registerRef: registerRef, registerRigidBodyRef: registerRigidBodyRef, loadedModels: models, loadedTextures: textures, editMode: editMode, parentMatrix: IDENTITY }) }), editMode && (_jsxs(_Fragment, { children: [_jsx(MapControls, { makeDefault: true }), selectedObject && (_jsx(TransformControls, { object: selectedObject, mode: transformMode, space: "local", onObjectChange: onTransformChange, translationSnap: snapResolution > 0 ? snapResolution : undefined, rotationSnap: snapResolution > 0 ? snapResolution : undefined, scaleSnap: snapResolution > 0 ? snapResolution : undefined }, `transform-${snapResolution}`))] }))] }));
107
112
  });
108
113
  export function GameObjectRenderer(props) {
109
114
  var _a, _b, _c;
110
115
  const node = props.gameObject;
111
- if (!node || node.hidden || node.disabled)
116
+ if (!node || node.disabled)
112
117
  return null;
113
118
  const isInstanced = (_c = (_b = (_a = node.components) === null || _a === void 0 ? void 0 : _a.model) === null || _b === void 0 ? void 0 : _b.properties) === null || _c === void 0 ? void 0 : _c.instanced;
114
119
  const prevInstancedRef = useRef(undefined);
@@ -160,7 +165,7 @@ function InstancedNode({ gameObject, parentMatrix = IDENTITY, editMode, register
160
165
  }
161
166
  return (_jsx(GameInstance, { id: gameObject.id, modelUrl: (_k = (_j = (_h = gameObject.components) === null || _h === void 0 ? void 0 : _h.model) === null || _j === void 0 ? void 0 : _j.properties) === null || _k === void 0 ? void 0 : _k.filename, position: worldPosition, rotation: worldRotation, scale: worldScale, physics: physicsProps }));
162
167
  }
163
- function StandardNode({ gameObject, selectedId, onSelect, onClick, registerRef, loadedModels, loadedTextures, editMode, parentMatrix = IDENTITY, }) {
168
+ function StandardNode({ gameObject, selectedId, onSelect, onClick, registerRef, registerRigidBodyRef, loadedModels, loadedTextures, editMode, parentMatrix = IDENTITY, }) {
164
169
  var _a, _b, _c, _d, _e, _f;
165
170
  const groupRef = useRef(null);
166
171
  const helperRef = useRef(null);
@@ -193,12 +198,12 @@ function StandardNode({ gameObject, selectedId, onSelect, onClick, registerRef,
193
198
  const physicsDef = hasPhysics ? getComponent("Physics") : null;
194
199
  const isInstanced = (_e = (_d = (_c = gameObject.components) === null || _c === void 0 ? void 0 : _c.model) === null || _d === void 0 ? void 0 : _d.properties) === null || _e === void 0 ? void 0 : _e.instanced;
195
200
  const physicsKey = `physics_${gameObject.id}_${isInstanced ? 'instanced' : 'standard'}`;
196
- const inner = (_jsxs("group", { onPointerDown: editMode ? onDown : undefined, onPointerMove: editMode ? () => (clickValid.current = false) : undefined, onPointerUp: editMode ? onUp : undefined, children: [renderCoreNode(gameObject, { loadedModels, loadedTextures, editMode, registerRef }, parentMatrix), (_f = gameObject.children) === null || _f === void 0 ? void 0 : _f.map(child => (_jsx(GameObjectRenderer, { gameObject: child, selectedId: selectedId, onSelect: onSelect, onClick: onClick, registerRef: registerRef, loadedModels: loadedModels, loadedTextures: loadedTextures, editMode: editMode, parentMatrix: world }, child.id)))] }));
201
+ const inner = (_jsxs("group", { onPointerDown: editMode ? onDown : undefined, onPointerMove: editMode ? () => (clickValid.current = false) : undefined, onPointerUp: editMode ? onUp : undefined, children: [renderCoreNode(gameObject, { loadedModels, loadedTextures, editMode, registerRef }, parentMatrix), (_f = gameObject.children) === null || _f === void 0 ? void 0 : _f.map(child => (_jsx(GameObjectRenderer, { gameObject: child, selectedId: selectedId, onSelect: onSelect, onClick: onClick, registerRef: registerRef, registerRigidBodyRef: registerRigidBodyRef, loadedModels: loadedModels, loadedTextures: loadedTextures, editMode: editMode, parentMatrix: world }, child.id)))] }));
197
202
  if (editMode) {
198
- return (_jsxs(_Fragment, { children: [_jsx("group", { ref: groupRef, position: transform.position, rotation: transform.rotation, scale: transform.scale, children: _jsx("mesh", { visible: false, children: _jsx("boxGeometry", { args: [0.01, 0.01, 0.01] }) }) }), _jsx("group", { ref: helperRef, position: transform.position, rotation: transform.rotation, scale: transform.scale, children: inner }), hasPhysics && (physicsDef === null || physicsDef === void 0 ? void 0 : physicsDef.View) ? (_jsx(physicsDef.View, { properties: physics.properties, position: transform.position, rotation: transform.rotation, scale: transform.scale, editMode: editMode, children: inner }, physicsKey)) : null] }));
203
+ return (_jsxs(_Fragment, { children: [_jsx("group", { ref: groupRef, position: transform.position, rotation: transform.rotation, scale: transform.scale, children: _jsx("mesh", { visible: false, children: _jsx("boxGeometry", { args: [0.01, 0.01, 0.01] }) }) }), _jsx("group", { ref: helperRef, position: transform.position, rotation: transform.rotation, scale: transform.scale, children: inner }), hasPhysics && (physicsDef === null || physicsDef === void 0 ? void 0 : physicsDef.View) ? (_jsx(physicsDef.View, { properties: physics.properties, position: transform.position, rotation: transform.rotation, scale: transform.scale, editMode: editMode, nodeId: gameObject.id, registerRigidBodyRef: registerRigidBodyRef, children: inner }, physicsKey)) : null] }));
199
204
  }
200
205
  if (hasPhysics && (physicsDef === null || physicsDef === void 0 ? void 0 : physicsDef.View)) {
201
- return (_jsx(physicsDef.View, { properties: physics.properties, position: transform.position, rotation: transform.rotation, scale: transform.scale, editMode: editMode, children: inner }, physicsKey));
206
+ return (_jsx(physicsDef.View, { properties: physics.properties, position: transform.position, rotation: transform.rotation, scale: transform.scale, editMode: editMode, nodeId: gameObject.id, registerRigidBodyRef: registerRigidBodyRef, children: inner }, physicsKey));
202
207
  }
203
208
  return (_jsx("group", { ref: groupRef, position: transform.position, rotation: transform.rotation, scale: transform.scale, onPointerDown: onDown, onPointerMove: () => (clickValid.current = false), onPointerUp: onUp, children: inner }));
204
209
  }
@@ -1,3 +1,12 @@
1
1
  import { Component } from './ComponentRegistry';
2
+ import { MeshStandardMaterialProperties } from 'three';
3
+ export interface MaterialProps extends Omit<MeshStandardMaterialProperties, 'args'> {
4
+ texture?: string;
5
+ repeat?: boolean;
6
+ repeatCount?: [number, number];
7
+ generateMipmaps?: boolean;
8
+ minFilter?: string;
9
+ magFilter?: string;
10
+ }
2
11
  declare const MaterialComponent: Component;
3
12
  export default MaterialComponent;
@@ -1,3 +1,14 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
1
12
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
13
  import { SingleTextureViewer, TextureListViewer } from '../../assetviewer/page';
3
14
  import { useEffect, useState } from 'react';
@@ -25,6 +36,13 @@ function MaterialComponentEditor({ component, onUpdate, basePath = "" }) {
25
36
  const fields = [
26
37
  { name: 'color', type: 'color', label: 'Color' },
27
38
  { name: 'wireframe', type: 'boolean', label: 'Wireframe' },
39
+ { name: 'transparent', type: 'boolean', label: 'Transparent' },
40
+ { name: 'opacity', type: 'number', label: 'Opacity', min: 0, max: 1, step: 0.01 },
41
+ { name: 'metalness', type: 'number', label: 'Metalness', min: 0, max: 1, step: 0.01 },
42
+ { name: 'roughness', type: 'number', label: 'Roughness', min: 0, max: 1, step: 0.01 },
43
+ { name: 'transmission', type: 'number', label: 'Transmission', min: 0, max: 1, step: 0.01 },
44
+ { name: 'thickness', type: 'number', label: 'Thickness', min: 0, step: 0.1 },
45
+ { name: 'ior', type: 'number', label: 'IOR (Index of Refraction)', min: 1, max: 2.333, step: 0.01 },
28
46
  {
29
47
  name: 'texture',
30
48
  type: 'custom',
@@ -80,6 +98,9 @@ function MaterialComponentView({ properties, loadedTextures }) {
80
98
  const minFilter = (properties === null || properties === void 0 ? void 0 : properties.minFilter) || 'LinearMipmapLinearFilter';
81
99
  const magFilter = (properties === null || properties === void 0 ? void 0 : properties.magFilter) || 'LinearFilter';
82
100
  const texture = textureName && loadedTextures ? loadedTextures[textureName] : undefined;
101
+ // Destructure all material props and separate custom texture handling props
102
+ const _b = properties || {}, { texture: _texture, repeat: _repeat, repeatCount: _repeatCount, generateMipmaps: _generateMipmaps, minFilter: _minFilter, magFilter: _magFilter, map: _map } = _b, // Filter out map since we set it explicitly
103
+ materialProps = __rest(_b, ["texture", "repeat", "repeatCount", "generateMipmaps", "minFilter", "magFilter", "map"]);
83
104
  const minFilterMap = {
84
105
  NearestFilter,
85
106
  LinearFilter,
@@ -116,8 +137,7 @@ function MaterialComponentView({ properties, loadedTextures }) {
116
137
  if (!properties) {
117
138
  return _jsx("meshStandardMaterial", { color: "red", wireframe: true });
118
139
  }
119
- const { color, wireframe = false } = properties;
120
- return (_jsx("meshStandardMaterial", { color: color, wireframe: wireframe, map: finalTexture, transparent: !!finalTexture }, (_a = finalTexture === null || finalTexture === void 0 ? void 0 : finalTexture.uuid) !== null && _a !== void 0 ? _a : 'no-texture'));
140
+ return (_jsx("meshStandardMaterial", Object.assign({ map: finalTexture }, materialProps), (_a = finalTexture === null || finalTexture === void 0 ? void 0 : finalTexture.uuid) !== null && _a !== void 0 ? _a : 'no-texture'));
121
141
  }
122
142
  const MaterialComponent = {
123
143
  name: 'Material',
@@ -126,7 +146,11 @@ const MaterialComponent = {
126
146
  nonComposable: true,
127
147
  defaultProperties: {
128
148
  color: '#ffffff',
129
- wireframe: false
149
+ wireframe: false,
150
+ transparent: false,
151
+ opacity: 1,
152
+ metalness: 0,
153
+ roughness: 1
130
154
  }
131
155
  };
132
156
  export default MaterialComponent;
@@ -1,10 +1,5 @@
1
+ import type { RigidBodyOptions } from "@react-three/rapier";
1
2
  import { Component } from "./ComponentRegistry";
2
- export interface PhysicsProps {
3
- type: "fixed" | "dynamic";
4
- collider?: string;
5
- mass?: number;
6
- restitution?: number;
7
- friction?: number;
8
- }
3
+ export type PhysicsProps = RigidBodyOptions;
9
4
  declare const PhysicsComponent: Component;
10
5
  export default PhysicsComponent;
@@ -1,5 +1,17 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
1
12
  import { jsx as _jsx } from "react/jsx-runtime";
2
13
  import { RigidBody } from "@react-three/rapier";
14
+ import { useRef, useEffect } from 'react';
3
15
  import { FieldRenderer } from "./Input";
4
16
  const physicsFields = [
5
17
  {
@@ -9,10 +21,12 @@ const physicsFields = [
9
21
  options: [
10
22
  { value: 'dynamic', label: 'Dynamic' },
11
23
  { value: 'fixed', label: 'Fixed' },
24
+ { value: 'kinematicPosition', label: 'Kinematic Position' },
25
+ { value: 'kinematicVelocity', label: 'Kinematic Velocity' },
12
26
  ],
13
27
  },
14
28
  {
15
- name: 'collider',
29
+ name: 'colliders',
16
30
  type: 'select',
17
31
  label: 'Collider',
18
32
  options: [
@@ -22,24 +36,77 @@ const physicsFields = [
22
36
  { value: 'ball', label: 'Ball (sphere)' },
23
37
  ],
24
38
  },
39
+ {
40
+ name: 'mass',
41
+ type: 'number',
42
+ label: 'Mass',
43
+ },
44
+ {
45
+ name: 'restitution',
46
+ type: 'number',
47
+ label: 'Restitution (Bounciness)',
48
+ min: 0,
49
+ max: 1,
50
+ step: 0.1,
51
+ },
52
+ {
53
+ name: 'friction',
54
+ type: 'number',
55
+ label: 'Friction',
56
+ min: 0,
57
+ step: 0.1,
58
+ },
59
+ {
60
+ name: 'linearDamping',
61
+ type: 'number',
62
+ label: 'Linear Damping',
63
+ min: 0,
64
+ step: 0.1,
65
+ },
66
+ {
67
+ name: 'angularDamping',
68
+ type: 'number',
69
+ label: 'Angular Damping',
70
+ min: 0,
71
+ step: 0.1,
72
+ },
73
+ {
74
+ name: 'gravityScale',
75
+ type: 'number',
76
+ label: 'Gravity Scale',
77
+ step: 0.1,
78
+ },
25
79
  ];
26
80
  function PhysicsComponentEditor({ component, onUpdate }) {
27
- return (_jsx(FieldRenderer, { fields: physicsFields, values: component.properties, onChange: onUpdate }));
81
+ return (_jsx(FieldRenderer, { fields: physicsFields, values: component.properties, onChange: (props) => onUpdate(Object.assign(Object.assign({}, component), { properties: Object.assign(Object.assign({}, component.properties), props) })) }));
28
82
  }
29
- function PhysicsComponentView({ properties, children, position, rotation, scale, editMode }) {
30
- const colliders = properties.collider || (properties.type === 'fixed' ? 'trimesh' : 'hull');
83
+ function PhysicsComponentView({ properties, children, position, rotation, scale, editMode, nodeId, registerRigidBodyRef }) {
84
+ const { type, colliders } = properties, otherProps = __rest(properties, ["type", "colliders"]);
85
+ const colliderType = colliders || (type === 'fixed' ? 'trimesh' : 'hull');
86
+ const rigidBodyRef = useRef(null);
87
+ // Register RigidBody ref when it's available
88
+ useEffect(() => {
89
+ if (nodeId && registerRigidBodyRef && rigidBodyRef.current) {
90
+ registerRigidBodyRef(nodeId, rigidBodyRef.current);
91
+ }
92
+ return () => {
93
+ if (nodeId && registerRigidBodyRef) {
94
+ registerRigidBodyRef(nodeId, null);
95
+ }
96
+ };
97
+ }, [nodeId, registerRigidBodyRef]);
31
98
  // In edit mode, include position/rotation in key to force remount when transform changes
32
99
  // This ensures the RigidBody debug visualization updates even when physics is paused
33
100
  const rbKey = editMode
34
- ? `${properties.type || 'dynamic'}_${colliders}_${position === null || position === void 0 ? void 0 : position.join(',')}_${rotation === null || rotation === void 0 ? void 0 : rotation.join(',')}`
35
- : `${properties.type || 'dynamic'}_${colliders}`;
36
- return (_jsx(RigidBody, { type: properties.type, colliders: colliders, position: position, rotation: rotation, scale: scale, children: children }, rbKey));
101
+ ? `${type || 'dynamic'}_${colliderType}_${position === null || position === void 0 ? void 0 : position.join(',')}_${rotation === null || rotation === void 0 ? void 0 : rotation.join(',')}`
102
+ : `${type || 'dynamic'}_${colliderType}`;
103
+ return (_jsx(RigidBody, Object.assign({ ref: rigidBodyRef, type: type, colliders: colliderType, position: position, rotation: rotation, scale: scale }, otherProps, { children: children }), rbKey));
37
104
  }
38
105
  const PhysicsComponent = {
39
106
  name: 'Physics',
40
107
  Editor: PhysicsComponentEditor,
41
108
  View: PhysicsComponentView,
42
109
  nonComposable: true,
43
- defaultProperties: { type: 'dynamic', collider: 'hull' }
110
+ defaultProperties: { type: 'dynamic', colliders: 'hull' }
44
111
  };
45
112
  export default PhysicsComponent;
@@ -7,7 +7,6 @@ export interface GameObject {
7
7
  id: string;
8
8
  name?: string;
9
9
  disabled?: boolean;
10
- hidden?: boolean;
11
10
  children?: GameObject[];
12
11
  components?: {
13
12
  [key: string]: ComponentData | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-three-game",
3
- "version": "0.0.48",
3
+ "version": "0.0.50",
4
4
  "description": "Batteries included React Three Fiber game engine",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
@@ -1 +1,7 @@
1
1
  Agent skill for [react-three-game](https://github.com/prnthh/react-three-game)
2
+
3
+ Gives your agent the ability to make 3D scenes, physics simulations and games.
4
+
5
+ ```
6
+ npx skills add https://github.com/prnthh/react-three-game-skill
7
+ ```