feat: async updates

This commit is contained in:
TrèsAbhi committed 2024-01-13 18:24:04 -06:00
1 parent 5e0e80e965
commit 237f676af8
9 files changed
+248 -304

No files matched your search

+1
View File
@@ -51,6 +51,7 @@
"lods",
"metallicness",
"MIPMAPCOUNT",
"modeltransform",
"MURICA",
"NEGATIVEX",
"NEGATIVEY",
@@ -1,7 +1,6 @@
import { Card, Flex, Tabs, Theme } from '@radix-ui/themes';
import { Html } from '@react-three/drei';
import { Canvas } from '@react-three/fiber';
import { useRouter } from 'next/navigation';
import { Suspense, use, useEffect, useRef, useState } from 'react';
import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits';
import { modelDefinitions } from '../../../../../../core/blitzkrieg/modelDefinitions';
@@ -20,7 +19,6 @@ import { TankArmor } from './components/TankArmor';
import { TankModel } from './components/TankModel';
export function TankDisplay() {
const router = useRouter();
const canvas = useRef<HTMLCanvasElement>(null);
const awaitedModelDefinitions = use(modelDefinitions);
const canvasWrapper = useRef<HTMLDivElement>(null);
@@ -66,24 +64,16 @@ export function TankDisplay() {
const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id];
mutateTankopedia((draft) => {
[draft.model.physical.gunPitch, draft.model.physical.turretYaw] =
[draft.model.physical.pitch, draft.model.physical.yaw] =
applyPitchYawLimits(
draft.model.physical.gunPitch,
draft.model.physical.turretYaw,
draft.model.physical.pitch,
draft.model.physical.yaw,
gunModelDefinition.pitch,
turretModelDefinition.yaw,
);
});
}, [protagonist?.gun, protagonist?.turret]);
useEffect(() => {
if (location.hash.startsWith('#')) {
mutateTankopedia((draft) => {
draft.mode = location.hash.match(/#(.+)/)![1] as TankopediaMode;
});
}
}, []);
if (!protagonist) return null;
return (
@@ -107,8 +97,6 @@ export function TankDisplay() {
<Tabs.Root
value={mode}
onValueChange={(mode) => {
router.push(`#${mode}`);
mutateTankopedia((draft) => {
draft.mode = mode as TankopediaMode;
});
@@ -2,14 +2,19 @@ import { GroupProps, useLoader } from '@react-three/fiber';
import { useEffect, useRef, useState } from 'react';
import { Euler, Group, Mesh, Vector3 } from 'three';
import { GLTFLoader } from 'three-stdlib';
import { degToRad } from 'three/src/math/MathUtils';
import { ArmorMesh } from '../../../../../../../components/ArmorMesh';
import { HeadsUpDisplay } from '../../../../../../../components/HeadsUpDisplay';
import { X_AXIS } from '../../../../../../../constants/axis';
import { X_AXIS, Y_AXIS, Z_AXIS } from '../../../../../../../constants/axis';
import { asset } from '../../../../../../../core/blitzkrieg/asset';
import {
ModelDefinitions,
modelDefinitions,
} from '../../../../../../../core/blitzkrieg/modelDefinitions';
import {
ModelTransformEventData,
modelTransformEvent,
} from '../../../../../../../core/blitzkrieg/modelTransform';
import { nameToArmorId } from '../../../../../../../core/blitzkrieg/nameToArmorId';
import { resolveArmor } from '../../../../../../../core/blitzkrieg/resolveThickness';
import { useTankopedia } from '../../../../../../../stores/tankopedia';
@@ -17,7 +22,6 @@ import { useTankopedia } from '../../../../../../../stores/tankopedia';
interface TankArmorProps extends GroupProps {}
export function TankArmor({ ...props }: TankArmorProps) {
const mode = useTankopedia((state) => state.mode);
const wrapper = useRef<Group>(null);
const [awaitedModelDefinitions, setAwaitedModelDefinitions] = useState<
ModelDefinitions | undefined
@@ -29,7 +33,9 @@ export function TankArmor({ ...props }: TankArmorProps) {
const showSpacedArmor = useTankopedia(
(state) => state.model.visual.showSpacedArmor,
);
const physical = useTankopedia((state) => state.model.physical);
const turretContainer = useRef<Group>(null);
const gunContainer = useRef<Group>(null);
const initialTankopediaState = useTankopedia.getState();
useEffect(() => {
(async () => {
@@ -37,8 +43,83 @@ export function TankArmor({ ...props }: TankArmorProps) {
})();
}, []);
// it's ok to have hooks after this early termination because this will never be true
// it's more for typescript to stop throwing a fit
useEffect(() => {
if (!awaitedModelDefinitions) return;
const turretOrigin = new Vector3(
tankModelDefinition.turretOrigin[0],
tankModelDefinition.turretOrigin[1],
-tankModelDefinition.turretOrigin[2],
).applyAxisAngle(X_AXIS, Math.PI / 2);
const gunOrigin = new Vector3(
turretModelDefinition.gunOrigin[0],
turretModelDefinition.gunOrigin[1],
-turretModelDefinition.gunOrigin[2],
).applyAxisAngle(X_AXIS, Math.PI / 2);
const turretPosition = new Vector3();
const turretRotation = new Euler();
const gunPosition = new Vector3();
const gunRotation = new Euler();
function handleModelTransform({ yaw, pitch }: ModelTransformEventData) {
gunPosition
.set(0, 0, 0)
.sub(turretOrigin)
.sub(gunOrigin)
.applyAxisAngle(X_AXIS, pitch)
.add(gunOrigin)
.add(turretOrigin);
gunRotation.set(pitch, 0, 0);
gunContainer.current?.position.copy(gunPosition);
gunContainer.current?.rotation.copy(gunRotation);
if (yaw === undefined) return;
turretPosition
.set(0, 0, 0)
.sub(turretOrigin)
.applyAxisAngle(new Vector3(0, 0, 1), yaw);
turretRotation.set(0, 0, yaw);
if (tankModelDefinition.turretRotation) {
const initialPitch = -degToRad(
tankModelDefinition.turretRotation.pitch,
);
const initialYaw = -degToRad(tankModelDefinition.turretRotation.yaw);
const initialRoll = -degToRad(tankModelDefinition.turretRotation.roll);
turretPosition
.applyAxisAngle(X_AXIS, initialPitch)
.applyAxisAngle(Y_AXIS, initialRoll)
.applyAxisAngle(Z_AXIS, initialYaw);
turretRotation.x += initialPitch;
turretRotation.y += initialRoll;
turretRotation.z += initialYaw;
}
turretPosition.add(turretOrigin);
turretContainer.current?.position.copy(turretPosition);
turretContainer.current?.rotation.copy(turretRotation);
}
modelTransformEvent.on(handleModelTransform);
return () => {
modelTransformEvent.off(handleModelTransform);
};
}, [awaitedModelDefinitions]);
useEffect(() => {
const unsubscribe = useTankopedia.subscribe(
(state) => state.mode,
(mode) => {
if (wrapper.current) wrapper.current.visible = mode === 'armor';
},
);
return unsubscribe;
});
if (!protagonist) return null;
const armorGltf = useLoader(
@@ -68,34 +149,14 @@ export function TankArmor({ ...props }: TankArmorProps) {
turretModelDefinition.gunOrigin[1],
-turretModelDefinition.gunOrigin[2],
).applyAxisAngle(X_AXIS, Math.PI / 2);
const turretPosition = new Vector3()
.sub(turretOrigin)
.applyAxisAngle(new Vector3(0, 0, 1), physical.turretYaw);
const turretRotation = new Euler(0, 0, physical.turretYaw);
if (tankModelDefinition.turretRotation) {
const pitch = -tankModelDefinition.turretRotation.pitch * (Math.PI / 180);
const yaw = -tankModelDefinition.turretRotation.yaw * (Math.PI / 180);
const roll = -tankModelDefinition.turretRotation.roll * (Math.PI / 180);
turretPosition
.applyAxisAngle(new Vector3(1, 0, 0), pitch)
.applyAxisAngle(new Vector3(0, 1, 0), roll)
.applyAxisAngle(new Vector3(0, 0, 1), yaw);
turretRotation.x += pitch;
turretRotation.y += roll;
turretRotation.z += yaw;
}
turretPosition.add(turretOrigin);
return (
<HeadsUpDisplay>
<group
{...props}
visible={mode === 'armor'}
ref={wrapper}
rotation={[-Math.PI / 2, 0, physical.hullYaw]}
rotation={[-Math.PI / 2, 0, 0]}
visible={initialTankopediaState.mode === 'armor'}
>
{armorNodes.map((node) => {
const isHull = node.name.startsWith('hull_');
@@ -142,7 +203,7 @@ export function TankArmor({ ...props }: TankArmorProps) {
);
})}
<group position={turretPosition} rotation={turretRotation}>
<group ref={turretContainer}>
{armorNodes.map((node) => {
const isCurrentTurret = node.name.startsWith(
`turret_${turretModelDefinition.model
@@ -173,15 +234,7 @@ export function TankArmor({ ...props }: TankArmorProps) {
/>
);
})}
<group
position={new Vector3()
.sub(turretOrigin)
.sub(gunOrigin)
.applyAxisAngle(new Vector3(1, 0, 0), physical.gunPitch)
.add(turretOrigin)
.add(gunOrigin)}
rotation={[physical.gunPitch, 0, 0]}
>
<group ref={gunContainer}>
{armorNodes.map((node) => {
const isCurrentGun = node.name.startsWith(
`gun_${gunModelDefinition.model.toString().padStart(2, '0')}`,
@@ -9,13 +9,18 @@ import {
Vector3,
} from 'three';
import { GLTFLoader } from 'three-stdlib';
import { X_AXIS } from '../../../../../../../constants/axis';
import { degToRad } from 'three/src/math/MathUtils';
import { X_AXIS, Y_AXIS, Z_AXIS } from '../../../../../../../constants/axis';
import { applyPitchYawLimits } from '../../../../../../../core/blitz/applyPitchYawLimits';
import { asset } from '../../../../../../../core/blitzkrieg/asset';
import {
ModelDefinitions,
modelDefinitions,
} from '../../../../../../../core/blitzkrieg/modelDefinitions';
import {
ModelTransformEventData,
modelTransformEvent,
} from '../../../../../../../core/blitzkrieg/modelTransform';
import { resolveJsxTree } from '../../../../../../../core/blitzkrieg/resolveJsxTree';
import { normalizeAnglePI } from '../../../../../../../core/math/normalizeAngle180';
import mutateTankopedia, {
@@ -28,11 +33,14 @@ export function TankModel() {
const [awaitedModelDefinitions, setAwaitedModelDefinitions] = useState<
ModelDefinitions | undefined
>(undefined);
const canvas = useThree((state) => state.gl.domElement);
const protagonist = useTankopedia((state) => {
if (!state.areTanksAssigned) return;
return state.protagonist;
});
if (!protagonist) return null;
const canvas = useThree((state) => state.gl.domElement);
const physical = useTankopedia((state) => state.model.physical);
const hullContainer = useRef<Group>(null);
const turretContainer = useRef<Group>(null);
@@ -44,7 +52,78 @@ export function TankModel() {
})();
}, []);
if (!protagonist) return null;
useEffect(() => {
if (!awaitedModelDefinitions) return;
const turretOrigin = new Vector3(
tankModelDefinition.turretOrigin[0],
tankModelDefinition.turretOrigin[1],
-tankModelDefinition.turretOrigin[2],
).applyAxisAngle(X_AXIS, Math.PI / 2);
const gunOrigin = new Vector3(
turretModelDefinition.gunOrigin[0],
turretModelDefinition.gunOrigin[1],
-turretModelDefinition.gunOrigin[2],
).applyAxisAngle(X_AXIS, Math.PI / 2);
const turretPosition = new Vector3();
const turretRotation = new Euler();
const gunPosition = new Vector3();
const gunRotation = new Euler();
function handleModelTransform({ yaw, pitch }: ModelTransformEventData) {
gunPosition
.set(0, 0, 0)
.sub(turretOrigin)
.sub(gunOrigin)
.applyAxisAngle(X_AXIS, pitch)
.add(gunOrigin)
.add(turretOrigin);
gunRotation.set(pitch, 0, 0);
gunContainer.current?.position.copy(gunPosition);
gunContainer.current?.rotation.copy(gunRotation);
if (yaw === undefined) return;
turretPosition
.set(0, 0, 0)
.sub(turretOrigin)
.applyAxisAngle(new Vector3(0, 0, 1), yaw);
turretRotation.set(0, 0, yaw);
if (tankModelDefinition.turretRotation) {
const initialPitch = -degToRad(
tankModelDefinition.turretRotation.pitch,
);
const initialYaw = -degToRad(tankModelDefinition.turretRotation.yaw);
const initialRoll = -degToRad(tankModelDefinition.turretRotation.roll);
turretPosition
.applyAxisAngle(X_AXIS, initialPitch)
.applyAxisAngle(Y_AXIS, initialRoll)
.applyAxisAngle(Z_AXIS, initialYaw);
turretRotation.x += initialPitch;
turretRotation.y += initialRoll;
turretRotation.z += initialYaw;
}
turretPosition.add(turretOrigin);
turretContainer.current?.position.copy(turretPosition);
turretContainer.current?.rotation.copy(turretRotation);
}
modelTransformEvent.on(handleModelTransform);
return () => {
modelTransformEvent.off(handleModelTransform);
};
}, [awaitedModelDefinitions]);
if (!awaitedModelDefinitions) return;
const tankModelDefinition = awaitedModelDefinitions[protagonist.tank.id];
const turretModelDefinition =
tankModelDefinition.turrets[protagonist.turret.id];
const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id];
const gltf = useLoader(
GLTFLoader,
@@ -52,51 +131,13 @@ export function TankModel() {
);
const nodes = Object.values(gltf.nodes);
if (!awaitedModelDefinitions) return null;
const tankModelDefinition = awaitedModelDefinitions[protagonist.tank.id];
const turretModelDefinition =
tankModelDefinition.turrets[protagonist.turret.id];
const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id];
const turretOrigin = new Vector3(
tankModelDefinition.turretOrigin[0],
tankModelDefinition.turretOrigin[1],
-tankModelDefinition.turretOrigin[2],
).applyAxisAngle(X_AXIS, Math.PI / 2);
const gunOrigin = new Vector3(
turretModelDefinition.gunOrigin[0],
turretModelDefinition.gunOrigin[1],
-turretModelDefinition.gunOrigin[2],
).applyAxisAngle(X_AXIS, Math.PI / 2);
const turretPosition = new Vector3()
.sub(turretOrigin)
.applyAxisAngle(new Vector3(0, 0, 1), physical.turretYaw);
const turretRotation = new Euler(0, 0, physical.turretYaw);
if (tankModelDefinition.turretRotation) {
const pitch = -tankModelDefinition.turretRotation.pitch * (Math.PI / 180);
const yaw = -tankModelDefinition.turretRotation.yaw * (Math.PI / 180);
const roll = -tankModelDefinition.turretRotation.roll * (Math.PI / 180);
turretPosition
.applyAxisAngle(new Vector3(1, 0, 0), pitch)
.applyAxisAngle(new Vector3(0, 1, 0), roll)
.applyAxisAngle(new Vector3(0, 0, 1), yaw);
turretRotation.x += pitch;
turretRotation.y += roll;
turretRotation.z += yaw;
}
turretPosition.add(turretOrigin);
return (
<group ref={hullContainer} rotation={[-Math.PI / 2, 0, physical.hullYaw]}>
<group ref={hullContainer} rotation={[-Math.PI / 2, 0, 0]}>
{nodes.map((node) => {
const isHull = node.name === 'hull';
const isWheel = node.name.startsWith('chassis_wheel_');
const isTrack = node.name.startsWith('chassis_track_');
const isVisible = isHull || isWheel || isTrack;
let draftHullYaw = 0;
function handlePointerDown(event: ThreeEvent<PointerEvent>) {
event.stopPropagation();
@@ -104,31 +145,12 @@ export function TankModel() {
draft.model.visual.controlsEnabled = false;
});
if (isHull) {
draftHullYaw = physical.hullYaw;
window.addEventListener('pointermove', handlePointerMoveHull);
window.addEventListener('pointerup', handlePointerUpHull);
} else if (isTrack) {
window.addEventListener('pointermove', handlePointerMoveTrack);
window.addEventListener('pointerup', handlePointerUpTrack);
if (isTrack) {
window.addEventListener('pointermove', handlePointerMove);
window.addEventListener('pointerup', handlePointerUp);
}
}
function handlePointerMoveHull(event: PointerEvent) {
draftHullYaw += event.movementX * ((2 * Math.PI) / canvas.width);
if (hullContainer.current) {
hullContainer.current.rotation.z = draftHullYaw;
}
}
function handlePointerUpHull() {
mutateTankopedia((draft) => {
draft.model.visual.controlsEnabled = true;
draft.model.physical.hullYaw = normalizeAnglePI(draftHullYaw);
});
window.removeEventListener('pointermove', handlePointerMoveHull);
window.removeEventListener('pointerup', handlePointerUpHull);
}
function handlePointerMoveTrack(event: PointerEvent) {
function handlePointerMove(event: PointerEvent) {
const mesh = node as Mesh;
const material = mesh.material as MeshStandardMaterial;
const offset = new Vector2(
@@ -141,12 +163,12 @@ export function TankModel() {
material.roughnessMap?.offset.add(offset);
material.normalMap?.offset.add(offset);
}
function handlePointerUpTrack() {
function handlePointerUp() {
mutateTankopedia((draft) => {
draft.model.visual.controlsEnabled = true;
});
window.removeEventListener('pointermove', handlePointerMoveTrack);
window.removeEventListener('pointerup', handlePointerUpTrack);
window.removeEventListener('pointermove', handlePointerMove);
window.removeEventListener('pointerup', handlePointerUp);
}
if (!isVisible) return null;
@@ -167,26 +189,22 @@ export function TankModel() {
);
})}
<group
position={turretPosition}
rotation={turretRotation}
ref={turretContainer}
>
<group ref={turretContainer}>
{nodes.map((node) => {
const isTurret = node.name.startsWith('turret_');
const isCurrentTurret =
node.name ===
`turret_${turretModelDefinition.model.toString().padStart(2, '0')}`;
const isVisible = isCurrentTurret;
let draftPitch = 0;
let draftYaw = 0;
let pitch = 0;
let yaw = 0;
function handlePointerDown(event: ThreeEvent<PointerEvent>) {
event.stopPropagation();
if (isTurret) {
draftYaw = physical.turretYaw;
draftPitch = physical.gunPitch;
yaw = physical.yaw;
pitch = physical.pitch;
mutateTankopedia((draft) => {
draft.model.visual.controlsEnabled = false;
@@ -196,53 +214,19 @@ export function TankModel() {
}
}
function handlePointerMove(event: PointerEvent) {
[draftPitch, draftYaw] = applyPitchYawLimits(
draftPitch,
draftYaw + event.movementX * (Math.PI / canvas.width),
[pitch, yaw] = applyPitchYawLimits(
pitch,
yaw + event.movementX * (Math.PI / canvas.width),
gunModelDefinition.pitch,
turretModelDefinition.yaw,
);
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);
turretContainer.current.rotation.z = draftYaw;
if (tankModelDefinition.turretRotation) {
const pitch =
-tankModelDefinition.turretRotation.pitch * (Math.PI / 180);
const yaw =
-tankModelDefinition.turretRotation.yaw * (Math.PI / 180);
const roll =
-tankModelDefinition.turretRotation.roll * (Math.PI / 180);
turretContainer.current.position
.applyAxisAngle(new Vector3(1, 0, 0), pitch)
.applyAxisAngle(new Vector3(0, 1, 0), roll)
.applyAxisAngle(new Vector3(0, 0, 1), yaw);
}
turretContainer.current.position.add(turretOrigin);
}
modelTransformEvent.emit({ pitch, yaw });
}
function handlePointerUp() {
mutateTankopedia((state) => {
state.model.visual.controlsEnabled = true;
state.model.physical.gunPitch = normalizeAnglePI(draftPitch);
state.model.physical.turretYaw = normalizeAnglePI(draftYaw);
state.model.physical.pitch = normalizeAnglePI(pitch);
state.model.physical.yaw = normalizeAnglePI(yaw);
});
window.removeEventListener('pointermove', handlePointerMove);
window.removeEventListener('pointerup', handlePointerUp);
@@ -267,16 +251,7 @@ export function TankModel() {
);
})}
<group
position={new Vector3()
.sub(turretOrigin)
.sub(gunOrigin)
.applyAxisAngle(new Vector3(1, 0, 0), physical.gunPitch)
.add(turretOrigin)
.add(gunOrigin)}
rotation={[physical.gunPitch, 0, 0]}
ref={gunContainer}
>
<group ref={gunContainer}>
{nodes.map((node) => {
const isCurrentMantlet =
node.name ===
@@ -287,8 +262,8 @@ export function TankModel() {
node.name ===
`gun_${gunModelDefinition.model.toString().padStart(2, '0')}`;
const isVisible = isCurrentGun || isCurrentMantlet;
let draftPitch = 0;
let draftYaw = 0;
let pitch = 0;
let yaw = 0;
function handlePointerDown(event: ThreeEvent<PointerEvent>) {
event.stopPropagation();
@@ -296,59 +271,25 @@ export function TankModel() {
mutateTankopedia((draft) => {
draft.model.visual.controlsEnabled = false;
});
draftPitch = physical.gunPitch;
draftYaw = physical.turretYaw;
pitch = physical.pitch;
yaw = physical.yaw;
window.addEventListener('pointermove', handlePointerMove);
window.addEventListener('pointerup', handlePointerUp);
}
function handlePointerMove(event: PointerEvent) {
[draftPitch, draftYaw] = applyPitchYawLimits(
draftPitch - event.movementY * (Math.PI / canvas.height),
draftYaw + event.movementX * (Math.PI / canvas.width),
[pitch, yaw] = applyPitchYawLimits(
pitch - event.movementY * (Math.PI / canvas.height),
yaw + event.movementX * (Math.PI / canvas.width),
gunModelDefinition.pitch,
turretModelDefinition.yaw,
);
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);
turretContainer.current.rotation.z = draftYaw;
if (tankModelDefinition.turretRotation) {
const pitch =
-tankModelDefinition.turretRotation.pitch * (Math.PI / 180);
const yaw =
-tankModelDefinition.turretRotation.yaw * (Math.PI / 180);
const roll =
-tankModelDefinition.turretRotation.roll * (Math.PI / 180);
turretContainer.current.position
.applyAxisAngle(new Vector3(1, 0, 0), pitch)
.applyAxisAngle(new Vector3(0, 1, 0), roll)
.applyAxisAngle(new Vector3(0, 0, 1), yaw);
}
turretContainer.current.position.add(turretOrigin);
}
modelTransformEvent.emit({ pitch, yaw });
}
function handlePointerUp() {
mutateTankopedia((state) => {
state.model.visual.controlsEnabled = true;
state.model.physical.gunPitch = normalizeAnglePI(draftPitch);
state.model.physical.turretYaw = normalizeAnglePI(draftYaw);
state.model.physical.pitch = normalizeAnglePI(pitch);
state.model.physical.yaw = normalizeAnglePI(yaw);
});
window.removeEventListener('pointermove', handlePointerMove);
window.removeEventListener('pointerup', handlePointerUp);
@@ -2,7 +2,6 @@ import { Flex, TextField } from '@radix-ui/themes';
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, {
useTankopedia,
} from '../../../../../stores/tankopedia';
@@ -11,28 +10,22 @@ export function RotationInputs() {
const physical = useTankopedia((state) => state.model.physical);
const awaitedModelDefinitions = use(modelDefinitions);
const turretYawInput = useRef<HTMLInputElement>(null);
const hullYawInput = useRef<HTMLInputElement>(null);
const gunPitchInput = useRef<HTMLInputElement>(null);
const protagonist = useTankopedia((state) => {
if (!state.areTanksAssigned) return;
return state.protagonist;
});
useEffect(() => {
hullYawInput.current!.value = `${-Math.round(
physical.hullYaw * (180 / Math.PI),
)}`;
}, [physical.hullYaw]);
useEffect(() => {
turretYawInput.current!.value = `${-Math.round(
physical.turretYaw * (180 / Math.PI),
physical.yaw * (180 / Math.PI),
)}`;
}, [physical.turretYaw]);
}, [physical.yaw]);
useEffect(() => {
gunPitchInput.current!.value = `${-Math.round(
physical.gunPitch * (180 / Math.PI),
physical.pitch * (180 / Math.PI),
)}`;
}, [physical.gunPitch]);
}, [physical.pitch]);
if (!protagonist) return null;
@@ -55,38 +48,9 @@ export function RotationInputs() {
<Flex
gap="2"
style={{
width: 352,
width: 256,
}}
>
<TextField.Root
style={{
flex: 1,
}}
>
<TextField.Slot>Hull</TextField.Slot>
<TextField.Input
defaultValue={Math.round(physical.hullYaw * (180 / Math.PI))}
onBlur={() => {
const normalized = normalizeAnglePI(
-Number(hullYawInput.current?.value) * (Math.PI / 180),
);
mutateTankopedia((state) => {
state.model.physical.hullYaw = normalized;
});
hullYawInput.current!.value = `${Math.round(normalized)}`;
}}
onKeyDown={(event) => {
if (event.key === 'Enter') {
hullYawInput.current?.blur();
}
}}
onFocus={() => hullYawInput.current?.focus()}
ref={hullYawInput}
style={{ textAlign: 'right' }}
/>
<TextField.Slot>°</TextField.Slot>
</TextField.Root>
<TextField.Root
style={{
flex: 1,
@@ -94,17 +58,17 @@ export function RotationInputs() {
>
<TextField.Slot>Turret</TextField.Slot>
<TextField.Input
defaultValue={Math.round(physical.turretYaw * (180 / Math.PI))}
defaultValue={Math.round(physical.yaw * (180 / Math.PI))}
onBlur={() => {
const [pitch, yaw] = applyPitchYawLimits(
physical.gunPitch,
physical.pitch,
-Number(turretYawInput.current?.value) * (Math.PI / 180),
gunModelDefinition.pitch,
turretModelDefinition.yaw,
);
mutateTankopedia((state) => {
state.model.physical.gunPitch = pitch;
state.model.physical.turretYaw = yaw;
state.model.physical.pitch = pitch;
state.model.physical.yaw = yaw;
});
turretYawInput.current!.value = `${Math.round(
yaw * (180 / Math.PI),
@@ -129,17 +93,17 @@ export function RotationInputs() {
>
<TextField.Slot>Gun</TextField.Slot>
<TextField.Input
defaultValue={-Math.round(physical.gunPitch * (180 / Math.PI))}
defaultValue={-Math.round(physical.pitch * (180 / Math.PI))}
onBlur={() => {
const [pitch, yaw] = applyPitchYawLimits(
-Number(gunPitchInput.current?.value) * (Math.PI / 180),
physical.turretYaw,
physical.yaw,
gunModelDefinition.pitch,
turretModelDefinition.yaw,
);
mutateTankopedia((state) => {
state.model.physical.gunPitch = pitch;
state.model.physical.turretYaw = yaw;
state.model.physical.pitch = pitch;
state.model.physical.yaw = yaw;
});
gunPitchInput.current!.value = `${Math.round(
pitch * (180 / Math.PI),
+3 -26
View File
@@ -18,9 +18,8 @@ export default function Page({ params }: { params: { id: string } }) {
useEffect(() => {
mutateTankopedia((draft) => {
draft.model.physical.hullYaw = 0;
draft.model.physical.turretYaw = 0;
draft.model.physical.gunPitch = 0;
draft.model.physical.yaw = 0;
draft.model.physical.pitch = 0;
draft.mode = 'model';
draft.areTanksAssigned = true;
@@ -129,6 +128,7 @@ export default function Page({ params }: { params: { id: string } }) {
{protagonist.turret.guns.map((thisGun, index) => {
return (
<ModuleButton
key={thisGun.id}
type="module"
module="gun"
tier={thisGun.tier}
@@ -146,29 +146,6 @@ export default function Page({ params }: { params: { id: string } }) {
}}
/>
);
// return (
// <Tooltip content={thisGun.name} key={thisGun.id}>
// <Button
// onClick={() => {
// mutateTankopedia((draft) => {
// if (!draft.areTanksAssigned) return;
// draft.protagonist.gun = thisGun;
// });
// }}
// variant={
// protagonist.gun.id === thisGun.id ? 'solid' : 'soft'
// }
// >
// <img
// src={asset('icons/modules/gun.webp')}
// width={32}
// height={32}
// />
// {TIER_ROMAN_NUMERALS[thisGun.tier]}
// </Button>
// </Tooltip>
// );
})}
</Flex>
</Flex>
+14
View File
@@ -0,0 +1,14 @@
export class EventManager<Type> {
private callbacks: Set<(data: Type) => void> = new Set();
on(callback: (data: Type) => void) {
this.callbacks.add(callback);
}
off(callback: (data: Type) => void) {
this.callbacks.delete(callback);
}
emit(data: Type) {
this.callbacks.forEach((callback) => callback(data));
}
}
+8
View File
@@ -0,0 +1,8 @@
import { EventManager } from './eventManager';
export interface ModelTransformEventData {
yaw?: number;
pitch: number;
}
export const modelTransformEvent = new EventManager<ModelTransformEventData>();
+4 -6
View File
@@ -29,9 +29,8 @@ type Tankopedia = {
};
model: {
physical: {
hullYaw: number;
turretYaw: number;
gunPitch: number;
yaw: number;
pitch: number;
};
visual: {
controlsEnabled: boolean;
@@ -74,9 +73,8 @@ export const useTankopedia = create<Tankopedia>()(
},
model: {
physical: {
gunPitch: 0,
hullYaw: 0,
turretYaw: 0,
yaw: 0,
pitch: 0,
},
visual: {
controlsEnabled: true,