mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-23 19:49:50 +02:00
feat: spaced armor thickness
This commit is contained in:
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>
|
||||||
|
|||||||
+16
@@ -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}
|
||||||
|
|||||||
+33
-3
@@ -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;
|
||||||
|
}
|
||||||
Reference in new issue
Block a user