From 237f676af88bca6f5cd1aefa399f6e46fca904c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Sat, 13 Jan 2024 18:24:04 -0600 Subject: [PATCH] feat: async updates --- .vscode/settings.json | 1 + .../[id]/components/Model/TankDisplay.tsx | 18 +- .../components/Model/components/TankArmor.tsx | 127 +++++--- .../components/Model/components/TankModel.tsx | 283 +++++++----------- .../[id]/components/RotationInputs.tsx | 62 +--- src/app/tools/tankopedia/[id]/page.tsx | 29 +- src/core/blitzkrieg/eventManager.ts | 14 + src/core/blitzkrieg/modelTransform.ts | 8 + src/stores/tankopedia.ts | 10 +- 9 files changed, 248 insertions(+), 304 deletions(-) create mode 100644 src/core/blitzkrieg/eventManager.ts create mode 100644 src/core/blitzkrieg/modelTransform.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index eed8bbe76..ba2131fea 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -51,6 +51,7 @@ "lods", "metallicness", "MIPMAPCOUNT", + "modeltransform", "MURICA", "NEGATIVEX", "NEGATIVEY", diff --git a/src/app/tools/tankopedia/[id]/components/Model/TankDisplay.tsx b/src/app/tools/tankopedia/[id]/components/Model/TankDisplay.tsx index b67e006e8..55acd2540 100644 --- a/src/app/tools/tankopedia/[id]/components/Model/TankDisplay.tsx +++ b/src/app/tools/tankopedia/[id]/components/Model/TankDisplay.tsx @@ -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(null); const awaitedModelDefinitions = use(modelDefinitions); const canvasWrapper = useRef(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() { { - router.push(`#${mode}`); - mutateTankopedia((draft) => { draft.mode = mode as TankopediaMode; }); diff --git a/src/app/tools/tankopedia/[id]/components/Model/components/TankArmor.tsx b/src/app/tools/tankopedia/[id]/components/Model/components/TankArmor.tsx index 27cbcbb20..d7b005f2c 100644 --- a/src/app/tools/tankopedia/[id]/components/Model/components/TankArmor.tsx +++ b/src/app/tools/tankopedia/[id]/components/Model/components/TankArmor.tsx @@ -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(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(null); + const gunContainer = useRef(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 ( {armorNodes.map((node) => { const isHull = node.name.startsWith('hull_'); @@ -142,7 +203,7 @@ export function TankArmor({ ...props }: TankArmorProps) { ); })} - + {armorNodes.map((node) => { const isCurrentTurret = node.name.startsWith( `turret_${turretModelDefinition.model @@ -173,15 +234,7 @@ export function TankArmor({ ...props }: TankArmorProps) { /> ); })} - + {armorNodes.map((node) => { const isCurrentGun = node.name.startsWith( `gun_${gunModelDefinition.model.toString().padStart(2, '0')}`, diff --git a/src/app/tools/tankopedia/[id]/components/Model/components/TankModel.tsx b/src/app/tools/tankopedia/[id]/components/Model/components/TankModel.tsx index 8217d1b74..25920b008 100644 --- a/src/app/tools/tankopedia/[id]/components/Model/components/TankModel.tsx +++ b/src/app/tools/tankopedia/[id]/components/Model/components/TankModel.tsx @@ -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(null); const turretContainer = useRef(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 ( - + {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) { 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() { ); })} - + {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) { 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() { ); })} - + {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) { 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); diff --git a/src/app/tools/tankopedia/[id]/components/RotationInputs.tsx b/src/app/tools/tankopedia/[id]/components/RotationInputs.tsx index 71f2257db..ba1e680ce 100644 --- a/src/app/tools/tankopedia/[id]/components/RotationInputs.tsx +++ b/src/app/tools/tankopedia/[id]/components/RotationInputs.tsx @@ -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(null); - const hullYawInput = useRef(null); const gunPitchInput = useRef(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() { - - Hull - { - 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' }} - /> - ° - - Turret { 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() { > Gun { 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), diff --git a/src/app/tools/tankopedia/[id]/page.tsx b/src/app/tools/tankopedia/[id]/page.tsx index 975393200..377abec9b 100644 --- a/src/app/tools/tankopedia/[id]/page.tsx +++ b/src/app/tools/tankopedia/[id]/page.tsx @@ -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 ( ); - - // return ( - // - // - // - // ); })} diff --git a/src/core/blitzkrieg/eventManager.ts b/src/core/blitzkrieg/eventManager.ts new file mode 100644 index 000000000..8486e920c --- /dev/null +++ b/src/core/blitzkrieg/eventManager.ts @@ -0,0 +1,14 @@ +export class EventManager { + 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)); + } +} diff --git a/src/core/blitzkrieg/modelTransform.ts b/src/core/blitzkrieg/modelTransform.ts new file mode 100644 index 000000000..1b5487d7f --- /dev/null +++ b/src/core/blitzkrieg/modelTransform.ts @@ -0,0 +1,8 @@ +import { EventManager } from './eventManager'; + +export interface ModelTransformEventData { + yaw?: number; + pitch: number; +} + +export const modelTransformEvent = new EventManager(); diff --git a/src/stores/tankopedia.ts b/src/stores/tankopedia.ts index 186c12245..67d43c4fe 100644 --- a/src/stores/tankopedia.ts +++ b/src/stores/tankopedia.ts @@ -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()( }, model: { physical: { - gunPitch: 0, - hullYaw: 0, - turretYaw: 0, + yaw: 0, + pitch: 0, }, visual: { controlsEnabled: true,