mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-25 03:29:51 +01:00
feat: start of new engine
This commit is contained in:
1 parent
bdb3844bcc
commit
b3a6a47a6a
7 files changed
+82
-627
No files matched your search
@@ -4,7 +4,6 @@ import { PerspectiveCamera } from '@react-three/drei';
|
|||||||
import { Canvas } from '@react-three/fiber';
|
import { Canvas } from '@react-three/fiber';
|
||||||
import { Suspense, use, useEffect, useRef, useState } from 'react';
|
import { Suspense, use, useEffect, useRef, useState } from 'react';
|
||||||
import { isMobile } from 'react-device-detect';
|
import { isMobile } from 'react-device-detect';
|
||||||
import { ShotDisplay } from '../../../../../../components/ShotDisplay';
|
|
||||||
import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits';
|
import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits';
|
||||||
import { modelDefinitions } from '../../../../../../core/blitzkrieg/modelDefinitions';
|
import { modelDefinitions } from '../../../../../../core/blitzkrieg/modelDefinitions';
|
||||||
import { modelTransformEvent } from '../../../../../../core/blitzkrieg/modelTransform';
|
import { modelTransformEvent } from '../../../../../../core/blitzkrieg/modelTransform';
|
||||||
@@ -19,15 +18,10 @@ import mutateTankopediaPersistent, {
|
|||||||
} from '../../../../../../stores/tankopedia';
|
} from '../../../../../../stores/tankopedia';
|
||||||
import { AntagonistBar } from '../AntagonistBar';
|
import { AntagonistBar } from '../AntagonistBar';
|
||||||
import { Controls } from '../Control';
|
import { Controls } from '../Control';
|
||||||
import { Lighting } from '../Lighting';
|
|
||||||
import { RotationInputs } from '../QuickInputs';
|
import { RotationInputs } from '../QuickInputs';
|
||||||
import { SceneProps } from '../SceneProps';
|
|
||||||
import { ModelLoader } from './components/ModelLoader';
|
import { ModelLoader } from './components/ModelLoader';
|
||||||
import { Options } from './components/Options';
|
import { Options } from './components/Options';
|
||||||
import { TankArmor } from './components/TankArmor';
|
import { SpacedArmorScene } from './components/TankArmor/components/Spaced';
|
||||||
import { ExternalModuleMask } from './components/TankArmor/components/ExternalModuleMask';
|
|
||||||
import { SpacedArmorDepth } from './components/TankArmor/components/SpacedArmorDepth';
|
|
||||||
import { TankModel } from './components/TankModel';
|
|
||||||
|
|
||||||
export function TankSandbox() {
|
export function TankSandbox() {
|
||||||
const canvas = useRef<HTMLCanvasElement>(null);
|
const canvas = useRef<HTMLCanvasElement>(null);
|
||||||
@@ -195,15 +189,13 @@ export function TankSandbox() {
|
|||||||
>
|
>
|
||||||
<PerspectiveCamera makeDefault fov={25} far={32} />
|
<PerspectiveCamera makeDefault fov={25} far={32} />
|
||||||
<Controls />
|
<Controls />
|
||||||
<SceneProps />
|
{/* <SceneProps /> */}
|
||||||
|
|
||||||
<Suspense fallback={<ModelLoader />}>
|
<Suspense fallback={<ModelLoader />}>
|
||||||
<ShotDisplay />
|
{/* <ShotDisplay />
|
||||||
<Lighting />
|
<Lighting />
|
||||||
<TankModel />
|
<TankModel /> */}
|
||||||
<TankArmor />
|
<SpacedArmorScene />
|
||||||
<ExternalModuleMask ornamental />
|
|
||||||
<SpacedArmorDepth ornamental />
|
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</Canvas>
|
</Canvas>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
-213
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
-286
@@ -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>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
+41
-47
@@ -1,8 +1,9 @@
|
|||||||
import { memo, useEffect, useRef } from 'react';
|
import { memo, useEffect, useRef } from 'react';
|
||||||
import { Euler, Group, Vector3 } from 'three';
|
import { Euler, Group, Vector3 } from 'three';
|
||||||
import { degToRad } from 'three/src/math/MathUtils';
|
import { degToRad } from 'three/src/math/MathUtils';
|
||||||
import { ArmorMeshSpacedArmorDepth } from '../../../../../../../../../components/ArmorMesh/components/SpacedArmorDepth';
|
import { SpacedArmor } from '../../../../../../../../../components/Armor/components/SpacedArmor';
|
||||||
import { I_HAT, J_HAT, K_HAT } from '../../../../../../../../../constants/axis';
|
import { I_HAT, J_HAT, K_HAT } from '../../../../../../../../../constants/axis';
|
||||||
|
import { resolveNearPenetration } from '../../../../../../../../../core/blitz/resolveNearPenetration';
|
||||||
import {
|
import {
|
||||||
ModelTransformEventData,
|
ModelTransformEventData,
|
||||||
modelTransformEvent,
|
modelTransformEvent,
|
||||||
@@ -15,22 +16,17 @@ import { useModelDefinitions } from '../../../../../../../../../hooks/useModelDe
|
|||||||
import { useDuel } from '../../../../../../../../../stores/duel';
|
import { useDuel } from '../../../../../../../../../stores/duel';
|
||||||
import { useTankopediaPersistent } from '../../../../../../../../../stores/tankopedia';
|
import { useTankopediaPersistent } from '../../../../../../../../../stores/tankopedia';
|
||||||
|
|
||||||
interface SpacedArmorDepthProps {
|
export enum ArmorType {
|
||||||
|
Core,
|
||||||
|
Spaced,
|
||||||
|
External,
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SpacedArmorSceneProps {
|
||||||
ornamental?: boolean;
|
ornamental?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export const SpacedArmorScene = memo<SpacedArmorSceneProps>(
|
||||||
* 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 }) => {
|
({ ornamental = false }) => {
|
||||||
const protagonist = useDuel((state) => state.protagonist!);
|
const protagonist = useDuel((state) => state.protagonist!);
|
||||||
const wrapper = useRef<Group>(null);
|
const wrapper = useRef<Group>(null);
|
||||||
@@ -138,16 +134,6 @@ export const SpacedArmorDepth = memo<SpacedArmorDepthProps>(
|
|||||||
const turretModelDefinition =
|
const turretModelDefinition =
|
||||||
tankModelDefinition.turrets[protagonist.turret.id];
|
tankModelDefinition.turrets[protagonist.turret.id];
|
||||||
const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.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(
|
const hullOrigin = new Vector3(
|
||||||
trackModelDefinition.origin[0],
|
trackModelDefinition.origin[0],
|
||||||
trackModelDefinition.origin[1],
|
trackModelDefinition.origin[1],
|
||||||
@@ -164,6 +150,17 @@ export const SpacedArmorDepth = memo<SpacedArmorDepthProps>(
|
|||||||
-turretModelDefinition.gunOrigin[2],
|
-turretModelDefinition.gunOrigin[2],
|
||||||
).applyAxisAngle(I_HAT, Math.PI / 2);
|
).applyAxisAngle(I_HAT, Math.PI / 2);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
'test',
|
||||||
|
gunModelDefinition.thickness,
|
||||||
|
resolveNearPenetration(useDuel.getState().antagonist!.shell.penetration),
|
||||||
|
(gunModelDefinition.thickness /
|
||||||
|
resolveNearPenetration(
|
||||||
|
useDuel.getState().antagonist!.shell.penetration,
|
||||||
|
)) *
|
||||||
|
255,
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<group
|
<group
|
||||||
ref={wrapper}
|
ref={wrapper}
|
||||||
@@ -183,13 +180,11 @@ export const SpacedArmorDepth = memo<SpacedArmorDepthProps>(
|
|||||||
if (!isVisible || thickness === undefined) return null;
|
if (!isVisible || thickness === undefined) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ArmorMeshSpacedArmorDepth
|
<SpacedArmor
|
||||||
ornamental={ornamental}
|
|
||||||
include={spaced}
|
|
||||||
isExternalModule={false}
|
|
||||||
thickness={thickness}
|
|
||||||
maxThickness={maxThickness}
|
|
||||||
key={node.uuid}
|
key={node.uuid}
|
||||||
|
ornamental={ornamental}
|
||||||
|
type={spaced ? ArmorType.Spaced : ArmorType.Core}
|
||||||
|
thickness={thickness}
|
||||||
node={node}
|
node={node}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -203,10 +198,11 @@ export const SpacedArmorDepth = memo<SpacedArmorDepthProps>(
|
|||||||
if (!isVisible) return null;
|
if (!isVisible) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ArmorMeshSpacedArmorDepth
|
<SpacedArmor
|
||||||
ornamental={ornamental}
|
|
||||||
isExternalModule
|
|
||||||
key={node.uuid}
|
key={node.uuid}
|
||||||
|
ornamental={ornamental}
|
||||||
|
type={ArmorType.External}
|
||||||
|
thickness={trackModelDefinition.thickness}
|
||||||
node={node}
|
node={node}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -229,12 +225,11 @@ export const SpacedArmorDepth = memo<SpacedArmorDepthProps>(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<group key={node.uuid} position={turretOrigin}>
|
<group key={node.uuid} position={turretOrigin}>
|
||||||
<ArmorMeshSpacedArmorDepth
|
<SpacedArmor
|
||||||
|
key={node.uuid}
|
||||||
ornamental={ornamental}
|
ornamental={ornamental}
|
||||||
include={spaced}
|
type={spaced ? ArmorType.Spaced : ArmorType.Core}
|
||||||
isExternalModule={false}
|
|
||||||
thickness={thickness}
|
thickness={thickness}
|
||||||
maxThickness={maxThickness}
|
|
||||||
node={node}
|
node={node}
|
||||||
/>
|
/>
|
||||||
</group>
|
</group>
|
||||||
@@ -261,12 +256,10 @@ export const SpacedArmorDepth = memo<SpacedArmorDepthProps>(
|
|||||||
key={node.uuid}
|
key={node.uuid}
|
||||||
position={turretOrigin.clone().add(gunOrigin)}
|
position={turretOrigin.clone().add(gunOrigin)}
|
||||||
>
|
>
|
||||||
<ArmorMeshSpacedArmorDepth
|
<SpacedArmor
|
||||||
ornamental={ornamental}
|
ornamental={ornamental}
|
||||||
include={spaced}
|
type={spaced ? ArmorType.Spaced : ArmorType.Core}
|
||||||
isExternalModule={false}
|
|
||||||
thickness={thickness}
|
thickness={thickness}
|
||||||
maxThickness={maxThickness}
|
|
||||||
node={node}
|
node={node}
|
||||||
/>
|
/>
|
||||||
</group>
|
</group>
|
||||||
@@ -275,18 +268,19 @@ export const SpacedArmorDepth = memo<SpacedArmorDepthProps>(
|
|||||||
</group>
|
</group>
|
||||||
|
|
||||||
{modelNodes.map((node) => {
|
{modelNodes.map((node) => {
|
||||||
const isCurrentGun =
|
const isCurrentGun = node.name.startsWith(
|
||||||
node.name ===
|
`gun_${gunModelDefinition.model.toString().padStart(2, '0')}`,
|
||||||
`gun_${gunModelDefinition.model.toString().padStart(2, '0')}`;
|
);
|
||||||
const isVisible = isCurrentGun;
|
const isVisible = isCurrentGun;
|
||||||
|
|
||||||
if (!isVisible) return null;
|
if (!isVisible) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ArmorMeshSpacedArmorDepth
|
<SpacedArmor
|
||||||
ornamental={ornamental}
|
|
||||||
isExternalModule
|
|
||||||
key={node.uuid}
|
key={node.uuid}
|
||||||
|
ornamental={ornamental}
|
||||||
|
type={ArmorType.External}
|
||||||
|
thickness={gunModelDefinition.thickness}
|
||||||
node={node}
|
node={node}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -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}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { AdditiveBlending, Color, MeshBasicMaterial, Object3D } from 'three';
|
||||||
|
import { ArmorType } from '../../../app/tools/tankopedia/[id]/components/Model/components/TankArmor/components/Spaced';
|
||||||
|
import { resolveNearPenetration } from '../../../core/blitz/resolveNearPenetration';
|
||||||
|
import { jsxTree } from '../../../core/blitzkrieg/jsxTree';
|
||||||
|
import { useDuel } from '../../../stores/duel';
|
||||||
|
|
||||||
|
interface SpacedArmorProps {
|
||||||
|
node: Object3D;
|
||||||
|
ornamental?: boolean;
|
||||||
|
type: ArmorType;
|
||||||
|
thickness: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const omitColor = new Color(0, 0, 0);
|
||||||
|
|
||||||
|
export function SpacedArmor({ node, type, thickness }: SpacedArmorProps) {
|
||||||
|
const penetrationRaw = useDuel(
|
||||||
|
(state) => state.antagonist!.shell.penetration,
|
||||||
|
);
|
||||||
|
const penetration = resolveNearPenetration(penetrationRaw);
|
||||||
|
|
||||||
|
return jsxTree(node, {
|
||||||
|
material: new MeshBasicMaterial({
|
||||||
|
color:
|
||||||
|
type === ArmorType.Core
|
||||||
|
? omitColor
|
||||||
|
: type === ArmorType.Spaced
|
||||||
|
? new Color(0, 0, 0)
|
||||||
|
: new Color(thickness / penetration, 0, 0),
|
||||||
|
depthWrite: type === ArmorType.Core,
|
||||||
|
blending: type === ArmorType.Core ? undefined : AdditiveBlending,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import { create } from 'zustand';
|
|||||||
import { persist, subscribeWithSelector } from 'zustand/middleware';
|
import { persist, subscribeWithSelector } from 'zustand/middleware';
|
||||||
import { ENVIRONMENTS } from '../app/tools/tankopedia/[id]/components/Lighting';
|
import { ENVIRONMENTS } from '../app/tools/tankopedia/[id]/components/Lighting';
|
||||||
import { TankType, TreeType } from '../components/Tanks';
|
import { TankType, TreeType } from '../components/Tanks';
|
||||||
|
import isDev from '../core/blitzkrieg/isDev';
|
||||||
import { Tier } from '../core/blitzkrieg/tankDefinitions';
|
import { Tier } from '../core/blitzkrieg/tankDefinitions';
|
||||||
|
|
||||||
export type TankopediaSortBy = keyof typeof SORT_NAMES;
|
export type TankopediaSortBy = keyof typeof SORT_NAMES;
|
||||||
@@ -123,7 +124,7 @@ export const useTankopediaPersistent = create<TankopediaPersistent>()(
|
|||||||
test: 'include',
|
test: 'include',
|
||||||
page: 0,
|
page: 0,
|
||||||
},
|
},
|
||||||
mode: 'model',
|
mode: isDev() ? 'armor' : 'model',
|
||||||
})),
|
})),
|
||||||
{ name: 'tankopedia', merge },
|
{ name: 'tankopedia', merge },
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in new issue
Block a user