react-three-game 0.0.17 → 0.0.19

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 (39) hide show
  1. package/.github/copilot-instructions.md +54 -183
  2. package/README.md +69 -214
  3. package/dist/index.d.ts +3 -1
  4. package/dist/index.js +3 -0
  5. package/dist/tools/prefabeditor/EditorTree.d.ts +2 -4
  6. package/dist/tools/prefabeditor/EditorTree.js +20 -194
  7. package/dist/tools/prefabeditor/EditorUI.js +43 -224
  8. package/dist/tools/prefabeditor/InstanceProvider.d.ts +4 -4
  9. package/dist/tools/prefabeditor/InstanceProvider.js +21 -13
  10. package/dist/tools/prefabeditor/PrefabEditor.js +33 -99
  11. package/dist/tools/prefabeditor/PrefabRoot.d.ts +0 -1
  12. package/dist/tools/prefabeditor/PrefabRoot.js +33 -50
  13. package/dist/tools/prefabeditor/components/DirectionalLightComponent.d.ts +3 -0
  14. package/dist/tools/prefabeditor/components/DirectionalLightComponent.js +102 -0
  15. package/dist/tools/prefabeditor/components/ModelComponent.js +12 -4
  16. package/dist/tools/prefabeditor/components/SpotLightComponent.js +10 -5
  17. package/dist/tools/prefabeditor/components/index.js +2 -0
  18. package/dist/tools/prefabeditor/hooks/useModelLoader.d.ts +10 -0
  19. package/dist/tools/prefabeditor/hooks/useModelLoader.js +40 -0
  20. package/dist/tools/prefabeditor/styles.d.ts +1809 -0
  21. package/dist/tools/prefabeditor/styles.js +168 -0
  22. package/dist/tools/prefabeditor/types.d.ts +3 -14
  23. package/dist/tools/prefabeditor/types.js +0 -1
  24. package/dist/tools/prefabeditor/utils.d.ts +19 -0
  25. package/dist/tools/prefabeditor/utils.js +72 -0
  26. package/package.json +3 -3
  27. package/src/index.ts +5 -1
  28. package/src/tools/prefabeditor/EditorTree.tsx +38 -270
  29. package/src/tools/prefabeditor/EditorUI.tsx +105 -322
  30. package/src/tools/prefabeditor/InstanceProvider.tsx +43 -32
  31. package/src/tools/prefabeditor/PrefabEditor.tsx +40 -151
  32. package/src/tools/prefabeditor/PrefabRoot.tsx +41 -73
  33. package/src/tools/prefabeditor/components/DirectionalLightComponent.tsx +317 -0
  34. package/src/tools/prefabeditor/components/ModelComponent.tsx +14 -4
  35. package/src/tools/prefabeditor/components/SpotLightComponent.tsx +27 -7
  36. package/src/tools/prefabeditor/components/index.ts +2 -0
  37. package/src/tools/prefabeditor/styles.ts +195 -0
  38. package/src/tools/prefabeditor/types.ts +4 -12
  39. package/src/tools/prefabeditor/utils.ts +80 -0
@@ -21,12 +21,20 @@ function ModelComponentEditor({ component, onUpdate, basePath = "" }) {
21
21
  function ModelComponentView({ properties, loadedModels, children }) {
22
22
  // Instanced models are handled elsewhere (GameInstance), so only render non-instanced here
23
23
  if (!properties.filename || properties.instanced)
24
- return children || null;
24
+ return _jsx(_Fragment, { children: children });
25
25
  if (loadedModels && loadedModels[properties.filename]) {
26
- return _jsxs(_Fragment, { children: [_jsx("primitive", { object: loadedModels[properties.filename].clone() }), children] });
26
+ const clonedModel = loadedModels[properties.filename].clone();
27
+ // Enable shadows on all meshes in the model
28
+ clonedModel.traverse((obj) => {
29
+ if (obj.isMesh) {
30
+ obj.castShadow = true;
31
+ obj.receiveShadow = true;
32
+ }
33
+ });
34
+ return _jsx("primitive", { object: clonedModel, children: children });
27
35
  }
28
- // Optionally, render a placeholder if model is not loaded
29
- return children || null;
36
+ // Model not loaded yet - render children only
37
+ return _jsx(_Fragment, { children: children });
30
38
  }
31
39
  const ModelComponent = {
32
40
  name: 'Model',
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useRef, useEffect } from "react";
2
3
  function SpotLightComponentEditor({ component, onUpdate }) {
3
4
  var _a, _b, _c, _d, _e, _f;
4
- // Provide default values to prevent NaN
5
5
  const props = {
6
6
  color: (_a = component.properties.color) !== null && _a !== void 0 ? _a : '#ffffff',
7
7
  intensity: (_b = component.properties.intensity) !== null && _b !== void 0 ? _b : 1.0,
@@ -12,17 +12,22 @@ function SpotLightComponentEditor({ component, onUpdate }) {
12
12
  };
13
13
  return _jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5", children: "Color" }), _jsxs("div", { className: "flex gap-0.5", children: [_jsx("input", { type: "color", className: "h-5 w-5 bg-transparent border-none cursor-pointer", value: props.color, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'color': e.target.value })) }), _jsx("input", { type: "text", className: "flex-1 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", value: props.color, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'color': e.target.value })) })] })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5", children: "Intensity" }), _jsx("input", { type: "number", step: "0.1", className: "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", value: props.intensity, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'intensity': parseFloat(e.target.value) })) })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5", children: "Angle" }), _jsx("input", { type: "number", step: "0.1", min: "0", max: Math.PI, className: "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", value: props.angle, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'angle': parseFloat(e.target.value) })) })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5", children: "Penumbra" }), _jsx("input", { type: "number", step: "0.1", min: "0", max: "1", className: "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", value: props.penumbra, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'penumbra': parseFloat(e.target.value) })) })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5", children: "Distance" }), _jsx("input", { type: "number", step: "1", min: "0", className: "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", value: props.distance, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'distance': parseFloat(e.target.value) })) })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5", children: "Cast Shadow" }), _jsx("input", { type: "checkbox", className: "h-4 w-4 bg-black/40 border border-cyan-500/30 cursor-pointer", checked: props.castShadow, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'castShadow': e.target.checked })) })] })] });
14
14
  }
15
- // The view component for SpotLight
16
- function SpotLightView({ properties }) {
15
+ function SpotLightView({ properties, editMode }) {
17
16
  var _a, _b, _c, _d, _e, _f;
18
- // Provide defaults in case properties are missing
19
17
  const color = (_a = properties.color) !== null && _a !== void 0 ? _a : '#ffffff';
20
18
  const intensity = (_b = properties.intensity) !== null && _b !== void 0 ? _b : 1.0;
21
19
  const angle = (_c = properties.angle) !== null && _c !== void 0 ? _c : Math.PI / 6;
22
20
  const penumbra = (_d = properties.penumbra) !== null && _d !== void 0 ? _d : 0.5;
23
21
  const distance = (_e = properties.distance) !== null && _e !== void 0 ? _e : 100;
24
22
  const castShadow = (_f = properties.castShadow) !== null && _f !== void 0 ? _f : true;
25
- return (_jsx(_Fragment, { children: _jsx("spotLight", { color: color, intensity: intensity, angle: angle, penumbra: penumbra, distance: distance, castShadow: castShadow }) }));
23
+ const spotLightRef = useRef(null);
24
+ const targetRef = useRef(null);
25
+ useEffect(() => {
26
+ if (spotLightRef.current && targetRef.current) {
27
+ spotLightRef.current.target = targetRef.current;
28
+ }
29
+ }, []);
30
+ return (_jsxs(_Fragment, { children: [_jsx("spotLight", { ref: spotLightRef, color: color, intensity: intensity, angle: angle, penumbra: penumbra, distance: distance, castShadow: castShadow, "shadow-bias": -0.0001, "shadow-normalBias": 0.02 }), _jsx("object3D", { ref: targetRef, position: [0, -5, 0] }), editMode && (_jsxs(_Fragment, { children: [_jsxs("mesh", { children: [_jsx("sphereGeometry", { args: [0.2, 8, 6] }), _jsx("meshBasicMaterial", { color: color, wireframe: true })] }), _jsxs("mesh", { position: [0, -5, 0], children: [_jsx("sphereGeometry", { args: [0.15, 8, 6] }), _jsx("meshBasicMaterial", { color: color, wireframe: true, opacity: 0.5, transparent: true })] })] }))] }));
26
31
  }
27
32
  const SpotLightComponent = {
28
33
  name: 'SpotLight',
@@ -3,6 +3,7 @@ import TransformComponent from './TransformComponent';
3
3
  import MaterialComponent from './MaterialComponent';
4
4
  import PhysicsComponent from './PhysicsComponent';
5
5
  import SpotLightComponent from './SpotLightComponent';
6
+ import DirectionalLightComponent from './DirectionalLightComponent';
6
7
  import ModelComponent from './ModelComponent';
7
8
  export default [
8
9
  GeometryComponent,
@@ -10,5 +11,6 @@ export default [
10
11
  MaterialComponent,
11
12
  PhysicsComponent,
12
13
  SpotLightComponent,
14
+ DirectionalLightComponent,
13
15
  ModelComponent
14
16
  ];
@@ -0,0 +1,10 @@
1
+ import { Object3D } from 'three';
2
+ /**
3
+ * Hook to load a model (GLB/GLTF/FBX) using drei's optimized loaders
4
+ * Returns the loaded model with proper caching and suspense support
5
+ */
6
+ export declare function useModel(filename: string | undefined): Object3D | null;
7
+ /**
8
+ * Preload a model to avoid suspense boundaries during runtime
9
+ */
10
+ export declare function preloadModel(filename: string): void;
@@ -0,0 +1,40 @@
1
+ import { useGLTF, useFBX } from '@react-three/drei';
2
+ import { useMemo } from 'react';
3
+ /**
4
+ * Hook to load a model (GLB/GLTF/FBX) using drei's optimized loaders
5
+ * Returns the loaded model with proper caching and suspense support
6
+ */
7
+ export function useModel(filename) {
8
+ const isFBX = filename === null || filename === void 0 ? void 0 : filename.toLowerCase().endsWith('.fbx');
9
+ const isGLTF = (filename === null || filename === void 0 ? void 0 : filename.toLowerCase().endsWith('.glb')) || (filename === null || filename === void 0 ? void 0 : filename.toLowerCase().endsWith('.gltf'));
10
+ // Normalize path (ensure leading slash)
11
+ const normalizedPath = useMemo(() => {
12
+ if (!filename)
13
+ return '';
14
+ return filename.startsWith('/') ? filename : `/${filename}`;
15
+ }, [filename]);
16
+ // Load models using drei hooks (these handle caching automatically)
17
+ const gltf = useGLTF(isGLTF && normalizedPath ? normalizedPath : '', true);
18
+ const fbx = useFBX(isFBX && normalizedPath ? normalizedPath : '');
19
+ // Return the appropriate model
20
+ if (!filename)
21
+ return null;
22
+ if (isGLTF)
23
+ return gltf.scene;
24
+ if (isFBX)
25
+ return fbx;
26
+ return null;
27
+ }
28
+ /**
29
+ * Preload a model to avoid suspense boundaries during runtime
30
+ */
31
+ export function preloadModel(filename) {
32
+ const normalizedPath = filename.startsWith('/') ? filename : `/${filename}`;
33
+ const isFBX = filename.toLowerCase().endsWith('.fbx');
34
+ if (isFBX) {
35
+ useFBX.preload(normalizedPath);
36
+ }
37
+ else {
38
+ useGLTF.preload(normalizedPath);
39
+ }
40
+ }