feat: ricochet angle

This commit is contained in:
TrèsAbhi committed 2024-02-02 17:41:18 -06:00
1 parent 44f00e042e
commit 3c63323814
1 file changed
+113 -54
+113 -54
View File
@@ -1,7 +1,8 @@
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 { useFrame } from '@react-three/fiber';
import { ReactNode, useEffect, useRef } from 'react'; import { ReactNode, useEffect, useRef } from 'react';
import { Group, Vector3 } from 'three'; import { Group, Mesh, Vector3 } from 'three';
import { radToDeg } from 'three/src/math/MathUtils'; import { radToDeg } from 'three/src/math/MathUtils';
import { AngledPenetration } from '../icons/AngledPenetration'; import { AngledPenetration } from '../icons/AngledPenetration';
import { Block } from '../icons/Block'; import { Block } from '../icons/Block';
@@ -21,75 +22,133 @@ export const SHOT_ICONS: Record<Shot['type'], ReactNode> = {
penetration: <NominalPenetration width={24} height={24} />, penetration: <NominalPenetration width={24} height={24} />,
}; };
const THICKNESS = 0.05; const THICKNESS = 0.025;
const LENGTH = 2; const LENGTH = 2;
export function ShotDisplay() { export function ShotDisplay() {
const shot = useTankopediaTemporary((state) => state.shot); const shot = useTankopediaTemporary((state) => state.shot);
const wrapper = useRef<Group>(null); const entryWrapper = useRef<Group>(null);
const entryLine = useRef<Mesh>(null);
const circle = useRef<Mesh>(null);
const exitWrapper = useRef<Group>(null);
const exitLine = useRef<Mesh>(null);
useFrame(({ clock }) => {
const t1 = clock.elapsedTime % 1;
const t2 = (clock.elapsedTime + 0.5) % 1;
entryLine.current?.position.set(0, 0, LENGTH - t1 * LENGTH);
entryLine.current?.scale.set(1, -2 * Math.abs(t1 - 0.5) + 1, 1);
exitLine.current?.position.set(0, 0, t2 * LENGTH);
exitLine.current?.scale.set(1, -2 * Math.abs(t2 - 0.5) + 1, 1);
});
useEffect(() => { useEffect(() => {
if (!shot) return; if (!shot) return;
const point = new Vector3(...shot.point); const point = new Vector3(...shot.point);
const shotNormal = new Vector3(...shot.shellNormal); const shotNormal = new Vector3(...shot.shellNormal);
const surfaceNormal = new Vector3(...shot.surfaceNormal);
const bounceNormal = shotNormal
.clone()
.multiplyScalar(-1)
.sub(
surfaceNormal
.clone()
.multiplyScalar(
2 * surfaceNormal.dot(shotNormal.clone().multiplyScalar(-1)),
),
);
wrapper.current?.position.set(...shot.point); entryWrapper.current?.position.set(...shot.point);
wrapper.current?.lookAt(point.clone().add(shotNormal)); entryWrapper.current?.lookAt(point.clone().add(shotNormal));
exitWrapper.current?.position.set(...shot.point);
exitWrapper.current?.lookAt(point.clone().add(bounceNormal));
circle.current?.position.set(...shot.point);
circle.current?.lookAt(point.clone().add(surfaceNormal));
}, [shot]); }, [shot]);
if (!shot) return null; if (!shot) return null;
return ( return (
<group ref={wrapper}> <>
<Html position={[0, 0, LENGTH]}> <mesh ref={circle}>
<Card <torusGeometry args={[0.05, 0.0125, 4, 16]} />
style={{ <meshBasicMaterial
width: 300, color={shot.type === 'penetration' ? 'green' : 'red'}
}} depthTest={false}
> />
<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>
{type !== 'external' && (
<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]} />
<meshBasicMaterial color="white" depthTest={false} />
</mesh> </mesh>
</group>
<group ref={entryWrapper}>
<Html position={[0, 0, LENGTH]}>
<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>
{type !== 'external' && (
<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]}
ref={entryLine}
>
<cylinderGeometry args={[THICKNESS / 2, THICKNESS / 2, LENGTH, 8]} />
<meshBasicMaterial color="white" depthTest={false} />
</mesh>
</group>
{shot.type === 'ricochet' && (
<group ref={exitWrapper}>
<mesh
position={[0, 0, LENGTH / 2]}
rotation={[Math.PI / 2, 0, 0]}
ref={exitLine}
>
<cylinderGeometry
args={[THICKNESS / 2, THICKNESS / 2, LENGTH, 8]}
/>
<meshBasicMaterial color="white" depthTest={false} />
</mesh>
</group>
)}
</>
); );
} }