mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 10:47:04 +02: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