react-three-game 0.0.85 → 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 +87 -35
- 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 +94 -44
- 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.js +4 -2
- 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.js +1 -1
- 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 +2 -2
- 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`
|
|
@@ -163,7 +161,7 @@ interface GameObject {
|
|
|
163
161
|
|
|
164
162
|
## Runtime Mutation
|
|
165
163
|
|
|
166
|
-
Use
|
|
164
|
+
Use native object access for Three.js behavior and the prefab store for authored data changes.
|
|
167
165
|
|
|
168
166
|
```tsx
|
|
169
167
|
import { useEffect, useRef } from "react";
|
|
@@ -173,48 +171,105 @@ function RaiseBall() {
|
|
|
173
171
|
const editorRef = useRef<PrefabEditorRef>(null);
|
|
174
172
|
|
|
175
173
|
useEffect(() => {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
+
}));
|
|
182
187
|
}, []);
|
|
183
188
|
|
|
184
189
|
return <PrefabEditor ref={editorRef} initialPrefab={prefab} />;
|
|
185
190
|
}
|
|
186
191
|
```
|
|
187
192
|
|
|
188
|
-
|
|
193
|
+
For live Three.js access, use mounted objects directly:
|
|
189
194
|
|
|
190
195
|
```tsx
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
scene.find("orb2")?.getComponent("Transform")?.set("position", [-1, 0, 0]);
|
|
194
|
-
});
|
|
196
|
+
const ball = editorRef.current?.getObject("ball");
|
|
197
|
+
ball?.rotateY(0.5);
|
|
195
198
|
```
|
|
196
199
|
|
|
197
|
-
|
|
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:
|
|
198
207
|
|
|
199
208
|
```tsx
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
+
},
|
|
211
249
|
},
|
|
212
|
-
}
|
|
250
|
+
}),
|
|
213
251
|
},
|
|
214
|
-
|
|
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
|
+
]);
|
|
215
270
|
```
|
|
216
271
|
|
|
217
|
-
`View`
|
|
272
|
+
Custom component `View`s should use normal React and R3F behavior, such as `useFrame`, refs, and native Three.js APIs.
|
|
218
273
|
|
|
219
274
|
## Useful Exports
|
|
220
275
|
|
|
@@ -224,9 +279,6 @@ const Rotator: Component = {
|
|
|
224
279
|
* `PrefabEditorMode`
|
|
225
280
|
* `Prefab`
|
|
226
281
|
* `GameObject`
|
|
227
|
-
* `Scene`
|
|
228
|
-
* `Entity`
|
|
229
|
-
* `EntityComponent`
|
|
230
282
|
* `registerComponent`
|
|
231
283
|
* `createPrefabStore`
|
|
232
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;
|