feat: transiton and reveal

This commit is contained in:
TrèsAbhi committed 2025-11-26 21:10:03 -06:00
1 parent b67fd5b58c
commit 778efb2372
6 files changed
+75 -11

No files matched your search

@@ -15,7 +15,7 @@ type ThicknessesProps = MaybeSkeletonComponentProps & {
}; };
export function Thicknesses({ thicknessRange, skeleton }: ThicknessesProps) { export function Thicknesses({ thicknessRange, skeleton }: ThicknessesProps) {
const display = Tankopedia.use((state) => state.display); const display = Tankopedia.use((state) => state.requestedDisplay);
const showExternalModules = TankopediaPersistent.use( const showExternalModules = TankopediaPersistent.use(
(state) => state.showExternalModules (state) => state.showExternalModules
); );
@@ -55,7 +55,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
const hasCustomShell = Tankopedia.use( const hasCustomShell = Tankopedia.use(
(state) => state.customShell !== undefined (state) => state.customShell !== undefined
); );
const display = Tankopedia.use((state) => state.display); const display = Tankopedia.use((state) => state.requestedDisplay);
const isFullScreen = useFullScreen(); const isFullScreen = useFullScreen();
const advancedHighlighting = TankopediaPersistent.use( const advancedHighlighting = TankopediaPersistent.use(
(state) => state.advancedHighlighting (state) => state.advancedHighlighting
@@ -388,7 +388,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
onValueChange={(value) => { onValueChange={(value) => {
Tankopedia.mutate((draft) => { Tankopedia.mutate((draft) => {
draft.disturbed = true; draft.disturbed = true;
draft.display = Number(value); draft.requestedDisplay = Number(value);
}); });
}} }}
> >
@@ -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"; 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() { export function Lighting() {
const wrapper = useRef<Group>(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 ( return (
<> <group ref={wrapper}>
<hemisphereLight <hemisphereLight
position={[0, 1, 0]} position={[0, 1, 0]}
color="#dadaff" color="#dadaff"
groundColor="#856331" groundColor="#856331"
intensity={4} intensity={0}
/> />
<HelpingSpotLight <HelpingSpotLight
@@ -18,6 +75,7 @@ export function Lighting() {
castShadow castShadow
color="#ffffff" color="#ffffff"
decay={0} decay={0}
angle={0}
/> />
<HelpingSpotLight <HelpingSpotLight
@@ -28,7 +86,8 @@ export function Lighting() {
castShadow castShadow
color="#ffffff" color="#ffffff"
decay={0} decay={0}
angle={0}
/> />
</> </group>
); );
} }
@@ -59,7 +59,10 @@ export function TankModel() {
} }
function onPointerDown(event: ThreeEvent<PointerEvent>) { function onPointerDown(event: ThreeEvent<PointerEvent>) {
if (isTrack && Tankopedia.state.display === TankopediaDisplay.Model) { if (
isTrack &&
Tankopedia.state.requestedDisplay === TankopediaDisplay.Model
) {
position.set(event.clientX, event.clientY); position.set(event.clientX, event.clientY);
event.stopPropagation(); event.stopPropagation();
@@ -106,7 +109,9 @@ export function TankModel() {
const isTurret = node.name.startsWith("turret_"); const isTurret = node.name.startsWith("turret_");
const isCurrentTurret = const isCurrentTurret =
node.name === node.name ===
`turret_${turretModelDefinition.model_id.toString().padStart(2, "0")}`; `turret_${turretModelDefinition.model_id
.toString()
.padStart(2, "0")}`;
const isVisible = isCurrentTurret; const isVisible = isCurrentTurret;
const position = new Vector2(); const position = new Vector2();
const delta = new Vector2(); const delta = new Vector2();
@@ -5,7 +5,6 @@ import {
useEffect, useEffect,
useImperativeHandle, useImperativeHandle,
useRef, useRef,
useState,
} from "react"; } from "react";
import { Fog } from "three"; import { Fog } from "three";
import { applyPitchYawLimits } from "../../../../../core/blitz/applyPitchYawLimits"; import { applyPitchYawLimits } from "../../../../../core/blitz/applyPitchYawLimits";
@@ -49,8 +48,7 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
const turretModelDefinition = const turretModelDefinition =
tankModelDefinition.turrets[protagonistTurret.id]; tankModelDefinition.turrets[protagonistTurret.id];
const gunModelDefinition = turretModelDefinition.guns[protagonistGun.id]; const gunModelDefinition = turretModelDefinition.guns[protagonistGun.id];
const rawDisplay = Tankopedia.use((state) => state.display); const display = Tankopedia.use((state) => state.requestedDisplay);
const [display, setDisplay] = useState(rawDisplay);
const hideTankModelUnderArmor = TankopediaPersistent.use( const hideTankModelUnderArmor = TankopediaPersistent.use(
(state) => state.hideTankModelUnderArmor (state) => state.hideTankModelUnderArmor
); );
@@ -100,6 +100,7 @@ interface Tankopedia {
); );
xpMultiplier: (typeof XP_MULTIPLIERS)[number]; xpMultiplier: (typeof XP_MULTIPLIERS)[number];
customShell?: ShellDefinition; customShell?: ShellDefinition;
requestedDisplay: TankopediaDisplay;
display: TankopediaDisplay; display: TankopediaDisplay;
statSearch?: string; statSearch?: string;
} }
@@ -114,5 +115,6 @@ export const Tankopedia = new Varuna<Tankopedia, ModelDefinition>((model) => ({
skills: createDefaultSkills(skillDefinitions), skills: createDefaultSkills(skillDefinitions),
model, model,
xpMultiplier: 1, xpMultiplier: 1,
requestedDisplay: TankopediaDisplay.Model,
display: TankopediaDisplay.Model, display: TankopediaDisplay.Model,
})); }));