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.
@@ -1,16 +1,26 @@
1
- import { PerspectiveCamera, useHelper } from '@react-three/drei';
2
- import { useRef } from 'react';
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={component.properties}
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={component.properties}
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={component.properties}
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={component.properties}
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 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);
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
- useHelper(editMode && isSelected ? (cameraRef as React.RefObject<ThreePerspectiveCamera>) : null, CameraHelper);
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 ref={cameraRef} makeDefault={!editMode} fov={fov} near={near} zoom={zoom} far={far} />
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={directionalLightFields}
77
- values={component.properties}
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 color = properties.color ?? '#ffffff';
85
- const intensity = properties.intensity ?? 1.0;
86
- const castShadow = properties.castShadow ?? true;
87
- const shadowMapSize = properties.shadowMapSize ?? 1024;
88
- const shadowCameraNear = properties.shadowCameraNear ?? 0.1;
89
- const shadowCameraFar = properties.shadowCameraFar ?? 100;
90
- const shadowCameraTop = properties.shadowCameraTop ?? 30;
91
- const shadowCameraBottom = properties.shadowCameraBottom ?? -30;
92
- const shadowCameraLeft = properties.shadowCameraLeft ?? -30;
93
- const shadowCameraRight = properties.shadowCameraRight ?? 30;
94
- const targetOffset = properties.targetOffset ?? [0, -5, 0];
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
- // Set up light target reference when both refs are ready
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
- const lightWorldPos = new Vector3();
111
- directionalLightRef.current.getWorldPosition(lightWorldPos);
141
+ directionalLightRef.current.target.updateMatrixWorld();
112
142
 
113
- // Target is positioned relative to light's world position
114
- targetRef.current.position.set(
115
- lightWorldPos.x + targetOffset[0],
116
- lightWorldPos.y + targetOffset[1],
117
- lightWorldPos.z + targetOffset[2]
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
- {/* Target object - rendered declaratively in scene graph */}
140
- <object3D ref={targetRef} />
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 <meshStandardMaterial color="red" wireframe />;
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 <meshBasicMaterial key={materialKey} {...sharedProps} />;
401
+ return <meshBasicNodeMaterial key={materialKey} {...sharedProps} />;
393
402
  }
394
403
 
395
404
  return (
396
- <meshStandardMaterial
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 { useHelper } from "@react-three/drei";
5
- import { SpotLightHelper } from "three";
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={component.properties} onChange={onUpdate} />
11
- <NumberField name="intensity" label="Intensity" values={component.properties} onChange={onUpdate} min={0} step={0.1} fallback={1} />
12
- <NumberField name="angle" label="Angle" values={component.properties} onChange={onUpdate} min={0} max={Math.PI} step={0.05} fallback={Math.PI / 6} />
13
- <NumberField name="penumbra" label="Penumbra" values={component.properties} onChange={onUpdate} min={0} max={1} step={0.05} fallback={0.5} />
14
- <NumberField name="distance" label="Distance" values={component.properties} onChange={onUpdate} min={0} step={1} fallback={100} />
15
- <BooleanField name="castShadow" label="Cast Shadow" values={component.properties} onChange={onUpdate} fallback={true} />
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 color = properties.color ?? '#ffffff';
22
- const intensity = properties.intensity ?? 1.0;
23
- const angle = properties.angle ?? Math.PI / 6;
24
- const penumbra = properties.penumbra ?? 0.5;
25
- const distance = properties.distance ?? 100;
26
- const castShadow = properties.castShadow ?? true;
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<any>(null);
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
- useHelper(editMode && isSelected ? spotLightRef : null, SpotLightHelper, color);
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;