1// SPDX-License-Identifier: GPL-2.0-only
2// Copyright (c) 2025-present FERS Contributors (see AUTHORS.md).
4import { useEffect, useRef } from 'react';
5import { useThree, useFrame } from '@react-three/fiber';
8 calculateInterpolatedPosition,
9} from '@/stores/scenarioStore';
10import { type MapControls as MapControlsImpl } from 'three-stdlib';
11import * as THREE from 'three';
13interface CameraManagerProps {
14 controlsRef: React.RefObject<MapControlsImpl | null>;
17export default function CameraManager({ controlsRef }: CameraManagerProps) {
18 const { camera } = useThree();
20 const platforms = useScenarioStore((state) => state.platforms);
21 const viewControlAction = useScenarioStore(
22 (state) => state.viewControlAction
24 const clearViewControlAction = useScenarioStore(
25 (state) => state.clearViewControlAction
28 const lastActionTimestamp = useRef(0);
32 !controlsRef.current ||
33 viewControlAction.timestamp === lastActionTimestamp.current
38 const controls = controlsRef.current;
39 const { type, targetId } = viewControlAction;
41 if (type === 'frame') {
42 const box = new THREE.Box3();
43 let hasPoints = false;
45 platforms.forEach((platform) => {
46 (platform.pathPoints ?? []).forEach((point: THREE.Vector3) => {
47 box.expandByPoint(point);
55 camera instanceof THREE.PerspectiveCamera
57 const center = new THREE.Vector3();
58 box.getCenter(center);
59 const size = new THREE.Vector3();
62 const maxDim = Math.max(size.x, size.y, size.z);
63 const fov = camera.fov * (Math.PI / 180);
64 let cameraZ = Math.abs(maxDim / 2 / Math.tan(fov / 2));
67 const newPos = new THREE.Vector3(
69 center.y + cameraZ / 2,
73 camera.position.copy(newPos);
74 controls.target.copy(center);
78 lastActionTimestamp.current = viewControlAction.timestamp;
79 clearViewControlAction();
80 } else if (type === 'focus' && targetId) {
81 const platform = platforms.find((p) => p.id === targetId);
83 const currentTime = useScenarioStore.getState().currentTime;
84 const position = calculateInterpolatedPosition(
88 controls.target.copy(position);
91 lastActionTimestamp.current = viewControlAction.timestamp;
92 clearViewControlAction();
99 clearViewControlAction,
103 !controlsRef.current ||
104 viewControlAction.type !== 'follow' ||
105 !viewControlAction.targetId
110 const controls = controlsRef.current;
111 const platform = platforms.find(
112 (p) => p.id === viewControlAction.targetId
116 const position = calculateInterpolatedPosition(
118 useScenarioStore.getState().currentTime
120 controls.target.copy(position);