react-three-game 0.0.82 → 0.0.84
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 +50 -2
- package/dist/index.d.ts +6 -3
- package/dist/index.js +3 -2
- package/dist/tools/prefabeditor/PrefabRoot.js +59 -50
- package/dist/tools/prefabeditor/components/CameraComponent.js +1 -2
- package/dist/tools/prefabeditor/components/ClickComponent.js +1 -1
- package/dist/tools/prefabeditor/components/ComponentRegistry.d.ts +3 -5
- package/dist/tools/prefabeditor/components/DirectionalLightComponent.js +3 -9
- package/dist/tools/prefabeditor/components/EnvironmentComponent.js +1 -1
- package/dist/tools/prefabeditor/components/MaterialComponent.d.ts +7 -0
- package/dist/tools/prefabeditor/components/MaterialComponent.js +18 -14
- package/dist/tools/prefabeditor/components/ModelComponent.js +1 -1
- package/dist/tools/prefabeditor/components/PhysicsComponent.js +1 -1
- package/dist/tools/prefabeditor/components/PointLightComponent.js +1 -1
- package/dist/tools/prefabeditor/components/SoundComponent.js +1 -2
- package/dist/tools/prefabeditor/components/SpotLightComponent.js +1 -1
- package/dist/tools/prefabeditor/runtime.d.ts +61 -0
- package/dist/tools/prefabeditor/runtime.js +184 -0
- package/dist/tools/prefabeditor/scene.d.ts +8 -0
- package/dist/tools/prefabeditor/scene.js +45 -8
- package/package.json +2 -2
- package/dist/tools/prefabeditor/runtimeContext.d.ts +0 -40
- package/dist/tools/prefabeditor/runtimeContext.js +0 -45
package/README.md
CHANGED
|
@@ -110,6 +110,16 @@ That means a saved scene is just a prefab, and the same prefab can be:
|
|
|
110
110
|
* rendered directly with `PrefabRoot`
|
|
111
111
|
* loaded inside another scene as reusable content
|
|
112
112
|
|
|
113
|
+
`PrefabRoot` keeps the rendering model narrow and compositional:
|
|
114
|
+
|
|
115
|
+
* `Transform` is the renderer-owned outer transform
|
|
116
|
+
* `Geometry` + `Material` become the primary mesh content
|
|
117
|
+
* non-instanced `Model` becomes the node's primary content
|
|
118
|
+
* `Physics` is a renderer-owned outer wrapper
|
|
119
|
+
* every other component `View` wraps the current subtree
|
|
120
|
+
|
|
121
|
+
Custom component `View`s use normal React Three Fiber composition with `children`.
|
|
122
|
+
|
|
113
123
|
## Prefab Format
|
|
114
124
|
|
|
115
125
|
```ts
|
|
@@ -131,7 +141,7 @@ interface GameObject {
|
|
|
131
141
|
|
|
132
142
|
## Runtime Mutation
|
|
133
143
|
|
|
134
|
-
|
|
144
|
+
Use the `Scene` API from `PrefabEditorRef` for authored state changes that should stay in prefab data.
|
|
135
145
|
|
|
136
146
|
```tsx
|
|
137
147
|
import { useEffect, useRef } from "react";
|
|
@@ -153,21 +163,59 @@ function RaiseBall() {
|
|
|
153
163
|
}
|
|
154
164
|
```
|
|
155
165
|
|
|
166
|
+
Batch related entity changes so they flush as one store revision:
|
|
167
|
+
|
|
168
|
+
```tsx
|
|
169
|
+
scene.batch(() => {
|
|
170
|
+
scene.find("orb1")?.getComponent("Transform")?.set("position", [1, 0, 0]);
|
|
171
|
+
scene.find("orb2")?.getComponent("Transform")?.set("position", [-1, 0, 0]);
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Define a component runtime factory with `create(ctx)` for hot runtime behavior that mutates the live Three.js object directly:
|
|
176
|
+
|
|
177
|
+
```tsx
|
|
178
|
+
import type { Component } from "react-three-game";
|
|
179
|
+
|
|
180
|
+
const Rotator: Component = {
|
|
181
|
+
name: "Rotator",
|
|
182
|
+
Editor: () => null,
|
|
183
|
+
defaultProperties: { speed: 1 },
|
|
184
|
+
create(ctx) {
|
|
185
|
+
return {
|
|
186
|
+
update(dt) {
|
|
187
|
+
const speed = ctx.component.get<number>("speed") ?? 1;
|
|
188
|
+
ctx.object.rotation.y += dt * speed;
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
`View` handles composition and rendering. `create(ctx)` handles imperative runtime behavior.
|
|
196
|
+
|
|
156
197
|
## Useful Exports
|
|
157
198
|
|
|
158
199
|
* `GameCanvas`
|
|
159
200
|
* `PrefabRoot`
|
|
160
201
|
* `PrefabEditor`
|
|
202
|
+
* `PrefabEditorMode`
|
|
161
203
|
* `Prefab`
|
|
162
204
|
* `GameObject`
|
|
163
205
|
* `Scene`
|
|
164
206
|
* `Entity`
|
|
165
207
|
* `EntityComponent`
|
|
166
208
|
* `registerComponent`
|
|
209
|
+
* `createPrefabStore`
|
|
210
|
+
* `usePrefabStoreApi`
|
|
211
|
+
* `useAssetRuntime()` / `useEntityRuntime()`
|
|
212
|
+
* `useEntityObjectRef()` / `useEntityRigidBodyRef()`
|
|
167
213
|
* `ground(...)`
|
|
168
214
|
* `loadJson()` / `saveJson()`
|
|
169
215
|
* `loadModel()` / `loadTexture()`
|
|
170
|
-
* `
|
|
216
|
+
* `loadSound()` / `loadFiles()`
|
|
217
|
+
* `exportGLB()`
|
|
218
|
+
* `computeParentWorldMatrix()`
|
|
171
219
|
|
|
172
220
|
## Development
|
|
173
221
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
export { default as GameCanvas } from './shared/GameCanvas';
|
|
2
2
|
export type { GameCanvasProps } from './shared/GameCanvas';
|
|
3
|
-
export { useBeforePhysicsStep, useAfterPhysicsStep, useRapier } from '@react-three/rapier';
|
|
4
3
|
export { ground } from './helpers';
|
|
5
4
|
export type { GroundOptions, Vec3 } from './helpers';
|
|
6
5
|
export { sound as soundManager } from './helpers/SoundManager';
|
|
@@ -21,12 +20,16 @@ export { createModelNode, createImageNode, } from './tools/prefabeditor/prefab';
|
|
|
21
20
|
export type { PrefabEditorProps, PrefabEditorRef, } from './tools/prefabeditor/PrefabEditor';
|
|
22
21
|
export type { SpawnOptions, Scene, Entity, EntityComponent, EntityData, EntityUpdate, PropertyPath, SceneUpdates, } from './tools/prefabeditor/scene';
|
|
23
22
|
export type { PrefabRootProps } from './tools/prefabeditor/PrefabRoot';
|
|
24
|
-
export type { AssetRuntime, EntityRuntime, LiveObjectRef, LiveRigidBodyRef } from './tools/prefabeditor/
|
|
25
|
-
export { useAssetRuntime, useEntityRuntime, useEntityObjectRef, useEntityRigidBodyRef } from './tools/prefabeditor/
|
|
23
|
+
export type { AssetRuntime, EntityRuntime, LiveObjectRef, LiveRigidBodyRef } from './tools/prefabeditor/runtime';
|
|
24
|
+
export { useAssetRuntime, useEntityRuntime, useEntityObjectRef, useEntityRigidBodyRef } from './tools/prefabeditor/runtime';
|
|
25
|
+
export type { ComponentInstance, ComponentRuntimeContext } from './tools/prefabeditor/runtime';
|
|
26
26
|
export type { Component, ComponentViewProps } from './tools/prefabeditor/components/ComponentRegistry';
|
|
27
27
|
export type { FieldDefinition, FieldType } from './tools/prefabeditor/components/Input';
|
|
28
|
+
export { MaterialOverridesProvider, useMaterialOverrides } from './tools/prefabeditor/components/MaterialComponent';
|
|
29
|
+
export type { MaterialOverrides } from './tools/prefabeditor/components/MaterialComponent';
|
|
28
30
|
export type { Prefab, GameObject, ComponentData } from './tools/prefabeditor/types';
|
|
29
31
|
export { findComponent, findComponentEntry, hasComponent } from './tools/prefabeditor/types';
|
|
32
|
+
export { float, positionLocal, sin, time, uniform, vec3, } from 'three/tsl';
|
|
30
33
|
export { gameEvents, useGameEvent, usePhysicsEvent, useClickEvent, getEntityIdFromRigidBody } from './tools/prefabeditor/GameEvents';
|
|
31
34
|
export type { GameEventType, GameEventMap, GameEventPayload, PhysicsEventType, InteractionEventType, PhysicsEventPayload, ClickEventPayload } from './tools/prefabeditor/GameEvents';
|
|
32
35
|
export { loadFiles } from './tools/dragdrop/DragDropLoader';
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// Core
|
|
2
2
|
export { default as GameCanvas } from './shared/GameCanvas';
|
|
3
|
-
export { useBeforePhysicsStep, useAfterPhysicsStep, useRapier } from '@react-three/rapier';
|
|
4
3
|
// Helpers
|
|
5
4
|
export { ground } from './helpers';
|
|
6
5
|
export { sound as soundManager } from './helpers/SoundManager';
|
|
@@ -20,8 +19,10 @@ export { FieldRenderer, FieldGroup, ListEditor, Label, Vector3Input, Vector3Fiel
|
|
|
20
19
|
// Prefab Editor - Utils
|
|
21
20
|
export { loadJson, saveJson, exportGLB, exportGLBData, regenerateIds, computeParentWorldMatrix, } from './tools/prefabeditor/utils';
|
|
22
21
|
export { createModelNode, createImageNode, } from './tools/prefabeditor/prefab';
|
|
23
|
-
export { useAssetRuntime, useEntityRuntime, useEntityObjectRef, useEntityRigidBodyRef } from './tools/prefabeditor/
|
|
22
|
+
export { useAssetRuntime, useEntityRuntime, useEntityObjectRef, useEntityRigidBodyRef } from './tools/prefabeditor/runtime';
|
|
23
|
+
export { MaterialOverridesProvider, useMaterialOverrides } from './tools/prefabeditor/components/MaterialComponent';
|
|
24
24
|
export { findComponent, findComponentEntry, hasComponent } from './tools/prefabeditor/types';
|
|
25
|
+
export { float, positionLocal, sin, time, uniform, vec3, } from 'three/tsl';
|
|
25
26
|
// Game Events (physics + custom events)
|
|
26
27
|
export { gameEvents, useGameEvent, usePhysicsEvent, useClickEvent, getEntityIdFromRigidBody } from './tools/prefabeditor/GameEvents';
|
|
27
28
|
// Asset Loading
|
|
@@ -13,6 +13,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
13
13
|
import { useHelper } from "@react-three/drei";
|
|
14
14
|
import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
|
|
15
15
|
import { BoxHelper, Euler, Matrix4, } from "three";
|
|
16
|
+
import { useFrame } from "@react-three/fiber";
|
|
16
17
|
import { useStore } from "zustand";
|
|
17
18
|
import { useClickValid } from "./useClickValid";
|
|
18
19
|
import { findComponent } from "./types";
|
|
@@ -22,9 +23,8 @@ import { loadModel, loadSound, loadTexture } from "../dragdrop";
|
|
|
22
23
|
import { GameInstance, GameInstanceProvider, getRepeatAxesFromModelProperties, useInstanceCheck } from "./InstanceProvider";
|
|
23
24
|
import { composeTransform, decompose } from "./utils";
|
|
24
25
|
import { isPhysicsProps } from "./components/PhysicsComponent";
|
|
25
|
-
import { denormalizePrefab } from "./prefab";
|
|
26
26
|
import { createPrefabStore, PrefabStoreProvider, useOptionalPrefabStoreApi, usePrefabChildIds, usePrefabNode, usePrefabRootId } from "./prefabStore";
|
|
27
|
-
import { AssetRuntimeContext, EntityRuntimeScope } from "./
|
|
27
|
+
import { AssetRuntimeContext, EntityRuntimeScope, createRuntimeEngine } from "./runtime";
|
|
28
28
|
import { sound as soundManager } from "../../helpers/SoundManager";
|
|
29
29
|
builtinComponents.forEach(registerComponent);
|
|
30
30
|
const IDENTITY = new Matrix4();
|
|
@@ -61,7 +61,13 @@ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSel
|
|
|
61
61
|
const rigidBodyRefs = useRef(new Map());
|
|
62
62
|
const rootRef = useRef(null);
|
|
63
63
|
const parentStore = useOptionalPrefabStoreApi();
|
|
64
|
-
const [ownedStore] = useState(() => {
|
|
64
|
+
const [ownedStore] = useState(() => {
|
|
65
|
+
if (data)
|
|
66
|
+
return createPrefabStore(data);
|
|
67
|
+
if (store || parentStore)
|
|
68
|
+
return null;
|
|
69
|
+
throw new Error("PrefabRoot requires either a `data` or `store` prop");
|
|
70
|
+
});
|
|
65
71
|
const resolvedStore = (_a = store !== null && store !== void 0 ? store : parentStore) !== null && _a !== void 0 ? _a : ownedStore;
|
|
66
72
|
const usesOwnedStore = resolvedStore === ownedStore;
|
|
67
73
|
const shouldProvideStoreContext = !parentStore || parentStore !== resolvedStore;
|
|
@@ -73,6 +79,10 @@ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSel
|
|
|
73
79
|
var _a;
|
|
74
80
|
return (_a = objectRefs.current[id]) !== null && _a !== void 0 ? _a : null;
|
|
75
81
|
}, []);
|
|
82
|
+
const getRigidBody = useCallback((id) => {
|
|
83
|
+
var _a;
|
|
84
|
+
return (_a = rigidBodyRefs.current.get(id)) !== null && _a !== void 0 ? _a : null;
|
|
85
|
+
}, []);
|
|
76
86
|
useImperativeHandle(ref, () => ({
|
|
77
87
|
root: rootRef.current,
|
|
78
88
|
getObject,
|
|
@@ -84,17 +94,29 @@ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSel
|
|
|
84
94
|
setInjectedSounds(prev => (Object.assign(Object.assign({}, prev), { [path]: sound })));
|
|
85
95
|
},
|
|
86
96
|
}), [getObject]);
|
|
97
|
+
const runtimeEngine = useMemo(() => createRuntimeEngine({
|
|
98
|
+
store: resolvedStore,
|
|
99
|
+
getObject,
|
|
100
|
+
getRigidBody,
|
|
101
|
+
}), [resolvedStore, getObject, getRigidBody]);
|
|
87
102
|
const registerRef = useCallback((id, obj) => {
|
|
88
103
|
objectRefs.current[id] = obj;
|
|
104
|
+
runtimeEngine.invalidate();
|
|
89
105
|
onObjectRefChange === null || onObjectRefChange === void 0 ? void 0 : onObjectRefChange(id, obj);
|
|
90
|
-
}, [onObjectRefChange]);
|
|
106
|
+
}, [onObjectRefChange, runtimeEngine]);
|
|
91
107
|
const registerRigidBodyRef = useCallback((id, rb) => {
|
|
92
108
|
rigidBodyRefs.current.set(id, rb);
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}, []);
|
|
109
|
+
runtimeEngine.invalidate();
|
|
110
|
+
}, [runtimeEngine]);
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
runtimeEngine.setActive(!editMode);
|
|
113
|
+
}, [editMode, runtimeEngine]);
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
return () => runtimeEngine.dispose();
|
|
116
|
+
}, [runtimeEngine]);
|
|
117
|
+
useFrame((_, dt) => {
|
|
118
|
+
runtimeEngine.tick(dt);
|
|
119
|
+
});
|
|
98
120
|
useEffect(() => {
|
|
99
121
|
if (usesOwnedStore && data) {
|
|
100
122
|
resolvedStore.getState().replacePrefab(data);
|
|
@@ -157,26 +179,15 @@ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSel
|
|
|
157
179
|
};
|
|
158
180
|
syncAssets();
|
|
159
181
|
}, [resolvedStore, assetManifestKey, basePath, injectedModels, injectedSounds, injectedTextures, models, sounds, textures]);
|
|
160
|
-
// Keep refs current so context getters are always fresh without changing context identity
|
|
161
|
-
const availableModelsRef = useRef(availableModels);
|
|
162
|
-
availableModelsRef.current = availableModels;
|
|
163
|
-
const availableTexturesRef = useRef(availableTextures);
|
|
164
|
-
availableTexturesRef.current = availableTextures;
|
|
165
|
-
const availableSoundsRef = useRef(availableSounds);
|
|
166
|
-
availableSoundsRef.current = availableSounds;
|
|
167
182
|
const assetRuntime = useMemo(() => ({
|
|
168
183
|
getObject,
|
|
169
184
|
getRigidBody,
|
|
170
185
|
registerRigidBodyRef,
|
|
171
|
-
getModel: (path) => { var _a; return (_a =
|
|
172
|
-
getTexture: (path) => { var _a; return (_a =
|
|
173
|
-
getSound: (path) => { var _a; return (_a =
|
|
174
|
-
getAssetRevision: () => {
|
|
175
|
-
|
|
176
|
-
const textureKeys = Object.keys(availableTexturesRef.current).sort().join('|');
|
|
177
|
-
return `${textureKeys}::${modelKeys}`;
|
|
178
|
-
},
|
|
179
|
-
}), [getObject, getRigidBody, registerRigidBodyRef]);
|
|
186
|
+
getModel: (path) => { var _a; return (_a = availableModels[path]) !== null && _a !== void 0 ? _a : null; },
|
|
187
|
+
getTexture: (path) => { var _a; return (_a = availableTextures[path]) !== null && _a !== void 0 ? _a : null; },
|
|
188
|
+
getSound: (path) => { var _a; return (_a = availableSounds[path]) !== null && _a !== void 0 ? _a : null; },
|
|
189
|
+
getAssetRevision: () => `${Object.keys(availableTextures).sort().join('|')}::${Object.keys(availableModels).sort().join('|')}`,
|
|
190
|
+
}), [getObject, getRigidBody, registerRigidBodyRef, availableModels, availableTextures, availableSounds]);
|
|
180
191
|
const content = (_jsx("group", { ref: rootRef, children: _jsx(GameInstanceProvider, { models: availableModels, selectedId: selectedId, editMode: editMode, onSelect: editMode ? onSelect : undefined, registerRef: registerRef, children: _jsx(StoreRootNode, { selectedId: selectedId, onSelect: editMode ? onSelect : undefined, onClick: onClick, registerRef: registerRef, loadedModels: availableModels, editMode: editMode, parentMatrix: IDENTITY }) }) }));
|
|
181
192
|
if (!shouldProvideStoreContext) {
|
|
182
193
|
return _jsx(AssetRuntimeContext.Provider, { value: assetRuntime, children: content });
|
|
@@ -197,6 +208,7 @@ export function GameObjectRenderer(props) {
|
|
|
197
208
|
if (prevInstancedRef.current !== undefined && prevInstancedRef.current !== isInstanced) {
|
|
198
209
|
setIsTransitioning(true);
|
|
199
210
|
const timer = setTimeout(() => setIsTransitioning(false), 100);
|
|
211
|
+
prevInstancedRef.current = isInstanced;
|
|
200
212
|
return () => clearTimeout(timer);
|
|
201
213
|
}
|
|
202
214
|
prevInstancedRef.current = isInstanced;
|
|
@@ -225,18 +237,18 @@ function InstancedNode({ nodeId, parentMatrix = IDENTITY, editMode, registerRef,
|
|
|
225
237
|
const modelUrl = (_c = (_b = findComponent(gameObject, "Model")) === null || _b === void 0 ? void 0 : _b.properties) === null || _c === void 0 ? void 0 : _c.filename;
|
|
226
238
|
const instances = useMemo(() => buildRepeatedInstances(gameObject, parentMatrix, modelUrl, physicsProps), [gameObject, modelUrl, parentMatrix, physicsProps]);
|
|
227
239
|
const groupRef = useRef(null);
|
|
240
|
+
const handleGroupRef = useCallback((object) => {
|
|
241
|
+
groupRef.current = object;
|
|
242
|
+
if (editMode) {
|
|
243
|
+
registerRef(nodeId, object);
|
|
244
|
+
}
|
|
245
|
+
}, [editMode, nodeId, registerRef]);
|
|
228
246
|
const editClickHandlers = useClickValid(!!editMode && !isLocked, (e) => {
|
|
229
247
|
onSelect === null || onSelect === void 0 ? void 0 : onSelect(nodeId);
|
|
230
248
|
onClick === null || onClick === void 0 ? void 0 : onClick(e, gameObject);
|
|
231
249
|
});
|
|
232
|
-
useEffect(() => {
|
|
233
|
-
if (editMode) {
|
|
234
|
-
registerRef(nodeId, groupRef.current);
|
|
235
|
-
return () => registerRef(nodeId, null);
|
|
236
|
-
}
|
|
237
|
-
}, [nodeId, registerRef, editMode]);
|
|
238
250
|
if (editMode) {
|
|
239
|
-
return (_jsxs(_Fragment, { children: [_jsx("group", Object.assign({ ref:
|
|
251
|
+
return (_jsxs(_Fragment, { children: [_jsx("group", Object.assign({ ref: handleGroupRef, position: localTransform.position, rotation: localTransform.rotation, scale: localTransform.scale }, editClickHandlers, { children: _jsx("mesh", { visible: false, children: _jsx("boxGeometry", { args: [0.01, 0.01, 0.01] }) }) })), instances.map(instance => (_jsx(GameInstance, { id: instance.id, sourceId: gameObject.id, clickable: clickable, clickEventName: clickEventName, modelUrl: instance.modelUrl, position: instance.position, rotation: instance.rotation, scale: instance.scale, locked: isLocked, physics: instance.physics }, instance.id)))] }));
|
|
240
252
|
}
|
|
241
253
|
return (_jsx(_Fragment, { children: instances.map(instance => (_jsx(GameInstance, { id: instance.id, sourceId: gameObject.id, clickable: clickable, clickEventName: clickEventName, modelUrl: instance.modelUrl, position: instance.position, rotation: instance.rotation, scale: instance.scale, locked: isLocked, physics: instance.physics }, instance.id))) }));
|
|
242
254
|
}
|
|
@@ -245,21 +257,25 @@ function StandardNode({ nodeId, selectedId, onSelect, onClick, registerRef, load
|
|
|
245
257
|
const gameObject = usePrefabNode(nodeId);
|
|
246
258
|
const childIds = usePrefabChildIds(nodeId);
|
|
247
259
|
const isSelected = selectedId === nodeId;
|
|
248
|
-
|
|
249
|
-
|
|
260
|
+
const isLocked = Boolean(gameObject === null || gameObject === void 0 ? void 0 : gameObject.locked);
|
|
261
|
+
const stillInstanced = useInstanceCheck(nodeId);
|
|
250
262
|
const groupRef = useRef(null);
|
|
251
263
|
const helperRef = useRef(null);
|
|
252
|
-
const
|
|
253
|
-
|
|
264
|
+
const handleGroupRef = useCallback((object) => {
|
|
265
|
+
groupRef.current = object;
|
|
266
|
+
registerRef(nodeId, object);
|
|
267
|
+
}, [nodeId, registerRef]);
|
|
268
|
+
const handleHelperRef = useCallback((object) => {
|
|
269
|
+
helperRef.current = object;
|
|
270
|
+
}, []);
|
|
254
271
|
const clickHandlers = useClickValid(!!editMode && !isLocked, (e) => {
|
|
255
272
|
onSelect === null || onSelect === void 0 ? void 0 : onSelect(nodeId);
|
|
256
|
-
|
|
273
|
+
if (gameObject)
|
|
274
|
+
onClick === null || onClick === void 0 ? void 0 : onClick(e, gameObject);
|
|
257
275
|
});
|
|
258
276
|
useHelper(editMode && isSelected ? helperRef : null, BoxHelper, "cyan");
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
return () => registerRef(nodeId, null);
|
|
262
|
-
}, [nodeId, registerRef]);
|
|
277
|
+
if (!gameObject)
|
|
278
|
+
return null;
|
|
263
279
|
const world = parentMatrix.clone().multiply(compose(gameObject));
|
|
264
280
|
const physics = findComponent(gameObject, "Physics");
|
|
265
281
|
const ready = isNodeReady(gameObject, loadedModels);
|
|
@@ -272,7 +288,7 @@ function StandardNode({ nodeId, selectedId, onSelect, onClick, registerRef, load
|
|
|
272
288
|
const childNodes = _jsx(ChildNodes, { childIds: childIds, parentMatrix: world, selectedId: selectedId, onSelect: onSelect, onClick: onClick, registerRef: registerRef, loadedModels: loadedModels, editMode: editMode });
|
|
273
289
|
const inner = (_jsx("group", Object.assign({}, clickHandlers, { children: renderCompositionNode(gameObject, renderCtx, childNodes) })));
|
|
274
290
|
const physicsInner = editMode ? _jsx("group", { visible: false, children: inner }) : inner;
|
|
275
|
-
return (_jsx(EntityRuntimeScope, { nodeId: nodeId, editMode: editMode, isSelected: isSelected, children: editMode ? (_jsxs(_Fragment, { children: [_jsx("group", { ref:
|
|
291
|
+
return (_jsx(EntityRuntimeScope, { nodeId: nodeId, editMode: editMode, isSelected: isSelected, children: editMode ? (_jsxs(_Fragment, { children: [_jsx("group", { ref: handleGroupRef, 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: handleHelperRef, 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, children: physicsInner }, physicsKey)) : null] })) : 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, children: inner }, physicsKey)) : (_jsx("group", { ref: handleGroupRef, position: transform.position, rotation: transform.rotation, scale: transform.scale, children: inner })) }));
|
|
276
292
|
}
|
|
277
293
|
function isRendererHandledComponent(componentType) {
|
|
278
294
|
return componentType === "Transform"
|
|
@@ -284,7 +300,6 @@ function isRendererHandledComponent(componentType) {
|
|
|
284
300
|
function getCompositionComponents(gameObject) {
|
|
285
301
|
var _a;
|
|
286
302
|
return Object.entries((_a = gameObject.components) !== null && _a !== void 0 ? _a : {}).reduce((result, [key, comp]) => {
|
|
287
|
-
var _a;
|
|
288
303
|
if (!(comp === null || comp === void 0 ? void 0 : comp.type) || isRendererHandledComponent(comp.type))
|
|
289
304
|
return result;
|
|
290
305
|
const def = getComponentDef(comp.type);
|
|
@@ -294,7 +309,6 @@ function getCompositionComponents(gameObject) {
|
|
|
294
309
|
key,
|
|
295
310
|
View: def.View,
|
|
296
311
|
properties: comp.properties,
|
|
297
|
-
composition: (_a = def.composition) !== null && _a !== void 0 ? _a : "wrap",
|
|
298
312
|
});
|
|
299
313
|
return result;
|
|
300
314
|
}, []);
|
|
@@ -394,11 +408,6 @@ function renderNodePrimaryContent(gameObject, ctx) {
|
|
|
394
408
|
}
|
|
395
409
|
function applyNodeComposition(gameObject, subtree) {
|
|
396
410
|
const components = getCompositionComponents(gameObject);
|
|
397
|
-
return components.reduce((acc, { key, View, properties
|
|
398
|
-
? (_jsxs(_Fragment, { children: [_jsx(View, { properties: properties }, key), acc] }))
|
|
399
|
-
: (_jsx(View, { properties: properties, children: acc }, key)), subtree);
|
|
411
|
+
return components.reduce((acc, { key, View, properties }) => (_jsx(View, { properties: properties, children: acc }, key)), subtree);
|
|
400
412
|
}
|
|
401
413
|
export default PrefabRoot;
|
|
402
|
-
function missingStoreState() {
|
|
403
|
-
throw new Error("PrefabRoot requires either data or store");
|
|
404
|
-
}
|
|
@@ -3,7 +3,7 @@ import { OrthographicCamera, PerspectiveCamera, useHelper } from '@react-three/d
|
|
|
3
3
|
import { useRef } from 'react';
|
|
4
4
|
import { CameraHelper } from 'three';
|
|
5
5
|
import { useFrame, useThree } from '@react-three/fiber';
|
|
6
|
-
import { useEntityRuntime } from '../
|
|
6
|
+
import { useEntityRuntime } from '../runtime';
|
|
7
7
|
import { FieldGroup, NumberField, SelectField } from './Input';
|
|
8
8
|
const CAMERA_PROJECTION_OPTIONS = [
|
|
9
9
|
{ value: 'perspective', label: 'Perspective' },
|
|
@@ -66,7 +66,6 @@ const CameraComponent = {
|
|
|
66
66
|
name: 'Camera',
|
|
67
67
|
Editor: CameraComponentEditor,
|
|
68
68
|
View: CameraComponentView,
|
|
69
|
-
composition: 'wrap',
|
|
70
69
|
defaultProperties: cameraDefaults,
|
|
71
70
|
};
|
|
72
71
|
export default CameraComponent;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useRef } from 'react';
|
|
3
3
|
import { gameEvents } from '../GameEvents';
|
|
4
|
-
import { useEntityRuntime } from '../
|
|
4
|
+
import { useEntityRuntime } from '../runtime';
|
|
5
5
|
import { EventField, FieldGroup } from './Input';
|
|
6
6
|
function ClickComponentEditor({ component, onUpdate }) {
|
|
7
7
|
return (_jsxs(FieldGroup, { children: [_jsx("div", { style: { fontSize: 12, opacity: 0.8 }, children: "Emits a game event in play mode when this entity is clicked." }), _jsx(EventField, { name: "eventName", label: "Emit Event", values: component.properties, onChange: onUpdate, placeholder: "click" })] }));
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { FC } from "react";
|
|
2
2
|
import { ComponentData, GameObject } from "../types";
|
|
3
|
+
import type { ComponentInstance, ComponentRuntimeContext } from "../runtime";
|
|
3
4
|
export type AssetRef = {
|
|
4
5
|
type: "model" | "texture" | "sound";
|
|
5
6
|
path: string;
|
|
@@ -27,11 +28,8 @@ export interface Component {
|
|
|
27
28
|
}>;
|
|
28
29
|
defaultProperties: any;
|
|
29
30
|
View?: FC<ComponentViewProps>;
|
|
30
|
-
/**
|
|
31
|
-
|
|
32
|
-
* Defaults to `wrap`; `sibling` renders next to the current subtree.
|
|
33
|
-
*/
|
|
34
|
-
composition?: "wrap" | "sibling";
|
|
31
|
+
/** Optional runtime factory for the non-React game loop. */
|
|
32
|
+
create?: (ctx: ComponentRuntimeContext) => ComponentInstance | void;
|
|
35
33
|
/** Declare which asset paths this component references (for asset loading). */
|
|
36
34
|
getAssetRefs?: (properties: Record<string, any>) => AssetRef[];
|
|
37
35
|
}
|
|
@@ -2,8 +2,8 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { useHelper } from "@react-three/drei";
|
|
3
3
|
import { useRef, useEffect, useState } from "react";
|
|
4
4
|
import { useFrame } from "@react-three/fiber";
|
|
5
|
-
import { CameraHelper
|
|
6
|
-
import { useEntityRuntime } from "../
|
|
5
|
+
import { CameraHelper } from "three";
|
|
6
|
+
import { useEntityRuntime } from "../runtime";
|
|
7
7
|
import { BooleanField, ColorField, NumberField, NumberInput, Vector3Input } from "./Input";
|
|
8
8
|
import { LightSection, ShadowBiasField, mergeWithDefaults } from "./lightUtils";
|
|
9
9
|
import { colors } from "../styles";
|
|
@@ -161,13 +161,7 @@ function DirectionalLightView({ properties, children }) {
|
|
|
161
161
|
shadowCamera.updateMatrixWorld();
|
|
162
162
|
}
|
|
163
163
|
});
|
|
164
|
-
return (_jsxs(_Fragment, { children: [_jsx("directionalLight", { ref: directionalLightRef, color: color, intensity: intensity, castShadow: castShadow, "shadow-mapSize-width": shadowMapSize, "shadow-mapSize-height": shadowMapSize, "shadow-camera-near": shadowCameraNear, "shadow-camera-far": shadowCameraFar, "shadow-camera-top": shadowCameraTop, "shadow-camera-bottom": shadowCameraBottom, "shadow-camera-left": shadowCameraLeft, "shadow-camera-right": shadowCameraRight, "shadow-bias": shadowBias, "shadow-normalBias": shadowNormalBias, "shadow-autoUpdate": shadowAutoUpdate }), _jsx("object3D", { ref: targetRef, position: targetOffset }), editMode && isSelected && (_jsxs(_Fragment, { children: [_jsxs("mesh", { children: [_jsx("sphereGeometry", { args: [0.3, 8, 6] }), _jsx("meshBasicMaterial", { color: color, wireframe: true })] }), _jsxs("mesh", { position: targetOffset, children: [_jsx("sphereGeometry", { args: [0.2, 8, 6] }), _jsx("meshBasicMaterial", { color: color, wireframe: true, opacity: 0.5, transparent: true })] }), _jsxs("line", { children: [_jsx("bufferGeometry", {
|
|
165
|
-
const points = [
|
|
166
|
-
new Vector3(0, 0, 0),
|
|
167
|
-
new Vector3(targetOffset[0], targetOffset[1], targetOffset[2])
|
|
168
|
-
];
|
|
169
|
-
geo.setFromPoints(points);
|
|
170
|
-
} }), _jsx("lineBasicMaterial", { color: color, opacity: 0.6, transparent: true })] })] })), children] }));
|
|
164
|
+
return (_jsxs(_Fragment, { children: [_jsx("directionalLight", { ref: directionalLightRef, color: color, intensity: intensity, castShadow: castShadow, "shadow-mapSize-width": shadowMapSize, "shadow-mapSize-height": shadowMapSize, "shadow-camera-near": shadowCameraNear, "shadow-camera-far": shadowCameraFar, "shadow-camera-top": shadowCameraTop, "shadow-camera-bottom": shadowCameraBottom, "shadow-camera-left": shadowCameraLeft, "shadow-camera-right": shadowCameraRight, "shadow-bias": shadowBias, "shadow-normalBias": shadowNormalBias, "shadow-autoUpdate": shadowAutoUpdate }), _jsx("object3D", { ref: targetRef, position: targetOffset }), editMode && isSelected && (_jsxs(_Fragment, { children: [_jsxs("mesh", { children: [_jsx("sphereGeometry", { args: [0.3, 8, 6] }), _jsx("meshBasicMaterial", { color: color, wireframe: true })] }), _jsxs("mesh", { position: targetOffset, children: [_jsx("sphereGeometry", { args: [0.2, 8, 6] }), _jsx("meshBasicMaterial", { color: color, wireframe: true, opacity: 0.5, transparent: true })] }), _jsxs("line", { children: [_jsx("bufferGeometry", { children: _jsx("bufferAttribute", { attach: "attributes-position", args: [new Float32Array([0, 0, 0, targetOffset[0], targetOffset[1], targetOffset[2]]), 3] }) }), _jsx("lineBasicMaterial", { color: color, opacity: 0.6, transparent: true })] })] })), children] }));
|
|
171
165
|
}
|
|
172
166
|
const DirectionalLightComponent = {
|
|
173
167
|
name: 'DirectionalLight',
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Environment } from '@react-three/drei';
|
|
3
3
|
import { FieldGroup, NumberField } from './Input';
|
|
4
|
-
import { useAssetRuntime } from '../
|
|
4
|
+
import { useAssetRuntime } from '../runtime';
|
|
5
5
|
function EnvironmentView({ properties, children, }) {
|
|
6
6
|
const { getAssetRevision } = useAssetRuntime();
|
|
7
7
|
const { intensity = 1, resolution = 256 } = properties;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
1
2
|
import type { ThreeElement } from '@react-three/fiber';
|
|
2
3
|
import { Component } from './ComponentRegistry';
|
|
3
4
|
import { MeshBasicNodeMaterial, MeshStandardNodeMaterial } from 'three/webgpu';
|
|
@@ -22,5 +23,11 @@ export interface MaterialProps extends Omit<MeshStandardMaterialProperties & Mes
|
|
|
22
23
|
normalMapTexture?: string;
|
|
23
24
|
normalScale?: [number, number];
|
|
24
25
|
}
|
|
26
|
+
export type MaterialOverrides = Record<string, unknown>;
|
|
27
|
+
export declare function useMaterialOverrides(): MaterialOverrides;
|
|
28
|
+
export declare function MaterialOverridesProvider({ overrides, children, }: {
|
|
29
|
+
overrides: MaterialOverrides;
|
|
30
|
+
children: ReactNode;
|
|
31
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
25
32
|
declare const MaterialComponent: Component;
|
|
26
33
|
export default MaterialComponent;
|
|
@@ -10,13 +10,23 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
10
10
|
return t;
|
|
11
11
|
};
|
|
12
12
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
13
|
+
import { createContext, useContext, useMemo } from 'react';
|
|
13
14
|
import { extend } from '@react-three/fiber';
|
|
14
15
|
import { FieldRenderer, Label, NumberInput } from './Input';
|
|
15
|
-
import { useAssetRuntime } from '../
|
|
16
|
-
import { useMemo } from 'react';
|
|
16
|
+
import { useAssetRuntime } from '../runtime';
|
|
17
17
|
import { MeshBasicNodeMaterial, MeshStandardNodeMaterial } from 'three/webgpu';
|
|
18
18
|
import { TexturePicker } from '../../assetviewer/page';
|
|
19
|
-
import { RepeatWrapping, ClampToEdgeWrapping, SRGBColorSpace, LinearSRGBColorSpace,
|
|
19
|
+
import { RepeatWrapping, ClampToEdgeWrapping, SRGBColorSpace, LinearSRGBColorSpace, NearestFilter, LinearFilter, NearestMipmapNearestFilter, NearestMipmapLinearFilter, LinearMipmapNearestFilter, LinearMipmapLinearFilter, FrontSide, BackSide, DoubleSide, } from 'three';
|
|
20
|
+
const EMPTY_MATERIAL_OVERRIDES = Object.freeze({});
|
|
21
|
+
const MaterialOverridesContext = createContext(EMPTY_MATERIAL_OVERRIDES);
|
|
22
|
+
export function useMaterialOverrides() {
|
|
23
|
+
return useContext(MaterialOverridesContext);
|
|
24
|
+
}
|
|
25
|
+
export function MaterialOverridesProvider({ overrides, children, }) {
|
|
26
|
+
const parent = useContext(MaterialOverridesContext);
|
|
27
|
+
const merged = useMemo(() => (Object.assign(Object.assign({}, parent), overrides)), [parent, overrides]);
|
|
28
|
+
return _jsx(MaterialOverridesContext.Provider, { value: merged, children: children });
|
|
29
|
+
}
|
|
20
30
|
extend({
|
|
21
31
|
MeshBasicNodeMaterial,
|
|
22
32
|
MeshStandardNodeMaterial,
|
|
@@ -121,7 +131,7 @@ function MaterialComponentEditor({ component, onUpdate, basePath = "" }) {
|
|
|
121
131
|
}
|
|
122
132
|
// View for Material component
|
|
123
133
|
function MaterialComponentView({ properties: rawProps }) {
|
|
124
|
-
var _a, _b, _c, _d, _e;
|
|
134
|
+
var _a, _b, _c, _d, _e, _f;
|
|
125
135
|
const { getTexture } = useAssetRuntime();
|
|
126
136
|
const properties = rawProps;
|
|
127
137
|
const materialType = (_a = properties === null || properties === void 0 ? void 0 : properties.materialType) !== null && _a !== void 0 ? _a : 'standard';
|
|
@@ -181,21 +191,15 @@ function MaterialComponentView({ properties: rawProps }) {
|
|
|
181
191
|
t.needsUpdate = true;
|
|
182
192
|
return t;
|
|
183
193
|
}, [normalMapTexture]);
|
|
184
|
-
const normalScaleVec = useMemo(() => {
|
|
185
|
-
var _a, _b;
|
|
186
|
-
if (!finalNormalMap)
|
|
187
|
-
return undefined;
|
|
188
|
-
return new Vector2((_a = normalScaleProp === null || normalScaleProp === void 0 ? void 0 : normalScaleProp[0]) !== null && _a !== void 0 ? _a : 1, (_b = normalScaleProp === null || normalScaleProp === void 0 ? void 0 : normalScaleProp[1]) !== null && _b !== void 0 ? _b : 1);
|
|
189
|
-
}, [finalNormalMap, normalScaleProp === null || normalScaleProp === void 0 ? void 0 : normalScaleProp[0], normalScaleProp === null || normalScaleProp === void 0 ? void 0 : normalScaleProp[1]]);
|
|
190
194
|
if (!properties) {
|
|
191
195
|
return _jsx("meshStandardNodeMaterial", { color: "red", wireframe: true });
|
|
192
196
|
}
|
|
193
|
-
const
|
|
194
|
-
const sharedProps = Object.assign({ map: finalTexture, side: resolvedSide }, materialProps);
|
|
197
|
+
const overrides = useMaterialOverrides();
|
|
198
|
+
const sharedProps = Object.assign(Object.assign({ map: finalTexture, side: resolvedSide }, materialProps), overrides);
|
|
195
199
|
if (materialType === 'basic') {
|
|
196
|
-
return _jsx("meshBasicNodeMaterial", Object.assign({}, sharedProps)
|
|
200
|
+
return _jsx("meshBasicNodeMaterial", Object.assign({}, sharedProps));
|
|
197
201
|
}
|
|
198
|
-
return (_jsx("meshStandardNodeMaterial", Object.assign({}, sharedProps, { normalMap: finalNormalMap, normalScale:
|
|
202
|
+
return (_jsx("meshStandardNodeMaterial", Object.assign({}, sharedProps, { normalMap: finalNormalMap, normalScale: finalNormalMap ? [(_e = normalScaleProp === null || normalScaleProp === void 0 ? void 0 : normalScaleProp[0]) !== null && _e !== void 0 ? _e : 1, (_f = normalScaleProp === null || normalScaleProp === void 0 ? void 0 : normalScaleProp[1]) !== null && _f !== void 0 ? _f : 1] : undefined })));
|
|
199
203
|
}
|
|
200
204
|
const MaterialComponent = {
|
|
201
205
|
name: 'Material',
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { ModelPicker } from '../../assetviewer/page';
|
|
3
3
|
import { useContext, useMemo } from 'react';
|
|
4
4
|
import { BooleanField, FieldGroup, Label, ListEditor, NumberInput, SelectInput } from './Input';
|
|
5
|
-
import { useAssetRuntime } from '../
|
|
5
|
+
import { useAssetRuntime } from '../runtime';
|
|
6
6
|
import { EditorContext } from '../PrefabEditor';
|
|
7
7
|
import { getRepeatAxesFromModelProperties, normalizeRepeatAxes } from '../InstanceProvider';
|
|
8
8
|
import { colors } from '../styles';
|
|
@@ -12,7 +12,7 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
12
12
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
13
13
|
import { CapsuleCollider, RigidBody, useRapier } from "@react-three/rapier";
|
|
14
14
|
import { useRef, useEffect, useCallback } from 'react';
|
|
15
|
-
import { useAssetRuntime, useEntityRuntime } from "../
|
|
15
|
+
import { useAssetRuntime, useEntityRuntime } from "../runtime";
|
|
16
16
|
import { BooleanField, EventInput, FieldGroup, ListEditor, NumberField, SelectField, SelectInput, Vector3Field } from "./Input";
|
|
17
17
|
import { gameEvents, getEntityIdFromRigidBody } from "../GameEvents";
|
|
18
18
|
import { colors } from "../styles";
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { useEffect, useRef } from 'react';
|
|
3
3
|
import { useHelper } from '@react-three/drei';
|
|
4
4
|
import { PointLightHelper } from 'three';
|
|
5
|
-
import { useEntityRuntime } from '../
|
|
5
|
+
import { useEntityRuntime } from '../runtime';
|
|
6
6
|
import { BooleanField, ColorField, NumberField } from './Input';
|
|
7
7
|
import { LightSection, ShadowBiasField, mergeWithDefaults } from './lightUtils';
|
|
8
8
|
const pointLightDefaults = {
|
|
@@ -4,7 +4,7 @@ import { useThree } from '@react-three/fiber';
|
|
|
4
4
|
import { SoundPicker } from '../../assetviewer/page';
|
|
5
5
|
import { sound as soundManager } from '../../../helpers/SoundManager';
|
|
6
6
|
import { gameEvents } from '../GameEvents';
|
|
7
|
-
import { useAssetRuntime, useEntityRuntime } from '../
|
|
7
|
+
import { useAssetRuntime, useEntityRuntime } from '../runtime';
|
|
8
8
|
import { BooleanField, EventField, FieldGroup, FieldRenderer, ListEditor, NumberField, SelectField } from './Input';
|
|
9
9
|
import { colors } from '../styles';
|
|
10
10
|
import { AudioListener } from 'three';
|
|
@@ -212,7 +212,6 @@ const SoundComponent = {
|
|
|
212
212
|
name: 'Sound',
|
|
213
213
|
Editor: SoundComponentEditor,
|
|
214
214
|
View: SoundComponentView,
|
|
215
|
-
composition: 'sibling',
|
|
216
215
|
defaultProperties: {
|
|
217
216
|
eventName: '',
|
|
218
217
|
clips: [],
|
|
@@ -3,7 +3,7 @@ import { useHelper } from "@react-three/drei";
|
|
|
3
3
|
import { useRef, useEffect } from "react";
|
|
4
4
|
import { BooleanField, ColorField, Label, NumberField, Vector3Input } from "./Input";
|
|
5
5
|
import { SpotLightHelper } from "three";
|
|
6
|
-
import { useAssetRuntime, useEntityRuntime } from "../
|
|
6
|
+
import { useAssetRuntime, useEntityRuntime } from "../runtime";
|
|
7
7
|
import { useFrame } from "@react-three/fiber";
|
|
8
8
|
import { TexturePicker } from "../../assetviewer/page";
|
|
9
9
|
import { LightSection, ShadowBiasField, mergeWithDefaults } from "./lightUtils";
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { Object3D, Texture } from "three";
|
|
3
|
+
import type { PrefabStoreApi } from "./prefabStore";
|
|
4
|
+
import { type EntityComponent, type Scene } from "./scene";
|
|
5
|
+
export interface AssetRuntime {
|
|
6
|
+
registerRigidBodyRef: (id: string, rb: any) => void;
|
|
7
|
+
getModel: (path: string) => Object3D | null;
|
|
8
|
+
getTexture: (path: string) => Texture | null;
|
|
9
|
+
getSound: (path: string) => AudioBuffer | null;
|
|
10
|
+
getAssetRevision: () => string;
|
|
11
|
+
getObject: (id: string) => Object3D | null;
|
|
12
|
+
getRigidBody: (id: string) => any;
|
|
13
|
+
}
|
|
14
|
+
export interface AssetRuntimeContextValue extends AssetRuntime {
|
|
15
|
+
}
|
|
16
|
+
export interface EntityRuntime {
|
|
17
|
+
nodeId: string;
|
|
18
|
+
editMode?: boolean;
|
|
19
|
+
isSelected?: boolean;
|
|
20
|
+
getObject: <T extends Object3D = Object3D>() => T | null;
|
|
21
|
+
getRigidBody: <T = any>() => T | null;
|
|
22
|
+
}
|
|
23
|
+
export interface LiveRef<T> {
|
|
24
|
+
readonly current: T | null;
|
|
25
|
+
}
|
|
26
|
+
export type LiveObjectRef<T extends Object3D = Object3D> = LiveRef<T>;
|
|
27
|
+
export type LiveRigidBodyRef<T = any> = LiveRef<T>;
|
|
28
|
+
export declare const AssetRuntimeContext: import("react").Context<AssetRuntimeContextValue | null>;
|
|
29
|
+
export declare function useAssetRuntime(): AssetRuntime;
|
|
30
|
+
export declare function useEntityRuntime(): EntityRuntime;
|
|
31
|
+
export declare function useEntityObjectRef<T extends Object3D = Object3D>(): LiveRef<T>;
|
|
32
|
+
export declare function useEntityRigidBodyRef<T = any>(): LiveRef<T>;
|
|
33
|
+
export declare function EntityRuntimeScope({ nodeId, editMode, isSelected, children, }: {
|
|
34
|
+
nodeId: string;
|
|
35
|
+
editMode?: boolean;
|
|
36
|
+
isSelected?: boolean;
|
|
37
|
+
children: ReactNode;
|
|
38
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
39
|
+
/** Runtime behaviour produced by `Component.create(ctx)`. All methods optional. */
|
|
40
|
+
export interface ComponentInstance {
|
|
41
|
+
start?(): void;
|
|
42
|
+
update?(dt: number): void;
|
|
43
|
+
destroy?(): void;
|
|
44
|
+
}
|
|
45
|
+
export interface ComponentRuntimeContext<TProperties = Record<string, any>> {
|
|
46
|
+
scene: Scene;
|
|
47
|
+
component: EntityComponent<TProperties>;
|
|
48
|
+
object: Object3D;
|
|
49
|
+
rigidBody: any;
|
|
50
|
+
}
|
|
51
|
+
export interface RuntimeEngine {
|
|
52
|
+
tick: (dt: number) => void;
|
|
53
|
+
setActive: (active: boolean) => void;
|
|
54
|
+
invalidate: () => void;
|
|
55
|
+
dispose: () => void;
|
|
56
|
+
}
|
|
57
|
+
export declare function createRuntimeEngine({ store, getObject, getRigidBody, }: {
|
|
58
|
+
store: PrefabStoreApi;
|
|
59
|
+
getObject: (id: string) => Object3D | null;
|
|
60
|
+
getRigidBody: (id: string) => any;
|
|
61
|
+
}): RuntimeEngine;
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext, useMemo } from "react";
|
|
3
|
+
import { getComponentDef } from "./components/ComponentRegistry";
|
|
4
|
+
import { createScene } from "./scene";
|
|
5
|
+
export const AssetRuntimeContext = createContext(null);
|
|
6
|
+
const EntityRuntimeContext = createContext(null);
|
|
7
|
+
export function useAssetRuntime() {
|
|
8
|
+
const ctx = useContext(AssetRuntimeContext);
|
|
9
|
+
if (!ctx)
|
|
10
|
+
throw new Error("useAssetRuntime must be used inside <PrefabRoot>");
|
|
11
|
+
return ctx;
|
|
12
|
+
}
|
|
13
|
+
export function useEntityRuntime() {
|
|
14
|
+
const ctx = useContext(EntityRuntimeContext);
|
|
15
|
+
if (!ctx)
|
|
16
|
+
throw new Error("useEntityRuntime must be used inside a component View rendered by <PrefabRoot>");
|
|
17
|
+
return ctx;
|
|
18
|
+
}
|
|
19
|
+
export function useEntityObjectRef() {
|
|
20
|
+
const { getObject } = useEntityRuntime();
|
|
21
|
+
return useMemo(() => ({ get current() { return getObject(); } }), [getObject]);
|
|
22
|
+
}
|
|
23
|
+
export function useEntityRigidBodyRef() {
|
|
24
|
+
const { getRigidBody } = useEntityRuntime();
|
|
25
|
+
return useMemo(() => ({ get current() { return getRigidBody(); } }), [getRigidBody]);
|
|
26
|
+
}
|
|
27
|
+
export function EntityRuntimeScope({ nodeId, editMode, isSelected, children, }) {
|
|
28
|
+
const asset = useContext(AssetRuntimeContext);
|
|
29
|
+
if (!asset)
|
|
30
|
+
throw new Error("EntityRuntimeScope must be used inside <PrefabRoot>");
|
|
31
|
+
const value = useMemo(() => ({
|
|
32
|
+
nodeId,
|
|
33
|
+
editMode,
|
|
34
|
+
isSelected,
|
|
35
|
+
getObject: () => asset.getObject(nodeId),
|
|
36
|
+
getRigidBody: () => asset.getRigidBody(nodeId),
|
|
37
|
+
}), [asset, editMode, isSelected, nodeId]);
|
|
38
|
+
return _jsx(EntityRuntimeContext.Provider, { value: value, children: children });
|
|
39
|
+
}
|
|
40
|
+
export function createRuntimeEngine({ store, getObject, getRigidBody, }) {
|
|
41
|
+
const scene = createScene({
|
|
42
|
+
getRootId: () => store.getState().rootId,
|
|
43
|
+
getNode: (id) => { var _a; return (_a = store.getState().nodesById[id]) !== null && _a !== void 0 ? _a : null; },
|
|
44
|
+
getChildIds: (id) => { var _a; return (_a = store.getState().childIdsById[id]) !== null && _a !== void 0 ? _a : []; },
|
|
45
|
+
getParentId: (id) => { var _a; return (_a = store.getState().parentIdById[id]) !== null && _a !== void 0 ? _a : null; },
|
|
46
|
+
updateNode: (id, update) => store.getState().updateNode(id, update),
|
|
47
|
+
updateNodes: (updates) => {
|
|
48
|
+
store.getState().updateNodes(Object.entries(updates).map(([id, update]) => ({ id, update })));
|
|
49
|
+
},
|
|
50
|
+
addNode: (node, options) => {
|
|
51
|
+
var _a;
|
|
52
|
+
const parentId = (_a = options === null || options === void 0 ? void 0 : options.parentId) !== null && _a !== void 0 ? _a : store.getState().rootId;
|
|
53
|
+
store.getState().addChild(parentId, node);
|
|
54
|
+
return node.id;
|
|
55
|
+
},
|
|
56
|
+
removeNode: (id) => store.getState().deleteNode(id),
|
|
57
|
+
getObject,
|
|
58
|
+
getRigidBody,
|
|
59
|
+
});
|
|
60
|
+
// key = `${nodeId}:${componentKey}:${componentType}`
|
|
61
|
+
const instances = new Map();
|
|
62
|
+
let active = false;
|
|
63
|
+
let dirty = true;
|
|
64
|
+
let lastRevision = store.getState().revision;
|
|
65
|
+
const unsubscribe = store.subscribe((state) => {
|
|
66
|
+
if (state.revision === lastRevision)
|
|
67
|
+
return;
|
|
68
|
+
lastRevision = state.revision;
|
|
69
|
+
dirty = true;
|
|
70
|
+
});
|
|
71
|
+
function destroy(key) {
|
|
72
|
+
var _a, _b;
|
|
73
|
+
const record = instances.get(key);
|
|
74
|
+
if (!record)
|
|
75
|
+
return;
|
|
76
|
+
try {
|
|
77
|
+
(_b = (_a = record.instance).destroy) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
console.error(`[runtime] destroy ${key}`, error);
|
|
81
|
+
}
|
|
82
|
+
instances.delete(key);
|
|
83
|
+
}
|
|
84
|
+
function sync() {
|
|
85
|
+
const state = store.getState();
|
|
86
|
+
const live = new Set();
|
|
87
|
+
let hasPendingMount = false;
|
|
88
|
+
const visit = (nodeId) => {
|
|
89
|
+
var _a, _b, _c;
|
|
90
|
+
const node = state.nodesById[nodeId];
|
|
91
|
+
if (!node)
|
|
92
|
+
return;
|
|
93
|
+
if (!node.disabled && node.components) {
|
|
94
|
+
for (const componentKey in node.components) {
|
|
95
|
+
const data = node.components[componentKey];
|
|
96
|
+
if (!(data === null || data === void 0 ? void 0 : data.type))
|
|
97
|
+
continue;
|
|
98
|
+
const def = getComponentDef(data.type);
|
|
99
|
+
if (!(def === null || def === void 0 ? void 0 : def.create))
|
|
100
|
+
continue;
|
|
101
|
+
const key = `${nodeId}:${componentKey}:${data.type}`;
|
|
102
|
+
live.add(key);
|
|
103
|
+
const object = getObject(nodeId);
|
|
104
|
+
if (!object) {
|
|
105
|
+
hasPendingMount = true;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const rigidBody = getRigidBody(nodeId);
|
|
109
|
+
const existing = instances.get(key);
|
|
110
|
+
if (existing && existing.object === object && existing.rigidBody === rigidBody) {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (existing) {
|
|
114
|
+
destroy(key);
|
|
115
|
+
}
|
|
116
|
+
const entity = scene.find(nodeId);
|
|
117
|
+
const component = entity === null || entity === void 0 ? void 0 : entity.getComponent(componentKey);
|
|
118
|
+
if (!entity || !component)
|
|
119
|
+
continue;
|
|
120
|
+
let instance;
|
|
121
|
+
try {
|
|
122
|
+
instance = (_a = def.create({ scene, component, object, rigidBody })) !== null && _a !== void 0 ? _a : {};
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
console.error(`[runtime] create ${key}`, error);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
instances.set(key, { instance, object, rigidBody });
|
|
129
|
+
try {
|
|
130
|
+
(_b = instance.start) === null || _b === void 0 ? void 0 : _b.call(instance);
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
console.error(`[runtime] start ${key}`, error);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
for (const childId of (_c = state.childIdsById[nodeId]) !== null && _c !== void 0 ? _c : [])
|
|
138
|
+
visit(childId);
|
|
139
|
+
};
|
|
140
|
+
visit(state.rootId);
|
|
141
|
+
for (const key of Array.from(instances.keys())) {
|
|
142
|
+
if (!live.has(key))
|
|
143
|
+
destroy(key);
|
|
144
|
+
}
|
|
145
|
+
dirty = hasPendingMount;
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
tick(dt) {
|
|
149
|
+
if (!active)
|
|
150
|
+
return;
|
|
151
|
+
if (dirty)
|
|
152
|
+
sync();
|
|
153
|
+
for (const [key, record] of instances) {
|
|
154
|
+
if (!record.instance.update)
|
|
155
|
+
continue;
|
|
156
|
+
try {
|
|
157
|
+
record.instance.update(dt);
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
console.error(`[runtime] update ${key}`, error);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
setActive(nextActive) {
|
|
165
|
+
if (active === nextActive)
|
|
166
|
+
return;
|
|
167
|
+
active = nextActive;
|
|
168
|
+
dirty = true;
|
|
169
|
+
if (!active) {
|
|
170
|
+
for (const key of Array.from(instances.keys()))
|
|
171
|
+
destroy(key);
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
invalidate() {
|
|
175
|
+
dirty = true;
|
|
176
|
+
},
|
|
177
|
+
dispose() {
|
|
178
|
+
active = false;
|
|
179
|
+
for (const key of Array.from(instances.keys()))
|
|
180
|
+
destroy(key);
|
|
181
|
+
unsubscribe();
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
}
|
|
@@ -45,6 +45,14 @@ export interface Scene {
|
|
|
45
45
|
};
|
|
46
46
|
add: (node: GameObject, options?: SpawnOptions) => Entity;
|
|
47
47
|
remove: (id: string) => void;
|
|
48
|
+
/**
|
|
49
|
+
* Coalesce many entity / component updates into a single store revision.
|
|
50
|
+
* Entity `update`/`set`, `EntityComponent` `set`/`update`, `addComponent`,
|
|
51
|
+
* and `removeComponent` calls inside the callback are buffered and flushed
|
|
52
|
+
* as one batched write. `add`, `remove`, and `destroy` (structural tree ops)
|
|
53
|
+
* still commit immediately.
|
|
54
|
+
*/
|
|
55
|
+
batch: (fn: () => void) => void;
|
|
48
56
|
}
|
|
49
57
|
interface SceneAdapter {
|
|
50
58
|
getRootId: () => string;
|
|
@@ -57,6 +57,42 @@ function setValueAtPath(value, path, nextValue) {
|
|
|
57
57
|
return cloneBranch(value, 0);
|
|
58
58
|
}
|
|
59
59
|
export function createScene(adapter) {
|
|
60
|
+
let batchBuffer = null;
|
|
61
|
+
function routeUpdate(id, update) {
|
|
62
|
+
if (batchBuffer) {
|
|
63
|
+
const prev = batchBuffer.get(id);
|
|
64
|
+
batchBuffer.set(id, prev ? (node) => update(prev(node)) : update);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
adapter.updateNode(id, update);
|
|
68
|
+
}
|
|
69
|
+
function routeUpdates(updates) {
|
|
70
|
+
if (batchBuffer) {
|
|
71
|
+
for (const id in updates)
|
|
72
|
+
routeUpdate(id, updates[id]);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
adapter.updateNodes(updates);
|
|
76
|
+
}
|
|
77
|
+
function batch(fn) {
|
|
78
|
+
if (batchBuffer) {
|
|
79
|
+
fn();
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
batchBuffer = new Map();
|
|
83
|
+
try {
|
|
84
|
+
fn();
|
|
85
|
+
if (batchBuffer.size > 0) {
|
|
86
|
+
const updates = {};
|
|
87
|
+
for (const [id, update] of batchBuffer)
|
|
88
|
+
updates[id] = update;
|
|
89
|
+
adapter.updateNodes(updates);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
finally {
|
|
93
|
+
batchBuffer = null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
60
96
|
const getNode = (id) => {
|
|
61
97
|
if (!adapter.getNode(id))
|
|
62
98
|
missingNode(id);
|
|
@@ -76,7 +112,7 @@ export function createScene(adapter) {
|
|
|
76
112
|
return getValueAtPath(component.properties, path);
|
|
77
113
|
},
|
|
78
114
|
set(path, value) {
|
|
79
|
-
|
|
115
|
+
routeUpdate(entityId, node => {
|
|
80
116
|
var _a;
|
|
81
117
|
const component = (_a = node.components) === null || _a === void 0 ? void 0 : _a[componentKey];
|
|
82
118
|
if (!component) {
|
|
@@ -86,7 +122,7 @@ export function createScene(adapter) {
|
|
|
86
122
|
});
|
|
87
123
|
},
|
|
88
124
|
update(update) {
|
|
89
|
-
|
|
125
|
+
routeUpdate(entityId, node => {
|
|
90
126
|
var _a;
|
|
91
127
|
const component = (_a = node.components) === null || _a === void 0 ? void 0 : _a[componentKey];
|
|
92
128
|
if (!component) {
|
|
@@ -128,10 +164,10 @@ export function createScene(adapter) {
|
|
|
128
164
|
return (_b = (_a = adapter.getRigidBody) === null || _a === void 0 ? void 0 : _a.call(adapter, id)) !== null && _b !== void 0 ? _b : null;
|
|
129
165
|
},
|
|
130
166
|
set(data) {
|
|
131
|
-
|
|
167
|
+
routeUpdate(id, () => data);
|
|
132
168
|
},
|
|
133
169
|
update(update) {
|
|
134
|
-
|
|
170
|
+
routeUpdate(id, update);
|
|
135
171
|
},
|
|
136
172
|
getComponent(name) {
|
|
137
173
|
const node = adapter.getNode(id);
|
|
@@ -145,11 +181,11 @@ export function createScene(adapter) {
|
|
|
145
181
|
},
|
|
146
182
|
addComponent(type, properties) {
|
|
147
183
|
const key = type.toLowerCase();
|
|
148
|
-
|
|
184
|
+
routeUpdate(id, node => (Object.assign(Object.assign({}, node), { components: Object.assign(Object.assign({}, node.components), { [key]: createComponentData(type, properties) }) })));
|
|
149
185
|
return createComponent(id, key, type);
|
|
150
186
|
},
|
|
151
187
|
removeComponent(name) {
|
|
152
|
-
|
|
188
|
+
routeUpdate(id, node => {
|
|
153
189
|
var _a;
|
|
154
190
|
const entry = findComponentEntry(node, name);
|
|
155
191
|
if (!entry)
|
|
@@ -169,13 +205,13 @@ export function createScene(adapter) {
|
|
|
169
205
|
if (!mutate) {
|
|
170
206
|
return;
|
|
171
207
|
}
|
|
172
|
-
|
|
208
|
+
routeUpdate(idOrUpdates, mutate);
|
|
173
209
|
return;
|
|
174
210
|
}
|
|
175
211
|
if (Object.keys(idOrUpdates).length === 0) {
|
|
176
212
|
return;
|
|
177
213
|
}
|
|
178
|
-
|
|
214
|
+
routeUpdates(idOrUpdates);
|
|
179
215
|
}
|
|
180
216
|
return {
|
|
181
217
|
get rootId() {
|
|
@@ -196,5 +232,6 @@ export function createScene(adapter) {
|
|
|
196
232
|
remove(id) {
|
|
197
233
|
adapter.removeNode(id);
|
|
198
234
|
},
|
|
235
|
+
batch,
|
|
199
236
|
};
|
|
200
237
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-three-game",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.84",
|
|
4
4
|
"description": "high performance 3D game engine built in React",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"concurrently": "^9.2.1",
|
|
44
44
|
"react": "^19.2.4",
|
|
45
45
|
"react-dom": "^19.2.4",
|
|
46
|
-
"three": "^0.
|
|
46
|
+
"three": "^0.184.0",
|
|
47
47
|
"typescript": "^5.9.3",
|
|
48
48
|
"vite": "^7.3.1"
|
|
49
49
|
},
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { type ReactNode } from "react";
|
|
2
|
-
import { Object3D, Texture } from "three";
|
|
3
|
-
type RuntimeScopeProps = {
|
|
4
|
-
nodeId: string;
|
|
5
|
-
editMode?: boolean;
|
|
6
|
-
isSelected?: boolean;
|
|
7
|
-
children: ReactNode;
|
|
8
|
-
};
|
|
9
|
-
export interface AssetRuntime {
|
|
10
|
-
registerRigidBodyRef: (id: string, rb: any) => void;
|
|
11
|
-
getModel: (path: string) => Object3D | null;
|
|
12
|
-
getTexture: (path: string) => Texture | null;
|
|
13
|
-
getSound: (path: string) => AudioBuffer | null;
|
|
14
|
-
getAssetRevision: () => string;
|
|
15
|
-
getObject: (id: string) => Object3D | null;
|
|
16
|
-
getRigidBody: (id: string) => any;
|
|
17
|
-
}
|
|
18
|
-
export interface AssetRuntimeContextValue extends AssetRuntime {
|
|
19
|
-
}
|
|
20
|
-
export interface EntityRuntime {
|
|
21
|
-
nodeId: string;
|
|
22
|
-
editMode?: boolean;
|
|
23
|
-
isSelected?: boolean;
|
|
24
|
-
getObject: <T extends Object3D = Object3D>() => T | null;
|
|
25
|
-
getRigidBody: <T = any>() => T | null;
|
|
26
|
-
}
|
|
27
|
-
export interface LiveObjectRef<T extends Object3D = Object3D> {
|
|
28
|
-
readonly current: T | null;
|
|
29
|
-
}
|
|
30
|
-
export interface LiveRigidBodyRef<T = any> {
|
|
31
|
-
readonly current: T | null;
|
|
32
|
-
}
|
|
33
|
-
export declare const AssetRuntimeContext: import("react").Context<AssetRuntimeContextValue | null>;
|
|
34
|
-
export declare const EntityRuntimeContext: import("react").Context<EntityRuntime | null>;
|
|
35
|
-
export declare function useAssetRuntime(): AssetRuntime;
|
|
36
|
-
export declare function useEntityRuntime(): EntityRuntime;
|
|
37
|
-
export declare function useEntityObjectRef<T extends Object3D = Object3D>(): LiveObjectRef<T>;
|
|
38
|
-
export declare function useEntityRigidBodyRef<T = any>(): LiveRigidBodyRef<T>;
|
|
39
|
-
export declare function EntityRuntimeScope({ nodeId, editMode, isSelected, children, }: RuntimeScopeProps): import("react/jsx-runtime").JSX.Element;
|
|
40
|
-
export {};
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { createContext, useContext, useMemo } from "react";
|
|
3
|
-
function createLiveRef(getCurrent) {
|
|
4
|
-
return {
|
|
5
|
-
get current() {
|
|
6
|
-
return getCurrent();
|
|
7
|
-
},
|
|
8
|
-
};
|
|
9
|
-
}
|
|
10
|
-
export const AssetRuntimeContext = createContext(null);
|
|
11
|
-
export const EntityRuntimeContext = createContext(null);
|
|
12
|
-
export function useAssetRuntime() {
|
|
13
|
-
const ctx = useContext(AssetRuntimeContext);
|
|
14
|
-
if (!ctx)
|
|
15
|
-
throw new Error("useAssetRuntime must be used inside <PrefabRoot>");
|
|
16
|
-
return ctx;
|
|
17
|
-
}
|
|
18
|
-
export function useEntityRuntime() {
|
|
19
|
-
const ctx = useContext(EntityRuntimeContext);
|
|
20
|
-
if (!ctx)
|
|
21
|
-
throw new Error("useEntityRuntime must be used inside a component View rendered by <PrefabRoot>");
|
|
22
|
-
return ctx;
|
|
23
|
-
}
|
|
24
|
-
export function useEntityObjectRef() {
|
|
25
|
-
const { getObject } = useEntityRuntime();
|
|
26
|
-
return useMemo(() => createLiveRef(() => getObject()), [getObject]);
|
|
27
|
-
}
|
|
28
|
-
export function useEntityRigidBodyRef() {
|
|
29
|
-
const { getRigidBody } = useEntityRuntime();
|
|
30
|
-
return useMemo(() => createLiveRef(() => getRigidBody()), [getRigidBody]);
|
|
31
|
-
}
|
|
32
|
-
export function EntityRuntimeScope({ nodeId, editMode, isSelected, children, }) {
|
|
33
|
-
const assetRuntime = useContext(AssetRuntimeContext);
|
|
34
|
-
if (!assetRuntime)
|
|
35
|
-
throw new Error("EntityRuntimeScope must be used inside <PrefabRoot>");
|
|
36
|
-
const { getObject, getRigidBody } = assetRuntime;
|
|
37
|
-
const value = useMemo(() => ({
|
|
38
|
-
nodeId,
|
|
39
|
-
editMode,
|
|
40
|
-
isSelected,
|
|
41
|
-
getObject: () => getObject(nodeId),
|
|
42
|
-
getRigidBody: () => getRigidBody(nodeId),
|
|
43
|
-
}), [editMode, getObject, getRigidBody, isSelected, nodeId]);
|
|
44
|
-
return _jsx(EntityRuntimeContext.Provider, { value: value, children: children });
|
|
45
|
-
}
|