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.
- package/.github/copilot-instructions.md +54 -183
- package/README.md +69 -214
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -0
- package/dist/tools/prefabeditor/EditorTree.d.ts +2 -4
- package/dist/tools/prefabeditor/EditorTree.js +20 -194
- package/dist/tools/prefabeditor/EditorUI.js +43 -224
- package/dist/tools/prefabeditor/InstanceProvider.d.ts +4 -4
- package/dist/tools/prefabeditor/InstanceProvider.js +21 -13
- package/dist/tools/prefabeditor/PrefabEditor.js +33 -99
- package/dist/tools/prefabeditor/PrefabRoot.d.ts +0 -1
- package/dist/tools/prefabeditor/PrefabRoot.js +33 -50
- package/dist/tools/prefabeditor/components/DirectionalLightComponent.d.ts +3 -0
- package/dist/tools/prefabeditor/components/DirectionalLightComponent.js +102 -0
- package/dist/tools/prefabeditor/components/ModelComponent.js +12 -4
- package/dist/tools/prefabeditor/components/SpotLightComponent.js +10 -5
- package/dist/tools/prefabeditor/components/index.js +2 -0
- package/dist/tools/prefabeditor/hooks/useModelLoader.d.ts +10 -0
- package/dist/tools/prefabeditor/hooks/useModelLoader.js +40 -0
- package/dist/tools/prefabeditor/styles.d.ts +1809 -0
- package/dist/tools/prefabeditor/styles.js +168 -0
- package/dist/tools/prefabeditor/types.d.ts +3 -14
- package/dist/tools/prefabeditor/types.js +0 -1
- package/dist/tools/prefabeditor/utils.d.ts +19 -0
- package/dist/tools/prefabeditor/utils.js +72 -0
- package/package.json +3 -3
- package/src/index.ts +5 -1
- package/src/tools/prefabeditor/EditorTree.tsx +38 -270
- package/src/tools/prefabeditor/EditorUI.tsx +105 -322
- package/src/tools/prefabeditor/InstanceProvider.tsx +43 -32
- package/src/tools/prefabeditor/PrefabEditor.tsx +40 -151
- package/src/tools/prefabeditor/PrefabRoot.tsx +41 -73
- package/src/tools/prefabeditor/components/DirectionalLightComponent.tsx +317 -0
- package/src/tools/prefabeditor/components/ModelComponent.tsx +14 -4
- package/src/tools/prefabeditor/components/SpotLightComponent.tsx +27 -7
- package/src/tools/prefabeditor/components/index.ts +2 -0
- package/src/tools/prefabeditor/styles.ts +195 -0
- package/src/tools/prefabeditor/types.ts +4 -12
- 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
|
|
24
|
+
return _jsx(_Fragment, { children: children });
|
|
25
25
|
if (loadedModels && loadedModels[properties.filename]) {
|
|
26
|
-
|
|
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
|
-
//
|
|
29
|
-
return children
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|