mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-23 19:49:50 +02:00
feat: foggy transition
This commit is contained in:
1 parent
7a18de6650
commit
6d8387fa9d
7 files changed
+109
-40
No files matched your search
+32
-19
@@ -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,27 +43,33 @@ export function PrimaryArmorSceneComponent({
|
|||||||
fragmentShader,
|
fragmentShader,
|
||||||
vertexShader,
|
vertexShader,
|
||||||
|
|
||||||
|
fog: true,
|
||||||
transparent: true,
|
transparent: true,
|
||||||
|
|
||||||
uniforms: {
|
uniforms: UniformsUtils.merge([
|
||||||
thickness: { value: null },
|
UniformsLib.common,
|
||||||
penetration: { value: null },
|
UniformsLib.fog,
|
||||||
caliber: { value: null },
|
|
||||||
ricochet: { value: null },
|
|
||||||
normalization: { value: null },
|
|
||||||
isExplosive: { value: null },
|
|
||||||
canSplash: { value: null },
|
|
||||||
damage: { value: null },
|
|
||||||
explosionRadius: { value: null },
|
|
||||||
greenPenetration: { value: null },
|
|
||||||
advancedHighlighting: { value: null },
|
|
||||||
opaque: { value: null },
|
|
||||||
|
|
||||||
inverseProjectionMatrix: { value: null },
|
{
|
||||||
resolution: { value: new Vector2() },
|
thickness: { value: null },
|
||||||
spacedArmorBuffer: { value: null },
|
penetration: { value: null },
|
||||||
spacedArmorDepth: { value: null },
|
caliber: { value: null },
|
||||||
},
|
ricochet: { value: null },
|
||||||
|
normalization: { value: null },
|
||||||
|
isExplosive: { value: null },
|
||||||
|
canSplash: { value: null },
|
||||||
|
damage: { value: null },
|
||||||
|
explosionRadius: { value: null },
|
||||||
|
greenPenetration: { value: null },
|
||||||
|
advancedHighlighting: { value: null },
|
||||||
|
opaque: { value: null },
|
||||||
|
|
||||||
|
inverseProjectionMatrix: { value: null },
|
||||||
|
resolution: { value: new Vector2() },
|
||||||
|
spacedArmorBuffer: { value: null },
|
||||||
|
spacedArmorDepth: { value: null },
|
||||||
|
},
|
||||||
|
]),
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
+8
@@ -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>
|
||||||
}
|
}
|
||||||
+10
-4
@@ -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,
|
||||||
|
|||||||
+5
-3
@@ -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;
|
||||||
|
|||||||
+44
-4
@@ -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)`
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user