feat: slicker loading

This commit is contained in:
tresabhi committed 2025-04-15 00:35:12 -05:00
1 parent 0e6aa734cb
commit 545b655d4e
6 files changed
+140 -78

No files matched your search

@@ -75,7 +75,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
);
const wireframe = TankopediaPersistent.use((state) => state.wireframe);
const opaque = TankopediaPersistent.use((state) => state.opaque);
const fullScreenAvailable = useFullscreenAvailability();
const fullScreenAvailable = useFullscreenAvailability(true);
const developerMode = App.use((state) => state.developerMode);
const protagonistTank = Duel.use((state) => state.protagonist.tank);
const antagonistGun = Duel.use((state) => state.antagonist.gun);
@@ -0,0 +1,34 @@
import { useThree } from '@react-three/fiber';
import { useEffect } from 'react';
import type { Fog } from 'three';
import { fogAnimationTime, fogFar0, fogFar1, forNear0, forNear1 } from '..';
import { TankopediaEphemeral } from '../../../../../../stores/tankopediaEphemeral';
export function InitialFogReveal() {
const fog = useThree((state) => state.scene.fog as Fog | null);
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
useEffect(() => {
mutateTankopediaEphemeral((draft) => {
draft.revealed = true;
});
if (!fog) return;
const t0 = Date.now();
const interval = setInterval(() => {
const t = (Date.now() - t0) / 1000;
const x = t / fogAnimationTime;
const near = forNear1 + (forNear0 - forNear1) * x;
const far = fogFar1 + (fogFar0 - fogFar1) * x;
fog.near = near;
fog.far = far;
}, 1000 / 60);
return () => clearInterval(interval);
}, []);
return null;
}
@@ -28,8 +28,8 @@ import {
} from '../../../../Armor/components/StaticArmor';
import { AutoClear } from './components/AutoClear';
import { Controls } from './components/Control';
import { InitialFogReveal } from './components/InitialFogReveal';
import { Lighting } from './components/Lighting';
import { ModelLoader } from './components/ModelLoader';
import { SceneProps } from './components/SceneProps';
import { TankModel } from './components/TankModel';
@@ -38,15 +38,15 @@ interface TankSandboxProps {
naked?: boolean;
}
const near0 = 15;
const far0 = 25;
const near1 = 0;
const far1 = 0;
const animationTime = 0.5;
export const forNear0 = 15;
export const fogFar0 = 25;
export const forNear1 = 0;
export const fogFar1 = 0;
export const fogAnimationTime = 0.5;
export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
({ thicknessRange, naked }, ref) => {
const fog = useRef(new Fog('black', near0, far0));
const fog = useRef(new Fog('black', forNear1, fogFar1));
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
const canvas = useRef<HTMLCanvasElement>(null);
const hasImprovedVerticalStabilizer = useEquipment(122);
@@ -73,11 +73,11 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
const interval = setInterval((e) => {
const t = (Date.now() - t0) / 1000;
const x = t / animationTime;
const x = t / fogAnimationTime;
const y = Math.cbrt(-2 * Math.abs(x - 0.5)) + 1;
const near = near0 + (near1 - near0) * y;
const far = far0 + (far1 - far0) * y;
const near = forNear0 + (forNear1 - forNear0) * y;
const far = fogFar0 + (fogFar1 - fogFar0) * y;
fog.current.near = near;
fog.current.far = far;
@@ -86,8 +86,8 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
if (rawDisplay !== display && x >= 0.5) setDisplay(rawDisplay);
if (x >= 1) {
fog.current.near = near0;
fog.current.far = far0;
fog.current.near = forNear0;
fog.current.far = fogFar0;
clearInterval(interval);
}
}, 1000 / 60);
@@ -234,7 +234,6 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
outline: naked ? '1rem red solid' : undefined,
}}
>
<Controls naked={naked} />
{!naked && <SceneProps />}
{(display === TankopediaDisplay.Model ||
(display === TankopediaDisplay.DynamicArmor &&
@@ -243,7 +242,11 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
<ArmorPlateDisplay />
<AutoClear />
<Suspense fallback={<ModelLoader />}>
<Suspense>
{/* Controls within Suspense to allow for frame-perfect start of camera auto-rotate */}
<Controls naked={naked} />
<InitialFogReveal />
{display === TankopediaDisplay.DynamicArmor && <Armor />}
{display === TankopediaDisplay.StaticArmor && (
<StaticArmor thicknessRange={thicknessRange} />
@@ -1,5 +1,5 @@
import { TankType } from '@blitzkit/core';
import { Box, Flex, Heading, Text } from '@radix-ui/themes';
import { Box, Flex, Heading, Spinner, Text } from '@radix-ui/themes';
import { times } from 'lodash-es';
import { Suspense, useEffect, useMemo, useRef } from 'react';
import { NAVBAR_HEIGHT } from '../../../constants/navbar';
@@ -13,11 +13,11 @@ import type { ThicknessRange } from '../../Armor/components/StaticArmor';
import { classIcons } from '../../ClassIcon';
import { Options } from './components/Options';
import { TankSandbox } from './components/TankSandbox';
import { TankSandboxLoader } from './components/TankSandboxLoader';
const tankDefinitions = await awaitableTankDefinitions;
export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
const revealed = TankopediaEphemeral.use((state) => state.revealed);
const disturbed = TankopediaEphemeral.use((state) => state.disturbed);
const { unwrap } = useLocale();
const canvas = useRef<HTMLCanvasElement>(null);
@@ -92,63 +92,6 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
top={isFullScreen ? '0' : undefined}
left={isFullScreen ? '0' : undefined}
>
<Flex
justify="center"
position={{ initial: 'relative', md: 'absolute' }}
left={{ initial: '0', md: '5', lg: '9' }}
py="4"
top={{ initial: '8', md: '50%' }}
style={{ transform: 'translateY(-50%)', transitionDuration: '200ms' }}
direction="column"
align={{ initial: 'center', md: 'start' }}
gap={disturbed ? '0' : { initial: '0', md: '2' }}
>
<Flex align="center" gap="3">
<Heading
color={treeColor}
trim="end"
size={
disturbed
? { initial: '7', lg: '8' }
: { initial: '8', lg: '9' }
}
style={{ transitionDuration: '200ms' }}
>
<Icon width="1em" height="1em" />
</Heading>
<Heading
weight="bold"
size={
disturbed
? { initial: '7', lg: '8' }
: { initial: '8', lg: '9' }
}
style={{ transitionDuration: '200ms' }}
wrap="nowrap"
color={treeColor}
>
{unwrap(protagonist.name)}
</Heading>
</Flex>
<Box ml="3">
<Text
color="gray"
size={{ initial: '3', lg: disturbed ? '4' : '5' }}
weight="light"
ml={{
initial: '0',
md: disturbed ? 'var(--font-size-7)' : 'var(--font-size-8)',
lg: disturbed ? 'var(--font-size-8)' : 'var(--font-size-9)',
}}
style={{ transitionDuration: '200ms' }}
>
BlitzKit Tankopedia
</Text>
</Box>
</Flex>
<Box
className="tank-sandbox-container"
flexGrow="1"
@@ -164,9 +107,9 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
left={disturbed ? '0' : { initial: '0', md: '12.5%' }}
style={{ transitionDuration: '200ms' }}
>
{skeleton && <TankSandboxLoader id={protagonist.id} />}
{/* {skeleton && <TankSandboxLoader id={protagonist.id} />} */}
<Suspense fallback={<TankSandboxLoader id={protagonist.id} />}>
<Suspense>
<TankSandbox ref={canvas} thicknessRange={thicknessRange} />
</Suspense>
</Box>
@@ -178,6 +121,86 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
/>
</Box>
</Box>
<Flex
justify="center"
position={{ initial: 'relative', md: 'absolute' }}
left={{ initial: '0', md: '5', lg: '9' }}
py="4"
top={{ initial: '8', md: '50%' }}
style={{
transform: 'translate(0, -50%)',
transitionDuration: '200ms',
}}
direction="column"
align={{ initial: 'center', md: 'start' }}
gap={disturbed ? '0' : { initial: '0', md: '2' }}
>
<Flex align="center" gap="3">
<Heading
color={treeColor}
trim="end"
size={
disturbed
? { initial: '6', lg: '7' }
: { initial: '7', lg: '8' }
}
style={{ transitionDuration: '200ms', position: 'relative' }}
>
<Icon
width="1em"
height="1em"
style={{ opacity: revealed ? 1 : 0 }}
/>
<Spinner
style={{
position: 'absolute',
top: '0',
left: '0',
width: '1em',
height: '1em',
opacity: revealed ? 0 : 1,
}}
/>
</Heading>
<Heading
weight="regular"
size={
disturbed
? { initial: '7', lg: '8' }
: { initial: '8', lg: '9' }
}
style={{ transitionDuration: '200ms' }}
wrap="nowrap"
color={treeColor}
>
{unwrap(protagonist.name)}
</Heading>
</Flex>
<Box
ml="3"
style={{
transitionDuration: '200ms',
opacity: revealed ? 1 : 0,
transform: revealed ? 'translateY(0)' : 'translateY(-100%)',
}}
>
<Text
color="gray"
size={{ initial: '3', lg: disturbed ? '4' : '5' }}
weight="light"
ml={{
initial: '0',
md: disturbed ? 'var(--font-size-6)' : 'var(--font-size-7)',
lg: disturbed ? 'var(--font-size-7)' : 'var(--font-size-8)',
}}
>
BlitzKit Tankopedia
</Text>
</Box>
</Flex>
</Flex>
</Flex>
);
@@ -1,7 +1,7 @@
import { useEffect, useState } from 'react';
export function useFullscreenAvailability() {
const [available, setAvailable] = useState(false);
export function useFullscreenAvailability(assumption = false) {
const [available, setAvailable] = useState(assumption);
useEffect(() => {
setAvailable(document.fullscreenEnabled);
@@ -97,6 +97,7 @@ export const SHOOTING_RANGE_ZOOM_COEFFICIENTS: Record<
interface TankopediaEphemeral {
disturbed: boolean;
revealed: boolean;
shot?: Shot;
shootingRangeZoom: ShootingRangeZoom;
skills: Record<string, number>;
@@ -130,6 +131,7 @@ export const TankopediaEphemeral = createContextualStore(
return create<TankopediaEphemeral>()(
subscribeWithSelector<TankopediaEphemeral>(() => ({
disturbed: false,
revealed: false,
shootingRangeZoom: ShootingRangeZoom.Arcade,
relativeAgainst: TankopediaRelativeAgainst.Class,
editStatic: false,