feat: layering

This commit is contained in:
TrèsAbhi committed 2024-02-02 16:59:59 -06:00
1 parent c2e550e33d
commit 3a99205194
9 files changed
+180 -29

No files matched your search

@@ -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);
});
}}
>
+1 -1
View File
@@ -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 (
+56 -6
View File
@@ -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<Shot['type'], string> = {
ricochet: 'Ricochet',
penetration: 'Penetration',
block: 'Blocked',
};
export const SHOT_ICONS: Record<Shot['type'], ReactNode> = {
ricochet: <Ricochet width={24} height={24} />,
block: <Block width={24} height={24} />,
penetration: <NominalPenetration width={24} height={24} />,
};
const THICKNESS = 0.05;
const LENGTH = 2;
@@ -26,16 +43,49 @@ export function ShotDisplay() {
return (
<group ref={wrapper}>
<Html position={[0, 0, LENGTH]}>
<Card>
<Flex direction="column">
<Text>{SHOT_NAMES[shot.type]}</Text>
<Card
style={{
width: 300,
}}
>
<Flex direction="column" gap="1">
<Flex align="center" gap="2">
{SHOT_ICONS[shot.type]}
<Text>
{SHOT_NAMES[shot.type]} at {Math.round(radToDeg(shot.angle))}°
</Text>
</Flex>
<Flex
direction="column"
style={{
paddingLeft: 8,
}}
>
{shot.thicknesses.map(({ nominal, angled, type }, index) => (
<Flex gap="2">
<Text>
{index + 1}. {type[0].toUpperCase()}
{type.slice(1)}
</Text>
<Flex gap="1">
<NominalPenetration width={24} height={24} />
<Text>{nominal}mm</Text>
</Flex>
<Flex gap="1">
<AngledPenetration width={24} height={24} />
<Text>{Math.round(angled)}mm</Text>
</Flex>
</Flex>
))}
</Flex>
</Flex>
</Card>
</Html>
<mesh position={[0, 0, LENGTH / 2]} rotation={[Math.PI / 2, 0, 0]}>
<cylinderGeometry args={[THICKNESS / 2, THICKNESS / 2, LENGTH, 8]} />
{/* <boxGeometry args={[THICKNESS, THICKNESS, LENGTH]} /> */}
<meshBasicMaterial color="white" depthTest={false} />
</mesh>
</group>
+25
View File
@@ -0,0 +1,25 @@
import { ComponentProps } from 'react';
export function AngledPenetration(props: ComponentProps<'svg'>) {
return (
<svg
width="64"
height="64"
viewBox="0 0 64 64"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M56 33C56.5523 33 57 32.5523 57 32C57 31.4477 56.5523 31 56 31V33ZM8 32L18 37.7735V26.2265L8 32ZM56 31H17V33H56V31Z"
fill="currentColor"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M28 8H12L22 28H38L28 8ZM42 36H26L36 56H52L42 36Z"
fill="currentColor"
/>
</svg>
);
}
@@ -1,4 +1,6 @@
export function BlitzkriegWormWide() {
import { ComponentProps } from 'react';
export function BlitzkriegWormWide(props: ComponentProps<'svg'>) {
return (
<svg
width="74"
@@ -6,6 +8,7 @@ export function BlitzkriegWormWide() {
viewBox="0 0 74 17"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M33.945 12.9858V0H36.875V7.50095L39.4608 4.63967H42.7879L36.875 10.7408V12.9858H33.945ZM39.7873 12.9858L37.0515 9.04164L38.8695 7.04314L43.2292 12.9858H39.7873Z"
+25
View File
@@ -0,0 +1,25 @@
import { ComponentProps } from 'react';
export function Block(props: ComponentProps<'svg'>) {
return (
<svg
width="64"
height="64"
viewBox="0 0 64 64"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path d="M40 8H24V26H40V8Z" fill="currentColor" />
<path d="M40 38H24V56H40V38Z" fill="currentColor" />
<path
d="M40 26H24C21 26 21.0302 30 21 32C20.9698 34 21 38 24 38H40C37 38 36.9698 34 37 32C37.0301 30 37 26 40 26Z"
fill="currentColor"
/>
<path
d="M56 33C56.5523 33 57 32.5523 57 32C57 31.4477 56.5523 31 56 31L56 33ZM37 32L47 37.7735L47 26.2265L37 32ZM56 31L46 31L46 33L56 33L56 31Z"
fill="currentColor"
/>
</svg>
);
}
+21
View File
@@ -0,0 +1,21 @@
import { ComponentProps } from 'react';
export function NominalPenetration(props: ComponentProps<'svg'>) {
return (
<svg
width="64"
height="64"
viewBox="0 0 64 64"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path d="M40 8H24V28H40V8Z" fill="currentColor" />
<path d="M40 36H24V56H40V36Z" fill="currentColor" />
<path
d="M56 33C56.5523 33 57 32.5523 57 32C57 31.4477 56.5523 31 56 31V33ZM8 32L18 37.7735V26.2265L8 32ZM56 31H17V33H56V31Z"
fill="currentColor"
/>
</svg>
);
}
+20
View File
@@ -0,0 +1,20 @@
import { ComponentProps } from 'react';
export function Ricochet(props: ComponentProps<'svg'>) {
return (
<svg
width="64"
height="64"
viewBox="0 0 64 64"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path d="M12 8H28L52 56H36L12 8Z" fill="currentColor" />
<path
d="M55.4 44.8C55.8418 45.1314 56.4686 45.0418 56.8 44.6C57.1314 44.1582 57.0418 43.5314 56.6 43.2L55.4 44.8ZM40 8L34.2265 18H45.7735L40 8ZM40 32H39V32.5L39.4 32.8L40 32ZM56.6 43.2L40.6 31.2L39.4 32.8L55.4 44.8L56.6 43.2ZM41 32V17H39V32H41Z"
fill="currentColor"
/>
</svg>
);
}
+4 -7
View File
@@ -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<Shot['type'], string> = {
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<TankopediaPersistent>()(