diff --git a/src/app/tools/tankopedia/[id]/components/tankAlignment.tsx b/src/app/tools/tankopedia/[id]/components/tankAlignment.tsx index acf0110d4..68ca5872b 100644 --- a/src/app/tools/tankopedia/[id]/components/tankAlignment.tsx +++ b/src/app/tools/tankopedia/[id]/components/tankAlignment.tsx @@ -1,6 +1,6 @@ import { OrbitControls } from '@react-three/drei'; import { useThree } from '@react-three/fiber'; -import { RefObject, useEffect, useRef } from 'react'; +import { RefObject, memo, useEffect, useRef } from 'react'; import { Box3, Group, Vector3 } from 'three'; import { OrbitControls as OrbitControlsImpl } from 'three-stdlib'; @@ -9,23 +9,24 @@ interface TankAlignmentProps { orbit: boolean; } -export function TankAlignment({ model, orbit }: TankAlignmentProps) { - const controls = useRef(null); - const camera = useThree((state) => state.camera); +export const TankAlignment = memo( + ({ model, orbit }: TankAlignmentProps) => { + const controls = useRef(null); + const camera = useThree((state) => state.camera); - useEffect(() => { - const box = new Box3().setFromObject(model.current!); - const diameter = Math.max(box.max.x - box.min.x, box.max.z - box.min.z); + useEffect(() => { + const box = new Box3().setFromObject(model.current!); + controls.current?.target.set(0, (2 * box.min.y + box.max.y) / 3, 0); + camera.position.set(-5, (box.min.y + box.max.y) / 2, -15); + }, []); - controls.current?.target.set(0, (2 * box.min.y + box.max.y) / 3, 0); - camera.position.set(-5, (box.min.y + box.max.y) / 2, -15); - }, []); - - return ( - - ); -} + return ( + + ); + }, + () => false, +); diff --git a/src/app/tools/tankopedia/[id]/page.tsx b/src/app/tools/tankopedia/[id]/page.tsx index e396930fe..2db1e28d5 100644 --- a/src/app/tools/tankopedia/[id]/page.tsx +++ b/src/app/tools/tankopedia/[id]/page.tsx @@ -74,6 +74,7 @@ export default function Page({ params }: { params: { id: string } }) { const turretRotationInput = useRef(null); const hullRotationInput = useRef(null); const gunRotationInput = useRef(null); + const mantletContainer = useRef(null); useEffect(() => { if (!turret.guns.some(({ id }) => gun.id === id)) { @@ -203,9 +204,8 @@ export default function Page({ params }: { params: { id: string } }) { const rotationSpeed = (2 * Math.PI) / canvas.current!.width; - const deltaTurretRotation = - event.movementX * rotationSpeed; - draftTurretRotation += deltaTurretRotation; + const delta = event.movementX * rotationSpeed; + draftTurretRotation += delta; if (hullContainer.current) { hullContainer.current.rotation.z = @@ -259,20 +259,7 @@ export default function Page({ params }: { params: { id: string } }) { `turret_${turret.model .toString() .padStart(2, '0')}`; - const isMantlet = - child.name.startsWith('gun_') && - child.name.endsWith('_mask'); - const isCurrentMantlet = - child.name === - `gun_${gun.model.toString().padStart(2, '0')}_mask`; - const isGun = - child.name.startsWith('gun_') && !isMantlet; - const isCurrentGun = - isGun && - child.name === - `gun_${gun.model.toString().padStart(2, '0')}`; - const isVisible = - isCurrentTurret || isCurrentGun || isCurrentMantlet; + const isVisible = isCurrentTurret; let draftTurretRotation = 0; if (!isVisible) return null; @@ -302,17 +289,13 @@ export default function Page({ params }: { params: { id: string } }) { const rotationSpeed = (2 * Math.PI) / canvas.current!.width; - const deltaTurretRotation = - event.movementX * rotationSpeed; - draftTurretRotation += deltaTurretRotation; + const delta = event.movementX * rotationSpeed; + draftTurretRotation += delta; if (turretContainer.current) { turretContainer.current.position .sub(turretOrigin) - .applyAxisAngle( - new Vector3(0, 0, 1), - deltaTurretRotation, - ) + .applyAxisAngle(new Vector3(0, 0, 1), delta) .add(turretOrigin); turretContainer.current.rotation.z = draftTurretRotation; @@ -350,6 +333,101 @@ export default function Page({ params }: { params: { id: string } }) { /> ); })} + + + {gltf.scene.children[0].children.map((child) => { + const isMantlet = + child.name.startsWith('gun_') && + child.name.endsWith('_mask'); + const isCurrentMantlet = + child.name === + `gun_${gun.model.toString().padStart(2, '0')}_mask`; + const isGun = + child.name.startsWith('gun_') && !isMantlet; + const isCurrentGun = + isGun && + child.name === + `gun_${gun.model.toString().padStart(2, '0')}`; + const isVisible = isCurrentGun || isCurrentMantlet; + let draftMantletPitch = 0; + + if (!isVisible) return null; + + function handlePointerDown( + event: ThreeEvent, + ) { + event.stopPropagation(); + + draftMantletPitch = turretRotation; + + setOrbitControlsEnabled(false); + window.addEventListener( + 'pointermove', + handlePointerMove, + ); + window.addEventListener( + 'pointerup', + handlePointerUp, + ); + } + function handlePointerMove(event: PointerEvent) { + event.stopPropagation(); + event.preventDefault(); + + const rotationSpeed = + (2 * Math.PI) / canvas.current!.width; + const delta = -event.movementY * rotationSpeed; + draftMantletPitch += delta; + + if (mantletContainer.current) { + mantletContainer.current.position + .sub(gunOrigin) + .applyAxisAngle(new Vector3(1, 0, 0), delta) + .add(gunOrigin); + mantletContainer.current.rotation.x = + draftMantletPitch; + } + } + function handlePointerUp(event: PointerEvent) { + setOrbitControlsEnabled(true); + setTurretRotation( + normalizeAngle180(draftMantletPitch), + ); + + window.removeEventListener( + 'pointermove', + handlePointerMove, + ); + window.removeEventListener( + 'pointerup', + handlePointerUp, + ); + } + + return ( + + ); + })} +