feat: spaced armor thickness

This commit is contained in:
TrèsAbhi committed 2024-01-16 09:52:30 -06:00
1 parent a11975adec
commit 8f4ea5f2fd
9 files changed
+128 -25

No files matched your search

@@ -132,7 +132,6 @@ export function TankDisplay({ duel }: TankDisplayProps) {
> >
<TankModel duel={duel} /> <TankModel duel={duel} />
<TankArmor duel={duel} /> <TankArmor duel={duel} />
{/* <SpacedArmorDepth duel={duel} /> */}
</Suspense> </Suspense>
</Canvas> </Canvas>
</div> </div>
@@ -136,6 +136,19 @@ export function ArmorHighlighting({ duel }: ArmorHighlightingProps) {
tankModelDefinition.trackThickness, tankModelDefinition.trackThickness,
gunModelDefinition.barrelThickness, gunModelDefinition.barrelThickness,
); );
const maxSpacedArmorThickness = Math.max(
...armorNodes
.map((node) => {
const armorId = nameToArmorId(node.name);
const { spaced, thickness } = resolveArmor(
tankModelDefinition.armor,
armorId,
);
return spaced ? thickness : null;
})
.filter(Boolean),
);
return ( return (
<group <group
@@ -162,6 +175,7 @@ export function ArmorHighlighting({ duel }: ArmorHighlightingProps) {
return ( return (
<ArmorMesh <ArmorMesh
maxExternalModuleThickness={maxExternalModuleThickness} maxExternalModuleThickness={maxExternalModuleThickness}
maxSpacedArmorThickness={maxSpacedArmorThickness}
duel={duel} duel={duel}
key={node.uuid} key={node.uuid}
geometry={(node as Mesh).geometry} geometry={(node as Mesh).geometry}
@@ -193,6 +207,7 @@ export function ArmorHighlighting({ duel }: ArmorHighlightingProps) {
return ( return (
<ArmorMesh <ArmorMesh
maxExternalModuleThickness={maxExternalModuleThickness} maxExternalModuleThickness={maxExternalModuleThickness}
maxSpacedArmorThickness={maxSpacedArmorThickness}
duel={duel} duel={duel}
key={node.uuid} key={node.uuid}
geometry={(node as Mesh).geometry} geometry={(node as Mesh).geometry}
@@ -224,6 +239,7 @@ export function ArmorHighlighting({ duel }: ArmorHighlightingProps) {
return ( return (
<ArmorMesh <ArmorMesh
maxExternalModuleThickness={maxExternalModuleThickness} maxExternalModuleThickness={maxExternalModuleThickness}
maxSpacedArmorThickness={maxSpacedArmorThickness}
duel={duel} duel={duel}
key={node.uuid} key={node.uuid}
geometry={(node as Mesh).geometry} geometry={(node as Mesh).geometry}
@@ -24,6 +24,17 @@ interface SpacedArmorDepthProps {
duel: Duel; duel: Duel;
} }
/**
* When rendered, this component provides the depth buffer for the armor which
* when compared to the depth of the core armor will give you the distance
* between the first point of contact and the core armor. The color channels
* hold information about the thickness of the spaced armor (external modules
* not included).
* - R: angle of the armor where 0 is 0 and 1 is 90
* - G: normalized thickness; multiply with max thickness to get nominal thickness
* - B: no data
* - A: 1 means is armor; 0 is background
*/
export const SpacedArmorDepth = memo<SpacedArmorDepthProps>(({ duel }) => { export const SpacedArmorDepth = memo<SpacedArmorDepthProps>(({ duel }) => {
const wrapper = useRef<Group>(null); const wrapper = useRef<Group>(null);
const awaitedModelDefinitions = useModelDefinitions(); const awaitedModelDefinitions = useModelDefinitions();
@@ -127,6 +138,19 @@ export const SpacedArmorDepth = memo<SpacedArmorDepthProps>(({ duel }) => {
tankModelDefinition.turrets[duel.protagonist.turret.id]; tankModelDefinition.turrets[duel.protagonist.turret.id];
const gunModelDefinition = const gunModelDefinition =
turretModelDefinition.guns[duel.protagonist.gun.id]; turretModelDefinition.guns[duel.protagonist.gun.id];
const maxThickness = Math.max(
...armorNodes
.map((node) => {
const armorId = nameToArmorId(node.name);
const { spaced, thickness } = resolveArmor(
tankModelDefinition.armor,
armorId,
);
return spaced ? thickness : null;
})
.filter(Boolean),
);
const turretOrigin = new Vector3( const turretOrigin = new Vector3(
tankModelDefinition.turretOrigin[0], tankModelDefinition.turretOrigin[0],
tankModelDefinition.turretOrigin[1], tankModelDefinition.turretOrigin[1],
@@ -162,7 +186,9 @@ export const SpacedArmorDepth = memo<SpacedArmorDepthProps>(({ duel }) => {
return ( return (
<ArmorMeshSpacedArmorDepth <ArmorMeshSpacedArmorDepth
exclude include={spaced}
thickness={thickness}
maxThickness={maxThickness}
key={node.uuid} key={node.uuid}
geometry={(node as Mesh).geometry} geometry={(node as Mesh).geometry}
/> />
@@ -205,7 +231,9 @@ export const SpacedArmorDepth = memo<SpacedArmorDepthProps>(({ duel }) => {
return ( return (
<ArmorMeshSpacedArmorDepth <ArmorMeshSpacedArmorDepth
exclude include={spaced}
thickness={thickness}
maxThickness={maxThickness}
key={node.uuid} key={node.uuid}
geometry={(node as Mesh).geometry} geometry={(node as Mesh).geometry}
position={turretOrigin} position={turretOrigin}
@@ -233,7 +261,9 @@ export const SpacedArmorDepth = memo<SpacedArmorDepthProps>(({ duel }) => {
return ( return (
<ArmorMeshSpacedArmorDepth <ArmorMeshSpacedArmorDepth
exclude include={spaced}
thickness={thickness}
maxThickness={maxThickness}
key={node.uuid} key={node.uuid}
geometry={(node as Mesh).geometry} geometry={(node as Mesh).geometry}
position={turretOrigin.clone().add(gunOrigin)} position={turretOrigin.clone().add(gunOrigin)}
@@ -18,6 +18,7 @@ interface ArmorMeshProps extends MeshProps {
isExternalModule?: boolean; isExternalModule?: boolean;
thickness: number; thickness: number;
maxExternalModuleThickness: number; maxExternalModuleThickness: number;
maxSpacedArmorThickness: number;
} }
export function ArmorMesh({ export function ArmorMesh({
@@ -25,6 +26,7 @@ export function ArmorMesh({
isExternalModule = false, isExternalModule = false,
thickness, thickness,
maxExternalModuleThickness, maxExternalModuleThickness,
maxSpacedArmorThickness,
duel, duel,
...props ...props
}: ArmorMeshProps) { }: ArmorMeshProps) {
@@ -43,6 +45,8 @@ export function ArmorMesh({
externalModuleMaskRenderTarget.texture; externalModuleMaskRenderTarget.texture;
material.current.uniforms.spacedArmorDepth.value = material.current.uniforms.spacedArmorDepth.value =
spacedArmorDepthRenderTarget.depthTexture; spacedArmorDepthRenderTarget.depthTexture;
material.current.uniforms.spacedArmorMask.value =
spacedArmorDepthRenderTarget.texture;
material.current.uniforms.projectionMatrixInverse.value = material.current.uniforms.projectionMatrixInverse.value =
camera.projectionMatrixInverse; camera.projectionMatrixInverse;
} }
@@ -69,10 +73,12 @@ export function ArmorMesh({
resolution: { value: null }, resolution: { value: null },
externalModuleMask: { value: null }, externalModuleMask: { value: null },
spacedArmorDepth: { value: null }, spacedArmorDepth: { value: null },
spacedArmorMask: { value: null },
projectionMatrixInverse: { value: null }, projectionMatrixInverse: { value: null },
zNear: { value: camera.near }, zNear: { value: camera.near },
zFar: { value: camera.far }, zFar: { value: camera.far },
maxExternalModuleThickness: { value: maxExternalModuleThickness }, maxExternalModuleThickness: { value: maxExternalModuleThickness },
maxSpacedArmorThickness: { value: maxSpacedArmorThickness },
isSpaced: { value: isSpaced }, isSpaced: { value: isSpaced },
isExplosive: { value: isExplosive(shell.type) }, isExplosive: { value: isExplosive(shell.type) },
canSplash: { value: canSplash(shell.type) }, canSplash: { value: canSplash(shell.type) },
@@ -10,9 +10,11 @@ uniform float ricochet;
uniform float normalization; uniform float normalization;
uniform sampler2D externalModuleMask; uniform sampler2D externalModuleMask;
uniform sampler2D spacedArmorDepth; uniform sampler2D spacedArmorDepth;
uniform sampler2D spacedArmorMask;
uniform vec2 resolution; uniform vec2 resolution;
varying mat4 vProjectionMatrix; varying mat4 vProjectionMatrix;
uniform float maxExternalModuleThickness; uniform float maxExternalModuleThickness;
uniform float maxSpacedArmorThickness;
float depthToDistance(float depth) { float depthToDistance(float depth) {
mat4 projectionMatrixInverse = inverse(vProjectionMatrix); mat4 projectionMatrixInverse = inverse(vProjectionMatrix);
@@ -26,12 +28,14 @@ float depthToDistance(float depth) {
void main() { void main() {
vec2 screenCoordinates = gl_FragCoord.xy / resolution; vec2 screenCoordinates = gl_FragCoord.xy / resolution;
float spacedArmorDepthValue = texture2D(spacedArmorDepth, screenCoordinates).r; float spacedArmorDepth = texture2D(spacedArmorDepth, screenCoordinates).r;
float currentDistance = depthToDistance(gl_FragCoord.z); float currentDistance = depthToDistance(gl_FragCoord.z);
float spacedArmorDistance = depthToDistance(spacedArmorDepthValue); float spacedArmorDistance = depthToDistance(spacedArmorDepth);
float distanceFromSpacedArmor = currentDistance - spacedArmorDistance; float distanceFromSpacedArmor = currentDistance - spacedArmorDistance;
vec4 externalModuleMaskColor = texture2D(externalModuleMask, screenCoordinates); vec4 externalModuleMaskColor = texture2D(externalModuleMask, screenCoordinates);
vec4 spacedArmorMaskColor = texture2D(spacedArmorMask, screenCoordinates);
bool isUnderExternalModule = externalModuleMaskColor.r == 1.0; bool isUnderExternalModule = externalModuleMaskColor.r == 1.0;
bool isUnderSpacedArmor = spacedArmorMaskColor.a == 1.0;
vec3 normalizedNormal = normalize(vNormal); vec3 normalizedNormal = normalize(vNormal);
vec3 normalizedViewPosition = normalize(vCSMViewPosition); vec3 normalizedViewPosition = normalize(vCSMViewPosition);
float dotProduct = dot(normalizedNormal, -normalizedViewPosition); float dotProduct = dot(normalizedNormal, -normalizedViewPosition);
@@ -53,6 +57,14 @@ void main() {
piercedPenetration -= externalModuleThickness; piercedPenetration -= externalModuleThickness;
} }
if (isUnderSpacedArmor) {
float spacedArmorAngle = spacedArmorMaskColor.r * PI;
float normalizedSpacedArmorThickness = spacedArmorMaskColor.g;
float spacedArmorNominalThickness = normalizedSpacedArmorThickness * maxSpacedArmorThickness;
float spacedArmorThickness = spacedArmorNominalThickness / cos(spacedArmorAngle);
piercedPenetration -= spacedArmorThickness;
}
if (isExplosive) { if (isExplosive) {
// 50% loss of remaining penetration per meter // 50% loss of remaining penetration per meter
piercedPenetration -= 0.5 * piercedPenetration * distanceFromSpacedArmor; piercedPenetration -= 0.5 * piercedPenetration * distanceFromSpacedArmor;
@@ -1,19 +0,0 @@
import { MeshProps } from '@react-three/fiber';
import { WebGLRenderTarget } from 'three';
export const spacedArmorDepthRenderTarget = new WebGLRenderTarget();
interface ArmorMeshSpacedArmorDepthProps extends MeshProps {
exclude?: boolean;
}
export function ArmorMeshSpacedArmorDepth({
exclude = false,
...props
}: ArmorMeshSpacedArmorDepthProps) {
return (
<mesh {...props}>
<meshBasicMaterial colorWrite={false} />
</mesh>
);
}
@@ -0,0 +1,41 @@
import { MeshProps } from '@react-three/fiber';
import { MeshStandardMaterial, WebGLRenderTarget } from 'three';
import ThreeCustomShaderMaterial from 'three-custom-shader-material';
import fragmentShader from './shaders/fragment.glsl';
import vertexShader from './shaders/vertex.glsl';
export const spacedArmorDepthRenderTarget = new WebGLRenderTarget();
type ArmorMeshSpacedArmorDepthProps = MeshProps &
(
| {
include?: false;
}
| {
include: true;
thickness: number;
maxThickness: number;
}
);
export function ArmorMeshSpacedArmorDepth(
props: ArmorMeshSpacedArmorDepthProps,
) {
return (
<mesh {...props}>
{!props.include && <meshBasicMaterial colorWrite={false} />}
{props.include && (
<ThreeCustomShaderMaterial
silent
baseMaterial={MeshStandardMaterial}
fragmentShader={fragmentShader}
vertexShader={vertexShader}
uniforms={{
thickness: { value: props.thickness },
maxThickness: { value: props.maxThickness },
}}
/>
)}
</mesh>
);
}
@@ -0,0 +1,12 @@
varying vec3 vCSMViewPosition;
uniform float thickness;
uniform float maxThickness;
void main() {
vec3 normalizedNormal = normalize(vNormal);
vec3 normalizedViewPosition = normalize(vCSMViewPosition);
float dotProduct = dot(normalizedNormal, -normalizedViewPosition);
float angle = acos(dotProduct);
csm_FragColor = vec4(angle / PI, thickness / maxThickness, 0.0, 1.0);
}
@@ -0,0 +1,6 @@
varying vec3 vCSMViewPosition;
void main() {
vec4 vViewPosition4 = modelViewMatrix * vec4(position, 1.0);
vCSMViewPosition = vViewPosition4.xyz;
}