From 778efb237287374f99c36b7257b88b8dc586256c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Wed, 26 Nov 2025 21:10:03 -0600 Subject: [PATCH] feat: transiton and reveal --- .../Options/components/Thicknesses.tsx | 2 +- .../HeroSection/components/Options/index.tsx | 4 +- .../TankSandbox/components/Lighting.tsx | 65 ++++++++++++++++++- .../TankSandbox/components/TankModel.tsx | 9 ++- .../components/TankSandbox/index.tsx | 4 +- packages/website/src/stores/tankopedia.ts | 2 + 6 files changed, 75 insertions(+), 11 deletions(-) diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/Thicknesses.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/Thicknesses.tsx index 7fdb09d73..3eefe7fe5 100644 --- a/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/Thicknesses.tsx +++ b/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/Thicknesses.tsx @@ -15,7 +15,7 @@ type ThicknessesProps = MaybeSkeletonComponentProps & { }; export function Thicknesses({ thicknessRange, skeleton }: ThicknessesProps) { - const display = Tankopedia.use((state) => state.display); + const display = Tankopedia.use((state) => state.requestedDisplay); const showExternalModules = TankopediaPersistent.use( (state) => state.showExternalModules ); diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/Options/index.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/Options/index.tsx index c768e9c46..59fdca5a6 100644 --- a/packages/website/src/components/Tankopedia/HeroSection/components/Options/index.tsx +++ b/packages/website/src/components/Tankopedia/HeroSection/components/Options/index.tsx @@ -55,7 +55,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) { const hasCustomShell = Tankopedia.use( (state) => state.customShell !== undefined ); - const display = Tankopedia.use((state) => state.display); + const display = Tankopedia.use((state) => state.requestedDisplay); const isFullScreen = useFullScreen(); const advancedHighlighting = TankopediaPersistent.use( (state) => state.advancedHighlighting @@ -388,7 +388,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) { onValueChange={(value) => { Tankopedia.mutate((draft) => { draft.disturbed = true; - draft.display = Number(value); + draft.requestedDisplay = Number(value); }); }} > diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/Lighting.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/Lighting.tsx index f3dd5af96..a339ecfd7 100644 --- a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/Lighting.tsx +++ b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/Lighting.tsx @@ -1,13 +1,70 @@ +import { useFrame, useThree } from "@react-three/fiber"; +import { useEffect, useRef } from "react"; +import { HemisphereLight, SpotLight, type Group } from "three"; +import { clamp } from "three/src/math/MathUtils.js"; +import { Tankopedia } from "../../../../../../stores/tankopedia"; import { HelpingSpotLight } from "../../../../../HelpingSpotLight"; +const ANGLE = Math.PI / 3; +const HEMISPHERE_INTENSITY = 4; +const REVEAL_ANIMATION_TIME = 5; +const TRANSITION_ANIMATION_TIME = 0.5; + export function Lighting() { + const wrapper = useRef(null!); + const t0 = useRef(0); + const clock = useThree((state) => state.clock); + const display = Tankopedia.use((state) => state.display); + const requestedDisplay = Tankopedia.use((state) => state.requestedDisplay); + const animationTime = useRef(REVEAL_ANIMATION_TIME); + + useEffect(() => { + Tankopedia.mutate((draft) => { + draft.revealed = true; + }); + + t0.current = clock.elapsedTime - REVEAL_ANIMATION_TIME; + }, []); + + useFrame(({ clock, invalidate }) => { + const x = clamp( + (clock.elapsedTime - t0.current) / animationTime.current, + 0, + 2 + ); + const t = 0.5 * Math.sin(Math.PI * (x + 0.5)) + 0.5; + + for (const child of wrapper.current.children) { + if (child instanceof HemisphereLight) { + child.intensity = HEMISPHERE_INTENSITY * t; + } else if (child instanceof SpotLight) { + child.angle = ANGLE * t; + } + } + + if (x < 2) invalidate(); + + if (x === 2 && display !== requestedDisplay) { + Tankopedia.mutate((draft) => { + draft.display = requestedDisplay; + }); + } + }); + + useEffect(() => { + if (requestedDisplay === display) return; + + t0.current = clock.elapsedTime; + animationTime.current = TRANSITION_ANIMATION_TIME; + }, [requestedDisplay]); + return ( - <> + - + ); } diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/TankModel.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/TankModel.tsx index b6f89c092..6a5b650c5 100644 --- a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/TankModel.tsx +++ b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/TankModel.tsx @@ -59,7 +59,10 @@ export function TankModel() { } function onPointerDown(event: ThreeEvent) { - if (isTrack && Tankopedia.state.display === TankopediaDisplay.Model) { + if ( + isTrack && + Tankopedia.state.requestedDisplay === TankopediaDisplay.Model + ) { position.set(event.clientX, event.clientY); event.stopPropagation(); @@ -106,7 +109,9 @@ export function TankModel() { const isTurret = node.name.startsWith("turret_"); const isCurrentTurret = node.name === - `turret_${turretModelDefinition.model_id.toString().padStart(2, "0")}`; + `turret_${turretModelDefinition.model_id + .toString() + .padStart(2, "0")}`; const isVisible = isCurrentTurret; const position = new Vector2(); const delta = new Vector2(); diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/index.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/index.tsx index ae835715b..5e782d95d 100644 --- a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/index.tsx +++ b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/index.tsx @@ -5,7 +5,6 @@ import { useEffect, useImperativeHandle, useRef, - useState, } from "react"; import { Fog } from "three"; import { applyPitchYawLimits } from "../../../../../core/blitz/applyPitchYawLimits"; @@ -49,8 +48,7 @@ export const TankSandbox = forwardRef( const turretModelDefinition = tankModelDefinition.turrets[protagonistTurret.id]; const gunModelDefinition = turretModelDefinition.guns[protagonistGun.id]; - const rawDisplay = Tankopedia.use((state) => state.display); - const [display, setDisplay] = useState(rawDisplay); + const display = Tankopedia.use((state) => state.requestedDisplay); const hideTankModelUnderArmor = TankopediaPersistent.use( (state) => state.hideTankModelUnderArmor ); diff --git a/packages/website/src/stores/tankopedia.ts b/packages/website/src/stores/tankopedia.ts index b699dc977..d5f54b26c 100644 --- a/packages/website/src/stores/tankopedia.ts +++ b/packages/website/src/stores/tankopedia.ts @@ -100,6 +100,7 @@ interface Tankopedia { ); xpMultiplier: (typeof XP_MULTIPLIERS)[number]; customShell?: ShellDefinition; + requestedDisplay: TankopediaDisplay; display: TankopediaDisplay; statSearch?: string; } @@ -114,5 +115,6 @@ export const Tankopedia = new Varuna((model) => ({ skills: createDefaultSkills(skillDefinitions), model, xpMultiplier: 1, + requestedDisplay: TankopediaDisplay.Model, display: TankopediaDisplay.Model, }));