feat: wireframe

This commit is contained in:
TrèsAbhi committed 2024-07-30 13:45:52 -05:00
1 parent 8ba56069c4
commit 15cf4ec0f1
4 files changed
+91 -98

No files matched your search

@@ -4,7 +4,7 @@ import Link from 'next/link';
import { Suspense, use, useEffect, useMemo, useState } from 'react'; import { Suspense, use, useEffect, useMemo, useState } from 'react';
import { classIcons } from '../../../../../../components/ClassIcon'; import { classIcons } from '../../../../../../components/ClassIcon';
import { ThicknessRange } from '../../../../../../components/StaticArmor/components/StaticArmorScene'; import { ThicknessRange } from '../../../../../../components/StaticArmor/components/StaticArmorScene';
import { modelDefinitions } from '../../../../../../core/blitzkit/modelDefinitions'; import { resolveNearPenetration } from '../../../../../../core/blitz/resolveNearPenetration';
import { tankDefinitions } from '../../../../../../core/blitzkit/tankDefinitions'; import { tankDefinitions } from '../../../../../../core/blitzkit/tankDefinitions';
import { TIER_ROMAN_NUMERALS } from '../../../../../../core/blitzkit/tankDefinitions/constants'; import { TIER_ROMAN_NUMERALS } from '../../../../../../core/blitzkit/tankDefinitions/constants';
import { useFullScreen } from '../../../../../../hooks/useFullScreen'; import { useFullScreen } from '../../../../../../hooks/useFullScreen';
@@ -35,48 +35,22 @@ export function HeroSection({ id }: HeroSectionProps) {
? 'amber' ? 'amber'
: undefined; : undefined;
const awaitedTankDefinitions = use(tankDefinitions); const awaitedTankDefinitions = use(tankDefinitions);
const awaitedModelDefinitions = use(modelDefinitions);
const tank = awaitedTankDefinitions[id]; const tank = awaitedTankDefinitions[id];
const thicknessRange = useMemo(() => { const thicknessRange = useMemo(() => {
const thicknesses = Object.values(awaitedTankDefinitions) const entries = Object.values(awaitedTankDefinitions);
.filter((thisTank) => thisTank.tier === tank.tier) const filtered = entries.filter((thisTank) => thisTank.tier === tank.tier);
.map((tank) => ({ const value =
model: awaitedModelDefinitions[tank.id], filtered.reduce((accumulator, thisTank) => {
id: tank.id, return (
})) accumulator +
.filter(Boolean) resolveNearPenetration(
.map(({ model, id }) => { thisTank.turrets.at(-1)!.guns.at(-1)!.shells[0].penetration,
const tankThicknesses = Object.values(model.armor.thickness); )
const turretThicknesses = Object.values(model.turrets) );
.map((turret) => { }, 0) /
const turretThicknesses = Object.values(turret.armor.thickness); ((4 / 3) * filtered.length);
const gunThicknesses = Object.values(turret.guns)
.map((gun) => Object.values(gun.armor.thickness))
.flat();
return [...turretThicknesses, ...gunThicknesses]; return { value } satisfies ThicknessRange;
})
.flat();
const totalThicknesses = [...tankThicknesses, ...turretThicknesses];
return totalThicknesses;
})
.flat();
const sortedThicknesses = thicknesses.sort((a, b) => a - b);
const quartileIndex = (1 - 2 ** -6) * (sortedThicknesses.length + 1) - 1;
let quartile: number;
if (Number.isInteger(quartileIndex)) {
quartile = sortedThicknesses[quartileIndex];
} else {
quartile =
(sortedThicknesses[Math.floor(quartileIndex)] +
sortedThicknesses[Math.ceil(quartileIndex)]) /
2;
}
return { quartile } satisfies ThicknessRange;
}, [tank.tier]); }, [tank.tier]);
useEffect(() => { useEffect(() => {
@@ -48,7 +48,7 @@ export function Thicknesses({ thicknessRange }: ThicknessesProps) {
> >
<Flex direction="column" align="end" justify="between"> <Flex direction="column" align="end" justify="between">
<Text color="gray" size="1"> <Text color="gray" size="1">
{thicknessRange.quartile.toFixed(0)} {thicknessRange.value.toFixed(0)}
</Text> </Text>
<Flex gap="1"> <Flex gap="1">
{showPrimaryArmor ? <EyeOpenIcon /> : <EyeClosedIcon />} {showPrimaryArmor ? <EyeOpenIcon /> : <EyeClosedIcon />}
@@ -85,7 +85,7 @@ export function Thicknesses({ thicknessRange }: ThicknessesProps) {
> >
<Flex direction="column" align="end" justify="between"> <Flex direction="column" align="end" justify="between">
<Text color="gray" size="1"> <Text color="gray" size="1">
{thicknessRange.quartile.toFixed(0)} {thicknessRange.value.toFixed(0)}
</Text> </Text>
<Flex gap="1"> <Flex gap="1">
{showSpacedArmor ? <EyeOpenIcon /> : <EyeClosedIcon />} {showSpacedArmor ? <EyeOpenIcon /> : <EyeClosedIcon />}
@@ -7,7 +7,10 @@ import {
DoubleSide, DoubleSide,
FrontSide, FrontSide,
Intersection, Intersection,
LineBasicMaterial,
Mesh,
MeshBasicMaterial, MeshBasicMaterial,
MeshStandardMaterial,
Object3D, Object3D,
Plane, Plane,
Quaternion, Quaternion,
@@ -97,7 +100,7 @@ export function SpacedArmorSceneComponent({
const camera = useThree((state) => state.camera); const camera = useThree((state) => state.camera);
if (props.static) { if (props.static) {
const x = clamp(thickness / props.thicknessRange.quartile, 0, 1); const x = clamp(thickness / props.thicknessRange.value, 0, 1);
let color: Color; let color: Color;
let opacity: number; let opacity: number;
@@ -125,9 +128,9 @@ export function SpacedArmorSceneComponent({
opacity = clamp(opacity, 0, 1); opacity = clamp(opacity, 0, 1);
const material = useMemo( const surfaceMaterial = useMemo(
() => () =>
new MeshBasicMaterial({ new MeshStandardMaterial({
color, color,
opacity, opacity,
transparent: true, transparent: true,
@@ -136,6 +139,13 @@ export function SpacedArmorSceneComponent({
}), }),
[thickness], [thickness],
); );
const outlineMaterial = useMemo(
() =>
new LineBasicMaterial({
color: color.clone().multiplyScalar(1 - 2 ** -3),
}),
[thickness],
);
/** /**
* hook inside an if statement?? don't panic! I assure you the static prop * hook inside an if statement?? don't panic! I assure you the static prop
@@ -147,10 +157,11 @@ export function SpacedArmorSceneComponent({
if (selectedName === undefined) { if (selectedName === undefined) {
// nothing selected, go back to defaults // nothing selected, go back to defaults
material.opacity = opacity; surfaceMaterial.opacity = opacity;
material.color = color; surfaceMaterial.color = color;
material.depthWrite = props.type !== ArmorType.External; surfaceMaterial.depthWrite = props.type !== ArmorType.External;
material.side = FrontSide; surfaceMaterial.side = FrontSide;
outlineMaterial.visible = true;
} else if ( } else if (
selectedName === props.name || selectedName === props.name ||
(props.name.startsWith('chassis_') && (props.name.startsWith('chassis_') &&
@@ -161,19 +172,21 @@ export function SpacedArmorSceneComponent({
!selectedName.includes('_armor_')) !selectedName.includes('_armor_'))
) { ) {
// this selected, stand out! // this selected, stand out!
material.opacity = 1; surfaceMaterial.opacity = 1;
material.color = color; surfaceMaterial.color = color;
material.depthWrite = true; surfaceMaterial.depthWrite = true;
material.side = DoubleSide; surfaceMaterial.side = DoubleSide;
outlineMaterial.visible = true;
} else { } else {
// something else selected, become background // something else selected, become background
material.opacity = 1 / 4; surfaceMaterial.opacity = 1 / 4;
material.color = unselectedColor; surfaceMaterial.color = unselectedColor;
material.depthWrite = props.type !== ArmorType.External; surfaceMaterial.depthWrite = props.type !== ArmorType.External;
material.side = FrontSide; surfaceMaterial.side = FrontSide;
outlineMaterial.visible = false;
} }
material.transparent = material.opacity < 1; surfaceMaterial.transparent = surfaceMaterial.opacity < 1;
} }
const unsubscribe = tankopediaEphemeralStore.subscribe( const unsubscribe = tankopediaEphemeralStore.subscribe(
@@ -184,44 +197,53 @@ export function SpacedArmorSceneComponent({
return unsubscribe; return unsubscribe;
}, [thickness]); }, [thickness]);
return jsxTree(node, { return (
material, <>
// renderOrder, {jsxTree(node, {
userData: { material: surfaceMaterial,
type: props.type, userData: {
variant: props.type === ArmorType.External ? props.variant : 'gun',
thickness,
} satisfies ArmorUserData,
onClick(event) {
event.stopPropagation();
const bounds = new Box3().setFromObject(event.object);
const point = bounds.min
.clone()
.add(bounds.max)
.divideScalar(2)
.setY(bounds.max.y);
const cameraNormal = camera.position.clone().sub(point).normalize();
const surfaceNormal = event
.normal!.clone()
.applyQuaternion(event.object.getWorldQuaternion(new Quaternion()));
const angle = surfaceNormal.angleTo(cameraNormal);
const thicknessAngled = thickness / Math.sin(Math.PI / 2 - angle);
mutateTankopediaEphemeralStore((draft) => {
draft.highlightArmor = {
type: props.type, type: props.type,
name: props.name, variant: props.type === ArmorType.External ? props.variant : 'gun',
point,
thickness, thickness,
thicknessAngled, } satisfies ArmorUserData,
angle,
color: `#${color.getHexString()}`, onClick(event) {
}; event.stopPropagation();
});
}, const bounds = new Box3().setFromObject(event.object);
}); const point = bounds.min
.clone()
.add(bounds.max)
.divideScalar(2)
.setY(bounds.max.y);
const cameraNormal = camera.position.clone().sub(point).normalize();
const surfaceNormal = event
.normal!.clone()
.applyQuaternion(
event.object.getWorldQuaternion(new Quaternion()),
);
const angle = surfaceNormal.angleTo(cameraNormal);
const thicknessAngled = thickness / Math.sin(Math.PI / 2 - angle);
mutateTankopediaEphemeralStore((draft) => {
draft.highlightArmor = {
type: props.type,
name: props.name,
point,
thickness,
thicknessAngled,
angle,
color: `#${color.getHexString()}`,
};
});
},
})}
<lineSegments material={outlineMaterial}>
<edgesGeometry args={[(node as Mesh).geometry]} />
</lineSegments>
</>
);
} }
const shoot = useCallback( const shoot = useCallback(
@@ -18,8 +18,7 @@ interface StaticArmorSceneProps {
} }
export interface ThicknessRange { export interface ThicknessRange {
// max: number; value: number;
quartile: number;
} }
export const StaticArmorScene = memo<StaticArmorSceneProps>( export const StaticArmorScene = memo<StaticArmorSceneProps>(
@@ -57,8 +56,6 @@ export const StaticArmorScene = memo<StaticArmorSceneProps>(
(state) => state.model.visual.showExternalModules, (state) => state.model.visual.showExternalModules,
); );
console.log('yeah');
useTankTransform(protagonist, turretContainer, gunContainer); useTankTransform(protagonist, turretContainer, gunContainer);
return ( return (