react-three-game 0.0.56 → 0.0.57
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/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/shared/GameCanvas.js +1 -3
- package/dist/tools/assetviewer/page.js +35 -14
- package/dist/tools/prefabeditor/Dropdown.d.ts +15 -0
- package/dist/tools/prefabeditor/Dropdown.js +82 -0
- package/dist/tools/prefabeditor/EditorContext.d.ts +5 -0
- package/dist/tools/prefabeditor/EditorTree.js +138 -56
- package/dist/tools/prefabeditor/EditorUI.js +1 -1
- package/dist/tools/prefabeditor/PrefabEditor.d.ts +1 -0
- package/dist/tools/prefabeditor/PrefabEditor.js +13 -2
- package/dist/tools/prefabeditor/PrefabRoot.d.ts +1 -0
- package/dist/tools/prefabeditor/PrefabRoot.js +120 -34
- package/dist/tools/prefabeditor/components/AmbientLightComponent.js +3 -7
- package/dist/tools/prefabeditor/components/CameraComponent.d.ts +3 -0
- package/dist/tools/prefabeditor/components/CameraComponent.js +25 -0
- package/dist/tools/prefabeditor/components/DirectionalLightComponent.js +2 -2
- package/dist/tools/prefabeditor/components/EnvironmentComponent.d.ts +3 -0
- package/dist/tools/prefabeditor/components/EnvironmentComponent.js +15 -0
- package/dist/tools/prefabeditor/components/GeometryComponent.js +46 -46
- package/dist/tools/prefabeditor/components/Input.d.ts +51 -1
- package/dist/tools/prefabeditor/components/Input.js +73 -21
- package/dist/tools/prefabeditor/components/MaterialComponent.d.ts +8 -2
- package/dist/tools/prefabeditor/components/MaterialComponent.js +122 -14
- package/dist/tools/prefabeditor/components/ModelComponent.js +44 -3
- package/dist/tools/prefabeditor/components/PhysicsComponent.js +16 -81
- package/dist/tools/prefabeditor/components/SpotLightComponent.js +4 -12
- package/dist/tools/prefabeditor/components/TextComponent.js +7 -53
- package/dist/tools/prefabeditor/components/TransformComponent.js +18 -8
- package/dist/tools/prefabeditor/components/index.js +5 -1
- package/dist/tools/prefabeditor/styles.d.ts +5 -2
- package/dist/tools/prefabeditor/styles.js +7 -3
- package/dist/tools/prefabeditor/utils.d.ts +4 -3
- package/dist/tools/prefabeditor/utils.js +53 -5
- package/package.json +1 -1
- package/src/index.ts +7 -0
- package/src/shared/GameCanvas.tsx +0 -3
- package/src/tools/assetviewer/page.tsx +77 -45
- package/src/tools/prefabeditor/Dropdown.tsx +112 -0
- package/src/tools/prefabeditor/EditorContext.tsx +5 -0
- package/src/tools/prefabeditor/EditorTree.tsx +234 -101
- package/src/tools/prefabeditor/EditorUI.tsx +1 -1
- package/src/tools/prefabeditor/PrefabEditor.tsx +17 -4
- package/src/tools/prefabeditor/PrefabRoot.tsx +208 -58
- package/src/tools/prefabeditor/components/AmbientLightComponent.tsx +5 -11
- package/src/tools/prefabeditor/components/CameraComponent.tsx +80 -0
- package/src/tools/prefabeditor/components/DirectionalLightComponent.tsx +2 -2
- package/src/tools/prefabeditor/components/EnvironmentComponent.tsx +47 -0
- package/src/tools/prefabeditor/components/GeometryComponent.tsx +69 -63
- package/src/tools/prefabeditor/components/Input.tsx +220 -27
- package/src/tools/prefabeditor/components/MaterialComponent.tsx +178 -16
- package/src/tools/prefabeditor/components/ModelComponent.tsx +51 -4
- package/src/tools/prefabeditor/components/PhysicsComponent.tsx +44 -85
- package/src/tools/prefabeditor/components/SpotLightComponent.tsx +11 -17
- package/src/tools/prefabeditor/components/TextComponent.tsx +58 -57
- package/src/tools/prefabeditor/components/TransformComponent.tsx +61 -9
- package/src/tools/prefabeditor/components/index.ts +5 -1
- package/src/tools/prefabeditor/styles.ts +7 -3
- package/src/tools/prefabeditor/utils.ts +55 -4
|
@@ -8,7 +8,7 @@ import { getComponent, registerComponent, getNonComposableKeys } from "./compone
|
|
|
8
8
|
import components from "./components";
|
|
9
9
|
import { loadModel } from "../dragdrop/modelLoader";
|
|
10
10
|
import { GameInstance, GameInstanceProvider, useInstanceCheck } from "./InstanceProvider";
|
|
11
|
-
import { updateNode } from "./utils";
|
|
11
|
+
import { focusCameraOnObject, updateNode } from "./utils";
|
|
12
12
|
import { PhysicsProps } from "./components/PhysicsComponent";
|
|
13
13
|
import { EditorContext } from "./EditorContext";
|
|
14
14
|
|
|
@@ -24,6 +24,7 @@ export interface PrefabRootRef {
|
|
|
24
24
|
rigidBodyRefs: Map<string, any>; // RigidBody refs only populated when using physics
|
|
25
25
|
injectModel: (filename: string, model: Object3D) => void;
|
|
26
26
|
injectTexture: (filename: string, file: File) => void;
|
|
27
|
+
focusNode: (nodeId: string) => void;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
export const PrefabRoot = forwardRef<PrefabRootRef, {
|
|
@@ -40,15 +41,19 @@ export const PrefabRoot = forwardRef<PrefabRootRef, {
|
|
|
40
41
|
const editorContext = useContext(EditorContext);
|
|
41
42
|
const transformMode = editorContext?.transformMode ?? "translate";
|
|
42
43
|
const snapResolution = editorContext?.snapResolution ?? 0;
|
|
44
|
+
const positionSnap = editorContext?.positionSnap ?? 0.5;
|
|
45
|
+
const rotationSnap = editorContext?.rotationSnap ?? Math.PI / 4;
|
|
43
46
|
|
|
44
47
|
// prefab root state
|
|
45
48
|
const [models, setModels] = useState<Record<string, Object3D>>({});
|
|
46
49
|
const [textures, setTextures] = useState<Record<string, Texture>>({});
|
|
47
50
|
const loading = useRef(new Set<string>());
|
|
51
|
+
const failedTextures = useRef(new Set<string>());
|
|
48
52
|
const objectRefs = useRef<Record<string, Object3D | null>>({});
|
|
49
53
|
const rigidBodyRefs = useRef<Map<string, RapierRigidBody | null>>(new Map());
|
|
50
54
|
const [selectedObject, setSelectedObject] = useState<Object3D | null>(null);
|
|
51
55
|
const rootRef = useRef<Group>(null);
|
|
56
|
+
const controlsRef = useRef<any>(null);
|
|
52
57
|
|
|
53
58
|
const injectModel = useCallback((filename: string, model: Object3D) => {
|
|
54
59
|
setModels(m => ({ ...m, [filename]: model }));
|
|
@@ -69,7 +74,16 @@ export const PrefabRoot = forwardRef<PrefabRootRef, {
|
|
|
69
74
|
root: rootRef.current,
|
|
70
75
|
rigidBodyRefs: rigidBodyRefs.current,
|
|
71
76
|
injectModel,
|
|
72
|
-
injectTexture
|
|
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
|
+
}
|
|
73
87
|
}), [injectModel, injectTexture]);
|
|
74
88
|
|
|
75
89
|
const registerRef = useCallback((id: string, obj: Object3D | null) => {
|
|
@@ -128,6 +142,8 @@ export const PrefabRoot = forwardRef<PrefabRootRef, {
|
|
|
128
142
|
modelsToLoad.add(node.components.model.properties.filename);
|
|
129
143
|
node.components?.material?.properties?.texture &&
|
|
130
144
|
texturesToLoad.add(node.components.material.properties.texture);
|
|
145
|
+
node.components?.material?.properties?.normalMapTexture &&
|
|
146
|
+
texturesToLoad.add(node.components.material.properties.normalMapTexture);
|
|
131
147
|
});
|
|
132
148
|
|
|
133
149
|
modelsToLoad.forEach(async file => {
|
|
@@ -145,7 +161,7 @@ export const PrefabRoot = forwardRef<PrefabRootRef, {
|
|
|
145
161
|
|
|
146
162
|
const loader = new TextureLoader();
|
|
147
163
|
texturesToLoad.forEach(file => {
|
|
148
|
-
if (textures[file] || loading.current.has(file)) return;
|
|
164
|
+
if (textures[file] || loading.current.has(file) || failedTextures.current.has(file)) return;
|
|
149
165
|
loading.current.add(file);
|
|
150
166
|
|
|
151
167
|
// Handle full URLs (http/https) or regular paths
|
|
@@ -159,8 +175,9 @@ export const PrefabRoot = forwardRef<PrefabRootRef, {
|
|
|
159
175
|
tex.colorSpace = SRGBColorSpace;
|
|
160
176
|
setTextures(t => ({ ...t, [file]: tex }));
|
|
161
177
|
}, undefined, (err) => {
|
|
162
|
-
console.
|
|
178
|
+
console.warn(`Failed to load texture: ${path}`, err);
|
|
163
179
|
loading.current.delete(file);
|
|
180
|
+
failedTextures.current.add(file);
|
|
164
181
|
});
|
|
165
182
|
});
|
|
166
183
|
}, [data, models, textures]);
|
|
@@ -190,16 +207,16 @@ export const PrefabRoot = forwardRef<PrefabRootRef, {
|
|
|
190
207
|
|
|
191
208
|
{editMode && (
|
|
192
209
|
<>
|
|
193
|
-
<MapControls makeDefault />
|
|
210
|
+
<MapControls ref={controlsRef} makeDefault />
|
|
194
211
|
{selectedObject && (
|
|
195
212
|
<TransformControls
|
|
196
|
-
key={`transform-${snapResolution}`}
|
|
213
|
+
key={`transform-${transformMode}-${positionSnap}-${rotationSnap}-${snapResolution}`}
|
|
197
214
|
object={selectedObject}
|
|
198
215
|
mode={transformMode}
|
|
199
216
|
space="local"
|
|
200
217
|
onObjectChange={onTransformChange}
|
|
201
|
-
translationSnap={
|
|
202
|
-
rotationSnap={
|
|
218
|
+
translationSnap={positionSnap > 0 ? positionSnap : undefined}
|
|
219
|
+
rotationSnap={rotationSnap > 0 ? rotationSnap : undefined}
|
|
203
220
|
scaleSnap={snapResolution > 0 ? snapResolution : undefined}
|
|
204
221
|
/>
|
|
205
222
|
)}
|
|
@@ -362,6 +379,19 @@ function StandardNode({
|
|
|
362
379
|
const physicsDef = hasPhysics ? getComponent("Physics") : null;
|
|
363
380
|
const isInstanced = gameObject.components?.model?.properties?.instanced;
|
|
364
381
|
const physicsKey = `physics_${gameObject.id}_${isInstanced ? 'instanced' : 'standard'}`;
|
|
382
|
+
const renderCtx = createRenderContext(loadedModels, loadedTextures, editMode, selectedId, registerRef);
|
|
383
|
+
const childNodes = getChildHostComponents(gameObject).length > 0
|
|
384
|
+
? renderHostedChildren(gameObject, renderCtx, world)
|
|
385
|
+
: renderSceneChildren(gameObject, world, {
|
|
386
|
+
selectedId,
|
|
387
|
+
onSelect,
|
|
388
|
+
onClick,
|
|
389
|
+
registerRef,
|
|
390
|
+
registerRigidBodyRef,
|
|
391
|
+
loadedModels,
|
|
392
|
+
loadedTextures,
|
|
393
|
+
editMode,
|
|
394
|
+
});
|
|
365
395
|
|
|
366
396
|
const inner = (
|
|
367
397
|
<group
|
|
@@ -369,22 +399,7 @@ function StandardNode({
|
|
|
369
399
|
onPointerMove={editMode ? () => (clickValid.current = false) : undefined}
|
|
370
400
|
onPointerUp={editMode ? onUp : undefined}
|
|
371
401
|
>
|
|
372
|
-
{
|
|
373
|
-
{gameObject.children?.map(child => (
|
|
374
|
-
<GameObjectRenderer
|
|
375
|
-
key={child.id}
|
|
376
|
-
gameObject={child}
|
|
377
|
-
selectedId={selectedId}
|
|
378
|
-
onSelect={onSelect}
|
|
379
|
-
onClick={onClick}
|
|
380
|
-
registerRef={registerRef}
|
|
381
|
-
registerRigidBodyRef={registerRigidBodyRef}
|
|
382
|
-
loadedModels={loadedModels}
|
|
383
|
-
loadedTextures={loadedTextures}
|
|
384
|
-
editMode={editMode}
|
|
385
|
-
parentMatrix={world}
|
|
386
|
-
/>
|
|
387
|
-
))}
|
|
402
|
+
{renderCompositionNode(gameObject, renderCtx, parentMatrix, childNodes)}
|
|
388
403
|
</group>
|
|
389
404
|
);
|
|
390
405
|
|
|
@@ -468,6 +483,74 @@ interface RendererProps {
|
|
|
468
483
|
parentMatrix?: Matrix4;
|
|
469
484
|
}
|
|
470
485
|
|
|
486
|
+
const CHILD_HOST_COMPONENT_TYPES = new Set(["Environment"]);
|
|
487
|
+
|
|
488
|
+
function isChildHostType(type: string) {
|
|
489
|
+
return CHILD_HOST_COMPONENT_TYPES.has(type);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function getChildHostComponents(gameObject: GameObjectType) {
|
|
493
|
+
return Object.entries(gameObject.components ?? {}).flatMap(([key, comp]) => {
|
|
494
|
+
if (!comp?.type || !isChildHostType(comp.type)) return [];
|
|
495
|
+
|
|
496
|
+
const def = getComponent(comp.type);
|
|
497
|
+
if (!def?.View) return [];
|
|
498
|
+
|
|
499
|
+
return { key, View: def.View, properties: comp.properties };
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
interface RenderContext {
|
|
504
|
+
loadedModels: Record<string, Object3D>;
|
|
505
|
+
loadedTextures: Record<string, Texture>;
|
|
506
|
+
editMode?: boolean;
|
|
507
|
+
selectedId?: string | null;
|
|
508
|
+
registerRef: (id: string, obj: Object3D | null) => void;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
interface RuntimeChildRendererProps {
|
|
512
|
+
selectedId?: string | null;
|
|
513
|
+
onSelect?: (id: string) => void;
|
|
514
|
+
onClick?: (event: ThreeEvent<PointerEvent>, entity: GameObjectType) => void;
|
|
515
|
+
registerRef: (id: string, obj: Object3D | null) => void;
|
|
516
|
+
registerRigidBodyRef: (id: string, rb: any) => void;
|
|
517
|
+
loadedModels: Record<string, Object3D>;
|
|
518
|
+
loadedTextures: Record<string, Texture>;
|
|
519
|
+
editMode?: boolean;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function createRenderContext(
|
|
523
|
+
loadedModels: Record<string, Object3D>,
|
|
524
|
+
loadedTextures: Record<string, Texture>,
|
|
525
|
+
editMode: boolean | undefined,
|
|
526
|
+
selectedId: string | null | undefined,
|
|
527
|
+
registerRef: (id: string, obj: Object3D | null) => void,
|
|
528
|
+
): RenderContext {
|
|
529
|
+
return { loadedModels, loadedTextures, editMode, selectedId, registerRef };
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function renderSceneChildren(
|
|
533
|
+
gameObject: GameObjectType,
|
|
534
|
+
parentMatrix: Matrix4,
|
|
535
|
+
props: RuntimeChildRendererProps,
|
|
536
|
+
) {
|
|
537
|
+
return gameObject.children?.map(child =>
|
|
538
|
+
<GameObjectRenderer
|
|
539
|
+
key={child.id}
|
|
540
|
+
gameObject={child}
|
|
541
|
+
selectedId={props.selectedId}
|
|
542
|
+
onSelect={props.onSelect}
|
|
543
|
+
onClick={props.onClick}
|
|
544
|
+
registerRef={props.registerRef}
|
|
545
|
+
registerRigidBodyRef={props.registerRigidBodyRef}
|
|
546
|
+
loadedModels={props.loadedModels}
|
|
547
|
+
loadedTextures={props.loadedTextures}
|
|
548
|
+
editMode={props.editMode}
|
|
549
|
+
parentMatrix={parentMatrix}
|
|
550
|
+
/>
|
|
551
|
+
);
|
|
552
|
+
}
|
|
553
|
+
|
|
471
554
|
function walk(node: GameObjectType, fn: (n: GameObjectType) => void) {
|
|
472
555
|
fn(node);
|
|
473
556
|
node.children?.forEach(c => walk(c, fn));
|
|
@@ -518,14 +601,55 @@ function computeParentWorldMatrix(root: GameObjectType, targetId: string): Matri
|
|
|
518
601
|
return result ?? IDENTITY;
|
|
519
602
|
}
|
|
520
603
|
|
|
521
|
-
function
|
|
604
|
+
function renderCompositionSubtree(
|
|
605
|
+
gameObject: GameObjectType,
|
|
606
|
+
ctx: RenderContext,
|
|
607
|
+
parentMatrix = IDENTITY
|
|
608
|
+
): React.ReactNode {
|
|
609
|
+
if (!gameObject || gameObject.disabled) return null;
|
|
610
|
+
|
|
611
|
+
const transform = getNodeTransformProps(gameObject);
|
|
612
|
+
const world = parentMatrix.clone().multiply(compose(gameObject));
|
|
613
|
+
const childNodes = renderHostedChildren(gameObject, ctx, world);
|
|
614
|
+
|
|
615
|
+
return (
|
|
616
|
+
<group
|
|
617
|
+
key={gameObject.id}
|
|
618
|
+
position={transform.position}
|
|
619
|
+
rotation={transform.rotation}
|
|
620
|
+
scale={transform.scale}
|
|
621
|
+
>
|
|
622
|
+
{renderCompositionNode(gameObject, ctx, parentMatrix, childNodes)}
|
|
623
|
+
</group>
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
function renderHostedChildren(
|
|
628
|
+
gameObject: GameObjectType,
|
|
629
|
+
ctx: RenderContext,
|
|
630
|
+
parentMatrix: Matrix4,
|
|
631
|
+
) {
|
|
632
|
+
return gameObject.children?.map(child =>
|
|
633
|
+
renderCompositionSubtree(child, ctx, parentMatrix)
|
|
634
|
+
);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
function renderCompositionNode(
|
|
522
638
|
gameObject: GameObjectType,
|
|
523
|
-
ctx:
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
639
|
+
ctx: RenderContext,
|
|
640
|
+
parentMatrix?: Matrix4,
|
|
641
|
+
childNodes?: React.ReactNode
|
|
642
|
+
) {
|
|
643
|
+
const ownContent = renderNodeOwnContent(gameObject, ctx, parentMatrix);
|
|
644
|
+
const siblingContent = renderNodeSiblingComponents(gameObject, ctx, parentMatrix);
|
|
645
|
+
const subtree = <>{ownContent}{siblingContent}{childNodes}</>;
|
|
646
|
+
|
|
647
|
+
return wrapWithChildHosts(gameObject, ctx, parentMatrix, subtree);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
function renderNodeOwnContent(
|
|
651
|
+
gameObject: GameObjectType,
|
|
652
|
+
ctx: RenderContext,
|
|
529
653
|
parentMatrix?: Matrix4
|
|
530
654
|
) {
|
|
531
655
|
const geometry = gameObject.components?.geometry;
|
|
@@ -542,31 +666,12 @@ function renderCoreNode(
|
|
|
542
666
|
loadedModels: ctx.loadedModels,
|
|
543
667
|
loadedTextures: ctx.loadedTextures,
|
|
544
668
|
editMode: ctx.editMode,
|
|
669
|
+
isSelected: ctx.selectedId === gameObject.id,
|
|
670
|
+
nodeId: gameObject.id,
|
|
545
671
|
parentMatrix,
|
|
546
672
|
registerRef: ctx.registerRef,
|
|
547
673
|
};
|
|
548
674
|
|
|
549
|
-
const wrappers: Array<{ key: string; View: any; properties: any }> = [];
|
|
550
|
-
const leaves: React.ReactNode[] = [];
|
|
551
|
-
|
|
552
|
-
if (gameObject.components) {
|
|
553
|
-
Object.entries(gameObject.components)
|
|
554
|
-
.filter(([k]) => !getNonComposableKeys().includes(k))
|
|
555
|
-
.forEach(([key, comp]) => {
|
|
556
|
-
if (!comp?.type) return;
|
|
557
|
-
const def = getComponent(comp.type);
|
|
558
|
-
if (!def?.View) return;
|
|
559
|
-
|
|
560
|
-
if (def.View.toString().includes("children")) {
|
|
561
|
-
wrappers.push({ key, View: def.View, properties: comp.properties });
|
|
562
|
-
} else {
|
|
563
|
-
leaves.push(
|
|
564
|
-
<def.View key={key} properties={comp.properties} {...contextProps} />
|
|
565
|
-
);
|
|
566
|
-
}
|
|
567
|
-
});
|
|
568
|
-
}
|
|
569
|
-
|
|
570
675
|
let core: React.ReactNode;
|
|
571
676
|
|
|
572
677
|
if (model && modelDef?.View) {
|
|
@@ -579,7 +684,6 @@ function renderCoreNode(
|
|
|
579
684
|
{...contextProps}
|
|
580
685
|
/>
|
|
581
686
|
)}
|
|
582
|
-
{leaves}
|
|
583
687
|
</modelDef.View>
|
|
584
688
|
);
|
|
585
689
|
} else if (geometry && geometryDef?.View) {
|
|
@@ -593,27 +697,73 @@ function renderCoreNode(
|
|
|
593
697
|
{...contextProps}
|
|
594
698
|
/>
|
|
595
699
|
)}
|
|
596
|
-
{leaves}
|
|
597
700
|
</mesh>
|
|
598
701
|
);
|
|
599
702
|
} else if (text && textDef?.View) {
|
|
600
703
|
core = (
|
|
601
704
|
<>
|
|
602
705
|
<textDef.View properties={text.properties} {...contextProps} />
|
|
603
|
-
{leaves}
|
|
604
706
|
</>
|
|
605
707
|
);
|
|
606
708
|
} else {
|
|
607
|
-
core =
|
|
709
|
+
core = null;
|
|
608
710
|
}
|
|
609
711
|
|
|
610
|
-
return
|
|
712
|
+
return core;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
function renderNodeSiblingComponents(
|
|
716
|
+
gameObject: GameObjectType,
|
|
717
|
+
ctx: RenderContext,
|
|
718
|
+
parentMatrix?: Matrix4
|
|
719
|
+
) {
|
|
720
|
+
const contextProps = {
|
|
721
|
+
loadedModels: ctx.loadedModels,
|
|
722
|
+
loadedTextures: ctx.loadedTextures,
|
|
723
|
+
editMode: ctx.editMode,
|
|
724
|
+
isSelected: ctx.selectedId === gameObject.id,
|
|
725
|
+
nodeId: gameObject.id,
|
|
726
|
+
parentMatrix,
|
|
727
|
+
registerRef: ctx.registerRef,
|
|
728
|
+
};
|
|
729
|
+
|
|
730
|
+
return Object.entries(gameObject.components ?? {})
|
|
731
|
+
.filter(([key]) => !getNonComposableKeys().includes(key))
|
|
732
|
+
.flatMap(([key, comp]) => {
|
|
733
|
+
if (!comp?.type || isChildHostType(comp.type)) return [];
|
|
734
|
+
|
|
735
|
+
const def = getComponent(comp.type);
|
|
736
|
+
if (!def?.View) return [];
|
|
737
|
+
|
|
738
|
+
return <def.View key={key} properties={comp.properties} {...contextProps} />;
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
function wrapWithChildHosts(
|
|
743
|
+
gameObject: GameObjectType,
|
|
744
|
+
ctx: RenderContext,
|
|
745
|
+
parentMatrix: Matrix4 | undefined,
|
|
746
|
+
subtree: React.ReactNode
|
|
747
|
+
) {
|
|
748
|
+
const contextProps = {
|
|
749
|
+
loadedModels: ctx.loadedModels,
|
|
750
|
+
loadedTextures: ctx.loadedTextures,
|
|
751
|
+
editMode: ctx.editMode,
|
|
752
|
+
isSelected: ctx.selectedId === gameObject.id,
|
|
753
|
+
nodeId: gameObject.id,
|
|
754
|
+
parentMatrix,
|
|
755
|
+
registerRef: ctx.registerRef,
|
|
756
|
+
};
|
|
757
|
+
|
|
758
|
+
const childHosts = getChildHostComponents(gameObject);
|
|
759
|
+
|
|
760
|
+
return childHosts.reduce(
|
|
611
761
|
(acc, { key, View, properties }) => (
|
|
612
762
|
<View key={key} properties={properties} {...contextProps}>
|
|
613
763
|
{acc}
|
|
614
764
|
</View>
|
|
615
765
|
),
|
|
616
|
-
|
|
766
|
+
subtree
|
|
617
767
|
);
|
|
618
768
|
}
|
|
619
769
|
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import { Component } from "./ComponentRegistry";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
const ambientLightFields: FieldDefinition[] = [
|
|
5
|
-
{ name: 'color', type: 'color', label: 'Color' },
|
|
6
|
-
{ name: 'intensity', type: 'number', label: 'Intensity', step: 0.1, min: 0 },
|
|
7
|
-
];
|
|
2
|
+
import { ColorField, FieldGroup, NumberField } from "./Input";
|
|
8
3
|
|
|
9
4
|
function AmbientLightComponentEditor({
|
|
10
5
|
component,
|
|
@@ -14,11 +9,10 @@ function AmbientLightComponentEditor({
|
|
|
14
9
|
onUpdate: (newProps: any) => void;
|
|
15
10
|
}) {
|
|
16
11
|
return (
|
|
17
|
-
<
|
|
18
|
-
|
|
19
|
-
values={component.properties}
|
|
20
|
-
|
|
21
|
-
/>
|
|
12
|
+
<FieldGroup>
|
|
13
|
+
<ColorField name="color" label="Color" values={component.properties} onChange={onUpdate} />
|
|
14
|
+
<NumberField name="intensity" label="Intensity" values={component.properties} onChange={onUpdate} min={0} step={0.1} fallback={1} />
|
|
15
|
+
</FieldGroup>
|
|
22
16
|
);
|
|
23
17
|
}
|
|
24
18
|
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { PerspectiveCamera, useHelper } from '@react-three/drei';
|
|
2
|
+
import { useRef } from 'react';
|
|
3
|
+
import { CameraHelper, PerspectiveCamera as ThreePerspectiveCamera } from 'three';
|
|
4
|
+
import { Component } from './ComponentRegistry';
|
|
5
|
+
import { FieldGroup, NumberField } from './Input';
|
|
6
|
+
|
|
7
|
+
function CameraComponentEditor({ component, onUpdate }: { component: any; onUpdate: (newComp: any) => void }) {
|
|
8
|
+
return (
|
|
9
|
+
<FieldGroup>
|
|
10
|
+
<NumberField
|
|
11
|
+
name="fov"
|
|
12
|
+
label="FOV"
|
|
13
|
+
values={component.properties}
|
|
14
|
+
onChange={onUpdate}
|
|
15
|
+
fallback={50}
|
|
16
|
+
min={1}
|
|
17
|
+
max={179}
|
|
18
|
+
step={1}
|
|
19
|
+
/>
|
|
20
|
+
<NumberField
|
|
21
|
+
name="near"
|
|
22
|
+
label="Near"
|
|
23
|
+
values={component.properties}
|
|
24
|
+
onChange={onUpdate}
|
|
25
|
+
fallback={0.1}
|
|
26
|
+
min={0.001}
|
|
27
|
+
step={0.1}
|
|
28
|
+
/>
|
|
29
|
+
<NumberField
|
|
30
|
+
name="zoom"
|
|
31
|
+
label="Zoom"
|
|
32
|
+
values={component.properties}
|
|
33
|
+
onChange={onUpdate}
|
|
34
|
+
fallback={1}
|
|
35
|
+
min={0.01}
|
|
36
|
+
step={0.1}
|
|
37
|
+
/>
|
|
38
|
+
<NumberField
|
|
39
|
+
name="far"
|
|
40
|
+
label="Far"
|
|
41
|
+
values={component.properties}
|
|
42
|
+
onChange={onUpdate}
|
|
43
|
+
fallback={1000}
|
|
44
|
+
min={0.1}
|
|
45
|
+
step={1}
|
|
46
|
+
/>
|
|
47
|
+
</FieldGroup>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function CameraComponentView({ properties, editMode, isSelected }: { properties: any; editMode?: boolean; isSelected?: boolean }) {
|
|
52
|
+
const fov = properties?.fov ?? 50;
|
|
53
|
+
const near = properties?.near ?? 0.1;
|
|
54
|
+
const zoom = properties?.zoom ?? 1;
|
|
55
|
+
const far = properties?.far ?? 1000;
|
|
56
|
+
const cameraRef = useRef<ThreePerspectiveCamera>(null);
|
|
57
|
+
|
|
58
|
+
useHelper(editMode && isSelected ? (cameraRef as React.RefObject<ThreePerspectiveCamera>) : null, CameraHelper);
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<>
|
|
62
|
+
<PerspectiveCamera ref={cameraRef} makeDefault={!editMode} fov={fov} near={near} zoom={zoom} far={far} />
|
|
63
|
+
{editMode && !isSelected ? (
|
|
64
|
+
<mesh>
|
|
65
|
+
<boxGeometry args={[0.34, 0.22, 0.18]} />
|
|
66
|
+
<meshBasicMaterial color="#22d3ee" wireframe />
|
|
67
|
+
</mesh>
|
|
68
|
+
) : null}
|
|
69
|
+
</>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const CameraComponent: Component = {
|
|
74
|
+
name: 'Camera',
|
|
75
|
+
Editor: CameraComponentEditor,
|
|
76
|
+
View: CameraComponentView,
|
|
77
|
+
defaultProperties: {},
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export default CameraComponent;
|
|
@@ -80,7 +80,7 @@ function DirectionalLightComponentEditor({ component, onUpdate }: { component: a
|
|
|
80
80
|
);
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
function DirectionalLightView({ properties, editMode }: { properties: any; editMode?: boolean }) {
|
|
83
|
+
function DirectionalLightView({ properties, editMode, isSelected }: { properties: any; editMode?: boolean; isSelected?: boolean }) {
|
|
84
84
|
const color = properties.color ?? '#ffffff';
|
|
85
85
|
const intensity = properties.intensity ?? 1.0;
|
|
86
86
|
const castShadow = properties.castShadow ?? true;
|
|
@@ -138,7 +138,7 @@ function DirectionalLightView({ properties, editMode }: { properties: any; editM
|
|
|
138
138
|
/>
|
|
139
139
|
{/* Target object - rendered declaratively in scene graph */}
|
|
140
140
|
<object3D ref={targetRef} />
|
|
141
|
-
{editMode && (
|
|
141
|
+
{editMode && isSelected && (
|
|
142
142
|
<>
|
|
143
143
|
{/* Light source indicator */}
|
|
144
144
|
<mesh>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Environment } from '@react-three/drei';
|
|
2
|
+
import { Component } from './ComponentRegistry';
|
|
3
|
+
import { FieldGroup, NumberField } from './Input';
|
|
4
|
+
import { Object3D, Texture } from 'three';
|
|
5
|
+
|
|
6
|
+
function EnvironmentView({
|
|
7
|
+
properties,
|
|
8
|
+
children,
|
|
9
|
+
editMode,
|
|
10
|
+
loadedTextures,
|
|
11
|
+
loadedModels,
|
|
12
|
+
}: {
|
|
13
|
+
properties: any;
|
|
14
|
+
children?: React.ReactNode;
|
|
15
|
+
editMode?: boolean;
|
|
16
|
+
loadedTextures?: Record<string, Texture>;
|
|
17
|
+
loadedModels?: Record<string, Object3D>;
|
|
18
|
+
}) {
|
|
19
|
+
const { intensity = 1, resolution = 256 } = properties;
|
|
20
|
+
const assetRevision = `${Object.keys(loadedTextures ?? {}).sort().join('|')}::${Object.keys(loadedModels ?? {}).sort().join('|')}`;
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<Environment
|
|
24
|
+
key={assetRevision}
|
|
25
|
+
background={true}
|
|
26
|
+
environmentIntensity={intensity}
|
|
27
|
+
resolution={resolution}
|
|
28
|
+
frames={editMode ? undefined : 1}
|
|
29
|
+
>
|
|
30
|
+
{children}
|
|
31
|
+
</Environment>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const EnvironmentComponent: Component = {
|
|
36
|
+
name: 'Environment',
|
|
37
|
+
Editor: ({ component, onUpdate }) => (
|
|
38
|
+
<FieldGroup>
|
|
39
|
+
<NumberField name="intensity" label="Intensity" values={component.properties} onChange={onUpdate} min={0} step={0.1} fallback={1} />
|
|
40
|
+
<NumberField name="resolution" label="Resolution" values={component.properties} onChange={onUpdate} min={64} step={64} fallback={256} />
|
|
41
|
+
</FieldGroup>
|
|
42
|
+
),
|
|
43
|
+
View: EnvironmentView,
|
|
44
|
+
defaultProperties: {},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export default EnvironmentComponent;
|