mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-25 03:29:51 +01:00
feat: remove unused code
This commit is contained in:
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);
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
export * from './components/Armor';
|
|
||||||
export * from './components/ExternalModuleMask';
|
|
||||||
Reference in new issue
Block a user