Fix dynamic armor overlay vanishing on re-render

PrimaryArmorSceneComponent rebuilt its ShaderMaterial every render with null uniforms that were only set once on mount. Create it once with useState instead.
This commit is contained in:
Mark Greene committed 2026-10-02 19:24:43 +13:00
1 parent 763ecf22b0
commit 0941c23a3c
1 file changed
+46 -43
@@ -5,7 +5,7 @@ import {
} from "@blitzkit/core"; } from "@blitzkit/core";
import { invalidate, useFrame } from "@react-three/fiber"; import { invalidate, useFrame } from "@react-three/fiber";
import type { QuicklimeEvent } from "quicklime"; import type { QuicklimeEvent } from "quicklime";
import { useEffect } from "react"; import { useEffect, useState } from "react";
import { import {
MeshBasicMaterial, MeshBasicMaterial,
NormalBlending, NormalBlending,
@@ -43,42 +43,45 @@ export function PrimaryArmorSceneComponent({
node, node,
thickness, thickness,
}: PrimaryArmorSceneComponentProps) { }: PrimaryArmorSceneComponentProps) {
const material = new ShaderMaterial({ const [material] = useState(
fragmentShader, () =>
vertexShader, new ShaderMaterial({
fragmentShader,
vertexShader,
fog: true, fog: true,
transparent: true, transparent: true,
blending: isHalloween ? SubtractiveBlending : NormalBlending, blending: isHalloween ? SubtractiveBlending : NormalBlending,
uniforms: UniformsUtils.merge([ uniforms: UniformsUtils.merge([
UniformsLib.common, UniformsLib.common,
UniformsLib.fog, UniformsLib.fog,
{ {
thickness: { value: null }, thickness: { value: null },
penetration: { value: null }, penetration: { value: null },
caliber: { value: null }, caliber: { value: null },
ricochet: { value: null }, ricochet: { value: null },
normalization: { value: null }, normalization: { value: null },
isExplosive: { value: null }, isExplosive: { value: null },
canSplash: { value: null }, canSplash: { value: null },
damage: { value: null }, damage: { value: null },
explosionRadius: { value: null }, explosionRadius: { value: null },
greenPenetration: { value: null }, greenPenetration: { value: null },
advancedHighlighting: { value: null }, advancedHighlighting: { value: null },
opaque: { value: null }, opaque: { value: null },
inverseProjectionMatrix: { value: null }, inverseProjectionMatrix: { value: null },
resolution: { value: new Vector2() }, resolution: { value: new Vector2() },
spacedArmorBuffer: { value: null }, spacedArmorBuffer: { value: null },
spacedArmorDepth: { value: null }, spacedArmorDepth: { value: null },
opacity: { value: 0 }, opacity: { value: 0 },
}, },
]), ]),
}); })
);
useEffect(() => { useEffect(() => {
function handleShellChange() { function handleShellChange() {
@@ -88,10 +91,10 @@ export function PrimaryArmorSceneComponent({
material.uniforms.caliber.value = shell.caliber; material.uniforms.caliber.value = shell.caliber;
material.uniforms.ricochet.value = degToRad( material.uniforms.ricochet.value = degToRad(
isExplosive(shell.type) ? 90 : shell.ricochet!, isExplosive(shell.type) ? 90 : shell.ricochet!
); );
material.uniforms.normalization.value = degToRad( material.uniforms.normalization.value = degToRad(
shell.normalization ?? 0, shell.normalization ?? 0
); );
material.uniforms.isExplosive.value = isExplosive(shell.type); material.uniforms.isExplosive.value = isExplosive(shell.type);
material.uniforms.canSplash.value = canSplash(shell.type); material.uniforms.canSplash.value = canSplash(shell.type);
@@ -121,7 +124,7 @@ export function PrimaryArmorSceneComponent({
const hasEnhancedArmor = hasEquipment( const hasEnhancedArmor = hasEquipment(
110, 110,
Duel.state.protagonist.tank.equipment_preset, Duel.state.protagonist.tank.equipment_preset,
equipment, equipment
); );
const equalizer = const equalizer =
(Duel.state.equalize (Duel.state.equalize
@@ -142,7 +145,7 @@ export function PrimaryArmorSceneComponent({
const hasCalibratedShells = hasEquipment( const hasCalibratedShells = hasEquipment(
103, 103,
Duel.state.antagonist.tank.equipment_preset, Duel.state.antagonist.tank.equipment_preset,
equipment, equipment
); );
material.uniforms.penetration.value = material.uniforms.penetration.value =
@@ -151,7 +154,7 @@ export function PrimaryArmorSceneComponent({
hasCalibratedShells, hasCalibratedShells,
Duel.state.equalize, Duel.state.equalize,
shell.type, shell.type,
Duel.state.antagonist.tank.equalizer, Duel.state.antagonist.tank.equalizer
); );
if (!noInvalidate) invalidate(); if (!noInvalidate) invalidate();
@@ -164,7 +167,7 @@ export function PrimaryArmorSceneComponent({
handleShellChange(); handleShellChange();
handleGreenPenetrationChange(TankopediaPersistent.state.greenPenetration); handleGreenPenetrationChange(TankopediaPersistent.state.greenPenetration);
handleAdvancedHighlightingChange( handleAdvancedHighlightingChange(
TankopediaPersistent.state.advancedHighlighting, TankopediaPersistent.state.advancedHighlighting
); );
handleOpaqueChange(TankopediaPersistent.state.opaque); handleOpaqueChange(TankopediaPersistent.state.opaque);
handleWireframeChange(TankopediaPersistent.state.wireframe); handleWireframeChange(TankopediaPersistent.state.wireframe);
@@ -178,27 +181,27 @@ export function PrimaryArmorSceneComponent({
Tankopedia.on((state) => state.customShell, handleShellChange), Tankopedia.on((state) => state.customShell, handleShellChange),
TankopediaPersistent.on( TankopediaPersistent.on(
(state) => state.greenPenetration, (state) => state.greenPenetration,
handleGreenPenetrationChange, handleGreenPenetrationChange
), ),
TankopediaPersistent.on( TankopediaPersistent.on(
(state) => state.advancedHighlighting, (state) => state.advancedHighlighting,
handleAdvancedHighlightingChange, handleAdvancedHighlightingChange
), ),
TankopediaPersistent.on((state) => state.opaque, handleOpaqueChange), TankopediaPersistent.on((state) => state.opaque, handleOpaqueChange),
Duel.on( Duel.on(
(state) => state.protagonist.equipmentMatrix, (state) => state.protagonist.equipmentMatrix,
() => handleProtagonistEquipmentChange(), () => handleProtagonistEquipmentChange()
), ),
Duel.on( Duel.on(
(state) => state.antagonist.equipmentMatrix, (state) => state.antagonist.equipmentMatrix,
() => handleAntagonistEquipmentChange(), () => handleAntagonistEquipmentChange()
), ),
Duel.on( Duel.on(
(state) => state.equalize, (state) => state.equalize,
() => { () => {
handleProtagonistEquipmentChange(); handleProtagonistEquipmentChange();
handleAntagonistEquipmentChange(); handleAntagonistEquipmentChange();
}, }
), ),
() => transitionEvent.off(handleTransitionEvent), () => transitionEvent.off(handleTransitionEvent),
]; ];
@@ -210,7 +213,7 @@ export function PrimaryArmorSceneComponent({
useFrame(({ gl, camera }) => { useFrame(({ gl, camera }) => {
gl.getSize(material.uniforms.resolution.value).multiplyScalar( gl.getSize(material.uniforms.resolution.value).multiplyScalar(
gl.getPixelRatio(), gl.getPixelRatio()
); );
material.uniforms.spacedArmorBuffer.value = spacedArmorRenderTarget.texture; material.uniforms.spacedArmorBuffer.value = spacedArmorRenderTarget.texture;
material.uniforms.spacedArmorDepth.value = material.uniforms.spacedArmorDepth.value =