feat: remove unused code

This commit is contained in:
TrèsAbhi committed 2024-02-27 17:57:59 -06:00
1 parent b2f026c212
commit d377067441
8 files changed
-739

No files matched your search

@@ -1,459 +0,0 @@
import { MeshProps, useFrame, useThree } from '@react-three/fiber';
import { memo, useEffect, useRef } from 'react';
import {
IUniform,
Mesh,
Quaternion,
Raycaster,
ShaderMaterial,
Vector2,
Vector3,
} from 'three';
import { degToRad } from 'three/src/math/MathUtils';
import { canRicochet } from '../../../../core/blitz/canRicochet';
import { canSplash } from '../../../../core/blitz/canSplash';
import { isExplosive } from '../../../../core/blitz/isExplosive';
import { resolveNearPenetration } from '../../../../core/blitz/resolveNearPenetration';
import { hasEquipment } from '../../../../core/blitzkrieg/hasEquipment';
import { useAwait } from '../../../../hooks/useAwait';
import { useDuel } from '../../../../stores/duel';
import {
ArmorPiercingLayer,
Shot,
mutateTankopediaTemporary,
useTankopediaPersistent,
} from '../../../../stores/tankopedia';
import { externalModuleMaskRenderTarget } from '../ExternalModuleMask';
import { spacedArmorDepthRenderTarget } from '../SpacedArmorDepth';
import fragmentShader from './shaders/fragment.glsl';
import vertexShader from './shaders/vertex.glsl';
interface ArmorMeshProps extends MeshProps {
thickness: number;
maxThickness: number;
}
export type ArmorMeshUserData = {
type: 'coreArmor' | 'spacedArmor' | 'externalModule';
thickness: number;
};
export const ArmorMesh = memo<ArmorMeshProps>(
({ thickness, maxThickness, ...props }) => {
const camera = useThree((state) => state.camera);
const initialShell = useDuel.getState().antagonist!.shell;
const initialTankopedia = useTankopediaPersistent.getState();
const material = useRef<ShaderMaterial>(null);
const explosionCapable = isExplosive(initialShell.type);
const ricochetCapable = canRicochet(initialShell.type);
const cameraNormal = new Vector3();
const shellNormal = new Vector3();
const surfaceNormal = new Vector3();
const mesh = useRef<Mesh>(null);
const scene = useThree((state) => state.scene);
const hasCalibratedShells = useAwait(hasEquipment(103, true));
const hasEnhancedArmor = useAwait(hasEquipment(110));
const wireframe = initialTankopedia.model.visual.wireframe;
useEffect(() => {
async function updateQuickEquipments() {
const shell = useDuel.getState().antagonist!.shell;
const nearPenetration = resolveNearPenetration(shell.penetration);
const hasCalibratedShells = await hasEquipment(103, true);
const hasEnhancedArmor = await hasEquipment(110);
if (material.current) {
material.current.uniforms.thickness.value =
thickness * (hasEnhancedArmor ? 1.04 : 1);
material.current.uniforms.penetration.value =
nearPenetration *
(hasCalibratedShells ? (isExplosive(shell.type) ? 1.1 : 1.05) : 1);
}
}
function updateShellProperties() {
const shell = useDuel.getState().antagonist!.shell;
if (material.current) {
material.current.uniforms.canRicochet.value = canRicochet(shell.type);
material.current.uniforms.isExplosive.value = isExplosive(shell.type);
material.current.uniforms.canSplash.value = canSplash(shell.type);
material.current.uniforms.caliber.value = shell.caliber;
material.current.uniforms.ricochetAngle.value = degToRad(
shell.ricochet ?? 90,
);
material.current.uniforms.damage.value = shell.damage.armor;
material.current.uniforms.explosionRadius.value =
shell.explosionRadius ?? 0;
}
updateQuickEquipments();
}
const unsubscribes = [
useDuel.subscribe(
(state) => state.protagonist!.equipment,
updateQuickEquipments,
),
useDuel.subscribe(
(state) => state.antagonist!.equipment,
updateQuickEquipments,
),
useDuel.subscribe(
(state) => state.antagonist!.shell,
updateShellProperties,
),
useTankopediaPersistent.subscribe(
(state) => state.model.visual.greenPenetration,
(greenPenetration) => {
if (material.current) {
material.current.uniforms.greenPenetration.value =
greenPenetration;
}
},
),
useTankopediaPersistent.subscribe(
(state) => state.model.visual.wireframe,
(wireframe) => {
if (material.current) {
material.current.wireframe = wireframe;
material.current.uniforms.wireframe.value = wireframe;
}
},
),
];
return () => {
unsubscribes.forEach((unsubscribe) => unsubscribe());
};
}, []);
useFrame(({ gl, camera }) => {
if (material.current) {
gl.getSize(material.current.uniforms.resolution.value);
(
material.current.uniforms.resolution as IUniform<Vector2>
).value.multiplyScalar(gl.getPixelRatio());
material.current.uniforms.externalModuleMask.value =
externalModuleMaskRenderTarget.texture;
material.current.uniforms.spacedArmorDepth.value =
spacedArmorDepthRenderTarget.depthTexture;
material.current.uniforms.spacedArmorMask.value =
spacedArmorDepthRenderTarget.texture;
material.current.uniforms.projectionMatrixInverse.value =
camera.projectionMatrixInverse;
}
});
return (
<>
<mesh {...props} renderOrder={0}>
<meshBasicMaterial colorWrite={false} />
</mesh>
<mesh
{...props}
renderOrder={1}
userData={
{
type: 'coreArmor',
thickness,
} satisfies ArmorMeshUserData
}
ref={mesh}
onClick={async (event) => {
// if (useTankopediaTemporary.getState().mode !== 'armor') return;
shellNormal.copy(
cameraNormal.copy(camera.position).sub(event.point).normalize(),
);
surfaceNormal
.copy(event.normal!)
.applyQuaternion(
event.object.getWorldQuaternion(new Quaternion()),
);
const angle = Math.acos(surfaceNormal.dot(shellNormal));
const shell = useDuel.getState().antagonist!.shell;
const hasCalibratedShells = await hasEquipment(103, true);
const resolvedPenetration =
resolveNearPenetration(shell.penetration) *
(hasCalibratedShells
? isExplosive(shell.type)
? 1.1
: 1.05
: 1);
const indexedObjects = new Set<ArmorMeshUserData['type']>();
const intersections = event.intersections
.filter(({ object }) => Boolean(object.userData.type))
.map((event) => {
const typedEvent = event as typeof event & {
object: (typeof event)['object'] & {
userData: ArmorMeshUserData;
};
};
return typedEvent;
})
.filter(({ object }) => {
if (indexedObjects.has(object.userData.type)) return false;
indexedObjects.add(object.userData.type);
return true;
});
const intersectionsTillFirstCoreArmor = intersections.slice(
0,
intersections.findIndex(
({ object }) => object.userData.type === 'coreArmor',
) + 1,
);
const coreArmorMesh =
intersectionsTillFirstCoreArmor.at(-1)!.object;
const hasEnhancedArmor = await hasEquipment(110);
const thicknessCoefficient = hasEnhancedArmor ? 1.04 : 1;
if (mesh.current !== (coreArmorMesh as unknown as Mesh)) return;
let hasRicochet = false;
const armorPiercingLayersRaw = intersectionsTillFirstCoreArmor
.map((event, index) => {
if (hasRicochet) return null;
switch (event.object.userData.type) {
case 'externalModule': {
return {
nominal:
event.object.userData.thickness * thicknessCoefficient,
angled:
event.object.userData.thickness * thicknessCoefficient,
type: 'external',
distance: event.distance,
ricochet: false,
} satisfies ArmorPiercingLayer;
}
case 'spacedArmor':
case 'coreArmor': {
const threeCalibersRule =
shell.caliber >
3 *
(event.object.userData.thickness *
thicknessCoefficient);
if (
index === 0 &&
canRicochet(shell.type) &&
!threeCalibersRule &&
angle >= degToRad(shell.ricochet!)
) {
hasRicochet = true;
}
const twoCalibersRule =
shell.caliber >
2 *
(event.object.userData.thickness *
thicknessCoefficient);
const normalization =
twoCalibersRule && event.object.userData.thickness > 0
? ((shell.normalization ?? 0) * 1.4 * shell.caliber) /
(2 *
event.object.userData.thickness *
thicknessCoefficient)
: shell.normalization ?? 0;
const angled =
(event.object.userData.thickness * thicknessCoefficient) /
Math.cos(angle - degToRad(normalization));
return {
ricochet: hasRicochet,
nominal:
event.object.userData.thickness * thicknessCoefficient,
angled,
type:
event.object.userData.type === 'coreArmor'
? 'core'
: 'spaced',
distance: event.distance,
} satisfies ArmorPiercingLayer;
}
}
})
.filter(Boolean);
const gaps = armorPiercingLayersRaw.map((layerA, aIndex) => {
if (aIndex === armorPiercingLayersRaw.length - 1) return null;
const layerB = armorPiercingLayersRaw[aIndex + 1];
return {
type: 'gap',
gap: Math.abs(layerB.distance - layerA.distance),
} satisfies ArmorPiercingLayer;
});
const armorPiercingLayers: ArmorPiercingLayer[] = [];
armorPiercingLayersRaw.forEach((layer, index) => {
const gap = gaps[index];
armorPiercingLayers.push(layer);
if (gap) armorPiercingLayers.push(gap);
});
let remainingPenetration = resolvedPenetration;
const explosiveCapable = isExplosive(shell.type);
armorPiercingLayers.forEach((layer) => {
if (layer.type !== 'gap') {
if (layer.ricochet) {
remainingPenetration *= 0.75;
} else {
remainingPenetration -= layer.angled;
}
} else if (explosiveCapable) {
// there is a 50% penetration loss per meter for HE based shells
remainingPenetration -= 0.5 * remainingPenetration * layer.gap;
}
remainingPenetration = Math.max(0, remainingPenetration);
});
const hasPenetrated = remainingPenetration > 0;
let ricochet: Shot['ricochet'] = undefined;
if (hasRicochet) {
const raycaster = new Raycaster();
const bounceNormal = shellNormal
.clone()
.multiplyScalar(-1)
.sub(
surfaceNormal
.clone()
.multiplyScalar(
2 *
surfaceNormal.dot(
shellNormal.clone().multiplyScalar(-1),
),
),
);
raycaster.set(event.point, bounceNormal);
const intersections = raycaster
.intersectObjects(scene.children, true)
.filter(
(intersection) =>
typeof intersection.object.userData?.type === 'string',
);
const indexOfCoreArmor = intersections.findIndex(
({ object }) => object.userData.type === 'coreArmor',
);
const intersectionsTillFirstCoreArmor = intersections.slice(
0,
indexOfCoreArmor + 1,
);
if (intersectionsTillFirstCoreArmor.length > 0) {
intersectionsTillFirstCoreArmor.forEach((intersection) => {
switch (intersection.object.userData.type) {
case 'externalModule': {
remainingPenetration -=
intersection.object.userData.thickness;
break;
}
case 'spacedArmor':
case 'coreArmor': {
const angle = bounceNormal.angleTo(
intersection.object.position.clone().sub(event.point),
);
const twoCalibersRule =
shell.caliber >
2 *
(intersection.object.userData.thickness *
thicknessCoefficient);
const normalization = twoCalibersRule
? ((shell.normalization ?? 0) * 1.4 * shell.caliber) /
(2 *
intersection.object.userData.thickness *
thicknessCoefficient)
: shell.normalization ?? 0;
const angled =
(intersection.object.userData.thickness *
thicknessCoefficient) /
Math.cos(angle - degToRad(normalization));
remainingPenetration -= angled;
}
}
});
ricochet = {
point: intersectionsTillFirstCoreArmor
.at(-1)!
.point.toArray(),
distance: intersectionsTillFirstCoreArmor.at(-1)!.distance,
penetration: remainingPenetration > 0,
};
}
}
mutateTankopediaTemporary((draft) => {
draft.shot = {
type: hasRicochet
? 'ricochet'
: hasPenetrated
? 'penetration'
: 'block',
point: event.point.toArray(),
thicknesses: armorPiercingLayers,
shellNormal: shellNormal.toArray(),
surfaceNormal: surfaceNormal.toArray(),
angle,
ricochet,
};
});
}}
>
<shaderMaterial
wireframe={wireframe}
ref={material}
fragmentShader={fragmentShader}
vertexShader={vertexShader}
transparent
depthWrite={false}
uniforms={{
resolution: { value: new Vector2() },
externalModuleMask: { value: null },
spacedArmorDepth: { value: null },
spacedArmorMask: { value: null },
projectionMatrixInverse: { value: null },
zNear: { value: camera.near },
zFar: { value: camera.far },
greenPenetration: {
value: initialTankopedia.model.visual.greenPenetration,
},
maxThickness: { value: maxThickness },
isExplosive: { value: explosionCapable },
canRicochet: { value: ricochetCapable },
canSplash: { value: canSplash(initialShell.type) },
thickness: {
value: thickness * (hasEnhancedArmor ? 1.04 : 1),
},
penetration: {
value:
resolveNearPenetration(initialShell.penetration) *
(hasCalibratedShells ? (explosionCapable ? 1.1 : 1.05) : 1),
},
caliber: { value: initialShell.caliber },
ricochetAngle: { value: degToRad(initialShell.ricochet ?? 90) },
normalization: {
value: degToRad(initialShell.normalization ?? 0),
},
damage: { value: initialShell.damage.armor },
explosionRadius: { value: initialShell.explosionRadius ?? 0 },
wireframe: { value: wireframe },
}}
/>
</mesh>
</>
);
},
() => true,
);
@@ -1,120 +0,0 @@
#define PI 3.1415926535897932384626433832795
varying vec3 vViewPosition;
varying vec3 vNormal;
uniform bool isExplosive;
uniform bool canRicochet;
uniform bool canSplash;
uniform float thickness;
uniform float penetration;
uniform float caliber;
uniform float ricochetAngle;
uniform float normalization;
uniform sampler2D externalModuleMask;
uniform highp sampler2D spacedArmorDepth;
uniform sampler2D spacedArmorMask;
uniform vec2 resolution;
varying mat4 vProjectionMatrix;
uniform float maxThickness;
uniform bool greenPenetration;
uniform float damage;
uniform float explosionRadius;
uniform bool wireframe;
float depthToDistance(float depth) {
mat4 projectionMatrixInverse = inverse(vProjectionMatrix);
vec4 clipPosition = vec4(gl_FragCoord.xy / resolution * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0);
vec4 eyePosition = projectionMatrixInverse * clipPosition;
vec3 NDCPosition = eyePosition.xyz / eyePosition.w;
float worldDistance = length(NDCPosition);
return worldDistance;
}
float angleTo(vec3 a, vec3 b) {
return acos(dot(a, b) / (length(a) * length(b)));
}
void main() {
float penetrationChance = -1.0;
float splashChance = -1.0;
vec2 screenCoordinates = gl_FragCoord.xy / resolution;
vec4 externalModuleMaskColor = texture2D(externalModuleMask, screenCoordinates);
vec4 spacedArmorMaskColor = texture2D(spacedArmorMask, screenCoordinates);
bool isUnderExternalModule = externalModuleMaskColor.r == 1.0;
bool isUnderSpacedArmor = spacedArmorMaskColor.a == 1.0;
float spacedArmorAngle = spacedArmorMaskColor.r * (PI / 2.0);
float coreArmorAngle = angleTo(vNormal, -vViewPosition);
bool coreArmorThreeCalibersRule = caliber > 3.0 * thickness;
float spacedArmorNominalThickness = spacedArmorMaskColor.g * maxThickness;
bool spacedArmorThreeCalibersRule = isUnderSpacedArmor && caliber > 3.0 * spacedArmorNominalThickness;
bool hasRicochetedSpacedArmor = isUnderSpacedArmor && !spacedArmorThreeCalibersRule && spacedArmorAngle >= ricochetAngle;
bool hasRicochetedCoreArmor = !hasRicochetedSpacedArmor && !isUnderExternalModule && canRicochet && !coreArmorThreeCalibersRule && coreArmorAngle >= ricochetAngle;
if (hasRicochetedCoreArmor || hasRicochetedSpacedArmor) {
penetrationChance = 0.0;
splashChance = 0.0;
} else {
// time to chip away at the penetration, one case at a time
float remainingPenetration = penetration;
if (isUnderExternalModule) {
// external modules don't care about angle, they'll reduce penetration by their thickness
float externalModuleThickness = externalModuleMaskColor.g * maxThickness;
remainingPenetration -= externalModuleThickness;
}
if (isUnderSpacedArmor) {
// spaced armor on the other hand, does care about angle
bool spacedArmorTwoCalibersRule = caliber > 2.0 * spacedArmorNominalThickness && spacedArmorNominalThickness > 0.0;
float finalSpacedArmorNormalization = spacedArmorTwoCalibersRule ? (normalization * 1.4 * caliber) / (2.0 * spacedArmorNominalThickness) : normalization;
float finalSpacedArmorThickness = spacedArmorNominalThickness / cos(spacedArmorAngle - finalSpacedArmorNormalization);
remainingPenetration -= finalSpacedArmorThickness;
}
remainingPenetration = max(remainingPenetration, 0.0);
float spacedArmorDepth = texture2D(spacedArmorDepth, screenCoordinates).r;
float spacedArmorDistance = depthToDistance(spacedArmorDepth);
float coreArmorDistance = depthToDistance(gl_FragCoord.z);
float distanceFromSpacedArmor = abs(coreArmorDistance - spacedArmorDistance);
if (isExplosive) {
// there is a 50% penetration loss per meter for HE based shells
remainingPenetration -= 0.5 * remainingPenetration * distanceFromSpacedArmor;
}
bool coreArmorTwoCalibersRule = caliber > 2.0 * thickness && thickness > 0.0;
float finalNormalization = coreArmorTwoCalibersRule ? (normalization * 1.4 * caliber) / (2.0 * thickness) : normalization;
float finalCoreArmorThickness = thickness / cos(coreArmorAngle - finalNormalization);
float deltaPenetration = finalCoreArmorThickness - remainingPenetration;
float randomRadius = remainingPenetration * 0.05;
if (deltaPenetration > randomRadius) {
// even with a 5% buff to the penetration, can't penetrate
penetrationChance = 0.0;
} else if (deltaPenetration < -randomRadius) {
// even with a 5% nerf, can penetrate
penetrationChance = 1.0;
} else {
// transition area
penetrationChance = max(1.0 - (deltaPenetration + randomRadius) / (2.0 * randomRadius), 0.0);
}
if (canSplash) {
// only allow splasing if the damage equation deals more than 0 damage
float nominalArmorThickness = finalCoreArmorThickness - remainingPenetration;
float finalDamage = 0.5 * damage * (1.0 - distanceFromSpacedArmor / explosionRadius) - 1.1 * nominalArmorThickness;
splashChance = finalDamage > 0.0 ? 1.0 : 0.0;
} else {
splashChance = 0.0;
}
}
vec3 color = vec3(1.0, splashChance * 0.392, 0.0);
if (greenPenetration || wireframe) {
gl_FragColor = vec4(mix(color, vec3(0.0, 1.0, 0.0), penetrationChance), wireframe ? 1.0 : 0.5);
} else {
gl_FragColor = vec4(color, (1.0 - penetrationChance) * 0.5);
}
}
@@ -1,13 +0,0 @@
varying vec3 vNormal;
varying vec3 vViewPosition;
varying mat4 vProjectionMatrix;
void main() {
vec4 vViewPosition4 = modelViewMatrix * vec4(position, 1.0);
vViewPosition = vViewPosition4.xyz;
vProjectionMatrix = projectionMatrix;
vNormal = normalMatrix * normal;
gl_Position = projectionMatrix * vViewPosition4;
}
@@ -1,52 +0,0 @@
import { Color, MeshBasicMaterial, Object3D, WebGLRenderTarget } from 'three';
import { jsxTree } from '../../../core/blitzkrieg/jsxTree';
import { ArmorMeshUserData } from './Armor';
export const externalModuleMaskRenderTarget = new WebGLRenderTarget();
type ArmorMeshExternalModuleMaskProps = {
ornamental?: boolean;
node: Object3D;
} & (
| {
exclude?: false;
maxThickness: number;
thickness: number;
}
| {
exclude: true;
}
);
const excludeMaterial = new MeshBasicMaterial({
colorWrite: false,
});
export function ArmorMeshExternalModuleMask({
ornamental = false,
node,
...props
}: ArmorMeshExternalModuleMaskProps) {
return (
<>
{props.exclude &&
!ornamental &&
jsxTree(node, { renderOrder: 0, material: excludeMaterial })}
{!props.exclude &&
jsxTree(node, {
renderOrder: 1,
onClick: ornamental ? () => {} : undefined,
userData: {
type: 'externalModule',
thickness: props.thickness,
} satisfies ArmorMeshUserData,
material: new MeshBasicMaterial({
color: new Color(1, props.thickness / props.maxThickness, 0),
depthWrite: false,
colorWrite: !ornamental,
}),
})}
</>
);
}
@@ -1,72 +0,0 @@
import {
MeshBasicMaterial,
Object3D,
ShaderMaterial,
WebGLRenderTarget,
} from 'three';
import { jsxTree } from '../../../../core/blitzkrieg/jsxTree';
import { ArmorMeshUserData } from '../Armor';
import fragmentShader from './shaders/fragment.glsl';
import vertexShader from './shaders/vertex.glsl';
export const spacedArmorDepthRenderTarget = new WebGLRenderTarget();
type ArmorMeshSpacedArmorDepthProps = {
ornamental?: boolean;
node: Object3D;
} & (
| {
include?: false;
isExternalModule: boolean;
}
| {
include: true;
thickness: number;
maxThickness: number;
}
);
const excludeMaterial = new MeshBasicMaterial({
colorWrite: false,
});
const includeMaterial = new MeshBasicMaterial({
colorWrite: false,
depthWrite: false,
});
export function ArmorMeshSpacedArmorDepth({
ornamental = false,
node,
...props
}: ArmorMeshSpacedArmorDepthProps) {
return (
<>
{!props.include &&
!ornamental &&
jsxTree(node, {
renderOrder: props.isExternalModule ? 2 : 0,
material: excludeMaterial,
})}
{props.include &&
jsxTree(node, {
renderOrder: 1,
onClick: ornamental ? () => {} : undefined,
userData: {
type: 'spacedArmor',
thickness: props.thickness,
} satisfies ArmorMeshUserData,
material: ornamental
? includeMaterial
: new ShaderMaterial({
fragmentShader,
vertexShader,
uniforms: {
thickness: { value: props.thickness },
maxThickness: { value: props.maxThickness },
},
}),
})}
</>
);
}
@@ -1,11 +0,0 @@
#define PI 3.1415926535897932384626433832795
varying vec3 vViewPosition;
varying vec3 vNormal;
uniform float thickness;
uniform float maxThickness;
void main() {
float angle = acos(dot(vNormal, -normalize(vViewPosition)));
gl_FragColor = vec4(angle / (PI / 2.0), thickness / maxThickness, 0.0, 1.0);
}
@@ -1,10 +0,0 @@
varying vec3 vViewPosition;
varying vec3 vNormal;
void main() {
vec4 vViewPosition4 = modelViewMatrix * vec4(position, 1.0);
vViewPosition = vViewPosition4.xyz;
vNormal = normalMatrix * normal;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
-2
View File
@@ -1,2 +0,0 @@
export * from './components/Armor';
export * from './components/ExternalModuleMask';