feat: pitch limits

This commit is contained in:
TrèsAbhi committed 2024-01-03 13:50:36 -06:00
1 parent 56d8392f12
commit a6e8dd6632
5 files changed
+316 -142

No files matched your search

Binary file not shown.
+22 -21
View File
@@ -408,26 +408,26 @@ if (allTargets || targets?.includes('definitions')) {
}), }),
); );
console.log('Committing tank definitions...'); console.log('Committing definitions...');
await commitMultipleFiles( // await commitMultipleFiles(
'tresabhi', // 'tresabhi',
'blitzkrieg-assets', // 'blitzkrieg-assets',
'main', // 'main',
'definitions', // 'definitions',
[ // [
{ // {
content: JSON.stringify(tankDefinitions), // content: JSON.stringify(tankDefinitions),
encoding: 'utf-8', // encoding: 'utf-8',
path: 'definitions/tanks.json', // path: 'definitions/tanks.json',
}, // },
{ // {
content: JSON.stringify(modelDefinitions), // content: JSON.stringify(modelDefinitions),
encoding: 'utf-8', // encoding: 'utf-8',
path: 'definitions/models.json', // path: 'definitions/models.json',
}, // },
], // ],
true, // true,
); // );
} }
if ( if (
@@ -587,8 +587,9 @@ if (allTargets || targets?.includes('tankModels')) {
// if (id !== 15697) continue; // chieftain TODO: investigate vertices stream over read // if (id !== 15697) continue; // chieftain TODO: investigate vertices stream over read
// if (id !== 24609) continue; // concept 1b // if (id !== 24609) continue; // concept 1b
if (id !== 16401) continue; // waffle
// if (id !== 7425) continue; // isu 152 // if (id !== 7425) continue; // isu 152
if (id !== 10369) continue; // mino // if (id !== 10369) continue; // mino
// if (id !== 4417) continue; // amx m4 mle // if (id !== 4417) continue; // amx m4 mle
// if (id !== 7297) continue; // 60tp // if (id !== 7297) continue; // 60tp
// if (id !== 1) continue; // t-34 // if (id !== 1) continue; // t-34
@@ -1,6 +1,6 @@
import { ThreeEvent, useThree } from '@react-three/fiber'; import { ThreeEvent, useThree } from '@react-three/fiber';
import { clamp } from 'lodash'; import { clamp } from 'lodash';
import { RefObject, 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 { import {
PitchLimits, PitchLimits,
@@ -12,8 +12,8 @@ import { normalizeAnglePI } from '../../../../../core/math/normalizeAngle180';
interface GunContainerProps { interface GunContainerProps {
turretOrigin: Vector3; turretOrigin: Vector3;
gunOrigin: Vector3; gunOrigin: Vector3;
gunPitch: number; pitch: number;
turretYaw: number; yaw: number;
objects: Object3D[]; objects: Object3D[];
model: number; model: number;
yawLimits?: YawLimits; yawLimits?: YawLimits;
@@ -23,113 +23,178 @@ interface GunContainerProps {
turretContainer: RefObject<Group>; turretContainer: RefObject<Group>;
} }
export function GunContainer({ export const GunContainer = forwardRef<Group, GunContainerProps>(
gunOrigin, (
turretOrigin, {
gunPitch, gunOrigin,
turretYaw, turretOrigin,
objects, pitch,
yawLimits, yaw,
onPitchStart, objects,
turretContainer, yawLimits,
pitchLimits, onPitchStart,
model, turretContainer,
onPitchEnd, pitchLimits,
}: GunContainerProps) { model,
const canvas = useThree((state) => state.gl.domElement); onPitchEnd,
const gunContainer = useRef<Group>(null); }: GunContainerProps,
ref,
) => {
const canvas = useThree((state) => state.gl.domElement);
const gunContainer = useRef<Group>(null);
return ( useImperativeHandle(ref, () => gunContainer.current!);
<group
position={new Vector3()
.sub(turretOrigin)
.sub(gunOrigin)
.applyAxisAngle(new Vector3(1, 0, 0), gunPitch)
.add(turretOrigin)
.add(gunOrigin)}
rotation={[gunPitch, 0, 0]}
ref={gunContainer}
>
{objects.map((object) => {
const isCurrentMantlet =
object.name === `gun_${model.toString().padStart(2, '0')}_mask`;
const isCurrentGun =
object.name === `gun_${model.toString().padStart(2, '0')}`;
const isVisible = isCurrentGun || isCurrentMantlet;
let draftMantletPitch = 0;
let draftTurretYaw = 0;
function handlePointerDown(event: ThreeEvent<PointerEvent>) { return (
event.stopPropagation(); <group
position={new Vector3()
.sub(turretOrigin)
.sub(gunOrigin)
.applyAxisAngle(new Vector3(1, 0, 0), pitch)
.add(turretOrigin)
.add(gunOrigin)}
rotation={[pitch, 0, 0]}
ref={gunContainer}
>
{objects.map((object) => {
const isCurrentMantlet =
object.name === `gun_${model.toString().padStart(2, '0')}_mask`;
const isCurrentGun =
object.name === `gun_${model.toString().padStart(2, '0')}`;
const isVisible = isCurrentGun || isCurrentMantlet;
let draftPitch = 0;
let draftYaw = 0;
onPitchStart(); function handlePointerDown(event: ThreeEvent<PointerEvent>) {
draftMantletPitch = gunPitch; event.stopPropagation();
draftTurretYaw = turretYaw;
window.addEventListener('pointermove', handlePointerMove); onPitchStart();
window.addEventListener('pointerup', handlePointerUp); draftPitch = pitch;
} draftYaw = yaw;
function handlePointerMove(event: PointerEvent) { window.addEventListener('pointermove', handlePointerMove);
if (yawLimits) { window.addEventListener('pointerup', handlePointerUp);
draftTurretYaw = clamp( }
draftTurretYaw + event.movementX * (Math.PI / canvas.width), function handlePointerMove(event: PointerEvent) {
-yawLimits.max * (Math.PI / 180), if (yawLimits) {
-yawLimits.min * (Math.PI / 180), 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),
lowerPitch,
upperPitch,
); );
} else {
draftTurretYaw += event.movementX * ((2 * Math.PI) / canvas.width); if (gunContainer.current) {
gunContainer.current.position
.set(0, 0, 0)
.sub(turretOrigin)
.sub(gunOrigin)
.applyAxisAngle(new Vector3(1, 0, 0), draftPitch)
.add(turretOrigin)
.add(gunOrigin);
gunContainer.current.rotation.x = draftPitch;
}
if (turretContainer.current) {
turretContainer.current.position
.set(0, 0, 0)
.sub(turretOrigin)
.applyAxisAngle(new Vector3(0, 0, 1), draftYaw)
.add(turretOrigin);
turretContainer.current.rotation.z = draftYaw;
}
} }
draftMantletPitch = clamp( function handlePointerUp() {
draftMantletPitch - event.movementY * (Math.PI / canvas.height), onPitchEnd(
-pitchLimits.max * (Math.PI / 180), normalizeAnglePI(draftPitch),
-pitchLimits.min * (Math.PI / 180), normalizeAnglePI(draftYaw),
);
window.removeEventListener('pointermove', handlePointerMove);
window.removeEventListener('pointerup', handlePointerUp);
}
if (!isVisible) return null;
return (
<mesh
visible={isVisible}
onPointerDown={handlePointerDown}
children={resolveJsxTree(object)}
key={object.uuid}
castShadow
receiveShadow
geometry={(object as Mesh).geometry}
material={(object as Mesh).material}
position={object.position}
rotation={object.rotation}
scale={object.scale}
/>
); );
})}
if (gunContainer.current) { </group>
gunContainer.current.position );
.set(0, 0, 0) },
.sub(turretOrigin) );
.sub(gunOrigin)
.applyAxisAngle(new Vector3(1, 0, 0), draftMantletPitch)
.add(turretOrigin)
.add(gunOrigin);
gunContainer.current.rotation.x = draftMantletPitch;
}
if (turretContainer.current) {
turretContainer.current.position
.set(0, 0, 0)
.sub(turretOrigin)
.applyAxisAngle(new Vector3(0, 0, 1), draftTurretYaw)
.add(turretOrigin);
turretContainer.current.rotation.z = draftTurretYaw;
}
}
function handlePointerUp() {
onPitchEnd(
normalizeAnglePI(draftMantletPitch),
normalizeAnglePI(draftTurretYaw),
);
window.removeEventListener('pointermove', handlePointerMove);
window.removeEventListener('pointerup', handlePointerUp);
}
if (!isVisible) return null;
return (
<mesh
visible={isVisible}
onPointerDown={handlePointerDown}
children={resolveJsxTree(object)}
key={object.uuid}
castShadow
receiveShadow
geometry={(object as Mesh).geometry}
material={(object as Mesh).material}
position={object.position}
rotation={object.rotation}
scale={object.scale}
/>
);
})}
</group>
);
}
@@ -1,28 +1,49 @@
import { ThreeEvent, useThree } from '@react-three/fiber'; import { ThreeEvent, useThree } from '@react-three/fiber';
import { ReactNode, forwardRef, useImperativeHandle, useRef } from 'react'; import { clamp } from 'lodash';
import {
ReactNode,
RefObject,
forwardRef,
useImperativeHandle,
useRef,
} from 'react';
import { Group, Mesh, Object3D, Vector3 } from 'three'; import { Group, Mesh, Object3D, Vector3 } from 'three';
import {
PitchLimits,
YawLimits,
} from '../../../../../core/blitzkrieg/modelDefinitions';
import { resolveJsxTree } from '../../../../../core/blitzkrieg/resolveJsxTree'; import { resolveJsxTree } from '../../../../../core/blitzkrieg/resolveJsxTree';
import { normalizeAnglePI } from '../../../../../core/math/normalizeAngle180'; import { normalizeAnglePI } from '../../../../../core/math/normalizeAngle180';
interface TurretContainerProps { interface TurretContainerProps {
origin: Vector3; turretOrigin: Vector3;
gunOrigin: Vector3;
objects: Object3D[]; objects: Object3D[];
children: ReactNode; children: ReactNode;
yaw: number; yaw: number;
pitch: number;
model: number; model: number;
yawLimits?: YawLimits;
pitchLimits: PitchLimits;
onYawStart: () => void; onYawStart: () => void;
onYawEnd: (yaw: number) => void; onYawEnd: (pitch: number, yaw: number) => void;
gunContainer: RefObject<Group>;
} }
export const TurretContainer = forwardRef( export const TurretContainer = forwardRef(
( (
{ {
origin, gunContainer,
turretOrigin,
yaw, yaw,
children, children,
objects, objects,
yawLimits,
model, model,
onYawEnd, onYawEnd,
pitch,
gunOrigin,
pitchLimits,
onYawStart, onYawStart,
}: TurretContainerProps, }: TurretContainerProps,
ref, ref,
@@ -35,9 +56,9 @@ export const TurretContainer = forwardRef(
return ( return (
<group <group
position={new Vector3() position={new Vector3()
.sub(origin) .sub(turretOrigin)
.applyAxisAngle(new Vector3(0, 0, 1), yaw) .applyAxisAngle(new Vector3(0, 0, 1), yaw)
.add(origin)} .add(turretOrigin)}
rotation={[0, 0, yaw]} rotation={[0, 0, yaw]}
ref={turretContainer} ref={turretContainer}
> >
@@ -46,6 +67,7 @@ export const TurretContainer = forwardRef(
const isCurrentTurret = const isCurrentTurret =
object.name === `turret_${model.toString().padStart(2, '0')}`; object.name === `turret_${model.toString().padStart(2, '0')}`;
const isVisible = isCurrentTurret; const isVisible = isCurrentTurret;
let draftPitch = 0;
let draftYaw = 0; let draftYaw = 0;
function handlePointerDown(event: ThreeEvent<PointerEvent>) { function handlePointerDown(event: ThreeEvent<PointerEvent>) {
@@ -53,6 +75,7 @@ export const TurretContainer = forwardRef(
if (isTurret) { if (isTurret) {
draftYaw = yaw; draftYaw = yaw;
draftPitch = pitch;
onYawStart(); onYawStart();
window.addEventListener('pointermove', handlePointerMove); window.addEventListener('pointermove', handlePointerMove);
@@ -60,19 +83,97 @@ export const TurretContainer = forwardRef(
} }
} }
function handlePointerMove(event: PointerEvent) { function handlePointerMove(event: PointerEvent) {
draftYaw += event.movementX * ((2 * Math.PI) / canvas.width); 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);
if (gunContainer.current) {
gunContainer.current.position
.set(0, 0, 0)
.sub(turretOrigin)
.sub(gunOrigin)
.applyAxisAngle(new Vector3(1, 0, 0), draftPitch)
.add(turretOrigin)
.add(gunOrigin);
gunContainer.current.rotation.x = draftPitch;
}
if (turretContainer.current) { if (turretContainer.current) {
turretContainer.current.position turretContainer.current.position
.set(0, 0, 0) .set(0, 0, 0)
.sub(origin) .sub(turretOrigin)
.applyAxisAngle(new Vector3(0, 0, 1), draftYaw) .applyAxisAngle(new Vector3(0, 0, 1), draftYaw)
.add(origin); .add(turretOrigin);
turretContainer.current.rotation.z = draftYaw; turretContainer.current.rotation.z = draftYaw;
} }
} }
function handlePointerUp() { function handlePointerUp() {
onYawEnd(normalizeAnglePI(draftYaw)); onYawEnd(normalizeAnglePI(draftPitch), normalizeAnglePI(draftYaw));
window.removeEventListener('pointermove', handlePointerMove); window.removeEventListener('pointermove', handlePointerMove);
window.removeEventListener('pointerup', handlePointerUp); window.removeEventListener('pointerup', handlePointerUp);
} }
+11 -4
View File
@@ -163,22 +163,29 @@ export default function Page({ params }: { params: { id: string } }) {
}} }}
> >
<TurretContainer <TurretContainer
gunOrigin={gunOrigin}
ref={turretContainer} ref={turretContainer}
objects={gltf.scene.children[0].children} objects={gltf.scene.children[0].children}
model={turretModelDefinition.model} model={turretModelDefinition.model}
onYawStart={() => setControlsEnabled(false)} onYawStart={() => setControlsEnabled(false)}
onYawEnd={(yaw) => { yawLimits={turretModelDefinition.yaw}
pitchLimits={gunModelDefinition.pitch}
pitch={gunPitch}
onYawEnd={(pitch, yaw) => {
setControlsEnabled(true); setControlsEnabled(true);
setGunPitch(pitch);
setTurretYaw(yaw); setTurretYaw(yaw);
}} }}
origin={turretOrigin} gunContainer={gunContainer}
turretOrigin={turretOrigin}
yaw={turretYaw} yaw={turretYaw}
> >
<GunContainer <GunContainer
ref={gunContainer}
onPitchStart={() => setControlsEnabled(false)} onPitchStart={() => setControlsEnabled(false)}
pitchLimits={gunModelDefinition.pitch} pitchLimits={gunModelDefinition.pitch}
turretContainer={turretContainer} turretContainer={turretContainer}
turretYaw={turretYaw} yaw={turretYaw}
onPitchEnd={(pitch, yaw) => { onPitchEnd={(pitch, yaw) => {
setControlsEnabled(true); setControlsEnabled(true);
setGunPitch(pitch); setGunPitch(pitch);
@@ -186,7 +193,7 @@ export default function Page({ params }: { params: { id: string } }) {
}} }}
yawLimits={turretModelDefinition.yaw} yawLimits={turretModelDefinition.yaw}
gunOrigin={gunOrigin} gunOrigin={gunOrigin}
gunPitch={gunPitch} pitch={gunPitch}
model={gunModelDefinition.model} model={gunModelDefinition.model}
objects={gltf.scene.children[0].children} objects={gltf.scene.children[0].children}
turretOrigin={turretOrigin} turretOrigin={turretOrigin}