mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-23 19:49:50 +02:00
feat: pitch and yaw limits for inputs
This commit is contained in:
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
|
||||||
|
|||||||
@@ -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];
|
||||||
|
}
|
||||||
Reference in new issue
Block a user