react-three-game 0.0.48 → 0.0.49
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 +17 -5
- package/dist/helpers/index.d.ts +0 -2
- package/dist/helpers/index.js +1 -2
- package/dist/tools/prefabeditor/PrefabRoot.js +1 -1
- package/dist/tools/prefabeditor/components/PhysicsComponent.d.ts +2 -7
- package/dist/tools/prefabeditor/components/PhysicsComponent.js +61 -7
- 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 +7 -5
- package/src/helpers/index.ts +0 -4
- package/src/tools/prefabeditor/PrefabRoot.tsx +1 -1
- package/src/tools/prefabeditor/components/PhysicsComponent.tsx +58 -20
- 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
|
|
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",
|
|
@@ -108,7 +108,7 @@ export const PrefabRoot = forwardRef(({ editMode, data, onPrefabChange, selected
|
|
|
108
108
|
export function GameObjectRenderer(props) {
|
|
109
109
|
var _a, _b, _c;
|
|
110
110
|
const node = props.gameObject;
|
|
111
|
-
if (!node || node.
|
|
111
|
+
if (!node || node.disabled)
|
|
112
112
|
return null;
|
|
113
113
|
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
114
|
const prevInstancedRef = useRef(undefined);
|
|
@@ -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,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 } from "react/jsx-runtime";
|
|
2
13
|
import { RigidBody } from "@react-three/rapier";
|
|
3
14
|
import { FieldRenderer } from "./Input";
|
|
@@ -9,10 +20,12 @@ const physicsFields = [
|
|
|
9
20
|
options: [
|
|
10
21
|
{ value: 'dynamic', label: 'Dynamic' },
|
|
11
22
|
{ value: 'fixed', label: 'Fixed' },
|
|
23
|
+
{ value: 'kinematicPosition', label: 'Kinematic Position' },
|
|
24
|
+
{ value: 'kinematicVelocity', label: 'Kinematic Velocity' },
|
|
12
25
|
],
|
|
13
26
|
},
|
|
14
27
|
{
|
|
15
|
-
name: '
|
|
28
|
+
name: 'colliders',
|
|
16
29
|
type: 'select',
|
|
17
30
|
label: 'Collider',
|
|
18
31
|
options: [
|
|
@@ -22,24 +35,65 @@ const physicsFields = [
|
|
|
22
35
|
{ value: 'ball', label: 'Ball (sphere)' },
|
|
23
36
|
],
|
|
24
37
|
},
|
|
38
|
+
{
|
|
39
|
+
name: 'mass',
|
|
40
|
+
type: 'number',
|
|
41
|
+
label: 'Mass',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: 'restitution',
|
|
45
|
+
type: 'number',
|
|
46
|
+
label: 'Restitution (Bounciness)',
|
|
47
|
+
min: 0,
|
|
48
|
+
max: 1,
|
|
49
|
+
step: 0.1,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: 'friction',
|
|
53
|
+
type: 'number',
|
|
54
|
+
label: 'Friction',
|
|
55
|
+
min: 0,
|
|
56
|
+
step: 0.1,
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: 'linearDamping',
|
|
60
|
+
type: 'number',
|
|
61
|
+
label: 'Linear Damping',
|
|
62
|
+
min: 0,
|
|
63
|
+
step: 0.1,
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: 'angularDamping',
|
|
67
|
+
type: 'number',
|
|
68
|
+
label: 'Angular Damping',
|
|
69
|
+
min: 0,
|
|
70
|
+
step: 0.1,
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: 'gravityScale',
|
|
74
|
+
type: 'number',
|
|
75
|
+
label: 'Gravity Scale',
|
|
76
|
+
step: 0.1,
|
|
77
|
+
},
|
|
25
78
|
];
|
|
26
79
|
function PhysicsComponentEditor({ component, onUpdate }) {
|
|
27
|
-
return (_jsx(FieldRenderer, { fields: physicsFields, values: component.properties, onChange: onUpdate }));
|
|
80
|
+
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
81
|
}
|
|
29
82
|
function PhysicsComponentView({ properties, children, position, rotation, scale, editMode }) {
|
|
30
|
-
const colliders = properties
|
|
83
|
+
const { type, colliders } = properties, otherProps = __rest(properties, ["type", "colliders"]);
|
|
84
|
+
const colliderType = colliders || (type === 'fixed' ? 'trimesh' : 'hull');
|
|
31
85
|
// In edit mode, include position/rotation in key to force remount when transform changes
|
|
32
86
|
// This ensures the RigidBody debug visualization updates even when physics is paused
|
|
33
87
|
const rbKey = editMode
|
|
34
|
-
? `${
|
|
35
|
-
: `${
|
|
36
|
-
return (_jsx(RigidBody, { type:
|
|
88
|
+
? `${type || 'dynamic'}_${colliderType}_${position === null || position === void 0 ? void 0 : position.join(',')}_${rotation === null || rotation === void 0 ? void 0 : rotation.join(',')}`
|
|
89
|
+
: `${type || 'dynamic'}_${colliderType}`;
|
|
90
|
+
return (_jsx(RigidBody, Object.assign({ type: type, colliders: colliderType, position: position, rotation: rotation, scale: scale }, otherProps, { children: children }), rbKey));
|
|
37
91
|
}
|
|
38
92
|
const PhysicsComponent = {
|
|
39
93
|
name: 'Physics',
|
|
40
94
|
Editor: PhysicsComponentEditor,
|
|
41
95
|
View: PhysicsComponentView,
|
|
42
96
|
nonComposable: true,
|
|
43
|
-
defaultProperties: { type: 'dynamic',
|
|
97
|
+
defaultProperties: { type: 'dynamic', colliders: 'hull' }
|
|
44
98
|
};
|
|
45
99
|
export default PhysicsComponent;
|
package/package.json
CHANGED
|
@@ -82,7 +82,6 @@ Every game object follows this schema:
|
|
|
82
82
|
interface GameObject {
|
|
83
83
|
id: string;
|
|
84
84
|
disabled?: boolean;
|
|
85
|
-
hidden?: boolean;
|
|
86
85
|
components?: Record<string, { type: string; properties: any }>;
|
|
87
86
|
children?: GameObject[];
|
|
88
87
|
}
|
|
@@ -117,7 +116,7 @@ Scenes are defined as JSON prefabs with a root node containing children:
|
|
|
117
116
|
| Transform | `Transform` | `position: [x,y,z]`, `rotation: [x,y,z]` (radians), `scale: [x,y,z]` |
|
|
118
117
|
| Geometry | `Geometry` | `geometryType`: box/sphere/plane/cylinder, `args`: dimension array |
|
|
119
118
|
| Material | `Material` | `color`, `texture?`, `metalness?`, `roughness?`, `repeat?`, `repeatCount?` |
|
|
120
|
-
| Physics | `Physics` | `type`:
|
|
119
|
+
| Physics | `Physics` | `type`: dynamic/fixed/kinematicPosition/kinematicVelocity, `mass?`, `restitution?`, `friction?`, `linearDamping?`, `angularDamping?`, `gravityScale?`, plus any Rapier RigidBody props |
|
|
121
120
|
| Model | `Model` | `filename` (GLB/FBX path), `instanced?` for GPU batching |
|
|
122
121
|
| SpotLight | `SpotLight` | `color`, `intensity`, `angle`, `penumbra`, `distance?`, `castShadow?` |
|
|
123
122
|
| DirectionalLight | `DirectionalLight` | `color`, `intensity`, `castShadow?`, `targetOffset?: [x,y,z]` |
|
|
@@ -165,7 +164,7 @@ Use radians: `1.57` = 90°, `3.14` = 180°, `-1.57` = -90°
|
|
|
165
164
|
|
|
166
165
|
### Usage Modes
|
|
167
166
|
|
|
168
|
-
**GameCanvas + PrefabRoot**:
|
|
167
|
+
**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.
|
|
169
168
|
|
|
170
169
|
```jsx
|
|
171
170
|
import { Physics } from '@react-three/rapier';
|
|
@@ -174,16 +173,19 @@ import { GameCanvas, PrefabRoot } from 'react-three-game';
|
|
|
174
173
|
<GameCanvas>
|
|
175
174
|
<Physics>
|
|
176
175
|
<PrefabRoot data={prefabData} />
|
|
176
|
+
<CustomComponent />
|
|
177
177
|
</Physics>
|
|
178
178
|
</GameCanvas>
|
|
179
179
|
```
|
|
180
180
|
|
|
181
|
-
**PrefabEditor**:
|
|
181
|
+
**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.
|
|
182
182
|
|
|
183
183
|
```jsx
|
|
184
184
|
import { PrefabEditor } from 'react-three-game';
|
|
185
185
|
|
|
186
|
-
<PrefabEditor initialPrefab={prefabData}
|
|
186
|
+
<PrefabEditor initialPrefab={prefabData}>
|
|
187
|
+
<CustomComponent />
|
|
188
|
+
</PrefabEditor>
|
|
187
189
|
```
|
|
188
190
|
|
|
189
191
|
### Tree Utilities
|
package/src/helpers/index.ts
CHANGED
|
@@ -25,8 +25,6 @@ export interface GroundOptions {
|
|
|
25
25
|
/** Physics body type. Defaults to "fixed". */
|
|
26
26
|
physicsType?: "fixed" | "dynamic" | "kinematic";
|
|
27
27
|
|
|
28
|
-
/** Set true to hide the node. */
|
|
29
|
-
hidden?: boolean;
|
|
30
28
|
/** Set true to disable the node. */
|
|
31
29
|
disabled?: boolean;
|
|
32
30
|
}
|
|
@@ -52,14 +50,12 @@ export function ground(options: GroundOptions = {}): GameObject {
|
|
|
52
50
|
repeat = texture ? true : false,
|
|
53
51
|
repeatCount = [25, 25],
|
|
54
52
|
physicsType = "fixed",
|
|
55
|
-
hidden = false,
|
|
56
53
|
disabled = false,
|
|
57
54
|
} = options;
|
|
58
55
|
|
|
59
56
|
return {
|
|
60
57
|
id,
|
|
61
58
|
disabled,
|
|
62
|
-
hidden,
|
|
63
59
|
components: {
|
|
64
60
|
transform: {
|
|
65
61
|
type: "Transform",
|
|
@@ -175,7 +175,7 @@ export const PrefabRoot = forwardRef<PrefabRootRef, {
|
|
|
175
175
|
|
|
176
176
|
export function GameObjectRenderer(props: RendererProps) {
|
|
177
177
|
const node = props.gameObject;
|
|
178
|
-
if (!node || node.
|
|
178
|
+
if (!node || node.disabled) return null;
|
|
179
179
|
|
|
180
180
|
const isInstanced = node.components?.model?.properties?.instanced;
|
|
181
181
|
const prevInstancedRef = useRef<boolean | undefined>(undefined);
|
|
@@ -1,17 +1,11 @@
|
|
|
1
1
|
import { RigidBody, RapierRigidBody } from "@react-three/rapier";
|
|
2
|
+
import type { RigidBodyOptions } from "@react-three/rapier";
|
|
2
3
|
import type { ReactNode } from 'react';
|
|
3
|
-
import { useEffect, useRef } from 'react';
|
|
4
4
|
import { Component } from "./ComponentRegistry";
|
|
5
5
|
import { FieldRenderer, FieldDefinition } from "./Input";
|
|
6
|
-
import {
|
|
6
|
+
import { ComponentData } from "../types";
|
|
7
7
|
|
|
8
|
-
export
|
|
9
|
-
type: "fixed" | "dynamic";
|
|
10
|
-
collider?: string;
|
|
11
|
-
mass?: number;
|
|
12
|
-
restitution?: number;
|
|
13
|
-
friction?: number;
|
|
14
|
-
}
|
|
8
|
+
export type PhysicsProps = RigidBodyOptions;
|
|
15
9
|
|
|
16
10
|
const physicsFields: FieldDefinition[] = [
|
|
17
11
|
{
|
|
@@ -21,10 +15,12 @@ const physicsFields: FieldDefinition[] = [
|
|
|
21
15
|
options: [
|
|
22
16
|
{ value: 'dynamic', label: 'Dynamic' },
|
|
23
17
|
{ value: 'fixed', label: 'Fixed' },
|
|
18
|
+
{ value: 'kinematicPosition', label: 'Kinematic Position' },
|
|
19
|
+
{ value: 'kinematicVelocity', label: 'Kinematic Velocity' },
|
|
24
20
|
],
|
|
25
21
|
},
|
|
26
22
|
{
|
|
27
|
-
name: '
|
|
23
|
+
name: 'colliders',
|
|
28
24
|
type: 'select',
|
|
29
25
|
label: 'Collider',
|
|
30
26
|
options: [
|
|
@@ -34,20 +30,60 @@ const physicsFields: FieldDefinition[] = [
|
|
|
34
30
|
{ value: 'ball', label: 'Ball (sphere)' },
|
|
35
31
|
],
|
|
36
32
|
},
|
|
33
|
+
{
|
|
34
|
+
name: 'mass',
|
|
35
|
+
type: 'number',
|
|
36
|
+
label: 'Mass',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'restitution',
|
|
40
|
+
type: 'number',
|
|
41
|
+
label: 'Restitution (Bounciness)',
|
|
42
|
+
min: 0,
|
|
43
|
+
max: 1,
|
|
44
|
+
step: 0.1,
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'friction',
|
|
48
|
+
type: 'number',
|
|
49
|
+
label: 'Friction',
|
|
50
|
+
min: 0,
|
|
51
|
+
step: 0.1,
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'linearDamping',
|
|
55
|
+
type: 'number',
|
|
56
|
+
label: 'Linear Damping',
|
|
57
|
+
min: 0,
|
|
58
|
+
step: 0.1,
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: 'angularDamping',
|
|
62
|
+
type: 'number',
|
|
63
|
+
label: 'Angular Damping',
|
|
64
|
+
min: 0,
|
|
65
|
+
step: 0.1,
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: 'gravityScale',
|
|
69
|
+
type: 'number',
|
|
70
|
+
label: 'Gravity Scale',
|
|
71
|
+
step: 0.1,
|
|
72
|
+
},
|
|
37
73
|
];
|
|
38
74
|
|
|
39
|
-
function PhysicsComponentEditor({ component, onUpdate }: { component:
|
|
75
|
+
function PhysicsComponentEditor({ component, onUpdate }: { component: ComponentData; onUpdate: (newComp: any) => void }) {
|
|
40
76
|
return (
|
|
41
77
|
<FieldRenderer
|
|
42
78
|
fields={physicsFields}
|
|
43
79
|
values={component.properties}
|
|
44
|
-
onChange={onUpdate}
|
|
80
|
+
onChange={(props) => onUpdate({ ...component, properties: { ...component.properties, ...props } })}
|
|
45
81
|
/>
|
|
46
82
|
);
|
|
47
83
|
}
|
|
48
84
|
|
|
49
85
|
interface PhysicsViewProps {
|
|
50
|
-
properties:
|
|
86
|
+
properties: PhysicsProps;
|
|
51
87
|
editMode?: boolean;
|
|
52
88
|
children?: ReactNode;
|
|
53
89
|
position?: [number, number, number];
|
|
@@ -56,22 +92,24 @@ interface PhysicsViewProps {
|
|
|
56
92
|
}
|
|
57
93
|
|
|
58
94
|
function PhysicsComponentView({ properties, children, position, rotation, scale, editMode }: PhysicsViewProps) {
|
|
59
|
-
const
|
|
95
|
+
const { type, colliders, ...otherProps } = properties;
|
|
96
|
+
const colliderType = colliders || (type === 'fixed' ? 'trimesh' : 'hull');
|
|
60
97
|
|
|
61
98
|
// In edit mode, include position/rotation in key to force remount when transform changes
|
|
62
99
|
// This ensures the RigidBody debug visualization updates even when physics is paused
|
|
63
100
|
const rbKey = editMode
|
|
64
|
-
? `${
|
|
65
|
-
: `${
|
|
101
|
+
? `${type || 'dynamic'}_${colliderType}_${position?.join(',')}_${rotation?.join(',')}`
|
|
102
|
+
: `${type || 'dynamic'}_${colliderType}`;
|
|
66
103
|
|
|
67
104
|
return (
|
|
68
105
|
<RigidBody
|
|
69
106
|
key={rbKey}
|
|
70
|
-
type={
|
|
71
|
-
colliders={
|
|
107
|
+
type={type}
|
|
108
|
+
colliders={colliderType as any}
|
|
72
109
|
position={position}
|
|
73
110
|
rotation={rotation}
|
|
74
111
|
scale={scale}
|
|
112
|
+
{...otherProps}
|
|
75
113
|
>
|
|
76
114
|
{children}
|
|
77
115
|
</RigidBody>
|
|
@@ -83,7 +121,7 @@ const PhysicsComponent: Component = {
|
|
|
83
121
|
Editor: PhysicsComponentEditor,
|
|
84
122
|
View: PhysicsComponentView,
|
|
85
123
|
nonComposable: true,
|
|
86
|
-
defaultProperties: { type: 'dynamic',
|
|
124
|
+
defaultProperties: { type: 'dynamic', colliders: 'hull' }
|
|
87
125
|
};
|
|
88
126
|
|
|
89
|
-
export default PhysicsComponent;
|
|
127
|
+
export default PhysicsComponent;
|