react-three-game 0.0.57 → 0.0.58
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -3
- package/dist/tools/prefabeditor/EditorTree.js +24 -48
- package/dist/tools/prefabeditor/EditorTreeMenus.d.ts +33 -0
- package/dist/tools/prefabeditor/EditorTreeMenus.js +136 -0
- package/dist/tools/prefabeditor/components/CameraComponent.js +32 -12
- package/dist/tools/prefabeditor/components/DirectionalLightComponent.js +49 -23
- package/dist/tools/prefabeditor/components/MaterialComponent.d.ts +8 -0
- package/dist/tools/prefabeditor/components/MaterialComponent.js +11 -5
- package/dist/tools/prefabeditor/components/SpotLightComponent.js +34 -13
- package/package.json +1 -1
- package/react-three-game-skill/react-three-game/SKILL.md +4 -1
- package/src/tools/prefabeditor/EditorTree.tsx +56 -125
- package/src/tools/prefabeditor/EditorTreeMenus.tsx +307 -0
- package/src/tools/prefabeditor/components/CameraComponent.tsx +51 -14
- package/src/tools/prefabeditor/components/DirectionalLightComponent.tsx +59 -28
- package/src/tools/prefabeditor/components/MaterialComponent.tsx +18 -9
- package/src/tools/prefabeditor/components/SpotLightComponent.tsx +49 -18
|
@@ -1,16 +1,26 @@
|
|
|
1
|
-
import { PerspectiveCamera
|
|
2
|
-
import {
|
|
1
|
+
import { PerspectiveCamera } from '@react-three/drei';
|
|
2
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
3
3
|
import { CameraHelper, PerspectiveCamera as ThreePerspectiveCamera } from 'three';
|
|
4
|
+
import { useFrame } from '@react-three/fiber';
|
|
4
5
|
import { Component } from './ComponentRegistry';
|
|
5
6
|
import { FieldGroup, NumberField } from './Input';
|
|
6
7
|
|
|
8
|
+
const cameraDefaults = {
|
|
9
|
+
fov: 50,
|
|
10
|
+
near: 0.1,
|
|
11
|
+
zoom: 1,
|
|
12
|
+
far: 1000,
|
|
13
|
+
};
|
|
14
|
+
|
|
7
15
|
function CameraComponentEditor({ component, onUpdate }: { component: any; onUpdate: (newComp: any) => void }) {
|
|
16
|
+
const values = { ...cameraDefaults, ...component.properties };
|
|
17
|
+
|
|
8
18
|
return (
|
|
9
19
|
<FieldGroup>
|
|
10
20
|
<NumberField
|
|
11
21
|
name="fov"
|
|
12
22
|
label="FOV"
|
|
13
|
-
values={
|
|
23
|
+
values={values}
|
|
14
24
|
onChange={onUpdate}
|
|
15
25
|
fallback={50}
|
|
16
26
|
min={1}
|
|
@@ -20,7 +30,7 @@ function CameraComponentEditor({ component, onUpdate }: { component: any; onUpda
|
|
|
20
30
|
<NumberField
|
|
21
31
|
name="near"
|
|
22
32
|
label="Near"
|
|
23
|
-
values={
|
|
33
|
+
values={values}
|
|
24
34
|
onChange={onUpdate}
|
|
25
35
|
fallback={0.1}
|
|
26
36
|
min={0.001}
|
|
@@ -29,7 +39,7 @@ function CameraComponentEditor({ component, onUpdate }: { component: any; onUpda
|
|
|
29
39
|
<NumberField
|
|
30
40
|
name="zoom"
|
|
31
41
|
label="Zoom"
|
|
32
|
-
values={
|
|
42
|
+
values={values}
|
|
33
43
|
onChange={onUpdate}
|
|
34
44
|
fallback={1}
|
|
35
45
|
min={0.01}
|
|
@@ -38,7 +48,7 @@ function CameraComponentEditor({ component, onUpdate }: { component: any; onUpda
|
|
|
38
48
|
<NumberField
|
|
39
49
|
name="far"
|
|
40
50
|
label="Far"
|
|
41
|
-
values={
|
|
51
|
+
values={values}
|
|
42
52
|
onChange={onUpdate}
|
|
43
53
|
fallback={1000}
|
|
44
54
|
min={0.1}
|
|
@@ -49,17 +59,44 @@ function CameraComponentEditor({ component, onUpdate }: { component: any; onUpda
|
|
|
49
59
|
}
|
|
50
60
|
|
|
51
61
|
function CameraComponentView({ properties, editMode, isSelected }: { properties: any; editMode?: boolean; isSelected?: boolean }) {
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
const
|
|
62
|
+
const merged = { ...cameraDefaults, ...properties };
|
|
63
|
+
const fov = merged.fov;
|
|
64
|
+
const near = merged.near;
|
|
65
|
+
const zoom = merged.zoom;
|
|
66
|
+
const far = merged.far;
|
|
67
|
+
const [camera, setCamera] = useState<ThreePerspectiveCamera | null>(null);
|
|
68
|
+
const cameraHelper = useMemo(
|
|
69
|
+
() => camera ? new CameraHelper(camera) : null,
|
|
70
|
+
[camera]
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
return () => {
|
|
75
|
+
cameraHelper?.dispose();
|
|
76
|
+
};
|
|
77
|
+
}, [cameraHelper]);
|
|
57
78
|
|
|
58
|
-
|
|
79
|
+
useFrame(() => {
|
|
80
|
+
if (camera && cameraHelper && editMode && isSelected) {
|
|
81
|
+
camera.updateProjectionMatrix();
|
|
82
|
+
camera.updateMatrixWorld();
|
|
83
|
+
cameraHelper.update();
|
|
84
|
+
}
|
|
85
|
+
});
|
|
59
86
|
|
|
60
87
|
return (
|
|
61
88
|
<>
|
|
62
|
-
<PerspectiveCamera
|
|
89
|
+
<PerspectiveCamera
|
|
90
|
+
ref={(instance) => setCamera(instance)}
|
|
91
|
+
makeDefault={!editMode}
|
|
92
|
+
fov={fov}
|
|
93
|
+
near={near}
|
|
94
|
+
zoom={zoom}
|
|
95
|
+
far={far}
|
|
96
|
+
/>
|
|
97
|
+
{editMode && isSelected && cameraHelper && (
|
|
98
|
+
<primitive object={cameraHelper} />
|
|
99
|
+
)}
|
|
63
100
|
{editMode && !isSelected ? (
|
|
64
101
|
<mesh>
|
|
65
102
|
<boxGeometry args={[0.34, 0.22, 0.18]} />
|
|
@@ -74,7 +111,7 @@ const CameraComponent: Component = {
|
|
|
74
111
|
name: 'Camera',
|
|
75
112
|
Editor: CameraComponentEditor,
|
|
76
113
|
View: CameraComponentView,
|
|
77
|
-
defaultProperties:
|
|
114
|
+
defaultProperties: cameraDefaults,
|
|
78
115
|
};
|
|
79
116
|
|
|
80
117
|
export default CameraComponent;
|
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
import { Component } from "./ComponentRegistry";
|
|
2
|
-
import { useRef, useEffect } from "react";
|
|
2
|
+
import { useRef, useEffect, useMemo, useState } from "react";
|
|
3
3
|
import { useFrame } from "@react-three/fiber";
|
|
4
|
-
import { DirectionalLight, Object3D, Vector3 } from "three";
|
|
4
|
+
import { CameraHelper, DirectionalLight, Object3D, OrthographicCamera, Vector3 } from "three";
|
|
5
5
|
import { FieldRenderer, FieldDefinition, Input } from "./Input";
|
|
6
6
|
|
|
7
7
|
const smallLabel = { display: 'block', fontSize: '8px', color: 'rgba(34, 211, 238, 0.5)', marginBottom: 2 } as const;
|
|
8
8
|
|
|
9
|
+
const directionalLightDefaults = {
|
|
10
|
+
color: '#ffffff',
|
|
11
|
+
intensity: 1,
|
|
12
|
+
castShadow: true,
|
|
13
|
+
shadowMapSize: 1024,
|
|
14
|
+
shadowCameraNear: 0.1,
|
|
15
|
+
shadowCameraFar: 100,
|
|
16
|
+
shadowCameraTop: 30,
|
|
17
|
+
shadowCameraBottom: -30,
|
|
18
|
+
shadowCameraLeft: -30,
|
|
19
|
+
shadowCameraRight: 30,
|
|
20
|
+
targetOffset: [0, -5, 0],
|
|
21
|
+
};
|
|
22
|
+
|
|
9
23
|
const directionalLightFields: FieldDefinition[] = [
|
|
10
24
|
{ name: 'color', type: 'color', label: 'Color' },
|
|
11
25
|
{ name: 'intensity', type: 'number', label: 'Intensity', step: 0.1, min: 0 },
|
|
@@ -71,51 +85,66 @@ const directionalLightFields: FieldDefinition[] = [
|
|
|
71
85
|
];
|
|
72
86
|
|
|
73
87
|
function DirectionalLightComponentEditor({ component, onUpdate }: { component: any; onUpdate: (newComp: any) => void }) {
|
|
88
|
+
const values = { ...directionalLightDefaults, ...component.properties };
|
|
89
|
+
const fields = values.castShadow
|
|
90
|
+
? directionalLightFields
|
|
91
|
+
: directionalLightFields.filter(field => field.name !== '_shadowCamera');
|
|
92
|
+
|
|
74
93
|
return (
|
|
75
94
|
<FieldRenderer
|
|
76
|
-
fields={
|
|
77
|
-
values={
|
|
95
|
+
fields={fields}
|
|
96
|
+
values={values}
|
|
78
97
|
onChange={onUpdate}
|
|
79
98
|
/>
|
|
80
99
|
);
|
|
81
100
|
}
|
|
82
101
|
|
|
83
102
|
function DirectionalLightView({ properties, editMode, isSelected }: { properties: any; editMode?: boolean; isSelected?: boolean }) {
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
const
|
|
87
|
-
const
|
|
88
|
-
const
|
|
89
|
-
const
|
|
90
|
-
const
|
|
91
|
-
const
|
|
92
|
-
const
|
|
93
|
-
const
|
|
94
|
-
const
|
|
103
|
+
const merged = { ...directionalLightDefaults, ...properties };
|
|
104
|
+
const color = merged.color;
|
|
105
|
+
const intensity = merged.intensity;
|
|
106
|
+
const castShadow = merged.castShadow;
|
|
107
|
+
const shadowMapSize = merged.shadowMapSize;
|
|
108
|
+
const shadowCameraNear = merged.shadowCameraNear;
|
|
109
|
+
const shadowCameraFar = merged.shadowCameraFar;
|
|
110
|
+
const shadowCameraTop = merged.shadowCameraTop;
|
|
111
|
+
const shadowCameraBottom = merged.shadowCameraBottom;
|
|
112
|
+
const shadowCameraLeft = merged.shadowCameraLeft;
|
|
113
|
+
const shadowCameraRight = merged.shadowCameraRight;
|
|
114
|
+
const targetOffset = merged.targetOffset;
|
|
95
115
|
|
|
96
116
|
const directionalLightRef = useRef<DirectionalLight>(null);
|
|
97
117
|
const targetRef = useRef<Object3D>(null);
|
|
118
|
+
const [shadowCamera, setShadowCamera] = useState<OrthographicCamera | null>(null);
|
|
119
|
+
const shadowCameraHelper = useMemo(
|
|
120
|
+
() => shadowCamera ? new CameraHelper(shadowCamera) : null,
|
|
121
|
+
[shadowCamera]
|
|
122
|
+
);
|
|
98
123
|
|
|
99
|
-
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
return () => {
|
|
126
|
+
shadowCameraHelper?.dispose();
|
|
127
|
+
};
|
|
128
|
+
}, [shadowCameraHelper]);
|
|
129
|
+
|
|
130
|
+
// Use a local target object so node transforms rotate the light direction naturally.
|
|
100
131
|
useEffect(() => {
|
|
101
132
|
if (directionalLightRef.current && targetRef.current) {
|
|
102
133
|
directionalLightRef.current.target = targetRef.current;
|
|
134
|
+
setShadowCamera(directionalLightRef.current.shadow.camera);
|
|
103
135
|
}
|
|
104
136
|
}, []);
|
|
105
137
|
|
|
106
|
-
// Update target world position based on light position + offset
|
|
107
138
|
useFrame(() => {
|
|
108
139
|
if (!directionalLightRef.current || !targetRef.current) return;
|
|
109
140
|
|
|
110
|
-
|
|
111
|
-
directionalLightRef.current.getWorldPosition(lightWorldPos);
|
|
141
|
+
directionalLightRef.current.target.updateMatrixWorld();
|
|
112
142
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
);
|
|
143
|
+
if (shadowCamera && shadowCameraHelper && castShadow) {
|
|
144
|
+
shadowCamera.updateProjectionMatrix();
|
|
145
|
+
shadowCamera.updateMatrixWorld();
|
|
146
|
+
shadowCameraHelper.update();
|
|
147
|
+
}
|
|
119
148
|
});
|
|
120
149
|
|
|
121
150
|
return (
|
|
@@ -136,8 +165,10 @@ function DirectionalLightView({ properties, editMode, isSelected }: { properties
|
|
|
136
165
|
shadow-bias={-0.001}
|
|
137
166
|
shadow-normalBias={0.02}
|
|
138
167
|
/>
|
|
139
|
-
{
|
|
140
|
-
|
|
168
|
+
<object3D ref={targetRef} position={targetOffset as [number, number, number]} />
|
|
169
|
+
{editMode && isSelected && castShadow && shadowCameraHelper && (
|
|
170
|
+
<primitive object={shadowCameraHelper} />
|
|
171
|
+
)}
|
|
141
172
|
{editMode && isSelected && (
|
|
142
173
|
<>
|
|
143
174
|
{/* Light source indicator */}
|
|
@@ -173,7 +204,7 @@ const DirectionalLightComponent: Component = {
|
|
|
173
204
|
name: 'DirectionalLight',
|
|
174
205
|
Editor: DirectionalLightComponentEditor,
|
|
175
206
|
View: DirectionalLightView,
|
|
176
|
-
defaultProperties:
|
|
207
|
+
defaultProperties: directionalLightDefaults
|
|
177
208
|
};
|
|
178
209
|
|
|
179
210
|
export default DirectionalLightComponent;
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { SingleTextureViewer, TextureListViewer } from '../../assetviewer/page';
|
|
2
|
+
import { extend } from '@react-three/fiber';
|
|
3
|
+
import type { ThreeElement } from '@react-three/fiber';
|
|
2
4
|
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|
3
5
|
import { createPortal } from 'react-dom';
|
|
4
6
|
import { Component } from './ComponentRegistry';
|
|
5
7
|
import { FieldRenderer, FieldDefinition, Input } from './Input';
|
|
6
8
|
import { colors } from '../styles';
|
|
7
9
|
import { useMemo } from 'react';
|
|
10
|
+
import { MeshBasicNodeMaterial, MeshStandardNodeMaterial } from 'three/webgpu';
|
|
8
11
|
import {
|
|
9
12
|
RepeatWrapping,
|
|
10
13
|
ClampToEdgeWrapping,
|
|
@@ -27,6 +30,13 @@ import {
|
|
|
27
30
|
DoubleSide,
|
|
28
31
|
} from 'three';
|
|
29
32
|
|
|
33
|
+
declare module '@react-three/fiber' {
|
|
34
|
+
interface ThreeElements {
|
|
35
|
+
meshBasicNodeMaterial: ThreeElement<typeof MeshBasicNodeMaterial>;
|
|
36
|
+
meshStandardNodeMaterial: ThreeElement<typeof MeshStandardNodeMaterial>;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
30
40
|
export interface MaterialProps extends Omit<MeshStandardMaterialProperties & MeshBasicMaterialProperties, 'args' | 'normalScale'> {
|
|
31
41
|
materialType?: 'standard' | 'basic';
|
|
32
42
|
transmission?: number;
|
|
@@ -44,6 +54,10 @@ export interface MaterialProps extends Omit<MeshStandardMaterialProperties & Mes
|
|
|
44
54
|
|
|
45
55
|
const PICKER_POPUP_WIDTH = 260;
|
|
46
56
|
const PICKER_POPUP_HEIGHT = 360;
|
|
57
|
+
extend({
|
|
58
|
+
MeshBasicNodeMaterial,
|
|
59
|
+
MeshStandardNodeMaterial,
|
|
60
|
+
});
|
|
47
61
|
|
|
48
62
|
function TexturePicker({
|
|
49
63
|
value,
|
|
@@ -321,11 +335,6 @@ function MaterialComponentView({ properties, loadedTextures }: { properties: Mat
|
|
|
321
335
|
normalScale: _normalScale,
|
|
322
336
|
normalMap: _normalMap,
|
|
323
337
|
side: sideProp,
|
|
324
|
-
metalness: _metalness,
|
|
325
|
-
roughness: _roughness,
|
|
326
|
-
transmission: _transmission,
|
|
327
|
-
thickness: _thickness,
|
|
328
|
-
ior: _ior,
|
|
329
338
|
...materialProps
|
|
330
339
|
} = materialSource;
|
|
331
340
|
|
|
@@ -378,10 +387,10 @@ function MaterialComponentView({ properties, loadedTextures }: { properties: Mat
|
|
|
378
387
|
}, [finalNormalMap, normalScaleProp?.[0], normalScaleProp?.[1]]);
|
|
379
388
|
|
|
380
389
|
if (!properties) {
|
|
381
|
-
return <
|
|
390
|
+
return <meshStandardNodeMaterial color="red" wireframe />;
|
|
382
391
|
}
|
|
383
392
|
|
|
384
|
-
const materialKey = finalTexture?.uuid ?? 'no-texture'
|
|
393
|
+
const materialKey = `${finalTexture?.uuid ?? 'no-texture'}:${materialProps.transparent ? 'transparent' : 'opaque'}`;
|
|
385
394
|
const sharedProps = {
|
|
386
395
|
map: finalTexture,
|
|
387
396
|
side: resolvedSide,
|
|
@@ -389,11 +398,11 @@ function MaterialComponentView({ properties, loadedTextures }: { properties: Mat
|
|
|
389
398
|
};
|
|
390
399
|
|
|
391
400
|
if (materialType === 'basic') {
|
|
392
|
-
return <
|
|
401
|
+
return <meshBasicNodeMaterial key={materialKey} {...sharedProps} />;
|
|
393
402
|
}
|
|
394
403
|
|
|
395
404
|
return (
|
|
396
|
-
<
|
|
405
|
+
<meshStandardNodeMaterial
|
|
397
406
|
key={materialKey}
|
|
398
407
|
{...sharedProps}
|
|
399
408
|
normalMap={finalNormalMap}
|
|
@@ -1,41 +1,69 @@
|
|
|
1
1
|
import { Component } from "./ComponentRegistry";
|
|
2
|
-
import { useRef, useEffect } from "react";
|
|
2
|
+
import { useRef, useEffect, useMemo, useState } from "react";
|
|
3
3
|
import { BooleanField, ColorField, FieldGroup, NumberField } from "./Input";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { SpotLight, SpotLightHelper } from "three";
|
|
5
|
+
import { useFrame } from "@react-three/fiber";
|
|
6
|
+
|
|
7
|
+
const spotLightDefaults = {
|
|
8
|
+
color: '#ffffff',
|
|
9
|
+
intensity: 1,
|
|
10
|
+
angle: Math.PI / 6,
|
|
11
|
+
penumbra: 0.5,
|
|
12
|
+
distance: 100,
|
|
13
|
+
castShadow: true,
|
|
14
|
+
};
|
|
6
15
|
|
|
7
16
|
function SpotLightComponentEditor({ component, onUpdate }: { component: any; onUpdate: (newComp: any) => void }) {
|
|
17
|
+
const values = { ...spotLightDefaults, ...component.properties };
|
|
18
|
+
|
|
8
19
|
return (
|
|
9
20
|
<FieldGroup>
|
|
10
|
-
<ColorField name="color" label="Color" values={
|
|
11
|
-
<NumberField name="intensity" label="Intensity" values={
|
|
12
|
-
<NumberField name="angle" label="Angle" values={
|
|
13
|
-
<NumberField name="penumbra" label="Penumbra" values={
|
|
14
|
-
<NumberField name="distance" label="Distance" values={
|
|
15
|
-
<BooleanField name="castShadow" label="Cast Shadow" values={
|
|
21
|
+
<ColorField name="color" label="Color" values={values} onChange={onUpdate} />
|
|
22
|
+
<NumberField name="intensity" label="Intensity" values={values} onChange={onUpdate} min={0} step={0.1} fallback={1} />
|
|
23
|
+
<NumberField name="angle" label="Angle" values={values} onChange={onUpdate} min={0} max={Math.PI} step={0.05} fallback={Math.PI / 6} />
|
|
24
|
+
<NumberField name="penumbra" label="Penumbra" values={values} onChange={onUpdate} min={0} max={1} step={0.05} fallback={0.5} />
|
|
25
|
+
<NumberField name="distance" label="Distance" values={values} onChange={onUpdate} min={0} step={1} fallback={100} />
|
|
26
|
+
<BooleanField name="castShadow" label="Cast Shadow" values={values} onChange={onUpdate} fallback={true} />
|
|
16
27
|
</FieldGroup>
|
|
17
28
|
);
|
|
18
29
|
}
|
|
19
30
|
|
|
20
31
|
function SpotLightView({ properties, editMode, isSelected }: { properties: any; editMode?: boolean; isSelected?: boolean }) {
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
const
|
|
32
|
+
const merged = { ...spotLightDefaults, ...properties };
|
|
33
|
+
const color = merged.color;
|
|
34
|
+
const intensity = merged.intensity;
|
|
35
|
+
const angle = merged.angle;
|
|
36
|
+
const penumbra = merged.penumbra;
|
|
37
|
+
const distance = merged.distance;
|
|
38
|
+
const castShadow = merged.castShadow;
|
|
27
39
|
|
|
28
|
-
const spotLightRef = useRef<
|
|
40
|
+
const spotLightRef = useRef<SpotLight>(null);
|
|
29
41
|
const targetRef = useRef<any>(null);
|
|
42
|
+
const [spotLight, setSpotLight] = useState<SpotLight | null>(null);
|
|
43
|
+
const spotLightHelper = useMemo(
|
|
44
|
+
() => spotLight ? new SpotLightHelper(spotLight, color) : null,
|
|
45
|
+
[spotLight, color]
|
|
46
|
+
);
|
|
30
47
|
|
|
31
|
-
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
return () => {
|
|
50
|
+
spotLightHelper?.dispose();
|
|
51
|
+
};
|
|
52
|
+
}, [spotLightHelper]);
|
|
32
53
|
|
|
33
54
|
useEffect(() => {
|
|
34
55
|
if (spotLightRef.current && targetRef.current) {
|
|
35
56
|
spotLightRef.current.target = targetRef.current;
|
|
57
|
+
setSpotLight(spotLightRef.current);
|
|
36
58
|
}
|
|
37
59
|
}, []);
|
|
38
60
|
|
|
61
|
+
useFrame(() => {
|
|
62
|
+
if (spotLightHelper && editMode && isSelected) {
|
|
63
|
+
spotLightHelper.update();
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
39
67
|
return (
|
|
40
68
|
<>
|
|
41
69
|
<spotLight
|
|
@@ -51,6 +79,9 @@ function SpotLightView({ properties, editMode, isSelected }: { properties: any;
|
|
|
51
79
|
shadow-bias={-0.0001}
|
|
52
80
|
shadow-normalBias={0.02}
|
|
53
81
|
/>
|
|
82
|
+
{editMode && isSelected && spotLightHelper && (
|
|
83
|
+
<primitive object={spotLightHelper} />
|
|
84
|
+
)}
|
|
54
85
|
<object3D ref={targetRef} position={[0, -5, 0]} />
|
|
55
86
|
{editMode && (
|
|
56
87
|
<>
|
|
@@ -72,7 +103,7 @@ const SpotLightComponent: Component = {
|
|
|
72
103
|
name: 'SpotLight',
|
|
73
104
|
Editor: SpotLightComponentEditor,
|
|
74
105
|
View: SpotLightView,
|
|
75
|
-
defaultProperties:
|
|
106
|
+
defaultProperties: spotLightDefaults
|
|
76
107
|
};
|
|
77
108
|
|
|
78
109
|
export default SpotLightComponent;
|