mirror of
https://github.com/exogen/t2-mapper.git
synced 2026-03-16 10:50:57 +00:00
153 lines
4.2 KiB
TypeScript
153 lines
4.2 KiB
TypeScript
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;
|
|
setCameraIndex: (index: number) => void;
|
|
cameraCount: number;
|
|
}
|
|
|
|
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((state) => state.camera);
|
|
const [cameraIndex, setCameraIndex] = useState(-1);
|
|
const [cameraMap, setCameraMap] = useState<Record<string, CameraEntry>>({});
|
|
const [initialViewState, setInitialViewState] = useState(() => ({
|
|
initialized: false,
|
|
position: null,
|
|
quarternion: null,
|
|
}));
|
|
|
|
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;
|
|
});
|
|
}, []);
|
|
|
|
const cameraCount = Object.keys(cameraMap).length;
|
|
|
|
const setCamera = useCallback(
|
|
(index: number) => {
|
|
if (index >= 0 && index < cameraCount) {
|
|
setCameraIndex(index);
|
|
const cameraId = Object.keys(cameraMap)[index];
|
|
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),
|
|
-Math.PI / 2,
|
|
);
|
|
camera.quaternion.copy(cameraInfo.rotation).multiply(correction);
|
|
}
|
|
},
|
|
[camera, cameraCount, cameraMap],
|
|
);
|
|
|
|
const nextCamera = useCallback(() => {
|
|
setCamera(cameraCount ? (cameraIndex + 1) % cameraCount : -1);
|
|
}, [cameraCount, cameraIndex, setCamera]);
|
|
|
|
useEffect(() => {
|
|
const handleHashChange = () => {
|
|
const hash = window.location.hash;
|
|
if (hash.startsWith("#c")) {
|
|
const [positionString, quarternionString] = hash.slice(2).split("~");
|
|
const position = positionString.split(",").map((s) => parseFloat(s));
|
|
const quarternion = quarternionString
|
|
.split(",")
|
|
.map((s) => parseFloat(s));
|
|
setInitialViewState({
|
|
initialized: true,
|
|
position: new Vector3(...position),
|
|
quarternion: new Quaternion(...quarternion),
|
|
});
|
|
} else {
|
|
setInitialViewState({
|
|
initialized: true,
|
|
position: null,
|
|
quarternion: null,
|
|
});
|
|
}
|
|
};
|
|
|
|
window.addEventListener("hashchange", handleHashChange);
|
|
handleHashChange();
|
|
|
|
return () => {
|
|
window.removeEventListener("hashchange", handleHashChange);
|
|
};
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (initialViewState.initialized && initialViewState.position) {
|
|
camera.position.copy(initialViewState.position);
|
|
if (initialViewState.quarternion) {
|
|
camera.quaternion.copy(initialViewState.quarternion);
|
|
}
|
|
}
|
|
}, [camera, initialViewState]);
|
|
|
|
useEffect(() => {
|
|
if (!initialViewState.initialized || initialViewState.position) return;
|
|
if (cameraCount > 0 && cameraIndex === -1) {
|
|
setCamera(0);
|
|
}
|
|
}, [cameraCount, setCamera, cameraIndex, initialViewState]);
|
|
|
|
const context: CamerasContextValue = useMemo(
|
|
() => ({
|
|
registerCamera,
|
|
unregisterCamera,
|
|
nextCamera,
|
|
setCameraIndex: setCamera,
|
|
cameraCount,
|
|
}),
|
|
[registerCamera, unregisterCamera, nextCamera, setCamera, cameraCount],
|
|
);
|
|
|
|
if (cameraCount === 0 && cameraIndex !== -1) {
|
|
setCameraIndex(-1);
|
|
}
|
|
|
|
return (
|
|
<CamerasContext.Provider value={context}>
|
|
{children}
|
|
</CamerasContext.Provider>
|
|
);
|
|
}
|