From 42e860b171b53e827e49bf3893bef9ce9780c640 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com>
Date: Fri, 8 Aug 2025 12:00:33 -0500
Subject: [PATCH] feat: start of gun depression visualzier
---
.../Characteristics/components/Firepower.tsx | 2 +
.../GunFlexibilityVisualizer/index.tsx | 94 +++++++++++++++++++
2 files changed, 96 insertions(+)
create mode 100644 packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/GunFlexibilityVisualizer/index.tsx
diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Firepower.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Firepower.tsx
index 350a28177..f8e6de584 100644
--- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Firepower.tsx
+++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Firepower.tsx
@@ -22,6 +22,7 @@ import { useLocale } from '../../../../../../hooks/useLocale';
import { useTankModelDefinition } from '../../../../../../hooks/useTankModelDefinition';
import { Duel } from '../../../../../../stores/duel';
import { AssaultRangesVisualizer } from './AssaultRangesVisualizer';
+import { GunFlexibilityVisualizer } from './GunFlexibilityVisualizer';
import { Info } from './Info';
import { InfoWithDelta } from './InfoWithDelta';
import { ReloadVisualizer } from './ReloadVisualizer';
@@ -404,6 +405,7 @@ export function Firepower({ stats }: StatsAcceptorProps) {
/>
>
)}
+
);
}
diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/GunFlexibilityVisualizer/index.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/GunFlexibilityVisualizer/index.tsx
new file mode 100644
index 000000000..2b52173b0
--- /dev/null
+++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/GunFlexibilityVisualizer/index.tsx
@@ -0,0 +1,94 @@
+import { Card } from '@radix-ui/themes';
+import { awaitableModelDefinitions } from '../../../../../../../core/awaitables/modelDefinitions';
+import { Duel } from '../../../../../../../stores/duel';
+
+const MAX_MAG = 20;
+
+const modelDefinition = await awaitableModelDefinitions;
+
+function mag(x: number) {
+ return (x / MAX_MAG + 1) / 2;
+}
+
+function c(thetaDeg: number, m = 1) {
+ const theta = thetaDeg * (Math.PI / 180);
+ return `${m * Math.cos(theta)} ${-m * Math.sin(theta)}`;
+}
+
+export function GunFlexibilityVisualizer() {
+ const tank = Duel.use((state) => state.protagonist.tank);
+ const turret = Duel.use((state) => state.protagonist.turret);
+ const gun = Duel.use((state) => state.protagonist.gun);
+
+ const tankModel = modelDefinition.models[tank.id];
+ const turretModel = tankModel.turrets[turret.id];
+ const gunModel = turretModel.guns[gun.gun_type!.value.base.id];
+
+ const p = gunModel.pitch;
+ const f = p.front;
+ const b = p.back;
+
+ let d = '';
+
+ d += `M 0 0`;
+ const ms = mag(p.max);
+ if (b) {
+ d += `L ${c(-90 + b.range / 2, ms)}`;
+ } else {
+ d += `L ${c(-90 / 2, ms)}`;
+ }
+ if (f) {
+ d += `A ${ms} ${ms} 0 0 0 ${c(90 + f.range / 2, ms)}`;
+ } else {
+ d += `A ${ms} ${ms} 0 0 0 ${c(90 / 2, ms)}`;
+ }
+
+ d += `M 0 0`;
+ if (f) {
+ d += `L ${c(90 + f.range / 2, ms)}`;
+ } else {
+ d += `L ${c(90 / 2, ms)}`;
+ }
+ if (b) {
+ d += `A ${ms} ${ms} 0 0 0 ${c(-90 - b.range / 2, ms)}`;
+ } else {
+ d += `A ${ms} ${ms} 0 0 0 ${c(-90 / 2, ms)}`;
+ }
+
+ if (f) {
+ const m = mag(f.max);
+ d += `M 0 0`;
+ d += `L ${c(90 - f.range / 2, m)}`;
+ d += `A ${m} ${m} 0 0 0 ${c(90 + f.range / 2, m)}`;
+ d += `Z`;
+ }
+
+ if (b) {
+ const m = mag(b.max);
+ d += `M 0 0`;
+ d += `L ${c(-90 - b.range / 2, m)}`;
+ d += `A ${m} ${m} 0 0 0 ${c(-90 + b.range / 2, m)}`;
+ d += `Z`;
+ }
+
+ return (
+
+
+
+ );
+}