Merge pull request #81 from tresabhi/1.16.1

This commit is contained in:
TrèsAbhi authored and GitHub committed 2024-02-29 16:05:48 -06:00
commit 7fb64e690f
37 files changed
+1683 -1781

No files matched your search

@@ -4,7 +4,8 @@ import { PerspectiveCamera } from '@react-three/drei';
import { Canvas } from '@react-three/fiber';
import { Suspense, use, useEffect, useRef, useState } from 'react';
import { isMobile } from 'react-device-detect';
import { ShotDisplay } from '../../../../../../components/ShotDisplay';
import { Armor } from '../../../../../../components/Armor';
import { ShotDisplay } from '../../../../../../components/Armor/components/ShotDisplay';
import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits';
import { modelDefinitions } from '../../../../../../core/blitzkrieg/modelDefinitions';
import { modelTransformEvent } from '../../../../../../core/blitzkrieg/modelTransform';
@@ -24,9 +25,6 @@ import { RotationInputs } from '../QuickInputs';
import { SceneProps } from '../SceneProps';
import { ModelLoader } from './components/ModelLoader';
import { Options } from './components/Options';
import { TankArmor } from './components/TankArmor';
import { ExternalModuleMask } from './components/TankArmor/components/ExternalModuleMask';
import { SpacedArmorDepth } from './components/TankArmor/components/SpacedArmorDepth';
import { TankModel } from './components/TankModel';
export function TankSandbox() {
@@ -198,12 +196,11 @@ export function TankSandbox() {
<SceneProps />
<Suspense fallback={<ModelLoader />}>
<ShotDisplay />
{/* TODO: add rotation cube to the top right */}
<Lighting />
<TankModel />
<TankArmor />
<ExternalModuleMask ornamental />
<SpacedArmorDepth ornamental />
<Armor />
<ShotDisplay />
</Suspense>
</Canvas>
</div>
@@ -30,6 +30,7 @@ export function Options({ isFullScreen, canvasWrapper }: OptionsProps) {
const wireframe = useTankopediaPersistent(
(state) => state.model.visual.wireframe,
);
const opaque = useTankopediaPersistent((state) => state.model.visual.opaque);
const fullScreenAvailable =
typeof document !== 'undefined' && document.fullscreenEnabled;
const environment = useTankopediaPersistent(
@@ -78,6 +79,8 @@ export function Options({ isFullScreen, canvasWrapper }: OptionsProps) {
</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.Label>Armor</DropdownMenu.Label>
<DropdownMenu.CheckboxItem
checked={greenPenetration}
onCheckedChange={(checked) => {
@@ -89,6 +92,30 @@ export function Options({ isFullScreen, canvasWrapper }: OptionsProps) {
Green penetration
</DropdownMenu.CheckboxItem>
<DropdownMenu.CheckboxItem
checked={wireframe}
onCheckedChange={(checked) => {
mutateTankopediaPersistent((draft) => {
draft.model.visual.wireframe = checked;
});
}}
>
Wireframe
</DropdownMenu.CheckboxItem>
<DropdownMenu.CheckboxItem
checked={opaque}
onCheckedChange={(checked) => {
mutateTankopediaPersistent((draft) => {
draft.model.visual.opaque = checked;
});
}}
>
Opaque
</DropdownMenu.CheckboxItem>
<DropdownMenu.Label>Environment</DropdownMenu.Label>
<DropdownMenu.CheckboxItem
checked={showGrid}
onCheckedChange={(checked) => {
@@ -132,17 +159,6 @@ export function Options({ isFullScreen, canvasWrapper }: OptionsProps) {
</DropdownMenu.RadioGroup>
</DropdownMenu.SubContent>
</DropdownMenu.Sub>
<DropdownMenu.CheckboxItem
checked={wireframe}
onCheckedChange={(checked) => {
mutateTankopediaPersistent((draft) => {
draft.model.visual.wireframe = checked;
});
}}
>
Wireframe
</DropdownMenu.CheckboxItem>
</DropdownMenu.Content>
</DropdownMenu.Root>
)}
@@ -1,213 +0,0 @@
import { useEffect, useRef } from 'react';
import { Euler, Group, Mesh, Vector3 } from 'three';
import { degToRad } from 'three/src/math/MathUtils';
import { ArmorMesh } from '../../../../../../../../../components/ArmorMesh';
import { I_HAT, J_HAT, K_HAT } from '../../../../../../../../../constants/axis';
import {
ModelTransformEventData,
modelTransformEvent,
} from '../../../../../../../../../core/blitzkrieg/modelTransform';
import { nameToArmorId } from '../../../../../../../../../core/blitzkrieg/nameToArmorId';
import { resolveArmor } from '../../../../../../../../../core/blitzkrieg/resolveThickness';
import { useArmor } from '../../../../../../../../../hooks/useArmor';
import { useModelDefinitions } from '../../../../../../../../../hooks/useModelDefinitions';
import { useDuel } from '../../../../../../../../../stores/duel';
import { useTankopediaPersistent } from '../../../../../../../../../stores/tankopedia';
export function ArmorHighlighting() {
const protagonist = useDuel((state) => state.protagonist!);
const wrapper = useRef<Group>(null);
const modelDefinitions = useModelDefinitions();
const gunContainer = useRef<Group>(null);
const turretContainer = useRef<Group>(null);
const initialTankopediaState = useTankopediaPersistent.getState();
useEffect(() => {
const hullOrigin = new Vector3(
trackModelDefinition.origin[0],
trackModelDefinition.origin[1],
-trackModelDefinition.origin[2],
).applyAxisAngle(I_HAT, Math.PI / 2);
const turretOrigin = new Vector3(
tankModelDefinition.turretOrigin[0],
tankModelDefinition.turretOrigin[1],
-tankModelDefinition.turretOrigin[2],
).applyAxisAngle(I_HAT, Math.PI / 2);
const turretPosition = new Vector3();
const turretRotation = new Euler();
const gunPosition = new Vector3();
const gunRotation = new Euler();
function handleModelTransform({ pitch, yaw }: ModelTransformEventData) {
if (yaw === undefined) return;
gunPosition
.set(0, 0, 0)
.sub(hullOrigin)
.sub(turretOrigin)
.sub(gunOrigin)
.applyAxisAngle(I_HAT, pitch)
.add(gunOrigin)
.add(turretOrigin)
.add(hullOrigin);
gunRotation.set(pitch, 0, 0);
gunContainer.current?.position.copy(gunPosition);
gunContainer.current?.rotation.copy(gunRotation);
turretPosition
.set(0, 0, 0)
.sub(hullOrigin)
.sub(turretOrigin)
.applyAxisAngle(new Vector3(0, 0, 1), yaw);
turretRotation.set(0, 0, yaw);
if (tankModelDefinition.turretRotation) {
const initialPitch = -degToRad(
tankModelDefinition.turretRotation.pitch,
);
const initialYaw = -degToRad(tankModelDefinition.turretRotation.yaw);
const initialRoll = -degToRad(tankModelDefinition.turretRotation.roll);
turretPosition
.applyAxisAngle(I_HAT, initialPitch)
.applyAxisAngle(J_HAT, initialRoll)
.applyAxisAngle(K_HAT, initialYaw);
turretRotation.x += initialPitch;
turretRotation.y += initialRoll;
turretRotation.z += initialYaw;
}
turretPosition.add(turretOrigin).add(hullOrigin);
turretContainer.current?.position.copy(turretPosition);
turretContainer.current?.rotation.copy(turretRotation);
}
const unsubscribe = useTankopediaPersistent.subscribe(
(state) => state.mode,
(mode) => {
if (wrapper.current) wrapper.current.visible = mode === 'armor';
},
);
handleModelTransform(protagonist);
modelTransformEvent.on(handleModelTransform);
return () => {
modelTransformEvent.off(handleModelTransform);
unsubscribe();
};
});
const armorGltf = useArmor(protagonist.tank.id);
const armorNodes = Object.values(armorGltf.nodes);
const tankModelDefinition = modelDefinitions[protagonist.tank.id];
const trackModelDefinition = tankModelDefinition.tracks[protagonist.track.id];
const turretModelDefinition =
tankModelDefinition.turrets[protagonist.turret.id];
const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id];
const hullOrigin = new Vector3(
trackModelDefinition.origin[0],
trackModelDefinition.origin[1],
-trackModelDefinition.origin[2],
);
const turretOrigin = new Vector3(
tankModelDefinition.turretOrigin[0],
tankModelDefinition.turretOrigin[1],
-tankModelDefinition.turretOrigin[2],
).applyAxisAngle(I_HAT, Math.PI / 2);
const gunOrigin = new Vector3(
turretModelDefinition.gunOrigin[0],
turretModelDefinition.gunOrigin[1],
-turretModelDefinition.gunOrigin[2],
).applyAxisAngle(I_HAT, Math.PI / 2);
const maxThickness = Math.max(
trackModelDefinition.thickness,
gunModelDefinition.thickness,
...armorNodes
.map((node) => {
const armorId = nameToArmorId(node.name);
return resolveArmor(tankModelDefinition.armor, armorId).thickness;
})
.filter(Boolean),
);
return (
<group
ref={wrapper}
rotation={[-Math.PI / 2, 0, 0]}
visible={initialTankopediaState.mode === 'armor'}
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 || spaced) return null;
return (
<ArmorMesh
maxThickness={maxThickness}
key={node.uuid}
geometry={(node as Mesh).geometry}
thickness={thickness}
/>
);
})}
<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 || spaced) return null;
return (
<ArmorMesh
maxThickness={maxThickness}
key={node.uuid}
geometry={(node as Mesh).geometry}
position={turretOrigin}
thickness={thickness}
/>
);
})}
<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 || spaced) return null;
return (
<ArmorMesh
maxThickness={maxThickness}
key={node.uuid}
geometry={(node as Mesh).geometry}
position={turretOrigin.clone().add(gunOrigin)}
thickness={thickness}
/>
);
})}
</group>
</group>
</group>
);
}
@@ -1,286 +0,0 @@
import { memo, useEffect, useRef } from 'react';
import { Euler, Group, Vector3 } from 'three';
import { degToRad } from 'three/src/math/MathUtils';
import { ArmorMeshExternalModuleMask } from '../../../../../../../../../components/ArmorMesh';
import { I_HAT, J_HAT, K_HAT } from '../../../../../../../../../constants/axis';
import {
ModelTransformEventData,
modelTransformEvent,
} from '../../../../../../../../../core/blitzkrieg/modelTransform';
import { nameToArmorId } from '../../../../../../../../../core/blitzkrieg/nameToArmorId';
import { resolveArmor } from '../../../../../../../../../core/blitzkrieg/resolveThickness';
import { useArmor } from '../../../../../../../../../hooks/useArmor';
import { useModel } from '../../../../../../../../../hooks/useModel';
import { useModelDefinitions } from '../../../../../../../../../hooks/useModelDefinitions';
import { useDuel } from '../../../../../../../../../stores/duel';
import { useTankopediaPersistent } from '../../../../../../../../../stores/tankopedia';
interface ExternalModuleMaskProps {
ornamental?: boolean;
}
/**
* When rendered, generates a mask and thickness buffer for external modules.
* - R: 1 means external module
* - G: normalized thickness; multiply with max thickness to get nominal thickness
* - B: no data
* - A: 1 means is armor; 0 is background
*/
export const ExternalModuleMask = memo<ExternalModuleMaskProps>(
({ ornamental = false }) => {
const protagonist = useDuel((state) => state.protagonist!);
const wrapper = useRef<Group>(null);
const modelDefinitions = useModelDefinitions();
const turretContainer = useRef<Group>(null);
const gunContainer = useRef<Group>(null);
const initialTankopediaState = useTankopediaPersistent.getState();
useEffect(() => {
const hullOrigin = new Vector3(
trackModelDefinition.origin[0],
trackModelDefinition.origin[1],
-trackModelDefinition.origin[2],
).applyAxisAngle(I_HAT, Math.PI / 2);
const turretOrigin = new Vector3(
tankModelDefinition.turretOrigin[0],
tankModelDefinition.turretOrigin[1],
-tankModelDefinition.turretOrigin[2],
).applyAxisAngle(I_HAT, Math.PI / 2);
const gunOrigin = new Vector3(
turretModelDefinition.gunOrigin[0],
turretModelDefinition.gunOrigin[1],
-turretModelDefinition.gunOrigin[2],
).applyAxisAngle(I_HAT, Math.PI / 2);
const turretPosition = new Vector3();
const turretRotation = new Euler();
const gunPosition = new Vector3();
const gunRotation = new Euler();
function handleModelTransform({ yaw, pitch }: ModelTransformEventData) {
gunPosition
.set(0, 0, 0)
.sub(hullOrigin)
.sub(turretOrigin)
.sub(gunOrigin)
.applyAxisAngle(I_HAT, pitch)
.add(gunOrigin)
.add(turretOrigin)
.add(hullOrigin);
gunRotation.set(pitch, 0, 0);
gunContainer.current?.position.copy(gunPosition);
gunContainer.current?.rotation.copy(gunRotation);
if (yaw === undefined) return;
turretPosition
.set(0, 0, 0)
.sub(turretOrigin)
.sub(hullOrigin)
.applyAxisAngle(new Vector3(0, 0, 1), yaw);
turretRotation.set(0, 0, yaw);
if (tankModelDefinition.turretRotation) {
const initialPitch = -degToRad(
tankModelDefinition.turretRotation.pitch,
);
const initialYaw = -degToRad(tankModelDefinition.turretRotation.yaw);
const initialRoll = -degToRad(
tankModelDefinition.turretRotation.roll,
);
turretPosition
.applyAxisAngle(I_HAT, initialPitch)
.applyAxisAngle(J_HAT, initialRoll)
.applyAxisAngle(K_HAT, initialYaw);
turretRotation.x += initialPitch;
turretRotation.y += initialRoll;
turretRotation.z += initialYaw;
}
turretPosition.add(turretOrigin).add(hullOrigin);
turretContainer.current?.position.copy(turretPosition);
turretContainer.current?.rotation.copy(turretRotation);
}
handleModelTransform(protagonist);
modelTransformEvent.on(handleModelTransform);
return () => {
modelTransformEvent.off(handleModelTransform);
};
});
useEffect(() => {
const unsubscribe = useTankopediaPersistent.subscribe(
(state) => state.mode,
(mode) => {
if (wrapper.current) wrapper.current.visible = mode === 'armor';
},
);
return unsubscribe;
});
const armorGltf = useArmor(protagonist.tank.id);
const { gltf: modelGltf } = useModel(protagonist.tank.id);
const armorNodes = Object.values(armorGltf.nodes);
const modelNodes = Object.values(modelGltf.nodes);
const tankModelDefinition = modelDefinitions[protagonist.tank.id];
const trackModelDefinition =
tankModelDefinition.tracks[protagonist.track.id];
const turretModelDefinition =
tankModelDefinition.turrets[protagonist.turret.id];
const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id];
const maxThickness = Math.max(
trackModelDefinition.thickness,
gunModelDefinition.thickness,
...armorNodes
.map((node) => {
const armorId = nameToArmorId(node.name);
return resolveArmor(tankModelDefinition.armor, armorId).thickness;
})
.filter(Boolean),
);
const hullOrigin = new Vector3(
trackModelDefinition.origin[0],
trackModelDefinition.origin[1],
-trackModelDefinition.origin[2],
).applyAxisAngle(I_HAT, Math.PI / 2);
const turretOrigin = new Vector3(
tankModelDefinition.turretOrigin[0],
tankModelDefinition.turretOrigin[1],
-tankModelDefinition.turretOrigin[2],
).applyAxisAngle(I_HAT, Math.PI / 2);
const gunOrigin = new Vector3(
turretModelDefinition.gunOrigin[0],
turretModelDefinition.gunOrigin[1],
-turretModelDefinition.gunOrigin[2],
).applyAxisAngle(I_HAT, Math.PI / 2);
return (
<group
ref={wrapper}
rotation={[-Math.PI / 2, 0, 0]}
visible={initialTankopediaState.mode === 'armor'}
>
<group position={hullOrigin}>
{armorNodes.map((node) => {
const isHull = node.name.startsWith('hull_');
const isVisible = isHull;
const armorId = nameToArmorId(node.name);
const { thickness, spaced } = resolveArmor(
tankModelDefinition.armor,
armorId,
);
if (!isVisible || thickness === undefined || spaced) return null;
return (
<ArmorMeshExternalModuleMask
ornamental={ornamental}
exclude
key={node.uuid}
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 (
<ArmorMeshExternalModuleMask
ornamental={ornamental}
maxThickness={maxThickness}
thickness={trackModelDefinition.thickness}
key={node.uuid}
node={node}
/>
);
})}
<group ref={turretContainer}>
<group position={hullOrigin}>
{armorNodes.map((node) => {
const isCurrentTurret = node.name.startsWith(
`turret_${turretModelDefinition.model.toString().padStart(2, '0')}`,
);
const isVisible = isCurrentTurret;
const armorId = nameToArmorId(node.name);
const { thickness, spaced } = resolveArmor(
turretModelDefinition.armor,
armorId,
);
if (!isVisible || thickness === undefined || spaced) return null;
return (
<group key={node.uuid} position={turretOrigin}>
<ArmorMeshExternalModuleMask
ornamental={ornamental}
exclude
node={node}
/>
</group>
);
})}
</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 { thickness, spaced } = resolveArmor(
gunModelDefinition.armor,
armorId,
);
if (!isVisible || thickness === undefined || spaced) return null;
return (
<group
key={node.uuid}
position={turretOrigin.clone().add(gunOrigin)}
>
<ArmorMeshExternalModuleMask
ornamental={ornamental}
exclude
node={node}
/>
</group>
);
})}
{modelNodes.map((node) => {
const isCurrentGun =
node.name ===
`gun_${gunModelDefinition.model.toString().padStart(2, '0')}`;
const isVisible = isCurrentGun;
if (!isVisible) return null;
return (
<ArmorMeshExternalModuleMask
ornamental={ornamental}
maxThickness={maxThickness}
thickness={gunModelDefinition.thickness}
key={node.uuid}
node={node}
/>
);
})}
</group>
</group>
</group>
);
},
);
@@ -1,299 +0,0 @@
import { memo, useEffect, useRef } from 'react';
import { Euler, Group, Vector3 } from 'three';
import { degToRad } from 'three/src/math/MathUtils';
import { ArmorMeshSpacedArmorDepth } from '../../../../../../../../../components/ArmorMesh/components/SpacedArmorDepth';
import { I_HAT, J_HAT, K_HAT } from '../../../../../../../../../constants/axis';
import {
ModelTransformEventData,
modelTransformEvent,
} from '../../../../../../../../../core/blitzkrieg/modelTransform';
import { nameToArmorId } from '../../../../../../../../../core/blitzkrieg/nameToArmorId';
import { resolveArmor } from '../../../../../../../../../core/blitzkrieg/resolveThickness';
import { useArmor } from '../../../../../../../../../hooks/useArmor';
import { useModel } from '../../../../../../../../../hooks/useModel';
import { useModelDefinitions } from '../../../../../../../../../hooks/useModelDefinitions';
import { useDuel } from '../../../../../../../../../stores/duel';
import { useTankopediaPersistent } from '../../../../../../../../../stores/tankopedia';
interface SpacedArmorDepthProps {
ornamental?: boolean;
}
/**
* 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>(
({ ornamental = false }) => {
const protagonist = useDuel((state) => state.protagonist!);
const wrapper = useRef<Group>(null);
const modelDefinitions = useModelDefinitions();
const turretContainer = useRef<Group>(null);
const gunContainer = useRef<Group>(null);
const initialTankopediaState = useTankopediaPersistent.getState();
useEffect(() => {
if (!modelDefinitions) return;
const hullOrigin = new Vector3(
trackModelDefinition.origin[0],
trackModelDefinition.origin[1],
-trackModelDefinition.origin[2],
).applyAxisAngle(I_HAT, Math.PI / 2);
const turretOrigin = new Vector3(
tankModelDefinition.turretOrigin[0],
tankModelDefinition.turretOrigin[1],
-tankModelDefinition.turretOrigin[2],
).applyAxisAngle(I_HAT, Math.PI / 2);
const gunOrigin = new Vector3(
turretModelDefinition.gunOrigin[0],
turretModelDefinition.gunOrigin[1],
-turretModelDefinition.gunOrigin[2],
).applyAxisAngle(I_HAT, Math.PI / 2);
const turretPosition = new Vector3();
const turretRotation = new Euler();
const gunPosition = new Vector3();
const gunRotation = new Euler();
function handleModelTransform({ yaw, pitch }: ModelTransformEventData) {
gunPosition
.set(0, 0, 0)
.sub(hullOrigin)
.sub(turretOrigin)
.sub(gunOrigin)
.applyAxisAngle(I_HAT, pitch)
.add(gunOrigin)
.add(turretOrigin)
.add(hullOrigin);
gunRotation.set(pitch, 0, 0);
gunContainer.current?.position.copy(gunPosition);
gunContainer.current?.rotation.copy(gunRotation);
if (yaw === undefined) return;
turretPosition
.set(0, 0, 0)
.sub(hullOrigin)
.sub(turretOrigin)
.applyAxisAngle(new Vector3(0, 0, 1), yaw);
turretRotation.set(0, 0, yaw);
if (tankModelDefinition.turretRotation) {
const initialPitch = -degToRad(
tankModelDefinition.turretRotation.pitch,
);
const initialYaw = -degToRad(tankModelDefinition.turretRotation.yaw);
const initialRoll = -degToRad(
tankModelDefinition.turretRotation.roll,
);
turretPosition
.applyAxisAngle(I_HAT, initialPitch)
.applyAxisAngle(J_HAT, initialRoll)
.applyAxisAngle(K_HAT, initialYaw);
turretRotation.x += initialPitch;
turretRotation.y += initialRoll;
turretRotation.z += initialYaw;
}
turretPosition.add(turretOrigin).add(hullOrigin);
turretContainer.current?.position.copy(turretPosition);
turretContainer.current?.rotation.copy(turretRotation);
}
handleModelTransform(protagonist);
modelTransformEvent.on(handleModelTransform);
return () => {
modelTransformEvent.off(handleModelTransform);
};
});
useEffect(() => {
const unsubscribe = useTankopediaPersistent.subscribe(
(state) => state.mode,
(mode) => {
if (wrapper.current) wrapper.current.visible = mode === 'armor';
},
);
return unsubscribe;
});
const armorGltf = useArmor(protagonist.tank.id);
const { gltf: modelGltf } = useModel(protagonist.tank.id);
const armorNodes = Object.values(armorGltf.nodes);
const modelNodes = Object.values(modelGltf.nodes);
const tankModelDefinition = modelDefinitions[protagonist.tank.id];
const trackModelDefinition =
tankModelDefinition.tracks[protagonist.track.id];
const turretModelDefinition =
tankModelDefinition.turrets[protagonist.turret.id];
const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id];
const maxThickness = Math.max(
trackModelDefinition.thickness,
gunModelDefinition.thickness,
...armorNodes
.map((node) => {
const armorId = nameToArmorId(node.name);
return resolveArmor(tankModelDefinition.armor, armorId).thickness;
})
.filter(Boolean),
);
const hullOrigin = new Vector3(
trackModelDefinition.origin[0],
trackModelDefinition.origin[1],
-trackModelDefinition.origin[2],
).applyAxisAngle(I_HAT, Math.PI / 2);
const turretOrigin = new Vector3(
tankModelDefinition.turretOrigin[0],
tankModelDefinition.turretOrigin[1],
-tankModelDefinition.turretOrigin[2],
).applyAxisAngle(I_HAT, Math.PI / 2);
const gunOrigin = new Vector3(
turretModelDefinition.gunOrigin[0],
turretModelDefinition.gunOrigin[1],
-turretModelDefinition.gunOrigin[2],
).applyAxisAngle(I_HAT, Math.PI / 2);
return (
<group
ref={wrapper}
rotation={[-Math.PI / 2, 0, 0]}
visible={initialTankopediaState.mode === 'armor'}
>
<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 (
<ArmorMeshSpacedArmorDepth
ornamental={ornamental}
include={spaced}
isExternalModule={false}
thickness={thickness}
maxThickness={maxThickness}
key={node.uuid}
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 (
<ArmorMeshSpacedArmorDepth
ornamental={ornamental}
isExternalModule
key={node.uuid}
node={node}
/>
);
})}
<group ref={turretContainer}>
<group position={hullOrigin}>
{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}>
<ArmorMeshSpacedArmorDepth
ornamental={ornamental}
include={spaced}
isExternalModule={false}
thickness={thickness}
maxThickness={maxThickness}
node={node}
/>
</group>
);
})}
</group>
<group ref={gunContainer}>
<group position={hullOrigin}>
{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)}
>
<ArmorMeshSpacedArmorDepth
ornamental={ornamental}
include={spaced}
isExternalModule={false}
thickness={thickness}
maxThickness={maxThickness}
node={node}
/>
</group>
);
})}
</group>
{modelNodes.map((node) => {
const isCurrentGun =
node.name ===
`gun_${gunModelDefinition.model.toString().padStart(2, '0')}`;
const isVisible = isCurrentGun;
if (!isVisible) return null;
return (
<ArmorMeshSpacedArmorDepth
ornamental={ornamental}
isExternalModule
key={node.uuid}
node={node}
/>
);
})}
</group>
</group>
</group>
);
},
);
@@ -1,67 +0,0 @@
import { createPortal, useFrame } from '@react-three/fiber';
import { memo } from 'react';
import { DepthTexture, Scene, Vector2 } from 'three';
import { externalModuleMaskRenderTarget } from '../../../../../../../../components/ArmorMesh';
import { spacedArmorDepthRenderTarget } from '../../../../../../../../components/ArmorMesh/components/SpacedArmorDepth';
import { ArmorHighlighting } from './components/ArmorHighlighting';
import { ExternalModuleMask } from './components/ExternalModuleMask';
import { SpacedArmorDepth } from './components/SpacedArmorDepth';
export const TankArmor = memo(() => {
const externalModuleMaskScene = new Scene();
const spacedArmorDepthScene = new Scene();
const armorHighlightingScene = new Scene();
const externalModuleMaskPortal = createPortal(
<ExternalModuleMask />,
externalModuleMaskScene,
);
const spacedArmorDepthPortal = createPortal(
<SpacedArmorDepth />,
spacedArmorDepthScene,
);
const armorHighlightingPortal = createPortal(
<ArmorHighlighting />,
armorHighlightingScene,
);
const renderSize = new Vector2();
const newRenderSize = new Vector2();
useFrame(({ gl, scene, camera }) => {
const pixelRatio = gl.getPixelRatio();
gl.autoClear = true;
gl.getSize(newRenderSize).multiplyScalar(pixelRatio);
if (!newRenderSize.equals(renderSize)) {
renderSize.copy(newRenderSize);
spacedArmorDepthRenderTarget.depthTexture = new DepthTexture(
renderSize.x,
renderSize.y,
);
}
externalModuleMaskRenderTarget.setSize(renderSize.x, renderSize.y);
gl.setRenderTarget(externalModuleMaskRenderTarget);
gl.render(externalModuleMaskScene, camera);
gl.clearDepth();
spacedArmorDepthRenderTarget.setSize(renderSize.x, renderSize.y * 0.5);
gl.setRenderTarget(spacedArmorDepthRenderTarget);
gl.render(spacedArmorDepthScene, camera);
gl.autoClear = false;
gl.setRenderTarget(null);
gl.render(scene, camera);
gl.clearDepth();
gl.render(armorHighlightingScene, camera);
}, 1);
return (
<>
{externalModuleMaskPortal}
{spacedArmorDepthPortal}
{armorHighlightingPortal}
</>
);
});
+2
View File
@@ -63,6 +63,8 @@ export default function Page({ params }: { params: { id: string } }) {
};
}, [initialId, assigned]);
return (
<PageWrapper color="purple" size="double">
{assigned && (