react-three-game 0.0.60 → 0.0.62

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 (66) hide show
  1. package/README.md +56 -0
  2. package/dist/index.d.ts +1 -1
  3. package/dist/shared/GameCanvas.d.ts +2 -1
  4. package/dist/shared/GameCanvas.js +7 -2
  5. package/dist/tools/prefabeditor/PrefabEditor.d.ts +18 -4
  6. package/dist/tools/prefabeditor/PrefabEditor.js +90 -36
  7. package/dist/tools/prefabeditor/utils.d.ts +2 -0
  8. package/dist/tools/prefabeditor/utils.js +15 -0
  9. package/package.json +9 -3
  10. package/.gitattributes +0 -2
  11. package/.github/copilot-instructions.md +0 -83
  12. package/.github/workflows/nextjs.yml +0 -99
  13. package/.gitmodules +0 -3
  14. package/assets/architecture.png +0 -0
  15. package/assets/editor.gif +0 -0
  16. package/assets/favicon.ico +0 -0
  17. package/assets/react-three-game-logo.png +0 -0
  18. package/dist/tools/dragdrop/page.d.ts +0 -1
  19. package/dist/tools/dragdrop/page.js +0 -11
  20. package/dist/tools/prefabeditor/EntityEvents.d.ts +0 -54
  21. package/dist/tools/prefabeditor/EntityEvents.js +0 -85
  22. package/dist/tools/prefabeditor/page.d.ts +0 -1
  23. package/dist/tools/prefabeditor/page.js +0 -5
  24. package/react-three-game-skill/.gitattributes +0 -2
  25. package/react-three-game-skill/README.md +0 -7
  26. package/react-three-game-skill/react-three-game/SKILL.md +0 -514
  27. package/react-three-game-skill/react-three-game/rules/ADVANCED_PHYSICS.md +0 -472
  28. package/react-three-game-skill/react-three-game/rules/LIGHTING.md +0 -6
  29. package/src/helpers/SoundManager.ts +0 -130
  30. package/src/helpers/index.ts +0 -91
  31. package/src/index.ts +0 -59
  32. package/src/shared/ContactShadow.tsx +0 -74
  33. package/src/shared/GameCanvas.tsx +0 -52
  34. package/src/tools/assetviewer/page.tsx +0 -425
  35. package/src/tools/dragdrop/DragDropLoader.tsx +0 -159
  36. package/src/tools/dragdrop/index.ts +0 -4
  37. package/src/tools/dragdrop/modelLoader.ts +0 -204
  38. package/src/tools/dragdrop/page.tsx +0 -45
  39. package/src/tools/prefabeditor/Dropdown.tsx +0 -112
  40. package/src/tools/prefabeditor/EditorContext.tsx +0 -25
  41. package/src/tools/prefabeditor/EditorTree.tsx +0 -452
  42. package/src/tools/prefabeditor/EditorTreeMenus.tsx +0 -307
  43. package/src/tools/prefabeditor/EditorUI.tsx +0 -204
  44. package/src/tools/prefabeditor/EventSystem.tsx +0 -36
  45. package/src/tools/prefabeditor/GameEvents.ts +0 -191
  46. package/src/tools/prefabeditor/InstanceProvider.tsx +0 -466
  47. package/src/tools/prefabeditor/PrefabEditor.tsx +0 -256
  48. package/src/tools/prefabeditor/PrefabRoot.tsx +0 -767
  49. package/src/tools/prefabeditor/components/AmbientLightComponent.tsx +0 -34
  50. package/src/tools/prefabeditor/components/CameraComponent.tsx +0 -117
  51. package/src/tools/prefabeditor/components/ComponentRegistry.ts +0 -40
  52. package/src/tools/prefabeditor/components/DirectionalLightComponent.tsx +0 -210
  53. package/src/tools/prefabeditor/components/EnvironmentComponent.tsx +0 -47
  54. package/src/tools/prefabeditor/components/GeometryComponent.tsx +0 -133
  55. package/src/tools/prefabeditor/components/Input.tsx +0 -820
  56. package/src/tools/prefabeditor/components/MaterialComponent.tsx +0 -431
  57. package/src/tools/prefabeditor/components/ModelComponent.tsx +0 -176
  58. package/src/tools/prefabeditor/components/PhysicsComponent.tsx +0 -188
  59. package/src/tools/prefabeditor/components/SpotLightComponent.tsx +0 -109
  60. package/src/tools/prefabeditor/components/TextComponent.tsx +0 -137
  61. package/src/tools/prefabeditor/components/TransformComponent.tsx +0 -173
  62. package/src/tools/prefabeditor/components/index.ts +0 -26
  63. package/src/tools/prefabeditor/page.tsx +0 -10
  64. package/src/tools/prefabeditor/styles.ts +0 -235
  65. package/src/tools/prefabeditor/types.ts +0 -20
  66. 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;