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) => {
48 new THREE.Vector3(point.x, point.y, point.z)
57 camera instanceof THREE.PerspectiveCamera
59 const center = new THREE.Vector3();
60 box.getCenter(center);
61 const size = new THREE.Vector3();
64 const maxDim = Math.max(size.x, size.y, size.z);
65 const fov = camera.fov * (Math.PI / 180);
66 let cameraZ = Math.abs(maxDim / 2 / Math.tan(fov / 2));
69 const newPos = new THREE.Vector3(
71 center.y + cameraZ / 2,
75 camera.position.copy(newPos);
76 controls.target.copy(center);
80 lastActionTimestamp.current = viewControlAction.timestamp;
81 clearViewControlAction();
82 } else if (type === 'focus' && targetId) {
83 const platform = platforms.find((p) => p.id === targetId);
85 const currentTime = useScenarioStore.getState().currentTime;
86 const position = calculateInterpolatedPosition(
90 controls.target.copy(position);
93 lastActionTimestamp.current = viewControlAction.timestamp;
94 clearViewControlAction();
101 clearViewControlAction,
105 !controlsRef.current ||
106 viewControlAction.type !== 'follow' ||
107 !viewControlAction.targetId
112 const controls = controlsRef.current;
113 const platform = platforms.find(
114 (p) => p.id === viewControlAction.targetId
118 const position = calculateInterpolatedPosition(
120 useScenarioStore.getState().currentTime
122 controls.target.copy(position);