feat: pitch and yaw limits for inputs

This commit is contained in:
TrèsAbhi committed 2024-01-06 13:21:11 -06:00
1 parent 21122c620b
commit 14fb62bda4
4 files changed
+113 -150

No files matched your search

@@ -1,7 +1,7 @@
import { ThreeEvent, useThree } from '@react-three/fiber'; import { ThreeEvent, useThree } from '@react-three/fiber';
import { clamp } from 'lodash';
import { RefObject, forwardRef, useImperativeHandle, useRef } from 'react'; import { RefObject, forwardRef, useImperativeHandle, useRef } from 'react';
import { Group, Mesh, Object3D, Vector3 } from 'three'; import { Group, Mesh, Object3D, Vector3 } from 'three';
import { applyPitchYawLimits } from '../../../../../core/blitz/applyPitchYawLimits';
import { import {
InitialTurretRotation, InitialTurretRotation,
PitchLimits, PitchLimits,
@@ -78,77 +78,11 @@ export const GunContainer = forwardRef<Group, GunContainerProps>(
window.addEventListener('pointerup', handlePointerUp); window.addEventListener('pointerup', handlePointerUp);
} }
function handlePointerMove(event: PointerEvent) { function handlePointerMove(event: PointerEvent) {
if (yawLimits) { [draftPitch, draftYaw] = applyPitchYawLimits(
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 - event.movementY * (Math.PI / canvas.height), draftPitch - event.movementY * (Math.PI / canvas.height),
lowerPitch, draftYaw + event.movementX * (Math.PI / canvas.width),
upperPitch, pitchLimits,
yawLimits,
); );
if (gunContainer.current) { if (gunContainer.current) {
@@ -1,6 +1,6 @@
import { Flex, TextField } from '@radix-ui/themes'; import { Flex, TextField } from '@radix-ui/themes';
import { clamp } from 'lodash';
import { use, useEffect, useRef } from 'react'; import { use, useEffect, useRef } from 'react';
import { applyPitchYawLimits } from '../../../../../core/blitz/applyPitchYawLimits';
import { modelDefinitions } from '../../../../../core/blitzkrieg/modelDefinitions'; import { modelDefinitions } from '../../../../../core/blitzkrieg/modelDefinitions';
import { normalizeAnglePI } from '../../../../../core/math/normalizeAngle180'; import { normalizeAnglePI } from '../../../../../core/math/normalizeAngle180';
import mutateTankopedia, { import mutateTankopedia, {
@@ -94,13 +94,19 @@ export function RotationInputs({
<TextField.Input <TextField.Input
defaultValue={Math.round(model.turretYaw * (180 / Math.PI))} defaultValue={Math.round(model.turretYaw * (180 / Math.PI))}
onBlur={() => { onBlur={() => {
const normalized = normalizeAnglePI( const [pitch, yaw] = applyPitchYawLimits(
model.gunPitch,
-Number(turretYawInput.current?.value) * (Math.PI / 180), -Number(turretYawInput.current?.value) * (Math.PI / 180),
gunModelDefinition.pitch,
turretModelDefinition.yaw,
); );
mutateTankopedia((state) => { 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) => { onKeyDown={(event) => {
if (event.key === 'Enter') { if (event.key === 'Enter') {
@@ -124,15 +130,19 @@ export function RotationInputs({
<TextField.Input <TextField.Input
defaultValue={-Math.round(model.gunPitch * (180 / Math.PI))} defaultValue={-Math.round(model.gunPitch * (180 / Math.PI))}
onBlur={() => { onBlur={() => {
const clamped = clamp( const [pitch, yaw] = applyPitchYawLimits(
Number(gunPitchInput.current?.value), -Number(gunPitchInput.current?.value) * (Math.PI / 180),
gunModelDefinition.pitch.min, model.turretYaw,
gunModelDefinition.pitch.max, gunModelDefinition.pitch,
turretModelDefinition.yaw,
); );
mutateTankopedia((state) => { 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) => { onKeyDown={(event) => {
if (event.key === 'Enter') { if (event.key === 'Enter') {
@@ -1,5 +1,4 @@
import { ThreeEvent, useThree } from '@react-three/fiber'; import { ThreeEvent, useThree } from '@react-three/fiber';
import { clamp } from 'lodash';
import { import {
ReactNode, ReactNode,
RefObject, RefObject,
@@ -8,6 +7,7 @@ import {
useRef, useRef,
} from 'react'; } from 'react';
import { Euler, Group, Mesh, Object3D, Vector3 } from 'three'; import { Euler, Group, Mesh, Object3D, Vector3 } from 'three';
import { applyPitchYawLimits } from '../../../../../core/blitz/applyPitchYawLimits';
import { import {
InitialTurretRotation, InitialTurretRotation,
PitchLimits, PitchLimits,
@@ -99,74 +99,12 @@ export const TurretContainer = forwardRef(
} }
} }
function handlePointerMove(event: PointerEvent) { function handlePointerMove(event: PointerEvent) {
if (yawLimits) { [draftPitch, draftYaw] = applyPitchYawLimits(
draftYaw = clamp( draftPitch,
draftYaw + event.movementX * (Math.PI / canvas.width), draftYaw + event.movementX * (Math.PI / canvas.width),
-yawLimits.max * (Math.PI / 180), pitchLimits,
-yawLimits.min * (Math.PI / 180), yawLimits,
); );
} 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);
if (gunContainer.current) { if (gunContainer.current) {
gunContainer.current.position gunContainer.current.position
+81
View File
@@ -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];
}