diff options
Diffstat (limited to 'src/studio.cpp')
| -rw-r--r-- | src/studio.cpp | 161 |
1 files changed, 153 insertions, 8 deletions
diff --git a/src/studio.cpp b/src/studio.cpp index cf145d1..3ee6cc5 100644 --- a/src/studio.cpp +++ b/src/studio.cpp | |||
| @@ -19,7 +19,10 @@ | |||
| 19 | #include <cstdio> | 19 | #include <cstdio> |
| 20 | #include <cstring> | 20 | #include <cstring> |
| 21 | #include <dlfcn.h> | 21 | #include <dlfcn.h> |
| 22 | #include <string> | ||
| 23 | #include <sys/stat.h> | ||
| 22 | #include <time.h> | 24 | #include <time.h> |
| 25 | #include <unistd.h> | ||
| 23 | 26 | ||
| 24 | namespace { | 27 | namespace { |
| 25 | 28 | ||
| @@ -197,7 +200,7 @@ using ForceOverrideFn = void (*)(void *, void *, int); | |||
| 197 | void *g_modelrender = nullptr; | 200 | void *g_modelrender = nullptr; |
| 198 | ModelExecFn g_orig_model_exec = nullptr; | 201 | ModelExecFn g_orig_model_exec = nullptr; |
| 199 | ForceOverrideFn g_force_override = nullptr; | 202 | ForceOverrideFn g_force_override = nullptr; |
| 200 | void *g_style_mats[7] = {}; | 203 | void *g_style_mats[8] = {}; |
| 201 | 204 | ||
| 202 | const char *chams_material_name(int idx) { | 205 | const char *chams_material_name(int idx) { |
| 203 | switch (idx) { | 206 | switch (idx) { |
| @@ -257,6 +260,12 @@ void chams_apply(void *mat, bool ignorez, int style, bool force_white, | |||
| 257 | float b = force_white ? 1.0f : (float)rgb[2] / 255.0f; | 260 | float b = force_white ? 1.0f : (float)rgb[2] / 255.0f; |
| 258 | chams_color(mat, r, g, b); | 261 | chams_color(mat, r, g, b); |
| 259 | chams_alpha(mat, (float)rgb[3] / 255.0f); | 262 | chams_alpha(mat, (float)rgb[3] / 255.0f); |
| 263 | { | ||
| 264 | // Entity-style alpha fade; UnlitGeneric takes translucency from the base | ||
| 265 | // texture/vertex alpha, so $alpha alone is not enough. | ||
| 266 | using AlphaFn = void (*)(void *, float); | ||
| 267 | vfunc<AlphaFn>(mat, 27)(mat, (float)rgb[3] / 255.0f); | ||
| 268 | } | ||
| 260 | // 0 Flat, 1 Wireframe, 2 Additive, 3 Solid, 4 White, 5 Fullbright | 269 | // 0 Flat, 1 Wireframe, 2 Additive, 3 Solid, 4 White, 5 Fullbright |
| 261 | // (additive white glow), 6 Glow outline (solid base; the wireframe overlay | 270 | // (additive white glow), 6 Glow outline (solid base; the wireframe overlay |
| 262 | // is a second draw with ZNEARER so its contour is not hidden by the base). | 271 | // is a second draw with ZNEARER so its contour is not hidden by the base). |
| @@ -270,8 +279,7 @@ void chams_apply(void *mat, bool ignorez, int style, bool force_white, | |||
| 270 | vfunc<FlagFn>(mat, 29)(mat, 1 << 6 /* SELFILLUM */, style == 5); | 279 | vfunc<FlagFn>(mat, 29)(mat, 1 << 6 /* SELFILLUM */, style == 5); |
| 271 | } | 280 | } |
| 272 | 281 | ||
| 273 | void chams_reset(void *mat) { | 282 | void chams_reset(void *mat) { using FlagFn = void (*)(void *, int, bool); |
| 274 | using FlagFn = void (*)(void *, int, bool); | ||
| 275 | vfunc<FlagFn>(mat, 29)(mat, 1 << 15, false); | 283 | vfunc<FlagFn>(mat, 29)(mat, 1 << 15, false); |
| 276 | vfunc<FlagFn>(mat, 29)(mat, 1 << 10, false); | 284 | vfunc<FlagFn>(mat, 29)(mat, 1 << 10, false); |
| 277 | vfunc<FlagFn>(mat, 29)(mat, 1 << 28, false); | 285 | vfunc<FlagFn>(mat, 29)(mat, 1 << 28, false); |
| @@ -282,6 +290,22 @@ void chams_reset(void *mat) { | |||
| 282 | chams_alpha(mat, 1.0f); | 290 | chams_alpha(mat, 1.0f); |
| 283 | } | 291 | } |
| 284 | 292 | ||
| 293 | // Model draws take their final alpha from IVRenderView::SetBlend; material | ||
| 294 | // $alpha alone is ignored by UnlitGeneric. Save/restore it around our passes. | ||
| 295 | float chams_get_blend() { | ||
| 296 | if (!g_iface.renderview) | ||
| 297 | return 1.0f; | ||
| 298 | using GetFn = float (*)(void *); | ||
| 299 | return vfunc<GetFn>(g_iface.renderview, 5)(g_iface.renderview); | ||
| 300 | } | ||
| 301 | |||
| 302 | void chams_set_blend(float a) { | ||
| 303 | if (!g_iface.renderview) | ||
| 304 | return; | ||
| 305 | using SetFn = void (*)(void *, float); | ||
| 306 | vfunc<SetFn>(g_iface.renderview, 4)(g_iface.renderview, a); | ||
| 307 | } | ||
| 308 | |||
| 285 | void hkdModelExecute(void *self, const void *state, const void *info, | 309 | void hkdModelExecute(void *self, const void *state, const void *info, |
| 286 | void *bone) { | 310 | void *bone) { |
| 287 | if (!g_orig_model_exec) | 311 | if (!g_orig_model_exec) |
| @@ -304,21 +328,35 @@ void hkdModelExecute(void *self, const void *state, const void *info, | |||
| 304 | if (style < 0) style = 0; | 328 | if (style < 0) style = 0; |
| 305 | if (style > 6) style = 6; | 329 | if (style > 6) style = 6; |
| 306 | void *mat = style_material(style); | 330 | void *mat = style_material(style); |
| 331 | bool alpha = rgb[3] < 255; | ||
| 332 | if (alpha && g_style_mats[7] && | ||
| 333 | (style == 0 || style == 3 || style == 4 || style == 6)) | ||
| 334 | mat = g_style_mats[7]; | ||
| 307 | if (!mat) | 335 | if (!mat) |
| 308 | return false; | 336 | return false; |
| 309 | bool white = style == 4 || style == 5; | 337 | bool white = style == 4 || style == 5; |
| 338 | // Wireframe lines use a brighter tint so they stay readable over a | ||
| 339 | // solid fill. | ||
| 340 | int wrgb[4]; | ||
| 310 | const int *use_rgb = rgb; | 341 | const int *use_rgb = rgb; |
| 342 | if (style == 1) { | ||
| 343 | for (int i = 0; i < 3; i++) | ||
| 344 | wrgb[i] = (rgb[i] + 255) / 2; | ||
| 345 | wrgb[3] = rgb[3]; | ||
| 346 | use_rgb = wrgb; | ||
| 347 | } | ||
| 311 | chams_apply(mat, ignorez, style, white, use_rgb, false); | 348 | chams_apply(mat, ignorez, style, white, use_rgb, false); |
| 349 | chams_set_blend((float)use_rgb[3] / 255.0f); | ||
| 312 | g_force_override(self, mat, 0 /* OVERRIDE_NORMAL */); | 350 | g_force_override(self, mat, 0 /* OVERRIDE_NORMAL */); |
| 313 | g_orig_model_exec(self, state, info, bone); | 351 | g_orig_model_exec(self, state, info, bone); |
| 314 | if (style == 6) { | 352 | if (style == 6) { |
| 315 | // Solid base plus a wireframe contour pass for depth. Brighten | 353 | // Solid base plus a wireframe contour pass for depth. |
| 316 | // the contour and bias it forward so the base cannot bury it. | ||
| 317 | void *wire = g_style_mats[1]; | 354 | void *wire = g_style_mats[1]; |
| 318 | if (wire && wire != mat) { | 355 | if (wire && wire != mat) { |
| 319 | int wrgb[4] = {(rgb[0] + 255) / 2, (rgb[1] + 255) / 2, | 356 | int crgb[4] = {(rgb[0] + 255) / 2, (rgb[1] + 255) / 2, |
| 320 | (rgb[2] + 255) / 2, rgb[3]}; | 357 | (rgb[2] + 255) / 2, rgb[3]}; |
| 321 | chams_apply(wire, ignorez, 1, false, wrgb, true); | 358 | chams_apply(wire, ignorez, 1, false, crgb, true); |
| 359 | chams_set_blend((float)crgb[3] / 255.0f); | ||
| 322 | g_force_override(self, wire, 0); | 360 | g_force_override(self, wire, 0); |
| 323 | g_orig_model_exec(self, state, info, bone); | 361 | g_orig_model_exec(self, state, info, bone); |
| 324 | chams_reset(wire); | 362 | chams_reset(wire); |
| @@ -327,17 +365,24 @@ void hkdModelExecute(void *self, const void *state, const void *info, | |||
| 327 | chams_reset(mat); | 365 | chams_reset(mat); |
| 328 | return true; | 366 | return true; |
| 329 | }; | 367 | }; |
| 368 | float old_blend = chams_get_blend(); | ||
| 330 | bool did = false; | 369 | bool did = false; |
| 331 | if (is_vm) { | 370 | if (is_vm) { |
| 332 | did = draw_pass(g_chams_vm_mat, g_chams_vm_rgb, false); | 371 | did = draw_pass(g_chams_vm_mat, g_chams_vm_rgb, false); |
| 333 | } else { | 372 | } else { |
| 334 | if (grp->inv_on) | 373 | if (grp->inv_on) |
| 335 | did |= draw_pass(grp->inv_mat, grp->inv_rgb, true); | 374 | did |= draw_pass(grp->inv_mat, grp->inv_rgb, true); |
| 336 | if (grp->vis_on) | 375 | if (grp->vis_on) { |
| 376 | // A wireframe visible pass leaves gaps, so the through-wall fill | ||
| 377 | // would show inside the visible silhouette. Fill it first. | ||
| 378 | if (grp->inv_on && grp->vis_mat == 1) | ||
| 379 | did |= draw_pass(3, grp->vis_rgb, false); | ||
| 337 | did |= draw_pass(grp->vis_mat, grp->vis_rgb, false); | 380 | did |= draw_pass(grp->vis_mat, grp->vis_rgb, false); |
| 381 | } | ||
| 338 | } | 382 | } |
| 339 | if (did) { | 383 | if (did) { |
| 340 | g_force_override(self, nullptr, 0); | 384 | g_force_override(self, nullptr, 0); |
| 385 | chams_set_blend(old_blend); | ||
| 341 | return; | 386 | return; |
| 342 | } | 387 | } |
| 343 | } | 388 | } |
| @@ -354,6 +399,96 @@ static double chams_now() { | |||
| 354 | return (double)ts.tv_sec + (double)ts.tv_nsec * 1e-9; | 399 | return (double)ts.tv_sec + (double)ts.tv_nsec * 1e-9; |
| 355 | } | 400 | } |
| 356 | 401 | ||
| 402 | // The base-game model debug materials are opaque. Drop a tiny VMT with | ||
| 403 | // $model + $translucent into the game's custom/materials folders so the alpha | ||
| 404 | // channel in the colour picker has something to drive. Written from a | ||
| 405 | // constructor too, so it exists before the material system scans its paths. | ||
| 406 | // Only our own hl2hake folder is touched. | ||
| 407 | static void chams_mkdirs(const std::string &path) { | ||
| 408 | std::string cur; | ||
| 409 | for (size_t i = 0; i < path.size(); i++) { | ||
| 410 | cur += path[i]; | ||
| 411 | if (path[i] == '/') | ||
| 412 | mkdir(cur.c_str(), 0755); | ||
| 413 | } | ||
| 414 | mkdir(path.c_str(), 0755); | ||
| 415 | } | ||
| 416 | |||
| 417 | static bool chams_write_vmt_at(const std::string &dir) { | ||
| 418 | chams_mkdirs(dir); | ||
| 419 | std::string path = dir + "/translucent.vmt"; | ||
| 420 | FILE *f = fopen(path.c_str(), "w"); | ||
| 421 | if (!f) | ||
| 422 | return false; | ||
| 423 | fputs("\"UnlitGeneric\"\n" | ||
| 424 | "{\n" | ||
| 425 | " \"$basetexture\" \"sprites/white\"\n" | ||
| 426 | " \"$color\" \"[1 1 1]\"\n" | ||
| 427 | " \"$alpha\" \"1\"\n" | ||
| 428 | " \"$translucent\" \"1\"\n" | ||
| 429 | " \"$model\" \"1\"\n" | ||
| 430 | " \"$nocull\" \"1\"\n" | ||
| 431 | " \"%noToolTexture\" \"1\"\n" | ||
| 432 | "}\n", | ||
| 433 | f); | ||
| 434 | fclose(f); | ||
| 435 | return true; | ||
| 436 | } | ||
| 437 | |||
| 438 | static std::string chams_game_arg() { | ||
| 439 | FILE *f = fopen("/proc/self/cmdline", "rb"); | ||
| 440 | if (!f) | ||
| 441 | return std::string(); | ||
| 442 | std::string data; | ||
| 443 | int c; | ||
| 444 | while ((c = fgetc(f)) != EOF) | ||
| 445 | data += (char)c; | ||
| 446 | fclose(f); | ||
| 447 | size_t pos = 0; | ||
| 448 | while (pos < data.size()) { | ||
| 449 | size_t end = data.find('\0', pos); | ||
| 450 | if (end == std::string::npos) | ||
| 451 | end = data.size(); | ||
| 452 | if (data.compare(pos, end - pos, "-game") == 0) { | ||
| 453 | size_t n = end + 1; | ||
| 454 | size_t e2 = data.find('\0', n); | ||
| 455 | if (e2 == std::string::npos) | ||
| 456 | e2 = data.size(); | ||
| 457 | return data.substr(n, e2 - n); | ||
| 458 | } | ||
| 459 | pos = end + 1; | ||
| 460 | } | ||
| 461 | return std::string(); | ||
| 462 | } | ||
| 463 | |||
| 464 | static void chams_write_translucent_vmt() { | ||
| 465 | static bool done = false; | ||
| 466 | if (done) | ||
| 467 | return; | ||
| 468 | char cwd[4096]; | ||
| 469 | ssize_t n = readlink("/proc/self/cwd", cwd, sizeof(cwd) - 1); | ||
| 470 | if (n <= 0) | ||
| 471 | return; | ||
| 472 | cwd[n] = 0; | ||
| 473 | std::string root(cwd); | ||
| 474 | std::string mod = chams_game_arg(); | ||
| 475 | if (mod.empty()) | ||
| 476 | mod = "hl2"; | ||
| 477 | std::string base = (!mod.empty() && mod[0] == '/') ? mod : root + "/" + mod; | ||
| 478 | // Only write when this really is a Source game directory (the wrapper | ||
| 479 | // processes also preload us, from the repo directory). | ||
| 480 | if (access((base + "/gameinfo.txt").c_str(), R_OK) != 0) | ||
| 481 | return; | ||
| 482 | bool ok = false; | ||
| 483 | ok |= chams_write_vmt_at(base + "/custom/hl2hake/materials/hl2hake"); | ||
| 484 | ok |= chams_write_vmt_at(base + "/materials/hl2hake"); | ||
| 485 | done = ok; | ||
| 486 | } | ||
| 487 | |||
| 488 | __attribute__((constructor)) static void chams_early_vmt() { | ||
| 489 | chams_write_translucent_vmt(); | ||
| 490 | } | ||
| 491 | |||
| 357 | // Called from outer_draw (outside the model draw) so FindMaterial can load | 492 | // Called from outer_draw (outside the model draw) so FindMaterial can load |
| 358 | // the style VMTs. The render hook only reads the cached pointers. | 493 | // the style VMTs. The render hook only reads the cached pointers. |
| 359 | void chams_preload() { | 494 | void chams_preload() { |
| @@ -375,6 +510,16 @@ void chams_preload() { | |||
| 375 | logf("chams: preloaded style %d %s = %p", i, chams_material_name(i), m); | 510 | logf("chams: preloaded style %d %s = %p", i, chams_material_name(i), m); |
| 376 | } | 511 | } |
| 377 | } | 512 | } |
| 513 | if (!g_style_mats[7]) { | ||
| 514 | chams_write_translucent_vmt(); | ||
| 515 | void *m = world_find_material("hl2hake/translucent"); | ||
| 516 | if (m) { | ||
| 517 | using RefFn = void (*)(void *); | ||
| 518 | vfunc<RefFn>(m, 12)(m); | ||
| 519 | g_style_mats[7] = m; | ||
| 520 | logf("chams: preloaded translucent material = %p", m); | ||
| 521 | } | ||
| 522 | } | ||
| 378 | } | 523 | } |
| 379 | 524 | ||
| 380 | void chams_init() { | 525 | void chams_init() { |
