2025-11-26 22:37:49 +00:00
|
|
|
import { Quaternion, Vector3 } from "three";
|
|
|
|
|
import { useThree } from "@react-three/fiber";
|
|
|
|
|
import {
|
|
|
|
|
createContext,
|
|
|
|
|
useCallback,
|
|
|
|
|
useContext,
|
|
|
|
|
useEffect,
|
|
|
|
|
useMemo,
|
|
|
|
|
useState,
|
|
|
|
|
type ReactNode,
|
|
|
|
|
} from "react";
|
|
|
|
|
|
|
|
|
|
interface CameraEntry {
|
|
|
|
|
id: string;
|
|
|
|
|
position: Vector3;
|
|
|
|
|
rotation: Quaternion;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface CamerasContextValue {
|
|
|
|
|
registerCamera: (camera: any) => void;
|
|
|
|
|
unregisterCamera: (camera: any) => void;
|
|
|
|
|
nextCamera: () => void;
|
2025-11-27 01:19:17 +00:00
|
|
|
setCameraIndex: (index: number) => void;
|
|
|
|
|
cameraCount: number;
|
2025-11-26 22:37:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const CamerasContext = createContext<CamerasContextValue | null>(null);
|
|
|
|
|
|
|
|
|
|
export function useCameras() {
|
|
|
|
|
const context = useContext(CamerasContext);
|
|
|
|
|
if (!context) {
|
|
|
|
|
throw new Error("useCameras must be used within CamerasProvider");
|
|
|
|
|
}
|
|
|
|
|
return context;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function CamerasProvider({ children }: { children: ReactNode }) {
|
|
|
|
|
const { camera } = useThree();
|
|
|
|
|
const [cameraIndex, setCameraIndex] = useState(0);
|
|
|
|
|
const [cameraMap, setCameraMap] = useState<Record<string, CameraEntry>>({});
|
|
|
|
|
|
|
|
|
|
const registerCamera = useCallback((camera: CameraEntry) => {
|
|
|
|
|
setCameraMap((prevCameraMap) => ({
|
|
|
|
|
...prevCameraMap,
|
|
|
|
|
[camera.id]: camera,
|
|
|
|
|
}));
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const unregisterCamera = useCallback((camera: CameraEntry) => {
|
|
|
|
|
setCameraMap((prevCameraMap) => {
|
|
|
|
|
const { [camera.id]: removedCamera, ...remainingCameras } = prevCameraMap;
|
|
|
|
|
return remainingCameras;
|
|
|
|
|
});
|
|
|
|
|
}, []);
|
|
|
|
|
|
2025-11-27 01:19:17 +00:00
|
|
|
const cameraCount = Object.keys(cameraMap).length;
|
|
|
|
|
|
2025-11-26 22:37:49 +00:00
|
|
|
const nextCamera = useCallback(() => {
|
|
|
|
|
setCameraIndex((prev) => {
|
|
|
|
|
if (cameraCount === 0) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
return (prev + 1) % cameraCount;
|
|
|
|
|
});
|
2025-11-27 01:19:17 +00:00
|
|
|
}, [cameraCount]);
|
|
|
|
|
|
|
|
|
|
const setCamera = useCallback(
|
|
|
|
|
(index: number) => {
|
|
|
|
|
if (index >= 0 && index < cameraCount) {
|
|
|
|
|
setCameraIndex(index);
|
|
|
|
|
}
|
|
|
|
|
},
|
2025-11-29 17:08:20 +00:00
|
|
|
[cameraCount],
|
2025-11-27 01:19:17 +00:00
|
|
|
);
|
2025-11-26 22:37:49 +00:00
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const cameraCount = Object.keys(cameraMap).length;
|
|
|
|
|
if (cameraIndex < cameraCount) {
|
|
|
|
|
const cameraId = Object.keys(cameraMap)[cameraIndex];
|
|
|
|
|
const cameraInfo = cameraMap[cameraId];
|
|
|
|
|
camera.position.copy(cameraInfo.position);
|
|
|
|
|
// Apply coordinate system correction for Torque3D to Three.js
|
|
|
|
|
const correction = new Quaternion().setFromAxisAngle(
|
|
|
|
|
new Vector3(0, 1, 0),
|
2025-11-29 17:08:20 +00:00
|
|
|
-Math.PI / 2,
|
2025-11-26 22:37:49 +00:00
|
|
|
);
|
|
|
|
|
camera.quaternion.copy(cameraInfo.rotation).multiply(correction);
|
|
|
|
|
}
|
|
|
|
|
}, [cameraIndex, cameraMap, camera]);
|
|
|
|
|
|
|
|
|
|
const context: CamerasContextValue = useMemo(
|
|
|
|
|
() => ({
|
|
|
|
|
registerCamera,
|
|
|
|
|
unregisterCamera,
|
|
|
|
|
nextCamera,
|
2025-11-27 01:19:17 +00:00
|
|
|
setCameraIndex: setCamera,
|
|
|
|
|
cameraCount,
|
2025-11-26 22:37:49 +00:00
|
|
|
}),
|
2025-11-29 17:08:20 +00:00
|
|
|
[registerCamera, unregisterCamera, nextCamera, setCamera, cameraCount],
|
2025-11-26 22:37:49 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<CamerasContext.Provider value={context}>
|
|
|
|
|
{children}
|
|
|
|
|
</CamerasContext.Provider>
|
|
|
|
|
);
|
|
|
|
|
}
|