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.
- package/.github/copilot-instructions.md +8 -3
- package/README.md +23 -7
- package/dist/helpers/index.d.ts +0 -2
- package/dist/helpers/index.js +1 -2
- package/dist/tools/prefabeditor/PrefabRoot.d.ts +3 -0
- package/dist/tools/prefabeditor/PrefabRoot.js +12 -7
- package/dist/tools/prefabeditor/components/MaterialComponent.d.ts +9 -0
- package/dist/tools/prefabeditor/components/MaterialComponent.js +27 -3
- package/dist/tools/prefabeditor/components/PhysicsComponent.d.ts +2 -7
- package/dist/tools/prefabeditor/components/PhysicsComponent.js +75 -8
- package/dist/tools/prefabeditor/types.d.ts +0 -1
- package/package.json +1 -1
- package/react-three-game-skill/README.md +6 -0
- package/react-three-game-skill/react-three-game/SKILL.md +70 -84
- package/react-three-game-skill/react-three-game/rules/ADVANCED_PHYSICS.md +349 -0
- package/src/helpers/index.ts +0 -4
- package/src/tools/prefabeditor/PrefabRoot.tsx +18 -2
- package/src/tools/prefabeditor/components/MaterialComponent.tsx +37 -8
- package/src/tools/prefabeditor/components/PhysicsComponent.tsx +76 -21
- package/src/tools/prefabeditor/types.ts +0 -1
|
@@ -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` |
|
|
42
|
-
| `src/tools/prefabeditor/PrefabEditor.tsx` |
|
|
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
|
-
##
|
|
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
|
|
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
|
package/dist/helpers/index.d.ts
CHANGED
|
@@ -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
|
}
|
package/dist/helpers/index.js
CHANGED
|
@@ -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",
|
|
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.
|
|
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
|
-
|
|
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
|
|
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: '
|
|
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
|
|
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
|
-
? `${
|
|
35
|
-
: `${
|
|
36
|
-
return (_jsx(RigidBody, { type:
|
|
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',
|
|
110
|
+
defaultProperties: { type: 'dynamic', colliders: 'hull' }
|
|
44
111
|
};
|
|
45
112
|
export default PhysicsComponent;
|
package/package.json
CHANGED