From 9c2acb1b5f16a52099a219dce9753f8483e67590 Mon Sep 17 00:00:00 2001 From: TimePath Date: Sun, 29 Nov 2015 21:11:49 +1100 Subject: [PATCH] Viewmodels: support cl_bobmodel, cl_leanmodel, cl_followmodel --- qcsrc/client/view.qc | 229 ++++++++++++++++++++++++++++++++++++++++++- qcsrc/lib/vector.qh | 10 ++ 2 files changed, 236 insertions(+), 3 deletions(-) diff --git a/qcsrc/client/view.qc b/qcsrc/client/view.qc index 6fc6bbfa2..5497d2ab9 100644 --- a/qcsrc/client/view.qc +++ b/qcsrc/client/view.qc @@ -32,6 +32,225 @@ #define EFMASK_CHEAP (EF_ADDITIVE | EF_DOUBLESIDED | EF_FULLBRIGHT | EF_NODEPTHTEST | EF_NODRAW | EF_NOSHADOW | EF_SELECTABLE | EF_TELEPORT_BIT) +float autocvar_cl_viewmodel_scale; + +bool autocvar_cl_bobmodel; +float autocvar_cl_bobmodel_speed; +float autocvar_cl_bobmodel_side; +float autocvar_cl_bobmodel_up; + +float autocvar_cl_followmodel; +float autocvar_cl_followmodel_side_speed; +float autocvar_cl_followmodel_side_highpass; +float autocvar_cl_followmodel_side_highpass1; +float autocvar_cl_followmodel_side_limit; +float autocvar_cl_followmodel_side_lowpass; +float autocvar_cl_followmodel_up_speed; +float autocvar_cl_followmodel_up_highpass; +float autocvar_cl_followmodel_up_highpass1; +float autocvar_cl_followmodel_up_limit; +float autocvar_cl_followmodel_up_lowpass; + +float autocvar_cl_leanmodel; +float autocvar_cl_leanmodel_side_speed; +float autocvar_cl_leanmodel_side_highpass; +float autocvar_cl_leanmodel_side_highpass1; +float autocvar_cl_leanmodel_side_lowpass; +float autocvar_cl_leanmodel_side_limit; +float autocvar_cl_leanmodel_up_speed; +float autocvar_cl_leanmodel_up_highpass; +float autocvar_cl_leanmodel_up_highpass1; +float autocvar_cl_leanmodel_up_lowpass; +float autocvar_cl_leanmodel_up_limit; + +#define lowpass(value, frac, ref_store, ret) do \ +{ \ + float __frac = bound(0, frac, 1); \ + ret = ref_store = ref_store * (1 - __frac) + (value) * __frac; \ +} while (0) + +#define lowpass_limited(value, frac, limit, ref_store, ret) do \ +{ \ + float __ignore; lowpass(value, frac, ref_store, __ignore); \ + ret = ref_store = bound((value) - (limit), ref_store, (value) + (limit)); \ +} while (0) + +#define highpass(value, frac, ref_store, ret) do \ +{ \ + float __f; lowpass(value, frac, ref_store, __f); \ + ret = (value) - __f; \ +} while (0) + +#define highpass_limited(value, frac, limit, ref_store, ret) do \ +{ \ + float __f; lowpass_limited(value, frac, limit, ref_store, __f); \ + ret = (value) - __f; \ +} while (0) + +#define lowpass3(value, fracx, fracy, fracz, ref_store, ref_out) do \ +{ \ + lowpass(value.x, fracx, ref_store.x, ref_out.x); \ + lowpass(value.y, fracy, ref_store.y, ref_out.y); \ + lowpass(value.z, fracz, ref_store.z, ref_out.z); \ +} while (0) + +#define highpass3(value, fracx, fracy, fracz, ref_store, ref_out) do \ +{ \ + highpass(value.x, fracx, ref_store.x, ref_out.x); \ + highpass(value.y, fracy, ref_store.y, ref_out.y); \ + highpass(value.z, fracz, ref_store.z, ref_out.z); \ +} while (0) + +#define highpass3_limited(value, fracx, limitx, fracy, limity, fracz, limitz, ref_store, ref_out) do \ +{ \ + highpass_limited(value.x, fracx, limitx, ref_store.x, ref_out.x); \ + highpass_limited(value.y, fracy, limity, ref_store.y, ref_out.y); \ + highpass_limited(value.z, fracz, limitz, ref_store.z, ref_out.z); \ +} while (0) + +void viewmodel_animate(entity this) +{ + static float prevtime; + float frametime = (time - prevtime) * getstatf(STAT_MOVEVARS_TIMESCALE); + prevtime = time; + + if (autocvar_chase_active) return; + if (getstati(STAT_HEALTH) <= 0) return; + + vector gunorg = '0 0 0', gunangles = '0 0 0'; + static vector gunorg_prev = '0 0 0', gunangles_prev = '0 0 0'; + + bool teleported = false; // TODO: detect + + // 1. if we teleported, clear the frametime... the lowpass will recover the previous value then + if (teleported) + { + // try to fix the first highpass; result is NOT + // perfect! TODO find a better fix + gunangles_prev = view_angles; + gunorg_prev = view_origin; + } + + static vector gunorg_highpass = '0 0 0'; + + // 2. for the gun origin, only keep the high frequency (non-DC) parts, which is "somewhat like velocity" + gunorg_highpass += gunorg_prev; + highpass3_limited(view_origin, + frametime * autocvar_cl_followmodel_side_highpass1, autocvar_cl_followmodel_side_limit, + frametime * autocvar_cl_followmodel_side_highpass1, autocvar_cl_followmodel_side_limit, + frametime * autocvar_cl_followmodel_up_highpass1, autocvar_cl_followmodel_up_limit, + gunorg_highpass, gunorg); + gunorg_prev = view_origin; + gunorg_highpass -= gunorg_prev; + + static vector gunangles_highpass = '0 0 0'; + + // in the highpass, we _store_ the DIFFERENCE to the actual view angles... + gunangles_highpass += gunangles_prev; + PITCH(gunangles_highpass) += 360 * floor((PITCH(view_angles) - PITCH(gunangles_highpass)) / 360 + 0.5); + YAW(gunangles_highpass) += 360 * floor((YAW(view_angles) - YAW(gunangles_highpass)) / 360 + 0.5); + ROLL(gunangles_highpass) += 360 * floor((ROLL(view_angles) - ROLL(gunangles_highpass)) / 360 + 0.5); + highpass3_limited(view_angles, + frametime * autocvar_cl_leanmodel_up_highpass1, autocvar_cl_leanmodel_up_limit, + frametime * autocvar_cl_leanmodel_side_highpass1, autocvar_cl_leanmodel_side_limit, + 0, 0, + gunangles_highpass, gunangles); + gunangles_prev = view_angles; + gunangles_highpass -= gunangles_prev; + + // 3. calculate the RAW adjustment vectors + gunorg.x *= (autocvar_cl_followmodel ? -autocvar_cl_followmodel_side_speed : 0); + gunorg.y *= (autocvar_cl_followmodel ? -autocvar_cl_followmodel_side_speed : 0); + gunorg.z *= (autocvar_cl_followmodel ? -autocvar_cl_followmodel_up_speed : 0); + + PITCH(gunangles) *= (autocvar_cl_leanmodel ? -autocvar_cl_leanmodel_up_speed : 0); + YAW(gunangles) *= (autocvar_cl_leanmodel ? -autocvar_cl_leanmodel_side_speed : 0); + ROLL(gunangles) = 0; + + static vector gunorg_adjustment_highpass; + static vector gunorg_adjustment_lowpass; + static vector gunangles_adjustment_highpass; + static vector gunangles_adjustment_lowpass; + + // 4. perform highpass/lowpass on the adjustment vectors (turning velocity into acceleration!) + // trick: we must do the lowpass LAST, so the lowpass vector IS the final vector! + highpass3(gunorg, + frametime * autocvar_cl_followmodel_side_highpass, + frametime * autocvar_cl_followmodel_side_highpass, + frametime * autocvar_cl_followmodel_up_highpass, + gunorg_adjustment_highpass, gunorg); + lowpass3(gunorg, + frametime * autocvar_cl_followmodel_side_lowpass, + frametime * autocvar_cl_followmodel_side_lowpass, + frametime * autocvar_cl_followmodel_up_lowpass, + gunorg_adjustment_lowpass, gunorg); + // we assume here: PITCH = 0, YAW = 1, ROLL = 2 + highpass3(gunangles, + frametime * autocvar_cl_leanmodel_up_highpass, + frametime * autocvar_cl_leanmodel_side_highpass, + 0, + gunangles_adjustment_highpass, gunangles); + lowpass3(gunangles, + frametime * autocvar_cl_leanmodel_up_lowpass, + frametime * autocvar_cl_leanmodel_side_lowpass, + 0, + gunangles_adjustment_lowpass, gunangles); + float xyspeed = bound(0, vlen(vec2(csqcplayer.velocity)), 400); + + // vertical view bobbing code + // TODO: cl_bob + + // horizontal view bobbing code + // TODO: cl_bob2 + + // fall bobbing code + // causes the view to swing down and back up when touching the ground + // TODO: cl_bobfall + + // gun model bobbing code + if (autocvar_cl_bobmodel) + { + // calculate for swinging gun model + // the gun bobs when running on the ground, but doesn't bob when you're in the air. + // Sajt: I tried to smooth out the transitions between bob and no bob, which works + // for the most part, but for some reason when you go through a message trigger or + // pick up an item or anything like that it will momentarily jolt the gun. + vector forward, right, up; + float bspeed; + float t = 1; + float s = time * autocvar_cl_bobmodel_speed; + // TODO +// if (clonground) +// { +// if (time - hitgroundtime < 0.2) +// { +// // just hit the ground, speed the bob back up over the next 0.2 seconds +// t = time - hitgroundtime; +// t = bound(0, t, 0.2); +// t *= 5; +// } +// } +// else +// { +// // recently left the ground, slow the bob down over the next 0.2 seconds +// t = time - lastongroundtime; +// t = 0.2 - bound(0, t, 0.2); +// t *= 5; +// } + bspeed = xyspeed * 0.01; + MAKEVECTORS(makevectors, gunangles, forward, right, up); + float bobr = bspeed * autocvar_cl_bobmodel_side * autocvar_cl_viewmodel_scale * sin(s) * t; + gunorg += bobr * right; + float bobu = bspeed * autocvar_cl_bobmodel_up * autocvar_cl_viewmodel_scale * cos(s * 2) * t; + gunorg += bobu * up; + } + this.origin += view_forward * gunorg.x + view_right * gunorg.y + view_up * gunorg.z; + gunangles.x = -gunangles.x; // pitch was inverted, now that actually matters + this.angles += gunangles; +} + +.vector viewmodel_origin, viewmodel_angles; + void viewmodel_draw(entity this) { int mask = (intermission || (getstati(STAT_HEALTH) <= 0) || autocvar_chase_active) ? 0 : MASK_NORMAL; @@ -58,6 +277,8 @@ void viewmodel_draw(entity this) { name_last = name; CL_WeaponEntity_SetModel(this, name); + this.viewmodel_origin = this.origin; + this.viewmodel_angles = this.angles; } anim_update(this); if (!this.animstate_override) @@ -72,7 +293,6 @@ void viewmodel_draw(entity this) // entity newwep = Weapons_from(activeweapon); float delay = 0.2; // TODO: newwep.switchdelay_raise; f = eta / max(eta, delay); - this.angles_x = -90 * f * f; break; } case WS_DROP: @@ -80,7 +300,6 @@ void viewmodel_draw(entity this) // entity oldwep = Weapons_from(activeweapon); float delay = 0.2; // TODO: newwep.switchdelay_drop; f = 1 - eta / max(eta, delay); - this.angles_x = -90 * f * f; break; } case WS_CLEAR: @@ -89,7 +308,11 @@ void viewmodel_draw(entity this) break; } } - this.angles_x = -90 * f * f; + this.origin = this.viewmodel_origin; + this.angles = this.viewmodel_angles; + this.angles_x = (-90 * f * f); + viewmodel_animate(this); + setorigin(this, this.origin); } entity viewmodel; diff --git a/qcsrc/lib/vector.qh b/qcsrc/lib/vector.qh index 6cbaebdcd..9813f2fa9 100644 --- a/qcsrc/lib/vector.qh +++ b/qcsrc/lib/vector.qh @@ -47,6 +47,16 @@ float boxesoverlap(vector m1, vector m2, vector m3, vector m4) { return m2_x >= /** requires the same as boxesoverlap, but is a stronger condition */ float boxinsidebox(vector smins, vector smaxs, vector bmins, vector bmaxs) { return smins.x >= bmins.x && smaxs.x <= bmaxs.x && smins.y >= bmins.y && smaxs.y <= bmaxs.y && smins.z >= bmins.z && smaxs.z <= bmaxs.z; } +#define PITCH(v) (v).x +#define YAW(v) (v).y +#define ROLL(v) (v).z + +#define MAKEVECTORS(f, angles, forward, right, up) do { \ + f(angles); \ + forward = v_forward; \ + right = v_right; \ + up = v_up; \ +} while (0) vector vec2(vector v) { -- 2.39.2