summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-11-04 16:03:34 +0100
committersalmonik <salmonik@tuta.io>2026-11-04 16:03:34 +0100
commit4171b26d1ce68d185f3d2611f5753f4288a92a69 (patch)
treeee271ec4b5330598830638f208eac96e8e79e12f
parentFix overlay-vanishing projections; ammo rule via held weapons (diff)
downloadhl2h-4171b26d1ce68d185f3d2611f5753f4288a92a69.tar.gz
hl2h-4171b26d1ce68d185f3d2611f5753f4288a92a69.zip
Clip transition and radius lines at the near plane
Transition-box edges and projectile radius rings were only drawn when both endpoints projected. An edge with one corner behind the camera vanished, so a half-visible box or ring showed nothing. Add a segment projector that clips at the near plane in view space, then clips the 2D result to the screen bounds, and use it for both.
-rw-r--r--CONTEXT.md7
-rw-r--r--src/esp.cpp102
2 files changed, 89 insertions, 20 deletions
diff --git a/CONTEXT.md b/CONTEXT.md
index 2cec0ad..acdab2f 100644
--- a/CONTEXT.md
+++ b/CONTEXT.md
@@ -271,7 +271,8 @@ vocabulary / decisions.
271 trigger whose target equals `g_map_prev` is marked `<- map` (the way back). 271 trigger whose target equals `g_map_prev` is marked `<- map` (the way back).
272- Scanning runs at 4 Hz in the ESP draw; the 8 world corners are projected and 272- Scanning runs at 4 Hz in the ESP draw; the 8 world corners are projected and
273 the 12 edges are drawn as a 3D wireframe, with a 10 px tag above the highest 273 the 12 edges are drawn as a 3D wireframe, with a 10 px tag above the highest
274 visible corner. 274 visible corner. Each edge is clipped at the camera near plane and then to the
275 screen, so a box straddling the camera still draws its visible portions.
275 276
276## Bones / ESP 277## Bones / ESP
277 278
@@ -644,7 +645,9 @@ vocabulary / decisions.
644 `m_DmgRadius` (`FIELD_FLOAT`) as fallback, then 300/200/150/300 for 645 `m_DmgRadius` (`FIELD_FLOAT`) as fallback, then 300/200/150/300 for
645 frag/RPG/AR2/bomb. 646 frag/RPG/AR2/bomb.
646- Draws a small marker and a `FRAG 1.2s`-style label; the optional radius ring 647- Draws a small marker and a `FRAG 1.2s`-style label; the optional radius ring
647 is a 24-segment horizontal circle projected at the projectile height. 648 is a 24-segment horizontal circle projected at the projectile height, with
649 every segment near-plane/screen clipped independently, so it still draws
650 when the grenade itself is off-screen or behind the camera.
648 Toggles: Fuse timer, Blast radius. 651 Toggles: Fuse timer, Blast radius.
649 652
650## Roadmap / TODO 653## Roadmap / TODO
diff --git a/src/esp.cpp b/src/esp.cpp
index 447bee7..715dbc8 100644
--- a/src/esp.cpp
+++ b/src/esp.cpp
@@ -1812,6 +1812,76 @@ static void transitions_scan() {
1812 } 1812 }
1813} 1813}
1814 1814
1815// Clip a 2D segment to the screen (with a small margin). Liang-Barsky.
1816static bool clip_line2d(float &x0, float &y0, float &x1, float &y1,
1817 float xmin, float ymin, float xmax, float ymax) {
1818 float dx = x1 - x0, dy = y1 - y0;
1819 float p[4] = {-dx, dx, -dy, dy};
1820 float q[4] = {x0 - xmin, xmax - x0, y0 - ymin, ymax - y0};
1821 float u1 = 0.0f, u2 = 1.0f;
1822 for (int i = 0; i < 4; i++) {
1823 if (fabsf(p[i]) < 1e-6f) {
1824 if (q[i] < 0.0f)
1825 return false;
1826 continue;
1827 }
1828 float r = q[i] / p[i];
1829 if (p[i] < 0.0f) {
1830 if (r > u2) return false;
1831 if (r > u1) u1 = r;
1832 } else {
1833 if (r < u1) return false;
1834 if (r < u2) u2 = r;
1835 }
1836 }
1837 float nx0 = x0 + u1 * dx, ny0 = y0 + u1 * dy;
1838 float nx1 = x0 + u2 * dx, ny1 = y0 + u2 * dy;
1839 x0 = nx0; y0 = ny0; x1 = nx1; y1 = ny1;
1840 return true;
1841}
1842
1843// Project a world-space segment, clipping it at the near plane first. Without
1844// this an edge with one corner behind the camera was dropped entirely, so a
1845// partially visible transition box or radius ring disappeared.
1846static bool project_segment(const Vector &a, const Vector &b, int &x0, int &y0,
1847 int &x1, int &y1) {
1848 if (!g_scr_w || !g_scr_h)
1849 return false;
1850 float pitch = g_cam_view.x * PI_F / 180.0f;
1851 float yaw = g_cam_view.y * PI_F / 180.0f;
1852 float cp = cosf(pitch), sp = sinf(pitch);
1853 float cy = cosf(yaw), sy = sinf(yaw);
1854 Vector fwd{cp * cy, cp * sy, -sp};
1855 Vector da{a.x - g_cam_eye.x, a.y - g_cam_eye.y, a.z - g_cam_eye.z};
1856 Vector db{b.x - g_cam_eye.x, b.y - g_cam_eye.y, b.z - g_cam_eye.z};
1857 const float nearz = 4.0f;
1858 float za = da.x * fwd.x + da.y * fwd.y + da.z * fwd.z;
1859 float zb = db.x * fwd.x + db.y * fwd.y + db.z * fwd.z;
1860 if (za <= nearz && zb <= nearz)
1861 return false;
1862 Vector ca = a, cb = b;
1863 if (za <= nearz) {
1864 float t = (nearz - za) / (zb - za);
1865 ca = Vector{a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t,
1866 a.z + (b.z - a.z) * t};
1867 } else if (zb <= nearz) {
1868 float t = (nearz - zb) / (za - zb);
1869 cb = Vector{b.x + (a.x - b.x) * t, b.y + (a.y - b.y) * t,
1870 b.z + (a.z - b.z) * t};
1871 }
1872 float ax, ay, bx, by;
1873 if (!world_to_screen(ca, ax, ay) || !world_to_screen(cb, bx, by))
1874 return false;
1875 if (!clip_line2d(ax, ay, bx, by, -8.0f, -8.0f, (float)g_scr_w + 8.0f,
1876 (float)g_scr_h + 8.0f))
1877 return false;
1878 x0 = (int)ax;
1879 y0 = (int)ay;
1880 x1 = (int)bx;
1881 y1 = (int)by;
1882 return true;
1883}
1884
1815static void draw_transitions() { 1885static void draw_transitions() {
1816 if (!g_surface || !g_ntrans) 1886 if (!g_surface || !g_ntrans)
1817 return; 1887 return;
@@ -1844,8 +1914,9 @@ static void draw_transitions() {
1844 surf_color(r, g, bl, 210); 1914 surf_color(r, g, bl, 210);
1845 for (int e = 0; e < 12; e++) { 1915 for (int e = 0; e < 12; e++) {
1846 int a = edges[e][0], c = edges[e][1]; 1916 int a = edges[e][0], c = edges[e][1];
1847 if (ok[a] && ok[c]) 1917 int lx, ly, hx, hy;
1848 surf_line((int)xs[a], (int)ys[a], (int)xs[c], (int)ys[c]); 1918 if (project_segment(corners[a], corners[c], lx, ly, hx, hy))
1919 surf_line(lx, ly, hx, hy);
1849 } 1920 }
1850 // Tag above the highest projected corner. 1921 // Tag above the highest projected corner.
1851 float topx = 0.0f, topy = 1e9f; 1922 float topx = 0.0f, topy = 1e9f;
@@ -2774,26 +2845,21 @@ void esp_draw_projectiles() {
2774 draw_text_small_col(tx, ty, text, r, g, b); 2845 draw_text_small_col(tx, ty, text, r, g, b);
2775 } 2846 }
2776 // The ring is useful even when the grenade itself is behind the camera or 2847 // The ring is useful even when the grenade itself is behind the camera or
2777 // off-screen, so it is not gated on center_ok. 2848 // off-screen, so it is not gated on center_ok and each segment is clipped
2849 // at the near plane independently.
2778 if (g_proj_radius && p.radius > 1.0f) { 2850 if (g_proj_radius && p.radius > 1.0f) {
2779 const int SEG = 24; 2851 const int SEG = 24;
2780 float lx = 0.0f, ly = 0.0f; 2852 Vector ring[SEG + 1];
2781 bool have = false;
2782 surf_color(r, g, b, 150);
2783 for (int k = 0; k <= SEG; k++) { 2853 for (int k = 0; k <= SEG; k++) {
2784 float a = (float)k * 2.0f * PI_F / (float)SEG; 2854 float a = (float)k * 2.0f * PI_F / (float)SEG;
2785 Vector w{p.origin.x + cosf(a) * p.radius, 2855 ring[k] = Vector{p.origin.x + cosf(a) * p.radius,
2786 p.origin.y + sinf(a) * p.radius, p.origin.z}; 2856 p.origin.y + sinf(a) * p.radius, p.origin.z};
2787 float rx, ry; 2857 }
2788 if (!world_to_screen(w, rx, ry)) { 2858 surf_color(r, g, b, 150);
2789 have = false; 2859 for (int k = 0; k < SEG; k++) {
2790 continue; 2860 int x0, y0, x1, y1;
2791 } 2861 if (project_segment(ring[k], ring[k + 1], x0, y0, x1, y1))
2792 if (have) 2862 surf_line(x0, y0, x1, y1);
2793 surf_line((int)lx, (int)ly, (int)rx, (int)ry);
2794 lx = rx;
2795 ly = ry;
2796 have = true;
2797 } 2863 }
2798 } 2864 }
2799 } 2865 }