react-three-game 0.0.84 → 0.0.86
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/README.md +110 -36
- package/dist/index.d.ts +3 -7
- package/dist/index.js +1 -4
- package/dist/tools/prefabeditor/InstanceProvider.d.ts +0 -4
- package/dist/tools/prefabeditor/InstanceProvider.js +13 -44
- package/dist/tools/prefabeditor/PrefabEditor.d.ts +7 -2
- package/dist/tools/prefabeditor/PrefabEditor.js +13 -24
- package/dist/tools/prefabeditor/PrefabRoot.js +99 -48
- package/dist/tools/prefabeditor/{runtime.d.ts → assetRuntime.d.ts} +0 -25
- package/dist/tools/prefabeditor/assetRuntime.js +37 -0
- package/dist/tools/prefabeditor/components/BufferGeometryComponent.d.ts +3 -0
- package/dist/tools/prefabeditor/components/BufferGeometryComponent.js +98 -0
- package/dist/tools/prefabeditor/components/CameraComponent.js +1 -1
- package/dist/tools/prefabeditor/components/ComponentRegistry.d.ts +0 -3
- package/dist/tools/prefabeditor/components/DataComponent.d.ts +3 -0
- package/dist/tools/prefabeditor/components/DataComponent.js +87 -0
- package/dist/tools/prefabeditor/components/DirectionalLightComponent.js +1 -1
- package/dist/tools/prefabeditor/components/EnvironmentComponent.js +1 -1
- package/dist/tools/prefabeditor/components/GeometryComponent.js +4 -2
- package/dist/tools/prefabeditor/components/Input.d.ts +2 -13
- package/dist/tools/prefabeditor/components/Input.js +0 -55
- package/dist/tools/prefabeditor/components/MaterialComponent.d.ts +3 -0
- package/dist/tools/prefabeditor/components/MaterialComponent.js +49 -18
- package/dist/tools/prefabeditor/components/ModelComponent.js +3 -3
- package/dist/tools/prefabeditor/components/PhysicsComponent.d.ts +4 -0
- package/dist/tools/prefabeditor/components/PhysicsComponent.js +69 -132
- package/dist/tools/prefabeditor/components/PointLightComponent.js +1 -1
- package/dist/tools/prefabeditor/components/SoundComponent.js +17 -17
- package/dist/tools/prefabeditor/components/SpotLightComponent.js +1 -1
- package/dist/tools/prefabeditor/components/index.js +10 -8
- package/dist/tools/prefabeditor/types.d.ts +1 -0
- package/dist/tools/prefabeditor/types.js +18 -0
- package/dist/tools/prefabeditor/usePointerEvents.d.ts +27 -0
- package/dist/tools/prefabeditor/usePointerEvents.js +52 -0
- package/package.json +1 -1
- package/dist/tools/prefabeditor/GameEvents.d.ts +0 -128
- package/dist/tools/prefabeditor/GameEvents.js +0 -118
- package/dist/tools/prefabeditor/components/ClickComponent.d.ts +0 -3
- package/dist/tools/prefabeditor/components/ClickComponent.js +0 -52
- package/dist/tools/prefabeditor/runtime.js +0 -184
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|
|
|
5
|
-
JSON-first
|
|
5
|
+
JSON-first scene mounting and authoring for React Three Fiber.
|
|
6
6
|
|
|
7
7
|
Built on top of [three.js](https://github.com/mrdoob/three.js), [@react-three/fiber](https://github.com/pmndrs/react-three-fiber), and [@react-three/rapier](https://github.com/pmndrs/react-three-rapier).
|
|
8
8
|
|
|
@@ -10,7 +10,7 @@ Built on top of [three.js](https://github.com/mrdoob/three.js), [@react-three/fi
|
|
|
10
10
|
* **🎬 Scene Editor** - Edit prefabs visually with hierarchy, inspector, transform gizmos, and play mode.
|
|
11
11
|
* **⚛️ Physics** - Author rigid bodies directly in prefab data and run them through Rapier.
|
|
12
12
|
* **🧩 Components** - Build scenes from reusable `GameObject` + component composition.
|
|
13
|
-
* **🔧 Runtime
|
|
13
|
+
* **🔧 Direct Runtime Access** - Get native `Object3D`, Rapier rigid body, and prefab store access without a parallel engine API.
|
|
14
14
|
* **⚡ R3F Native** - Use normal React Three Fiber components whenever runtime behavior is clearer in code.
|
|
15
15
|
|
|
16
16
|
## Documentation
|
|
@@ -98,12 +98,10 @@ Open the hosted editor here:
|
|
|
98
98
|
|
|
99
99
|
* https://prnth.com/react-three-game/editor
|
|
100
100
|
|
|
101
|
-
## Prefabs And Scenes
|
|
101
|
+
## Prefabs And Mounted Scenes
|
|
102
102
|
|
|
103
103
|
`Prefab` is the serializable pure data format.
|
|
104
104
|
|
|
105
|
-
`Scene` is the live runtime/editor world handle.
|
|
106
|
-
|
|
107
105
|
That means a saved scene is just a prefab, and the same prefab can be:
|
|
108
106
|
|
|
109
107
|
* edited directly in `PrefabEditor`
|
|
@@ -113,13 +111,35 @@ That means a saved scene is just a prefab, and the same prefab can be:
|
|
|
113
111
|
`PrefabRoot` keeps the rendering model narrow and compositional:
|
|
114
112
|
|
|
115
113
|
* `Transform` is the renderer-owned outer transform
|
|
116
|
-
* `Geometry` + `Material` become the primary mesh content
|
|
114
|
+
* `Geometry` or `BufferGeometry` + `Material` become the primary mesh content
|
|
117
115
|
* non-instanced `Model` becomes the node's primary content
|
|
118
116
|
* `Physics` is a renderer-owned outer wrapper
|
|
119
117
|
* every other component `View` wraps the current subtree
|
|
120
118
|
|
|
121
119
|
Custom component `View`s use normal React Three Fiber composition with `children`.
|
|
122
120
|
|
|
121
|
+
For agent-authored custom meshes, use `BufferGeometry` with flat numeric arrays:
|
|
122
|
+
|
|
123
|
+
```json
|
|
124
|
+
{
|
|
125
|
+
"id": "triangle",
|
|
126
|
+
"components": {
|
|
127
|
+
"bufferGeometry": {
|
|
128
|
+
"type": "BufferGeometry",
|
|
129
|
+
"properties": {
|
|
130
|
+
"positions": [0, 0, 0, 1, 0, 0, 0, 1, 0],
|
|
131
|
+
"indices": [0, 1, 2],
|
|
132
|
+
"computeVertexNormals": true
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
"material": {
|
|
136
|
+
"type": "Material",
|
|
137
|
+
"properties": { "color": "#ff8844" }
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
123
143
|
## Prefab Format
|
|
124
144
|
|
|
125
145
|
```ts
|
|
@@ -141,7 +161,7 @@ interface GameObject {
|
|
|
141
161
|
|
|
142
162
|
## Runtime Mutation
|
|
143
163
|
|
|
144
|
-
Use
|
|
164
|
+
Use native object access for Three.js behavior and the prefab store for authored data changes.
|
|
145
165
|
|
|
146
166
|
```tsx
|
|
147
167
|
import { useEffect, useRef } from "react";
|
|
@@ -151,48 +171,105 @@ function RaiseBall() {
|
|
|
151
171
|
const editorRef = useRef<PrefabEditorRef>(null);
|
|
152
172
|
|
|
153
173
|
useEffect(() => {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
174
|
+
editorRef.current?.store.getState().updateNode("ball", (node) => ({
|
|
175
|
+
...node,
|
|
176
|
+
components: {
|
|
177
|
+
...node.components,
|
|
178
|
+
transform: {
|
|
179
|
+
type: "Transform",
|
|
180
|
+
properties: {
|
|
181
|
+
...node.components?.transform?.properties,
|
|
182
|
+
position: [0, 8, 0],
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
}));
|
|
160
187
|
}, []);
|
|
161
188
|
|
|
162
189
|
return <PrefabEditor ref={editorRef} initialPrefab={prefab} />;
|
|
163
190
|
}
|
|
164
191
|
```
|
|
165
192
|
|
|
166
|
-
|
|
193
|
+
For live Three.js access, use mounted objects directly:
|
|
167
194
|
|
|
168
195
|
```tsx
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
scene.find("orb2")?.getComponent("Transform")?.set("position", [-1, 0, 0]);
|
|
172
|
-
});
|
|
196
|
+
const ball = editorRef.current?.getObject("ball");
|
|
197
|
+
ball?.rotateY(0.5);
|
|
173
198
|
```
|
|
174
199
|
|
|
175
|
-
|
|
200
|
+
Mounted node metadata is mirrored onto the canonical Three.js wrapper object:
|
|
201
|
+
|
|
202
|
+
* `GameObject.id` -> `object.userData.prefabNodeId`
|
|
203
|
+
* `GameObject.name` -> `object.name` and `object.userData.prefabNodeName`
|
|
204
|
+
* `Data.properties.data` -> merged into `object.userData`
|
|
205
|
+
|
|
206
|
+
That gives you a stable authored id for traversal-based integrations, while still making Three.js name lookup convenient:
|
|
176
207
|
|
|
177
208
|
```tsx
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
209
|
+
const playerByName = editorRef.current?.root?.getObjectByName("Player");
|
|
210
|
+
const playerById = editorRef.current?.root?.getObjectByProperty("userData.prefabNodeId", "player");
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Treat names as a convenience surface, not the primary lookup key:
|
|
214
|
+
|
|
215
|
+
* names are not guaranteed unique
|
|
216
|
+
* `getObject(id)` is still the stable authored-node lookup
|
|
217
|
+
* traversal metadata is applied to the prefab node transform object, not necessarily the inner mesh or model child
|
|
218
|
+
|
|
219
|
+
You can author extra `userData` from the editor with a `Data` component:
|
|
220
|
+
|
|
221
|
+
```json
|
|
222
|
+
{
|
|
223
|
+
"data": {
|
|
224
|
+
"faction": "enemy",
|
|
225
|
+
"health": 100,
|
|
226
|
+
"loot": { "table": "crate" }
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
For batched authored updates, write through the store once:
|
|
232
|
+
|
|
233
|
+
```tsx
|
|
234
|
+
editorRef.current?.store.getState().updateNodes([
|
|
235
|
+
{
|
|
236
|
+
id: "orb1",
|
|
237
|
+
update: (node) => ({
|
|
238
|
+
...node,
|
|
239
|
+
components: {
|
|
240
|
+
...node.components,
|
|
241
|
+
transform: {
|
|
242
|
+
...node.components?.transform,
|
|
243
|
+
type: "Transform",
|
|
244
|
+
properties: {
|
|
245
|
+
...node.components?.transform?.properties,
|
|
246
|
+
position: [1, 0, 0],
|
|
247
|
+
},
|
|
248
|
+
},
|
|
189
249
|
},
|
|
190
|
-
}
|
|
250
|
+
}),
|
|
191
251
|
},
|
|
192
|
-
|
|
252
|
+
{
|
|
253
|
+
id: "orb2",
|
|
254
|
+
update: (node) => ({
|
|
255
|
+
...node,
|
|
256
|
+
components: {
|
|
257
|
+
...node.components,
|
|
258
|
+
transform: {
|
|
259
|
+
...node.components?.transform,
|
|
260
|
+
type: "Transform",
|
|
261
|
+
properties: {
|
|
262
|
+
...node.components?.transform?.properties,
|
|
263
|
+
position: [-1, 0, 0],
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
}),
|
|
268
|
+
},
|
|
269
|
+
]);
|
|
193
270
|
```
|
|
194
271
|
|
|
195
|
-
`View`
|
|
272
|
+
Custom component `View`s should use normal React and R3F behavior, such as `useFrame`, refs, and native Three.js APIs.
|
|
196
273
|
|
|
197
274
|
## Useful Exports
|
|
198
275
|
|
|
@@ -202,9 +279,6 @@ const Rotator: Component = {
|
|
|
202
279
|
* `PrefabEditorMode`
|
|
203
280
|
* `Prefab`
|
|
204
281
|
* `GameObject`
|
|
205
|
-
* `Scene`
|
|
206
|
-
* `Entity`
|
|
207
|
-
* `EntityComponent`
|
|
208
282
|
* `registerComponent`
|
|
209
283
|
* `createPrefabStore`
|
|
210
284
|
* `usePrefabStoreApi`
|
package/dist/index.d.ts
CHANGED
|
@@ -10,7 +10,6 @@ export { useEditorContext } from './tools/prefabeditor/PrefabEditor';
|
|
|
10
10
|
export type { EditorContextType } from './tools/prefabeditor/PrefabEditor';
|
|
11
11
|
export { createPrefabStore, prefabStoreToPrefab, usePrefabStoreApi } from './tools/prefabeditor/prefabStore';
|
|
12
12
|
export type { PrefabStoreApi, PrefabStoreState } from './tools/prefabeditor/prefabStore';
|
|
13
|
-
export { createScene } from './tools/prefabeditor/scene';
|
|
14
13
|
export { denormalizePrefab } from './tools/prefabeditor/prefab';
|
|
15
14
|
export { registerComponent } from './tools/prefabeditor/components/ComponentRegistry';
|
|
16
15
|
export { FieldRenderer, FieldGroup, ListEditor, Label, Vector3Input, Vector3Field, NumberField, ColorInput, ColorField, StringInput, StringField, BooleanInput, BooleanField, SelectInput, SelectField, } from './tools/prefabeditor/components/Input';
|
|
@@ -18,11 +17,10 @@ export { loadJson, saveJson, exportGLB, exportGLBData, regenerateIds, computePar
|
|
|
18
17
|
export type { ExportGLBOptions } from './tools/prefabeditor/utils';
|
|
19
18
|
export { createModelNode, createImageNode, } from './tools/prefabeditor/prefab';
|
|
20
19
|
export type { PrefabEditorProps, PrefabEditorRef, } from './tools/prefabeditor/PrefabEditor';
|
|
21
|
-
export type { SpawnOptions,
|
|
20
|
+
export type { SpawnOptions, } from './tools/prefabeditor/scene';
|
|
22
21
|
export type { PrefabRootProps } from './tools/prefabeditor/PrefabRoot';
|
|
23
|
-
export type { AssetRuntime, EntityRuntime, LiveObjectRef, LiveRigidBodyRef } from './tools/prefabeditor/
|
|
24
|
-
export { useAssetRuntime, useEntityRuntime, useEntityObjectRef, useEntityRigidBodyRef } from './tools/prefabeditor/
|
|
25
|
-
export type { ComponentInstance, ComponentRuntimeContext } from './tools/prefabeditor/runtime';
|
|
22
|
+
export type { AssetRuntime, EntityRuntime, LiveObjectRef, LiveRigidBodyRef } from './tools/prefabeditor/assetRuntime';
|
|
23
|
+
export { useAssetRuntime, useEntityRuntime, useEntityObjectRef, useEntityRigidBodyRef } from './tools/prefabeditor/assetRuntime';
|
|
26
24
|
export type { Component, ComponentViewProps } from './tools/prefabeditor/components/ComponentRegistry';
|
|
27
25
|
export type { FieldDefinition, FieldType } from './tools/prefabeditor/components/Input';
|
|
28
26
|
export { MaterialOverridesProvider, useMaterialOverrides } from './tools/prefabeditor/components/MaterialComponent';
|
|
@@ -30,8 +28,6 @@ export type { MaterialOverrides } from './tools/prefabeditor/components/Material
|
|
|
30
28
|
export type { Prefab, GameObject, ComponentData } from './tools/prefabeditor/types';
|
|
31
29
|
export { findComponent, findComponentEntry, hasComponent } from './tools/prefabeditor/types';
|
|
32
30
|
export { float, positionLocal, sin, time, uniform, vec3, } from 'three/tsl';
|
|
33
|
-
export { gameEvents, useGameEvent, usePhysicsEvent, useClickEvent, getEntityIdFromRigidBody } from './tools/prefabeditor/GameEvents';
|
|
34
|
-
export type { GameEventType, GameEventMap, GameEventPayload, PhysicsEventType, InteractionEventType, PhysicsEventPayload, ClickEventPayload } from './tools/prefabeditor/GameEvents';
|
|
35
31
|
export { loadFiles } from './tools/dragdrop/DragDropLoader';
|
|
36
32
|
export type { AssetLoadOptions } from './tools/dragdrop/DragDropLoader';
|
|
37
33
|
export { loadModel, loadSound, loadTexture } from './tools/dragdrop/modelLoader';
|
package/dist/index.js
CHANGED
|
@@ -10,7 +10,6 @@ export { default as PrefabRoot } from './tools/prefabeditor/PrefabRoot';
|
|
|
10
10
|
export { useEditorContext } from './tools/prefabeditor/PrefabEditor';
|
|
11
11
|
// Prefab Editor - Store & Scene API
|
|
12
12
|
export { createPrefabStore, prefabStoreToPrefab, usePrefabStoreApi } from './tools/prefabeditor/prefabStore';
|
|
13
|
-
export { createScene } from './tools/prefabeditor/scene';
|
|
14
13
|
export { denormalizePrefab } from './tools/prefabeditor/prefab';
|
|
15
14
|
// Prefab Editor - Component Registry
|
|
16
15
|
export { registerComponent } from './tools/prefabeditor/components/ComponentRegistry';
|
|
@@ -19,12 +18,10 @@ export { FieldRenderer, FieldGroup, ListEditor, Label, Vector3Input, Vector3Fiel
|
|
|
19
18
|
// Prefab Editor - Utils
|
|
20
19
|
export { loadJson, saveJson, exportGLB, exportGLBData, regenerateIds, computeParentWorldMatrix, } from './tools/prefabeditor/utils';
|
|
21
20
|
export { createModelNode, createImageNode, } from './tools/prefabeditor/prefab';
|
|
22
|
-
export { useAssetRuntime, useEntityRuntime, useEntityObjectRef, useEntityRigidBodyRef } from './tools/prefabeditor/
|
|
21
|
+
export { useAssetRuntime, useEntityRuntime, useEntityObjectRef, useEntityRigidBodyRef } from './tools/prefabeditor/assetRuntime';
|
|
23
22
|
export { MaterialOverridesProvider, useMaterialOverrides } from './tools/prefabeditor/components/MaterialComponent';
|
|
24
23
|
export { findComponent, findComponentEntry, hasComponent } from './tools/prefabeditor/types';
|
|
25
24
|
export { float, positionLocal, sin, time, uniform, vec3, } from 'three/tsl';
|
|
26
|
-
// Game Events (physics + custom events)
|
|
27
|
-
export { gameEvents, useGameEvent, usePhysicsEvent, useClickEvent, getEntityIdFromRigidBody } from './tools/prefabeditor/GameEvents';
|
|
28
25
|
// Asset Loading
|
|
29
26
|
export { loadFiles } from './tools/dragdrop/DragDropLoader';
|
|
30
27
|
export { loadModel, loadSound, loadTexture } from './tools/dragdrop/modelLoader';
|
|
@@ -12,8 +12,6 @@ export declare function getRepeatAxesFromModelProperties(properties: Record<stri
|
|
|
12
12
|
export type InstanceData = {
|
|
13
13
|
id: string;
|
|
14
14
|
sourceId: string;
|
|
15
|
-
clickable?: boolean;
|
|
16
|
-
clickEventName?: string;
|
|
17
15
|
locked?: boolean;
|
|
18
16
|
position: [number, number, number];
|
|
19
17
|
rotation: [number, number, number];
|
|
@@ -35,8 +33,6 @@ export declare function useInstanceCheck(id: string): boolean;
|
|
|
35
33
|
export declare const GameInstance: React.ForwardRefExoticComponent<{
|
|
36
34
|
id: string;
|
|
37
35
|
sourceId?: string;
|
|
38
|
-
clickable?: boolean;
|
|
39
|
-
clickEventName?: string;
|
|
40
36
|
modelUrl: string;
|
|
41
37
|
locked?: boolean;
|
|
42
38
|
position: [number, number, number];
|
|
@@ -15,8 +15,7 @@ import { Merged, useHelper } from '@react-three/drei';
|
|
|
15
15
|
import { InstancedRigidBodies } from "@react-three/rapier";
|
|
16
16
|
import { ActiveCollisionTypes } from "@dimforge/rapier3d-compat";
|
|
17
17
|
import { Mesh, Matrix4, Vector3, Quaternion, Euler, BoxHelper } from "three";
|
|
18
|
-
import {
|
|
19
|
-
import { useClickValid } from "./useClickValid";
|
|
18
|
+
import { usePointerEvents } from "./usePointerEvents";
|
|
20
19
|
export const DEFAULT_REPEAT_AXES = [{ axis: 'x', count: 1, offset: 1 }];
|
|
21
20
|
export function normalizeRepeatAxes(value) {
|
|
22
21
|
if (!Array.isArray(value)) {
|
|
@@ -103,32 +102,9 @@ function hasPhysics(instance) {
|
|
|
103
102
|
function getColliderType(physics) {
|
|
104
103
|
return physics.colliders || (physics.type === 'fixed' ? 'trimesh' : 'hull');
|
|
105
104
|
}
|
|
106
|
-
function emitPhysicsEvent(sourceId, eventName, payload) {
|
|
107
|
-
if (!eventName)
|
|
108
|
-
return;
|
|
109
|
-
gameEvents.emit(eventName, {
|
|
110
|
-
sourceEntityId: sourceId,
|
|
111
|
-
targetEntityId: getEntityIdFromRigidBody(payload.other.rigidBody),
|
|
112
|
-
targetRigidBody: payload.other.rigidBody,
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
function emitClick(sourceId, instanceId, eventName, event) {
|
|
116
|
-
gameEvents.emit(eventName, {
|
|
117
|
-
sourceEntityId: sourceId,
|
|
118
|
-
instanceEntityId: instanceId && instanceId !== sourceId ? instanceId : undefined,
|
|
119
|
-
point: [event.point.x, event.point.y, event.point.z],
|
|
120
|
-
button: event.button,
|
|
121
|
-
altKey: event.nativeEvent.altKey,
|
|
122
|
-
ctrlKey: event.nativeEvent.ctrlKey,
|
|
123
|
-
metaKey: event.nativeEvent.metaKey,
|
|
124
|
-
shiftKey: event.nativeEvent.shiftKey,
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
105
|
function instanceEquals(a, b) {
|
|
128
106
|
return a.id === b.id &&
|
|
129
107
|
a.sourceId === b.sourceId &&
|
|
130
|
-
a.clickable === b.clickable &&
|
|
131
|
-
a.clickEventName === b.clickEventName &&
|
|
132
108
|
a.locked === b.locked &&
|
|
133
109
|
a.meshPath === b.meshPath &&
|
|
134
110
|
arrayEquals(a.position, b.position) &&
|
|
@@ -245,7 +221,7 @@ function InstancedRigidGroup({ group, modelKey, partCount, flatMeshes, onSelect,
|
|
|
245
221
|
const rigidBodiesRef = useRef(null);
|
|
246
222
|
const instances = useMemo(() => group.instances.filter(hasPhysics).map(inst => {
|
|
247
223
|
const _a = inst.physics, { activeCollisionTypes: _activeCollisionTypes, colliders: _colliders, userData } = _a, rigidBodyProps = __rest(_a, ["activeCollisionTypes", "colliders", "userData"]);
|
|
248
|
-
return Object.assign(Object.assign({ key: inst.id, position: inst.position, rotation: inst.rotation, scale: inst.scale }, rigidBodyProps), { colliders: getColliderType(inst.physics), userData: Object.assign(Object.assign({}, userData), { entityId: inst.sourceId })
|
|
224
|
+
return Object.assign(Object.assign({ key: inst.id, position: inst.position, rotation: inst.rotation, scale: inst.scale }, rigidBodyProps), { colliders: getColliderType(inst.physics), userData: Object.assign(Object.assign({}, userData), { entityId: inst.sourceId }) });
|
|
249
225
|
}), [group.instances]);
|
|
250
226
|
// Apply scale to visual meshes (InstancedRigidBodies only scales colliders, not visuals)
|
|
251
227
|
useEffect(() => {
|
|
@@ -317,12 +293,8 @@ function InstancedRigidGroup({ group, modelKey, partCount, flatMeshes, onSelect,
|
|
|
317
293
|
onSelect(instance.sourceId);
|
|
318
294
|
return;
|
|
319
295
|
}
|
|
320
|
-
if (!instance.clickable)
|
|
321
|
-
return;
|
|
322
|
-
e.stopPropagation();
|
|
323
|
-
emitClick(instance.sourceId, instance.id, instance.clickEventName || 'click', e);
|
|
324
296
|
};
|
|
325
|
-
const shouldHandleClick = editMode
|
|
297
|
+
const shouldHandleClick = editMode;
|
|
326
298
|
// Add key to force remount when instance count changes significantly (helps with cleanup)
|
|
327
299
|
const rigidBodyKey = `rb_${modelKey}_${group.instances.map(inst => `${inst.id}:${getPhysicsSignature(inst.physics)}`).join('|')}`;
|
|
328
300
|
return (_jsx(InstancedRigidBodies, { ref: rigidBodiesRef, instances: instances, children: Array.from({ length: partCount }).map((_, i) => {
|
|
@@ -344,14 +316,15 @@ function InstanceGroupItem({ instance, InstanceComponents, onSelect, registerRef
|
|
|
344
316
|
const isLocked = Boolean(instance.locked);
|
|
345
317
|
const isSelected = selectedId === instance.id || selectedId === instance.sourceId;
|
|
346
318
|
const canSelect = editMode && !isLocked;
|
|
347
|
-
const canClick =
|
|
348
|
-
const pointerHandlers =
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
319
|
+
const canClick = false;
|
|
320
|
+
const pointerHandlers = usePointerEvents({
|
|
321
|
+
enabled: canSelect || canClick,
|
|
322
|
+
entity: instance,
|
|
323
|
+
onClick: () => {
|
|
324
|
+
if (editMode) {
|
|
325
|
+
onSelect === null || onSelect === void 0 ? void 0 : onSelect(instance.sourceId);
|
|
326
|
+
}
|
|
327
|
+
},
|
|
355
328
|
});
|
|
356
329
|
// Use BoxHelper when object is selected in edit mode
|
|
357
330
|
useHelper(editMode && isSelected ? groupRef : null, BoxHelper, 'cyan');
|
|
@@ -370,7 +343,7 @@ export function useInstanceCheck(id) {
|
|
|
370
343
|
return (_a = ctx === null || ctx === void 0 ? void 0 : ctx.hasInstance(id)) !== null && _a !== void 0 ? _a : false;
|
|
371
344
|
}
|
|
372
345
|
// GameInstance component: registers an instance for batch rendering (renders nothing itself)
|
|
373
|
-
export const GameInstance = React.forwardRef(({ id, sourceId,
|
|
346
|
+
export const GameInstance = React.forwardRef(({ id, sourceId, modelUrl, locked = false, position, rotation, scale, physics = undefined, }, ref) => {
|
|
374
347
|
const ctx = useContext(GameInstanceContext);
|
|
375
348
|
const addInstance = ctx === null || ctx === void 0 ? void 0 : ctx.addInstance;
|
|
376
349
|
const removeInstance = ctx === null || ctx === void 0 ? void 0 : ctx.removeInstance;
|
|
@@ -381,8 +354,6 @@ export const GameInstance = React.forwardRef(({ id, sourceId, clickable = false,
|
|
|
381
354
|
const instance = useMemo(() => ({
|
|
382
355
|
id,
|
|
383
356
|
sourceId: sourceId !== null && sourceId !== void 0 ? sourceId : id,
|
|
384
|
-
clickable,
|
|
385
|
-
clickEventName,
|
|
386
357
|
locked,
|
|
387
358
|
meshPath: modelUrl,
|
|
388
359
|
position,
|
|
@@ -392,8 +363,6 @@ export const GameInstance = React.forwardRef(({ id, sourceId, clickable = false,
|
|
|
392
363
|
}), [
|
|
393
364
|
id,
|
|
394
365
|
sourceId,
|
|
395
|
-
clickable,
|
|
396
|
-
clickEventName,
|
|
397
366
|
locked,
|
|
398
367
|
modelUrl,
|
|
399
368
|
positionX,
|
|
@@ -2,18 +2,23 @@ import GameCanvas from "../../shared/GameCanvas";
|
|
|
2
2
|
import { Object3D, Texture } from "three";
|
|
3
3
|
import { GameObject, Prefab } from "./types";
|
|
4
4
|
import type { ExportGLBOptions } from "./utils";
|
|
5
|
-
import { type
|
|
5
|
+
import { type PrefabStoreApi } from "./prefabStore";
|
|
6
|
+
import type { SpawnOptions } from "./scene";
|
|
6
7
|
export interface PrefabEditorRef {
|
|
8
|
+
root: Object3D | null;
|
|
9
|
+
store: PrefabStoreApi;
|
|
10
|
+
getObject: (nodeId: string) => Object3D | null;
|
|
11
|
+
getRigidBody: (nodeId: string) => any;
|
|
7
12
|
screenshot: () => void;
|
|
8
13
|
exportGLB: (options?: ExportGLBOptions) => Promise<ArrayBuffer | undefined>;
|
|
9
14
|
exportGLBData: () => Promise<ArrayBuffer | undefined>;
|
|
10
15
|
clearSelection: () => Promise<void>;
|
|
11
16
|
save: () => Prefab;
|
|
12
|
-
scene: Scene;
|
|
13
17
|
load: (prefab: Prefab, options?: {
|
|
14
18
|
resetHistory?: boolean;
|
|
15
19
|
notifyChange?: boolean;
|
|
16
20
|
}) => void;
|
|
21
|
+
addNode: (node: GameObject, options?: SpawnOptions) => GameObject;
|
|
17
22
|
addModel: (path: string, model: Object3D, options?: SpawnOptions) => GameObject;
|
|
18
23
|
addTexture: (path: string, texture: Texture, options?: SpawnOptions) => GameObject;
|
|
19
24
|
}
|
|
@@ -10,7 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
11
11
|
import { MapControls, TransformControls } from "@react-three/drei";
|
|
12
12
|
import GameCanvas from "../../shared/GameCanvas";
|
|
13
|
-
import { useCallback, useEffect,
|
|
13
|
+
import { useCallback, useEffect, useReducer, useRef, useState, forwardRef, useImperativeHandle, createContext, useContext } from "react";
|
|
14
14
|
import { findComponentEntry } from "./types";
|
|
15
15
|
import PrefabRoot from "./PrefabRoot";
|
|
16
16
|
import { Physics } from "@react-three/rapier";
|
|
@@ -20,7 +20,6 @@ import { computeParentWorldMatrix, decompose, exportGLB as exportGLBFile, export
|
|
|
20
20
|
import { loadFiles } from "../dragdrop";
|
|
21
21
|
import { denormalizePrefab, createImageNode, createModelNode, createNode } from './prefab';
|
|
22
22
|
import { createPrefabStore, PrefabStoreProvider } from "./prefabStore";
|
|
23
|
-
import { createScene } from "./scene";
|
|
24
23
|
function isObjectAttachedToRoot(root, object) {
|
|
25
24
|
if (!root || !object)
|
|
26
25
|
return false;
|
|
@@ -53,7 +52,6 @@ const DEFAULT_PREFAB = {
|
|
|
53
52
|
root: createNode('Root', {}, { id: 'root' })
|
|
54
53
|
};
|
|
55
54
|
const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, mode: initialMode = PrefabEditorMode.Edit, onChange, showUI = true, enableWindowDrop = true, canvasProps, uiPlugins, children }, ref) => {
|
|
56
|
-
var _a, _b;
|
|
57
55
|
const [mode, setMode] = useState(initialMode);
|
|
58
56
|
const [selectedId, setSelectedId] = useState(null);
|
|
59
57
|
const [transformMode, setTransformMode] = useState("translate");
|
|
@@ -73,6 +71,7 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, mode
|
|
|
73
71
|
const onChangeRef = useRef(onChange);
|
|
74
72
|
const isEditMode = mode === PrefabEditorMode.Edit;
|
|
75
73
|
const getPrefab = useCallback(() => denormalizePrefab(prefabStore.getState()), [prefabStore]);
|
|
74
|
+
const getRootObject = useCallback(() => { var _a, _b; return (_b = (_a = prefabRootRef.current) === null || _a === void 0 ? void 0 : _a.root) !== null && _b !== void 0 ? _b : null; }, []);
|
|
76
75
|
const getObject = useCallback((nodeId) => { var _a, _b; return (_b = (_a = prefabRootRef.current) === null || _a === void 0 ? void 0 : _a.getObject(nodeId)) !== null && _b !== void 0 ? _b : null; }, []);
|
|
77
76
|
const getRigidBody = useCallback((nodeId) => { var _a, _b; return (_b = (_a = prefabRootRef.current) === null || _a === void 0 ? void 0 : _a.getRigidBody(nodeId)) !== null && _b !== void 0 ? _b : null; }, []);
|
|
78
77
|
const handleObjectRefChange = useCallback((nodeId) => {
|
|
@@ -173,7 +172,7 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, mode
|
|
|
173
172
|
return () => unsubscribe();
|
|
174
173
|
}, [prefabStore, selectedId]);
|
|
175
174
|
const selectedObject = selectedId ? getObject(selectedId) : null;
|
|
176
|
-
const transformObject = isObjectAttachedToRoot((
|
|
175
|
+
const transformObject = isObjectAttachedToRoot(getRootObject(), selectedObject)
|
|
177
176
|
? selectedObject
|
|
178
177
|
: null;
|
|
179
178
|
const addNode = useCallback((node, options) => {
|
|
@@ -251,21 +250,19 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, mode
|
|
|
251
250
|
});
|
|
252
251
|
}), [setSelection]);
|
|
253
252
|
const handleExportGLB = useCallback((...args_1) => __awaiter(void 0, [...args_1], void 0, function* (options = {}) {
|
|
254
|
-
var _a;
|
|
255
253
|
yield clearSelection();
|
|
256
|
-
const rootObject = (
|
|
254
|
+
const rootObject = getRootObject();
|
|
257
255
|
if (!rootObject)
|
|
258
256
|
return;
|
|
259
257
|
return exportGLBFile(rootObject, Object.assign({ filename: `${prefabStore.getState().prefabName || 'prefab'}.glb` }, options));
|
|
260
|
-
}), [clearSelection, prefabStore]);
|
|
258
|
+
}), [clearSelection, getRootObject, prefabStore]);
|
|
261
259
|
const handleExportGLBData = useCallback(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
262
|
-
var _a;
|
|
263
260
|
yield clearSelection();
|
|
264
|
-
const rootObject = (
|
|
261
|
+
const rootObject = getRootObject();
|
|
265
262
|
if (!rootObject)
|
|
266
263
|
return;
|
|
267
264
|
return exportGLBData(rootObject);
|
|
268
|
-
}), [clearSelection]);
|
|
265
|
+
}), [clearSelection, getRootObject]);
|
|
269
266
|
const handleFocusNode = useCallback((nodeId) => {
|
|
270
267
|
const object = getObject(nodeId);
|
|
271
268
|
const controls = controlsRef.current;
|
|
@@ -274,18 +271,6 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, mode
|
|
|
274
271
|
return;
|
|
275
272
|
focusCameraOnObject(object, camera, controls.target, () => { var _a; return (_a = controls.update) === null || _a === void 0 ? void 0 : _a.call(controls); });
|
|
276
273
|
}, [getObject]);
|
|
277
|
-
const scene = useMemo(() => createScene({
|
|
278
|
-
getRootId: () => prefabStore.getState().rootId,
|
|
279
|
-
getNode: (id) => { var _a; return (_a = prefabStore.getState().nodesById[id]) !== null && _a !== void 0 ? _a : null; },
|
|
280
|
-
getChildIds: (id) => { var _a; return (_a = prefabStore.getState().childIdsById[id]) !== null && _a !== void 0 ? _a : []; },
|
|
281
|
-
getParentId: (id) => { var _a; return (_a = prefabStore.getState().parentIdById[id]) !== null && _a !== void 0 ? _a : null; },
|
|
282
|
-
updateNode: (id, update) => prefabStore.getState().updateNode(id, update),
|
|
283
|
-
updateNodes: (updates) => prefabStore.getState().updateNodes(Object.entries(updates).map(([id, update]) => ({ id, update }))),
|
|
284
|
-
addNode: (node, options) => addNode(node, options).id,
|
|
285
|
-
removeNode: (id) => prefabStore.getState().deleteNode(id),
|
|
286
|
-
getObject,
|
|
287
|
-
getRigidBody,
|
|
288
|
-
}), [addNode, getObject, getRigidBody, prefabStore]);
|
|
289
274
|
const handleTransformChange = () => {
|
|
290
275
|
if (!selectedId)
|
|
291
276
|
return;
|
|
@@ -342,16 +327,20 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, mode
|
|
|
342
327
|
};
|
|
343
328
|
}, [addModel, addTexture, isEditMode, enableWindowDrop]);
|
|
344
329
|
useImperativeHandle(ref, () => ({
|
|
330
|
+
root: getRootObject(),
|
|
331
|
+
store: prefabStore,
|
|
332
|
+
getObject,
|
|
333
|
+
getRigidBody,
|
|
345
334
|
screenshot: handleScreenshot,
|
|
346
335
|
exportGLB: handleExportGLB,
|
|
347
336
|
exportGLBData: handleExportGLBData,
|
|
348
337
|
clearSelection,
|
|
349
338
|
save: getPrefab,
|
|
350
|
-
scene,
|
|
351
339
|
load: loadPrefab,
|
|
340
|
+
addNode,
|
|
352
341
|
addModel,
|
|
353
342
|
addTexture
|
|
354
|
-
}), [addModel, addTexture, clearSelection, getPrefab, handleExportGLB, handleExportGLBData, handleScreenshot, loadPrefab,
|
|
343
|
+
}), [addModel, addNode, addTexture, clearSelection, getObject, getPrefab, getRigidBody, getRootObject, handleExportGLB, handleExportGLBData, handleScreenshot, loadPrefab, prefabStore]);
|
|
355
344
|
const content = (_jsxs(_Fragment, { children: [isEditMode ? _jsx("gridHelper", { args: [10, 10], position: [0, -1, 0] }) : null, _jsx(PrefabRoot, { ref: prefabRootRef, store: prefabStore, editMode: isEditMode, selectedId: selectedId, onSelect: setSelection, onObjectRefChange: handleObjectRefChange, basePath: basePath }), children] }));
|
|
356
345
|
const handleCanvasCreated = useCallback((state) => {
|
|
357
346
|
var _a;
|