summaryrefslogtreecommitdiffstats
path: root/src/bones.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/bones.cpp')
-rw-r--r--src/bones.cpp288
1 files changed, 288 insertions, 0 deletions
diff --git a/src/bones.cpp b/src/bones.cpp
new file mode 100644
index 0000000..2ff282b
--- /dev/null
+++ b/src/bones.cpp
@@ -0,0 +1,288 @@
1// bones.cpp — client-side studio bone/hitbox access.
2//
3// C_BaseAnimating implements IClientRenderable::SetupBones(), which fills an
4// array of world-space bone matrices. The studio header (from
5// IVModelInfoClient::GetStudiomodel) gives bone names/parents and hitboxes.
6// Everything here is client-side; no server access is involved.
7
8#include "hl2h.h"
9#include <cstdint>
10#include <cstring>
11#include <ctime>
12#include <strings.h>
13
14namespace {
15
16constexpr int MAX_BONES = 256;
17constexpr int BONE_USED_BY_ANYTHING = 0x0007FF00;
18constexpr uint32_t STUDIO_ID = 0x54534449; // ('T'<<24)+('S'<<16)+('D'<<8)+'I'
19
20struct Matrix3x4 {
21 float m[3][4];
22};
23
24// Partial mirrors of the Source 2013 studio structures (32-bit). Only the
25// fields we use are named; layout must still match exactly.
26struct StudioHdr {
27 int id;
28 int version;
29 int checksum;
30 char name[64];
31 int length;
32 Vector eyeposition;
33 Vector illumposition;
34 Vector hull_min;
35 Vector hull_max;
36 Vector view_bbmin;
37 Vector view_bbmax;
38 int flags;
39 int numbones;
40 int boneindex;
41 int numbonecontrollers;
42 int bonecontrollerindex;
43 int numhitboxsets;
44 int hitboxsetindex;
45};
46
47struct StudioBone {
48 int sznameindex;
49 int parent;
50 int bonecontroller[6];
51 Vector pos;
52 float quat[4];
53 Vector rot;
54 Vector posscale;
55 Vector rotscale;
56 float poseToBone[12];
57 float qAlignment[4];
58 int flags;
59 int proctype;
60 int procindex;
61 int physicsbone;
62 int surfacepropidx;
63 int contents;
64 int unused[8];
65};
66
67struct HitboxSet {
68 int sznameindex;
69 int numhitboxes;
70 int hitboxindex;
71};
72
73struct Hitbox {
74 int bone;
75 int group;
76 Vector bbmin;
77 Vector bbmax;
78 int szhitboxnameindex;
79 int unused[8];
80};
81
82static_assert(sizeof(Vector) == 12, "Vector layout");
83static_assert(sizeof(Matrix3x4) == 48, "matrix3x4 layout");
84static_assert(sizeof(StudioHdr) == 0xB4, "studiohdr prefix layout");
85static_assert(sizeof(StudioBone) == 0xD8, "mstudiobone_t layout");
86static_assert(sizeof(HitboxSet) == 12, "mstudiohitboxset_t layout");
87static_assert(sizeof(Hitbox) == 0x44, "mstudiobbox_t layout");
88
89struct BoneEntry {
90 int idx;
91 void *ent;
92 double stamp;
93 bool valid;
94 const StudioHdr *hdr;
95 int count;
96 Matrix3x4 mats[MAX_BONES];
97};
98
99static BoneEntry g_entries[64];
100static int g_next = 0;
101static int g_logged = 0;
102static bool g_renderable_warned = false;
103
104static double bone_now() {
105 timespec ts;
106 clock_gettime(CLOCK_MONOTONIC, &ts);
107 return (double)ts.tv_sec + (double)ts.tv_nsec * 1e-9;
108}
109
110static BoneEntry *find_entry(int idx) {
111 for (int i = 0; i < 64; i++)
112 if (g_entries[i].ent && g_entries[i].idx == idx)
113 return &g_entries[i];
114 return nullptr;
115}
116
117static BoneEntry *alloc_entry(int idx, void *ent) {
118 BoneEntry *e = &g_entries[g_next++ % 64];
119 memset(e, 0, sizeof(*e));
120 e->idx = idx;
121 e->ent = ent;
122 return e;
123}
124
125static const StudioBone *bone_at(const StudioHdr *h, int i) {
126 return (const StudioBone *)((const uint8_t *)h + h->boneindex) + i;
127}
128
129static const char *bone_name_raw(const StudioHdr *h, int i) {
130 if (!h || i < 0 || i >= h->numbones)
131 return nullptr;
132 const StudioBone *b = bone_at(h, i);
133 if (b->sznameindex <= 0 || b->sznameindex > 0x10000)
134 return nullptr;
135 const char *n = (const char *)b + b->sznameindex;
136 return n[0] ? n : nullptr;
137}
138
139} // namespace
140
141bool bones_update(int idx, void *ent) {
142 if (!ent || !g_iface.modelinfo || !g_iface.engine || g_net.modelindex < 0)
143 return false;
144
145 BoneEntry *e = find_entry(idx);
146 double now = bone_now();
147 if (e && e->ent == ent && e->valid && now - e->stamp < 0.03)
148 return true;
149 if (!e)
150 e = alloc_entry(idx, ent);
151 if (e->ent != ent) {
152 e->ent = ent;
153 e->valid = false;
154 }
155
156 int modelindex = *(int *)((uintptr_t)ent + g_net.modelindex);
157 if (modelindex <= 0) {
158 e->valid = false;
159 return false;
160 }
161
162 using GetModelFn = const void *(*)(void *);
163 using GetModelIdxFn = const void *(*)(void *, int);
164 using GetStudioFn = const StudioHdr *(*)(void *, const void *);
165 using SetupFn = bool (*)(void *, Matrix3x4 *, int, int, float);
166 using GetRenderableFn = void *(*)(void *);
167 using TimeFn = float (*)(void *);
168
169 const void *expected =
170 vfunc<GetModelIdxFn>(g_iface.modelinfo, 2)(g_iface.modelinfo, modelindex);
171 if (!expected)
172 return false;
173
174 // IClientUnknown::GetClientRenderable = primary vtable slot 6.
175 void *r = vfunc<GetRenderableFn>(ent, 6)(ent);
176 if (!r)
177 return false;
178 // Validate the interface before using SetupBones on it.
179 const void *rmodel = vfunc<GetModelFn>(r, 9)(r);
180 if (rmodel != expected) {
181 if (!g_renderable_warned) {
182 g_renderable_warned = true;
183 logf("bones: renderable/model mismatch (renderable=%p model=%p "
184 "expected=%p)",
185 r, rmodel, expected);
186 }
187 e->valid = false;
188 return false;
189 }
190
191 const StudioHdr *hdr =
192 vfunc<GetStudioFn>(g_iface.modelinfo, 29)(g_iface.modelinfo, expected);
193 if (!hdr || hdr->id != STUDIO_ID || hdr->numbones <= 0 ||
194 hdr->numbones > MAX_BONES || hdr->boneindex <= 0) {
195 e->valid = false;
196 return false;
197 }
198
199 float curtime = vfunc<TimeFn>(g_iface.engine, 14)(g_iface.engine);
200 // IClientRenderable::SetupBones = slot 16.
201 if (!vfunc<SetupFn>(r, 16)(r, e->mats, MAX_BONES, BONE_USED_BY_ANYTHING,
202 curtime)) {
203 e->valid = false;
204 return false;
205 }
206
207 e->hdr = hdr;
208 e->count = hdr->numbones;
209 e->valid = true;
210 e->stamp = now;
211 if (g_logged < 4) {
212 g_logged++;
213 logf("bones: idx=%d count=%d bone0=%s", idx, e->count,
214 bone_name_raw(hdr, 0) ? bone_name_raw(hdr, 0) : "?");
215 }
216 return true;
217}
218
219int bones_count(int idx) {
220 BoneEntry *e = find_entry(idx);
221 return (e && e->valid) ? e->count : 0;
222}
223
224int bone_find(int idx, const char *name) {
225 BoneEntry *e = find_entry(idx);
226 if (!e || !e->valid || !name)
227 return -1;
228 for (int i = 0; i < e->count; i++) {
229 const char *n = bone_name_raw(e->hdr, i);
230 if (n && strcasecmp(n, name) == 0)
231 return i;
232 }
233 return -1;
234}
235
236int bone_parent(int idx, int bone) {
237 BoneEntry *e = find_entry(idx);
238 if (!e || !e->valid || bone < 0 || bone >= e->count)
239 return -1;
240 return bone_at(e->hdr, bone)->parent;
241}
242
243bool bone_pos(int idx, int bone, Vector &out) {
244 BoneEntry *e = find_entry(idx);
245 if (!e || !e->valid || bone < 0 || bone >= e->count)
246 return false;
247 const Matrix3x4 &m = e->mats[bone];
248 out = {m.m[0][3], m.m[1][3], m.m[2][3]};
249 return true;
250}
251
252bool hitbox_center(int idx, const char *name, Vector &out) {
253 BoneEntry *e = find_entry(idx);
254 if (!e || !e->valid || !name || !e->hdr)
255 return false;
256 const StudioHdr *h = e->hdr;
257 if (h->numhitboxsets <= 0 || h->hitboxsetindex <= 0)
258 return false;
259 const HitboxSet *set =
260 (const HitboxSet *)((const uint8_t *)h + h->hitboxsetindex);
261 if (set->numhitboxes <= 0 || set->numhitboxes > 512 ||
262 set->hitboxindex <= 0)
263 return false;
264 const Hitbox *boxes =
265 (const Hitbox *)((const uint8_t *)set + set->hitboxindex);
266 for (int i = 0; i < set->numhitboxes; i++) {
267 const Hitbox &b = boxes[i];
268 if (b.szhitboxnameindex <= 0)
269 continue;
270 const char *hn = (const char *)&b + b.szhitboxnameindex;
271 if (strcasecmp(hn, name) != 0)
272 continue;
273 if (b.bone < 0 || b.bone >= e->count)
274 return false;
275 Vector local{(b.bbmin.x + b.bbmax.x) * 0.5f,
276 (b.bbmin.y + b.bbmax.y) * 0.5f,
277 (b.bbmin.z + b.bbmax.z) * 0.5f};
278 const Matrix3x4 &m = e->mats[b.bone];
279 out.x = local.x * m.m[0][0] + local.y * m.m[0][1] + local.z * m.m[0][2] +
280 m.m[0][3];
281 out.y = local.x * m.m[1][0] + local.y * m.m[1][1] + local.z * m.m[1][2] +
282 m.m[1][3];
283 out.z = local.x * m.m[2][0] + local.y * m.m[2][1] + local.z * m.m[2][2] +
284 m.m[2][3];
285 return true;
286 }
287 return false;
288}