From 3a9920519487342da773404495a5aa42bf810f36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Fri, 2 Feb 2024 16:59:59 -0600 Subject: [PATCH] feat: layering --- .../ArmorMesh/components/Armor/index.tsx | 38 +++++++----- src/components/Navbar/index.tsx | 2 +- src/components/ShotDisplay.tsx | 62 +++++++++++++++++-- src/icons/AngledPenetration.tsx | 25 ++++++++ .../BlitzkriegWormWide.tsx | 5 +- src/icons/Block.tsx | 25 ++++++++ src/icons/NominalPenetration.tsx | 21 +++++++ src/icons/Ricochet.tsx | 20 ++++++ src/stores/tankopedia.ts | 11 ++-- 9 files changed, 180 insertions(+), 29 deletions(-) create mode 100644 src/icons/AngledPenetration.tsx rename src/{components => icons}/BlitzkriegWormWide.tsx (97%) create mode 100644 src/icons/Block.tsx create mode 100644 src/icons/NominalPenetration.tsx create mode 100644 src/icons/Ricochet.tsx diff --git a/src/components/ArmorMesh/components/Armor/index.tsx b/src/components/ArmorMesh/components/Armor/index.tsx index 5376cf755..a5a8c41cf 100644 --- a/src/components/ArmorMesh/components/Armor/index.tsx +++ b/src/components/ArmorMesh/components/Armor/index.tsx @@ -140,6 +140,7 @@ export function ArmorMesh({ cameraNormal.copy(camera.position).sub(event.point).normalize(), ); surfaceNormal.copy(event.normal!).applyAxisAngle(I_HAT, -Math.PI / 2); + const angle = Math.acos(surfaceNormal.dot(shellNormal)); const shell = useDuel.getState().antagonist!.shell; const intersections = event.intersections .filter(({ object }) => Boolean(object.userData.type)) @@ -161,23 +162,29 @@ export function ArmorMesh({ if (mesh.current !== (coreArmorMesh as unknown as Mesh)) return; + let accumulatedThickness = 0; const armorPiercingLayers = intersectionsTillFirstCoreArmor.map( (event, index) => { switch (event.object.userData.type) { case 'spacedArmor': { + accumulatedThickness += event.object.userData.thickness; + return { nominal: event.object.userData.thickness, angled: event.object.userData.thickness, ricochet: false, + block: + accumulatedThickness > + resolveNearPenetration(shell.penetration), + type: 'external', } satisfies ArmorPiercingLayer; } case 'externalModule': case 'coreArmor': { - const angle = Math.acos(surfaceNormal.dot(shellNormal)); const threeCalibersRule = shell.caliber > 3 * event.object.userData.thickness; - let hasRicochet = false; + let ricochet = false; if ( index === 0 && @@ -185,22 +192,30 @@ export function ArmorMesh({ !threeCalibersRule && angle >= degToRad(shell.ricochet!) ) { - hasRicochet = true; + ricochet = true; } const twoCalibersRule = shell.caliber > 2 * event.object.userData.thickness; const normalization = twoCalibersRule ? ((shell.normalization ?? 0) * 1.4 * shell.caliber) / - (2.0 * thickness) + (2 * thickness) : shell.normalization ?? 0; - const angledThickness = - thickness / Math.cos(angle - normalization); + const angled = + thickness / Math.cos(angle - degToRad(normalization)); + accumulatedThickness += angled; return { nominal: event.object.userData.thickness, - angled: angledThickness, - ricochet: hasRicochet, + angled, + ricochet, + block: + accumulatedThickness > + resolveNearPenetration(shell.penetration), + type: + event.object.userData.type === 'coreArmor' + ? 'core' + : 'spaced', } satisfies ArmorPiercingLayer; } } @@ -210,10 +225,6 @@ export function ArmorMesh({ const hasRicochet = armorPiercingLayers.some( ({ ricochet }) => ricochet, ); - const accumulatedThickness = armorPiercingLayers.reduce( - (sum, { angled }) => sum + angled, - 0, - ); const hasPenetrated = resolveNearPenetration(shell.penetration) >= accumulatedThickness; @@ -228,9 +239,8 @@ export function ArmorMesh({ thicknesses: armorPiercingLayers, shellNormal: shellNormal.toArray(), surfaceNormal: surfaceNormal.toArray(), + angle, }; - - console.log(draft.shot); }); }} > diff --git a/src/components/Navbar/index.tsx b/src/components/Navbar/index.tsx index 762afa985..0d437c4a6 100644 --- a/src/components/Navbar/index.tsx +++ b/src/components/Navbar/index.tsx @@ -2,7 +2,7 @@ import { DiscordLogoIcon } from '@radix-ui/react-icons'; import { Flex, Text, Theme } from '@radix-ui/themes'; import Link from 'next/link'; import { theme } from '../../stitches.config'; -import { BlitzkriegWormWide } from '../BlitzkriegWormWide'; +import { BlitzkriegWormWide } from '../../icons/BlitzkriegWormWide'; export default function Navbar() { return ( diff --git a/src/components/ShotDisplay.tsx b/src/components/ShotDisplay.tsx index b5e276954..bbb37b41d 100644 --- a/src/components/ShotDisplay.tsx +++ b/src/components/ShotDisplay.tsx @@ -1,8 +1,25 @@ import { Card, Flex, Text } from '@radix-ui/themes'; import { Html } from '@react-three/drei'; -import { useEffect, useRef } from 'react'; +import { ReactNode, useEffect, useRef } from 'react'; import { Group, Vector3 } from 'three'; -import { SHOT_NAMES, useTankopediaTemporary } from '../stores/tankopedia'; +import { radToDeg } from 'three/src/math/MathUtils'; +import { AngledPenetration } from '../icons/AngledPenetration'; +import { Block } from '../icons/Block'; +import { NominalPenetration } from '../icons/NominalPenetration'; +import { Ricochet } from '../icons/Ricochet'; +import { Shot, useTankopediaTemporary } from '../stores/tankopedia'; + +export const SHOT_NAMES: Record = { + ricochet: 'Ricochet', + penetration: 'Penetration', + block: 'Blocked', +}; + +export const SHOT_ICONS: Record = { + ricochet: , + block: , + penetration: , +}; const THICKNESS = 0.05; const LENGTH = 2; @@ -26,16 +43,49 @@ export function ShotDisplay() { return ( - - - {SHOT_NAMES[shot.type]} + + + + {SHOT_ICONS[shot.type]} + + {SHOT_NAMES[shot.type]} at {Math.round(radToDeg(shot.angle))}° + + + + + {shot.thicknesses.map(({ nominal, angled, type }, index) => ( + + + {index + 1}. {type[0].toUpperCase()} + {type.slice(1)} + + + + + {nominal}mm + + + + {Math.round(angled)}mm + + + ))} + - {/* */} diff --git a/src/icons/AngledPenetration.tsx b/src/icons/AngledPenetration.tsx new file mode 100644 index 000000000..fc72a22ce --- /dev/null +++ b/src/icons/AngledPenetration.tsx @@ -0,0 +1,25 @@ +import { ComponentProps } from 'react'; + +export function AngledPenetration(props: ComponentProps<'svg'>) { + return ( + + + + + ); +} diff --git a/src/components/BlitzkriegWormWide.tsx b/src/icons/BlitzkriegWormWide.tsx similarity index 97% rename from src/components/BlitzkriegWormWide.tsx rename to src/icons/BlitzkriegWormWide.tsx index c5bd33b5d..8c2e46784 100644 --- a/src/components/BlitzkriegWormWide.tsx +++ b/src/icons/BlitzkriegWormWide.tsx @@ -1,4 +1,6 @@ -export function BlitzkriegWormWide() { +import { ComponentProps } from 'react'; + +export function BlitzkriegWormWide(props: ComponentProps<'svg'>) { return ( ) { + return ( + + + + + + + ); +} diff --git a/src/icons/NominalPenetration.tsx b/src/icons/NominalPenetration.tsx new file mode 100644 index 000000000..7b6ed0758 --- /dev/null +++ b/src/icons/NominalPenetration.tsx @@ -0,0 +1,21 @@ +import { ComponentProps } from 'react'; + +export function NominalPenetration(props: ComponentProps<'svg'>) { + return ( + + + + + + ); +} diff --git a/src/icons/Ricochet.tsx b/src/icons/Ricochet.tsx new file mode 100644 index 000000000..f79672ec6 --- /dev/null +++ b/src/icons/Ricochet.tsx @@ -0,0 +1,20 @@ +import { ComponentProps } from 'react'; + +export function Ricochet(props: ComponentProps<'svg'>) { + return ( + + + + + ); +} diff --git a/src/stores/tankopedia.ts b/src/stores/tankopedia.ts index daf61c8a2..4fba02d7b 100644 --- a/src/stores/tankopedia.ts +++ b/src/stores/tankopedia.ts @@ -67,24 +67,21 @@ interface TankopediaTemporary { shot?: Shot; } -type Shot = { +export type Shot = { point: Vector3Tuple; surfaceNormal: Vector3Tuple; shellNormal: Vector3Tuple; thicknesses: ArmorPiercingLayer[]; type: 'ricochet' | 'penetration' | 'block'; -}; - -export const SHOT_NAMES: Record = { - ricochet: 'Ricochet', - penetration: 'Penetration', - block: 'Blocked', + angle: number; }; export interface ArmorPiercingLayer { nominal: number; angled: number; ricochet: boolean; + block: boolean; + type: 'core' | 'spaced' | 'external'; } export const useTankopediaPersistent = create()(