From 3c63323814aa9fd3e9b9aab8a69931f2c069663c 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 17:41:18 -0600 Subject: [PATCH] feat: ricochet angle --- src/components/ShotDisplay.tsx | 167 ++++++++++++++++++++++----------- 1 file changed, 113 insertions(+), 54 deletions(-) diff --git a/src/components/ShotDisplay.tsx b/src/components/ShotDisplay.tsx index e5757129e..58466c8df 100644 --- a/src/components/ShotDisplay.tsx +++ b/src/components/ShotDisplay.tsx @@ -1,7 +1,8 @@ import { Card, Flex, Text } from '@radix-ui/themes'; import { Html } from '@react-three/drei'; +import { useFrame } from '@react-three/fiber'; 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 { AngledPenetration } from '../icons/AngledPenetration'; import { Block } from '../icons/Block'; @@ -21,75 +22,133 @@ export const SHOT_ICONS: Record = { penetration: , }; -const THICKNESS = 0.05; +const THICKNESS = 0.025; const LENGTH = 2; export function ShotDisplay() { const shot = useTankopediaTemporary((state) => state.shot); - const wrapper = useRef(null); + const entryWrapper = useRef(null); + const entryLine = useRef(null); + const circle = useRef(null); + const exitWrapper = useRef(null); + const exitLine = useRef(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(() => { if (!shot) return; const point = new Vector3(...shot.point); 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); - wrapper.current?.lookAt(point.clone().add(shotNormal)); + entryWrapper.current?.position.set(...shot.point); + 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]); if (!shot) return null; return ( - - - - - - {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 - - {type !== 'external' && ( - - - {Math.round(angled)}mm - - )} - - ))} - - - - - - - - + <> + + + - + + + + + + + {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 + + {type !== 'external' && ( + + + {Math.round(angled)}mm + + )} + + ))} + + + + + + + + + + + + {shot.type === 'ricochet' && ( + + + + + + + )} + ); }