react-three-game 0.0.18 → 0.0.20
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/assetviewer/page.js +24 -13
- 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/PrefabEditor.js +33 -99
- package/dist/tools/prefabeditor/PrefabRoot.d.ts +0 -1
- package/dist/tools/prefabeditor/PrefabRoot.js +7 -23
- package/dist/tools/prefabeditor/components/DirectionalLightComponent.js +31 -43
- 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 +8 -3
- package/src/index.ts +5 -1
- package/src/tools/assetviewer/page.tsx +22 -12
- package/src/tools/prefabeditor/EditorTree.tsx +38 -270
- package/src/tools/prefabeditor/EditorUI.tsx +105 -322
- package/src/tools/prefabeditor/PrefabEditor.tsx +40 -151
- package/src/tools/prefabeditor/PrefabRoot.tsx +11 -32
- package/src/tools/prefabeditor/components/DirectionalLightComponent.tsx +38 -53
- package/src/tools/prefabeditor/styles.ts +195 -0
- package/src/tools/prefabeditor/types.ts +4 -12
- package/src/tools/prefabeditor/utils.ts +80 -0
|
@@ -14,28 +14,24 @@ import { useState, useRef, useEffect } from "react";
|
|
|
14
14
|
import PrefabRoot from "./PrefabRoot";
|
|
15
15
|
import { Physics } from "@react-three/rapier";
|
|
16
16
|
import EditorUI from "./EditorUI";
|
|
17
|
+
import { base, toolbar } from "./styles";
|
|
17
18
|
const PrefabEditor = ({ basePath, initialPrefab, onPrefabChange, children }) => {
|
|
18
19
|
const [editMode, setEditMode] = useState(true);
|
|
19
20
|
const [loadedPrefab, setLoadedPrefab] = useState(initialPrefab !== null && initialPrefab !== void 0 ? initialPrefab : {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
"position": [0, 0, 0],
|
|
29
|
-
"rotation": [0, 0, 0],
|
|
30
|
-
"scale": [1, 1, 1]
|
|
31
|
-
}
|
|
21
|
+
id: "prefab-default",
|
|
22
|
+
name: "New Prefab",
|
|
23
|
+
root: {
|
|
24
|
+
id: "root",
|
|
25
|
+
components: {
|
|
26
|
+
transform: {
|
|
27
|
+
type: "Transform",
|
|
28
|
+
properties: { position: [0, 0, 0], rotation: [0, 0, 0], scale: [1, 1, 1] }
|
|
32
29
|
}
|
|
33
30
|
}
|
|
34
31
|
}
|
|
35
32
|
});
|
|
36
33
|
const [selectedId, setSelectedId] = useState(null);
|
|
37
34
|
const [transformMode, setTransformMode] = useState("translate");
|
|
38
|
-
const prefabRef = useRef(null);
|
|
39
35
|
// Sync internal state with external initialPrefab prop
|
|
40
36
|
useEffect(() => {
|
|
41
37
|
if (initialPrefab) {
|
|
@@ -48,136 +44,74 @@ const PrefabEditor = ({ basePath, initialPrefab, onPrefabChange, children }) =>
|
|
|
48
44
|
const resolved = typeof newPrefab === 'function' ? newPrefab(loadedPrefab) : newPrefab;
|
|
49
45
|
onPrefabChange === null || onPrefabChange === void 0 ? void 0 : onPrefabChange(resolved);
|
|
50
46
|
};
|
|
51
|
-
return _jsxs(_Fragment, { children: [_jsx(GameCanvas, { children: _jsxs(Physics, { paused: editMode, children: [_jsx("ambientLight", { intensity: 1.5 }), _jsx("gridHelper", { args: [10, 10], position: [0, -1, 0] }), _jsx(PrefabRoot, { data: loadedPrefab,
|
|
52
|
-
// props for edit mode
|
|
53
|
-
editMode: editMode, onPrefabChange: updatePrefab, selectedId: selectedId, onSelect: setSelectedId, transformMode: transformMode, setTransformMode: setTransformMode, basePath: basePath }), children] }) }), _jsx(SaveDataPanel, { currentData: loadedPrefab, onDataChange: updatePrefab, editMode: editMode, onEditModeChange: setEditMode }), editMode && _jsx(EditorUI, { prefabData: loadedPrefab, setPrefabData: updatePrefab, selectedId: selectedId, setSelectedId: setSelectedId, transformMode: transformMode, setTransformMode: setTransformMode, basePath: basePath })] });
|
|
47
|
+
return _jsxs(_Fragment, { children: [_jsx(GameCanvas, { children: _jsxs(Physics, { paused: editMode, children: [_jsx("ambientLight", { intensity: 1.5 }), _jsx("gridHelper", { args: [10, 10], position: [0, -1, 0] }), _jsx(PrefabRoot, { data: loadedPrefab, editMode: editMode, onPrefabChange: updatePrefab, selectedId: selectedId, onSelect: setSelectedId, transformMode: transformMode, basePath: basePath }), children] }) }), _jsx(SaveDataPanel, { currentData: loadedPrefab, onDataChange: updatePrefab, editMode: editMode, onEditModeChange: setEditMode }), editMode && _jsx(EditorUI, { prefabData: loadedPrefab, setPrefabData: updatePrefab, selectedId: selectedId, setSelectedId: setSelectedId, transformMode: transformMode, setTransformMode: setTransformMode, basePath: basePath })] });
|
|
54
48
|
};
|
|
55
49
|
const SaveDataPanel = ({ currentData, onDataChange, editMode, onEditModeChange }) => {
|
|
56
50
|
const [history, setHistory] = useState([currentData]);
|
|
57
51
|
const [historyIndex, setHistoryIndex] = useState(0);
|
|
58
|
-
const
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
const handleUndo = () => {
|
|
52
|
+
const throttleRef = useRef(null);
|
|
53
|
+
const lastDataRef = useRef(JSON.stringify(currentData));
|
|
54
|
+
const undo = () => {
|
|
62
55
|
if (historyIndex > 0) {
|
|
63
56
|
const newIndex = historyIndex - 1;
|
|
64
57
|
setHistoryIndex(newIndex);
|
|
65
|
-
|
|
58
|
+
lastDataRef.current = JSON.stringify(history[newIndex]);
|
|
66
59
|
onDataChange(history[newIndex]);
|
|
67
60
|
}
|
|
68
61
|
};
|
|
69
|
-
const
|
|
62
|
+
const redo = () => {
|
|
70
63
|
if (historyIndex < history.length - 1) {
|
|
71
64
|
const newIndex = historyIndex + 1;
|
|
72
65
|
setHistoryIndex(newIndex);
|
|
73
|
-
|
|
66
|
+
lastDataRef.current = JSON.stringify(history[newIndex]);
|
|
74
67
|
onDataChange(history[newIndex]);
|
|
75
68
|
}
|
|
76
69
|
};
|
|
77
|
-
// Keyboard shortcuts for undo/redo
|
|
78
70
|
useEffect(() => {
|
|
79
71
|
const handleKeyDown = (e) => {
|
|
80
|
-
// Undo: Ctrl+Z (Cmd+Z on Mac)
|
|
81
72
|
if ((e.ctrlKey || e.metaKey) && e.key === 'z' && !e.shiftKey) {
|
|
82
73
|
e.preventDefault();
|
|
83
|
-
|
|
74
|
+
undo();
|
|
84
75
|
}
|
|
85
|
-
// Redo: Ctrl+Shift+Z or Ctrl+Y (Cmd+Shift+Z or Cmd+Y on Mac)
|
|
86
76
|
else if ((e.ctrlKey || e.metaKey) && (e.shiftKey && e.key === 'z' || e.key === 'y')) {
|
|
87
77
|
e.preventDefault();
|
|
88
|
-
|
|
78
|
+
redo();
|
|
89
79
|
}
|
|
90
80
|
};
|
|
91
81
|
window.addEventListener('keydown', handleKeyDown);
|
|
92
82
|
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
93
83
|
}, [historyIndex, history]);
|
|
94
|
-
// Throttled history update when currentData changes
|
|
95
84
|
useEffect(() => {
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
if (currentDataStr === lastSavedDataRef.current) {
|
|
85
|
+
const currentStr = JSON.stringify(currentData);
|
|
86
|
+
if (currentStr === lastDataRef.current)
|
|
99
87
|
return;
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
// Set new throttled update
|
|
106
|
-
throttleTimeoutRef.current = setTimeout(() => {
|
|
107
|
-
lastSavedDataRef.current = currentDataStr;
|
|
88
|
+
if (throttleRef.current)
|
|
89
|
+
clearTimeout(throttleRef.current);
|
|
90
|
+
throttleRef.current = setTimeout(() => {
|
|
91
|
+
lastDataRef.current = currentStr;
|
|
108
92
|
setHistory(prev => {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
// Add new state
|
|
112
|
-
newHistory.push(currentData);
|
|
113
|
-
// Limit history size to 50 states
|
|
114
|
-
if (newHistory.length > 50) {
|
|
115
|
-
newHistory.shift();
|
|
116
|
-
return newHistory;
|
|
117
|
-
}
|
|
118
|
-
return newHistory;
|
|
93
|
+
const newHistory = [...prev.slice(0, historyIndex + 1), currentData];
|
|
94
|
+
return newHistory.length > 50 ? newHistory.slice(1) : newHistory;
|
|
119
95
|
});
|
|
120
|
-
setHistoryIndex(prev =>
|
|
121
|
-
|
|
122
|
-
newHistory.push(currentData);
|
|
123
|
-
return Math.min(newHistory.length - 1, 49);
|
|
124
|
-
});
|
|
125
|
-
}, 500); // 500ms throttle
|
|
96
|
+
setHistoryIndex(prev => Math.min(prev + 1, 49));
|
|
97
|
+
}, 500);
|
|
126
98
|
return () => {
|
|
127
|
-
if (
|
|
128
|
-
clearTimeout(
|
|
129
|
-
}
|
|
99
|
+
if (throttleRef.current)
|
|
100
|
+
clearTimeout(throttleRef.current);
|
|
130
101
|
};
|
|
131
|
-
}, [currentData
|
|
102
|
+
}, [currentData]);
|
|
132
103
|
const handleLoad = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
133
104
|
const prefab = yield loadJson();
|
|
134
105
|
if (prefab) {
|
|
135
106
|
onDataChange(prefab);
|
|
136
|
-
// Reset history when loading new file
|
|
137
107
|
setHistory([prefab]);
|
|
138
108
|
setHistoryIndex(0);
|
|
139
|
-
|
|
109
|
+
lastDataRef.current = JSON.stringify(prefab);
|
|
140
110
|
}
|
|
141
111
|
});
|
|
142
112
|
const canUndo = historyIndex > 0;
|
|
143
113
|
const canRedo = historyIndex < history.length - 1;
|
|
144
|
-
return _jsxs("div", { style: {
|
|
145
|
-
position: "absolute",
|
|
146
|
-
top: 8,
|
|
147
|
-
left: "50%",
|
|
148
|
-
transform: "translateX(-50%)",
|
|
149
|
-
display: "flex",
|
|
150
|
-
alignItems: "center",
|
|
151
|
-
gap: 6,
|
|
152
|
-
padding: "2px 4px",
|
|
153
|
-
background: "rgba(0,0,0,0.55)",
|
|
154
|
-
border: "1px solid rgba(255,255,255,0.12)",
|
|
155
|
-
borderRadius: 4,
|
|
156
|
-
color: "rgba(255,255,255,0.9)",
|
|
157
|
-
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace",
|
|
158
|
-
fontSize: 11,
|
|
159
|
-
lineHeight: 1,
|
|
160
|
-
WebkitUserSelect: "none",
|
|
161
|
-
userSelect: "none",
|
|
162
|
-
}, children: [_jsx(PanelButton, { onClick: () => onEditModeChange(!editMode), children: editMode ? "▶" : "⏸" }), _jsx("span", { style: { opacity: 0.35 }, children: "|" }), _jsx(PanelButton, { onClick: handleUndo, disabled: !canUndo, title: "Undo (Ctrl+Z)", children: "\u21B6" }), _jsx(PanelButton, { onClick: handleRedo, disabled: !canRedo, title: "Redo (Ctrl+Shift+Z)", children: "\u21B7" }), _jsx("span", { style: { opacity: 0.35 }, children: "|" }), _jsx(PanelButton, { onClick: handleLoad, title: "Load JSON", children: "\uD83D\uDCE5" }), _jsx(PanelButton, { onClick: () => saveJson(currentData, "prefab"), title: "Save JSON", children: "\uD83D\uDCBE" })] });
|
|
163
|
-
};
|
|
164
|
-
const PanelButton = ({ onClick, disabled, title, children }) => {
|
|
165
|
-
return _jsx("button", { style: {
|
|
166
|
-
padding: "2px 6px",
|
|
167
|
-
font: "inherit",
|
|
168
|
-
background: "transparent",
|
|
169
|
-
color: disabled ? "rgba(255,255,255,0.3)" : "inherit",
|
|
170
|
-
border: "1px solid rgba(255,255,255,0.18)",
|
|
171
|
-
borderRadius: 3,
|
|
172
|
-
cursor: disabled ? "not-allowed" : "pointer",
|
|
173
|
-
opacity: disabled ? 0.5 : 1,
|
|
174
|
-
}, onClick: onClick, disabled: disabled, title: title, onPointerEnter: (e) => {
|
|
175
|
-
if (!disabled) {
|
|
176
|
-
e.currentTarget.style.background = "rgba(255,255,255,0.08)";
|
|
177
|
-
}
|
|
178
|
-
}, onPointerLeave: (e) => {
|
|
179
|
-
e.currentTarget.style.background = "transparent";
|
|
180
|
-
}, children: children });
|
|
114
|
+
return _jsxs("div", { style: toolbar.panel, children: [_jsx("button", { style: base.btn, onClick: () => onEditModeChange(!editMode), children: editMode ? "▶" : "⏸" }), _jsx("div", { style: toolbar.divider }), _jsx("button", { style: Object.assign(Object.assign({}, base.btn), (canUndo ? {} : toolbar.disabled)), onClick: undo, disabled: !canUndo, children: "\u21B6" }), _jsx("button", { style: Object.assign(Object.assign({}, base.btn), (canRedo ? {} : toolbar.disabled)), onClick: redo, disabled: !canRedo, children: "\u21B7" }), _jsx("div", { style: toolbar.divider }), _jsx("button", { style: base.btn, onClick: handleLoad, children: "\uD83D\uDCE5" }), _jsx("button", { style: base.btn, onClick: () => saveJson(currentData, "prefab"), children: "\uD83D\uDCBE" })] });
|
|
181
115
|
};
|
|
182
116
|
const saveJson = (data, filename) => {
|
|
183
117
|
const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(data, null, 2));
|
|
@@ -7,7 +7,6 @@ export declare const PrefabRoot: import("react").ForwardRefExoticComponent<{
|
|
|
7
7
|
selectedId?: string | null;
|
|
8
8
|
onSelect?: (id: string | null) => void;
|
|
9
9
|
transformMode?: "translate" | "rotate" | "scale";
|
|
10
|
-
setTransformMode?: (mode: "translate" | "rotate" | "scale") => void;
|
|
11
10
|
basePath?: string;
|
|
12
11
|
} & import("react").RefAttributes<Group<import("three").Object3DEventMap>>>;
|
|
13
12
|
export default PrefabRoot;
|
|
@@ -12,42 +12,26 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
12
12
|
import { MapControls, TransformControls } from "@react-three/drei";
|
|
13
13
|
import { useState, useRef, useEffect, forwardRef, useCallback } from "react";
|
|
14
14
|
import { Vector3, Euler, Quaternion, SRGBColorSpace, TextureLoader, Matrix4 } from "three";
|
|
15
|
-
import { getComponent } from "./components/ComponentRegistry";
|
|
15
|
+
import { getComponent, registerComponent } from "./components/ComponentRegistry";
|
|
16
16
|
import { loadModel } from "../dragdrop/modelLoader";
|
|
17
17
|
import { GameInstance, GameInstanceProvider } from "./InstanceProvider";
|
|
18
|
-
|
|
19
|
-
import { registerComponent } from './components/ComponentRegistry';
|
|
18
|
+
import { updateNode } from "./utils";
|
|
20
19
|
import components from './components/';
|
|
20
|
+
// Register all components
|
|
21
21
|
components.forEach(registerComponent);
|
|
22
|
-
|
|
23
|
-
if (root.id === id) {
|
|
24
|
-
return update(root);
|
|
25
|
-
}
|
|
26
|
-
if (root.children) {
|
|
27
|
-
return Object.assign(Object.assign({}, root), { children: root.children.map(child => updatePrefabNode(child, id, update)) });
|
|
28
|
-
}
|
|
29
|
-
return root;
|
|
30
|
-
}
|
|
31
|
-
export const PrefabRoot = forwardRef(({ editMode, data, onPrefabChange, selectedId, onSelect, transformMode, setTransformMode, basePath = "" }, ref) => {
|
|
22
|
+
export const PrefabRoot = forwardRef(({ editMode, data, onPrefabChange, selectedId, onSelect, transformMode, basePath = "" }, ref) => {
|
|
32
23
|
const [loadedModels, setLoadedModels] = useState({});
|
|
33
24
|
const [loadedTextures, setLoadedTextures] = useState({});
|
|
34
|
-
// const [prefabRoot, setPrefabRoot] = useState<Prefab>(data); // Removed local state
|
|
35
25
|
const loadingRefs = useRef(new Set());
|
|
36
26
|
const objectRefs = useRef({});
|
|
37
27
|
const [selectedObject, setSelectedObject] = useState(null);
|
|
38
28
|
const registerRef = useCallback((id, obj) => {
|
|
39
29
|
objectRefs.current[id] = obj;
|
|
40
|
-
if (id === selectedId)
|
|
30
|
+
if (id === selectedId)
|
|
41
31
|
setSelectedObject(obj);
|
|
42
|
-
}
|
|
43
32
|
}, [selectedId]);
|
|
44
33
|
useEffect(() => {
|
|
45
|
-
|
|
46
|
-
setSelectedObject(objectRefs.current[selectedId] || null);
|
|
47
|
-
}
|
|
48
|
-
else {
|
|
49
|
-
setSelectedObject(null);
|
|
50
|
-
}
|
|
34
|
+
setSelectedObject(selectedId ? objectRefs.current[selectedId] || null : null);
|
|
51
35
|
}, [selectedId]);
|
|
52
36
|
const onTransformChange = () => {
|
|
53
37
|
if (!selectedId || !onPrefabChange)
|
|
@@ -68,7 +52,7 @@ export const PrefabRoot = forwardRef(({ editMode, data, onPrefabChange, selected
|
|
|
68
52
|
localMatrix.decompose(lp, lq, ls);
|
|
69
53
|
const le = new Euler().setFromQuaternion(lq);
|
|
70
54
|
// 4. Write back LOCAL transform into the prefab node
|
|
71
|
-
const newRoot =
|
|
55
|
+
const newRoot = updateNode(data.root, selectedId, (node) => (Object.assign(Object.assign({}, node), { components: Object.assign(Object.assign({}, node.components), { transform: {
|
|
72
56
|
type: "Transform",
|
|
73
57
|
properties: {
|
|
74
58
|
position: [lp.x, lp.y, lp.z],
|
|
@@ -35,30 +35,44 @@ function DirectionalLightView({ properties, editMode }) {
|
|
|
35
35
|
const { scene } = useThree();
|
|
36
36
|
const directionalLightRef = useRef(null);
|
|
37
37
|
const targetRef = useRef(new Object3D());
|
|
38
|
-
const lastUpdate = useRef(0);
|
|
39
38
|
const cameraHelperRef = useRef(null);
|
|
40
|
-
|
|
41
|
-
// Add target to scene
|
|
39
|
+
// Add target to scene once
|
|
42
40
|
useEffect(() => {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
41
|
+
const target = targetRef.current;
|
|
42
|
+
scene.add(target);
|
|
43
|
+
return () => {
|
|
44
|
+
scene.remove(target);
|
|
45
|
+
};
|
|
49
46
|
}, [scene]);
|
|
50
|
-
//
|
|
47
|
+
// Set up light target reference once
|
|
51
48
|
useEffect(() => {
|
|
52
|
-
if (directionalLightRef.current
|
|
53
|
-
const lightWorldPos = new Vector3();
|
|
54
|
-
directionalLightRef.current.getWorldPosition(lightWorldPos);
|
|
55
|
-
targetRef.current.position.set(lightWorldPos.x + targetOffset[0], lightWorldPos.y + targetOffset[1], lightWorldPos.z + targetOffset[2]);
|
|
49
|
+
if (directionalLightRef.current) {
|
|
56
50
|
directionalLightRef.current.target = targetRef.current;
|
|
57
51
|
}
|
|
52
|
+
}, []);
|
|
53
|
+
// Update target position and mark shadow for update when light moves or offset changes
|
|
54
|
+
useFrame(() => {
|
|
55
|
+
if (!directionalLightRef.current)
|
|
56
|
+
return;
|
|
57
|
+
const lightWorldPos = new Vector3();
|
|
58
|
+
directionalLightRef.current.getWorldPosition(lightWorldPos);
|
|
59
|
+
const newTargetPos = new Vector3(lightWorldPos.x + targetOffset[0], lightWorldPos.y + targetOffset[1], lightWorldPos.z + targetOffset[2]);
|
|
60
|
+
// Only update if position actually changed
|
|
61
|
+
if (!targetRef.current.position.equals(newTargetPos)) {
|
|
62
|
+
targetRef.current.position.copy(newTargetPos);
|
|
63
|
+
if (directionalLightRef.current.shadow) {
|
|
64
|
+
directionalLightRef.current.shadow.needsUpdate = true;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// Update camera helper in edit mode
|
|
68
|
+
if (editMode && cameraHelperRef.current) {
|
|
69
|
+
cameraHelperRef.current.update();
|
|
70
|
+
}
|
|
58
71
|
});
|
|
72
|
+
// Create/destroy camera helper for edit mode
|
|
59
73
|
useEffect(() => {
|
|
60
|
-
|
|
61
|
-
if (editMode && directionalLightRef.current
|
|
74
|
+
var _a;
|
|
75
|
+
if (editMode && ((_a = directionalLightRef.current) === null || _a === void 0 ? void 0 : _a.shadow.camera)) {
|
|
62
76
|
const helper = new CameraHelper(directionalLightRef.current.shadow.camera);
|
|
63
77
|
cameraHelperRef.current = helper;
|
|
64
78
|
scene.add(helper);
|
|
@@ -66,37 +80,11 @@ function DirectionalLightView({ properties, editMode }) {
|
|
|
66
80
|
if (cameraHelperRef.current) {
|
|
67
81
|
scene.remove(cameraHelperRef.current);
|
|
68
82
|
cameraHelperRef.current.dispose();
|
|
83
|
+
cameraHelperRef.current = null;
|
|
69
84
|
}
|
|
70
85
|
};
|
|
71
86
|
}
|
|
72
87
|
}, [editMode, scene]);
|
|
73
|
-
useFrame(({ clock }) => {
|
|
74
|
-
if (!directionalLightRef.current || !directionalLightRef.current.shadow)
|
|
75
|
-
return;
|
|
76
|
-
// Disable auto-update for shadows
|
|
77
|
-
if (directionalLightRef.current.shadow.autoUpdate) {
|
|
78
|
-
directionalLightRef.current.shadow.autoUpdate = false;
|
|
79
|
-
directionalLightRef.current.shadow.needsUpdate = true;
|
|
80
|
-
}
|
|
81
|
-
// Check if position has changed
|
|
82
|
-
const currentPosition = new Vector3();
|
|
83
|
-
directionalLightRef.current.getWorldPosition(currentPosition);
|
|
84
|
-
const positionChanged = !currentPosition.equals(lastPositionRef.current);
|
|
85
|
-
if (positionChanged) {
|
|
86
|
-
lastPositionRef.current.copy(currentPosition);
|
|
87
|
-
directionalLightRef.current.shadow.needsUpdate = true;
|
|
88
|
-
lastUpdate.current = clock.elapsedTime; // Reset timer on position change
|
|
89
|
-
}
|
|
90
|
-
// Update shadow map infrequently (every 5 seconds) if position hasn't changed
|
|
91
|
-
if (!editMode && !positionChanged && clock.elapsedTime - lastUpdate.current > 5) {
|
|
92
|
-
lastUpdate.current = clock.elapsedTime;
|
|
93
|
-
directionalLightRef.current.shadow.needsUpdate = true;
|
|
94
|
-
}
|
|
95
|
-
// Update camera helper in edit mode
|
|
96
|
-
if (editMode && cameraHelperRef.current) {
|
|
97
|
-
cameraHelperRef.current.update();
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
88
|
return (_jsxs(_Fragment, { children: [_jsx("directionalLight", { ref: directionalLightRef, color: color, intensity: intensity, castShadow: castShadow, "shadow-mapSize": [shadowMapSize, shadowMapSize], "shadow-bias": -0.001, "shadow-normalBias": 0.02, children: _jsx("orthographicCamera", { attach: "shadow-camera", near: shadowCameraNear, far: shadowCameraFar, top: shadowCameraTop, bottom: shadowCameraBottom, left: shadowCameraLeft, right: shadowCameraRight }) }), editMode && (_jsxs(_Fragment, { children: [_jsxs("mesh", { children: [_jsx("sphereGeometry", { args: [0.3, 8, 6] }), _jsx("meshBasicMaterial", { color: color, wireframe: true })] }), _jsxs("mesh", { position: targetOffset, children: [_jsx("sphereGeometry", { args: [0.2, 8, 6] }), _jsx("meshBasicMaterial", { color: color, wireframe: true, opacity: 0.5, transparent: true })] }), _jsxs("line", { children: [_jsx("bufferGeometry", { onUpdate: (geo) => {
|
|
101
89
|
const points = [
|
|
102
90
|
new Vector3(0, 0, 0),
|