mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-24 20:19:49 +02:00
feat: static armor
This commit is contained in:
1 parent
e88fdd861f
commit
ceb759aba1
6 files changed
+341
-36
No files matched your search
@@ -7,6 +7,7 @@ import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawL
|
|||||||
import { modelDefinitions } from '../../../../../../core/blitzkit/modelDefinitions';
|
import { modelDefinitions } from '../../../../../../core/blitzkit/modelDefinitions';
|
||||||
import { modelTransformEvent } from '../../../../../../core/blitzkit/modelTransform';
|
import { modelTransformEvent } from '../../../../../../core/blitzkit/modelTransform';
|
||||||
import { Pose, poseEvent } from '../../../../../../core/blitzkit/pose';
|
import { Pose, poseEvent } from '../../../../../../core/blitzkit/pose';
|
||||||
|
import { tankDefinitions } from '../../../../../../core/blitzkit/tankDefinitions';
|
||||||
import { useEquipment } from '../../../../../../hooks/useEquipment';
|
import { useEquipment } from '../../../../../../hooks/useEquipment';
|
||||||
import * as Duel from '../../../../../../stores/duel';
|
import * as Duel from '../../../../../../stores/duel';
|
||||||
import * as TankopediaEphemeral from '../../../../../../stores/tankopediaEphemeral';
|
import * as TankopediaEphemeral from '../../../../../../stores/tankopediaEphemeral';
|
||||||
@@ -22,6 +23,7 @@ export function TankSandbox() {
|
|||||||
const canvas = useRef<HTMLCanvasElement>(null);
|
const canvas = useRef<HTMLCanvasElement>(null);
|
||||||
const hasImprovedVerticalStabilizer = useEquipment(122);
|
const hasImprovedVerticalStabilizer = useEquipment(122);
|
||||||
const awaitedModelDefinitions = use(modelDefinitions);
|
const awaitedModelDefinitions = use(modelDefinitions);
|
||||||
|
const awaitedTankDefinitions = use(tankDefinitions);
|
||||||
const protagonist = Duel.use((state) => state.protagonist);
|
const protagonist = Duel.use((state) => state.protagonist);
|
||||||
const mutateDuel = Duel.useMutation();
|
const mutateDuel = Duel.useMutation();
|
||||||
const tankModelDefinition = awaitedModelDefinitions[protagonist.tank.id];
|
const tankModelDefinition = awaitedModelDefinitions[protagonist.tank.id];
|
||||||
@@ -137,13 +139,14 @@ export function TankSandbox() {
|
|||||||
>
|
>
|
||||||
<Controls />
|
<Controls />
|
||||||
<SceneProps />
|
<SceneProps />
|
||||||
|
|
||||||
<TankModel />
|
<TankModel />
|
||||||
|
|
||||||
{/* idk why the shot display doesn't work without suspense here lol */}
|
{/* idk why the shot display doesn't work without suspense here lol */}
|
||||||
<Suspense fallback={<ModelLoader />}>
|
<Suspense fallback={<ModelLoader />}>
|
||||||
{armorMode === 'blitz' && <Armor />}
|
{armorMode === 'blitz' && <Armor />}
|
||||||
{armorMode === 'static' && <StaticArmor />}
|
{armorMode === 'static' && (
|
||||||
|
<StaticArmor awaitedTankDefinitions={awaitedTankDefinitions} />
|
||||||
|
)}
|
||||||
<Lighting />
|
<Lighting />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ThreeEvent, useThree } from '@react-three/fiber';
|
import { ThreeEvent, useThree } from '@react-three/fiber';
|
||||||
import { memo, useRef } from 'react';
|
import { memo, useEffect, useRef } from 'react';
|
||||||
import { Group, Mesh, MeshStandardMaterial, Vector2 } from 'three';
|
import { Group, Mesh, MeshStandardMaterial, Vector2 } from 'three';
|
||||||
import { ModelTankWrapper } from '../../../../../../../components/Armor/components/ModelTankWrapper';
|
import { ModelTankWrapper } from '../../../../../../../components/Armor/components/ModelTankWrapper';
|
||||||
import { applyPitchYawLimits } from '../../../../../../../core/blitz/applyPitchYawLimits';
|
import { applyPitchYawLimits } from '../../../../../../../core/blitz/applyPitchYawLimits';
|
||||||
@@ -13,12 +13,14 @@ import { useModel } from '../../../../../../../hooks/useModel';
|
|||||||
import { useTankTransform } from '../../../../../../../hooks/useTankTransform';
|
import { useTankTransform } from '../../../../../../../hooks/useTankTransform';
|
||||||
import * as Duel from '../../../../../../../stores/duel';
|
import * as Duel from '../../../../../../../stores/duel';
|
||||||
import * as TankopediaEphemeral from '../../../../../../../stores/tankopediaEphemeral';
|
import * as TankopediaEphemeral from '../../../../../../../stores/tankopediaEphemeral';
|
||||||
|
import * as TankopediaPersistent from '../../../../../../../stores/tankopediaPersistent';
|
||||||
|
|
||||||
export const TankModel = memo(() => {
|
export const TankModel = memo(() => {
|
||||||
const mutateDuel = Duel.useMutation();
|
const mutateDuel = Duel.useMutation();
|
||||||
const duelStore = Duel.useStore();
|
const duelStore = Duel.useStore();
|
||||||
const awaitedModelDefinitions = useAwait(modelDefinitions);
|
const awaitedModelDefinitions = useAwait(modelDefinitions);
|
||||||
const protagonist = Duel.use((draft) => draft.protagonist);
|
const protagonist = Duel.use((draft) => draft.protagonist);
|
||||||
|
const tankopediaPersistentStore = TankopediaPersistent.useStore();
|
||||||
const canvas = useThree((state) => state.gl.domElement);
|
const canvas = useThree((state) => state.gl.domElement);
|
||||||
const hullContainer = useRef<Group>(null);
|
const hullContainer = useRef<Group>(null);
|
||||||
const turretContainer = useRef<Group>(null);
|
const turretContainer = useRef<Group>(null);
|
||||||
@@ -36,6 +38,32 @@ export const TankModel = memo(() => {
|
|||||||
const { gltf } = useModel(protagonist.tank.id);
|
const { gltf } = useModel(protagonist.tank.id);
|
||||||
const nodes = Object.values(gltf.nodes);
|
const nodes = Object.values(gltf.nodes);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
function handleVisibility() {
|
||||||
|
if (!hullContainer.current) return;
|
||||||
|
|
||||||
|
const state = tankopediaPersistentStore.getState();
|
||||||
|
|
||||||
|
hullContainer.current.visible =
|
||||||
|
state.mode === 'model' || state.armorMode === 'blitz';
|
||||||
|
}
|
||||||
|
|
||||||
|
const unsubscribes = [
|
||||||
|
tankopediaPersistentStore.subscribe(
|
||||||
|
(state) => state.mode,
|
||||||
|
handleVisibility,
|
||||||
|
),
|
||||||
|
tankopediaPersistentStore.subscribe(
|
||||||
|
(state) => state.armorMode,
|
||||||
|
handleVisibility,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
handleVisibility();
|
||||||
|
|
||||||
|
return () => unsubscribes.forEach((unsubscribe) => unsubscribe());
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModelTankWrapper ref={hullContainer}>
|
<ModelTankWrapper ref={hullContainer}>
|
||||||
{nodes.map((node) => {
|
{nodes.map((node) => {
|
||||||
|
|||||||
@@ -16,23 +16,7 @@ export const CoreArmorScene = memo(() => {
|
|||||||
const modelDefinitions = useModelDefinitions();
|
const modelDefinitions = useModelDefinitions();
|
||||||
const turretContainer = useRef<Group>(null);
|
const turretContainer = useRef<Group>(null);
|
||||||
const gunContainer = useRef<Group>(null);
|
const gunContainer = useRef<Group>(null);
|
||||||
const tankopediaPersistentStore = TankopediaPersistent.useStore();
|
|
||||||
const initialTankopediaState = tankopediaPersistentStore.getState();
|
|
||||||
const protagonist = Duel.use((draft) => draft.protagonist);
|
const protagonist = Duel.use((draft) => draft.protagonist);
|
||||||
|
|
||||||
useTankTransform(protagonist, turretContainer, gunContainer);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const unsubscribe = tankopediaPersistentStore.subscribe(
|
|
||||||
(state) => state.mode,
|
|
||||||
(mode) => {
|
|
||||||
if (wrapper.current) wrapper.current.visible = mode === 'armor';
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return unsubscribe;
|
|
||||||
});
|
|
||||||
|
|
||||||
const tank = Duel.use((state) => state.protagonist.tank);
|
const tank = Duel.use((state) => state.protagonist.tank);
|
||||||
const track = Duel.use((state) => state.protagonist.track);
|
const track = Duel.use((state) => state.protagonist.track);
|
||||||
const turret = Duel.use((state) => state.protagonist.turret);
|
const turret = Duel.use((state) => state.protagonist.turret);
|
||||||
@@ -46,6 +30,21 @@ export const CoreArmorScene = memo(() => {
|
|||||||
const hullOrigin = correctZYTuple(trackModelDefinition.origin);
|
const hullOrigin = correctZYTuple(trackModelDefinition.origin);
|
||||||
const turretOrigin = correctZYTuple(tankModelDefinition.turretOrigin);
|
const turretOrigin = correctZYTuple(tankModelDefinition.turretOrigin);
|
||||||
const gunOrigin = correctZYTuple(turretModelDefinition.gunOrigin);
|
const gunOrigin = correctZYTuple(turretModelDefinition.gunOrigin);
|
||||||
|
const tankopediaPersistentStore = TankopediaPersistent.useStore();
|
||||||
|
const initialTankopediaState = tankopediaPersistentStore.getState();
|
||||||
|
|
||||||
|
useTankTransform(protagonist, turretContainer, gunContainer);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const unsubscribe = tankopediaPersistentStore.subscribe(
|
||||||
|
(state) => state.mode,
|
||||||
|
(mode) => {
|
||||||
|
if (wrapper.current) wrapper.current.visible = mode === 'armor';
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return unsubscribe;
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModelTankWrapper
|
<ModelTankWrapper
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { useThree } from '@react-three/fiber';
|
import { ThreeEvent, useThree } from '@react-three/fiber';
|
||||||
|
import { clamp } from 'lodash';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import {
|
import {
|
||||||
Intersection,
|
Intersection,
|
||||||
MeshBasicMaterial,
|
MeshBasicMaterial,
|
||||||
|
MeshStandardMaterial,
|
||||||
Object3D,
|
Object3D,
|
||||||
Plane,
|
Plane,
|
||||||
Quaternion,
|
Quaternion,
|
||||||
@@ -19,15 +21,22 @@ import { jsxTree } from '../../../../core/blitzkit/jsxTree';
|
|||||||
import { ShellType } from '../../../../core/blitzkit/tankDefinitions';
|
import { ShellType } from '../../../../core/blitzkit/tankDefinitions';
|
||||||
import * as Duel from '../../../../stores/duel';
|
import * as Duel from '../../../../stores/duel';
|
||||||
import * as TankopediaEphemeral from '../../../../stores/tankopediaEphemeral';
|
import * as TankopediaEphemeral from '../../../../stores/tankopediaEphemeral';
|
||||||
|
import { ThicknessRange } from '../../../StaticArmor/components/StaticArmorScene';
|
||||||
import { ArmorType } from '../SpacedArmorScene';
|
import { ArmorType } from '../SpacedArmorScene';
|
||||||
import { SpacedArmorSubExternal } from './components/SpacedArmorSubExternal';
|
import { SpacedArmorSubExternal } from './components/SpacedArmorSubExternal';
|
||||||
import { SpacedArmorSubSpaced } from './components/SpacedArmorSubSpaced';
|
import { SpacedArmorSubSpaced } from './components/SpacedArmorSubSpaced';
|
||||||
|
|
||||||
|
function to255(value: number) {
|
||||||
|
return Math.round(255 * value);
|
||||||
|
}
|
||||||
|
|
||||||
type SpacedArmorSceneComponentProps = {
|
type SpacedArmorSceneComponentProps = {
|
||||||
|
static?: boolean;
|
||||||
node: Object3D;
|
node: Object3D;
|
||||||
thickness: number;
|
thickness: number;
|
||||||
scene: Scene;
|
scene: Scene;
|
||||||
clip?: Plane;
|
clip?: Plane;
|
||||||
|
thicknessRange: ThicknessRange;
|
||||||
} & (
|
} & (
|
||||||
| {
|
| {
|
||||||
type: Exclude<ArmorType, ArmorType.External>;
|
type: Exclude<ArmorType, ArmorType.External>;
|
||||||
@@ -68,6 +77,8 @@ export function SpacedArmorSceneComponent({
|
|||||||
thickness,
|
thickness,
|
||||||
scene,
|
scene,
|
||||||
clip,
|
clip,
|
||||||
|
static: staticMode,
|
||||||
|
thicknessRange,
|
||||||
...props
|
...props
|
||||||
}: SpacedArmorSceneComponentProps) {
|
}: SpacedArmorSceneComponentProps) {
|
||||||
const tankopediaEphemeralStore = TankopediaEphemeral.useStore();
|
const tankopediaEphemeralStore = TankopediaEphemeral.useStore();
|
||||||
@@ -354,6 +365,41 @@ export function SpacedArmorSceneComponent({
|
|||||||
[camera],
|
[camera],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const onClick = useCallback(
|
||||||
|
async (event: ThreeEvent<MouseEvent>) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
const shot = (await shoot(event.point, event.intersections, true))!;
|
||||||
|
tankopediaEphemeralStore.setState({ shot });
|
||||||
|
},
|
||||||
|
[camera],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (staticMode) {
|
||||||
|
const x = clamp(thickness / thicknessRange.max, 0, 1);
|
||||||
|
const r = -((1 - x) ** 2) + 1;
|
||||||
|
const g = -(x ** 2) + 1;
|
||||||
|
|
||||||
|
return jsxTree(node, {
|
||||||
|
material: new MeshStandardMaterial({
|
||||||
|
color:
|
||||||
|
props.type === ArmorType.Core
|
||||||
|
? `rgb(${to255(r)}, ${to255(g)}, 0)`
|
||||||
|
: props.type === ArmorType.Spaced
|
||||||
|
? `rgb(${to255(1 - x)}, 0, ${to255(1 - x)})`
|
||||||
|
: `rgb(0, 255, 255)`,
|
||||||
|
opacity: props.type === ArmorType.External ? 0.25 : 1,
|
||||||
|
transparent: props.type === ArmorType.External,
|
||||||
|
}),
|
||||||
|
|
||||||
|
userData: {
|
||||||
|
type: props.type,
|
||||||
|
variant: props.type === ArmorType.External ? props.variant : 'gun',
|
||||||
|
thickness,
|
||||||
|
} satisfies ArmorUserData,
|
||||||
|
onClick: props.type === ArmorType.Core ? onClick : () => {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{props.type === ArmorType.Core &&
|
{props.type === ArmorType.Core &&
|
||||||
@@ -366,18 +412,7 @@ export function SpacedArmorSceneComponent({
|
|||||||
type: ArmorType.Core,
|
type: ArmorType.Core,
|
||||||
thickness,
|
thickness,
|
||||||
} satisfies ArmorUserData,
|
} satisfies ArmorUserData,
|
||||||
|
onClick,
|
||||||
async onClick(event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
|
|
||||||
const shot = (await shoot(
|
|
||||||
event.point,
|
|
||||||
event.intersections,
|
|
||||||
true,
|
|
||||||
))!;
|
|
||||||
|
|
||||||
tankopediaEphemeralStore.setState({ shot });
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
node.uuid,
|
node.uuid,
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,3 +1,230 @@
|
|||||||
export function StaticArmorScene() {
|
import { memo, useMemo, useRef } from 'react';
|
||||||
return null;
|
import { Group, Plane, Scene, Vector3 } from 'three';
|
||||||
|
import { correctZYTuple } from '../../../core/blitz/correctZYTuple';
|
||||||
|
import { nameToArmorId } from '../../../core/blitzkit/nameToArmorId';
|
||||||
|
import { resolveArmor } from '../../../core/blitzkit/resolveThickness';
|
||||||
|
import { TankDefinitions } from '../../../core/blitzkit/tankDefinitions';
|
||||||
|
import { useArmor } from '../../../hooks/useArmor';
|
||||||
|
import { useModel } from '../../../hooks/useModel';
|
||||||
|
import { useModelDefinitions } from '../../../hooks/useModelDefinitions';
|
||||||
|
import { useTankTransform } from '../../../hooks/useTankTransform';
|
||||||
|
import * as Duel from '../../../stores/duel';
|
||||||
|
import { ModelTankWrapper } from '../../Armor/components/ModelTankWrapper';
|
||||||
|
import { ArmorType } from '../../Armor/components/SpacedArmorScene';
|
||||||
|
import { SpacedArmorSceneComponent } from '../../Armor/components/SpacedArmorSceneComponent';
|
||||||
|
|
||||||
|
interface StaticArmorSceneProps {
|
||||||
|
scene: Scene;
|
||||||
|
awaitedTankDefinitions: TankDefinitions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ThicknessRange {
|
||||||
|
max: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StaticArmorScene = memo<StaticArmorSceneProps>(
|
||||||
|
({ scene, awaitedTankDefinitions }) => {
|
||||||
|
const wrapper = useRef<Group>(null);
|
||||||
|
const awaitedTankModelDefinitions = useModelDefinitions();
|
||||||
|
const turretContainer = useRef<Group>(null);
|
||||||
|
const gunContainer = useRef<Group>(null);
|
||||||
|
const protagonist = Duel.use((draft) => draft.protagonist);
|
||||||
|
const tank = Duel.use((state) => state.protagonist.tank);
|
||||||
|
const track = Duel.use((state) => state.protagonist.track);
|
||||||
|
const turret = Duel.use((state) => state.protagonist.turret);
|
||||||
|
const gun = Duel.use((state) => state.protagonist.gun);
|
||||||
|
const armorGltf = useArmor(tank.id);
|
||||||
|
const { gltf: modelGltf } = useModel(tank.id);
|
||||||
|
const armorNodes = Object.values(armorGltf.nodes);
|
||||||
|
const modelNodes = Object.values(modelGltf.nodes);
|
||||||
|
const tankModelDefinition = awaitedTankModelDefinitions[tank.id];
|
||||||
|
const trackModelDefinition = tankModelDefinition.tracks[track.id];
|
||||||
|
const turretModelDefinition = tankModelDefinition.turrets[turret.id];
|
||||||
|
const gunModelDefinition = turretModelDefinition.guns[gun.id];
|
||||||
|
const hullOrigin = correctZYTuple(trackModelDefinition.origin);
|
||||||
|
const turretOrigin = correctZYTuple(tankModelDefinition.turretOrigin);
|
||||||
|
const gunOrigin = correctZYTuple(turretModelDefinition.gunOrigin);
|
||||||
|
const maskOrigin =
|
||||||
|
gunModelDefinition.mask === undefined
|
||||||
|
? undefined
|
||||||
|
: gunModelDefinition.mask + hullOrigin.y + turretOrigin.y + gunOrigin.y;
|
||||||
|
const thicknessRange = useMemo(() => {
|
||||||
|
const thicknesses = Object.values(awaitedTankDefinitions)
|
||||||
|
.filter((thisTank) => thisTank.tier === tank.tier)
|
||||||
|
.map((tank) => awaitedTankModelDefinitions[tank.id])
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((model) => {
|
||||||
|
const tankThicknesses = Object.values(model.armor.thickness);
|
||||||
|
const trackThicknesses = Object.values(model.tracks)
|
||||||
|
.map((track) => track.thickness)
|
||||||
|
.flat();
|
||||||
|
const turretThicknesses = Object.values(model.turrets)
|
||||||
|
.map((turret) => {
|
||||||
|
const turretThicknesses = Object.values(turret.armor.thickness);
|
||||||
|
const gunThicknesses = Object.values(turret.guns)
|
||||||
|
.map((gun) => {
|
||||||
|
return Object.values(gun.armor.thickness);
|
||||||
|
})
|
||||||
|
.flat();
|
||||||
|
|
||||||
|
return [...turretThicknesses, ...gunThicknesses];
|
||||||
|
})
|
||||||
|
.flat();
|
||||||
|
|
||||||
|
return [
|
||||||
|
...tankThicknesses,
|
||||||
|
...trackThicknesses,
|
||||||
|
...turretThicknesses,
|
||||||
|
];
|
||||||
|
})
|
||||||
|
.flat();
|
||||||
|
|
||||||
|
const max = Math.max(...thicknesses) * 0.75;
|
||||||
|
|
||||||
|
return { max } satisfies ThicknessRange;
|
||||||
|
}, [tank.tier]);
|
||||||
|
|
||||||
|
useTankTransform(protagonist, turretContainer, gunContainer);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModelTankWrapper ref={wrapper}>
|
||||||
|
<group position={hullOrigin}>
|
||||||
|
{armorNodes.map((node) => {
|
||||||
|
const isHull = node.name.startsWith('hull_');
|
||||||
|
const isVisible = isHull;
|
||||||
|
const armorId = nameToArmorId(node.name);
|
||||||
|
const { spaced, thickness } = resolveArmor(
|
||||||
|
tankModelDefinition.armor,
|
||||||
|
armorId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isVisible || thickness === undefined) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SpacedArmorSceneComponent
|
||||||
|
thicknessRange={thicknessRange}
|
||||||
|
static
|
||||||
|
scene={scene}
|
||||||
|
key={node.uuid}
|
||||||
|
type={spaced ? ArmorType.Spaced : ArmorType.Core}
|
||||||
|
thickness={thickness}
|
||||||
|
node={node}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</group>
|
||||||
|
|
||||||
|
{modelNodes.map((node) => {
|
||||||
|
const isWheel = node.name.startsWith('chassis_wheel_');
|
||||||
|
const isTrack = node.name.startsWith('chassis_track_');
|
||||||
|
const isVisible = isWheel || isTrack;
|
||||||
|
|
||||||
|
if (!isVisible) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SpacedArmorSceneComponent
|
||||||
|
thicknessRange={thicknessRange}
|
||||||
|
static
|
||||||
|
scene={scene}
|
||||||
|
key={node.uuid}
|
||||||
|
type={ArmorType.External}
|
||||||
|
thickness={trackModelDefinition.thickness}
|
||||||
|
variant="track"
|
||||||
|
node={node}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
<group ref={turretContainer}>
|
||||||
|
{armorNodes.map((node) => {
|
||||||
|
const isCurrentTurret = node.name.startsWith(
|
||||||
|
`turret_${turretModelDefinition.model.toString().padStart(2, '0')}`,
|
||||||
|
);
|
||||||
|
const isVisible = isCurrentTurret;
|
||||||
|
const armorId = nameToArmorId(node.name);
|
||||||
|
const { spaced, thickness } = resolveArmor(
|
||||||
|
turretModelDefinition.armor,
|
||||||
|
armorId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isVisible || thickness === undefined) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<group key={node.uuid} position={turretOrigin}>
|
||||||
|
<SpacedArmorSceneComponent
|
||||||
|
thicknessRange={thicknessRange}
|
||||||
|
static
|
||||||
|
scene={scene}
|
||||||
|
key={node.uuid}
|
||||||
|
type={spaced ? ArmorType.Spaced : ArmorType.Core}
|
||||||
|
thickness={thickness}
|
||||||
|
node={node}
|
||||||
|
/>
|
||||||
|
</group>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
<group ref={gunContainer}>
|
||||||
|
{armorNodes.map((node) => {
|
||||||
|
const isCurrentGun = node.name.startsWith(
|
||||||
|
`gun_${gunModelDefinition.model.toString().padStart(2, '0')}`,
|
||||||
|
);
|
||||||
|
const isVisible = isCurrentGun;
|
||||||
|
const armorId = nameToArmorId(node.name);
|
||||||
|
const { spaced, thickness } = resolveArmor(
|
||||||
|
gunModelDefinition.armor,
|
||||||
|
armorId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isVisible || thickness === undefined) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<group
|
||||||
|
key={node.uuid}
|
||||||
|
position={turretOrigin.clone().add(gunOrigin)}
|
||||||
|
>
|
||||||
|
<SpacedArmorSceneComponent
|
||||||
|
thicknessRange={thicknessRange}
|
||||||
|
static
|
||||||
|
scene={scene}
|
||||||
|
type={spaced ? ArmorType.Spaced : ArmorType.Core}
|
||||||
|
thickness={thickness}
|
||||||
|
node={node}
|
||||||
|
/>
|
||||||
|
</group>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
{modelNodes.map((node) => {
|
||||||
|
const gunString = `gun_${gunModelDefinition.model.toString().padStart(2, '0')}`;
|
||||||
|
const isCurrentGun = gunModelDefinition.mask
|
||||||
|
? node.name.startsWith(gunString)
|
||||||
|
: node.name === gunString;
|
||||||
|
const isVisible = isCurrentGun;
|
||||||
|
|
||||||
|
if (!isVisible) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SpacedArmorSceneComponent
|
||||||
|
thicknessRange={thicknessRange}
|
||||||
|
static
|
||||||
|
scene={scene}
|
||||||
|
key={node.uuid}
|
||||||
|
type={ArmorType.External}
|
||||||
|
thickness={gunModelDefinition.thickness}
|
||||||
|
variant="gun"
|
||||||
|
node={node}
|
||||||
|
clip={
|
||||||
|
maskOrigin === undefined
|
||||||
|
? undefined
|
||||||
|
: new Plane(new Vector3(0, 0, -1), -maskOrigin)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</ModelTankWrapper>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
@@ -1,5 +1,18 @@
|
|||||||
|
import { useThree } from '@react-three/fiber';
|
||||||
|
import { TankDefinitions } from '../../core/blitzkit/tankDefinitions';
|
||||||
import { StaticArmorScene } from './components/StaticArmorScene';
|
import { StaticArmorScene } from './components/StaticArmorScene';
|
||||||
|
|
||||||
export function StaticArmor() {
|
export function StaticArmor({
|
||||||
return <StaticArmorScene />;
|
awaitedTankDefinitions,
|
||||||
|
}: {
|
||||||
|
awaitedTankDefinitions: TankDefinitions;
|
||||||
|
}) {
|
||||||
|
const scene = useThree((state) => state.scene);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StaticArmorScene
|
||||||
|
awaitedTankDefinitions={awaitedTankDefinitions}
|
||||||
|
scene={scene}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user