diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/Control.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/Control.tsx index 5cbdbb370..54bf3c682 100644 --- a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/Control.tsx +++ b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/Control.tsx @@ -8,6 +8,10 @@ import { degToRad } from 'three/src/math/MathUtils.js'; import { awaitableModelDefinitions } from '../../../../../../core/awaitables/modelDefinitions'; import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits'; import { hasEquipment } from '../../../../../../core/blitzkit/hasEquipment'; +import { + modelTransformEvent, + type ModelTransformEventData, +} from '../../../../../../core/blitzkit/modelTransform'; import { Pose, poseEvent } from '../../../../../../core/blitzkit/pose'; import { Duel } from '../../../../../../stores/duel'; import { @@ -16,6 +20,7 @@ import { TankopediaEphemeral, } from '../../../../../../stores/tankopediaEphemeral'; import { TankopediaDisplay } from '../../../../../../stores/tankopediaPersistent/constants'; +import { aimTarget } from './SceneProps/constants'; const poseDistances: Record = { [Pose.HullDown]: 15, @@ -199,6 +204,8 @@ export function Controls() { }, [camera, protagonistTank.id, antagonistTank.id]); useEffect(() => { + let isInitialIteration = true; + function handleDisturbance() { setAutoRotate(false); } @@ -243,6 +250,8 @@ export function Controls() { document.body.addEventListener('scroll', handleScroll); const sniperModeCameraOffset = new Vector3(); + let pitch = 0; + let yaw = 0; function updateCamera() { if (!orbitControls.current) return; @@ -261,19 +270,32 @@ export function Controls() { if (shootingRangeZoom === ShootingRangeZoom.Arcade) { orbitControls.current.target.set(0, gunHeight + 3, 0); - camera.position.set( - 0, - gunHeight + ARCADE_MODE_DISTANCE * Math.sin(ARCADE_MODE_ANGLE), - ARCADE_MODE_DISTANCE * Math.cos(ARCADE_MODE_ANGLE), - ); + + if (isInitialIteration) { + isInitialIteration = false; + camera.position.set( + 0, + gunHeight + ARCADE_MODE_DISTANCE * Math.sin(ARCADE_MODE_ANGLE), + ARCADE_MODE_DISTANCE * Math.cos(ARCADE_MODE_ANGLE), + ); + } else { + camera.position + .copy(aimTarget) + .sub(orbitControls.current.target) + .normalize() + .multiplyScalar(-ARCADE_MODE_DISTANCE) + .add(orbitControls.current.target); + } } else { orbitControls.current.target.copy(shellOrigin); - camera.position.copy( - sniperModeCameraOffset.set(0, 0, 1e-4).add(shellOrigin), - ); - } - console.log(ShootingRangeZoom[shootingRangeZoom], camera.zoom); + camera.position + .copy(aimTarget) + .sub(shellOrigin) + .normalize() + .multiplyScalar(-1e-4) + .add(shellOrigin); + } } else { camera.position.set(...inspectModeInitialPosition); orbitControls.current.target.set(0, gunHeight / 2, 0); @@ -285,6 +307,13 @@ export function Controls() { camera.updateProjectionMatrix(); } + function handleModelTransform(event: ModelTransformEventData) { + pitch = event.pitch; + yaw = event.yaw ?? yaw; + } + + modelTransformEvent.on(handleModelTransform); + updateCamera(); const unsubscribeDisplay = tankopediaEphemeralStore.subscribe( @@ -308,6 +337,7 @@ export function Controls() { window.removeEventListener('keydown', handleKeyDown); canvas.removeEventListener('wheel', handleWheel); document.body.removeEventListener('scroll', handleScroll); + modelTransformEvent.off(handleModelTransform); }; }, [display === TankopediaDisplay.ShootingRange]); diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/SceneProps/constants.ts b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/SceneProps/constants.ts new file mode 100644 index 000000000..d696148df --- /dev/null +++ b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/SceneProps/constants.ts @@ -0,0 +1,3 @@ +import { Vector3 } from 'three'; + +export const aimTarget = new Vector3(); diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/SceneProps/index.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/SceneProps/index.tsx index e372d7d82..cef76819e 100644 --- a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/SceneProps/index.tsx +++ b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/SceneProps/index.tsx @@ -24,6 +24,7 @@ import { TankopediaEphemeral } from '../../../../../../../stores/tankopediaEphem import { TankopediaPersistent } from '../../../../../../../stores/tankopediaPersistent'; import { TankopediaDisplay } from '../../../../../../../stores/tankopediaPersistent/constants'; import { TargetCircle } from './components/TargetCircle'; +import { aimTarget } from './constants'; const [modelDefinitions, equipmentDefinitions, provisionDefinitions] = await Promise.all([ @@ -106,6 +107,8 @@ export function SceneProps() { const gunRotationSpeed = degToRad(characteristics.gunTraverseSpeed); const mockTank = useLoader(GLTFLoader, asset(`3d/tanks/models/12305.glb`)); const texture = useLoader(TextureLoader, imgur('C28Z8nU')); + const path = new Vector3(); + const direction = new Vector3(); texture.anisotropy = 2; useFrame(({ camera }) => { @@ -136,16 +139,16 @@ export function SceneProps() { ); let length: number; - let direction: Vector3; if (cameraIntersections.length === 0) { length = FAR_DISTANCE; - direction = new Vector3(0, 0, -1).applyQuaternion(camera.quaternion); + direction.set(0, 0, -1).applyQuaternion(camera.quaternion); + aimTarget.copy(shellOrigin).addScaledVector(direction, length); } else { - const target = cameraIntersections[0].point; - const path = target.clone().sub(shellOrigin); + aimTarget.copy(cameraIntersections[0].point); + path.copy(aimTarget).sub(shellOrigin); length = path.length(); - direction = path.clone().normalize(); + direction.copy(path).normalize(); } const deltaT = clock.elapsedTime - lastTime;