From 5295aa37be04b9ef08c39ffc0c219f9ab7606f1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Mon, 22 Jan 2024 16:54:07 -0600 Subject: [PATCH] feat: hull origins --- scripts/buildAssets/definitions.ts | 11 +- .../tankopedia/[id]/components/Control.tsx | 20 ++- .../[id]/components/Model/TankSandbox.tsx | 1 + .../components/Model/components/Options.tsx | 12 +- .../components/ArmorHighlighting.tsx | 82 +++--------- .../components/ExternalModuleMask.tsx | 111 +++++++--------- .../TankArmor/components/SpacedArmorDepth.tsx | 123 ++++++++---------- .../components/Model/components/TankModel.tsx | 56 ++++---- .../[id]/components/QuickEquipments.tsx | 11 +- .../tankopedia/[id]/components/SceneProps.tsx | 4 +- .../ArmorMesh/components/Armor/index.tsx | 6 +- src/constants/axis.ts | 6 +- src/core/blitzkrieg/modelDefinitions.ts | 1 + src/stores/tankopedia.ts | 40 +++--- 14 files changed, 225 insertions(+), 259 deletions(-) diff --git a/scripts/buildAssets/definitions.ts b/scripts/buildAssets/definitions.ts index 93687ee11..f02e7070b 100644 --- a/scripts/buildAssets/definitions.ts +++ b/scripts/buildAssets/definitions.ts @@ -65,6 +65,7 @@ interface VehicleDefinitions { }; chassis: { [key: string]: { + hullPosition: string; armor: { leftTrack: | number @@ -294,14 +295,19 @@ export async function buildDefinitions(production: boolean) { const tankDefinition = await readXMLDVPL<{ root: VehicleDefinitions }>( `${DATA}/${POI.vehicleDefinitions}/${nation}/${tankKey}.xml.dvpl`, ); + const defaultChassis = Object.values(tankDefinition.root.chassis).at( + -1, + )!; const turretOrigin = tankDefinition.root.hull.turretPositions.turret .split(' ') .map(Number) as Vector3Tuple; + const hullOrigin = defaultChassis.hullPosition + .split(' ') + .map(Number) as Vector3Tuple; const tankId = toUniqueId(nation, tank.id); const tankTags = tank.tags.split(' '); const hullArmor: ModelArmor = { thickness: {} }; - const trackArmorRaw = Object.values(tankDefinition.root.chassis).at(-1)! - .armor.leftTrack; + const trackArmorRaw = defaultChassis.armor.leftTrack; const equipment = tankDefinition.root.optDevicePreset; tankStringIdMap[`${nation}:${tankKey}`] = tankId; @@ -368,6 +374,7 @@ export async function buildDefinitions(production: boolean) { ? trackArmorRaw : trackArmorRaw['#text'], turretOrigin, + hullOrigin, turretRotation: tankDefinition.root.hull.turretInitialRotation ? { yaw: tankDefinition.root.hull.turretInitialRotation.yaw, diff --git a/src/app/tools/tankopedia/[id]/components/Control.tsx b/src/app/tools/tankopedia/[id]/components/Control.tsx index 349a595b6..28b60b6d1 100644 --- a/src/app/tools/tankopedia/[id]/components/Control.tsx +++ b/src/app/tools/tankopedia/[id]/components/Control.tsx @@ -7,7 +7,7 @@ import { applyPitchYawLimits } from '../../../../../core/blitz/applyPitchYawLimi import { modelDefinitions } from '../../../../../core/blitzkrieg/modelDefinitions'; import { Pose, poseEvent } from '../../../../../core/blitzkrieg/pose'; import { useAwait } from '../../../../../hooks/useAwait'; -import { useTankopediaTemporary } from '../../../../../stores/tankopedia'; +import { useTankopediaPersistent } from '../../../../../stores/tankopedia'; import { Duel } from '../page'; const poseDistances: Record = { @@ -34,6 +34,11 @@ export function Controls({ duel }: ControlsProps) { antagonistModelDefinition.turrets[duel.antagonist.turret.id]; const protagonistGunModelDefinition = protagonistTurretModelDefinition.guns[duel.protagonist.gun.id]; + const protagonistHullOrigin = new Vector3( + protagonistModelDefinition.hullOrigin[0], + protagonistModelDefinition.hullOrigin[1], + -protagonistModelDefinition.hullOrigin[2], + ); const protagonistTurretOrigin = new Vector3( protagonistModelDefinition.turretOrigin[0], protagonistModelDefinition.turretOrigin[1], @@ -45,6 +50,7 @@ export function Controls({ duel }: ControlsProps) { -protagonistTurretModelDefinition.gunOrigin[2], ); const antagonistGunHeight = + antagonistModelDefinition.hullOrigin[1] + antagonistModelDefinition.turretOrigin[1] + antagonistTurretModelDefinition.gunOrigin[1]; @@ -52,7 +58,7 @@ export function Controls({ duel }: ControlsProps) { camera.position.set(-4, 4, -16); orbitControls.current?.target.set(0, antagonistGunHeight / 2, 0); - const unsubscribeTankopedia = useTankopediaTemporary.subscribe( + const unsubscribeTankopedia = useTankopediaPersistent.subscribe( (state) => state.model.visual.controlsEnabled, (enabled) => { if (orbitControls.current) orbitControls.current.enabled = enabled; @@ -71,6 +77,7 @@ export function Controls({ duel }: ControlsProps) { camera.position .set(0, 0, 0) + .add(protagonistHullOrigin) .add(protagonistTurretOrigin) .add(protagonistGunOrigin) .add( @@ -81,7 +88,10 @@ export function Controls({ duel }: ControlsProps) { ), ); camera.lookAt( - protagonistTurretOrigin.clone().add(protagonistGunOrigin), + protagonistHullOrigin + .clone() + .add(protagonistTurretOrigin) + .add(protagonistGunOrigin), ); orbitControls.current?.target.set(0, antagonistGunHeight, 0); @@ -98,6 +108,7 @@ export function Controls({ duel }: ControlsProps) { camera.position .set(0, 0, 0) + .add(protagonistHullOrigin) .add(protagonistTurretOrigin) .add(protagonistGunOrigin) .add( @@ -109,6 +120,7 @@ export function Controls({ duel }: ControlsProps) { ); orbitControls.current?.target .set(0, 0, 0) + .add(protagonistHullOrigin) .add(protagonistTurretOrigin) .add(protagonistGunOrigin) .add( @@ -141,7 +153,7 @@ export function Controls({ duel }: ControlsProps) { return ( diff --git a/src/app/tools/tankopedia/[id]/components/Model/TankSandbox.tsx b/src/app/tools/tankopedia/[id]/components/Model/TankSandbox.tsx index 9b1dd80b1..a5ee79b4f 100644 --- a/src/app/tools/tankopedia/[id]/components/Model/TankSandbox.tsx +++ b/src/app/tools/tankopedia/[id]/components/Model/TankSandbox.tsx @@ -165,6 +165,7 @@ export function TankSandbox({ duel }: TankSandboxProps) { + {/* */} diff --git a/src/app/tools/tankopedia/[id]/components/Model/components/Options.tsx b/src/app/tools/tankopedia/[id]/components/Model/components/Options.tsx index ffd83e134..e6bcd152a 100644 --- a/src/app/tools/tankopedia/[id]/components/Model/components/Options.tsx +++ b/src/app/tools/tankopedia/[id]/components/Model/components/Options.tsx @@ -7,9 +7,9 @@ import { import { Button, DropdownMenu, Flex } from '@radix-ui/themes'; import { RefObject } from 'react'; import { Pose, poseEvent } from '../../../../../../../core/blitzkrieg/pose'; -import { +import mutateTankopediaPersistent, { mutateTankopediaTemporary, - useTankopediaTemporary, + useTankopediaPersistent, } from '../../../../../../../stores/tankopedia'; interface OptionsProps { @@ -18,10 +18,10 @@ interface OptionsProps { } export function Options({ isFullScreen, canvas }: OptionsProps) { - const showGrid = useTankopediaTemporary( + const showGrid = useTankopediaPersistent( (state) => state.model.visual.showGrid, ); - const greenPenetration = useTankopediaTemporary( + const greenPenetration = useTankopediaPersistent( (state) => state.model.visual.greenPenetration, ); const fullScreenAvailable = @@ -69,7 +69,7 @@ export function Options({ isFullScreen, canvas }: OptionsProps) { { - mutateTankopediaTemporary((draft) => { + mutateTankopediaPersistent((draft) => { draft.model.visual.showGrid = checked; }); }} @@ -80,7 +80,7 @@ export function Options({ isFullScreen, canvas }: OptionsProps) { { - mutateTankopediaTemporary((draft) => { + mutateTankopediaPersistent((draft) => { draft.model.visual.greenPenetration = checked; }); }} diff --git a/src/app/tools/tankopedia/[id]/components/Model/components/TankArmor/components/ArmorHighlighting.tsx b/src/app/tools/tankopedia/[id]/components/Model/components/TankArmor/components/ArmorHighlighting.tsx index d8608ddfb..9bedff0a8 100644 --- a/src/app/tools/tankopedia/[id]/components/Model/components/TankArmor/components/ArmorHighlighting.tsx +++ b/src/app/tools/tankopedia/[id]/components/Model/components/TankArmor/components/ArmorHighlighting.tsx @@ -2,11 +2,7 @@ import { useEffect, useRef } from 'react'; import { Euler, Group, Mesh, Vector3 } from 'three'; import { degToRad } from 'three/src/math/MathUtils'; import { ArmorMesh } from '../../../../../../../../../components/ArmorMesh'; -import { - X_AXIS, - Y_AXIS, - Z_AXIS, -} from '../../../../../../../../../constants/axis'; +import { I_HAT, J_HAT, K_HAT } from '../../../../../../../../../constants/axis'; import { ModelTransformEventData, modelTransformEvent, @@ -26,41 +22,28 @@ export function ArmorHighlighting({ duel }: ArmorHighlightingProps) { const wrapper = useRef(null); const modelDefinitions = useModelDefinitions(); const turretContainer = useRef(null); - const gunContainer = useRef(null); const initialTankopediaState = useTankopediaTemporary.getState(); useEffect(() => { + const hullOrigin = new Vector3( + tankModelDefinition.hullOrigin[0], + tankModelDefinition.hullOrigin[1], + -tankModelDefinition.hullOrigin[2], + ).applyAxisAngle(I_HAT, Math.PI / 2); 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); + ).applyAxisAngle(I_HAT, 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); + function handleModelTransform({ yaw }: ModelTransformEventData) { if (yaw === undefined) return; turretPosition .set(0, 0, 0) + .sub(hullOrigin) .sub(turretOrigin) .applyAxisAngle(new Vector3(0, 0, 1), yaw); turretRotation.set(0, 0, yaw); @@ -73,15 +56,15 @@ export function ArmorHighlighting({ duel }: ArmorHighlightingProps) { const initialRoll = -degToRad(tankModelDefinition.turretRotation.roll); turretPosition - .applyAxisAngle(X_AXIS, initialPitch) - .applyAxisAngle(Y_AXIS, initialRoll) - .applyAxisAngle(Z_AXIS, initialYaw); + .applyAxisAngle(I_HAT, initialPitch) + .applyAxisAngle(J_HAT, initialRoll) + .applyAxisAngle(K_HAT, initialYaw); turretRotation.x += initialPitch; turretRotation.y += initialRoll; turretRotation.z += initialYaw; } - turretPosition.add(turretOrigin); + turretPosition.add(turretOrigin).add(hullOrigin); turretContainer.current?.position.copy(turretPosition); turretContainer.current?.rotation.copy(turretRotation); } @@ -112,16 +95,16 @@ export function ArmorHighlighting({ duel }: ArmorHighlightingProps) { tankModelDefinition.turrets[duel.protagonist.turret.id]; const gunModelDefinition = turretModelDefinition.guns[duel.protagonist.gun.id]; + const hullOrigin = new Vector3( + tankModelDefinition.hullOrigin[0], + tankModelDefinition.hullOrigin[1], + -tankModelDefinition.hullOrigin[2], + ); 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); + ).applyAxisAngle(I_HAT, Math.PI / 2); const maxThickness = Math.max( tankModelDefinition.trackThickness, gunModelDefinition.barrelThickness, @@ -138,6 +121,7 @@ export function ArmorHighlighting({ duel }: ArmorHighlightingProps) { ref={wrapper} rotation={[-Math.PI / 2, 0, 0]} visible={initialTankopediaState.mode === 'armor'} + position={hullOrigin} > {armorNodes.map((node) => { const isHull = node.name.startsWith('hull_'); @@ -186,32 +170,6 @@ export function ArmorHighlighting({ duel }: ArmorHighlightingProps) { /> ); })} - - {armorNodes.map((node) => { - const isCurrentGun = node.name.startsWith( - `gun_${gunModelDefinition.model.toString().padStart(2, '0')}`, - ); - const isVisible = isCurrentGun; - const armorId = nameToArmorId(node.name); - const { spaced, thickness } = resolveArmor( - gunModelDefinition.armor, - armorId, - ); - - if (!isVisible || thickness === undefined || spaced) return null; - - return ( - - ); - })} - ); diff --git a/src/app/tools/tankopedia/[id]/components/Model/components/TankArmor/components/ExternalModuleMask.tsx b/src/app/tools/tankopedia/[id]/components/Model/components/TankArmor/components/ExternalModuleMask.tsx index 309001195..8e3dff784 100644 --- a/src/app/tools/tankopedia/[id]/components/Model/components/TankArmor/components/ExternalModuleMask.tsx +++ b/src/app/tools/tankopedia/[id]/components/Model/components/TankArmor/components/ExternalModuleMask.tsx @@ -2,11 +2,7 @@ import { memo, useEffect, useRef } from 'react'; import { Euler, Group, Mesh, Vector3 } from 'three'; import { degToRad } from 'three/src/math/MathUtils'; import { ArmorMeshExternalModuleMask } from '../../../../../../../../../components/ArmorMesh'; -import { - X_AXIS, - Y_AXIS, - Z_AXIS, -} from '../../../../../../../../../constants/axis'; +import { I_HAT, J_HAT, K_HAT } from '../../../../../../../../../constants/axis'; import { ModelTransformEventData, modelTransformEvent, @@ -38,16 +34,21 @@ export const ExternalModuleMask = memo(({ duel }) => { const initialTankopediaState = useTankopediaTemporary.getState(); useEffect(() => { + const hullOrigin = new Vector3( + tankModelDefinition.hullOrigin[0], + tankModelDefinition.hullOrigin[1], + -tankModelDefinition.hullOrigin[2], + ).applyAxisAngle(I_HAT, Math.PI / 2); const turretOrigin = new Vector3( tankModelDefinition.turretOrigin[0], tankModelDefinition.turretOrigin[1], -tankModelDefinition.turretOrigin[2], - ).applyAxisAngle(X_AXIS, Math.PI / 2); + ).applyAxisAngle(I_HAT, Math.PI / 2); const gunOrigin = new Vector3( turretModelDefinition.gunOrigin[0], turretModelDefinition.gunOrigin[1], -turretModelDefinition.gunOrigin[2], - ).applyAxisAngle(X_AXIS, Math.PI / 2); + ).applyAxisAngle(I_HAT, Math.PI / 2); const turretPosition = new Vector3(); const turretRotation = new Euler(); const gunPosition = new Vector3(); @@ -56,11 +57,13 @@ export const ExternalModuleMask = memo(({ duel }) => { function handleModelTransform({ yaw, pitch }: ModelTransformEventData) { gunPosition .set(0, 0, 0) + .sub(hullOrigin) .sub(turretOrigin) .sub(gunOrigin) - .applyAxisAngle(X_AXIS, pitch) + .applyAxisAngle(I_HAT, pitch) .add(gunOrigin) - .add(turretOrigin); + .add(turretOrigin) + .add(hullOrigin); gunRotation.set(pitch, 0, 0); gunContainer.current?.position.copy(gunPosition); gunContainer.current?.rotation.copy(gunRotation); @@ -70,6 +73,7 @@ export const ExternalModuleMask = memo(({ duel }) => { turretPosition .set(0, 0, 0) .sub(turretOrigin) + .sub(hullOrigin) .applyAxisAngle(new Vector3(0, 0, 1), yaw); turretRotation.set(0, 0, yaw); @@ -81,15 +85,15 @@ export const ExternalModuleMask = memo(({ duel }) => { const initialRoll = -degToRad(tankModelDefinition.turretRotation.roll); turretPosition - .applyAxisAngle(X_AXIS, initialPitch) - .applyAxisAngle(Y_AXIS, initialRoll) - .applyAxisAngle(Z_AXIS, initialYaw); + .applyAxisAngle(I_HAT, initialPitch) + .applyAxisAngle(J_HAT, initialRoll) + .applyAxisAngle(K_HAT, initialYaw); turretRotation.x += initialPitch; turretRotation.y += initialRoll; turretRotation.z += initialYaw; } - turretPosition.add(turretOrigin); + turretPosition.add(turretOrigin).add(hullOrigin); turretContainer.current?.position.copy(turretPosition); turretContainer.current?.rotation.copy(turretRotation); } @@ -133,16 +137,16 @@ export const ExternalModuleMask = memo(({ duel }) => { }) .filter(Boolean), ); + const hullOrigin = new Vector3( + tankModelDefinition.hullOrigin[0], + tankModelDefinition.hullOrigin[1], + -tankModelDefinition.hullOrigin[2], + ).applyAxisAngle(I_HAT, Math.PI / 2); 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); + ).applyAxisAngle(I_HAT, Math.PI / 2); return ( (({ duel }) => { rotation={[-Math.PI / 2, 0, 0]} visible={initialTankopediaState.mode === 'armor'} > - {armorNodes.map((node) => { - const isHull = node.name.startsWith('hull_'); - const isVisible = isHull; - const armorId = nameToArmorId(node.name); - const { thickness } = resolveArmor(tankModelDefinition.armor, armorId); + + {armorNodes.map((node) => { + const isHull = node.name.startsWith('hull_'); + const isVisible = isHull; + const armorId = nameToArmorId(node.name); + const { thickness } = resolveArmor( + tankModelDefinition.armor, + armorId, + ); - if (!isVisible || thickness === undefined) return null; + if (!isVisible || thickness === undefined) return null; - return ( - - ); - })} + return ( + + ); + })} + {modelNodes.map((node) => { const isWheel = node.name.startsWith('chassis_wheel_'); @@ -185,37 +194,15 @@ export const ExternalModuleMask = memo(({ duel }) => { })} - {armorNodes.map((node) => { - const isCurrentTurret = node.name.startsWith( - `turret_${turretModelDefinition.model.toString().padStart(2, '0')}`, - ); - const isVisible = isCurrentTurret; - const armorId = nameToArmorId(node.name); - const { thickness } = resolveArmor( - turretModelDefinition.armor, - armorId, - ); - - if (!isVisible || thickness === undefined) return null; - - return ( - - ); - })} - + {armorNodes.map((node) => { - const isCurrentGun = node.name.startsWith( - `gun_${gunModelDefinition.model.toString().padStart(2, '0')}`, + const isCurrentTurret = node.name.startsWith( + `turret_${turretModelDefinition.model.toString().padStart(2, '0')}`, ); - const isVisible = isCurrentGun; + const isVisible = isCurrentTurret; const armorId = nameToArmorId(node.name); const { thickness } = resolveArmor( - gunModelDefinition.armor, + turretModelDefinition.armor, armorId, ); @@ -226,11 +213,13 @@ export const ExternalModuleMask = memo(({ duel }) => { exclude key={node.uuid} geometry={(node as Mesh).geometry} - position={turretOrigin.clone().add(gunOrigin)} + position={turretOrigin} /> ); })} + + {modelNodes.map((node) => { const isCurrentGun = node.name === diff --git a/src/app/tools/tankopedia/[id]/components/Model/components/TankArmor/components/SpacedArmorDepth.tsx b/src/app/tools/tankopedia/[id]/components/Model/components/TankArmor/components/SpacedArmorDepth.tsx index 9f7d2903a..5646d17c8 100644 --- a/src/app/tools/tankopedia/[id]/components/Model/components/TankArmor/components/SpacedArmorDepth.tsx +++ b/src/app/tools/tankopedia/[id]/components/Model/components/TankArmor/components/SpacedArmorDepth.tsx @@ -2,11 +2,7 @@ import { memo, useEffect, useRef } from 'react'; import { Euler, Group, Mesh, Vector3 } from 'three'; import { degToRad } from 'three/src/math/MathUtils'; import { ArmorMeshSpacedArmorDepth } from '../../../../../../../../../components/ArmorMesh/components/SpacedArmorDepth'; -import { - X_AXIS, - Y_AXIS, - Z_AXIS, -} from '../../../../../../../../../constants/axis'; +import { I_HAT, J_HAT, K_HAT } from '../../../../../../../../../constants/axis'; import { ModelTransformEventData, modelTransformEvent, @@ -44,16 +40,21 @@ export const SpacedArmorDepth = memo(({ duel }) => { useEffect(() => { if (!modelDefinitions) return; + const hullOrigin = new Vector3( + tankModelDefinition.hullOrigin[0], + tankModelDefinition.hullOrigin[1], + -tankModelDefinition.hullOrigin[2], + ).applyAxisAngle(I_HAT, Math.PI / 2); const turretOrigin = new Vector3( tankModelDefinition.turretOrigin[0], tankModelDefinition.turretOrigin[1], -tankModelDefinition.turretOrigin[2], - ).applyAxisAngle(X_AXIS, Math.PI / 2); + ).applyAxisAngle(I_HAT, Math.PI / 2); const gunOrigin = new Vector3( turretModelDefinition.gunOrigin[0], turretModelDefinition.gunOrigin[1], -turretModelDefinition.gunOrigin[2], - ).applyAxisAngle(X_AXIS, Math.PI / 2); + ).applyAxisAngle(I_HAT, Math.PI / 2); const turretPosition = new Vector3(); const turretRotation = new Euler(); const gunPosition = new Vector3(); @@ -62,11 +63,13 @@ export const SpacedArmorDepth = memo(({ duel }) => { function handleModelTransform({ yaw, pitch }: ModelTransformEventData) { gunPosition .set(0, 0, 0) + .sub(hullOrigin) .sub(turretOrigin) .sub(gunOrigin) - .applyAxisAngle(X_AXIS, pitch) + .applyAxisAngle(I_HAT, pitch) .add(gunOrigin) - .add(turretOrigin); + .add(turretOrigin) + .add(hullOrigin); gunRotation.set(pitch, 0, 0); gunContainer.current?.position.copy(gunPosition); gunContainer.current?.rotation.copy(gunRotation); @@ -75,6 +78,7 @@ export const SpacedArmorDepth = memo(({ duel }) => { turretPosition .set(0, 0, 0) + .sub(hullOrigin) .sub(turretOrigin) .applyAxisAngle(new Vector3(0, 0, 1), yaw); turretRotation.set(0, 0, yaw); @@ -87,15 +91,15 @@ export const SpacedArmorDepth = memo(({ duel }) => { const initialRoll = -degToRad(tankModelDefinition.turretRotation.roll); turretPosition - .applyAxisAngle(X_AXIS, initialPitch) - .applyAxisAngle(Y_AXIS, initialRoll) - .applyAxisAngle(Z_AXIS, initialYaw); + .applyAxisAngle(I_HAT, initialPitch) + .applyAxisAngle(J_HAT, initialRoll) + .applyAxisAngle(K_HAT, initialYaw); turretRotation.x += initialPitch; turretRotation.y += initialRoll; turretRotation.z += initialYaw; } - turretPosition.add(turretOrigin); + turretPosition.add(turretOrigin).add(hullOrigin); turretContainer.current?.position.copy(turretPosition); turretContainer.current?.rotation.copy(turretRotation); } @@ -139,16 +143,16 @@ export const SpacedArmorDepth = memo(({ duel }) => { }) .filter(Boolean), ); + const hullOrigin = new Vector3( + tankModelDefinition.hullOrigin[0], + tankModelDefinition.hullOrigin[1], + -tankModelDefinition.hullOrigin[2], + ).applyAxisAngle(I_HAT, Math.PI / 2); 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); + ).applyAxisAngle(I_HAT, Math.PI / 2); return ( (({ duel }) => { rotation={[-Math.PI / 2, 0, 0]} visible={initialTankopediaState.mode === 'armor'} > - {armorNodes.map((node) => { - const isHull = node.name.startsWith('hull_'); - const isVisible = isHull; - const armorId = nameToArmorId(node.name); - const { spaced, thickness } = resolveArmor( - tankModelDefinition.armor, - armorId, - ); + + {armorNodes.map((node) => { + const isHull = node.name.startsWith('hull_'); + const isVisible = isHull; + const armorId = nameToArmorId(node.name); + const { spaced, thickness } = resolveArmor( + tankModelDefinition.armor, + armorId, + ); - if (!isVisible || thickness === undefined) return null; + if (!isVisible || thickness === undefined) return null; - return ( - - ); - })} + return ( + + ); + })} + {modelNodes.map((node) => { const isWheel = node.name.startsWith('chassis_wheel_'); const isTrack = node.name.startsWith('chassis_track_'); @@ -195,40 +201,15 @@ export const SpacedArmorDepth = memo(({ duel }) => { })} - {armorNodes.map((node) => { - const isCurrentTurret = node.name.startsWith( - `turret_${turretModelDefinition.model.toString().padStart(2, '0')}`, - ); - const isVisible = isCurrentTurret; - const armorId = nameToArmorId(node.name); - const { spaced, thickness } = resolveArmor( - turretModelDefinition.armor, - armorId, - ); - - if (!isVisible || thickness === undefined) return null; - - return ( - - ); - })} - + {armorNodes.map((node) => { - const isCurrentGun = node.name.startsWith( - `gun_${gunModelDefinition.model.toString().padStart(2, '0')}`, + const isCurrentTurret = node.name.startsWith( + `turret_${turretModelDefinition.model.toString().padStart(2, '0')}`, ); - const isVisible = isCurrentGun; + const isVisible = isCurrentTurret; const armorId = nameToArmorId(node.name); const { spaced, thickness } = resolveArmor( - gunModelDefinition.armor, + turretModelDefinition.armor, armorId, ); @@ -242,10 +223,12 @@ export const SpacedArmorDepth = memo(({ duel }) => { maxThickness={maxThickness} key={node.uuid} geometry={(node as Mesh).geometry} - position={turretOrigin.clone().add(gunOrigin)} + position={turretOrigin} /> ); })} + + {modelNodes.map((node) => { const isCurrentGun = node.name === 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 2ec51a609..bd4acd060 100644 --- a/src/app/tools/tankopedia/[id]/components/Model/components/TankModel.tsx +++ b/src/app/tools/tankopedia/[id]/components/Model/components/TankModel.tsx @@ -9,7 +9,7 @@ import { Vector3, } from 'three'; import { degToRad } from 'three/src/math/MathUtils'; -import { X_AXIS, Y_AXIS, Z_AXIS } from '../../../../../../../constants/axis'; +import { I_HAT, J_HAT, K_HAT } from '../../../../../../../constants/axis'; import { applyPitchYawLimits } from '../../../../../../../core/blitz/applyPitchYawLimits'; import { ModelDefinitions, @@ -22,7 +22,7 @@ import { import { resolveJsxTree } from '../../../../../../../core/blitzkrieg/resolveJsxTree'; import { normalizeAnglePI } from '../../../../../../../core/math/normalizeAngle180'; import { useModel } from '../../../../../../../hooks/useModel'; -import { +import mutateTankopediaPersistent, { mutateTankopediaTemporary, useTankopediaTemporary, } from '../../../../../../../stores/tankopedia'; @@ -52,16 +52,21 @@ export const TankModel = memo(({ duel: { protagonist } }: TankModelProps) => { useEffect(() => { if (!awaitedModelDefinitions) return; + const hullOrigin = new Vector3( + tankModelDefinition.hullOrigin[0], + tankModelDefinition.hullOrigin[1], + -tankModelDefinition.hullOrigin[2], + ).applyAxisAngle(I_HAT, Math.PI / 2); const turretOrigin = new Vector3( tankModelDefinition.turretOrigin[0], tankModelDefinition.turretOrigin[1], -tankModelDefinition.turretOrigin[2], - ).applyAxisAngle(X_AXIS, Math.PI / 2); + ).applyAxisAngle(I_HAT, Math.PI / 2); const gunOrigin = new Vector3( turretModelDefinition.gunOrigin[0], turretModelDefinition.gunOrigin[1], -turretModelDefinition.gunOrigin[2], - ).applyAxisAngle(X_AXIS, Math.PI / 2); + ).applyAxisAngle(I_HAT, Math.PI / 2); const turretPosition = new Vector3(); const turretRotation = new Euler(); const gunPosition = new Vector3(); @@ -70,11 +75,13 @@ export const TankModel = memo(({ duel: { protagonist } }: TankModelProps) => { function handleModelTransform({ yaw, pitch }: ModelTransformEventData) { gunPosition .set(0, 0, 0) + .sub(hullOrigin) .sub(turretOrigin) .sub(gunOrigin) - .applyAxisAngle(X_AXIS, pitch) + .applyAxisAngle(I_HAT, pitch) .add(gunOrigin) - .add(turretOrigin); + .add(turretOrigin) + .add(hullOrigin); gunRotation.set(pitch, 0, 0); gunContainer.current?.position.copy(gunPosition); gunContainer.current?.rotation.copy(gunRotation); @@ -83,6 +90,7 @@ export const TankModel = memo(({ duel: { protagonist } }: TankModelProps) => { turretPosition .set(0, 0, 0) + .sub(hullOrigin) .sub(turretOrigin) .applyAxisAngle(new Vector3(0, 0, 1), yaw); turretRotation.set(0, 0, yaw); @@ -95,15 +103,15 @@ export const TankModel = memo(({ duel: { protagonist } }: TankModelProps) => { const initialRoll = -degToRad(tankModelDefinition.turretRotation.roll); turretPosition - .applyAxisAngle(X_AXIS, initialPitch) - .applyAxisAngle(Y_AXIS, initialRoll) - .applyAxisAngle(Z_AXIS, initialYaw); + .applyAxisAngle(I_HAT, initialPitch) + .applyAxisAngle(J_HAT, initialRoll) + .applyAxisAngle(K_HAT, initialYaw); turretRotation.x += initialPitch; turretRotation.y += initialRoll; turretRotation.z += initialYaw; } - turretPosition.add(turretOrigin); + turretPosition.add(turretOrigin).add(hullOrigin); turretContainer.current?.position.copy(turretPosition); turretContainer.current?.rotation.copy(turretRotation); } @@ -137,7 +145,7 @@ export const TankModel = memo(({ duel: { protagonist } }: TankModelProps) => { event.stopPropagation(); if (isTrack) { - mutateTankopediaTemporary((draft) => { + mutateTankopediaPersistent((draft) => { draft.model.visual.controlsEnabled = false; }); @@ -159,7 +167,7 @@ export const TankModel = memo(({ duel: { protagonist } }: TankModelProps) => { material.normalMap?.offset.add(offset); } function handlePointerUp() { - mutateTankopediaTemporary((draft) => { + mutateTankopediaPersistent((draft) => { draft.model.visual.controlsEnabled = true; }); window.removeEventListener('pointermove', handlePointerMove); @@ -201,7 +209,7 @@ export const TankModel = memo(({ duel: { protagonist } }: TankModelProps) => { yaw = physical.yaw; pitch = physical.pitch; - mutateTankopediaTemporary((draft) => { + mutateTankopediaPersistent((draft) => { draft.model.visual.controlsEnabled = false; }); window.addEventListener('pointermove', handlePointerMove); @@ -218,10 +226,12 @@ export const TankModel = memo(({ duel: { protagonist } }: TankModelProps) => { modelTransformEvent.emit({ pitch, yaw }); } function handlePointerUp() { - mutateTankopediaTemporary((state) => { - state.model.visual.controlsEnabled = true; - state.model.pose.pitch = normalizeAnglePI(pitch); - state.model.pose.yaw = normalizeAnglePI(yaw); + mutateTankopediaTemporary((draft) => { + draft.model.pose.pitch = normalizeAnglePI(pitch); + draft.model.pose.yaw = normalizeAnglePI(yaw); + }); + mutateTankopediaPersistent((draft) => { + draft.model.visual.controlsEnabled = true; }); window.removeEventListener('pointermove', handlePointerMove); window.removeEventListener('pointerup', handlePointerUp); @@ -263,7 +273,7 @@ export const TankModel = memo(({ duel: { protagonist } }: TankModelProps) => { function handlePointerDown(event: ThreeEvent) { event.stopPropagation(); - mutateTankopediaTemporary((draft) => { + mutateTankopediaPersistent((draft) => { draft.model.visual.controlsEnabled = false; }); pitch = physical.pitch; @@ -281,10 +291,12 @@ export const TankModel = memo(({ duel: { protagonist } }: TankModelProps) => { modelTransformEvent.emit({ pitch, yaw }); } function handlePointerUp() { - mutateTankopediaTemporary((state) => { - state.model.visual.controlsEnabled = true; - state.model.pose.pitch = normalizeAnglePI(pitch); - state.model.pose.yaw = normalizeAnglePI(yaw); + mutateTankopediaPersistent((draft) => { + draft.model.visual.controlsEnabled = true; + }); + mutateTankopediaTemporary((draft) => { + draft.model.pose.pitch = normalizeAnglePI(pitch); + draft.model.pose.yaw = normalizeAnglePI(yaw); }); window.removeEventListener('pointermove', handlePointerMove); window.removeEventListener('pointerup', handlePointerUp); diff --git a/src/app/tools/tankopedia/[id]/components/QuickEquipments.tsx b/src/app/tools/tankopedia/[id]/components/QuickEquipments.tsx index 52b1fe972..5f69e20ad 100644 --- a/src/app/tools/tankopedia/[id]/components/QuickEquipments.tsx +++ b/src/app/tools/tankopedia/[id]/components/QuickEquipments.tsx @@ -1,12 +1,11 @@ import { Flex } from '@radix-ui/themes'; import { ModuleButton } from '../../../../../components/ModuleButton'; -import { - mutateTankopediaTemporary, - useTankopediaTemporary, +import mutateTankopediaPersistent, { + useTankopediaPersistent, } from '../../../../../stores/tankopedia'; export function QuickEquipments() { - const equipment = useTankopediaTemporary((state) => state.model.equipment); + const equipment = useTankopediaPersistent((state) => state.model.equipment); return ( { - mutateTankopediaTemporary((draft) => { + mutateTankopediaPersistent((draft) => { draft.model.equipment.calibratedShells = !draft.model.equipment.calibratedShells; }); @@ -39,7 +38,7 @@ export function QuickEquipments() { rowChild equipment={110} onClick={() => { - mutateTankopediaTemporary((draft) => { + mutateTankopediaPersistent((draft) => { draft.model.equipment.enhancedArmor = !draft.model.equipment.enhancedArmor; }); diff --git a/src/app/tools/tankopedia/[id]/components/SceneProps.tsx b/src/app/tools/tankopedia/[id]/components/SceneProps.tsx index 527254d2c..49cd9a8ec 100644 --- a/src/app/tools/tankopedia/[id]/components/SceneProps.tsx +++ b/src/app/tools/tankopedia/[id]/components/SceneProps.tsx @@ -1,9 +1,9 @@ import { Color } from 'three'; import InfiniteGridHelper from '../../../../../components/InfiniteGridHelper'; -import { useTankopediaTemporary } from '../../../../../stores/tankopedia'; +import { useTankopediaPersistent } from '../../../../../stores/tankopedia'; export function SceneProps() { - const showGrid = useTankopediaTemporary( + const showGrid = useTankopediaPersistent( (state) => state.model.visual.showGrid, ); diff --git a/src/components/ArmorMesh/components/Armor/index.tsx b/src/components/ArmorMesh/components/Armor/index.tsx index cbfdc5804..1af404c25 100644 --- a/src/components/ArmorMesh/components/Armor/index.tsx +++ b/src/components/ArmorMesh/components/Armor/index.tsx @@ -7,7 +7,7 @@ import { Duel } from '../../../../app/tools/tankopedia/[id]/page'; import { canSplash } from '../../../../core/blitz/canSplash'; import { isExplosive } from '../../../../core/blitz/isExplosive'; import { resolveNearPenetration } from '../../../../core/blitz/resolveNearPenetration'; -import { useTankopediaTemporary } from '../../../../stores/tankopedia'; +import { useTankopediaPersistent } from '../../../../stores/tankopedia'; import { externalModuleMaskRenderTarget } from '../ExternalModuleMask'; import { spacedArmorDepthRenderTarget } from '../SpacedArmorDepth'; import fragmentShader from './shaders/fragment.glsl'; @@ -25,10 +25,10 @@ export function ArmorMesh({ duel, ...props }: ArmorMeshProps) { - const equipment = useTankopediaTemporary((draft) => draft.model.equipment); + const equipment = useTankopediaPersistent((draft) => draft.model.equipment); const camera = useThree((state) => state.camera); const { shell } = duel.antagonist; - const greenPenetration = useTankopediaTemporary( + const greenPenetration = useTankopediaPersistent( (state) => state.model.visual.greenPenetration, ); const material = useRef(null); diff --git a/src/constants/axis.ts b/src/constants/axis.ts index ca2463dd5..9d600d2be 100644 --- a/src/constants/axis.ts +++ b/src/constants/axis.ts @@ -1,5 +1,5 @@ import { Vector3 } from 'three'; -export const X_AXIS = new Vector3(1, 0, 0); -export const Y_AXIS = new Vector3(0, 1, 0); -export const Z_AXIS = new Vector3(0, 0, 1); +export const I_HAT = new Vector3(1, 0, 0); +export const J_HAT = new Vector3(0, 1, 0); +export const K_HAT = new Vector3(0, 0, 1); diff --git a/src/core/blitzkrieg/modelDefinitions.ts b/src/core/blitzkrieg/modelDefinitions.ts index 9d30914e2..27c29c9e6 100644 --- a/src/core/blitzkrieg/modelDefinitions.ts +++ b/src/core/blitzkrieg/modelDefinitions.ts @@ -12,6 +12,7 @@ interface ModelDefinition { armor: ModelArmor; trackThickness: number; turretOrigin: Vector3Tuple; + hullOrigin: Vector3Tuple; turretRotation?: InitialTurretRotation; turrets: Record; } diff --git a/src/stores/tankopedia.ts b/src/stores/tankopedia.ts index de73ad424..53be5d307 100644 --- a/src/stores/tankopedia.ts +++ b/src/stores/tankopedia.ts @@ -21,6 +21,17 @@ export interface DuelMember { shell: ShellDefinition; } interface TankopediaPersistent { + model: { + equipment: { + enhancedArmor: boolean; + calibratedShells: boolean; + }; + visual: { + controlsEnabled: boolean; + showGrid: boolean; + greenPenetration: boolean; + }; + }; sort: { by: TankopediaSortBy; direction: TankopediaSortDirection; @@ -43,15 +54,6 @@ interface TankopediaTemporary { yaw: number; pitch: number; }; - equipment: { - enhancedArmor: boolean; - calibratedShells: boolean; - }; - visual: { - controlsEnabled: boolean; - showGrid: boolean; - greenPenetration: boolean; - }; }; equipmentMatrix: EquipmentMatrix; mode: TankopediaMode; @@ -60,6 +62,17 @@ interface TankopediaTemporary { export const useTankopediaPersistent = create()( persist( subscribeWithSelector(() => ({ + model: { + equipment: { + enhancedArmor: false, + calibratedShells: false, + }, + visual: { + controlsEnabled: true, + showGrid: true, + greenPenetration: false, + }, + }, sort: { by: 'tier', direction: 'descending', @@ -84,15 +97,6 @@ export const useTankopediaTemporary = create()( yaw: 0, pitch: 0, }, - equipment: { - enhancedArmor: false, - calibratedShells: false, - }, - visual: { - controlsEnabled: true, - showGrid: true, - greenPenetration: false, - }, }, equipmentMatrix: [ [1, 0, 0],