summaryrefslogtreecommitdiffstats
path: root/src/vischeck.cpp
blob: 92d194bc8ae8aa729cc4beace84e453e42646b91 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
// vischeck.cpp — world-only line-of-sight test via EngineTraceClient003.
//
// IEngineTrace::TraceRay (vtable slot 4) is given a ray from the eye to the
// aim point and a TRACE_WORLD_ONLY filter. MASK_VISIBLE ignores glass/windows
// (you can see through them) while still hitting opaque world geometry.
// Per the SDK, TRACE_WORLD_ONLY does not test static props; that is the
// deliberate trade-off for this build.

#include "hl2h.h"
#include <cstddef>
#include <cstdint>

namespace {

struct alignas(16) VectorAlignedCompat {
  float x, y, z, w;
};

// Ray_t: four VectorAligned (16 bytes each) followed by two bools.
struct alignas(16) RayCompat {
  VectorAlignedCompat start;
  VectorAlignedCompat delta;
  VectorAlignedCompat startOffset;
  VectorAlignedCompat extents;
  bool isRay;
  bool isSwept;
};

static_assert(sizeof(VectorAlignedCompat) == 16, "VectorAligned layout");
static_assert(offsetof(RayCompat, delta) == 16, "Ray_t delta offset");
static_assert(offsetof(RayCompat, isRay) == 64, "Ray_t isRay offset");

// trace_t / CBaseTrace prefix. cplane_t is Vector + float + type + signbits
// + 2 pad bytes, so fraction lands at 0x2C and startsolid at 0x37.
struct TraceCompat {
  Vector startpos;
  Vector endpos;
  Vector normal;
  float dist;
  uint8_t type;
  uint8_t signbits;
  uint8_t pad[2];
  float fraction;
  int contents;
  uint16_t dispFlags;
  bool allsolid;
  bool startsolid;
  uint8_t rest[128]; // CGameTrace tail (surface, ent, hitbox, ...)
};

static_assert(offsetof(TraceCompat, fraction) == 44, "trace_t fraction");
static_assert(offsetof(TraceCompat, startsolid) == 55, "trace_t startsolid");

// ITraceFilter has no virtual destructor: vtable[0] = ShouldHitEntity,
// vtable[1] = GetTraceType.
struct ITraceFilterCompat {
  virtual bool ShouldHitEntity(void *pEntity, int contentsMask) = 0;
  virtual int GetTraceType() const = 0;
};

struct WorldOnlyFilter : ITraceFilterCompat {
  bool ShouldHitEntity(void *, int) override { return false; }
  int GetTraceType() const override { return 1; } // TRACE_WORLD_ONLY
};

// MASK_VISIBLE = CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_OPAQUE |
//                CONTENTS_IGNORE_NODRAW_OPAQUE
constexpr unsigned MASK_VISIBLE = 0x1u | 0x4000u | 0x80u | 0x2000u;

} // namespace

bool is_visible(const Vector &eye, const Vector &point) {
  static bool warned = false;
  if (!g_iface.enginetrace) {
    if (!warned) {
      warned = true;
      logf("vischeck: EngineTraceClient003 unavailable; failing open");
    }
    return true;
  }

  float dx = point.x - eye.x, dy = point.y - eye.y, dz = point.z - eye.z;
  RayCompat ray{};
  ray.start = {eye.x, eye.y, eye.z, 0.0f};
  ray.delta = {dx, dy, dz, 0.0f};
  ray.startOffset = {0.0f, 0.0f, 0.0f, 0.0f};
  ray.extents = {0.0f, 0.0f, 0.0f, 0.0f};
  ray.isRay = true;
  ray.isSwept = (dx * dx + dy * dy + dz * dz) > 0.0f;

  TraceCompat tr{};
  WorldOnlyFilter filter;
  using TraceRayFn = void (*)(void *, const RayCompat *, unsigned, void *,
                              TraceCompat *);
  vfunc<TraceRayFn>(g_iface.enginetrace, 4)(g_iface.enginetrace, &ray,
                                            MASK_VISIBLE, &filter, &tr);
  return tr.fraction > 0.97f && !tr.startsolid;
}