From 14fb62bda458eb82efc3d8f361044aefc6845ace Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Sat, 6 Jan 2024 13:21:11 -0600 Subject: [PATCH] feat: pitch and yaw limits for inputs --- .../[id]/components/GunContainer.tsx | 76 ++--------------- .../[id]/components/RotationInputs.tsx | 30 ++++--- .../[id]/components/TurretContainer.tsx | 76 ++--------------- src/core/blitz/applyPitchYawLimits.ts | 81 +++++++++++++++++++ 4 files changed, 113 insertions(+), 150 deletions(-) create mode 100644 src/core/blitz/applyPitchYawLimits.ts diff --git a/src/app/tools/tankopedia/[id]/components/GunContainer.tsx b/src/app/tools/tankopedia/[id]/components/GunContainer.tsx index efd2555c5..455e5c1e8 100644 --- a/src/app/tools/tankopedia/[id]/components/GunContainer.tsx +++ b/src/app/tools/tankopedia/[id]/components/GunContainer.tsx @@ -1,7 +1,7 @@ import { ThreeEvent, useThree } from '@react-three/fiber'; -import { clamp } from 'lodash'; import { RefObject, forwardRef, useImperativeHandle, useRef } from 'react'; import { Group, Mesh, Object3D, Vector3 } from 'three'; +import { applyPitchYawLimits } from '../../../../../core/blitz/applyPitchYawLimits'; import { InitialTurretRotation, PitchLimits, @@ -78,77 +78,11 @@ export const GunContainer = forwardRef( window.addEventListener('pointerup', handlePointerUp); } function handlePointerMove(event: PointerEvent) { - if (yawLimits) { - draftYaw = clamp( - draftYaw + event.movementX * (Math.PI / canvas.width), - -yawLimits.max * (Math.PI / 180), - -yawLimits.min * (Math.PI / 180), - ); - } else { - draftYaw += event.movementX * ((2 * Math.PI) / canvas.width); - } - - let lowerPitch = -pitchLimits.max * (Math.PI / 180); - let upperPitch = -pitchLimits.min * (Math.PI / 180); - const transition = (pitchLimits.transition ?? 10) * (Math.PI / 180); - - if (pitchLimits.back) { - const range = pitchLimits.back.range * (Math.PI / 180); - const yawRotatedAbs = Math.abs( - normalizeAnglePI(draftYaw - Math.PI), - ); - - if (yawRotatedAbs <= range + transition) { - if (yawRotatedAbs <= range) { - // inside range - lowerPitch = -pitchLimits.back.max * (Math.PI / 180); - upperPitch = -pitchLimits.back.min * (Math.PI / 180); - } else { - // inside transition - const transitionProgress = - (yawRotatedAbs - range) / transition; - const maxInterpolatedPitch = - (1 - transitionProgress) * pitchLimits.back.max + - transitionProgress * pitchLimits.max; - const minInterpolatedPitch = - (1 - transitionProgress) * pitchLimits.back.min + - transitionProgress * pitchLimits.min; - - lowerPitch = -maxInterpolatedPitch * (Math.PI / 180); - upperPitch = -minInterpolatedPitch * (Math.PI / 180); - } - } - } - - if (pitchLimits.front) { - const range = pitchLimits.front.range * (Math.PI / 180); - const yawAbs = Math.abs(normalizeAnglePI(draftYaw)); - - if (yawAbs <= range + transition) { - if (yawAbs <= range) { - // inside range - lowerPitch = -pitchLimits.front.max * (Math.PI / 180); - upperPitch = -pitchLimits.front.min * (Math.PI / 180); - } else { - // inside transition - const transitionProgress = (yawAbs - range) / transition; - const maxInterpolatedPitch = - (1 - transitionProgress) * pitchLimits.front.max + - transitionProgress * pitchLimits.max; - const minInterpolatedPitch = - (1 - transitionProgress) * pitchLimits.front.min + - transitionProgress * pitchLimits.min; - - lowerPitch = -maxInterpolatedPitch * (Math.PI / 180); - upperPitch = -minInterpolatedPitch * (Math.PI / 180); - } - } - } - - draftPitch = clamp( + [draftPitch, draftYaw] = applyPitchYawLimits( draftPitch - event.movementY * (Math.PI / canvas.height), - lowerPitch, - upperPitch, + draftYaw + event.movementX * (Math.PI / canvas.width), + pitchLimits, + yawLimits, ); if (gunContainer.current) { diff --git a/src/app/tools/tankopedia/[id]/components/RotationInputs.tsx b/src/app/tools/tankopedia/[id]/components/RotationInputs.tsx index 0ba164840..73601c6fd 100644 --- a/src/app/tools/tankopedia/[id]/components/RotationInputs.tsx +++ b/src/app/tools/tankopedia/[id]/components/RotationInputs.tsx @@ -1,6 +1,6 @@ import { Flex, TextField } from '@radix-ui/themes'; -import { clamp } from 'lodash'; import { use, useEffect, useRef } from 'react'; +import { applyPitchYawLimits } from '../../../../../core/blitz/applyPitchYawLimits'; import { modelDefinitions } from '../../../../../core/blitzkrieg/modelDefinitions'; import { normalizeAnglePI } from '../../../../../core/math/normalizeAngle180'; import mutateTankopedia, { @@ -94,13 +94,19 @@ export function RotationInputs({ { - const normalized = normalizeAnglePI( + const [pitch, yaw] = applyPitchYawLimits( + model.gunPitch, -Number(turretYawInput.current?.value) * (Math.PI / 180), + gunModelDefinition.pitch, + turretModelDefinition.yaw, ); mutateTankopedia((state) => { - state.model.turretYaw = normalized; + state.model.gunPitch = pitch; + state.model.turretYaw = yaw; }); - turretYawInput.current!.value = `${Math.round(normalized)}`; + turretYawInput.current!.value = `${Math.round( + yaw * (180 / Math.PI), + )}`; }} onKeyDown={(event) => { if (event.key === 'Enter') { @@ -124,15 +130,19 @@ export function RotationInputs({ { - const clamped = clamp( - Number(gunPitchInput.current?.value), - gunModelDefinition.pitch.min, - gunModelDefinition.pitch.max, + const [pitch, yaw] = applyPitchYawLimits( + -Number(gunPitchInput.current?.value) * (Math.PI / 180), + model.turretYaw, + gunModelDefinition.pitch, + turretModelDefinition.yaw, ); mutateTankopedia((state) => { - state.model.gunPitch = -clamped * (Math.PI / 180); + state.model.gunPitch = pitch; + state.model.turretYaw = yaw; }); - gunPitchInput.current!.value = `${Math.round(clamped)}`; + gunPitchInput.current!.value = `${Math.round( + pitch * (180 / Math.PI), + )}`; }} onKeyDown={(event) => { if (event.key === 'Enter') { diff --git a/src/app/tools/tankopedia/[id]/components/TurretContainer.tsx b/src/app/tools/tankopedia/[id]/components/TurretContainer.tsx index 51f50f925..c4bc80e1a 100644 --- a/src/app/tools/tankopedia/[id]/components/TurretContainer.tsx +++ b/src/app/tools/tankopedia/[id]/components/TurretContainer.tsx @@ -1,5 +1,4 @@ import { ThreeEvent, useThree } from '@react-three/fiber'; -import { clamp } from 'lodash'; import { ReactNode, RefObject, @@ -8,6 +7,7 @@ import { useRef, } from 'react'; import { Euler, Group, Mesh, Object3D, Vector3 } from 'three'; +import { applyPitchYawLimits } from '../../../../../core/blitz/applyPitchYawLimits'; import { InitialTurretRotation, PitchLimits, @@ -99,74 +99,12 @@ export const TurretContainer = forwardRef( } } function handlePointerMove(event: PointerEvent) { - if (yawLimits) { - draftYaw = clamp( - draftYaw + event.movementX * (Math.PI / canvas.width), - -yawLimits.max * (Math.PI / 180), - -yawLimits.min * (Math.PI / 180), - ); - } else { - draftYaw += event.movementX * ((2 * Math.PI) / canvas.width); - } - - let lowerPitch = -pitchLimits.max * (Math.PI / 180); - let upperPitch = -pitchLimits.min * (Math.PI / 180); - const transition = (pitchLimits.transition ?? 10) * (Math.PI / 180); - - if (pitchLimits.back) { - const range = pitchLimits.back.range * (Math.PI / 180); - const yawRotatedAbs = Math.abs( - normalizeAnglePI(draftYaw - Math.PI), - ); - - if (yawRotatedAbs <= range + transition) { - if (yawRotatedAbs <= range) { - // inside range - lowerPitch = -pitchLimits.back.max * (Math.PI / 180); - upperPitch = -pitchLimits.back.min * (Math.PI / 180); - } else { - // inside transition - const transitionProgress = - (yawRotatedAbs - range) / transition; - const maxInterpolatedPitch = - (1 - transitionProgress) * pitchLimits.back.max + - transitionProgress * pitchLimits.max; - const minInterpolatedPitch = - (1 - transitionProgress) * pitchLimits.back.min + - transitionProgress * pitchLimits.min; - - lowerPitch = -maxInterpolatedPitch * (Math.PI / 180); - upperPitch = -minInterpolatedPitch * (Math.PI / 180); - } - } - } - - if (pitchLimits.front) { - const range = pitchLimits.front.range * (Math.PI / 180); - const yawAbs = Math.abs(normalizeAnglePI(draftYaw)); - - if (yawAbs <= range + transition) { - if (yawAbs <= range) { - // inside range - lowerPitch = -pitchLimits.front.max * (Math.PI / 180); - upperPitch = -pitchLimits.front.min * (Math.PI / 180); - } else { - // inside transition - const transitionProgress = (yawAbs - range) / transition; - const maxInterpolatedPitch = - (1 - transitionProgress) * pitchLimits.front.max + - transitionProgress * pitchLimits.max; - const minInterpolatedPitch = - (1 - transitionProgress) * pitchLimits.front.min + - transitionProgress * pitchLimits.min; - - lowerPitch = -maxInterpolatedPitch * (Math.PI / 180); - upperPitch = -minInterpolatedPitch * (Math.PI / 180); - } - } - } - - draftPitch = clamp(draftPitch, lowerPitch, upperPitch); + [draftPitch, draftYaw] = applyPitchYawLimits( + draftPitch, + draftYaw + event.movementX * (Math.PI / canvas.width), + pitchLimits, + yawLimits, + ); if (gunContainer.current) { gunContainer.current.position diff --git a/src/core/blitz/applyPitchYawLimits.ts b/src/core/blitz/applyPitchYawLimits.ts new file mode 100644 index 000000000..06994d837 --- /dev/null +++ b/src/core/blitz/applyPitchYawLimits.ts @@ -0,0 +1,81 @@ +import { clamp } from 'lodash'; +import { PitchLimits, YawLimits } from '../blitzkrieg/modelDefinitions'; +import { normalizeAnglePI } from '../math/normalizeAngle180'; + +export function applyPitchYawLimits( + pitch: number, // rad + yaw: number, // rad + pitchLimits: PitchLimits, // deg + yawLimits?: YawLimits, // deg +) { + let newPitch = pitch; + let newYaw = yaw; + + if (yawLimits) { + newYaw = normalizeAnglePI( + clamp( + newYaw, + -yawLimits.max * (Math.PI / 180), + -yawLimits.min * (Math.PI / 180), + ), + ); + } + + let lowerPitch = -pitchLimits.max * (Math.PI / 180); + let upperPitch = -pitchLimits.min * (Math.PI / 180); + const transition = (pitchLimits.transition ?? 10) * (Math.PI / 180); + + if (pitchLimits.back) { + const range = pitchLimits.back.range * (Math.PI / 180); + const yawRotatedAbs = Math.abs(normalizeAnglePI(newYaw - Math.PI)); + + if (yawRotatedAbs <= range + transition) { + if (yawRotatedAbs <= range) { + // inside range + lowerPitch = -pitchLimits.back.max * (Math.PI / 180); + upperPitch = -pitchLimits.back.min * (Math.PI / 180); + } else { + // inside transition + const transitionProgress = (yawRotatedAbs - range) / transition; + const maxInterpolatedPitch = + (1 - transitionProgress) * pitchLimits.back.max + + transitionProgress * pitchLimits.max; + const minInterpolatedPitch = + (1 - transitionProgress) * pitchLimits.back.min + + transitionProgress * pitchLimits.min; + + lowerPitch = -maxInterpolatedPitch * (Math.PI / 180); + upperPitch = -minInterpolatedPitch * (Math.PI / 180); + } + } + } + + if (pitchLimits.front) { + const range = pitchLimits.front.range * (Math.PI / 180); + const yawAbs = Math.abs(normalizeAnglePI(newYaw)); + + if (yawAbs <= range + transition) { + if (yawAbs <= range) { + // inside range + lowerPitch = -pitchLimits.front.max * (Math.PI / 180); + upperPitch = -pitchLimits.front.min * (Math.PI / 180); + } else { + // inside transition + const transitionProgress = (yawAbs - range) / transition; + const maxInterpolatedPitch = + (1 - transitionProgress) * pitchLimits.front.max + + transitionProgress * pitchLimits.max; + const minInterpolatedPitch = + (1 - transitionProgress) * pitchLimits.front.min + + transitionProgress * pitchLimits.min; + + lowerPitch = -maxInterpolatedPitch * (Math.PI / 180); + upperPitch = -minInterpolatedPitch * (Math.PI / 180); + } + } + } + + newPitch = clamp(newPitch, lowerPitch, upperPitch); + + return [newPitch, newYaw]; +}