react-three-game 0.0.21 → 0.0.23

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,7 +1,7 @@
1
1
  import { Component } from "./ComponentRegistry";
2
2
  import { useRef, useEffect } from "react";
3
- import { useFrame, useThree } from "@react-three/fiber";
4
- import { CameraHelper, DirectionalLight, Object3D, Vector3 } from "three";
3
+ import { useFrame } from "@react-three/fiber";
4
+ import { DirectionalLight, Object3D, Vector3 } from "three";
5
5
 
6
6
  function DirectionalLightComponentEditor({ component, onUpdate }: { component: any; onUpdate: (newComp: any) => void }) {
7
7
  const props = {
@@ -190,71 +190,31 @@ function DirectionalLightView({ properties, editMode }: { properties: any; editM
190
190
  const shadowCameraRight = properties.shadowCameraRight ?? 30;
191
191
  const targetOffset = properties.targetOffset ?? [0, -5, 0];
192
192
 
193
- const { scene } = useThree();
194
193
  const directionalLightRef = useRef<DirectionalLight>(null);
195
- const targetRef = useRef<Object3D>(new Object3D());
196
- const cameraHelperRef = useRef<CameraHelper | null>(null);
194
+ const targetRef = useRef<Object3D>(null);
197
195
 
198
- // Add target to scene once
196
+ // Set up light target reference when both refs are ready
199
197
  useEffect(() => {
200
- const target = targetRef.current;
201
- scene.add(target);
202
- return () => {
203
- scene.remove(target);
204
- };
205
- }, [scene]);
206
-
207
- // Set up light target reference once
208
- useEffect(() => {
209
- if (directionalLightRef.current) {
198
+ if (directionalLightRef.current && targetRef.current) {
210
199
  directionalLightRef.current.target = targetRef.current;
211
200
  }
212
201
  }, []);
213
202
 
214
- // Update target position and mark shadow for update when light moves or offset changes
203
+ // Update target world position based on light position + offset
215
204
  useFrame(() => {
216
- if (!directionalLightRef.current) return;
205
+ if (!directionalLightRef.current || !targetRef.current) return;
217
206
 
218
207
  const lightWorldPos = new Vector3();
219
208
  directionalLightRef.current.getWorldPosition(lightWorldPos);
220
209
 
221
- const newTargetPos = new Vector3(
210
+ // Target is positioned relative to light's world position
211
+ targetRef.current.position.set(
222
212
  lightWorldPos.x + targetOffset[0],
223
213
  lightWorldPos.y + targetOffset[1],
224
214
  lightWorldPos.z + targetOffset[2]
225
215
  );
226
-
227
- // Only update if position actually changed
228
- if (!targetRef.current.position.equals(newTargetPos)) {
229
- targetRef.current.position.copy(newTargetPos);
230
- if (directionalLightRef.current.shadow) {
231
- directionalLightRef.current.shadow.needsUpdate = true;
232
- }
233
- }
234
-
235
- // Update camera helper in edit mode
236
- if (editMode && cameraHelperRef.current) {
237
- cameraHelperRef.current.update();
238
- }
239
216
  });
240
217
 
241
- // Create/destroy camera helper for edit mode
242
- useEffect(() => {
243
- if (editMode && directionalLightRef.current?.shadow.camera) {
244
- const helper = new CameraHelper(directionalLightRef.current.shadow.camera);
245
- cameraHelperRef.current = helper;
246
- scene.add(helper);
247
-
248
- return () => {
249
- if (cameraHelperRef.current) {
250
- scene.remove(cameraHelperRef.current);
251
- cameraHelperRef.current.dispose();
252
- cameraHelperRef.current = null;
253
- }
254
- };
255
- }
256
- }, [editMode, scene]);
257
-
258
218
  return (
259
219
  <>
260
220
  <directionalLight
@@ -262,20 +222,19 @@ function DirectionalLightView({ properties, editMode }: { properties: any; editM
262
222
  color={color}
263
223
  intensity={intensity}
264
224
  castShadow={castShadow}
265
- shadow-mapSize={[shadowMapSize, shadowMapSize]}
225
+ shadow-mapSize-width={shadowMapSize}
226
+ shadow-mapSize-height={shadowMapSize}
227
+ shadow-camera-near={shadowCameraNear}
228
+ shadow-camera-far={shadowCameraFar}
229
+ shadow-camera-top={shadowCameraTop}
230
+ shadow-camera-bottom={shadowCameraBottom}
231
+ shadow-camera-left={shadowCameraLeft}
232
+ shadow-camera-right={shadowCameraRight}
266
233
  shadow-bias={-0.001}
267
234
  shadow-normalBias={0.02}
268
- >
269
- <orthographicCamera
270
- attach="shadow-camera"
271
- near={shadowCameraNear}
272
- far={shadowCameraFar}
273
- top={shadowCameraTop}
274
- bottom={shadowCameraBottom}
275
- left={shadowCameraLeft}
276
- right={shadowCameraRight}
277
- />
278
- </directionalLight>
235
+ />
236
+ {/* Target object - rendered declaratively in scene graph */}
237
+ <object3D ref={targetRef} />
279
238
  {editMode && (
280
239
  <>
281
240
  {/* Light source indicator */}
@@ -130,14 +130,16 @@ function MaterialComponentView({ properties, loadedTextures, isSelected }: { pro
130
130
  const { color, wireframe = false } = properties;
131
131
  const displayColor = isSelected ? "cyan" : color;
132
132
 
133
- return <meshStandardMaterial
134
- key={finalTexture?.uuid ?? 'no-texture'}
135
- color={displayColor}
136
- wireframe={wireframe}
137
- map={finalTexture}
138
- transparent={!!finalTexture}
139
- side={DoubleSide}
140
- />;
133
+ return (
134
+ <meshStandardMaterial
135
+ key={finalTexture?.uuid ?? 'no-texture'}
136
+ color={displayColor}
137
+ wireframe={wireframe}
138
+ map={finalTexture}
139
+ transparent={!!finalTexture}
140
+ side={DoubleSide}
141
+ />
142
+ );
141
143
  }
142
144
 
143
145
  const MaterialComponent: Component = {
@@ -1,5 +1,5 @@
1
1
  import { ModelListViewer } from '../../assetviewer/page';
2
- import { useEffect, useState } from 'react';
2
+ import { useEffect, useState, useMemo } from 'react';
3
3
  import { Component } from './ComponentRegistry';
4
4
 
5
5
  function ModelComponentEditor({ component, onUpdate, basePath = "" }: { component: any; onUpdate: (newComp: any) => void; basePath?: string }) {
@@ -49,20 +49,24 @@ function ModelComponentView({ properties, loadedModels, children }: { properties
49
49
  // Instanced models are handled elsewhere (GameInstance), so only render non-instanced here
50
50
  if (!properties.filename || properties.instanced) return <>{children}</>;
51
51
 
52
- if (loadedModels && loadedModels[properties.filename]) {
53
- const clonedModel = loadedModels[properties.filename].clone();
54
- // Enable shadows on all meshes in the model
55
- clonedModel.traverse((obj: any) => {
52
+ const sourceModel = loadedModels?.[properties.filename];
53
+
54
+ // Clone model once and set up shadows - memoized to avoid cloning on every render
55
+ const clonedModel = useMemo(() => {
56
+ if (!sourceModel) return null;
57
+ const clone = sourceModel.clone();
58
+ clone.traverse((obj: any) => {
56
59
  if (obj.isMesh) {
57
60
  obj.castShadow = true;
58
61
  obj.receiveShadow = true;
59
62
  }
60
63
  });
61
- return <primitive object={clonedModel}>{children}</primitive>;
62
- }
64
+ return clone;
65
+ }, [sourceModel]);
66
+
67
+ if (!clonedModel) return <>{children}</>;
63
68
 
64
- // Model not loaded yet - render children only
65
- return <>{children}</>;
69
+ return <primitive object={clonedModel}>{children}</primitive>;
66
70
  }
67
71
 
68
72
  const ModelComponent: Component = {
@@ -1,4 +1,4 @@
1
- import { RigidBody, RigidBodyProps } from "@react-three/rapier";
1
+ import { RigidBody } from "@react-three/rapier";
2
2
  import { Component } from "./ComponentRegistry";
3
3
 
4
4
  const selectClass = "w-full bg-black/40 border border-cyan-500/30 px-1 py-0.5 text-[10px] text-cyan-300 font-mono focus:outline-none focus:border-cyan-400/50";
@@ -25,18 +25,19 @@ function PhysicsComponentEditor({ component, onUpdate }: { component: any; onUpd
25
25
  );
26
26
  }
27
27
 
28
- interface PhysicsViewProps extends Omit<RigidBodyProps, 'type' | 'colliders'> {
29
- properties: { type: RigidBodyProps['type']; collider?: string };
28
+ interface PhysicsViewProps {
29
+ properties: { type: 'dynamic' | 'fixed'; collider?: string };
30
30
  editMode?: boolean;
31
+ children?: React.ReactNode;
31
32
  }
32
33
 
33
- function PhysicsComponentView({ properties, editMode, children, ...rigidBodyProps }: PhysicsViewProps) {
34
+ function PhysicsComponentView({ properties, editMode, children }: PhysicsViewProps) {
34
35
  if (editMode) return <>{children}</>;
35
36
 
36
37
  const colliders = properties.collider || (properties.type === 'fixed' ? 'trimesh' : 'hull');
37
38
 
38
39
  return (
39
- <RigidBody type={properties.type} colliders={colliders as any} {...rigidBodyProps}>
40
+ <RigidBody type={properties.type} colliders={colliders as any}>
40
41
  {children}
41
42
  </RigidBody>
42
43
  );
@@ -113,6 +113,8 @@ function SpotLightView({ properties, editMode }: { properties: any; editMode?: b
113
113
  penumbra={penumbra}
114
114
  distance={distance}
115
115
  castShadow={castShadow}
116
+ shadow-mapSize-width={1024}
117
+ shadow-mapSize-height={1024}
116
118
  shadow-bias={-0.0001}
117
119
  shadow-normalBias={0.02}
118
120
  />