summaryrefslogtreecommitdiffstats
path: root/src/vischeck.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/vischeck.cpp')
-rw-r--r--src/vischeck.cpp98
1 files changed, 98 insertions, 0 deletions
diff --git a/src/vischeck.cpp b/src/vischeck.cpp
new file mode 100644
index 0000000..92d194b
--- /dev/null
+++ b/src/vischeck.cpp
@@ -0,0 +1,98 @@
1// vischeck.cpp — world-only line-of-sight test via EngineTraceClient003.
2//
3// IEngineTrace::TraceRay (vtable slot 4) is given a ray from the eye to the
4// aim point and a TRACE_WORLD_ONLY filter. MASK_VISIBLE ignores glass/windows
5// (you can see through them) while still hitting opaque world geometry.
6// Per the SDK, TRACE_WORLD_ONLY does not test static props; that is the
7// deliberate trade-off for this build.
8
9#include "hl2h.h"
10#include <cstddef>
11#include <cstdint>
12
13namespace {
14
15struct alignas(16) VectorAlignedCompat {
16 float x, y, z, w;
17};
18
19// Ray_t: four VectorAligned (16 bytes each) followed by two bools.
20struct alignas(16) RayCompat {
21 VectorAlignedCompat start;
22 VectorAlignedCompat delta;
23 VectorAlignedCompat startOffset;
24 VectorAlignedCompat extents;
25 bool isRay;
26 bool isSwept;
27};
28
29static_assert(sizeof(VectorAlignedCompat) == 16, "VectorAligned layout");
30static_assert(offsetof(RayCompat, delta) == 16, "Ray_t delta offset");
31static_assert(offsetof(RayCompat, isRay) == 64, "Ray_t isRay offset");
32
33// trace_t / CBaseTrace prefix. cplane_t is Vector + float + type + signbits
34// + 2 pad bytes, so fraction lands at 0x2C and startsolid at 0x37.
35struct TraceCompat {
36 Vector startpos;
37 Vector endpos;
38 Vector normal;
39 float dist;
40 uint8_t type;
41 uint8_t signbits;
42 uint8_t pad[2];
43 float fraction;
44 int contents;
45 uint16_t dispFlags;
46 bool allsolid;
47 bool startsolid;
48 uint8_t rest[128]; // CGameTrace tail (surface, ent, hitbox, ...)
49};
50
51static_assert(offsetof(TraceCompat, fraction) == 44, "trace_t fraction");
52static_assert(offsetof(TraceCompat, startsolid) == 55, "trace_t startsolid");
53
54// ITraceFilter has no virtual destructor: vtable[0] = ShouldHitEntity,
55// vtable[1] = GetTraceType.
56struct ITraceFilterCompat {
57 virtual bool ShouldHitEntity(void *pEntity, int contentsMask) = 0;
58 virtual int GetTraceType() const = 0;
59};
60
61struct WorldOnlyFilter : ITraceFilterCompat {
62 bool ShouldHitEntity(void *, int) override { return false; }
63 int GetTraceType() const override { return 1; } // TRACE_WORLD_ONLY
64};
65
66// MASK_VISIBLE = CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_OPAQUE |
67// CONTENTS_IGNORE_NODRAW_OPAQUE
68constexpr unsigned MASK_VISIBLE = 0x1u | 0x4000u | 0x80u | 0x2000u;
69
70} // namespace
71
72bool is_visible(const Vector &eye, const Vector &point) {
73 static bool warned = false;
74 if (!g_iface.enginetrace) {
75 if (!warned) {
76 warned = true;
77 logf("vischeck: EngineTraceClient003 unavailable; failing open");
78 }
79 return true;
80 }
81
82 float dx = point.x - eye.x, dy = point.y - eye.y, dz = point.z - eye.z;
83 RayCompat ray{};
84 ray.start = {eye.x, eye.y, eye.z, 0.0f};
85 ray.delta = {dx, dy, dz, 0.0f};
86 ray.startOffset = {0.0f, 0.0f, 0.0f, 0.0f};
87 ray.extents = {0.0f, 0.0f, 0.0f, 0.0f};
88 ray.isRay = true;
89 ray.isSwept = (dx * dx + dy * dy + dz * dz) > 0.0f;
90
91 TraceCompat tr{};
92 WorldOnlyFilter filter;
93 using TraceRayFn = void (*)(void *, const RayCompat *, unsigned, void *,
94 TraceCompat *);
95 vfunc<TraceRayFn>(g_iface.enginetrace, 4)(g_iface.enginetrace, &ray,
96 MASK_VISIBLE, &filter, &tr);
97 return tr.fraction > 0.97f && !tr.startsolid;
98}