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