feat: foggy transition

This commit is contained in:
tresabhi committed 2025-04-14 18:52:49 -05:00
1 parent 7a18de6650
commit 6d8387fa9d
7 files changed
+91 -22

No files matched your search

@@ -5,7 +5,14 @@ import {
} from '@blitzkit/core'; } from '@blitzkit/core';
import { invalidate, useFrame } from '@react-three/fiber'; import { invalidate, useFrame } from '@react-three/fiber';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { MeshBasicMaterial, Object3D, ShaderMaterial, Vector2 } from 'three'; import {
MeshBasicMaterial,
Object3D,
ShaderMaterial,
UniformsLib,
UniformsUtils,
Vector2,
} from 'three';
import { degToRad } from 'three/src/math/MathUtils.js'; import { degToRad } from 'three/src/math/MathUtils.js';
import { hasEquipment } from '../../../../core/blitzkit/hasEquipment'; import { hasEquipment } from '../../../../core/blitzkit/hasEquipment';
import { jsxTree } from '../../../../core/blitzkit/jsxTree'; import { jsxTree } from '../../../../core/blitzkit/jsxTree';
@@ -36,9 +43,14 @@ export function PrimaryArmorSceneComponent({
fragmentShader, fragmentShader,
vertexShader, vertexShader,
fog: true,
transparent: true, transparent: true,
uniforms: { uniforms: UniformsUtils.merge([
UniformsLib.common,
UniformsLib.fog,
{
thickness: { value: null }, thickness: { value: null },
penetration: { value: null }, penetration: { value: null },
caliber: { value: null }, caliber: { value: null },
@@ -57,6 +69,7 @@ export function PrimaryArmorSceneComponent({
spacedArmorBuffer: { value: null }, spacedArmorBuffer: { value: null },
spacedArmorDepth: { value: null }, spacedArmorDepth: { value: null },
}, },
]),
}); });
useEffect(() => { useEffect(() => {
@@ -1,3 +1,7 @@
#define USE_FOG
#include <fog_pars_fragment>
varying vec3 vNormal; varying vec3 vNormal;
varying vec3 vViewPosition; varying vec3 vViewPosition;
@@ -39,6 +43,8 @@ vec3 getPenetrationColor(bool isThreeCalibersRule, bool couldHaveRicochet) {
} }
void main() { void main() {
#include <clipping_planes_fragment>
vec2 screenCoord = gl_FragCoord.xy / resolution; vec2 screenCoord = gl_FragCoord.xy / resolution;
vec4 spacedArmorData = texture2D(spacedArmorBuffer, screenCoord); vec4 spacedArmorData = texture2D(spacedArmorBuffer, screenCoord);
bool isUnderSpacedArmor = spacedArmorData.a != 0.0; bool isUnderSpacedArmor = spacedArmorData.a != 0.0;
@@ -111,4 +117,6 @@ void main() {
} else { } else {
gl_FragColor = vec4(baseColor, (1.0 - penetrationChance) * alpha); gl_FragColor = vec4(baseColor, (1.0 - penetrationChance) * alpha);
} }
#include <fog_fragment>
} }
@@ -1,11 +1,17 @@
#define USE_FOG
#include <fog_pars_vertex>
varying vec3 vNormal; varying vec3 vNormal;
varying vec3 vViewPosition; varying vec3 vViewPosition;
void main() { void main() {
vec4 vViewPosition4 = modelViewMatrix * vec4(position, 1.0); vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
vViewPosition = vViewPosition4.xyz; vViewPosition = mvPosition.xyz;
vNormal = normalMatrix * normal; vNormal = normalMatrix * normal;
gl_Position = projectionMatrix * vViewPosition4; gl_Position = projectionMatrix * mvPosition;
#include <fog_vertex>
} }
@@ -1,4 +1,4 @@
import { createPortal, useFrame } from '@react-three/fiber'; import { createPortal, useFrame, useThree } from '@react-three/fiber';
import { memo, useState } from 'react'; import { memo, useState } from 'react';
import { DepthTexture, Scene, Vector2 } from 'three'; import { DepthTexture, Scene, Vector2 } from 'three';
import { PrimaryArmorScene } from './components/PrimaryArmorScene'; import { PrimaryArmorScene } from './components/PrimaryArmorScene';
@@ -6,8 +6,14 @@ import { spacedArmorRenderTarget } from './components/PrimaryArmorSceneComponent
import { SpacedArmorScene } from './components/SpacedArmorScene'; import { SpacedArmorScene } from './components/SpacedArmorScene';
export const Armor = memo(() => { export const Armor = memo(() => {
const rootScene = useThree((state) => state.scene);
const [spacedArmorScene] = useState(() => new Scene()); const [spacedArmorScene] = useState(() => new Scene());
const [primaryArmorScene] = useState(() => new Scene()); const [primaryArmorScene] = useState(() => {
const primaryArmorScene = new Scene();
primaryArmorScene.fog = rootScene.fog;
return primaryArmorScene;
});
const spacedArmorPortal = createPortal( const spacedArmorPortal = createPortal(
<SpacedArmorScene scene={spacedArmorScene} />, <SpacedArmorScene scene={spacedArmorScene} />,
spacedArmorScene, spacedArmorScene,
@@ -4,14 +4,16 @@ import { Euler } from 'three';
import { degToRad } from 'three/src/math/MathUtils.js'; import { degToRad } from 'three/src/math/MathUtils.js';
import { useModel } from '../../../../../../hooks/useModel'; import { useModel } from '../../../../../../hooks/useModel';
import { Duel } from '../../../../../../stores/duel'; import { Duel } from '../../../../../../stores/duel';
import { TankopediaEphemeral } from '../../../../../../stores/tankopediaEphemeral';
import { TankopediaDisplay } from '../../../../../../stores/tankopediaPersistent/constants'; import { TankopediaDisplay } from '../../../../../../stores/tankopediaPersistent/constants';
const LIGHTS_COUNT = 5; const LIGHTS_COUNT = 5;
export function Lighting() { interface LightingProps {
display: TankopediaDisplay;
}
export function Lighting({ display }: LightingProps) {
const protagonist = Duel.use((state) => state.protagonist); const protagonist = Duel.use((state) => state.protagonist);
const display = TankopediaEphemeral.use((state) => state.display);
const { hasPbr } = useModel(protagonist.tank.id); const { hasPbr } = useModel(protagonist.tank.id);
const isBrighterLighting = const isBrighterLighting =
!hasPbr && display !== TankopediaDisplay.StaticArmor; !hasPbr && display !== TankopediaDisplay.StaticArmor;
@@ -1,12 +1,14 @@
import { BLITZKIT_TANK_ICON_SIZE } from '@blitzkit/core'; import { BLITZKIT_TANK_ICON_SIZE } from '@blitzkit/core';
import { Canvas } from '@react-three/fiber'; import { Canvas, invalidate } from '@react-three/fiber';
import { import {
forwardRef, forwardRef,
Suspense, Suspense,
useEffect, useEffect,
useImperativeHandle, useImperativeHandle,
useRef, useRef,
useState,
} from 'react'; } from 'react';
import { Fog } from 'three';
import { applyPitchYawLimits } from '../../../../../core/blitz/applyPitchYawLimits'; import { applyPitchYawLimits } from '../../../../../core/blitz/applyPitchYawLimits';
import { modelTransformEvent } from '../../../../../core/blitzkit/modelTransform'; import { modelTransformEvent } from '../../../../../core/blitzkit/modelTransform';
import { Pose, poseEvent } from '../../../../../core/blitzkit/pose'; import { Pose, poseEvent } from '../../../../../core/blitzkit/pose';
@@ -36,8 +38,15 @@ interface TankSandboxProps {
naked?: boolean; naked?: boolean;
} }
const near0 = 15;
const far0 = 25;
const near1 = 0;
const far1 = 0;
const animationTime = 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 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);
@@ -50,12 +59,42 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
tankModelDefinition.turrets[protagonistTurret.id]; tankModelDefinition.turrets[protagonistTurret.id];
const gunModelDefinition = const gunModelDefinition =
turretModelDefinition.guns[protagonistGun.gun_type!.value.base.id]; turretModelDefinition.guns[protagonistGun.gun_type!.value.base.id];
const display = TankopediaEphemeral.use((state) => state.display); const rawDisplay = TankopediaEphemeral.use((state) => state.display);
const [display, setDisplay] = useState(rawDisplay);
const hideTankModelUnderArmor = TankopediaPersistent.use( const hideTankModelUnderArmor = TankopediaPersistent.use(
(state) => state.hideTankModelUnderArmor, (state) => state.hideTankModelUnderArmor,
); );
const onScreen = useOnScreen(canvas); const onScreen = useOnScreen(canvas);
useEffect(() => {
if (rawDisplay === display) return;
const t0 = Date.now();
const interval = setInterval((e) => {
const t = (Date.now() - t0) / 1000;
const x = t / animationTime;
const y = Math.cbrt(-2 * Math.abs(x - 0.5)) + 1;
const near = near0 + (near1 - near0) * y;
const far = far0 + (far1 - far0) * y;
fog.current.near = near;
fog.current.far = far;
invalidate();
if (rawDisplay !== display && x >= 0.5) setDisplay(rawDisplay);
if (x >= 1) {
fog.current.near = near0;
fog.current.far = far0;
clearInterval(interval);
}
}, 1000 / 60);
return () => clearInterval(interval);
}, [rawDisplay]);
useImperativeHandle(ref, () => canvas.current!, []); useImperativeHandle(ref, () => canvas.current!, []);
function handlePointerDown() { function handlePointerDown() {
@@ -166,6 +205,8 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
return ( return (
<Canvas <Canvas
ref={canvas}
scene={{ fog: naked ? undefined : fog.current }}
frameloop={ frameloop={
onScreen onScreen
? display === TankopediaDisplay.ShootingRange ? display === TankopediaDisplay.ShootingRange
@@ -179,7 +220,6 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
preserveDrawingBuffer: true, preserveDrawingBuffer: true,
}} }}
shadows shadows
ref={canvas}
onPointerDown={handlePointerDown} onPointerDown={handlePointerDown}
onPointerMissed={() => { onPointerMissed={() => {
mutateTankopediaEphemeral((draft) => { mutateTankopediaEphemeral((draft) => {
@@ -208,7 +248,7 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
{display === TankopediaDisplay.StaticArmor && ( {display === TankopediaDisplay.StaticArmor && (
<StaticArmor thicknessRange={thicknessRange} /> <StaticArmor thicknessRange={thicknessRange} />
)} )}
<Lighting /> <Lighting display={display} />
</Suspense> </Suspense>
</Canvas> </Canvas>
); );
@@ -4,7 +4,6 @@ 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';
import { awaitableTankDefinitions } from '../../../core/awaitables/tankDefinitions'; import { awaitableTankDefinitions } from '../../../core/awaitables/tankDefinitions';
import { Var } from '../../../core/radix/var';
import { useFullScreen } from '../../../hooks/useFullScreen'; import { useFullScreen } from '../../../hooks/useFullScreen';
import { useLocale } from '../../../hooks/useLocale'; import { useLocale } from '../../../hooks/useLocale';
import { Duel } from '../../../stores/duel'; import { Duel } from '../../../stores/duel';
@@ -74,15 +73,10 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
}, []); }, []);
return ( return (
<Flex justify="center" style={{ backgroundColor: Var('color-surface') }}> <Flex justify="center" style={{ backgroundColor: 'black' }}>
<Flex <Flex
direction={{ initial: 'column', md: 'row' }} direction={{ initial: 'column', md: 'row' }}
style={{ style={{ zIndex: isFullScreen ? 2 : undefined }}
backgroundColor: isFullScreen
? Var('color-background')
: Var('color-surface'),
zIndex: isFullScreen ? 2 : undefined,
}}
height={ height={
isFullScreen ? '100vh' : `calc(100vh - ${NAVBAR_HEIGHT}px - 8rem)` isFullScreen ? '100vh' : `calc(100vh - ${NAVBAR_HEIGHT}px - 8rem)`
} }