feat: more advanced armor highlighting

This commit is contained in:
TrèsAbhi committed 2024-01-12 13:57:32 -06:00
1 parent 909474d4cb
commit d469f990f6
10 files changed
+118 -198

No files matched your search

+1 -1
View File
@@ -20,7 +20,7 @@ export async function buildTankArmors() {
await Promise.all( await Promise.all(
nations.map(async (nation) => { nations.map(async (nation) => {
if (nation !== 'china') return; if (nation !== 'france') return;
const tanks = await readXMLDVPL<{ root: VehicleDefinitionList }>( const tanks = await readXMLDVPL<{ root: VehicleDefinitionList }>(
`${DATA}/${DOI.vehicleDefinitions}/${nation}/list.xml.dvpl`, `${DATA}/${DOI.vehicleDefinitions}/${nation}/list.xml.dvpl`,
+1 -1
View File
@@ -27,7 +27,7 @@ export async function buildTankModels() {
await Promise.all( await Promise.all(
nations.map(async (nation) => { nations.map(async (nation) => {
if (nation !== 'china') return; if (nation !== 'france') return;
const tanks = await readXMLDVPL<{ root: VehicleDefinitionList }>( const tanks = await readXMLDVPL<{ root: VehicleDefinitionList }>(
`${DATA}/${DOI.vehicleDefinitions}/${nation}/list.xml.dvpl`, `${DATA}/${DOI.vehicleDefinitions}/${nation}/list.xml.dvpl`,
@@ -5,16 +5,14 @@ import { GLTFLoader } from 'three-stdlib';
import { ArmorMesh } from '../../../../../../../components/ArmorMesh'; import { ArmorMesh } from '../../../../../../../components/ArmorMesh';
import { HeadsUpDisplay } from '../../../../../../../components/HeadsUpDisplay'; import { HeadsUpDisplay } from '../../../../../../../components/HeadsUpDisplay';
import { X_AXIS } from '../../../../../../../constants/axis'; import { X_AXIS } from '../../../../../../../constants/axis';
import { canRicochet } from '../../../../../../../core/blitz/canRicochet';
import { canSplash } from '../../../../../../../core/blitz/canSplash'; import { canSplash } from '../../../../../../../core/blitz/canSplash';
import { isAffectedBySpaced } from '../../../../../../../core/blitz/isAffectedBySpaced'; import { isExplosive } from '../../../../../../../core/blitz/isExplosive';
import { numericPenetration } from '../../../../../../../core/blitz/numericPenetration'; import { numericPenetration } from '../../../../../../../core/blitz/numericPenetration';
import { asset } from '../../../../../../../core/blitzkrieg/asset'; import { asset } from '../../../../../../../core/blitzkrieg/asset';
import { import {
ModelDefinitions, ModelDefinitions,
modelDefinitions, modelDefinitions,
} from '../../../../../../../core/blitzkrieg/modelDefinitions'; } from '../../../../../../../core/blitzkrieg/modelDefinitions';
import { RicochetCapableShell } from '../../../../../../../core/blitzkrieg/tankDefinitions';
import { useTankopedia } from '../../../../../../../stores/tankopedia'; import { useTankopedia } from '../../../../../../../stores/tankopedia';
import { Lighting } from '../../Lighting'; import { Lighting } from '../../Lighting';
@@ -77,9 +75,11 @@ export function TankArmor({ ...props }: TankArmorProps) {
.sub(turretOrigin) .sub(turretOrigin)
.applyAxisAngle(new Vector3(0, 0, 1), model.turretYaw); .applyAxisAngle(new Vector3(0, 0, 1), model.turretYaw);
const turretRotation = new Euler(0, 0, model.turretYaw); const turretRotation = new Euler(0, 0, model.turretYaw);
const ricochetCapable = canRicochet(antagonist.shell.type);
const splashCapable = canSplash(antagonist.shell.type); const splashCapable = canSplash(antagonist.shell.type);
const affectedBySpacedCapable = isAffectedBySpaced(antagonist.shell.type); const explosiveCapable = isExplosive(antagonist.shell.type);
const penetration = numericPenetration(antagonist.shell.penetration);
const ricochet = antagonist.shell.ricochet ?? Math.PI / 2;
const normalization = antagonist.shell.normalization ?? 0;
if (tankModelDefinition.turretRotation) { if (tankModelDefinition.turretRotation) {
const pitch = -tankModelDefinition.turretRotation.pitch * (Math.PI / 180); const pitch = -tankModelDefinition.turretRotation.pitch * (Math.PI / 180);
@@ -122,25 +122,17 @@ export function TankArmor({ ...props }: TankArmorProps) {
return ( return (
<ArmorMesh <ArmorMesh
isAffectedBySpaced={affectedBySpacedCapable}
canSplash={splashCapable}
spaced={isSpaced}
key={node.uuid} key={node.uuid}
geometry={(node as Mesh).geometry} geometry={(node as Mesh).geometry}
thickness={thickness}
penetration={numericPenetration(antagonist.shell.penetration)}
canRicochet={ricochetCapable}
ricochet={
ricochetCapable
? (antagonist.shell as RicochetCapableShell).ricochet
: undefined
}
normalization={
ricochetCapable
? (antagonist.shell as RicochetCapableShell).normalization
: undefined
}
caliber={antagonist.shell.caliber} caliber={antagonist.shell.caliber}
canSplash={splashCapable}
isExplosive={explosiveCapable}
isSpaced={isSpaced ?? false}
normalization={normalization}
penetration={penetration}
thickness={thickness}
ricochet={ricochet}
isArmor
/> />
); );
})} })}
@@ -155,25 +147,17 @@ export function TankArmor({ ...props }: TankArmorProps) {
return ( return (
<ArmorMesh <ArmorMesh
isAffectedBySpaced={affectedBySpacedCapable}
canSplash={splashCapable}
spaced
key={node.uuid} key={node.uuid}
geometry={(node as Mesh).geometry} geometry={(node as Mesh).geometry}
thickness={thickness}
penetration={numericPenetration(antagonist.shell.penetration)}
canRicochet={ricochetCapable}
ricochet={
ricochetCapable
? (antagonist.shell as RicochetCapableShell).ricochet
: undefined
}
normalization={
ricochetCapable
? (antagonist.shell as RicochetCapableShell).normalization
: undefined
}
caliber={antagonist.shell.caliber} caliber={antagonist.shell.caliber}
canSplash={splashCapable}
isExplosive={explosiveCapable}
isSpaced
normalization={normalization}
penetration={penetration}
thickness={thickness}
ricochet={ricochet}
isArmor={false}
/> />
); );
})} })}
@@ -200,26 +184,18 @@ export function TankArmor({ ...props }: TankArmorProps) {
return ( return (
<ArmorMesh <ArmorMesh
isAffectedBySpaced={affectedBySpacedCapable}
canSplash={splashCapable}
spaced={isSpaced}
key={node.uuid} key={node.uuid}
position={turretOrigin}
geometry={(node as Mesh).geometry} geometry={(node as Mesh).geometry}
thickness={thickness} position={turretOrigin}
penetration={numericPenetration(antagonist.shell.penetration)}
caliber={antagonist.shell.caliber} caliber={antagonist.shell.caliber}
canRicochet={ricochetCapable} canSplash={splashCapable}
ricochet={ isExplosive={explosiveCapable}
ricochetCapable isSpaced={isSpaced ?? false}
? (antagonist.shell as RicochetCapableShell).ricochet normalization={normalization}
: undefined penetration={penetration}
} thickness={thickness}
normalization={ ricochet={ricochet}
ricochetCapable isArmor
? (antagonist.shell as RicochetCapableShell).normalization
: undefined
}
/> />
); );
})} })}
@@ -252,26 +228,18 @@ export function TankArmor({ ...props }: TankArmorProps) {
return ( return (
<ArmorMesh <ArmorMesh
isAffectedBySpaced={affectedBySpacedCapable}
canSplash={splashCapable}
spaced={isSpaced}
key={node.uuid} key={node.uuid}
position={turretOrigin.clone().add(gunOrigin)}
geometry={(node as Mesh).geometry} geometry={(node as Mesh).geometry}
thickness={thickness} position={turretOrigin.clone().add(gunOrigin)}
penetration={numericPenetration(antagonist.shell.penetration)}
caliber={antagonist.shell.caliber} caliber={antagonist.shell.caliber}
canRicochet={ricochetCapable} canSplash={splashCapable}
ricochet={ isExplosive={explosiveCapable}
ricochetCapable isSpaced={isSpaced ?? false}
? (antagonist.shell as RicochetCapableShell).ricochet normalization={normalization}
: undefined penetration={penetration}
} thickness={thickness}
normalization={ ricochet={ricochet}
ricochetCapable isArmor
? (antagonist.shell as RicochetCapableShell).normalization
: undefined
}
/> />
); );
})} })}
@@ -287,25 +255,17 @@ export function TankArmor({ ...props }: TankArmorProps) {
return ( return (
<ArmorMesh <ArmorMesh
isAffectedBySpaced={affectedBySpacedCapable}
canSplash={splashCapable}
spaced
key={node.uuid} key={node.uuid}
geometry={(node as Mesh).geometry} geometry={(node as Mesh).geometry}
thickness={thickness}
penetration={numericPenetration(antagonist.shell.penetration)}
canRicochet={ricochetCapable}
ricochet={
ricochetCapable
? (antagonist.shell as RicochetCapableShell).ricochet
: undefined
}
normalization={
ricochetCapable
? (antagonist.shell as RicochetCapableShell).normalization
: undefined
}
caliber={antagonist.shell.caliber} caliber={antagonist.shell.caliber}
canSplash={splashCapable}
isExplosive={explosiveCapable}
isSpaced
normalization={normalization}
penetration={penetration}
thickness={thickness}
ricochet={ricochet}
isArmor={false}
/> />
); );
})} })}
+1 -1
View File
@@ -24,7 +24,7 @@ export default function Page({ params }: { params: { id: string } }) {
draft.model.hullYaw = 0; draft.model.hullYaw = 0;
draft.model.turretYaw = 0; draft.model.turretYaw = 0;
draft.model.gunPitch = 0; draft.model.gunPitch = 0;
draft.mode = 'model'; draft.mode = 'armor';
draft.areTanksAssigned = true; draft.areTanksAssigned = true;
if (!draft.areTanksAssigned) return; if (!draft.areTanksAssigned) return;
+21 -32
View File
@@ -1,69 +1,58 @@
import { MeshProps } from '@react-three/fiber'; import { MeshProps } from '@react-three/fiber';
import { MeshStandardMaterial } from 'three'; import { MeshStandardMaterial } from 'three';
import CustomShaderMaterial from 'three-custom-shader-material'; import CustomShaderMaterial from 'three-custom-shader-material';
import fragment from './shaders/fragment.glsl'; import fragmentShader from './shaders/fragment.glsl';
import vertex from './shaders/vertex.glsl';
interface ArmorMeshProps extends MeshProps { interface ArmorMeshProps extends MeshProps {
isExplosive: boolean;
canSplash: boolean;
isSpaced: boolean;
isArmor: boolean;
thickness: number; thickness: number;
penetration: number; penetration: number;
caliber: number; caliber: number;
spaced?: boolean; ricochet: number;
canRicochet: boolean; normalization: number;
canSplash: boolean;
ricochet?: number;
normalization?: number;
isAffectedBySpaced: boolean;
} }
// shader split: https://github.com/FarazzShaikh/THREE-CustomShaderMaterial/issues/48
export function ArmorMesh({ export function ArmorMesh({
isExplosive,
canSplash,
isSpaced,
isArmor,
thickness, thickness,
penetration, penetration,
caliber, caliber,
canRicochet,
ricochet, ricochet,
canSplash,
isAffectedBySpaced,
normalization, normalization,
spaced = false,
...props ...props
}: ArmorMeshProps) { }: ArmorMeshProps) {
return ( return (
<> <>
{!spaced && ( {!isSpaced && (
<mesh {...props}> <mesh {...props}>
<meshBasicMaterial colorWrite={false} /> <meshBasicMaterial colorWrite={false} />
</mesh> </mesh>
)} )}
<mesh {...props} renderOrder={spaced ? 3 : 1}> <mesh {...props} renderOrder={1}>
<CustomShaderMaterial <CustomShaderMaterial
silent
baseMaterial={MeshStandardMaterial} baseMaterial={MeshStandardMaterial}
transparent transparent
silent
depthWrite={false} depthWrite={false}
fragmentShader={fragmentShader}
uniforms={{ uniforms={{
isExplosive: { value: isExplosive },
canSplash: { value: canSplash },
isSpaced: { value: isSpaced },
isArmor: { value: isArmor },
thickness: { value: thickness }, thickness: { value: thickness },
penetration: { value: penetration }, penetration: { value: penetration },
ricochet: {
value: ricochet === undefined ? 0 : ricochet * (Math.PI / 180),
},
normalization: {
value:
normalization === undefined
? 0
: normalization * (Math.PI / 180),
},
caliber: { value: caliber }, caliber: { value: caliber },
spaced: { value: spaced ?? false }, ricochet: { value: ricochet * (Math.PI / 180) },
canRicochet: { value: canRicochet }, normalization: { value: normalization * (Math.PI / 180) },
canSplash: { value: canSplash },
isAffectedBySpaced: { value: isAffectedBySpaced },
}} }}
vertexShader={vertex}
fragmentShader={fragment}
/> />
</mesh> </mesh>
</> </>
+41 -52
View File
@@ -1,75 +1,64 @@
// split shaders due to https://github.com/FarazzShaikh/THREE-CustomShaderMaterial/issues/48 uniform bool isExplosive;
uniform bool canSplash;
varying vec3 vCSMViewPosition; uniform bool isSpaced;
uniform bool isArmor;
uniform float thickness; uniform float thickness;
uniform float penetration; uniform float penetration;
uniform float ricochet;
uniform float caliber; uniform float caliber;
uniform float ricochet;
uniform float normalization; uniform float normalization;
uniform bool spaced;
uniform bool canRichochet; vec3 zPositive = vec3(0.0, 0.0, 1.0);
uniform bool canSplash;
uniform bool isAffectedBySpaced;
void main() { void main() {
vec3 normalizedNormal = normalize(vNormal); float cosAngle = dot(zPositive, vNormal);
vec3 normalizedViewPosition = normalize(vCSMViewPosition); float angle = acos(cosAngle);
float dotProduct = dot(normalizedNormal, -normalizedViewPosition);
float angle = acos(dotProduct);
float penetrationChance = -1.0; float penetrationChance = -1.0;
bool threeCaliberRule = caliber > 3.0 * thickness; float splashChance = -1.0;
bool threeCalibersRule = caliber > 3.0 * thickness;
if (canRichochet && !threeCaliberRule && angle >= ricochet) { if (isArmor && !isExplosive && !threeCalibersRule && angle >= ricochet) {
// auto ricochet angle and three caliber rule not met
penetrationChance = 0.0; penetrationChance = 0.0;
splashChance = 0.0;
} else { } else {
bool twoCaliberRule = caliber > 2.0 * thickness; bool twoCalibersRule = caliber > 2.0 * thickness;
float finalNormalization = twoCaliberRule ? (normalization * 1.4 * caliber) / (2.0 * thickness) : normalization; float finalNormalization = twoCalibersRule ? (normalization * 1.4 * caliber) / (2.0 * thickness) : normalization;
float effectiveArmor = thickness / cos(angle - finalNormalization); float finalThickness = thickness / cos(angle - finalNormalization);
float penetrationRandomRadius = penetration * 0.05;
float penetrationDifference = effectiveArmor - penetration;
if (penetrationDifference > penetrationRandomRadius) { if (canSplash) {
// even with a +5% buff, can't penetrate if (finalThickness * (5.0 / 11.0) > penetration) {
penetrationChance = 0.0; penetrationChance = 0.0;
} else if (penetrationDifference < -penetrationRandomRadius) { splashChance = 0.0;
// event with a -5% nerf, can penetrate } else if (finalThickness > penetration) {
penetrationChance = 1.0; penetrationChance = 0.0;
splashChance = 1.0;
} else {
penetrationChance = 1.0;
splashChance = 1.0;
}
} else { } else {
// penetration gradiant if (finalThickness > penetration) {
penetrationChance = 1.0 - (penetrationDifference + penetrationRandomRadius) / (2.0 * penetrationRandomRadius); penetrationChance = 0.0;
splashChance = 0.0;
} else {
penetrationChance = 1.0;
splashChance = 0.0;
}
} }
} }
float alpha = (1.0 - penetrationChance) * 0.5; if (isSpaced) {
if (isArmor) {
if (canSplash) { if (isExplosive) {
// he
if (spaced) {
csm_FragColor = vec4(1.0, 0.0, 1.0, 0.5);
} else {
float angledThickness = thickness / cos(angle);
if (angledThickness * (5.0 / 11.0) > penetration) {
csm_FragColor = vec4(1.0, 0.0, 0.0, alpha);
} else {
csm_FragColor = vec4(1.0, 0.392, 0.0, alpha);
}
}
} else {
if (spaced) {
if (isAffectedBySpaced) {
// hc
csm_FragColor = vec4(1.0, 0.0, 1.0, 0.5); csm_FragColor = vec4(1.0, 0.0, 1.0, 0.5);
} else { } else {
// ap, ap_cr csm_FragColor = vec4(1.0, 0.0, 1.0, (1.0 - penetrationChance) * 0.5);
csm_FragColor = vec4(1.0, 0.0, 1.0, alpha);
} }
} else { } else {
// ap, ap_cr, hc csm_FragColor = vec4(0.5, 0.0, 1.0, 0.5);
csm_FragColor = vec4(1.0, 0.0, 0.0, alpha);
} }
} else {
csm_FragColor = vec4(1.0, splashChance * 0.392, 0.0, (1.0 - penetrationChance) * 0.5);
} }
} }
@@ -1,6 +0,0 @@
varying vec3 vCSMViewPosition;
void main() {
vec4 vViewPosition4 = modelViewMatrix * vec4(position, 1.0);
vCSMViewPosition = vViewPosition4.xyz;
}
-7
View File
@@ -1,7 +0,0 @@
import { ShellDefinition } from '../blitzkrieg/tankDefinitions';
export function canRicochet(
type: ShellDefinition['type'],
): type is 'ap' | 'ap_cr' {
return type === 'ap' || type === 'ap_cr';
}
@@ -1,6 +1,6 @@
import { ShellDefinition } from '../blitzkrieg/tankDefinitions'; import { ShellDefinition } from '../blitzkrieg/tankDefinitions';
export function isAffectedBySpaced( export function isExplosive(
type: ShellDefinition['type'], type: ShellDefinition['type'],
): type is 'he' | 'hc' { ): type is 'he' | 'hc' {
return type === 'he' || type === 'hc'; return type === 'he' || type === 'hc';
+5 -10
View File
@@ -58,15 +58,7 @@ interface GunDefinitionAutoReloader {
interClip: number; interClip: number;
count: number; count: number;
} }
export interface RicochetCapableShell { export interface ShellDefinition {
type: 'ap' | 'ap_cr';
normalization: number;
ricochet: number;
}
export interface RicochetIncapableShell {
type: 'hc' | 'he';
}
export type ShellDefinition = {
id: number; id: number;
name: string; name: string;
speed: number; speed: number;
@@ -74,7 +66,10 @@ export type ShellDefinition = {
caliber: number; caliber: number;
icon: string; icon: string;
penetration: number | Vector2Tuple; penetration: number | Vector2Tuple;
} & (RicochetCapableShell | RicochetIncapableShell); type: ShellType;
normalization?: number;
ricochet?: number;
}
export const tankDefinitions = fetch(asset('definitions/tanks.json'), { export const tankDefinitions = fetch(asset('definitions/tanks.json'), {
cache: 'no-cache', cache: 'no-cache',