feat: hull origins

This commit is contained in:
TrèsAbhi committed 2024-01-22 16:54:07 -06:00
1 parent 3e12d6bb9d
commit 5295aa37be
14 files changed
+225 -259

No files matched your search

+9 -2
View File
@@ -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,
@@ -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<Pose, number> = {
@@ -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 (
<OrbitControls
ref={orbitControls}
enabled={useTankopediaTemporary.getState().model.visual.controlsEnabled}
enabled={useTankopediaPersistent.getState().model.visual.controlsEnabled}
rotateSpeed={0.25}
enableDamping={false}
/>
@@ -165,6 +165,7 @@ export function TankSandbox({ duel }: TankSandboxProps) {
<Lighting duel={duel} />
<TankModel duel={duel} />
<TankArmor duel={duel} />
{/* <SpacedArmorDepth duel={duel} /> */}
</Suspense>
</Canvas>
</div>
@@ -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) {
<DropdownMenu.CheckboxItem
checked={showGrid}
onCheckedChange={(checked) => {
mutateTankopediaTemporary((draft) => {
mutateTankopediaPersistent((draft) => {
draft.model.visual.showGrid = checked;
});
}}
@@ -80,7 +80,7 @@ export function Options({ isFullScreen, canvas }: OptionsProps) {
<DropdownMenu.CheckboxItem
checked={greenPenetration}
onCheckedChange={(checked) => {
mutateTankopediaTemporary((draft) => {
mutateTankopediaPersistent((draft) => {
draft.model.visual.greenPenetration = checked;
});
}}
@@ -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<Group>(null);
const modelDefinitions = useModelDefinitions();
const turretContainer = useRef<Group>(null);
const gunContainer = useRef<Group>(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) {
/>
);
})}
<group ref={gunContainer}>
{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 (
<ArmorMesh
maxThickness={maxThickness}
duel={duel}
key={node.uuid}
geometry={(node as Mesh).geometry}
position={turretOrigin.clone().add(gunOrigin)}
thickness={thickness}
/>
);
})}
</group>
</group>
</group>
);
@@ -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<ExternalModuleMaskProps>(({ 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<ExternalModuleMaskProps>(({ 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<ExternalModuleMaskProps>(({ 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<ExternalModuleMaskProps>(({ 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<ExternalModuleMaskProps>(({ 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 (
<group
@@ -150,22 +154,27 @@ export const ExternalModuleMask = memo<ExternalModuleMaskProps>(({ 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);
<group position={hullOrigin}>
{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 (
<ArmorMeshExternalModuleMask
exclude
key={node.uuid}
geometry={(node as Mesh).geometry}
/>
);
})}
return (
<ArmorMeshExternalModuleMask
exclude
key={node.uuid}
geometry={(node as Mesh).geometry}
/>
);
})}
</group>
{modelNodes.map((node) => {
const isWheel = node.name.startsWith('chassis_wheel_');
@@ -185,37 +194,15 @@ export const ExternalModuleMask = memo<ExternalModuleMaskProps>(({ duel }) => {
})}
<group ref={turretContainer}>
{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 (
<ArmorMeshExternalModuleMask
exclude
key={node.uuid}
geometry={(node as Mesh).geometry}
position={turretOrigin}
/>
);
})}
<group ref={gunContainer}>
<group position={hullOrigin}>
{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<ExternalModuleMaskProps>(({ duel }) => {
exclude
key={node.uuid}
geometry={(node as Mesh).geometry}
position={turretOrigin.clone().add(gunOrigin)}
position={turretOrigin}
/>
);
})}
</group>
<group ref={gunContainer}>
{modelNodes.map((node) => {
const isCurrentGun =
node.name ===
@@ -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<SpacedArmorDepthProps>(({ 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<SpacedArmorDepthProps>(({ 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<SpacedArmorDepthProps>(({ 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<SpacedArmorDepthProps>(({ 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<SpacedArmorDepthProps>(({ 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 (
<group
@@ -156,28 +160,30 @@ export const SpacedArmorDepth = memo<SpacedArmorDepthProps>(({ 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,
);
<group position={hullOrigin}>
{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 (
<ArmorMeshSpacedArmorDepth
include={spaced}
isExternalModule={false}
thickness={thickness}
maxThickness={maxThickness}
key={node.uuid}
geometry={(node as Mesh).geometry}
/>
);
})}
return (
<ArmorMeshSpacedArmorDepth
include={spaced}
isExternalModule={false}
thickness={thickness}
maxThickness={maxThickness}
key={node.uuid}
geometry={(node as Mesh).geometry}
/>
);
})}
</group>
{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<SpacedArmorDepthProps>(({ duel }) => {
})}
<group ref={turretContainer}>
{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 (
<ArmorMeshSpacedArmorDepth
include={spaced}
isExternalModule={false}
thickness={thickness}
maxThickness={maxThickness}
key={node.uuid}
geometry={(node as Mesh).geometry}
position={turretOrigin}
/>
);
})}
<group ref={gunContainer}>
<group position={hullOrigin}>
{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<SpacedArmorDepthProps>(({ duel }) => {
maxThickness={maxThickness}
key={node.uuid}
geometry={(node as Mesh).geometry}
position={turretOrigin.clone().add(gunOrigin)}
position={turretOrigin}
/>
);
})}
</group>
<group ref={gunContainer}>
{modelNodes.map((node) => {
const isCurrentGun =
node.name ===
@@ -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<PointerEvent>) {
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);
@@ -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 (
<Flex
@@ -26,7 +25,7 @@ export function QuickEquipments() {
rowChild
equipment={103}
onClick={() => {
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;
});
@@ -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,
);
@@ -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<ShaderMaterial>(null);
+3 -3
View File
@@ -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);
+1
View File
@@ -12,6 +12,7 @@ interface ModelDefinition {
armor: ModelArmor;
trackThickness: number;
turretOrigin: Vector3Tuple;
hullOrigin: Vector3Tuple;
turretRotation?: InitialTurretRotation;
turrets: Record<number, TurretModelDefinition>;
}
+22 -18
View File
@@ -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<TankopediaPersistent>()(
persist(
subscribeWithSelector<TankopediaPersistent>(() => ({
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<TankopediaTemporary>()(
yaw: 0,
pitch: 0,
},
equipment: {
enhancedArmor: false,
calibratedShells: false,
},
visual: {
controlsEnabled: true,
showGrid: true,
greenPenetration: false,
},
},
equipmentMatrix: [
[1, 0, 0],