feat: transition skeleton

This commit is contained in:
tresabhi committed 2025-04-16 23:18:24 -05:00
1 parent 64d346ccb6
commit da704e6181
5 files changed
+147 -3

No files matched your search

@@ -0,0 +1,116 @@
import { useEffect, useRef } from 'react';
import { Group, ShaderMaterial, UniformsLib, UniformsUtils } from 'three';
import { jsxTree } from '../../../../../../../core/blitzkit/jsxTree';
import { useModel } from '../../../../../../../hooks/useModel';
import { useTankModelDefinition } from '../../../../../../../hooks/useTankModelDefinition';
import { useTankTransform } from '../../../../../../../hooks/useTankTransform';
import { Duel } from '../../../../../../../stores/duel';
import {
ShootingRangeZoom,
TankopediaEphemeral,
} from '../../../../../../../stores/tankopediaEphemeral';
import { ModelTankWrapper } from '../../../../../../Armor/components/ModelTankWrapper';
import fragmentShader from './shaders/fragment.glsl?raw';
import vertexShader from './shaders/vertex.glsl?raw';
const skeletonMaterial = new ShaderMaterial({
vertexShader,
fragmentShader,
wireframe: true,
fog: true,
transparent: true,
uniforms: UniformsUtils.merge([UniformsLib.common, UniformsLib.fog]),
depthWrite: false,
});
export function TransitionSkeleton() {
const protagonist = Duel.use((draft) => draft.protagonist);
const tankopediaEphemeralStore = TankopediaEphemeral.useStore();
const track = Duel.use((state) => state.protagonist.track);
const turret = Duel.use((state) => state.protagonist.turret);
const hullContainer = useRef<Group>(null);
const turretContainer = useRef<Group>(null);
const gunContainer = useRef<Group>(null);
const tankModelDefinition = useTankModelDefinition();
const turretModelDefinition =
tankModelDefinition.turrets[protagonist.turret.id];
const gunModelDefinition =
turretModelDefinition.guns[protagonist.gun.gun_type!.value.base.id];
const { gltf } = useModel(protagonist.tank.id);
const nodes = Object.values(gltf.nodes);
const revealed = tankopediaEphemeralStore((state) => state.revealed);
useEffect(() => {
const unsubscribeShootingRangeZoom = tankopediaEphemeralStore.subscribe(
(state) => state.shootingRangeZoom,
(zoom) => {
if (!hullContainer.current) return;
hullContainer.current.visible = zoom === ShootingRangeZoom.Arcade;
},
);
return unsubscribeShootingRangeZoom;
}, []);
useTankTransform(track, turret, turretContainer, gunContainer);
return (
<ModelTankWrapper ref={hullContainer} visible={revealed}>
{nodes.map((node) => {
const isHull = node.name === 'hull';
const isWheel = node.name.startsWith('chassis_wheel_');
const isTrack = node.name.startsWith('chassis_track_');
const isVisible = isHull || isWheel || isTrack;
if (!isVisible) return null;
return jsxTree(node, {
mesh(_, props, key) {
return <mesh {...props} key={key} material={skeletonMaterial} />;
},
});
})}
<group ref={turretContainer}>
{nodes.map((node) => {
const isCurrentTurret =
node.name ===
`turret_${turretModelDefinition.model_id.toString().padStart(2, '0')}`;
const isVisible = isCurrentTurret;
if (!isVisible) return null;
return jsxTree(node, {
mesh(_, props, key) {
return <mesh {...props} key={key} material={skeletonMaterial} />;
},
});
})}
<group ref={gunContainer}>
{nodes.map((node) => {
const isCurrentMantlet =
node.name ===
`gun_${gunModelDefinition.model_id
.toString()
.padStart(2, '0')}_mask`;
const isCurrentGun =
node.name ===
`gun_${gunModelDefinition.model_id.toString().padStart(2, '0')}`;
const isVisible = isCurrentGun || isCurrentMantlet;
if (!isVisible) return null;
return jsxTree(node, {
mesh(_, props, key) {
return (
<mesh {...props} key={key} material={skeletonMaterial} />
);
},
});
})}
</group>
</group>
</ModelTankWrapper>
);
}
@@ -0,0 +1,9 @@
#define USE_FOG
#include <fog_pars_fragment>
void main() {
float fogFactor = smoothstep(fogNear, fogFar, vFogDepth);
gl_FragColor = vec4(0.75, 0.75, 0.75, fogFactor);
gl_FragColor.rgb = mix(gl_FragColor.rgb, fogColor, 1.0 - fogFactor);
}
@@ -0,0 +1,17 @@
#define USE_FOG
#include <fog_pars_vertex>
varying vec3 vNormal;
varying vec3 vViewPosition;
void main() {
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
vViewPosition = mvPosition.xyz;
vNormal = normalMatrix * normal;
gl_Position = projectionMatrix * mvPosition;
#include <fog_vertex>
}
@@ -32,17 +32,18 @@ import { InitialFogReveal } from './components/InitialFogReveal';
import { Lighting } from './components/Lighting';
import { SceneProps } from './components/SceneProps';
import { TankModel } from './components/TankModel';
import { TransitionSkeleton } from './components/TransitionSkeleton';
interface TankSandboxProps {
thicknessRange: ThicknessRange;
naked?: boolean;
}
export const forNear0 = 15;
export const forNear0 = 20;
export const fogFar0 = 25;
export const forNear1 = 0;
export const fogFar1 = 0;
export const fogAnimationTime = 0.5;
export const fogAnimationTime = 1.5;
export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
({ thicknessRange, naked }, ref) => {
@@ -237,6 +238,7 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
{(display === TankopediaDisplay.Model ||
(display === TankopediaDisplay.DynamicArmor &&
!hideTankModelUnderArmor)) && <TankModel />}
<TransitionSkeleton />
<ShotDisplay />
<ArmorPlateDisplay />
<AutoClear />
@@ -187,7 +187,7 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
</Heading>
<Heading
weight="regular"
weight="medium"
size={
disturbed
? { initial: '7', lg: '8' }