mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-23 19:49:50 +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 wireframe = TankopediaPersistent.use((state) => state.wireframe);
|
||||||
const opaque = TankopediaPersistent.use((state) => state.opaque);
|
const opaque = TankopediaPersistent.use((state) => state.opaque);
|
||||||
const fullScreenAvailable = useFullscreenAvailability();
|
const fullScreenAvailable = useFullscreenAvailability(true);
|
||||||
const developerMode = App.use((state) => state.developerMode);
|
const developerMode = App.use((state) => state.developerMode);
|
||||||
const protagonistTank = Duel.use((state) => state.protagonist.tank);
|
const protagonistTank = Duel.use((state) => state.protagonist.tank);
|
||||||
const antagonistGun = Duel.use((state) => state.antagonist.gun);
|
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';
|
} from '../../../../Armor/components/StaticArmor';
|
||||||
import { AutoClear } from './components/AutoClear';
|
import { AutoClear } from './components/AutoClear';
|
||||||
import { Controls } from './components/Control';
|
import { Controls } from './components/Control';
|
||||||
|
import { InitialFogReveal } from './components/InitialFogReveal';
|
||||||
import { Lighting } from './components/Lighting';
|
import { Lighting } from './components/Lighting';
|
||||||
import { ModelLoader } from './components/ModelLoader';
|
|
||||||
import { SceneProps } from './components/SceneProps';
|
import { SceneProps } from './components/SceneProps';
|
||||||
import { TankModel } from './components/TankModel';
|
import { TankModel } from './components/TankModel';
|
||||||
|
|
||||||
@@ -38,15 +38,15 @@ interface TankSandboxProps {
|
|||||||
naked?: boolean;
|
naked?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const near0 = 15;
|
export const forNear0 = 15;
|
||||||
const far0 = 25;
|
export const fogFar0 = 25;
|
||||||
const near1 = 0;
|
export const forNear1 = 0;
|
||||||
const far1 = 0;
|
export const fogFar1 = 0;
|
||||||
const animationTime = 0.5;
|
export const fogAnimationTime = 0.5;
|
||||||
|
|
||||||
export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
|
export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
|
||||||
({ thicknessRange, naked }, ref) => {
|
({ thicknessRange, naked }, ref) => {
|
||||||
const fog = useRef(new Fog('black', near0, far0));
|
const fog = useRef(new Fog('black', forNear1, fogFar1));
|
||||||
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
|
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
|
||||||
const canvas = useRef<HTMLCanvasElement>(null);
|
const canvas = useRef<HTMLCanvasElement>(null);
|
||||||
const hasImprovedVerticalStabilizer = useEquipment(122);
|
const hasImprovedVerticalStabilizer = useEquipment(122);
|
||||||
@@ -73,11 +73,11 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
|
|||||||
|
|
||||||
const interval = setInterval((e) => {
|
const interval = setInterval((e) => {
|
||||||
const t = (Date.now() - t0) / 1000;
|
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 y = Math.cbrt(-2 * Math.abs(x - 0.5)) + 1;
|
||||||
|
|
||||||
const near = near0 + (near1 - near0) * y;
|
const near = forNear0 + (forNear1 - forNear0) * y;
|
||||||
const far = far0 + (far1 - far0) * y;
|
const far = fogFar0 + (fogFar1 - fogFar0) * y;
|
||||||
|
|
||||||
fog.current.near = near;
|
fog.current.near = near;
|
||||||
fog.current.far = far;
|
fog.current.far = far;
|
||||||
@@ -86,8 +86,8 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
|
|||||||
|
|
||||||
if (rawDisplay !== display && x >= 0.5) setDisplay(rawDisplay);
|
if (rawDisplay !== display && x >= 0.5) setDisplay(rawDisplay);
|
||||||
if (x >= 1) {
|
if (x >= 1) {
|
||||||
fog.current.near = near0;
|
fog.current.near = forNear0;
|
||||||
fog.current.far = far0;
|
fog.current.far = fogFar0;
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
}
|
}
|
||||||
}, 1000 / 60);
|
}, 1000 / 60);
|
||||||
@@ -234,7 +234,6 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
|
|||||||
outline: naked ? '1rem red solid' : undefined,
|
outline: naked ? '1rem red solid' : undefined,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Controls naked={naked} />
|
|
||||||
{!naked && <SceneProps />}
|
{!naked && <SceneProps />}
|
||||||
{(display === TankopediaDisplay.Model ||
|
{(display === TankopediaDisplay.Model ||
|
||||||
(display === TankopediaDisplay.DynamicArmor &&
|
(display === TankopediaDisplay.DynamicArmor &&
|
||||||
@@ -243,7 +242,11 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
|
|||||||
<ArmorPlateDisplay />
|
<ArmorPlateDisplay />
|
||||||
<AutoClear />
|
<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.DynamicArmor && <Armor />}
|
||||||
{display === TankopediaDisplay.StaticArmor && (
|
{display === TankopediaDisplay.StaticArmor && (
|
||||||
<StaticArmor thicknessRange={thicknessRange} />
|
<StaticArmor thicknessRange={thicknessRange} />
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { TankType } from '@blitzkit/core';
|
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 { times } from 'lodash-es';
|
||||||
import { Suspense, useEffect, useMemo, useRef } from 'react';
|
import { Suspense, useEffect, useMemo, useRef } from 'react';
|
||||||
import { NAVBAR_HEIGHT } from '../../../constants/navbar';
|
import { NAVBAR_HEIGHT } from '../../../constants/navbar';
|
||||||
@@ -13,11 +13,11 @@ import type { ThicknessRange } from '../../Armor/components/StaticArmor';
|
|||||||
import { classIcons } from '../../ClassIcon';
|
import { classIcons } from '../../ClassIcon';
|
||||||
import { Options } from './components/Options';
|
import { Options } from './components/Options';
|
||||||
import { TankSandbox } from './components/TankSandbox';
|
import { TankSandbox } from './components/TankSandbox';
|
||||||
import { TankSandboxLoader } from './components/TankSandboxLoader';
|
|
||||||
|
|
||||||
const tankDefinitions = await awaitableTankDefinitions;
|
const tankDefinitions = await awaitableTankDefinitions;
|
||||||
|
|
||||||
export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
|
export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
|
||||||
|
const revealed = TankopediaEphemeral.use((state) => state.revealed);
|
||||||
const disturbed = TankopediaEphemeral.use((state) => state.disturbed);
|
const disturbed = TankopediaEphemeral.use((state) => state.disturbed);
|
||||||
const { unwrap } = useLocale();
|
const { unwrap } = useLocale();
|
||||||
const canvas = useRef<HTMLCanvasElement>(null);
|
const canvas = useRef<HTMLCanvasElement>(null);
|
||||||
@@ -92,63 +92,6 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
|
|||||||
top={isFullScreen ? '0' : undefined}
|
top={isFullScreen ? '0' : undefined}
|
||||||
left={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
|
<Box
|
||||||
className="tank-sandbox-container"
|
className="tank-sandbox-container"
|
||||||
flexGrow="1"
|
flexGrow="1"
|
||||||
@@ -164,9 +107,9 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
|
|||||||
left={disturbed ? '0' : { initial: '0', md: '12.5%' }}
|
left={disturbed ? '0' : { initial: '0', md: '12.5%' }}
|
||||||
style={{ transitionDuration: '200ms' }}
|
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} />
|
<TankSandbox ref={canvas} thicknessRange={thicknessRange} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -178,6 +121,86 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
|
|||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</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>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
export function useFullscreenAvailability() {
|
export function useFullscreenAvailability(assumption = false) {
|
||||||
const [available, setAvailable] = useState(false);
|
const [available, setAvailable] = useState(assumption);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setAvailable(document.fullscreenEnabled);
|
setAvailable(document.fullscreenEnabled);
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ export const SHOOTING_RANGE_ZOOM_COEFFICIENTS: Record<
|
|||||||
|
|
||||||
interface TankopediaEphemeral {
|
interface TankopediaEphemeral {
|
||||||
disturbed: boolean;
|
disturbed: boolean;
|
||||||
|
revealed: boolean;
|
||||||
shot?: Shot;
|
shot?: Shot;
|
||||||
shootingRangeZoom: ShootingRangeZoom;
|
shootingRangeZoom: ShootingRangeZoom;
|
||||||
skills: Record<string, number>;
|
skills: Record<string, number>;
|
||||||
@@ -130,6 +131,7 @@ export const TankopediaEphemeral = createContextualStore(
|
|||||||
return create<TankopediaEphemeral>()(
|
return create<TankopediaEphemeral>()(
|
||||||
subscribeWithSelector<TankopediaEphemeral>(() => ({
|
subscribeWithSelector<TankopediaEphemeral>(() => ({
|
||||||
disturbed: false,
|
disturbed: false,
|
||||||
|
revealed: false,
|
||||||
shootingRangeZoom: ShootingRangeZoom.Arcade,
|
shootingRangeZoom: ShootingRangeZoom.Arcade,
|
||||||
relativeAgainst: TankopediaRelativeAgainst.Class,
|
relativeAgainst: TankopediaRelativeAgainst.Class,
|
||||||
editStatic: false,
|
editStatic: false,
|
||||||
|
|||||||
Reference in new issue
Block a user