react-three-game 0.0.60 → 0.0.61
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/package.json +9 -3
- package/.gitattributes +0 -2
- package/.github/copilot-instructions.md +0 -83
- package/.github/workflows/nextjs.yml +0 -99
- package/.gitmodules +0 -3
- package/assets/architecture.png +0 -0
- package/assets/editor.gif +0 -0
- package/assets/favicon.ico +0 -0
- package/assets/react-three-game-logo.png +0 -0
- package/dist/tools/dragdrop/page.d.ts +0 -1
- package/dist/tools/dragdrop/page.js +0 -11
- package/dist/tools/prefabeditor/EntityEvents.d.ts +0 -54
- package/dist/tools/prefabeditor/EntityEvents.js +0 -85
- package/dist/tools/prefabeditor/page.d.ts +0 -1
- package/dist/tools/prefabeditor/page.js +0 -5
- package/react-three-game-skill/.gitattributes +0 -2
- package/react-three-game-skill/README.md +0 -7
- package/react-three-game-skill/react-three-game/SKILL.md +0 -514
- package/react-three-game-skill/react-three-game/rules/ADVANCED_PHYSICS.md +0 -472
- package/react-three-game-skill/react-three-game/rules/LIGHTING.md +0 -6
- package/src/helpers/SoundManager.ts +0 -130
- package/src/helpers/index.ts +0 -91
- package/src/index.ts +0 -59
- package/src/shared/ContactShadow.tsx +0 -74
- package/src/shared/GameCanvas.tsx +0 -52
- package/src/tools/assetviewer/page.tsx +0 -425
- package/src/tools/dragdrop/DragDropLoader.tsx +0 -159
- package/src/tools/dragdrop/index.ts +0 -4
- package/src/tools/dragdrop/modelLoader.ts +0 -204
- package/src/tools/dragdrop/page.tsx +0 -45
- package/src/tools/prefabeditor/Dropdown.tsx +0 -112
- package/src/tools/prefabeditor/EditorContext.tsx +0 -25
- package/src/tools/prefabeditor/EditorTree.tsx +0 -452
- package/src/tools/prefabeditor/EditorTreeMenus.tsx +0 -307
- package/src/tools/prefabeditor/EditorUI.tsx +0 -204
- package/src/tools/prefabeditor/EventSystem.tsx +0 -36
- package/src/tools/prefabeditor/GameEvents.ts +0 -191
- package/src/tools/prefabeditor/InstanceProvider.tsx +0 -466
- package/src/tools/prefabeditor/PrefabEditor.tsx +0 -256
- package/src/tools/prefabeditor/PrefabRoot.tsx +0 -767
- package/src/tools/prefabeditor/components/AmbientLightComponent.tsx +0 -34
- package/src/tools/prefabeditor/components/CameraComponent.tsx +0 -117
- package/src/tools/prefabeditor/components/ComponentRegistry.ts +0 -40
- package/src/tools/prefabeditor/components/DirectionalLightComponent.tsx +0 -210
- package/src/tools/prefabeditor/components/EnvironmentComponent.tsx +0 -47
- package/src/tools/prefabeditor/components/GeometryComponent.tsx +0 -133
- package/src/tools/prefabeditor/components/Input.tsx +0 -820
- package/src/tools/prefabeditor/components/MaterialComponent.tsx +0 -431
- package/src/tools/prefabeditor/components/ModelComponent.tsx +0 -176
- package/src/tools/prefabeditor/components/PhysicsComponent.tsx +0 -188
- package/src/tools/prefabeditor/components/SpotLightComponent.tsx +0 -109
- package/src/tools/prefabeditor/components/TextComponent.tsx +0 -137
- package/src/tools/prefabeditor/components/TransformComponent.tsx +0 -173
- package/src/tools/prefabeditor/components/index.ts +0 -26
- package/src/tools/prefabeditor/page.tsx +0 -10
- package/src/tools/prefabeditor/styles.ts +0 -235
- package/src/tools/prefabeditor/types.ts +0 -20
- package/src/tools/prefabeditor/utils.ts +0 -312
|
@@ -1,767 +0,0 @@
|
|
|
1
|
-
import { MapControls, TransformControls, useHelper } from "@react-three/drei";
|
|
2
|
-
import { forwardRef, useCallback, useContext, useEffect, useImperativeHandle, useRef, useState } from "react";
|
|
3
|
-
import { BoxHelper, Euler, Group, Matrix4, Object3D, Quaternion, SRGBColorSpace, Texture, TextureLoader, Vector3, } from "three";
|
|
4
|
-
import { ThreeEvent } from "@react-three/fiber";
|
|
5
|
-
|
|
6
|
-
import { Prefab, GameObject as GameObjectType } from "./types";
|
|
7
|
-
import { getComponent, registerComponent, getNonComposableKeys } from "./components/ComponentRegistry";
|
|
8
|
-
import components from "./components";
|
|
9
|
-
import { loadModel } from "../dragdrop";
|
|
10
|
-
import { GameInstance, GameInstanceProvider, useInstanceCheck } from "./InstanceProvider";
|
|
11
|
-
import { focusCameraOnObject, updateNode } from "./utils";
|
|
12
|
-
import { PhysicsProps } from "./components/PhysicsComponent";
|
|
13
|
-
import { EditorContext } from "./EditorContext";
|
|
14
|
-
|
|
15
|
-
// Dynamic type to avoid requiring @react-three/rapier when not using physics
|
|
16
|
-
type RapierRigidBody = any;
|
|
17
|
-
|
|
18
|
-
components.forEach(registerComponent);
|
|
19
|
-
|
|
20
|
-
const IDENTITY = new Matrix4();
|
|
21
|
-
|
|
22
|
-
export interface PrefabRootRef {
|
|
23
|
-
root: Group | null;
|
|
24
|
-
rigidBodyRefs: Map<string, any>; // RigidBody refs only populated when using physics
|
|
25
|
-
injectModel: (filename: string, model: Object3D) => void;
|
|
26
|
-
injectTexture: (filename: string, texture: Texture) => void;
|
|
27
|
-
focusNode: (nodeId: string) => void;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export const PrefabRoot = forwardRef<PrefabRootRef, {
|
|
31
|
-
editMode?: boolean;
|
|
32
|
-
data: Prefab;
|
|
33
|
-
onPrefabChange?: (data: Prefab) => void;
|
|
34
|
-
selectedId?: string | null;
|
|
35
|
-
onSelect?: (id: string | null) => void;
|
|
36
|
-
onClick?: (event: ThreeEvent<PointerEvent>, entity: GameObjectType) => void;
|
|
37
|
-
basePath?: string;
|
|
38
|
-
}>(({ editMode, data, onPrefabChange, selectedId, onSelect, onClick, basePath = "" }, ref) => {
|
|
39
|
-
|
|
40
|
-
// optional editor context
|
|
41
|
-
const editorContext = useContext(EditorContext);
|
|
42
|
-
const transformMode = editorContext?.transformMode ?? "translate";
|
|
43
|
-
const snapResolution = editorContext?.snapResolution ?? 0;
|
|
44
|
-
const positionSnap = editorContext?.positionSnap ?? 0.5;
|
|
45
|
-
const rotationSnap = editorContext?.rotationSnap ?? Math.PI / 4;
|
|
46
|
-
|
|
47
|
-
// prefab root state
|
|
48
|
-
const [models, setModels] = useState<Record<string, Object3D>>({});
|
|
49
|
-
const [textures, setTextures] = useState<Record<string, Texture>>({});
|
|
50
|
-
const loading = useRef(new Set<string>());
|
|
51
|
-
const failedTextures = useRef(new Set<string>());
|
|
52
|
-
const objectRefs = useRef<Record<string, Object3D | null>>({});
|
|
53
|
-
const rigidBodyRefs = useRef<Map<string, RapierRigidBody | null>>(new Map());
|
|
54
|
-
const [selectedObject, setSelectedObject] = useState<Object3D | null>(null);
|
|
55
|
-
const rootRef = useRef<Group>(null);
|
|
56
|
-
const controlsRef = useRef<any>(null);
|
|
57
|
-
|
|
58
|
-
const injectModel = useCallback((filename: string, model: Object3D) => {
|
|
59
|
-
setModels(m => ({ ...m, [filename]: model }));
|
|
60
|
-
}, []);
|
|
61
|
-
|
|
62
|
-
const injectTexture = useCallback((filename: string, texture: Texture) => {
|
|
63
|
-
loading.current.add(filename);
|
|
64
|
-
setTextures(t => ({ ...t, [filename]: texture }));
|
|
65
|
-
}, []);
|
|
66
|
-
|
|
67
|
-
useImperativeHandle(ref, () => ({
|
|
68
|
-
root: rootRef.current,
|
|
69
|
-
rigidBodyRefs: rigidBodyRefs.current,
|
|
70
|
-
injectModel,
|
|
71
|
-
injectTexture,
|
|
72
|
-
focusNode: (nodeId: string) => {
|
|
73
|
-
const object = objectRefs.current[nodeId];
|
|
74
|
-
const controls = controlsRef.current;
|
|
75
|
-
const camera = controls?.object;
|
|
76
|
-
|
|
77
|
-
if (!object || !controls || !camera) return;
|
|
78
|
-
|
|
79
|
-
focusCameraOnObject(object, camera, controls.target, () => controls.update?.());
|
|
80
|
-
}
|
|
81
|
-
}), [injectModel, injectTexture]);
|
|
82
|
-
|
|
83
|
-
const registerRef = useCallback((id: string, obj: Object3D | null) => {
|
|
84
|
-
objectRefs.current[id] = obj;
|
|
85
|
-
if (id === selectedId) setSelectedObject(obj);
|
|
86
|
-
}, [selectedId]);
|
|
87
|
-
|
|
88
|
-
const registerRigidBodyRef = useCallback((id: string, rb: any) => {
|
|
89
|
-
rigidBodyRefs.current.set(id, rb);
|
|
90
|
-
}, []);
|
|
91
|
-
|
|
92
|
-
useEffect(() => {
|
|
93
|
-
const originalError = console.error;
|
|
94
|
-
console.error = (...args: any[]) => {
|
|
95
|
-
if (typeof args[0] === 'string' && args[0].includes('TransformControls') && args[0].includes('scene graph')) return;
|
|
96
|
-
originalError.apply(console, args);
|
|
97
|
-
};
|
|
98
|
-
return () => { console.error = originalError; };
|
|
99
|
-
}, []);
|
|
100
|
-
|
|
101
|
-
useEffect(() => {
|
|
102
|
-
setSelectedObject(selectedId ? objectRefs.current[selectedId] ?? null : null);
|
|
103
|
-
}, [selectedId]);
|
|
104
|
-
|
|
105
|
-
const onTransformChange = () => {
|
|
106
|
-
if (!selectedId || !onPrefabChange) return;
|
|
107
|
-
|
|
108
|
-
const obj = objectRefs.current[selectedId];
|
|
109
|
-
if (!obj) return;
|
|
110
|
-
|
|
111
|
-
const parentWorld = computeParentWorldMatrix(data.root, selectedId);
|
|
112
|
-
const local = parentWorld.clone().invert().multiply(obj.matrixWorld);
|
|
113
|
-
|
|
114
|
-
const { position, rotation, scale } = decompose(local);
|
|
115
|
-
|
|
116
|
-
const root = updateNode(data.root, selectedId, node => ({
|
|
117
|
-
...node,
|
|
118
|
-
components: {
|
|
119
|
-
...node.components,
|
|
120
|
-
transform: {
|
|
121
|
-
type: "Transform",
|
|
122
|
-
properties: { position, rotation, scale },
|
|
123
|
-
},
|
|
124
|
-
},
|
|
125
|
-
}));
|
|
126
|
-
|
|
127
|
-
onPrefabChange({ ...data, root });
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
useEffect(() => {
|
|
131
|
-
const modelsToLoad = new Set<string>();
|
|
132
|
-
const texturesToLoad = new Set<string>();
|
|
133
|
-
|
|
134
|
-
walk(data.root, node => {
|
|
135
|
-
node.components?.model?.properties?.filename &&
|
|
136
|
-
modelsToLoad.add(node.components.model.properties.filename);
|
|
137
|
-
node.components?.material?.properties?.texture &&
|
|
138
|
-
texturesToLoad.add(node.components.material.properties.texture);
|
|
139
|
-
node.components?.material?.properties?.normalMapTexture &&
|
|
140
|
-
texturesToLoad.add(node.components.material.properties.normalMapTexture);
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
modelsToLoad.forEach(async file => {
|
|
144
|
-
if (models[file] || loading.current.has(file)) return;
|
|
145
|
-
loading.current.add(file);
|
|
146
|
-
const path =
|
|
147
|
-
file.startsWith("/")
|
|
148
|
-
? `${basePath}${file}`
|
|
149
|
-
: `${basePath}/${file}`;
|
|
150
|
-
|
|
151
|
-
const res = await loadModel(path);
|
|
152
|
-
const model = res.model;
|
|
153
|
-
|
|
154
|
-
if (res.success && model) {
|
|
155
|
-
setModels(m => ({ ...m, [file]: model }));
|
|
156
|
-
}
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
const loader = new TextureLoader();
|
|
160
|
-
texturesToLoad.forEach(file => {
|
|
161
|
-
if (textures[file] || loading.current.has(file) || failedTextures.current.has(file)) return;
|
|
162
|
-
loading.current.add(file);
|
|
163
|
-
|
|
164
|
-
// Handle full URLs (http/https) or regular paths
|
|
165
|
-
const path = file.startsWith("http://") || file.startsWith("https://")
|
|
166
|
-
? file
|
|
167
|
-
: file.startsWith("/")
|
|
168
|
-
? `${basePath}${file}`
|
|
169
|
-
: `${basePath}/${file}`;
|
|
170
|
-
|
|
171
|
-
loader.load(path, tex => {
|
|
172
|
-
tex.colorSpace = SRGBColorSpace;
|
|
173
|
-
setTextures(t => ({ ...t, [file]: tex }));
|
|
174
|
-
}, undefined, (err) => {
|
|
175
|
-
console.warn(`Failed to load texture: ${path}`, err);
|
|
176
|
-
loading.current.delete(file);
|
|
177
|
-
failedTextures.current.add(file);
|
|
178
|
-
});
|
|
179
|
-
});
|
|
180
|
-
}, [data, models, textures]);
|
|
181
|
-
|
|
182
|
-
return (
|
|
183
|
-
<group ref={rootRef}>
|
|
184
|
-
<GameInstanceProvider
|
|
185
|
-
models={models}
|
|
186
|
-
selectedId={selectedId}
|
|
187
|
-
editMode={editMode}
|
|
188
|
-
onSelect={editMode ? onSelect : undefined}
|
|
189
|
-
registerRef={registerRef}
|
|
190
|
-
>
|
|
191
|
-
<GameObjectRenderer
|
|
192
|
-
gameObject={data.root}
|
|
193
|
-
selectedId={selectedId}
|
|
194
|
-
onSelect={editMode ? onSelect : undefined}
|
|
195
|
-
onClick={onClick}
|
|
196
|
-
registerRef={registerRef}
|
|
197
|
-
registerRigidBodyRef={registerRigidBodyRef}
|
|
198
|
-
loadedModels={models}
|
|
199
|
-
loadedTextures={textures}
|
|
200
|
-
editMode={editMode}
|
|
201
|
-
parentMatrix={IDENTITY}
|
|
202
|
-
/>
|
|
203
|
-
</GameInstanceProvider>
|
|
204
|
-
|
|
205
|
-
{editMode && (
|
|
206
|
-
<>
|
|
207
|
-
<MapControls ref={controlsRef} makeDefault />
|
|
208
|
-
{selectedObject && (
|
|
209
|
-
<TransformControls
|
|
210
|
-
key={`transform-${transformMode}-${positionSnap}-${rotationSnap}-${snapResolution}`}
|
|
211
|
-
object={selectedObject}
|
|
212
|
-
mode={transformMode}
|
|
213
|
-
space="local"
|
|
214
|
-
onObjectChange={onTransformChange}
|
|
215
|
-
translationSnap={positionSnap > 0 ? positionSnap : undefined}
|
|
216
|
-
rotationSnap={rotationSnap > 0 ? rotationSnap : undefined}
|
|
217
|
-
scaleSnap={snapResolution > 0 ? snapResolution : undefined}
|
|
218
|
-
/>
|
|
219
|
-
)}
|
|
220
|
-
</>
|
|
221
|
-
)}
|
|
222
|
-
</group>
|
|
223
|
-
);
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
export function GameObjectRenderer(props: RendererProps) {
|
|
227
|
-
const node = props.gameObject;
|
|
228
|
-
if (!node || node.disabled) return null;
|
|
229
|
-
|
|
230
|
-
const isInstanced = node.components?.model?.properties?.instanced;
|
|
231
|
-
const prevInstancedRef = useRef<boolean | undefined>(undefined);
|
|
232
|
-
const [isTransitioning, setIsTransitioning] = useState(false);
|
|
233
|
-
|
|
234
|
-
useEffect(() => {
|
|
235
|
-
if (prevInstancedRef.current !== undefined && prevInstancedRef.current !== isInstanced) {
|
|
236
|
-
setIsTransitioning(true);
|
|
237
|
-
const timer = setTimeout(() => setIsTransitioning(false), 100);
|
|
238
|
-
return () => clearTimeout(timer);
|
|
239
|
-
}
|
|
240
|
-
prevInstancedRef.current = isInstanced;
|
|
241
|
-
}, [isInstanced]);
|
|
242
|
-
|
|
243
|
-
if (isTransitioning) return null;
|
|
244
|
-
|
|
245
|
-
const key = `${node.id}_${isInstanced ? 'instanced' : 'standard'}`;
|
|
246
|
-
return isInstanced
|
|
247
|
-
? <InstancedNode key={key} {...props} />
|
|
248
|
-
: <StandardNode key={key} {...props} />;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
function isPhysicsProps(v: any): v is PhysicsProps {
|
|
252
|
-
return v?.type === "fixed" || v?.type === "dynamic";
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
function InstancedNode({ gameObject, parentMatrix = IDENTITY, editMode, registerRef, selectedId: _selectedId, onSelect, onClick }: RendererProps) {
|
|
256
|
-
const world = parentMatrix.clone().multiply(compose(gameObject));
|
|
257
|
-
const { position: worldPosition, rotation: worldRotation, scale: worldScale } = decompose(world);
|
|
258
|
-
const localTransform = getNodeTransformProps(gameObject);
|
|
259
|
-
|
|
260
|
-
const physicsProps = isPhysicsProps(
|
|
261
|
-
gameObject.components?.physics?.properties
|
|
262
|
-
)
|
|
263
|
-
? gameObject.components?.physics?.properties
|
|
264
|
-
: undefined;
|
|
265
|
-
|
|
266
|
-
const groupRef = useRef<Group>(null);
|
|
267
|
-
const clickValid = useRef(false);
|
|
268
|
-
|
|
269
|
-
useEffect(() => {
|
|
270
|
-
if (editMode) {
|
|
271
|
-
registerRef(gameObject.id, groupRef.current);
|
|
272
|
-
return () => registerRef(gameObject.id, null);
|
|
273
|
-
}
|
|
274
|
-
}, [gameObject.id, registerRef, editMode]);
|
|
275
|
-
|
|
276
|
-
const modelUrl = gameObject.components?.model?.properties?.filename;
|
|
277
|
-
|
|
278
|
-
if (editMode) {
|
|
279
|
-
return (
|
|
280
|
-
<>
|
|
281
|
-
<group
|
|
282
|
-
ref={groupRef}
|
|
283
|
-
position={localTransform.position}
|
|
284
|
-
rotation={localTransform.rotation}
|
|
285
|
-
scale={localTransform.scale}
|
|
286
|
-
onPointerDown={(e) => { e.stopPropagation(); clickValid.current = true; }}
|
|
287
|
-
onPointerMove={() => { clickValid.current = false; }}
|
|
288
|
-
onPointerUp={(e) => {
|
|
289
|
-
if (clickValid.current) {
|
|
290
|
-
e.stopPropagation();
|
|
291
|
-
onSelect?.(gameObject.id);
|
|
292
|
-
onClick?.(e, gameObject);
|
|
293
|
-
}
|
|
294
|
-
clickValid.current = false;
|
|
295
|
-
}}
|
|
296
|
-
>
|
|
297
|
-
<mesh visible={false}>
|
|
298
|
-
<boxGeometry args={[0.01, 0.01, 0.01]} />
|
|
299
|
-
</mesh>
|
|
300
|
-
</group>
|
|
301
|
-
<GameInstance
|
|
302
|
-
id={gameObject.id}
|
|
303
|
-
modelUrl={modelUrl}
|
|
304
|
-
position={worldPosition}
|
|
305
|
-
rotation={worldRotation}
|
|
306
|
-
scale={worldScale}
|
|
307
|
-
physics={physicsProps}
|
|
308
|
-
/>
|
|
309
|
-
</>
|
|
310
|
-
);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
return (
|
|
314
|
-
<GameInstance
|
|
315
|
-
id={gameObject.id}
|
|
316
|
-
modelUrl={gameObject.components?.model?.properties?.filename}
|
|
317
|
-
position={worldPosition}
|
|
318
|
-
rotation={worldRotation}
|
|
319
|
-
scale={worldScale}
|
|
320
|
-
physics={physicsProps}
|
|
321
|
-
/>
|
|
322
|
-
);
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
function StandardNode({
|
|
326
|
-
gameObject,
|
|
327
|
-
selectedId,
|
|
328
|
-
onSelect,
|
|
329
|
-
onClick,
|
|
330
|
-
registerRef,
|
|
331
|
-
registerRigidBodyRef,
|
|
332
|
-
loadedModels,
|
|
333
|
-
loadedTextures,
|
|
334
|
-
editMode,
|
|
335
|
-
parentMatrix = IDENTITY,
|
|
336
|
-
}: RendererProps) {
|
|
337
|
-
|
|
338
|
-
const groupRef = useRef<Object3D | null>(null);
|
|
339
|
-
const helperRef = useRef<Object3D | null>(null);
|
|
340
|
-
const clickValid = useRef(false);
|
|
341
|
-
const isSelected = selectedId === gameObject.id;
|
|
342
|
-
const stillInstanced = useInstanceCheck(gameObject.id);
|
|
343
|
-
|
|
344
|
-
useHelper(
|
|
345
|
-
editMode && isSelected ? helperRef as React.RefObject<Object3D> : null,
|
|
346
|
-
BoxHelper,
|
|
347
|
-
"cyan"
|
|
348
|
-
);
|
|
349
|
-
|
|
350
|
-
useEffect(() => {
|
|
351
|
-
registerRef(gameObject.id, groupRef.current);
|
|
352
|
-
return () => registerRef(gameObject.id, null);
|
|
353
|
-
}, [gameObject.id, registerRef]);
|
|
354
|
-
|
|
355
|
-
const world = parentMatrix.clone().multiply(compose(gameObject));
|
|
356
|
-
|
|
357
|
-
const onDown = (e: ThreeEvent<PointerEvent>) => {
|
|
358
|
-
e.stopPropagation();
|
|
359
|
-
clickValid.current = true;
|
|
360
|
-
};
|
|
361
|
-
|
|
362
|
-
const onUp = (e: ThreeEvent<PointerEvent>) => {
|
|
363
|
-
if (clickValid.current) {
|
|
364
|
-
e.stopPropagation();
|
|
365
|
-
onSelect?.(gameObject.id);
|
|
366
|
-
onClick?.(e, gameObject);
|
|
367
|
-
}
|
|
368
|
-
clickValid.current = false;
|
|
369
|
-
};
|
|
370
|
-
|
|
371
|
-
const physics = gameObject.components?.physics;
|
|
372
|
-
const ready = !gameObject.components?.model ||
|
|
373
|
-
loadedModels[gameObject.components.model.properties.filename];
|
|
374
|
-
const hasPhysics = physics && ready && !stillInstanced;
|
|
375
|
-
const transform = getNodeTransformProps(gameObject);
|
|
376
|
-
const physicsDef = hasPhysics ? getComponent("Physics") : null;
|
|
377
|
-
const isInstanced = gameObject.components?.model?.properties?.instanced;
|
|
378
|
-
const physicsKey = `physics_${gameObject.id}_${isInstanced ? 'instanced' : 'standard'}`;
|
|
379
|
-
const renderCtx = createRenderContext(loadedModels, loadedTextures, editMode, selectedId, registerRef);
|
|
380
|
-
const childNodes = getChildHostComponents(gameObject).length > 0
|
|
381
|
-
? renderHostedChildren(gameObject, renderCtx, world)
|
|
382
|
-
: renderSceneChildren(gameObject, world, {
|
|
383
|
-
selectedId,
|
|
384
|
-
onSelect,
|
|
385
|
-
onClick,
|
|
386
|
-
registerRef,
|
|
387
|
-
registerRigidBodyRef,
|
|
388
|
-
loadedModels,
|
|
389
|
-
loadedTextures,
|
|
390
|
-
editMode,
|
|
391
|
-
});
|
|
392
|
-
|
|
393
|
-
const inner = (
|
|
394
|
-
<group
|
|
395
|
-
onPointerDown={editMode ? onDown : undefined}
|
|
396
|
-
onPointerMove={editMode ? () => (clickValid.current = false) : undefined}
|
|
397
|
-
onPointerUp={editMode ? onUp : undefined}
|
|
398
|
-
>
|
|
399
|
-
{renderCompositionNode(gameObject, renderCtx, parentMatrix, childNodes)}
|
|
400
|
-
</group>
|
|
401
|
-
);
|
|
402
|
-
|
|
403
|
-
if (editMode) {
|
|
404
|
-
return (
|
|
405
|
-
<>
|
|
406
|
-
<group
|
|
407
|
-
ref={groupRef}
|
|
408
|
-
position={transform.position}
|
|
409
|
-
rotation={transform.rotation}
|
|
410
|
-
scale={transform.scale}
|
|
411
|
-
>
|
|
412
|
-
<mesh visible={false}>
|
|
413
|
-
<boxGeometry args={[0.01, 0.01, 0.01]} />
|
|
414
|
-
</mesh>
|
|
415
|
-
</group>
|
|
416
|
-
<group
|
|
417
|
-
ref={helperRef}
|
|
418
|
-
position={transform.position}
|
|
419
|
-
rotation={transform.rotation}
|
|
420
|
-
scale={transform.scale}
|
|
421
|
-
>
|
|
422
|
-
{inner}
|
|
423
|
-
</group>
|
|
424
|
-
{hasPhysics && physicsDef?.View ? (
|
|
425
|
-
<physicsDef.View
|
|
426
|
-
key={physicsKey}
|
|
427
|
-
properties={physics.properties}
|
|
428
|
-
position={transform.position}
|
|
429
|
-
rotation={transform.rotation}
|
|
430
|
-
scale={transform.scale}
|
|
431
|
-
editMode={editMode}
|
|
432
|
-
nodeId={gameObject.id}
|
|
433
|
-
registerRigidBodyRef={registerRigidBodyRef}
|
|
434
|
-
>{inner}</physicsDef.View>
|
|
435
|
-
) : null}
|
|
436
|
-
</>
|
|
437
|
-
);
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
if (hasPhysics && physicsDef?.View) {
|
|
441
|
-
return (
|
|
442
|
-
<physicsDef.View
|
|
443
|
-
key={physicsKey}
|
|
444
|
-
properties={physics.properties}
|
|
445
|
-
position={transform.position}
|
|
446
|
-
rotation={transform.rotation}
|
|
447
|
-
scale={transform.scale}
|
|
448
|
-
editMode={editMode}
|
|
449
|
-
nodeId={gameObject.id}
|
|
450
|
-
registerRigidBodyRef={registerRigidBodyRef}
|
|
451
|
-
>{inner}</physicsDef.View>
|
|
452
|
-
);
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
return (
|
|
456
|
-
<group
|
|
457
|
-
ref={groupRef}
|
|
458
|
-
position={transform.position}
|
|
459
|
-
rotation={transform.rotation}
|
|
460
|
-
scale={transform.scale}
|
|
461
|
-
onPointerDown={onDown}
|
|
462
|
-
onPointerMove={() => (clickValid.current = false)}
|
|
463
|
-
onPointerUp={onUp}
|
|
464
|
-
>
|
|
465
|
-
{inner}
|
|
466
|
-
</group>
|
|
467
|
-
);
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
interface RendererProps {
|
|
471
|
-
gameObject: GameObjectType;
|
|
472
|
-
selectedId?: string | null;
|
|
473
|
-
onSelect?: (id: string) => void;
|
|
474
|
-
onClick?: (event: ThreeEvent<PointerEvent>, entity: GameObjectType) => void;
|
|
475
|
-
registerRef: (id: string, obj: Object3D | null) => void;
|
|
476
|
-
registerRigidBodyRef: (id: string, rb: any) => void;
|
|
477
|
-
loadedModels: Record<string, Object3D>;
|
|
478
|
-
loadedTextures: Record<string, Texture>;
|
|
479
|
-
editMode?: boolean;
|
|
480
|
-
parentMatrix?: Matrix4;
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
const CHILD_HOST_COMPONENT_TYPES = new Set(["Environment"]);
|
|
484
|
-
|
|
485
|
-
function isChildHostType(type: string) {
|
|
486
|
-
return CHILD_HOST_COMPONENT_TYPES.has(type);
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
function getChildHostComponents(gameObject: GameObjectType) {
|
|
490
|
-
return Object.entries(gameObject.components ?? {}).flatMap(([key, comp]) => {
|
|
491
|
-
if (!comp?.type || !isChildHostType(comp.type)) return [];
|
|
492
|
-
|
|
493
|
-
const def = getComponent(comp.type);
|
|
494
|
-
if (!def?.View) return [];
|
|
495
|
-
|
|
496
|
-
return { key, View: def.View, properties: comp.properties };
|
|
497
|
-
});
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
interface RenderContext {
|
|
501
|
-
loadedModels: Record<string, Object3D>;
|
|
502
|
-
loadedTextures: Record<string, Texture>;
|
|
503
|
-
editMode?: boolean;
|
|
504
|
-
selectedId?: string | null;
|
|
505
|
-
registerRef: (id: string, obj: Object3D | null) => void;
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
interface RuntimeChildRendererProps {
|
|
509
|
-
selectedId?: string | null;
|
|
510
|
-
onSelect?: (id: string) => void;
|
|
511
|
-
onClick?: (event: ThreeEvent<PointerEvent>, entity: GameObjectType) => void;
|
|
512
|
-
registerRef: (id: string, obj: Object3D | null) => void;
|
|
513
|
-
registerRigidBodyRef: (id: string, rb: any) => void;
|
|
514
|
-
loadedModels: Record<string, Object3D>;
|
|
515
|
-
loadedTextures: Record<string, Texture>;
|
|
516
|
-
editMode?: boolean;
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
function createRenderContext(
|
|
520
|
-
loadedModels: Record<string, Object3D>,
|
|
521
|
-
loadedTextures: Record<string, Texture>,
|
|
522
|
-
editMode: boolean | undefined,
|
|
523
|
-
selectedId: string | null | undefined,
|
|
524
|
-
registerRef: (id: string, obj: Object3D | null) => void,
|
|
525
|
-
): RenderContext {
|
|
526
|
-
return { loadedModels, loadedTextures, editMode, selectedId, registerRef };
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
function renderSceneChildren(
|
|
530
|
-
gameObject: GameObjectType,
|
|
531
|
-
parentMatrix: Matrix4,
|
|
532
|
-
props: RuntimeChildRendererProps,
|
|
533
|
-
) {
|
|
534
|
-
return gameObject.children?.map(child =>
|
|
535
|
-
<GameObjectRenderer
|
|
536
|
-
key={child.id}
|
|
537
|
-
gameObject={child}
|
|
538
|
-
selectedId={props.selectedId}
|
|
539
|
-
onSelect={props.onSelect}
|
|
540
|
-
onClick={props.onClick}
|
|
541
|
-
registerRef={props.registerRef}
|
|
542
|
-
registerRigidBodyRef={props.registerRigidBodyRef}
|
|
543
|
-
loadedModels={props.loadedModels}
|
|
544
|
-
loadedTextures={props.loadedTextures}
|
|
545
|
-
editMode={props.editMode}
|
|
546
|
-
parentMatrix={parentMatrix}
|
|
547
|
-
/>
|
|
548
|
-
);
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
function walk(node: GameObjectType, fn: (n: GameObjectType) => void) {
|
|
552
|
-
fn(node);
|
|
553
|
-
node.children?.forEach(c => walk(c, fn));
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
function compose(node?: GameObjectType | null) {
|
|
557
|
-
const { position, rotation, scale } = getNodeTransformProps(node);
|
|
558
|
-
return new Matrix4().compose(
|
|
559
|
-
new Vector3(...position),
|
|
560
|
-
new Quaternion().setFromEuler(new Euler(...rotation)),
|
|
561
|
-
new Vector3(...scale)
|
|
562
|
-
);
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
function decompose(m: Matrix4) {
|
|
566
|
-
const p = new Vector3(), q = new Quaternion(), s = new Vector3();
|
|
567
|
-
m.decompose(p, q, s);
|
|
568
|
-
const e = new Euler().setFromQuaternion(q);
|
|
569
|
-
return {
|
|
570
|
-
position: [p.x, p.y, p.z] as [number, number, number],
|
|
571
|
-
rotation: [e.x, e.y, e.z] as [number, number, number],
|
|
572
|
-
scale: [s.x, s.y, s.z] as [number, number, number],
|
|
573
|
-
};
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
function getNodeTransformProps(node?: GameObjectType | null) {
|
|
577
|
-
const t = node?.components?.transform?.properties;
|
|
578
|
-
return {
|
|
579
|
-
position: t?.position ?? [0, 0, 0],
|
|
580
|
-
rotation: t?.rotation ?? [0, 0, 0],
|
|
581
|
-
scale: t?.scale ?? [1, 1, 1],
|
|
582
|
-
};
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
function computeParentWorldMatrix(root: GameObjectType, targetId: string): Matrix4 {
|
|
586
|
-
let result: Matrix4 | null = null;
|
|
587
|
-
|
|
588
|
-
const visit = (node: GameObjectType, parent: Matrix4) => {
|
|
589
|
-
if (node.id === targetId) {
|
|
590
|
-
result = parent.clone();
|
|
591
|
-
return;
|
|
592
|
-
}
|
|
593
|
-
const world = parent.clone().multiply(compose(node));
|
|
594
|
-
node.children?.forEach(c => !result && visit(c, world));
|
|
595
|
-
};
|
|
596
|
-
|
|
597
|
-
visit(root, IDENTITY);
|
|
598
|
-
return result ?? IDENTITY;
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
function renderCompositionSubtree(
|
|
602
|
-
gameObject: GameObjectType,
|
|
603
|
-
ctx: RenderContext,
|
|
604
|
-
parentMatrix = IDENTITY
|
|
605
|
-
): React.ReactNode {
|
|
606
|
-
if (!gameObject || gameObject.disabled) return null;
|
|
607
|
-
|
|
608
|
-
const transform = getNodeTransformProps(gameObject);
|
|
609
|
-
const world = parentMatrix.clone().multiply(compose(gameObject));
|
|
610
|
-
const childNodes = renderHostedChildren(gameObject, ctx, world);
|
|
611
|
-
|
|
612
|
-
return (
|
|
613
|
-
<group
|
|
614
|
-
key={gameObject.id}
|
|
615
|
-
position={transform.position}
|
|
616
|
-
rotation={transform.rotation}
|
|
617
|
-
scale={transform.scale}
|
|
618
|
-
>
|
|
619
|
-
{renderCompositionNode(gameObject, ctx, parentMatrix, childNodes)}
|
|
620
|
-
</group>
|
|
621
|
-
);
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
function renderHostedChildren(
|
|
625
|
-
gameObject: GameObjectType,
|
|
626
|
-
ctx: RenderContext,
|
|
627
|
-
parentMatrix: Matrix4,
|
|
628
|
-
) {
|
|
629
|
-
return gameObject.children?.map(child =>
|
|
630
|
-
renderCompositionSubtree(child, ctx, parentMatrix)
|
|
631
|
-
);
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
function renderCompositionNode(
|
|
635
|
-
gameObject: GameObjectType,
|
|
636
|
-
ctx: RenderContext,
|
|
637
|
-
parentMatrix?: Matrix4,
|
|
638
|
-
childNodes?: React.ReactNode
|
|
639
|
-
) {
|
|
640
|
-
const ownContent = renderNodeOwnContent(gameObject, ctx, parentMatrix);
|
|
641
|
-
const siblingContent = renderNodeSiblingComponents(gameObject, ctx, parentMatrix);
|
|
642
|
-
const subtree = <>{ownContent}{siblingContent}{childNodes}</>;
|
|
643
|
-
|
|
644
|
-
return wrapWithChildHosts(gameObject, ctx, parentMatrix, subtree);
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
function renderNodeOwnContent(
|
|
648
|
-
gameObject: GameObjectType,
|
|
649
|
-
ctx: RenderContext,
|
|
650
|
-
parentMatrix?: Matrix4
|
|
651
|
-
) {
|
|
652
|
-
const geometry = gameObject.components?.geometry;
|
|
653
|
-
const material = gameObject.components?.material;
|
|
654
|
-
const model = gameObject.components?.model;
|
|
655
|
-
const text = gameObject.components?.text;
|
|
656
|
-
|
|
657
|
-
const geometryDef = geometry && getComponent("Geometry");
|
|
658
|
-
const materialDef = material && getComponent("Material");
|
|
659
|
-
const modelDef = model && getComponent("Model");
|
|
660
|
-
const textDef = text && getComponent("Text");
|
|
661
|
-
|
|
662
|
-
const contextProps = {
|
|
663
|
-
loadedModels: ctx.loadedModels,
|
|
664
|
-
loadedTextures: ctx.loadedTextures,
|
|
665
|
-
editMode: ctx.editMode,
|
|
666
|
-
isSelected: ctx.selectedId === gameObject.id,
|
|
667
|
-
nodeId: gameObject.id,
|
|
668
|
-
parentMatrix,
|
|
669
|
-
registerRef: ctx.registerRef,
|
|
670
|
-
};
|
|
671
|
-
|
|
672
|
-
let core: React.ReactNode;
|
|
673
|
-
|
|
674
|
-
if (model && modelDef?.View) {
|
|
675
|
-
core = (
|
|
676
|
-
<modelDef.View properties={model.properties} {...contextProps}>
|
|
677
|
-
{material && materialDef?.View && (
|
|
678
|
-
<materialDef.View
|
|
679
|
-
key="material"
|
|
680
|
-
properties={material.properties}
|
|
681
|
-
{...contextProps}
|
|
682
|
-
/>
|
|
683
|
-
)}
|
|
684
|
-
</modelDef.View>
|
|
685
|
-
);
|
|
686
|
-
} else if (geometry && geometryDef?.View) {
|
|
687
|
-
core = (
|
|
688
|
-
<mesh castShadow receiveShadow>
|
|
689
|
-
<geometryDef.View properties={geometry.properties} {...contextProps} />
|
|
690
|
-
{material && materialDef?.View && (
|
|
691
|
-
<materialDef.View
|
|
692
|
-
key="material"
|
|
693
|
-
properties={material.properties}
|
|
694
|
-
{...contextProps}
|
|
695
|
-
/>
|
|
696
|
-
)}
|
|
697
|
-
</mesh>
|
|
698
|
-
);
|
|
699
|
-
} else if (text && textDef?.View) {
|
|
700
|
-
core = (
|
|
701
|
-
<>
|
|
702
|
-
<textDef.View properties={text.properties} {...contextProps} />
|
|
703
|
-
</>
|
|
704
|
-
);
|
|
705
|
-
} else {
|
|
706
|
-
core = null;
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
return core;
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
function renderNodeSiblingComponents(
|
|
713
|
-
gameObject: GameObjectType,
|
|
714
|
-
ctx: RenderContext,
|
|
715
|
-
parentMatrix?: Matrix4
|
|
716
|
-
) {
|
|
717
|
-
const contextProps = {
|
|
718
|
-
loadedModels: ctx.loadedModels,
|
|
719
|
-
loadedTextures: ctx.loadedTextures,
|
|
720
|
-
editMode: ctx.editMode,
|
|
721
|
-
isSelected: ctx.selectedId === gameObject.id,
|
|
722
|
-
nodeId: gameObject.id,
|
|
723
|
-
parentMatrix,
|
|
724
|
-
registerRef: ctx.registerRef,
|
|
725
|
-
};
|
|
726
|
-
|
|
727
|
-
return Object.entries(gameObject.components ?? {})
|
|
728
|
-
.filter(([key]) => !getNonComposableKeys().includes(key))
|
|
729
|
-
.flatMap(([key, comp]) => {
|
|
730
|
-
if (!comp?.type || isChildHostType(comp.type)) return [];
|
|
731
|
-
|
|
732
|
-
const def = getComponent(comp.type);
|
|
733
|
-
if (!def?.View) return [];
|
|
734
|
-
|
|
735
|
-
return <def.View key={key} properties={comp.properties} {...contextProps} />;
|
|
736
|
-
});
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
function wrapWithChildHosts(
|
|
740
|
-
gameObject: GameObjectType,
|
|
741
|
-
ctx: RenderContext,
|
|
742
|
-
parentMatrix: Matrix4 | undefined,
|
|
743
|
-
subtree: React.ReactNode
|
|
744
|
-
) {
|
|
745
|
-
const contextProps = {
|
|
746
|
-
loadedModels: ctx.loadedModels,
|
|
747
|
-
loadedTextures: ctx.loadedTextures,
|
|
748
|
-
editMode: ctx.editMode,
|
|
749
|
-
isSelected: ctx.selectedId === gameObject.id,
|
|
750
|
-
nodeId: gameObject.id,
|
|
751
|
-
parentMatrix,
|
|
752
|
-
registerRef: ctx.registerRef,
|
|
753
|
-
};
|
|
754
|
-
|
|
755
|
-
const childHosts = getChildHostComponents(gameObject);
|
|
756
|
-
|
|
757
|
-
return childHosts.reduce(
|
|
758
|
-
(acc, { key, View, properties }) => (
|
|
759
|
-
<View key={key} properties={properties} {...contextProps}>
|
|
760
|
-
{acc}
|
|
761
|
-
</View>
|
|
762
|
-
),
|
|
763
|
-
subtree
|
|
764
|
-
);
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
export default PrefabRoot;
|