feat: some armor rendering

This commit is contained in:
tresabhi committed 2025-03-20 21:48:17 -05:00
1 parent 3a32624823
commit 5b16e22750
13 files changed
+662 -1060

No files matched your search

@@ -1,23 +1,10 @@
import { normalizeAngleRad } from '@blitzkit/core';
import { useThree, type ThreeEvent } from '@react-three/fiber';
import { memo, useRef } from 'react';
import { Group, Plane, Vector2, Vector3 } from 'three';
import { applyPitchYawLimits } from '../../../core/blitz/applyPitchYawLimits';
import { correctZYTuple } from '../../../core/blitz/correctZYTuple';
import { hasEquipment } from '../../../core/blitzkit/hasEquipment';
import { modelTransformEvent } from '../../../core/blitzkit/modelTransform';
import { nameToArmorId } from '../../../core/blitzkit/nameToArmorId';
import { resolveArmor } from '../../../core/blitzkit/resolveThickness';
import { useArmor } from '../../../hooks/useArmor';
import { useModel } from '../../../hooks/useModel';
import { useTankModelDefinition } from '../../../hooks/useTankModelDefinition';
import { useTankTransform } from '../../../hooks/useTankTransform';
import { asset } from '@blitzkit/core';
import { useGLTF } from '@react-three/drei';
import { memo } from 'react';
import { Color, Material, MeshBasicMaterial } from 'three';
import { jsxTree } from '../../../core/blitzkit/jsxTree';
import { Duel } from '../../../stores/duel';
import { TankopediaEphemeral } from '../../../stores/tankopediaEphemeral';
import { TankopediaPersistent } from '../../../stores/tankopediaPersistent';
import { ModelTankWrapper } from './ModelTankWrapper';
import { ArmorType } from './SpacedArmorScene';
import { StaticArmorSceneComponent } from './StaticArmorSceneComponent';
interface ArmorSceneProps {
thicknessRange: ThicknessRange;
@@ -28,401 +15,87 @@ export interface ThicknessRange {
}
export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
const wrapper = useRef<Group>(null);
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 { gltf: armorGltf } = useArmor(tank.id);
const { gltf: modelGltf } = useModel(tank.id);
const armorNodes = Object.values(armorGltf.nodes);
const modelNodes = Object.values(modelGltf.nodes);
const tankModelDefinition = useTankModelDefinition();
const trackModelDefinition = tankModelDefinition.tracks[track.id];
const turretModelDefinition = tankModelDefinition.turrets[turret.id];
const gunModelDefinition =
turretModelDefinition.guns[gun.gun_type!.value.base.id];
const hullOrigin = correctZYTuple(trackModelDefinition.origin);
const turretOrigin = correctZYTuple(tankModelDefinition.turret_origin);
const gunOrigin = correctZYTuple(turretModelDefinition.gun_origin);
const mutateDuel = Duel.useMutation();
const canvas = useThree((state) => state.gl.domElement);
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
const duelStore = Duel.useStore();
const maskOrigin =
gunModelDefinition.mask === undefined
? undefined
: gunModelDefinition.mask + hullOrigin.y + turretOrigin.y + gunOrigin.y;
const showPrimaryArmor = TankopediaPersistent.use(
(state) => state.showPrimaryArmor,
);
const showSpacedArmor = TankopediaPersistent.use(
(state) => state.showSpacedArmor,
);
const showExternalModules = TankopediaPersistent.use(
(state) => state.showExternalModules,
);
const isDynamicArmorActive = Duel.use((state) =>
state.protagonist.consumables.includes(73),
);
// const wrapper = useRef<Group>(null);
// 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 { gltf: armorGltf } = useArmor(tank.id);
// const { gltf: modelGltf } = useModel(tank.id);
// const armorNodes = Object.values(armorGltf.nodes);
// const modelNodes = Object.values(modelGltf.nodes);
// const tankModelDefinition = useTankModelDefinition();
// const trackModelDefinition = tankModelDefinition.tracks[track.id];
// const turretModelDefinition = tankModelDefinition.turrets[turret.id];
// const gunModelDefinition =
// turretModelDefinition.guns[gun.gun_type!.value.base.id];
// const hullOrigin = correctZYTuple(trackModelDefinition.origin);
// const turretOrigin = correctZYTuple(tankModelDefinition.turret_origin);
// const gunOrigin = correctZYTuple(turretModelDefinition.gun_origin);
// const mutateDuel = Duel.useMutation();
// const canvas = useThree((state) => state.gl.domElement);
// const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
// const duelStore = Duel.useStore();
// const maskOrigin =
// gunModelDefinition.mask === undefined
// ? undefined
// : gunModelDefinition.mask + hullOrigin.y + turretOrigin.y + gunOrigin.y;
// const showPrimaryArmor = TankopediaPersistent.use(
// (state) => state.showPrimaryArmor,
// );
// const showSpacedArmor = TankopediaPersistent.use(
// (state) => state.showSpacedArmor,
// );
// const showExternalModules = TankopediaPersistent.use(
// (state) => state.showExternalModules,
// );
// const isDynamicArmorActive = Duel.use((state) =>
// state.protagonist.consumables.includes(73),
// );
useTankTransform(track, turret, turretContainer, gunContainer);
// useTankTransform(track, turret, turretContainer, gunContainer);
const tank = Duel.use((state) => state.protagonist.tank);
const armor = TankopediaEphemeral.use((state) => state.armor);
const hullArmor = armor.groups.hull;
const hullGltf = useGLTF(asset(`tanks/${tank.id}/collision/hull.glb`));
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,
);
<group>
{jsxTree(hullGltf.scene, {
mesh(mesh) {
if (!(mesh.material instanceof Material)) {
return { visible: false };
}
if (
!isVisible ||
thickness === undefined ||
(!showPrimaryArmor && !spaced) ||
(!showSpacedArmor && spaced) ||
(isDynamicArmorActive && node.name.includes('state_00')) ||
(!isDynamicArmorActive && node.name.includes('state_01'))
)
return null;
const armorName = mesh.material.name;
const material = new MeshBasicMaterial({});
const armorPlate = hullArmor.armors[armorName];
return (
<StaticArmorSceneComponent
name={node.name}
thicknessRange={thicknessRange}
key={node.uuid}
type={spaced ? ArmorType.Spaced : ArmorType.Primary}
thickness={thickness}
node={node}
onPointerDown={(event) => event.stopPropagation()}
/>
);
})}
</group>
switch (armorPlate.type) {
case 'Armor':
material.color = new Color('#ec6142');
material.transparent = true;
material.opacity = 0.5;
break;
{modelNodes.map((node) => {
const isWheel = node.name.startsWith('chassis_wheel_');
const isTrack = node.name.startsWith('chassis_track_');
const isVisible = isWheel || isTrack;
case 'ArmorScreen':
material.color = new Color('#ff8dcc');
material.transparent = true;
material.opacity = 0.5;
break;
if (!isVisible || !showExternalModules) return null;
default:
material.color = new Color('#70b8ff');
break;
}
return (
<StaticArmorSceneComponent
name={node.name}
thicknessRange={thicknessRange}
key={node.uuid}
type={ArmorType.External}
thickness={trackModelDefinition.thickness}
variant="track"
node={node}
onPointerDown={(event) => event.stopPropagation()}
/>
);
return { material };
},
})}
<group ref={turretContainer}>
{armorNodes.map((node) => {
const isCurrentTurret = node.name.startsWith(
`turret_${turretModelDefinition.model_id.toString().padStart(2, '0')}`,
);
const isVisible = isCurrentTurret;
const armorId = nameToArmorId(node.name);
const { spaced, thickness } = resolveArmor(
turretModelDefinition.armor,
armorId,
);
if (
!isVisible ||
thickness === undefined ||
(!showPrimaryArmor && !spaced) ||
(!showSpacedArmor && spaced) ||
(isDynamicArmorActive && node.name.includes('state_00')) ||
(!isDynamicArmorActive && node.name.includes('state_01'))
) {
return null;
}
const position = new Vector2();
const delta = new Vector2();
let pitch = 0;
let yaw = 0;
function onPointerDown(event: ThreeEvent<PointerEvent>) {
event.stopPropagation();
position.set(event.clientX, event.clientY);
yaw = protagonist.yaw;
pitch = protagonist.pitch;
mutateTankopediaEphemeral((draft) => {
draft.controlsEnabled = false;
});
mutateTankopediaEphemeral((draft) => {
draft.shot = undefined;
draft.highlightArmor = undefined;
});
window.addEventListener('pointermove', handlePointerMove);
window.addEventListener('pointerup', handlePointerUp);
}
async function handlePointerMove(event: PointerEvent) {
const duel = duelStore.getState();
const hasImprovedVerticalStabilizer = await hasEquipment(
122,
duel.protagonist.tank.equipment_preset,
duel.protagonist.equipmentMatrix,
);
const hasDownImprovedVerticalStabilizer = await hasEquipment(
124,
duel.protagonist.tank.equipment_preset,
duel.protagonist.equipmentMatrix,
);
const boundingRect = canvas.getBoundingClientRect();
delta.set(event.clientX, event.clientY).sub(position);
position.set(event.clientX, event.clientY);
[pitch, yaw] = applyPitchYawLimits(
pitch,
yaw + delta.x * (Math.PI / boundingRect.width),
gunModelDefinition.pitch,
turretModelDefinition.yaw,
hasImprovedVerticalStabilizer,
hasDownImprovedVerticalStabilizer,
);
modelTransformEvent.emit({ pitch, yaw });
}
function handlePointerUp() {
mutateDuel((draft) => {
draft.protagonist.pitch = normalizeAngleRad(pitch);
draft.protagonist.yaw = normalizeAngleRad(yaw);
});
mutateTankopediaEphemeral((draft) => {
draft.controlsEnabled = true;
});
window.removeEventListener('pointermove', handlePointerMove);
window.removeEventListener('pointerup', handlePointerUp);
}
return (
<group key={node.uuid} position={hullOrigin}>
<group position={turretOrigin}>
<StaticArmorSceneComponent
name={node.name}
thicknessRange={thicknessRange}
key={node.uuid}
type={spaced ? ArmorType.Spaced : ArmorType.Primary}
thickness={thickness}
node={node}
onPointerDown={onPointerDown}
/>
</group>
</group>
);
})}
<group ref={gunContainer}>
{armorNodes.map((node) => {
const isCurrentGun = node.name.startsWith(
`gun_${gunModelDefinition.model_id.toString().padStart(2, '0')}`,
);
const isVisible = isCurrentGun;
const armorId = nameToArmorId(node.name);
const { spaced, thickness } = resolveArmor(
gunModelDefinition.armor,
armorId,
);
if (
!isVisible ||
thickness === undefined ||
(!showPrimaryArmor && !spaced) ||
(!showSpacedArmor && spaced) ||
(isDynamicArmorActive && node.name.includes('state_00')) ||
(!isDynamicArmorActive && node.name.includes('state_01'))
)
return null;
const position = new Vector2();
const delta = new Vector2();
let pitch = 0;
let yaw = 0;
function onPointerDown(event: ThreeEvent<PointerEvent>) {
event.stopPropagation();
mutateTankopediaEphemeral((draft) => {
draft.controlsEnabled = false;
});
mutateTankopediaEphemeral((draft) => {
draft.shot = undefined;
draft.highlightArmor = undefined;
});
position.set(event.clientX, event.clientY);
pitch = protagonist.pitch;
yaw = protagonist.yaw;
window.addEventListener('pointermove', handlePointerMove);
window.addEventListener('pointerup', handlePointerUp);
}
async function handlePointerMove(event: PointerEvent) {
const duel = duelStore.getState();
const hasImprovedVerticalStabilizer = await hasEquipment(
122,
duel.protagonist.tank.equipment_preset,
duel.protagonist.equipmentMatrix,
);
const hasDownImprovedVerticalStabilizer = await hasEquipment(
124,
duel.protagonist.tank.equipment_preset,
duel.protagonist.equipmentMatrix,
);
const boundingRect = canvas.getBoundingClientRect();
delta.set(event.clientX, event.clientY).sub(position);
position.set(event.clientX, event.clientY);
[pitch, yaw] = applyPitchYawLimits(
pitch - delta.y * (Math.PI / boundingRect.height),
yaw + delta.x * (Math.PI / boundingRect.width),
gunModelDefinition.pitch,
turretModelDefinition.yaw,
hasImprovedVerticalStabilizer,
hasDownImprovedVerticalStabilizer,
);
modelTransformEvent.emit({ pitch, yaw });
}
function handlePointerUp() {
mutateTankopediaEphemeral((draft) => {
draft.controlsEnabled = true;
});
mutateDuel((draft) => {
draft.protagonist.pitch = normalizeAngleRad(pitch);
draft.protagonist.yaw = normalizeAngleRad(yaw);
});
window.removeEventListener('pointermove', handlePointerMove);
window.removeEventListener('pointerup', handlePointerUp);
}
return (
<group key={node.uuid} position={hullOrigin}>
<group position={turretOrigin.clone().add(gunOrigin)}>
<StaticArmorSceneComponent
name={node.name}
thicknessRange={thicknessRange}
type={spaced ? ArmorType.Spaced : ArmorType.Primary}
thickness={thickness}
node={node}
onPointerDown={onPointerDown}
/>
</group>
</group>
);
})}
{modelNodes.map((node) => {
const gunString = `gun_${gunModelDefinition.model_id
.toString()
.padStart(2, '0')}`;
const isCurrentGun = gunModelDefinition.mask
? node.name.startsWith(gunString)
: node.name === gunString;
const isVisible = isCurrentGun;
if (!isVisible || !showExternalModules) return null;
const position = new Vector2();
const delta = new Vector2();
let pitch = 0;
let yaw = 0;
function onPointerDown(event: ThreeEvent<PointerEvent>) {
event.stopPropagation();
mutateTankopediaEphemeral((draft) => {
draft.controlsEnabled = false;
});
mutateTankopediaEphemeral((draft) => {
draft.shot = undefined;
draft.highlightArmor = undefined;
});
position.set(event.clientX, event.clientY);
pitch = protagonist.pitch;
yaw = protagonist.yaw;
window.addEventListener('pointermove', handlePointerMove);
window.addEventListener('pointerup', handlePointerUp);
}
async function handlePointerMove(event: PointerEvent) {
const duel = duelStore.getState();
const hasImprovedVerticalStabilizer = await hasEquipment(
122,
duel.protagonist.tank.equipment_preset,
duel.protagonist.equipmentMatrix,
);
const hasDownImprovedVerticalStabilizer = await hasEquipment(
124,
duel.protagonist.tank.equipment_preset,
duel.protagonist.equipmentMatrix,
);
const boundingRect = canvas.getBoundingClientRect();
delta.set(event.clientX, event.clientY).sub(position);
position.set(event.clientX, event.clientY);
[pitch, yaw] = applyPitchYawLimits(
pitch - delta.y * (Math.PI / boundingRect.height),
yaw + delta.x * (Math.PI / boundingRect.width),
gunModelDefinition.pitch,
turretModelDefinition.yaw,
hasImprovedVerticalStabilizer,
hasDownImprovedVerticalStabilizer,
);
modelTransformEvent.emit({ pitch, yaw });
}
function handlePointerUp() {
mutateTankopediaEphemeral((draft) => {
draft.controlsEnabled = true;
});
mutateDuel((draft) => {
draft.protagonist.pitch = normalizeAngleRad(pitch);
draft.protagonist.yaw = normalizeAngleRad(yaw);
});
window.removeEventListener('pointermove', handlePointerMove);
window.removeEventListener('pointerup', handlePointerUp);
}
return (
<StaticArmorSceneComponent
name={node.name}
thicknessRange={thicknessRange}
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)
}
hullOrigin={hullOrigin}
turretOrigin={turretOrigin}
gunOrigin={gunOrigin}
onPointerDown={onPointerDown}
/>
);
})}
</group>
</group>
</ModelTankWrapper>
</group>
);
});
@@ -1,7 +1,7 @@
import { asset } from '@blitzkit/core';
interface SmallTankIconProps {
id: number;
id: string;
size?: number;
}
@@ -19,7 +19,7 @@ export function SmallTankIcon({ id, size = HEIGHT }: SmallTankIconProps) {
>
<img
alt={`Tank ${id}`}
src={asset(`icons/tanks/small/${id}.webp`)}
src={asset(`api/tanks/${id}/icons/small.png`)}
style={{
transform: `scale(${(size / HEIGHT) * 100}%)`,
transformOrigin: 'top left',
@@ -3,6 +3,7 @@ import {
normalizeBoundingBox,
resolveDpm,
SEARCH_KEYS,
Tank,
unionBoundingBox,
type TankDefinition,
} from '@blitzkit/core';
@@ -35,8 +36,8 @@ import { MAX_RECENTLY_VIEWED } from './constants';
type TankSearchProps = Omit<FlexProps, 'onSelect'> & {
compact?: boolean;
onSelect?: (tank: TankDefinition) => void;
onSelectAll?: (tanks: TankDefinition[]) => void;
onSelect?: (tank: Tank) => void;
onSelectAll?: (tanks: Tank[]) => void;
};
const PREVIEW_COUNT = 20;
@@ -18,28 +18,28 @@ export function QuickInputs() {
const yawInput = useRef<HTMLInputElement>(null);
const pitchInput = useRef<HTMLInputElement>(null);
const tankModelDefinition = useTankModelDefinition();
const turretModelDefinition =
tankModelDefinition.turrets[protagonist.turret.id];
const gunModelDefinition =
turretModelDefinition.guns[protagonist.gun.gun_type!.value.base.id];
// const turretModelDefinition =
// tankModelDefinition.turrets[protagonist.turret.id];
// const gunModelDefinition =
// turretModelDefinition.guns[protagonist.gun.gun_type!.value.base.id];
const initialGunPitch =
tankModelDefinition.initial_turret_rotation?.pitch ?? 0;
const hasImprovedVerticalStabilizer = useEquipment(122);
const hasDownImprovedVerticalStabilizer = useEquipment(124);
useEffect(() => {
if (!yawInput.current) return;
// useEffect(() => {
// if (!yawInput.current) return;
yawInput.current.value = radToDeg(protagonist.yaw).toFixed(1);
}, [protagonist.yaw]);
// yawInput.current.value = radToDeg(protagonist.yaw).toFixed(1);
// }, [protagonist.yaw]);
useEffect(() => {
if (!pitchInput.current) return;
// useEffect(() => {
// if (!pitchInput.current) return;
pitchInput.current.value = (
-radToDeg(protagonist.pitch) + initialGunPitch
).toFixed(1);
}, [protagonist.pitch]);
// pitchInput.current.value = (
// -radToDeg(protagonist.pitch) + initialGunPitch
// ).toFixed(1);
// }, [protagonist.pitch]);
useEffect(() => {
function handleTransformEvent({ pitch, yaw }: ModelTransformEventData) {
@@ -1,12 +1,10 @@
import { literals } from '@blitzkit/i18n/src/literals';
import { EyeClosedIcon, EyeOpenIcon, ReloadIcon } from '@radix-ui/react-icons';
import { Button, Checkbox, Flex, Text } from '@radix-ui/themes';
import { Suspense } from 'react';
import { useLocale } from '../../../../../../hooks/useLocale';
import { TankopediaEphemeral } from '../../../../../../stores/tankopediaEphemeral';
import { TankopediaPersistent } from '../../../../../../stores/tankopediaPersistent';
import type { ThicknessRange } from '../../../../../Armor/components/StaticArmor';
import { DynamicArmorSwitcher } from './DynamicArmorSwitcher';
interface ThicknessesProps {
thicknessRange: ThicknessRange;
@@ -190,9 +188,9 @@ export function Thicknesses({ thicknessRange }: ThicknessesProps) {
</Button>
</Flex>
<Suspense>
{/* <Suspense>
<DynamicArmorSwitcher />
</Suspense>
</Suspense> */}
</Flex>
);
}
@@ -1,37 +1,6 @@
import {
asset,
imgur,
resolvePenetrationCoefficient,
TIER_ROMAN_NUMERALS,
uniqueGuns,
} from '@blitzkit/core';
import { literals } from '@blitzkit/i18n/src/literals';
import {
CameraIcon,
CopyIcon,
DownloadIcon,
EnterFullScreenIcon,
ExitFullScreenIcon,
EyeOpenIcon,
GearIcon,
} from '@radix-ui/react-icons';
import {
Box,
Button,
Checkbox,
Dialog,
DropdownMenu,
Flex,
IconButton,
Popover,
SegmentedControl,
Tabs,
Text,
Tooltip,
} from '@radix-ui/themes';
import { Suspense, useState, type RefObject } from 'react';
import { Pose, poseEvent } from '../../../../../core/blitzkit/pose';
import { useEquipment } from '../../../../../hooks/useEquipment';
import { imgur } from '@blitzkit/core';
import { Box, Flex, SegmentedControl, Tooltip } from '@radix-ui/themes';
import { useState, type RefObject } from 'react';
import { useFullScreen } from '../../../../../hooks/useFullScreen';
import { useFullscreenAvailability } from '../../../../../hooks/useFullscreenAvailability';
import { useLocale } from '../../../../../hooks/useLocale';
@@ -44,12 +13,6 @@ import {
import { TankopediaPersistent } from '../../../../../stores/tankopediaPersistent';
import { TankopediaDisplay } from '../../../../../stores/tankopediaPersistent/constants';
import type { ThicknessRange } from '../../../../Armor/components/StaticArmor';
import { ModuleButton } from '../../../../ModuleButtons/ModuleButton';
import { SmallTankIcon } from '../../../../SmallTankIcon';
import { TankSearch } from '../../../../TankSearch';
import { CustomShellButton } from './components/CustomShellButton';
import { DynamicArmorSwitcher } from './components/DynamicArmorSwitcher';
import { QuickInputs } from './components/QuickInputs';
import { Thicknesses } from './components/Thicknesses';
interface OptionsProps {
@@ -78,22 +41,22 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
const fullScreenAvailable = useFullscreenAvailability();
const developerMode = App.use((state) => state.developerMode);
const protagonistTank = Duel.use((state) => state.protagonist.tank);
const antagonistGun = Duel.use((state) => state.antagonist.gun);
const antagonistShell = Duel.use((state) => state.antagonist.shell);
// const antagonistGun = Duel.use((state) => state.antagonist.gun);
// const antagonistShell = Duel.use((state) => state.antagonist.shell);
const [antagonistSelectorOpen, setAntagonistSelectorOpen] = useState(false);
const antagonistTank = Duel.use((state) => state.antagonist.tank);
const hasCalibratedShells = useEquipment(103, true);
// const hasCalibratedShells = useEquipment(103, true);
const [tab, setTab] = useState('search');
const mutateDuel = Duel.useMutation();
const hasEnhancedArmor = useEquipment(110);
const antagonistUniqueGuns = uniqueGuns(antagonistTank.turrets);
// const hasEnhancedArmor = useEquipment(110);
// const antagonistUniqueGuns = uniqueGuns(antagonistTank.turrets);
const mutateTankopediaPersistent = TankopediaPersistent.useMutation();
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
const { strings, unwrap } = useLocale();
return (
<>
<QuickInputs />
{/* <QuickInputs /> */}
{display === TankopediaDisplay.ShootingRange && (
<Box
@@ -116,7 +79,7 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
<Thicknesses thicknessRange={thicknessRange} />
)}
{display === TankopediaDisplay.DynamicArmor && (
{/* {display === TankopediaDisplay.DynamicArmor && (
<Flex
gap="2"
direction="column"
@@ -137,6 +100,7 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
])}
</Text>
)}
{antagonistUniqueGuns.length > 1 && (
<Popover.Root>
<Popover.Trigger>
@@ -215,6 +179,8 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
</Popover.Content>
</Popover.Root>
)}
<Flex
direction="column"
style={{
@@ -321,7 +287,7 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
<DynamicArmorSwitcher />
</Suspense>
</Flex>
)}
)} */}
<Flex
direction="column"
@@ -331,7 +297,7 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
left="50%"
style={{ transform: 'translateX(-50%)' }}
>
{display === TankopediaDisplay.DynamicArmor && (
{/* {display === TankopediaDisplay.DynamicArmor && (
<>
<Flex
align="center"
@@ -380,13 +346,13 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
onSelect={(tank) => {
mutateDuel((draft) => {
draft.antagonist.tank = tank;
draft.antagonist.engine = tank.engines.at(-1)!;
draft.antagonist.track = tank.tracks.at(-1)!;
draft.antagonist.turret = tank.turrets.at(-1)!;
draft.antagonist.gun =
draft.antagonist.turret.guns.at(-1)!;
draft.antagonist.shell =
draft.antagonist.gun.gun_type!.value.base.shells[0];
// draft.antagonist.engine = tank.engines.at(-1)!;
// draft.antagonist.track = tank.tracks.at(-1)!;
// draft.antagonist.turret = tank.turrets.at(-1)!;
// draft.antagonist.gun =
// draft.antagonist.turret.guns.at(-1)!;
// draft.antagonist.shell =
// draft.antagonist.gun.gun_type!.value.base.shells[0];
});
setAntagonistSelectorOpen(false);
}}
@@ -396,7 +362,8 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
</Dialog.Root>
</Flex>
</>
)}
)} */}
<Flex gap="3" align="center" mt="2">
<SegmentedControl.Root
value={`${display}`}
@@ -441,7 +408,7 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
</SegmentedControl.Item> */}
</SegmentedControl.Root>
<DropdownMenu.Root>
{/* <DropdownMenu.Root>
<DropdownMenu.Trigger>
<IconButton variant="ghost" color="gray">
<EyeOpenIcon />
@@ -461,9 +428,9 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
{strings.website.tools.tanks.sandbox.poses.default}
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
</DropdownMenu.Root> */}
<DropdownMenu.Root>
{/* <DropdownMenu.Root>
<DropdownMenu.Trigger>
<IconButton variant="ghost" color="gray">
<GearIcon />
@@ -549,9 +516,9 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
{strings.website.tools.tanks.sandbox.settings.show_grid}
</DropdownMenu.CheckboxItem>
</DropdownMenu.Content>
</DropdownMenu.Root>
</DropdownMenu.Root> */}
<Popover.Root>
{/* <Popover.Root>
<Popover.Trigger>
<IconButton color="gray" variant="ghost">
<CameraIcon />
@@ -603,9 +570,9 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
</Popover.Close>
</Flex>
</Popover.Content>
</Popover.Root>
</Popover.Root> */}
{fullScreenAvailable && (
{/* {fullScreenAvailable && (
<IconButton
color="gray"
variant="ghost"
@@ -617,7 +584,7 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
>
{isFullScreen ? <ExitFullScreenIcon /> : <EnterFullScreenIcon />}
</IconButton>
)}
)} */}
</Flex>
</Flex>
</>
@@ -1,22 +1,14 @@
import { OrbitControls } from '@react-three/drei';
import { invalidate, useThree } from '@react-three/fiber';
import { clamp } from 'lodash-es';
import { useEffect, useRef, useState } from 'react';
import { PerspectiveCamera, Vector3 } from 'three';
import { useThree } from '@react-three/fiber';
import { useRef, useState } from 'react';
import { PerspectiveCamera } from 'three';
import { OrbitControls as OrbitControlsClass } from 'three-stdlib';
import { degToRad } from 'three/src/math/MathUtils.js';
import { awaitableModelDefinitions } from '../../../../../../core/awaitables/modelDefinitions';
import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits';
import { hasEquipment } from '../../../../../../core/blitzkit/hasEquipment';
import { Pose, poseEvent } from '../../../../../../core/blitzkit/pose';
import { Pose } from '../../../../../../core/blitzkit/pose';
import { Duel } from '../../../../../../stores/duel';
import {
SHOOTING_RANGE_ZOOM_COEFFICIENTS,
ShootingRangeZoom,
TankopediaEphemeral,
} from '../../../../../../stores/tankopediaEphemeral';
import { TankopediaEphemeral } from '../../../../../../stores/tankopediaEphemeral';
import { TankopediaDisplay } from '../../../../../../stores/tankopediaPersistent/constants';
import { aimTarget } from './SceneProps/constants';
const poseDistances: Record<Pose, number> = {
[Pose.HullDown]: 15,
@@ -43,302 +35,302 @@ export function Controls({ naked }: ControlsProps) {
const camera = useThree((state) => state.camera as PerspectiveCamera);
const canvas = useThree((state) => state.gl.domElement);
const orbitControls = useRef<OrbitControlsClass>(null);
const protagonistTurret = Duel.use((state) => state.protagonist.turret);
const antagonistTurret = Duel.use((state) => state.antagonist.turret);
const protagonistTrack = Duel.use((state) => state.protagonist.track);
// const protagonistTurret = Duel.use((state) => state.protagonist.turret);
// const antagonistTurret = Duel.use((state) => state.antagonist.turret);
// const protagonistTrack = Duel.use((state) => state.protagonist.track);
const protagonistTank = Duel.use((state) => state.protagonist.tank);
const antagonistTank = Duel.use((state) => state.antagonist.tank);
const protagonistGun = Duel.use((state) => state.protagonist.gun);
const protagonistModelDefinition =
modelDefinitions.models[protagonistTank.id];
const protagonistTrackModelDefinition =
modelDefinitions.models[protagonistTank.id].tracks[protagonistTrack.id];
const antagonistModelDefinition = modelDefinitions.models[antagonistTank.id];
const protagonistTurretModelDefinition =
protagonistModelDefinition.turrets[protagonistTurret.id];
const antagonistTurretModelDefinition =
antagonistModelDefinition.turrets[antagonistTurret.id];
const protagonistGunModelDefinition =
protagonistTurretModelDefinition.guns[
protagonistGun.gun_type!.value.base.id
];
const protagonistHullOrigin = new Vector3(
protagonistTrackModelDefinition.origin.x,
protagonistTrackModelDefinition.origin.y,
-protagonistTrackModelDefinition.origin.z,
);
const protagonistTurretOrigin = new Vector3(
protagonistModelDefinition.turret_origin.x,
protagonistModelDefinition.turret_origin.y,
-protagonistModelDefinition.turret_origin.z,
);
const protagonistGunOrigin = new Vector3(
protagonistTurretModelDefinition.gun_origin.x,
protagonistTurretModelDefinition.gun_origin.y,
-protagonistTurretModelDefinition.gun_origin.z,
);
const antagonistGunHeight =
protagonistTrackModelDefinition.origin.y +
antagonistModelDefinition.turret_origin.y +
antagonistTurretModelDefinition.gun_origin.y;
// const antagonistTank = Duel.use((state) => state.antagonist.tank);
// const protagonistGun = Duel.use((state) => state.protagonist.gun);
// const protagonistModelDefinition =
// modelDefinitions.models[protagonistTank.id];
// const protagonistTrackModelDefinition =
// modelDefinitions.models[protagonistTank.id].tracks[protagonistTrack.id];
// const antagonistModelDefinition = modelDefinitions.models[antagonistTank.id];
// const protagonistTurretModelDefinition =
// protagonistModelDefinition.turrets[protagonistTurret.id];
// const antagonistTurretModelDefinition =
// antagonistModelDefinition.turrets[antagonistTurret.id];
// const protagonistGunModelDefinition =
// protagonistTurretModelDefinition.guns[
// protagonistGun.gun_type!.value.base.id
// ];
// const protagonistHullOrigin = new Vector3(
// protagonistTrackModelDefinition.origin.x,
// protagonistTrackModelDefinition.origin.y,
// -protagonistTrackModelDefinition.origin.z,
// );
// const protagonistTurretOrigin = new Vector3(
// protagonistModelDefinition.turret_origin.x,
// protagonistModelDefinition.turret_origin.y,
// -protagonistModelDefinition.turret_origin.z,
// );
// const protagonistGunOrigin = new Vector3(
// protagonistTurretModelDefinition.gun_origin.x,
// protagonistTurretModelDefinition.gun_origin.y,
// -protagonistTurretModelDefinition.gun_origin.z,
// );
// const antagonistGunHeight =
// protagonistTrackModelDefinition.origin.y +
// antagonistModelDefinition.turret_origin.y +
// antagonistTurretModelDefinition.gun_origin.y;
const [autoRotate, setAutoRotate] = useState(
display !== TankopediaDisplay.ShootingRange && !naked,
);
const gunHeight =
protagonistHullOrigin.y +
protagonistTurretOrigin.y +
protagonistGunOrigin.y;
const inspectModeInitialPosition = [
-8,
gunHeight + (naked ? 10 : 4),
-13,
] as const;
const protagonistGunOriginOnlyY = new Vector3(
0,
protagonistTurretModelDefinition.gun_origin.y,
0,
);
const shellOrigin = protagonistHullOrigin
.clone()
.add(protagonistTurretOrigin)
.add(protagonistGunOriginOnlyY);
// const gunHeight =
// protagonistHullOrigin.y +
// protagonistTurretOrigin.y +
// protagonistGunOrigin.y;
// const inspectModeInitialPosition = [
// -8,
// gunHeight + (naked ? 10 : 4),
// -13,
// ] as const;
// const protagonistGunOriginOnlyY = new Vector3(
// 0,
// protagonistTurretModelDefinition.gun_origin.y,
// 0,
// );
// const shellOrigin = protagonistHullOrigin
// .clone()
// .add(protagonistTurretOrigin)
// .add(protagonistGunOriginOnlyY);
useEffect(() => {
const unsubscribeTankopediaEphemeral = tankopediaEphemeralStore.subscribe(
(state) => state.controlsEnabled,
(enabled) => {
if (orbitControls.current) orbitControls.current.enabled = enabled;
},
);
// useEffect(() => {
// const unsubscribeTankopediaEphemeral = tankopediaEphemeralStore.subscribe(
// (state) => state.controlsEnabled,
// (enabled) => {
// if (orbitControls.current) orbitControls.current.enabled = enabled;
// },
// );
async function handlePoseEvent(event: Pose) {
const duel = duelStore.getState();
const hasImprovedVerticalStabilizer = await hasEquipment(
122,
duel.protagonist.tank.equipment_preset,
duel.protagonist.equipmentMatrix,
);
const hasDownImprovedVerticalStabilizer = await hasEquipment(
124,
duel.protagonist.tank.equipment_preset,
duel.protagonist.equipmentMatrix,
);
// async function handlePoseEvent(event: Pose) {
// const duel = duelStore.getState();
// const hasImprovedVerticalStabilizer = await hasEquipment(
// 122,
// duel.protagonist.tank.equipment_preset,
// duel.protagonist.equipmentMatrix,
// );
// const hasDownImprovedVerticalStabilizer = await hasEquipment(
// 124,
// duel.protagonist.tank.equipment_preset,
// duel.protagonist.equipmentMatrix,
// );
switch (event) {
case Pose.HullDown: {
const [pitch] = applyPitchYawLimits(
-Infinity,
0,
protagonistGunModelDefinition.pitch,
protagonistTurretModelDefinition.yaw,
hasImprovedVerticalStabilizer,
hasDownImprovedVerticalStabilizer,
);
// switch (event) {
// case Pose.HullDown: {
// const [pitch] = applyPitchYawLimits(
// -Infinity,
// 0,
// protagonistGunModelDefinition.pitch,
// protagonistTurretModelDefinition.yaw,
// hasImprovedVerticalStabilizer,
// hasDownImprovedVerticalStabilizer,
// );
camera.position
.set(0, 0, 0)
.add(protagonistHullOrigin)
.add(protagonistTurretOrigin)
.add(protagonistGunOrigin)
.add(
new Vector3(
0,
poseDistances[event] * Math.sin(pitch),
poseDistances[event] * -Math.cos(pitch),
),
);
camera.lookAt(
protagonistHullOrigin
.clone()
.add(protagonistTurretOrigin)
.add(protagonistGunOrigin),
);
orbitControls.current?.target.set(0, antagonistGunHeight, 0);
// camera.position
// .set(0, 0, 0)
// .add(protagonistHullOrigin)
// .add(protagonistTurretOrigin)
// .add(protagonistGunOrigin)
// .add(
// new Vector3(
// 0,
// poseDistances[event] * Math.sin(pitch),
// poseDistances[event] * -Math.cos(pitch),
// ),
// );
// camera.lookAt(
// protagonistHullOrigin
// .clone()
// .add(protagonistTurretOrigin)
// .add(protagonistGunOrigin),
// );
// orbitControls.current?.target.set(0, antagonistGunHeight, 0);
break;
}
// break;
// }
case Pose.FaceHug: {
const [pitch] = applyPitchYawLimits(
0,
0,
protagonistGunModelDefinition.pitch,
protagonistTurretModelDefinition.yaw,
hasImprovedVerticalStabilizer,
hasDownImprovedVerticalStabilizer,
);
// case Pose.FaceHug: {
// const [pitch] = applyPitchYawLimits(
// 0,
// 0,
// protagonistGunModelDefinition.pitch,
// protagonistTurretModelDefinition.yaw,
// hasImprovedVerticalStabilizer,
// hasDownImprovedVerticalStabilizer,
// );
camera.position
.set(0, 0, 0)
.add(protagonistHullOrigin)
.add(protagonistTurretOrigin)
.add(protagonistGunOrigin)
.add(
new Vector3(
0,
poseDistances[event] * Math.sin(pitch),
poseDistances[event] * -Math.cos(pitch),
),
);
orbitControls.current?.target
.set(0, 0, 0)
.add(protagonistHullOrigin)
.add(protagonistTurretOrigin)
.add(protagonistGunOrigin)
.add(
new Vector3(
0,
0.5 * poseDistances[event] * Math.sin(pitch),
0.5 * poseDistances[event] * -Math.cos(pitch),
),
);
// camera.position
// .set(0, 0, 0)
// .add(protagonistHullOrigin)
// .add(protagonistTurretOrigin)
// .add(protagonistGunOrigin)
// .add(
// new Vector3(
// 0,
// poseDistances[event] * Math.sin(pitch),
// poseDistances[event] * -Math.cos(pitch),
// ),
// );
// orbitControls.current?.target
// .set(0, 0, 0)
// .add(protagonistHullOrigin)
// .add(protagonistTurretOrigin)
// .add(protagonistGunOrigin)
// .add(
// new Vector3(
// 0,
// 0.5 * poseDistances[event] * Math.sin(pitch),
// 0.5 * poseDistances[event] * -Math.cos(pitch),
// ),
// );
break;
}
// break;
// }
case Pose.Default: {
camera.position.set(...inspectModeInitialPosition);
orbitControls.current?.target.set(0, 1.25, 0);
break;
}
}
// case Pose.Default: {
// camera.position.set(...inspectModeInitialPosition);
// orbitControls.current?.target.set(0, 1.25, 0);
// break;
// }
// }
invalidate();
}
// invalidate();
// }
poseEvent.on(handlePoseEvent);
// poseEvent.on(handlePoseEvent);
return () => {
unsubscribeTankopediaEphemeral();
poseEvent.off(handlePoseEvent);
};
}, [camera, protagonistTank.id, antagonistTank.id]);
// return () => {
// unsubscribeTankopediaEphemeral();
// poseEvent.off(handlePoseEvent);
// };
// }, [camera, protagonistTank.id, antagonistTank.id]);
useEffect(() => {
let isInitialIteration = true;
// useEffect(() => {
// let isInitialIteration = true;
function handleDisturbance() {
setAutoRotate(false);
}
// function handleDisturbance() {
// setAutoRotate(false);
// }
function handleKeyDown(event: KeyboardEvent) {
if (display !== TankopediaDisplay.ShootingRange || event.repeat) return;
// function handleKeyDown(event: KeyboardEvent) {
// if (display !== TankopediaDisplay.ShootingRange || event.repeat) return;
if (event.key === 'Shift') {
mutateTankopediaEphemeral((draft) => {
draft.shootingRangeZoom =
draft.shootingRangeZoom === ShootingRangeZoom.Arcade
? ShootingRangeZoom.Zoom0
: ShootingRangeZoom.Arcade;
});
}
}
// if (event.key === 'Shift') {
// mutateTankopediaEphemeral((draft) => {
// draft.shootingRangeZoom =
// draft.shootingRangeZoom === ShootingRangeZoom.Arcade
// ? ShootingRangeZoom.Zoom0
// : ShootingRangeZoom.Arcade;
// });
// }
// }
function handleWheel(event: WheelEvent) {
if (display !== TankopediaDisplay.ShootingRange) return;
// function handleWheel(event: WheelEvent) {
// if (display !== TankopediaDisplay.ShootingRange) return;
event.preventDefault();
// event.preventDefault();
mutateTankopediaEphemeral((draft) => {
draft.shootingRangeZoom = clamp(
draft.shootingRangeZoom - Math.sign(event.deltaY),
ShootingRangeZoom.Arcade,
ShootingRangeZoom.Zoom2,
);
});
}
// mutateTankopediaEphemeral((draft) => {
// draft.shootingRangeZoom = clamp(
// draft.shootingRangeZoom - Math.sign(event.deltaY),
// ShootingRangeZoom.Arcade,
// ShootingRangeZoom.Zoom2,
// );
// });
// }
function handleScroll(event: Event) {
if (display !== TankopediaDisplay.ShootingRange) return;
// function handleScroll(event: Event) {
// if (display !== TankopediaDisplay.ShootingRange) return;
event.preventDefault();
}
// event.preventDefault();
// }
poseEvent.on(handleDisturbance);
canvas.addEventListener('pointerdown', handleDisturbance);
window.addEventListener('keydown', handleKeyDown);
canvas.addEventListener('wheel', handleWheel);
document.body.addEventListener('scroll', handleScroll);
// poseEvent.on(handleDisturbance);
// canvas.addEventListener('pointerdown', handleDisturbance);
// window.addEventListener('keydown', handleKeyDown);
// canvas.addEventListener('wheel', handleWheel);
// document.body.addEventListener('scroll', handleScroll);
function updateCamera() {
if (!orbitControls.current) return;
// function updateCamera() {
// if (!orbitControls.current) return;
orbitControls.current.rotateSpeed = 0.25;
// orbitControls.current.rotateSpeed = 0.25;
if (display === TankopediaDisplay.ShootingRange) {
const { shootingRangeZoom } = tankopediaEphemeralStore.getState();
// if (display === TankopediaDisplay.ShootingRange) {
// const { shootingRangeZoom } = tankopediaEphemeralStore.getState();
orbitControls.current.enablePan = false;
orbitControls.current.enableZoom = false;
orbitControls.current.rotateSpeed *=
SHOOTING_RANGE_ZOOM_COEFFICIENTS[shootingRangeZoom];
camera.fov =
ARCADE_MODE_FOV * SHOOTING_RANGE_ZOOM_COEFFICIENTS[shootingRangeZoom];
// orbitControls.current.enablePan = false;
// orbitControls.current.enableZoom = false;
// orbitControls.current.rotateSpeed *=
// SHOOTING_RANGE_ZOOM_COEFFICIENTS[shootingRangeZoom];
// camera.fov =
// ARCADE_MODE_FOV * SHOOTING_RANGE_ZOOM_COEFFICIENTS[shootingRangeZoom];
if (shootingRangeZoom === ShootingRangeZoom.Arcade) {
orbitControls.current.target.set(0, gunHeight + 3, 0);
// if (shootingRangeZoom === ShootingRangeZoom.Arcade) {
// orbitControls.current.target.set(0, gunHeight + 3, 0);
if (isInitialIteration) {
isInitialIteration = false;
camera.position.set(
0,
gunHeight + ARCADE_MODE_DISTANCE * Math.sin(ARCADE_MODE_ANGLE),
ARCADE_MODE_DISTANCE * Math.cos(ARCADE_MODE_ANGLE),
);
} else {
camera.position
.copy(aimTarget)
.sub(orbitControls.current.target)
.normalize()
.multiplyScalar(-ARCADE_MODE_DISTANCE)
.add(orbitControls.current.target);
}
} else {
orbitControls.current.target.copy(shellOrigin);
// if (isInitialIteration) {
// isInitialIteration = false;
// camera.position.set(
// 0,
// gunHeight + ARCADE_MODE_DISTANCE * Math.sin(ARCADE_MODE_ANGLE),
// ARCADE_MODE_DISTANCE * Math.cos(ARCADE_MODE_ANGLE),
// );
// } else {
// camera.position
// .copy(aimTarget)
// .sub(orbitControls.current.target)
// .normalize()
// .multiplyScalar(-ARCADE_MODE_DISTANCE)
// .add(orbitControls.current.target);
// }
// } else {
// orbitControls.current.target.copy(shellOrigin);
camera.position
.copy(aimTarget)
.sub(shellOrigin)
.normalize()
.multiplyScalar(-Number.EPSILON)
.add(shellOrigin);
}
} else {
camera.position.set(...inspectModeInitialPosition);
orbitControls.current.target.set(0, gunHeight / (naked ? 4 : 2), 0);
orbitControls.current.enablePan = true;
orbitControls.current.enableZoom = true;
camera.fov = naked ? 20 : 25;
}
// camera.position
// .copy(aimTarget)
// .sub(shellOrigin)
// .normalize()
// .multiplyScalar(-Number.EPSILON)
// .add(shellOrigin);
// }
// } else {
// camera.position.set(...inspectModeInitialPosition);
// orbitControls.current.target.set(0, gunHeight / (naked ? 4 : 2), 0);
// orbitControls.current.enablePan = true;
// orbitControls.current.enableZoom = true;
// camera.fov = naked ? 20 : 25;
// }
camera.updateProjectionMatrix();
}
// camera.updateProjectionMatrix();
// }
updateCamera();
// updateCamera();
const unsubscribeDisplay = tankopediaEphemeralStore.subscribe(
(state) => state.display,
() => {
handleDisturbance();
updateCamera();
},
);
// const unsubscribeDisplay = tankopediaEphemeralStore.subscribe(
// (state) => state.display,
// () => {
// handleDisturbance();
// updateCamera();
// },
// );
const unsubscribeShootingRangeZoom = tankopediaEphemeralStore.subscribe(
(state) => state.shootingRangeZoom,
updateCamera,
);
// const unsubscribeShootingRangeZoom = tankopediaEphemeralStore.subscribe(
// (state) => state.shootingRangeZoom,
// updateCamera,
// );
return () => {
canvas.removeEventListener('pointerdown', handleDisturbance);
poseEvent.off(handleDisturbance);
unsubscribeDisplay();
unsubscribeShootingRangeZoom();
window.removeEventListener('keydown', handleKeyDown);
canvas.removeEventListener('wheel', handleWheel);
document.body.removeEventListener('scroll', handleScroll);
};
}, [display === TankopediaDisplay.ShootingRange]);
// return () => {
// canvas.removeEventListener('pointerdown', handleDisturbance);
// poseEvent.off(handleDisturbance);
// unsubscribeDisplay();
// unsubscribeShootingRangeZoom();
// window.removeEventListener('keydown', handleKeyDown);
// canvas.removeEventListener('wheel', handleWheel);
// document.body.removeEventListener('scroll', handleScroll);
// };
// }, [display === TankopediaDisplay.ShootingRange]);
return (
<OrbitControls
@@ -2,19 +2,14 @@ import { Environment } from '@react-three/drei';
import { times } from 'lodash-es';
import { Euler } from 'three';
import { degToRad } from 'three/src/math/MathUtils.js';
import { useModel } from '../../../../../../hooks/useModel';
import { Duel } from '../../../../../../stores/duel';
import { TankopediaEphemeral } from '../../../../../../stores/tankopediaEphemeral';
import { TankopediaDisplay } from '../../../../../../stores/tankopediaPersistent/constants';
const LIGHTS_COUNT = 5;
export function Lighting() {
const protagonist = Duel.use((state) => state.protagonist);
const display = TankopediaEphemeral.use((state) => state.display);
const { hasPbr } = useModel(protagonist.tank.id);
const isBrighterLighting =
!hasPbr && display !== TankopediaDisplay.StaticArmor;
const isBrighterLighting = display !== TankopediaDisplay.StaticArmor;
return (
<>
@@ -1,29 +1,13 @@
import { asset, I_HAT, imgur, J_HAT } from '@blitzkit/core';
import { Html } from '@react-three/drei';
import { imgur } from '@blitzkit/core';
import { useFrame, useLoader } from '@react-three/fiber';
import { clamp } from 'lodash-es';
import { useRef } from 'react';
import {
Group,
MeshStandardMaterial,
PerspectiveCamera,
TextureLoader,
Vector2,
Vector3,
} from 'three';
import { GLTFLoader } from 'three/examples/jsm/Addons.js';
import { degToRad, radToDeg } from 'three/src/math/MathUtils.js';
import { MeshStandardMaterial, TextureLoader, Vector2 } from 'three';
import { awaitableEquipmentDefinitions } from '../../../../../../../core/awaitables/equipmentDefinitions';
import { awaitableModelDefinitions } from '../../../../../../../core/awaitables/modelDefinitions';
import { awaitableProvisionDefinitions } from '../../../../../../../core/awaitables/provisionDefinitions';
import { applyPitchYawLimits } from '../../../../../../../core/blitz/applyPitchYawLimits';
import { modelTransformEvent } from '../../../../../../../core/blitzkit/modelTransform';
import { tankCharacteristics } from '../../../../../../../core/blitzkit/tankCharacteristics';
import { Duel } from '../../../../../../../stores/duel';
import { TankopediaEphemeral } from '../../../../../../../stores/tankopediaEphemeral';
import { TankopediaDisplay } from '../../../../../../../stores/tankopediaPersistent/constants';
import { TargetCircle } from './components/TargetCircle';
import { aimTarget } from './constants';
const [modelDefinitions, equipmentDefinitions, provisionDefinitions] =
await Promise.all([
@@ -35,203 +19,203 @@ const [modelDefinitions, equipmentDefinitions, provisionDefinitions] =
const emptyVector = new Vector2();
export function SceneProps() {
const targetCircleWrapper = useRef<Group>(null);
const clientTargetCircle = useRef<HTMLDivElement>(null);
const serverTargetCircle = useRef<HTMLDivElement>(null);
// const targetCircleWrapper = useRef<Group>(null);
// const clientTargetCircle = useRef<HTMLDivElement>(null);
// const serverTargetCircle = useRef<HTMLDivElement>(null);
const material = useRef<MeshStandardMaterial>(null);
const playground = useRef<Group>(null);
// const playground = useRef<Group>(null);
let lastPitch = Duel.use((state) => state.protagonist.pitch);
let lastYaw = Duel.use((state) => state.protagonist.yaw);
// let lastPitch = Duel.use((state) => state.protagonist.pitch);
// let lastYaw = Duel.use((state) => state.protagonist.yaw);
const display = TankopediaEphemeral.use((state) => state.display);
const turret = Duel.use((state) => state.protagonist.turret);
const track = Duel.use((state) => state.protagonist.track);
const tank = Duel.use((state) => state.protagonist.tank);
const gun = Duel.use((state) => state.protagonist.gun);
const shell = Duel.use((state) => state.protagonist.shell);
const engine = Duel.use((state) => state.protagonist.engine);
const tankModelDefinition = modelDefinitions.models[tank.id];
const trackModelDefinition =
modelDefinitions.models[tank.id].tracks[track.id];
const gunModelDefinition =
tankModelDefinition.turrets[turret.id].guns[gun.gun_type!.value.base.id];
const turretModelDefinition = tankModelDefinition.turrets[turret.id];
const hullOrigin = new Vector3(
trackModelDefinition.origin.x,
trackModelDefinition.origin.y,
-trackModelDefinition.origin.z,
);
const turretOrigin = new Vector3(
tankModelDefinition.turret_origin.x,
tankModelDefinition.turret_origin.y,
-tankModelDefinition.turret_origin.z,
);
const gunOriginOnlyY = new Vector3(0, turretModelDefinition.gun_origin.y, 0);
const shellOrigin = hullOrigin.clone().add(turretOrigin).add(gunOriginOnlyY);
const camouflage = Duel.use((state) => state.protagonist.camouflage);
const crewSkills = TankopediaEphemeral.use((state) => state.skills);
const consumables = Duel.use((state) => state.protagonist.consumables);
const provisions = Duel.use((state) => state.protagonist.provisions);
const equipmentMatrix = Duel.use(
(state) => state.protagonist.equipmentMatrix,
);
const characteristics = tankCharacteristics(
{
tank,
turret,
gun,
shell,
consumables,
applyDynamicArmor: false,
applyReactiveArmor: false,
applySpallLiner: false,
camouflage,
crewSkills,
engine,
equipmentMatrix,
provisions,
stockEngine: tank.engines[0],
stockGun: tank.turrets[0].guns[0],
stockTurret: tank.turrets[0],
stockTrack: tank.tracks[0],
track,
},
{ equipmentDefinitions, provisionDefinitions, tankModelDefinition },
);
let { dispersion } = characteristics;
const gunRotationSpeed = degToRad(characteristics.gunTraverseSpeed);
const mockTank = useLoader(GLTFLoader, asset(`3d/tanks/models/6929.glb`));
// const turret = Duel.use((state) => state.protagonist.turret);
// const track = Duel.use((state) => state.protagonist.track);
// const tank = Duel.use((state) => state.protagonist.tank);
// const gun = Duel.use((state) => state.protagonist.gun);
// const shell = Duel.use((state) => state.protagonist.shell);
// const engine = Duel.use((state) => state.protagonist.engine);
// const tankModelDefinition = modelDefinitions.models[tank.id];
// const trackModelDefinition =
// modelDefinitions.models[tank.id].tracks[track.id];
// const gunModelDefinition =
// tankModelDefinition.turrets[turret.id].guns[gun.gun_type!.value.base.id];
// const turretModelDefinition = tankModelDefinition.turrets[turret.id];
// const hullOrigin = new Vector3(
// trackModelDefinition.origin.x,
// trackModelDefinition.origin.y,
// -trackModelDefinition.origin.z,
// );
// const turretOrigin = new Vector3(
// tankModelDefinition.turret_origin.x,
// tankModelDefinition.turret_origin.y,
// -tankModelDefinition.turret_origin.z,
// );
// const gunOriginOnlyY = new Vector3(0, turretModelDefinition.gun_origin.y, 0);
// const shellOrigin = hullOrigin.clone().add(turretOrigin).add(gunOriginOnlyY);
// const camouflage = Duel.use((state) => state.protagonist.camouflage);
// const crewSkills = TankopediaEphemeral.use((state) => state.skills);
// const consumables = Duel.use((state) => state.protagonist.consumables);
// const provisions = Duel.use((state) => state.protagonist.provisions);
// const equipmentMatrix = Duel.use(
// (state) => state.protagonist.equipmentMatrix,
// );
// const characteristics = tankCharacteristics(
// {
// tank,
// turret,
// gun,
// shell,
// consumables,
// applyDynamicArmor: false,
// applyReactiveArmor: false,
// applySpallLiner: false,
// camouflage,
// crewSkills,
// engine,
// equipmentMatrix,
// provisions,
// stockEngine: tank.engines[0],
// stockGun: tank.turrets[0].guns[0],
// stockTurret: tank.turrets[0],
// stockTrack: tank.tracks[0],
// track,
// },
// { equipmentDefinitions, provisionDefinitions, tankModelDefinition },
// );
// let { dispersion } = characteristics;
// const gunRotationSpeed = degToRad(characteristics.gunTraverseSpeed);
// const mockTank = useLoader(GLTFLoader, asset(`3d/tanks/models/6929.glb`));
const texture = useLoader(TextureLoader, imgur('C28Z8nU'));
const path = new Vector3();
const direction = new Vector3();
texture.anisotropy = 2;
// const path = new Vector3();
// const direction = new Vector3();
useFrame(({ camera }) => {
if (!material.current) return;
material.current.opacity = clamp(0.5 * camera.position.y, 0, 1);
});
let lastTime = 0;
let DEBUG_disable = false;
// let lastTime = 0;
// let DEBUG_disable = false;
useFrame(({ raycaster, camera, clock, gl }) => {
if (
display !== TankopediaDisplay.ShootingRange ||
!playground.current ||
!targetCircleWrapper.current ||
DEBUG_disable ||
!clientTargetCircle.current ||
!serverTargetCircle.current
) {
return;
}
// useFrame(({ raycaster, camera, clock, gl }) => {
// if (
// display !== TankopediaDisplay.ShootingRange ||
// !playground.current ||
// !targetCircleWrapper.current ||
// DEBUG_disable ||
// !clientTargetCircle.current ||
// !serverTargetCircle.current
// ) {
// return;
// }
raycaster.setFromCamera(emptyVector, camera);
// raycaster.setFromCamera(emptyVector, camera);
const cameraIntersections = raycaster.intersectObjects(
playground.current.children,
true,
);
// const cameraIntersections = raycaster.intersectObjects(
// playground.current.children,
// true,
// );
let length: number;
// let length: number;
if (cameraIntersections.length === 0) {
length = characteristics.shellRange;
direction.set(0, 0, -1).applyQuaternion(camera.quaternion);
aimTarget.copy(shellOrigin).addScaledVector(direction, length);
} else {
aimTarget.copy(cameraIntersections[0].point);
path.copy(aimTarget).sub(shellOrigin);
length = path.length();
direction.copy(path).normalize();
}
// if (cameraIntersections.length === 0) {
// length = characteristics.shellRange;
// direction.set(0, 0, -1).applyQuaternion(camera.quaternion);
// aimTarget.copy(shellOrigin).addScaledVector(direction, length);
// } else {
// aimTarget.copy(cameraIntersections[0].point);
// path.copy(aimTarget).sub(shellOrigin);
// length = path.length();
// direction.copy(path).normalize();
// }
const deltaT = clock.elapsedTime - lastTime;
lastTime = clock.elapsedTime;
// const deltaT = clock.elapsedTime - lastTime;
// lastTime = clock.elapsedTime;
const desiredPitch = Math.asin(direction.y);
const desiredYaw = Math.atan2(-direction.x, -direction.z);
const deltaPitch = desiredPitch - lastPitch;
const deltaYaw = desiredYaw - lastYaw;
const deltaAngle = Math.sqrt(deltaPitch ** 2 + deltaYaw ** 2);
const maxPossibleRotationInTime = gunRotationSpeed * deltaT;
const clampedDeltaAngle = Math.min(maxPossibleRotationInTime, deltaAngle);
const clampedDeltaPitch =
deltaAngle === 0 ? 0 : deltaPitch * (clampedDeltaAngle / deltaAngle);
const clampedDeltaYaw =
deltaAngle === 0 ? 0 : deltaYaw * (clampedDeltaAngle / deltaAngle);
// const desiredPitch = Math.asin(direction.y);
// const desiredYaw = Math.atan2(-direction.x, -direction.z);
// const deltaPitch = desiredPitch - lastPitch;
// const deltaYaw = desiredYaw - lastYaw;
// const deltaAngle = Math.sqrt(deltaPitch ** 2 + deltaYaw ** 2);
// const maxPossibleRotationInTime = gunRotationSpeed * deltaT;
// const clampedDeltaAngle = Math.min(maxPossibleRotationInTime, deltaAngle);
// const clampedDeltaPitch =
// deltaAngle === 0 ? 0 : deltaPitch * (clampedDeltaAngle / deltaAngle);
// const clampedDeltaYaw =
// deltaAngle === 0 ? 0 : deltaYaw * (clampedDeltaAngle / deltaAngle);
const [pitch, yaw] = applyPitchYawLimits(
lastPitch + clampedDeltaPitch,
lastYaw + clampedDeltaYaw,
gunModelDefinition.pitch,
turretModelDefinition.yaw,
);
// const [pitch, yaw] = applyPitchYawLimits(
// lastPitch + clampedDeltaPitch,
// lastYaw + clampedDeltaYaw,
// gunModelDefinition.pitch,
// turretModelDefinition.yaw,
// );
const actualDeltaPitch = pitch - lastPitch;
const actualDeltaYaw = yaw - lastYaw;
const deltaRotation = Math.abs(actualDeltaPitch) + Math.abs(actualDeltaYaw);
const rotationSpeed = deltaRotation / deltaT;
const turretTraversePenalty =
characteristics.dispersionTurretTraversing * radToDeg(rotationSpeed);
// const actualDeltaPitch = pitch - lastPitch;
// const actualDeltaYaw = yaw - lastYaw;
// const deltaRotation = Math.abs(actualDeltaPitch) + Math.abs(actualDeltaYaw);
// const rotationSpeed = deltaRotation / deltaT;
// const turretTraversePenalty =
// characteristics.dispersionTurretTraversing * radToDeg(rotationSpeed);
const penalty = Math.sqrt(1 + turretTraversePenalty ** 2);
// const penalty = Math.sqrt(1 + turretTraversePenalty ** 2);
if (penalty === 1) {
const dispersionMod = dispersion - characteristics.dispersion;
const dispersionModDischarged =
dispersionMod * Math.exp(-deltaT / characteristics.aimTime) -
dispersionMod;
// if (penalty === 1) {
// const dispersionMod = dispersion - characteristics.dispersion;
// const dispersionModDischarged =
// dispersionMod * Math.exp(-deltaT / characteristics.aimTime) -
// dispersionMod;
dispersion += dispersionModDischarged;
} else {
dispersion = Math.max(characteristics.dispersion * penalty, dispersion);
}
// dispersion += dispersionModDischarged;
// } else {
// dispersion = Math.max(characteristics.dispersion * penalty, dispersion);
// }
modelTransformEvent.emit({ pitch, yaw });
lastPitch = pitch;
lastYaw = yaw;
// modelTransformEvent.emit({ pitch, yaw });
// lastPitch = pitch;
// lastYaw = yaw;
const gunDirection = new Vector3(0, 0, -1)
.applyAxisAngle(I_HAT, pitch)
.applyAxisAngle(J_HAT, yaw);
// const gunDirection = new Vector3(0, 0, -1)
// .applyAxisAngle(I_HAT, pitch)
// .applyAxisAngle(J_HAT, yaw);
raycaster.set(shellOrigin, gunDirection);
// raycaster.set(shellOrigin, gunDirection);
const gunIntersections = raycaster.intersectObjects(
playground.current.children,
true,
);
// const gunIntersections = raycaster.intersectObjects(
// playground.current.children,
// true,
// );
let gunTarget: Vector3;
// let gunTarget: Vector3;
if (gunIntersections.length === 0) {
gunTarget = shellOrigin
.clone()
.add(gunDirection.multiplyScalar(characteristics.shellRange));
} else {
gunTarget = gunIntersections[0].point;
}
// if (gunIntersections.length === 0) {
// gunTarget = shellOrigin
// .clone()
// .add(gunDirection.multiplyScalar(characteristics.shellRange));
// } else {
// gunTarget = gunIntersections[0].point;
// }
targetCircleWrapper.current.position.copy(gunTarget);
// targetCircleWrapper.current.position.copy(gunTarget);
const distanceToTarget = gunTarget.distanceTo(shellOrigin);
const dispersionRadius = dispersion * (distanceToTarget / 100);
const dispersionSize = 2 * dispersionRadius;
const targetDistanceFromCamera = gunTarget.distanceTo(camera.position);
const angularSize =
2 * Math.atan(dispersionSize / (2 * targetDistanceFromCamera));
const verticalScreenSize =
2 * Math.tan(degToRad((camera as PerspectiveCamera).fov) / 2);
const screenSpaceSize =
gl.domElement.clientHeight * (angularSize / verticalScreenSize);
const targetCircleDotsSize = `${screenSpaceSize}px`;
// const distanceToTarget = gunTarget.distanceTo(shellOrigin);
// const dispersionRadius = dispersion * (distanceToTarget / 100);
// const dispersionSize = 2 * dispersionRadius;
// const targetDistanceFromCamera = gunTarget.distanceTo(camera.position);
// const angularSize =
// 2 * Math.atan(dispersionSize / (2 * targetDistanceFromCamera));
// const verticalScreenSize =
// 2 * Math.tan(degToRad((camera as PerspectiveCamera).fov) / 2);
// const screenSpaceSize =
// gl.domElement.clientHeight * (angularSize / verticalScreenSize);
// const targetCircleDotsSize = `${screenSpaceSize}px`;
clientTargetCircle.current.style.width = targetCircleDotsSize;
clientTargetCircle.current.style.height = targetCircleDotsSize;
serverTargetCircle.current.style.width = targetCircleDotsSize;
serverTargetCircle.current.style.height = targetCircleDotsSize;
});
// clientTargetCircle.current.style.width = targetCircleDotsSize;
// clientTargetCircle.current.style.height = targetCircleDotsSize;
// serverTargetCircle.current.style.width = targetCircleDotsSize;
// serverTargetCircle.current.style.height = targetCircleDotsSize;
// });
return (
<>
@@ -242,7 +226,7 @@ export function SceneProps() {
</mesh>
)}
{display === TankopediaDisplay.ShootingRange && (
{/* {display === TankopediaDisplay.ShootingRange && (
<>
<group ref={targetCircleWrapper}>
<Html center occlude={false} zIndexRange={[0, 0]}>
@@ -272,7 +256,7 @@ export function SceneProps() {
</group>
</group>
</>
)}
)} */}
</>
);
}
@@ -18,6 +18,8 @@ import { TankopediaDisplay } from '../../../../../../stores/tankopediaPersistent
import { ModelTankWrapper } from '../../../../../Armor/components/ModelTankWrapper';
export function TankModel() {
return null;
const mutateDuel = Duel.useMutation();
const duelStore = Duel.useStore();
const protagonist = Duel.use((draft) => draft.protagonist);
@@ -1,19 +1,7 @@
import { BLITZKIT_TANK_ICON_SIZE } from '@blitzkit/core';
import { Canvas } from '@react-three/fiber';
import {
forwardRef,
Suspense,
useEffect,
useImperativeHandle,
useRef,
} from 'react';
import { applyPitchYawLimits } from '../../../../../core/blitz/applyPitchYawLimits';
import { modelTransformEvent } from '../../../../../core/blitzkit/modelTransform';
import { Pose, poseEvent } from '../../../../../core/blitzkit/pose';
import { useEquipment } from '../../../../../hooks/useEquipment';
import { forwardRef, Suspense, useImperativeHandle, useRef } from 'react';
import { useOnScreen } from '../../../../../hooks/useOnScreen';
import { useTankModelDefinition } from '../../../../../hooks/useTankModelDefinition';
import { Duel } from '../../../../../stores/duel';
import { TankopediaEphemeral } from '../../../../../stores/tankopediaEphemeral';
import { TankopediaPersistent } from '../../../../../stores/tankopediaPersistent';
import { TankopediaDisplay } from '../../../../../stores/tankopediaPersistent/constants';
@@ -40,16 +28,16 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
({ thicknessRange, naked }, ref) => {
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
const canvas = useRef<HTMLCanvasElement>(null);
const hasImprovedVerticalStabilizer = useEquipment(122);
const hasDownImprovedVerticalStabilizer = useEquipment(124);
const protagonistGun = Duel.use((state) => state.protagonist.gun);
const protagonistTurret = Duel.use((state) => state.protagonist.turret);
const mutateDuel = Duel.useMutation();
const tankModelDefinition = useTankModelDefinition();
const turretModelDefinition =
tankModelDefinition.turrets[protagonistTurret.id];
const gunModelDefinition =
turretModelDefinition.guns[protagonistGun.gun_type!.value.base.id];
// const hasImprovedVerticalStabilizer = useEquipment(122);
// const hasDownImprovedVerticalStabilizer = useEquipment(124);
// const protagonistGun = Duel.use((state) => state.protagonist.gun);
// const protagonistTurret = Duel.use((state) => state.protagonist.turret);
// const mutateDuel = Duel.useMutation();
// const tankModelDefinition = useTankModelDefinition();
// const turretModelDefinition =
// tankModelDefinition.turrets[protagonistTurret.id];
// const gunModelDefinition =
// turretModelDefinition.guns[protagonistGun.gun_type!.value.base.id];
const display = TankopediaEphemeral.use((state) => state.display);
const hideTankModelUnderArmor = TankopediaPersistent.use(
(state) => state.hideTankModelUnderArmor,
@@ -72,97 +60,97 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
window.removeEventListener('pointerup', handlePointerUp);
}
useEffect(() => {
function handlePoseEvent(pose: Pose) {
switch (pose) {
case Pose.HullDown: {
mutateDuel((draft) => {
const [pitch, yaw] = applyPitchYawLimits(
-Infinity,
0,
gunModelDefinition.pitch,
turretModelDefinition.yaw,
hasImprovedVerticalStabilizer,
hasDownImprovedVerticalStabilizer,
);
// useEffect(() => {
// function handlePoseEvent(pose: Pose) {
// switch (pose) {
// case Pose.HullDown: {
// mutateDuel((draft) => {
// const [pitch, yaw] = applyPitchYawLimits(
// -Infinity,
// 0,
// gunModelDefinition.pitch,
// turretModelDefinition.yaw,
// hasImprovedVerticalStabilizer,
// hasDownImprovedVerticalStabilizer,
// );
modelTransformEvent.emit({ pitch, yaw });
draft.protagonist.pitch = pitch;
draft.protagonist.yaw = yaw;
});
// modelTransformEvent.emit({ pitch, yaw });
// draft.protagonist.pitch = pitch;
// draft.protagonist.yaw = yaw;
// });
break;
}
// break;
// }
case Pose.FaceHug: {
mutateDuel((draft) => {
const [pitch, yaw] = applyPitchYawLimits(
Infinity,
0,
gunModelDefinition.pitch,
turretModelDefinition.yaw,
hasImprovedVerticalStabilizer,
);
// case Pose.FaceHug: {
// mutateDuel((draft) => {
// const [pitch, yaw] = applyPitchYawLimits(
// Infinity,
// 0,
// gunModelDefinition.pitch,
// turretModelDefinition.yaw,
// hasImprovedVerticalStabilizer,
// );
modelTransformEvent.emit({ pitch, yaw });
draft.protagonist.pitch = pitch;
draft.protagonist.yaw = yaw;
});
// modelTransformEvent.emit({ pitch, yaw });
// draft.protagonist.pitch = pitch;
// draft.protagonist.yaw = yaw;
// });
break;
}
// break;
// }
case Pose.Default:
mutateDuel((draft) => {
const [pitch, yaw] = applyPitchYawLimits(
0,
0,
gunModelDefinition.pitch,
turretModelDefinition.yaw,
hasImprovedVerticalStabilizer,
);
// case Pose.Default:
// mutateDuel((draft) => {
// const [pitch, yaw] = applyPitchYawLimits(
// 0,
// 0,
// gunModelDefinition.pitch,
// turretModelDefinition.yaw,
// hasImprovedVerticalStabilizer,
// );
modelTransformEvent.emit({ pitch, yaw });
draft.protagonist.pitch = pitch;
draft.protagonist.yaw = yaw;
});
// modelTransformEvent.emit({ pitch, yaw });
// draft.protagonist.pitch = pitch;
// draft.protagonist.yaw = yaw;
// });
break;
}
}
// break;
// }
// }
poseEvent.on(handlePoseEvent);
// poseEvent.on(handlePoseEvent);
return () => {
poseEvent.off(handlePoseEvent);
};
});
// return () => {
// poseEvent.off(handlePoseEvent);
// };
// });
useEffect(() => {
mutateDuel((draft) => {
[draft.protagonist.pitch, draft.protagonist.yaw] = applyPitchYawLimits(
draft.protagonist.pitch,
draft.protagonist.yaw,
gunModelDefinition.pitch,
turretModelDefinition.yaw,
hasImprovedVerticalStabilizer,
);
});
}, [protagonistGun, protagonistTurret]);
// useEffect(() => {
// mutateDuel((draft) => {
// [draft.protagonist.pitch, draft.protagonist.yaw] = applyPitchYawLimits(
// draft.protagonist.pitch,
// draft.protagonist.yaw,
// gunModelDefinition.pitch,
// turretModelDefinition.yaw,
// hasImprovedVerticalStabilizer,
// );
// });
// }, [protagonistGun, protagonistTurret]);
useEffect(() => {
if (display !== TankopediaDisplay.DynamicArmor) {
mutateTankopediaEphemeral((draft) => {
draft.shot = undefined;
});
}
// useEffect(() => {
// if (display !== TankopediaDisplay.DynamicArmor) {
// mutateTankopediaEphemeral((draft) => {
// draft.shot = undefined;
// });
// }
if (display !== TankopediaDisplay.StaticArmor) {
mutateTankopediaEphemeral((draft) => {
draft.highlightArmor = undefined;
});
}
}, [display]);
// if (display !== TankopediaDisplay.StaticArmor) {
// mutateTankopediaEphemeral((draft) => {
// draft.highlightArmor = undefined;
// });
// }
// }, [display]);
return (
<Canvas
@@ -1,5 +1,5 @@
import { Box, Flex, Heading, Text } from '@radix-ui/themes';
import { useRef } from 'react';
import { Suspense, useRef } from 'react';
import { NAVBAR_HEIGHT } from '../../../constants/navbar';
import { Var } from '../../../core/radix/var';
import { useFullScreen } from '../../../hooks/useFullScreen';
@@ -7,6 +7,8 @@ import { useLocale } from '../../../hooks/useLocale';
import { Duel } from '../../../stores/duel';
import { TankopediaEphemeral } from '../../../stores/tankopediaEphemeral';
import type { MaybeSkeletonComponentProps } from '../../../types/maybeSkeletonComponentProps';
import { Options } from './components/Options';
import { TankSandbox } from './components/TankSandbox';
import { TankSandboxLoader } from './components/TankSandboxLoader';
export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
@@ -137,12 +139,12 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
<Box width="100%" height="100%">
{skeleton && <TankSandboxLoader id={protagonist.id} />}
{/* <Suspense fallback={<TankSandboxLoader id={protagonist.id} />}>
<Suspense fallback={<TankSandboxLoader id={protagonist.id} />}>
<TankSandbox ref={canvas} thicknessRange={thicknessRange} />
</Suspense> */}
</Suspense>
</Box>
{/* <Options canvas={canvas} thicknessRange={thicknessRange} /> */}
<Options canvas={canvas} thicknessRange={thicknessRange} />
</Box>
</Box>
</Flex>
@@ -134,7 +134,7 @@ export const TankopediaEphemeral = createContextualStore((armor: TankArmor) => {
armor,
controlsEnabled: true,
xpMultiplier: 1,
display: TankopediaDisplay.Model,
display: TankopediaDisplay.StaticArmor,
})),
);
});