2025-09-01 22:56:39 -05:00
|
|
|
import { isExplosive, resolvePenetrationCoefficient } from "@blitzkit/core";
|
|
|
|
|
import { useEffect } from "react";
|
2024-02-27 17:15:24 -06:00
|
|
|
import {
|
|
|
|
|
AdditiveBlending,
|
|
|
|
|
MeshBasicMaterial,
|
|
|
|
|
ShaderMaterial,
|
2024-09-22 21:12:18 -05:00
|
|
|
type Object3D,
|
2025-09-01 22:56:39 -05:00
|
|
|
} from "three";
|
|
|
|
|
import { degToRad } from "three/src/math/MathUtils.js";
|
2026-09-27 16:46:22 -05:00
|
|
|
import type { ArmorUserData } from "../SpacedArmorSceneComponent";
|
|
|
|
|
import { hasEquipment } from "../../core/blitzkit/hasEquipment";
|
|
|
|
|
import { jsxTree } from "../../core/blitzkit/jsxTree";
|
|
|
|
|
import { Duel } from "../../stores/duel";
|
|
|
|
|
import { Tankopedia } from "../../stores/tankopedia";
|
|
|
|
|
import { ArmorType } from "./SpacedArmorScene";
|
2025-09-01 22:56:39 -05:00
|
|
|
import fragmentShader from "./shaders/fragment.glsl?raw";
|
|
|
|
|
import vertexShader from "./shaders/vertex.glsl?raw";
|
2024-02-27 17:15:24 -06:00
|
|
|
|
|
|
|
|
interface SpacedArmorSubSpacedProps {
|
|
|
|
|
node: Object3D;
|
|
|
|
|
thickness: number;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const depthWriteMaterial = new MeshBasicMaterial({
|
|
|
|
|
depthWrite: true,
|
|
|
|
|
colorWrite: false,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export function SpacedArmorSubSpaced({
|
|
|
|
|
node,
|
|
|
|
|
thickness,
|
|
|
|
|
}: SpacedArmorSubSpacedProps) {
|
|
|
|
|
const material = new ShaderMaterial({
|
|
|
|
|
fragmentShader,
|
|
|
|
|
vertexShader,
|
|
|
|
|
|
|
|
|
|
depthTest: true,
|
|
|
|
|
depthWrite: false,
|
|
|
|
|
blending: AdditiveBlending,
|
|
|
|
|
|
|
|
|
|
uniforms: {
|
2024-02-27 18:08:50 -06:00
|
|
|
thickness: { value: null },
|
2024-02-27 17:15:24 -06:00
|
|
|
penetration: { value: null },
|
|
|
|
|
caliber: { value: null },
|
|
|
|
|
ricochet: { value: null },
|
|
|
|
|
normalization: { value: null },
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
2024-11-10 20:59:01 -06:00
|
|
|
function handleShellChange() {
|
2025-09-01 22:56:39 -05:00
|
|
|
const tankopediaEphemeral = Tankopedia.state;
|
2025-09-02 16:24:13 -05:00
|
|
|
const shell =
|
|
|
|
|
tankopediaEphemeral.customShell ?? Duel.state.antagonist.shell;
|
2024-11-10 20:59:01 -06:00
|
|
|
|
2026-03-31 14:11:45 -05:00
|
|
|
material.uniforms.penetration.value = shell.penetration!.near;
|
2024-02-27 17:15:24 -06:00
|
|
|
material.uniforms.caliber.value = shell.caliber;
|
2024-05-24 13:48:50 -05:00
|
|
|
material.uniforms.ricochet.value = degToRad(
|
2026-03-31 14:11:45 -05:00
|
|
|
isExplosive(shell.type) ? 90 : shell.ricochet!,
|
2024-05-24 13:48:50 -05:00
|
|
|
);
|
2024-02-27 17:15:24 -06:00
|
|
|
material.uniforms.normalization.value = degToRad(
|
2026-03-31 14:11:45 -05:00
|
|
|
shell.normalization ?? 0,
|
2024-02-27 17:15:24 -06:00
|
|
|
);
|
|
|
|
|
}
|
2026-04-03 12:31:46 -05:00
|
|
|
function handleProtagonistEquipmentChange() {
|
|
|
|
|
const equipment = Duel.state.protagonist.equipmentMatrix;
|
2025-08-29 13:42:44 -05:00
|
|
|
const hasEnhancedArmor = hasEquipment(
|
2024-04-04 13:16:12 -05:00
|
|
|
110,
|
2025-09-02 16:24:13 -05:00
|
|
|
Duel.state.protagonist.tank.equipment_preset,
|
2026-03-31 14:11:45 -05:00
|
|
|
equipment,
|
2024-04-04 13:16:12 -05:00
|
|
|
);
|
2026-04-03 12:31:46 -05:00
|
|
|
const equalizer =
|
|
|
|
|
(Duel.state.equalize
|
|
|
|
|
? Duel.state.protagonist.tank.equalizer?.armor
|
|
|
|
|
: undefined) ?? 1;
|
|
|
|
|
|
|
|
|
|
material.uniforms.thickness.value =
|
2026-07-31 19:48:03 -05:00
|
|
|
thickness * (hasEnhancedArmor ? 1.04 : 1) * equalizer;
|
2024-02-27 18:08:50 -06:00
|
|
|
}
|
2026-04-03 12:31:46 -05:00
|
|
|
function handleAntagonistEquipmentChange() {
|
|
|
|
|
const equipment = Duel.state.antagonist.equipmentMatrix;
|
2025-09-01 22:56:39 -05:00
|
|
|
const tankopediaEphemeral = Tankopedia.state;
|
2025-09-02 16:24:13 -05:00
|
|
|
const shell =
|
|
|
|
|
tankopediaEphemeral.customShell ?? Duel.state.antagonist.shell;
|
2026-03-31 14:11:45 -05:00
|
|
|
const penetration = shell.penetration!.near;
|
2025-08-29 13:42:44 -05:00
|
|
|
const hasCalibratedShells = hasEquipment(
|
2024-04-04 13:16:12 -05:00
|
|
|
103,
|
2025-09-02 16:24:13 -05:00
|
|
|
Duel.state.antagonist.tank.equipment_preset,
|
2026-03-31 14:11:45 -05:00
|
|
|
equipment,
|
2024-04-04 13:16:12 -05:00
|
|
|
);
|
2026-04-03 12:31:46 -05:00
|
|
|
const equalize =
|
|
|
|
|
(Duel.state.equalize
|
|
|
|
|
? Duel.state.antagonist.tank.equalizer?.penetration
|
|
|
|
|
: undefined) ?? 1;
|
2024-05-25 13:32:00 -05:00
|
|
|
|
|
|
|
|
material.uniforms.penetration.value =
|
|
|
|
|
penetration *
|
2026-04-03 12:57:43 -05:00
|
|
|
resolvePenetrationCoefficient(
|
|
|
|
|
hasCalibratedShells,
|
|
|
|
|
Duel.state.equalize,
|
|
|
|
|
shell.type,
|
|
|
|
|
Duel.state.antagonist.tank.equalizer,
|
|
|
|
|
) *
|
2026-04-03 12:31:46 -05:00
|
|
|
equalize;
|
2024-02-27 18:13:26 -06:00
|
|
|
}
|
2024-02-27 17:15:24 -06:00
|
|
|
|
2024-11-10 20:59:01 -06:00
|
|
|
handleShellChange();
|
2026-04-03 12:31:46 -05:00
|
|
|
handleProtagonistEquipmentChange();
|
|
|
|
|
handleAntagonistEquipmentChange();
|
2024-02-27 18:08:50 -06:00
|
|
|
|
|
|
|
|
const unsubscribes = [
|
2025-09-02 16:24:13 -05:00
|
|
|
Duel.on((state) => state.antagonist.shell, handleShellChange),
|
2025-09-01 22:56:39 -05:00
|
|
|
Tankopedia.on((state) => state.customShell, handleShellChange),
|
2025-09-02 16:24:13 -05:00
|
|
|
Duel.on(
|
2024-07-10 13:11:09 -05:00
|
|
|
(state) => state.protagonist.equipmentMatrix,
|
2026-03-31 14:11:45 -05:00
|
|
|
handleProtagonistEquipmentChange,
|
2024-02-27 18:13:26 -06:00
|
|
|
),
|
2025-09-02 16:24:13 -05:00
|
|
|
Duel.on(
|
2024-07-10 13:11:09 -05:00
|
|
|
(state) => state.antagonist.equipmentMatrix,
|
2026-03-31 14:11:45 -05:00
|
|
|
handleAntagonistEquipmentChange,
|
2024-02-27 18:08:50 -06:00
|
|
|
),
|
2026-04-03 12:31:46 -05:00
|
|
|
Duel.on(
|
|
|
|
|
(state) => state.equalize,
|
|
|
|
|
() => {
|
|
|
|
|
handleProtagonistEquipmentChange();
|
|
|
|
|
handleAntagonistEquipmentChange();
|
|
|
|
|
},
|
|
|
|
|
),
|
2024-02-27 18:08:50 -06:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
unsubscribes.forEach((unsubscribe) => unsubscribe());
|
|
|
|
|
};
|
2024-04-02 09:52:46 -05:00
|
|
|
});
|
2024-02-27 17:15:24 -06:00
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<>
|
2025-04-15 00:11:49 -05:00
|
|
|
{jsxTree(node, {
|
2025-04-15 16:29:14 -05:00
|
|
|
group(_, props, key) {
|
|
|
|
|
return <group {...props} key={`${key}-spaced-sub-spaced-exclude`} />;
|
|
|
|
|
},
|
|
|
|
|
|
2025-04-15 00:11:49 -05:00
|
|
|
mesh(_, props, key) {
|
|
|
|
|
return (
|
|
|
|
|
<mesh
|
|
|
|
|
{...props}
|
2025-04-15 16:29:14 -05:00
|
|
|
key={`${key}-spaced-sub-spaced-exclude`}
|
2025-04-15 00:11:49 -05:00
|
|
|
renderOrder={2}
|
|
|
|
|
material={material}
|
|
|
|
|
onClick={() => {}}
|
|
|
|
|
userData={
|
|
|
|
|
{
|
|
|
|
|
type: ArmorType.Spaced,
|
|
|
|
|
thickness,
|
|
|
|
|
} satisfies ArmorUserData
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
);
|
2024-04-21 11:09:24 -05:00
|
|
|
},
|
2025-04-15 00:11:49 -05:00
|
|
|
})}
|
2024-04-13 10:36:13 -05:00
|
|
|
|
2025-04-15 00:11:49 -05:00
|
|
|
{jsxTree(node, {
|
2025-04-15 16:29:14 -05:00
|
|
|
group(_, props, key) {
|
|
|
|
|
return <group {...props} key={`${key}-spaced-sub-spaced-include`} />;
|
|
|
|
|
},
|
|
|
|
|
|
2025-04-15 00:11:49 -05:00
|
|
|
mesh(_, props, key) {
|
|
|
|
|
return (
|
|
|
|
|
<mesh
|
|
|
|
|
{...props}
|
2025-04-15 16:29:14 -05:00
|
|
|
key={`${key}-spaced-sub-spaced-include`}
|
2025-04-15 00:11:49 -05:00
|
|
|
renderOrder={5}
|
|
|
|
|
material={depthWriteMaterial}
|
|
|
|
|
/>
|
|
|
|
|
);
|
2024-04-21 11:09:24 -05:00
|
|
|
},
|
2025-04-15 00:11:49 -05:00
|
|
|
})}
|
2024-02-27 17:15:24 -06:00
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
}
|