From 1427f61c5202a34a2a5bf698b84cf6c9b6e1f8fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Thu, 27 Nov 2025 13:42:37 -0600 Subject: [PATCH] feat: complete transition --- .../Armor/components/PrimaryArmorScene.tsx | 4 +++- .../PrimaryArmorSceneComponent/index.tsx | 13 ++++++++++++- .../shaders/fragment.glsl | 7 ++----- .../shaders/vertex.glsl | 6 ------ .../components/StaticArmorSceneComponent.tsx | 19 +++++++++++++++++-- .../TankSandbox/components/Lighting.tsx | 7 +++++++ 6 files changed, 41 insertions(+), 15 deletions(-) diff --git a/packages/website/src/components/Armor/components/PrimaryArmorScene.tsx b/packages/website/src/components/Armor/components/PrimaryArmorScene.tsx index 5d016e978..acc4b125c 100644 --- a/packages/website/src/components/Armor/components/PrimaryArmorScene.tsx +++ b/packages/website/src/components/Armor/components/PrimaryArmorScene.tsx @@ -67,7 +67,9 @@ export const PrimaryArmorScene = memo(() => { {armorNodes.map((node) => { const isCurrentTurret = node.name.startsWith( - `turret_${turretModelDefinition.model_id.toString().padStart(2, "0")}` + `turret_${turretModelDefinition.model_id + .toString() + .padStart(2, "0")}` ); const isVisible = isCurrentTurret; const armorId = nameToArmorId(node.name); diff --git a/packages/website/src/components/Armor/components/PrimaryArmorSceneComponent/index.tsx b/packages/website/src/components/Armor/components/PrimaryArmorSceneComponent/index.tsx index c1220e84d..866299ec5 100644 --- a/packages/website/src/components/Armor/components/PrimaryArmorSceneComponent/index.tsx +++ b/packages/website/src/components/Armor/components/PrimaryArmorSceneComponent/index.tsx @@ -4,6 +4,7 @@ import { resolvePenetrationCoefficient, } from "@blitzkit/core"; import { invalidate, useFrame } from "@react-three/fiber"; +import type { QuicklimeEvent } from "quicklime"; import { useEffect } from "react"; import { MeshBasicMaterial, @@ -21,6 +22,7 @@ import { jsxTree } from "../../../../core/blitzkit/jsxTree"; import { Duel, type EquipmentMatrix } from "../../../../stores/duel"; import { Tankopedia } from "../../../../stores/tankopedia"; import { TankopediaPersistent } from "../../../../stores/tankopediaPersistent"; +import { transitionEvent } from "../../../Tankopedia/HeroSection/components/TankSandbox/components/Lighting"; import fragmentShader from "./shaders/fragment.glsl?raw"; import vertexShader from "./shaders/vertex.glsl?raw"; import { spacedArmorRenderTarget } from "./target"; @@ -72,6 +74,8 @@ export function PrimaryArmorSceneComponent({ resolution: { value: new Vector2() }, spacedArmorBuffer: { value: null }, spacedArmorDepth: { value: null }, + + opacity: { value: 0 }, }, ]), }); @@ -154,6 +158,10 @@ export function PrimaryArmorSceneComponent({ if (!noInvalidate) invalidate(); } + function handleTransitionEvent(event: QuicklimeEvent) { + material.uniforms.opacity.value = event.data; + } + handleShellChange(); handleGreenPenetrationChange(TankopediaPersistent.state.greenPenetration); handleAdvancedHighlightingChange( @@ -164,6 +172,8 @@ export function PrimaryArmorSceneComponent({ handleProtagonistEquipmentChange(Duel.state.protagonist.equipmentMatrix); handleAntagonistEquipmentChange(Duel.state.antagonist.equipmentMatrix); + transitionEvent.on(handleTransitionEvent); + const unsubscribes = [ Duel.on((state) => state.antagonist.shell, handleShellChange), Tankopedia.on((state) => state.customShell, handleShellChange), @@ -184,12 +194,13 @@ export function PrimaryArmorSceneComponent({ (state) => state.antagonist.equipmentMatrix, (equipment) => handleAntagonistEquipmentChange(equipment) ), + () => transitionEvent.off(handleTransitionEvent), ]; return () => { unsubscribes.forEach((unsubscribe) => unsubscribe()); }; - }); + }, []); useFrame(({ gl, camera }) => { gl.getSize(material.uniforms.resolution.value).multiplyScalar( diff --git a/packages/website/src/components/Armor/components/PrimaryArmorSceneComponent/shaders/fragment.glsl b/packages/website/src/components/Armor/components/PrimaryArmorSceneComponent/shaders/fragment.glsl index b77093bb1..eb5b86d1d 100644 --- a/packages/website/src/components/Armor/components/PrimaryArmorSceneComponent/shaders/fragment.glsl +++ b/packages/website/src/components/Armor/components/PrimaryArmorSceneComponent/shaders/fragment.glsl @@ -1,12 +1,9 @@ precision mediump float; -#define USE_FOG - -#include - varying vec3 vNormal; varying vec3 vViewPosition; +uniform float opacity; uniform float thickness; uniform float penetration; uniform float caliber; @@ -119,5 +116,5 @@ void main() { gl_FragColor = vec4(baseColor, (1.0 - penetrationChance) * alpha); } - #include + gl_FragColor.a *= opacity; } \ No newline at end of file diff --git a/packages/website/src/components/Armor/components/PrimaryArmorSceneComponent/shaders/vertex.glsl b/packages/website/src/components/Armor/components/PrimaryArmorSceneComponent/shaders/vertex.glsl index 2c0140061..ebcd2593a 100644 --- a/packages/website/src/components/Armor/components/PrimaryArmorSceneComponent/shaders/vertex.glsl +++ b/packages/website/src/components/Armor/components/PrimaryArmorSceneComponent/shaders/vertex.glsl @@ -1,9 +1,5 @@ precision mediump float; -#define USE_FOG - -#include - varying vec3 vNormal; varying vec3 vViewPosition; @@ -14,6 +10,4 @@ void main() { vNormal = normalMatrix * normal; gl_Position = projectionMatrix * mvPosition; - - #include } diff --git a/packages/website/src/components/Armor/components/StaticArmorSceneComponent.tsx b/packages/website/src/components/Armor/components/StaticArmorSceneComponent.tsx index eef50a2da..79186e8bf 100644 --- a/packages/website/src/components/Armor/components/StaticArmorSceneComponent.tsx +++ b/packages/website/src/components/Armor/components/StaticArmorSceneComponent.tsx @@ -24,6 +24,7 @@ import { } from "../../../core/blitzkit/modelTransform"; import { discardClippingPlane } from "../../../core/three/discardClippingPlane"; import { Tankopedia } from "../../../stores/tankopedia"; +import { transitionEvent } from "../../Tankopedia/HeroSection/components/TankSandbox/components/Lighting"; import { ArmorType } from "./SpacedArmorScene"; import type { ArmorUserData, @@ -108,10 +109,11 @@ export function StaticArmorSceneComponent({ () => new MeshBasicMaterial({ color, - opacity, - transparent: opacity < 1, + transparent: true, depthWrite, ...(props.clip ? { clippingPlanes: [props.clip] } : {}), + opacity: 0, + userData: { opacity0: opacity }, }), [thickness] ); @@ -121,6 +123,8 @@ export function StaticArmorSceneComponent({ color: color .clone() .multiplyScalar(props.type === ArmorType.Spaced ? 2 ** 2 : 2 ** -1), + opacity: 0, + transparent: true, }), [thickness] ); @@ -163,11 +167,22 @@ export function StaticArmorSceneComponent({ surfaceMaterial.needsUpdate = true; } + function handleTransitionEvent(event: QuicklimeEvent) { + surfaceMaterial.opacity = surfaceMaterial.userData.opacity0 * event.data; + outlineMaterial.opacity = event.data; + + surfaceMaterial.transparent = surfaceMaterial.opacity < 1; + outlineMaterial.transparent = outlineMaterial.opacity < 1; + } + + transitionEvent.on(handleTransitionEvent); + const unsubscribes = [ Tankopedia.on( (state) => state.highlightArmor?.name, handleHighlightArmor ), + () => transitionEvent.off(handleTransitionEvent), ]; if (props.clip) { diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/Lighting.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/Lighting.tsx index 1fbae0edc..70ed33161 100644 --- a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/Lighting.tsx +++ b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/Lighting.tsx @@ -1,5 +1,6 @@ import { useFrame, useThree } from "@react-three/fiber"; import { times } from "lodash-es"; +import { Quicklime } from "quicklime"; import { useEffect, useRef } from "react"; import { SpotLight, type Group } from "three"; import { clamp, degToRad, lerp } from "three/src/math/MathUtils.js"; @@ -19,6 +20,8 @@ const LIGHT_HEIGHT_1 = 10; const INTENSITY_0 = 60; const INTENSITY_1 = 30; +export const transitionEvent = new Quicklime(0); + export function Lighting() { const wrapper = useRef(null!); const t0 = useRef(0); @@ -50,6 +53,10 @@ export function Lighting() { ); const t = (0.5 * Math.sin(Math.PI * (x + 0.5)) + 0.5) ** 2; + if (t !== transitionEvent.last) { + transitionEvent.dispatch(t); + } + for (const child of wrapper.current.children) { if (child instanceof SpotLight) { child.angle = ANGLE * t;