mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 10:47:04 +02:00
feat: slicker loading
This commit is contained in:
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);
|
||||
|
||||
+34
@@ -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;
|
||||
}
|
||||
+17
-14
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user