mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-23 19:49:50 +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(),
|
cameraNormal.copy(camera.position).sub(event.point).normalize(),
|
||||||
);
|
);
|
||||||
surfaceNormal.copy(event.normal!).applyAxisAngle(I_HAT, -Math.PI / 2);
|
surfaceNormal.copy(event.normal!).applyAxisAngle(I_HAT, -Math.PI / 2);
|
||||||
|
const angle = Math.acos(surfaceNormal.dot(shellNormal));
|
||||||
const shell = useDuel.getState().antagonist!.shell;
|
const shell = useDuel.getState().antagonist!.shell;
|
||||||
const intersections = event.intersections
|
const intersections = event.intersections
|
||||||
.filter(({ object }) => Boolean(object.userData.type))
|
.filter(({ object }) => Boolean(object.userData.type))
|
||||||
@@ -161,23 +162,29 @@ export function ArmorMesh({
|
|||||||
|
|
||||||
if (mesh.current !== (coreArmorMesh as unknown as Mesh)) return;
|
if (mesh.current !== (coreArmorMesh as unknown as Mesh)) return;
|
||||||
|
|
||||||
|
let accumulatedThickness = 0;
|
||||||
const armorPiercingLayers = intersectionsTillFirstCoreArmor.map(
|
const armorPiercingLayers = intersectionsTillFirstCoreArmor.map(
|
||||||
(event, index) => {
|
(event, index) => {
|
||||||
switch (event.object.userData.type) {
|
switch (event.object.userData.type) {
|
||||||
case 'spacedArmor': {
|
case 'spacedArmor': {
|
||||||
|
accumulatedThickness += event.object.userData.thickness;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
nominal: event.object.userData.thickness,
|
nominal: event.object.userData.thickness,
|
||||||
angled: event.object.userData.thickness,
|
angled: event.object.userData.thickness,
|
||||||
ricochet: false,
|
ricochet: false,
|
||||||
|
block:
|
||||||
|
accumulatedThickness >
|
||||||
|
resolveNearPenetration(shell.penetration),
|
||||||
|
type: 'external',
|
||||||
} satisfies ArmorPiercingLayer;
|
} satisfies ArmorPiercingLayer;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'externalModule':
|
case 'externalModule':
|
||||||
case 'coreArmor': {
|
case 'coreArmor': {
|
||||||
const angle = Math.acos(surfaceNormal.dot(shellNormal));
|
|
||||||
const threeCalibersRule =
|
const threeCalibersRule =
|
||||||
shell.caliber > 3 * event.object.userData.thickness;
|
shell.caliber > 3 * event.object.userData.thickness;
|
||||||
let hasRicochet = false;
|
let ricochet = false;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
index === 0 &&
|
index === 0 &&
|
||||||
@@ -185,22 +192,30 @@ export function ArmorMesh({
|
|||||||
!threeCalibersRule &&
|
!threeCalibersRule &&
|
||||||
angle >= degToRad(shell.ricochet!)
|
angle >= degToRad(shell.ricochet!)
|
||||||
) {
|
) {
|
||||||
hasRicochet = true;
|
ricochet = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const twoCalibersRule =
|
const twoCalibersRule =
|
||||||
shell.caliber > 2 * event.object.userData.thickness;
|
shell.caliber > 2 * event.object.userData.thickness;
|
||||||
const normalization = twoCalibersRule
|
const normalization = twoCalibersRule
|
||||||
? ((shell.normalization ?? 0) * 1.4 * shell.caliber) /
|
? ((shell.normalization ?? 0) * 1.4 * shell.caliber) /
|
||||||
(2.0 * thickness)
|
(2 * thickness)
|
||||||
: shell.normalization ?? 0;
|
: shell.normalization ?? 0;
|
||||||
const angledThickness =
|
const angled =
|
||||||
thickness / Math.cos(angle - normalization);
|
thickness / Math.cos(angle - degToRad(normalization));
|
||||||
|
accumulatedThickness += angled;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
nominal: event.object.userData.thickness,
|
nominal: event.object.userData.thickness,
|
||||||
angled: angledThickness,
|
angled,
|
||||||
ricochet: hasRicochet,
|
ricochet,
|
||||||
|
block:
|
||||||
|
accumulatedThickness >
|
||||||
|
resolveNearPenetration(shell.penetration),
|
||||||
|
type:
|
||||||
|
event.object.userData.type === 'coreArmor'
|
||||||
|
? 'core'
|
||||||
|
: 'spaced',
|
||||||
} satisfies ArmorPiercingLayer;
|
} satisfies ArmorPiercingLayer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -210,10 +225,6 @@ export function ArmorMesh({
|
|||||||
const hasRicochet = armorPiercingLayers.some(
|
const hasRicochet = armorPiercingLayers.some(
|
||||||
({ ricochet }) => ricochet,
|
({ ricochet }) => ricochet,
|
||||||
);
|
);
|
||||||
const accumulatedThickness = armorPiercingLayers.reduce(
|
|
||||||
(sum, { angled }) => sum + angled,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
const hasPenetrated =
|
const hasPenetrated =
|
||||||
resolveNearPenetration(shell.penetration) >= accumulatedThickness;
|
resolveNearPenetration(shell.penetration) >= accumulatedThickness;
|
||||||
|
|
||||||
@@ -228,9 +239,8 @@ export function ArmorMesh({
|
|||||||
thicknesses: armorPiercingLayers,
|
thicknesses: armorPiercingLayers,
|
||||||
shellNormal: shellNormal.toArray(),
|
shellNormal: shellNormal.toArray(),
|
||||||
surfaceNormal: surfaceNormal.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 { Flex, Text, Theme } from '@radix-ui/themes';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { theme } from '../../stitches.config';
|
import { theme } from '../../stitches.config';
|
||||||
import { BlitzkriegWormWide } from '../BlitzkriegWormWide';
|
import { BlitzkriegWormWide } from '../../icons/BlitzkriegWormWide';
|
||||||
|
|
||||||
export default function Navbar() {
|
export default function Navbar() {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,8 +1,25 @@
|
|||||||
import { Card, Flex, Text } from '@radix-ui/themes';
|
import { Card, Flex, Text } from '@radix-ui/themes';
|
||||||
import { Html } from '@react-three/drei';
|
import { Html } from '@react-three/drei';
|
||||||
import { useEffect, useRef } from 'react';
|
import { ReactNode, useEffect, useRef } from 'react';
|
||||||
import { Group, Vector3 } from 'three';
|
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 THICKNESS = 0.05;
|
||||||
const LENGTH = 2;
|
const LENGTH = 2;
|
||||||
@@ -26,16 +43,49 @@ export function ShotDisplay() {
|
|||||||
return (
|
return (
|
||||||
<group ref={wrapper}>
|
<group ref={wrapper}>
|
||||||
<Html position={[0, 0, LENGTH]}>
|
<Html position={[0, 0, LENGTH]}>
|
||||||
<Card>
|
<Card
|
||||||
<Flex direction="column">
|
style={{
|
||||||
<Text>{SHOT_NAMES[shot.type]}</Text>
|
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>
|
</Flex>
|
||||||
</Card>
|
</Card>
|
||||||
</Html>
|
</Html>
|
||||||
|
|
||||||
<mesh position={[0, 0, LENGTH / 2]} rotation={[Math.PI / 2, 0, 0]}>
|
<mesh position={[0, 0, LENGTH / 2]} rotation={[Math.PI / 2, 0, 0]}>
|
||||||
<cylinderGeometry args={[THICKNESS / 2, THICKNESS / 2, LENGTH, 8]} />
|
<cylinderGeometry args={[THICKNESS / 2, THICKNESS / 2, LENGTH, 8]} />
|
||||||
{/* <boxGeometry args={[THICKNESS, THICKNESS, LENGTH]} /> */}
|
|
||||||
<meshBasicMaterial color="white" depthTest={false} />
|
<meshBasicMaterial color="white" depthTest={false} />
|
||||||
</mesh>
|
</mesh>
|
||||||
</group>
|
</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 (
|
return (
|
||||||
<svg
|
<svg
|
||||||
width="74"
|
width="74"
|
||||||
@@ -6,6 +8,7 @@ export function BlitzkriegWormWide() {
|
|||||||
viewBox="0 0 74 17"
|
viewBox="0 0 74 17"
|
||||||
fill="none"
|
fill="none"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
{...props}
|
||||||
>
|
>
|
||||||
<path
|
<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"
|
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;
|
shot?: Shot;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Shot = {
|
export type Shot = {
|
||||||
point: Vector3Tuple;
|
point: Vector3Tuple;
|
||||||
surfaceNormal: Vector3Tuple;
|
surfaceNormal: Vector3Tuple;
|
||||||
shellNormal: Vector3Tuple;
|
shellNormal: Vector3Tuple;
|
||||||
thicknesses: ArmorPiercingLayer[];
|
thicknesses: ArmorPiercingLayer[];
|
||||||
type: 'ricochet' | 'penetration' | 'block';
|
type: 'ricochet' | 'penetration' | 'block';
|
||||||
};
|
angle: number;
|
||||||
|
|
||||||
export const SHOT_NAMES: Record<Shot['type'], string> = {
|
|
||||||
ricochet: 'Ricochet',
|
|
||||||
penetration: 'Penetration',
|
|
||||||
block: 'Blocked',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface ArmorPiercingLayer {
|
export interface ArmorPiercingLayer {
|
||||||
nominal: number;
|
nominal: number;
|
||||||
angled: number;
|
angled: number;
|
||||||
ricochet: boolean;
|
ricochet: boolean;
|
||||||
|
block: boolean;
|
||||||
|
type: 'core' | 'spaced' | 'external';
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useTankopediaPersistent = create<TankopediaPersistent>()(
|
export const useTankopediaPersistent = create<TankopediaPersistent>()(
|
||||||
|
|||||||
Reference in new issue
Block a user