mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 10:47:04 +02:00
feat: layering
This commit is contained in:
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);
|
||||
});
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>()(
|
||||
|
||||
Reference in new issue
Block a user