/* * State: * cl.bob2_smooth * cl.bobfall_speed * cl.bobfall_swing * cl.gunangles_adjustment_highpass * cl.gunangles_adjustment_lowpass * cl.gunangles_highpass * cl.gunangles_prev * cl.gunorg_adjustment_highpass * cl.gunorg_adjustment_lowpass * cl.gunorg_highpass * cl.gunorg_prev * cl.hitgroundtime * cl.lastongroundtime * cl.oldongrounbd * cl.stairsmoothtime * cl.stairsmoothz * cl.calcrefdef_prevtime * Extra input: * cl.movecmd[0].time * cl.movevars_stepheight * cl.movevars_timescale * cl.oldtime * cl.punchangle * cl.punchvector * cl.qw_intermission_angles * cl.qw_intermission_origin * cl.qw_weaponkick * cls.protocol * cl.time * Output: * cl.csqc_viewanglesfromengine * cl.csqc_viewmodelmatrixfromengine * cl.csqc_vieworiginfromengine * r_refdef.view.matrix * viewmodelmatrix_nobob * viewmodelmatrix_withbob */ void V_CalcRefdefUsing (const matrix4x4_t *entrendermatrix, const vec3_t clviewangles, qboolean teleported, qboolean clonground, qboolean clcmdjump, float clstatsviewheight, qboolean cldead, qboolean clintermission, const vec3_t clvelocity) { float vieworg[3], viewangles[3], smoothtime; float gunorg[3], gunangles[3]; matrix4x4_t tmpmatrix; static float viewheightavg; float viewheight; #if 0 // begin of chase camera bounding box size for proper collisions by Alexander Zubov vec3_t camboxmins = {-3, -3, -3}; vec3_t camboxmaxs = {3, 3, 3}; // end of chase camera bounding box size for proper collisions by Alexander Zubov #endif trace_t trace; // react to clonground state changes (for gun bob) if (clonground) { if (!cl.oldonground) cl.hitgroundtime = cl.movecmd[0].time; cl.lastongroundtime = cl.movecmd[0].time; } cl.oldonground = clonground; cl.calcrefdef_prevtime = max(cl.calcrefdef_prevtime, cl.oldtime); VectorClear(gunorg); viewmodelmatrix_nobob = identitymatrix; viewmodelmatrix_withbob = identitymatrix; r_refdef.view.matrix = identitymatrix; // player can look around, so take the origin from the entity, // and the angles from the input system Matrix4x4_OriginFromMatrix(entrendermatrix, vieworg); VectorCopy(clviewangles, viewangles); // calculate how much time has passed since the last V_CalcRefdef smoothtime = bound(0, cl.time - cl.stairsmoothtime, 0.1); cl.stairsmoothtime = cl.time; // fade damage flash if (v_dmg_time > 0) v_dmg_time -= bound(0, smoothtime, 0.1); if (clintermission) { // entity is a fixed camera, just copy the matrix if (cls.protocol == PROTOCOL_QUAKEWORLD) Matrix4x4_CreateFromQuakeEntity(&r_refdef.view.matrix, cl.qw_intermission_origin[0], cl.qw_intermission_origin[1], cl.qw_intermission_origin[2], cl.qw_intermission_angles[0], cl.qw_intermission_angles[1], cl.qw_intermission_angles[2], 1); else { r_refdef.view.matrix = *entrendermatrix; Matrix4x4_AdjustOrigin(&r_refdef.view.matrix, 0, 0, clstatsviewheight); } Matrix4x4_Copy(&viewmodelmatrix_nobob, &r_refdef.view.matrix); Matrix4x4_ConcatScale(&viewmodelmatrix_nobob, cl_viewmodel_scale.value); Matrix4x4_Copy(&viewmodelmatrix_withbob, &viewmodelmatrix_nobob); VectorCopy(vieworg, cl.csqc_vieworiginfromengine); VectorCopy(viewangles, cl.csqc_viewanglesfromengine); Matrix4x4_Invert_Simple(&tmpmatrix, &r_refdef.view.matrix); Matrix4x4_CreateScale(&cl.csqc_viewmodelmatrixfromengine, cl_viewmodel_scale.value); } else { // smooth stair stepping, but only if clonground and enabled if (!clonground || cl_stairsmoothspeed.value <= 0 || teleported) cl.stairsmoothz = vieworg[2]; else { if (cl.stairsmoothz < vieworg[2]) vieworg[2] = cl.stairsmoothz = bound(vieworg[2] - cl.movevars_stepheight, cl.stairsmoothz + smoothtime * cl_stairsmoothspeed.value, vieworg[2]); else if (cl.stairsmoothz > vieworg[2]) vieworg[2] = cl.stairsmoothz = bound(vieworg[2], cl.stairsmoothz - smoothtime * cl_stairsmoothspeed.value, vieworg[2] + cl.movevars_stepheight); } // apply qw weapon recoil effect (this did not work in QW) // TODO: add a cvar to disable this viewangles[PITCH] += cl.qw_weaponkick; // apply the viewofs (even if chasecam is used) // Samual: Lets add smoothing for this too so that things like crouching are done with a transition. viewheight = bound(0, (cl.time - cl.calcrefdef_prevtime) / max(0.0001, cl_smoothviewheight.value), 1); viewheightavg = viewheightavg * (1 - viewheight) + clstatsviewheight * viewheight; vieworg[2] += viewheightavg; if (chase_active.value) { // observing entity from third person. Added "campitch" by Alexander "motorsep" Zubov vec_t camback, camup, dist, campitch, forward[3], chase_dest[3]; camback = chase_back.value; camup = chase_up.value; campitch = chase_pitchangle.value; AngleVectors(viewangles, forward, NULL, NULL); if (chase_overhead.integer) { #if 1 vec3_t offset; vec3_t bestvieworg; #endif vec3_t up; viewangles[PITCH] = 0; AngleVectors(viewangles, forward, NULL, up); // trace a little further so it hits a surface more consistently (to avoid 'snapping' on the edge of the range) chase_dest[0] = vieworg[0] - forward[0] * camback + up[0] * camup; chase_dest[1] = vieworg[1] - forward[1] * camback + up[1] * camup; chase_dest[2] = vieworg[2] - forward[2] * camback + up[2] * camup; #if 0 #if 1 //trace = CL_TraceLine(vieworg, eyeboxmins, eyeboxmaxs, chase_dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_SKY, true, false, NULL, false); trace = CL_TraceLine(vieworg, camboxmins, camboxmaxs, chase_dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_SKY, true, false, NULL, false); #else //trace = CL_TraceBox(vieworg, eyeboxmins, eyeboxmaxs, chase_dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_SKY, true, false, NULL, false); trace = CL_TraceBox(vieworg, camboxmins, camboxmaxs, chase_dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_SKY, true, false, NULL, false); #endif VectorCopy(trace.endpos, vieworg); vieworg[2] -= 8; #else // trace from first person view location to our chosen third person view location #if 1 trace = CL_TraceLine(vieworg, chase_dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_SKY, true, false, NULL, false, true); #else trace = CL_TraceBox(vieworg, camboxmins, camboxmaxs, chase_dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_SKY, true, false, NULL, false); #endif VectorCopy(trace.endpos, bestvieworg); offset[2] = 0; for (offset[0] = -16;offset[0] <= 16;offset[0] += 8) { for (offset[1] = -16;offset[1] <= 16;offset[1] += 8) { AngleVectors(viewangles, NULL, NULL, up); chase_dest[0] = vieworg[0] - forward[0] * camback + up[0] * camup + offset[0]; chase_dest[1] = vieworg[1] - forward[1] * camback + up[1] * camup + offset[1]; chase_dest[2] = vieworg[2] - forward[2] * camback + up[2] * camup + offset[2]; #if 1 trace = CL_TraceLine(vieworg, chase_dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_SKY, true, false, NULL, false, true); #else trace = CL_TraceBox(vieworg, camboxmins, camboxmaxs, chase_dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_SKY, true, false, NULL, false); #endif if (bestvieworg[2] > trace.endpos[2]) bestvieworg[2] = trace.endpos[2]; } } bestvieworg[2] -= 8; VectorCopy(bestvieworg, vieworg); #endif viewangles[PITCH] = campitch; } else { if (gamemode == GAME_GOODVSBAD2 && chase_stevie.integer) { // look straight down from high above viewangles[PITCH] = 90; camback = 2048; VectorSet(forward, 0, 0, -1); } // trace a little further so it hits a surface more consistently (to avoid 'snapping' on the edge of the range) dist = -camback - 8; chase_dest[0] = vieworg[0] + forward[0] * dist; chase_dest[1] = vieworg[1] + forward[1] * dist; chase_dest[2] = vieworg[2] + forward[2] * dist + camup; trace = CL_TraceLine(vieworg, chase_dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_SKY, true, false, NULL, false, true); VectorMAMAM(1, trace.endpos, 8, forward, 4, trace.plane.normal, vieworg); } } else { // first person view from entity // angles if (cldead && v_deathtilt.integer) viewangles[ROLL] = v_deathtiltangle.value; if (cl_weaponrecoil.integer > 0) VectorAdd(viewangles, cl.punchangle, viewangles); viewangles[ROLL] += V_CalcRoll(clviewangles, clvelocity); if (v_dmg_time > 0) { viewangles[ROLL] += v_dmg_time/v_kicktime.value*v_dmg_roll; viewangles[PITCH] += v_dmg_time/v_kicktime.value*v_dmg_pitch; } // origin if (cl_weaponrecoil.integer > 0) VectorAdd(vieworg, cl.punchvector, vieworg); if (!cldead) { double xyspeed, bob, bobfall; float cycle; vec_t frametime; frametime = (cl.time - cl.calcrefdef_prevtime) * cl.movevars_timescale; // 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 VectorCopy(viewangles, cl.gunangles_prev); VectorCopy(vieworg, cl.gunorg_prev); } // 2. for the gun origin, only keep the high frequency (non-DC) parts, which is "somewhat like velocity" VectorAdd(cl.gunorg_highpass, cl.gunorg_prev, cl.gunorg_highpass); highpass3_limited(vieworg, frametime*cl_followmodel_side_highpass1.value, cl_followmodel_side_limit.value, frametime*cl_followmodel_side_highpass1.value, cl_followmodel_side_limit.value, frametime*cl_followmodel_up_highpass1.value, cl_followmodel_up_limit.value, cl.gunorg_highpass, gunorg); VectorCopy(vieworg, cl.gunorg_prev); VectorSubtract(cl.gunorg_highpass, cl.gunorg_prev, cl.gunorg_highpass); // in the highpass, we _store_ the DIFFERENCE to the actual view angles... VectorAdd(cl.gunangles_highpass, cl.gunangles_prev, cl.gunangles_highpass); cl.gunangles_highpass[PITCH] += 360 * floor((viewangles[PITCH] - cl.gunangles_highpass[PITCH]) / 360 + 0.5); cl.gunangles_highpass[YAW] += 360 * floor((viewangles[YAW] - cl.gunangles_highpass[YAW]) / 360 + 0.5); cl.gunangles_highpass[ROLL] += 360 * floor((viewangles[ROLL] - cl.gunangles_highpass[ROLL]) / 360 + 0.5); highpass3_limited(viewangles, frametime*cl_leanmodel_up_highpass1.value, cl_leanmodel_up_limit.value, frametime*cl_leanmodel_side_highpass1.value, cl_leanmodel_side_limit.value, 0, 0, cl.gunangles_highpass, gunangles); VectorCopy(viewangles, cl.gunangles_prev); VectorSubtract(cl.gunangles_highpass, cl.gunangles_prev, cl.gunangles_highpass); // 3. calculate the RAW adjustment vectors gunorg[0] *= (cl_followmodel.value ? -cl_followmodel_side_speed.value : 0); gunorg[1] *= (cl_followmodel.value ? -cl_followmodel_side_speed.value : 0); gunorg[2] *= (cl_followmodel.value ? -cl_followmodel_up_speed.value : 0); gunangles[PITCH] *= (cl_leanmodel.value ? -cl_leanmodel_up_speed.value : 0); gunangles[YAW] *= (cl_leanmodel.value ? -cl_leanmodel_side_speed.value : 0); gunangles[ROLL] = 0; // 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*cl_followmodel_side_highpass.value, frametime*cl_followmodel_side_highpass.value, frametime*cl_followmodel_up_highpass.value, cl.gunorg_adjustment_highpass, gunorg); lowpass3(gunorg, frametime*cl_followmodel_side_lowpass.value, frametime*cl_followmodel_side_lowpass.value, frametime*cl_followmodel_up_lowpass.value, cl.gunorg_adjustment_lowpass, gunorg); // we assume here: PITCH = 0, YAW = 1, ROLL = 2 highpass3(gunangles, frametime*cl_leanmodel_up_highpass.value, frametime*cl_leanmodel_side_highpass.value, 0, cl.gunangles_adjustment_highpass, gunangles); lowpass3(gunangles, frametime*cl_leanmodel_up_lowpass.value, frametime*cl_leanmodel_side_lowpass.value, 0, cl.gunangles_adjustment_lowpass, gunangles); // 5. use the adjusted vectors VectorAdd(vieworg, gunorg, gunorg); VectorAdd(viewangles, gunangles, gunangles); // bounded XY speed, used by several effects below xyspeed = bound (0, sqrt(clvelocity[0]*clvelocity[0] + clvelocity[1]*clvelocity[1]), 400); // vertical view bobbing code if (cl_bob.value && cl_bobcycle.value) { // LordHavoc: this code is *weird*, but not replacable (I think it // should be done in QC on the server, but oh well, quake is quake) // LordHavoc: figured out bobup: the time at which the sin is at 180 // degrees (which allows lengthening or squishing the peak or valley) cycle = cl.time / cl_bobcycle.value; cycle -= (int) cycle; if (cycle < cl_bobup.value) cycle = sin(M_PI * cycle / cl_bobup.value); else cycle = sin(M_PI + M_PI * (cycle-cl_bobup.value)/(1.0 - cl_bobup.value)); // bob is proportional to velocity in the xy plane // (don't count Z, or jumping messes it up) bob = xyspeed * bound(0, cl_bob.value, 0.05); bob = bob*0.3 + bob*0.7*cycle; vieworg[2] += bob; // we also need to adjust gunorg, or this appears like pushing the gun! // In the old code, this was applied to vieworg BEFORE copying to gunorg, // but this is not viable with the new followmodel code as that would mean // that followmodel would work on the munged-by-bob vieworg and do feedback gunorg[2] += bob; } // horizontal view bobbing code if (cl_bob2.value && cl_bob2cycle.value) { vec3_t bob2vel; vec3_t forward, right, up; float side, front; cycle = cl.time / cl_bob2cycle.value; cycle -= (int) cycle; if (cycle < 0.5) cycle = cos(M_PI * cycle / 0.5); // cos looks better here with the other view bobbing using sin else cycle = cos(M_PI + M_PI * (cycle-0.5)/0.5); bob = bound(0, cl_bob2.value, 0.05) * cycle; // this value slowly decreases from 1 to 0 when we stop touching the ground. // The cycle is later multiplied with it so the view smooths back to normal if (clonground && !clcmdjump) // also block the effect while the jump button is pressed, to avoid twitches when bunny-hopping cl.bob2_smooth = 1; else { if(cl.bob2_smooth > 0) cl.bob2_smooth -= bound(0, cl_bob2smooth.value, 1); else cl.bob2_smooth = 0; } // calculate the front and side of the player between the X and Y axes AngleVectors(viewangles, forward, right, up); // now get the speed based on those angles. The bounds should match the same value as xyspeed's side = bound(-400, DotProduct (clvelocity, right) * cl.bob2_smooth, 400); front = bound(-400, DotProduct (clvelocity, forward) * cl.bob2_smooth, 400); VectorScale(forward, bob, forward); VectorScale(right, bob, right); // we use side with forward and front with right, so the bobbing goes // to the side when we walk forward and to the front when we strafe VectorMAMAM(side, forward, front, right, 0, up, bob2vel); vieworg[0] += bob2vel[0]; vieworg[1] += bob2vel[1]; // we also need to adjust gunorg, or this appears like pushing the gun! // In the old code, this was applied to vieworg BEFORE copying to gunorg, // but this is not viable with the new followmodel code as that would mean // that followmodel would work on the munged-by-bob vieworg and do feedback gunorg[0] += bob2vel[0]; gunorg[1] += bob2vel[1]; } // fall bobbing code // causes the view to swing down and back up when touching the ground if (cl_bobfall.value && cl_bobfallcycle.value) { if (!clonground) { cl.bobfall_speed = bound(-400, clvelocity[2], 0) * bound(0, cl_bobfall.value, 0.1); if (clvelocity[2] < -cl_bobfallminspeed.value) cl.bobfall_swing = 1; else cl.bobfall_swing = 0; // TODO really? } else { cl.bobfall_swing = max(0, cl.bobfall_swing - cl_bobfallcycle.value * frametime); bobfall = sin(M_PI * cl.bobfall_swing) * cl.bobfall_speed; vieworg[2] += bobfall; gunorg[2] += bobfall; } } // gun model bobbing code if (cl_bobmodel.value) { // 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. vec3_t forward, right, up; float bspeed; float s; float t; s = cl.time * cl_bobmodel_speed.value; if (clonground) { if (cl.time - cl.hitgroundtime < 0.2) { // just hit the ground, speed the bob back up over the next 0.2 seconds t = cl.time - cl.hitgroundtime; t = bound(0, t, 0.2); t *= 5; } else t = 1; } else { // recently left the ground, slow the bob down over the next 0.2 seconds t = cl.time - cl.lastongroundtime; t = 0.2 - bound(0, t, 0.2); t *= 5; } bspeed = xyspeed * 0.01f; AngleVectors (gunangles, forward, right, up); bob = bspeed * cl_bobmodel_side.value * cl_viewmodel_scale.value * sin (s) * t; VectorMA (gunorg, bob, right, gunorg); bob = bspeed * cl_bobmodel_up.value * cl_viewmodel_scale.value * cos (s * 2) * t; VectorMA (gunorg, bob, up, gunorg); } } } // calculate a view matrix for rendering the scene if (v_idlescale.value) { viewangles[0] += v_idlescale.value * sin(cl.time*v_ipitch_cycle.value) * v_ipitch_level.value; viewangles[1] += v_idlescale.value * sin(cl.time*v_iyaw_cycle.value) * v_iyaw_level.value; viewangles[2] += v_idlescale.value * sin(cl.time*v_iroll_cycle.value) * v_iroll_level.value; } Matrix4x4_CreateFromQuakeEntity(&r_refdef.view.matrix, vieworg[0], vieworg[1], vieworg[2], viewangles[0], viewangles[1], viewangles[2], 1); // calculate a viewmodel matrix for use in view-attached entities Matrix4x4_Copy(&viewmodelmatrix_nobob, &r_refdef.view.matrix); Matrix4x4_ConcatScale(&viewmodelmatrix_nobob, cl_viewmodel_scale.value); Matrix4x4_CreateFromQuakeEntity(&viewmodelmatrix_withbob, gunorg[0], gunorg[1], gunorg[2], gunangles[0], gunangles[1], gunangles[2], cl_viewmodel_scale.value); VectorCopy(vieworg, cl.csqc_vieworiginfromengine); VectorCopy(viewangles, cl.csqc_viewanglesfromengine); Matrix4x4_Invert_Simple(&tmpmatrix, &r_refdef.view.matrix); Matrix4x4_Concat(&cl.csqc_viewmodelmatrixfromengine, &tmpmatrix, &viewmodelmatrix_withbob); } cl.calcrefdef_prevtime = cl.time; }
static void R_Model_Sprite_Draw_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist) { int i; dp_model_t *model = ent->model; vec3_t left, up, org, mforward, mleft, mup, middle; float scale, dx, dy, hud_vs_screen; int edge = 0; float dir_angle = 0.0f; float vertex3f[12]; // nudge it toward the view to make sure it isn't in a wall Matrix4x4_ToVectors(&ent->matrix, mforward, mleft, mup, org); VectorSubtract(org, r_refdef.view.forward, org); switch(model->sprite.sprnum_type) { case SPR_VP_PARALLEL_UPRIGHT: // flames and such // vertical beam sprite, faces view plane scale = ent->scale / sqrt(r_refdef.view.forward[0]*r_refdef.view.forward[0]+r_refdef.view.forward[1]*r_refdef.view.forward[1]); left[0] = -r_refdef.view.forward[1] * scale; left[1] = r_refdef.view.forward[0] * scale; left[2] = 0; up[0] = 0; up[1] = 0; up[2] = ent->scale; break; case SPR_FACING_UPRIGHT: // flames and such // vertical beam sprite, faces viewer's origin (not the view plane) scale = ent->scale / sqrt((org[0] - r_refdef.view.origin[0])*(org[0] - r_refdef.view.origin[0])+(org[1] - r_refdef.view.origin[1])*(org[1] - r_refdef.view.origin[1])); left[0] = (org[1] - r_refdef.view.origin[1]) * scale; left[1] = -(org[0] - r_refdef.view.origin[0]) * scale; left[2] = 0; up[0] = 0; up[1] = 0; up[2] = ent->scale; break; default: Con_Printf("R_SpriteSetup: unknown sprite type %i\n", model->sprite.sprnum_type); // fall through to normal sprite case SPR_VP_PARALLEL: // normal sprite // faces view plane VectorScale(r_refdef.view.left, ent->scale, left); VectorScale(r_refdef.view.up, ent->scale, up); break; case SPR_LABEL_SCALE: // normal sprite // faces view plane // fixed HUD pixel size specified in sprite // honors scale // honors a global label scaling cvar if(r_fb.water.renderingscene) // labels are considered HUD items, and don't appear in reflections return; // See the R_TrackSprite definition for a reason for this copying VectorCopy(r_refdef.view.left, left); VectorCopy(r_refdef.view.up, up); // It has to be done before the calculations, because it moves the origin. if(r_track_sprites.integer) R_TrackSprite(ent, org, left, up, &edge, &dir_angle); scale = 2 * ent->scale * (DotProduct(r_refdef.view.forward, org) - DotProduct(r_refdef.view.forward, r_refdef.view.origin)) * r_labelsprites_scale.value; VectorScale(left, scale * r_refdef.view.frustum_x / vid_conwidth.integer, left); // 1px VectorScale(up, scale * r_refdef.view.frustum_y / vid_conheight.integer, up); // 1px break; case SPR_LABEL: // normal sprite // faces view plane // fixed pixel size specified in sprite // tries to get the right size in HUD units, if possible // ignores scale // honors a global label scaling cvar before the rounding // FIXME assumes that 1qu is 1 pixel in the sprite like in SPR32 format. Should not do that, but instead query the source image! This bug only applies to the roundtopixels case, though. if(r_fb.water.renderingscene) // labels are considered HUD items, and don't appear in reflections return; // See the R_TrackSprite definition for a reason for this copying VectorCopy(r_refdef.view.left, left); VectorCopy(r_refdef.view.up, up); // It has to be done before the calculations, because it moves the origin. if(r_track_sprites.integer) R_TrackSprite(ent, org, left, up, &edge, &dir_angle); scale = 2 * (DotProduct(r_refdef.view.forward, org) - DotProduct(r_refdef.view.forward, r_refdef.view.origin)); if(r_labelsprites_roundtopixels.integer) { hud_vs_screen = max( vid_conwidth.integer / (float) r_refdef.view.width, vid_conheight.integer / (float) r_refdef.view.height ) / max(0.125, r_labelsprites_scale.value); // snap to "good sizes" // 1 for (0.6, 1.41] // 2 for (1.8, 3.33] if(hud_vs_screen <= 0.6) hud_vs_screen = 0; // don't, use real HUD pixels else if(hud_vs_screen <= 1.41) hud_vs_screen = 1; else if(hud_vs_screen <= 3.33) hud_vs_screen = 2; else hud_vs_screen = 0; // don't, use real HUD pixels if(hud_vs_screen) { // use screen pixels VectorScale(left, scale * r_refdef.view.frustum_x / (r_refdef.view.width * hud_vs_screen), left); // 1px VectorScale(up, scale * r_refdef.view.frustum_y / (r_refdef.view.height * hud_vs_screen), up); // 1px } else { // use HUD pixels VectorScale(left, scale * r_refdef.view.frustum_x / vid_conwidth.integer * r_labelsprites_scale.value, left); // 1px VectorScale(up, scale * r_refdef.view.frustum_y / vid_conheight.integer * r_labelsprites_scale.value, up); // 1px } if(hud_vs_screen == 1) { VectorMA(r_refdef.view.origin, scale, r_refdef.view.forward, middle); // center of screen in distance scale dx = 0.5 - fmod(r_refdef.view.width * 0.5 + (DotProduct(org, left) - DotProduct(middle, left)) / DotProduct(left, left) + 0.5, 1.0); dy = 0.5 - fmod(r_refdef.view.height * 0.5 + (DotProduct(org, up) - DotProduct(middle, up)) / DotProduct(up, up) + 0.5, 1.0); VectorMAMAM(1, org, dx, left, dy, up, org); } } else { // use HUD pixels VectorScale(left, scale * r_refdef.view.frustum_x / vid_conwidth.integer * r_labelsprites_scale.value, left); // 1px VectorScale(up, scale * r_refdef.view.frustum_y / vid_conheight.integer * r_labelsprites_scale.value, up); // 1px } break; case SPR_ORIENTED: // bullet marks on walls // ignores viewer entirely VectorCopy(mleft, left); VectorCopy(mup, up); break; case SPR_VP_PARALLEL_ORIENTED: // I have no idea what people would use this for... // oriented relative to view space // FIXME: test this and make sure it mimicks software left[0] = mleft[0] * r_refdef.view.forward[0] + mleft[1] * r_refdef.view.left[0] + mleft[2] * r_refdef.view.up[0]; left[1] = mleft[0] * r_refdef.view.forward[1] + mleft[1] * r_refdef.view.left[1] + mleft[2] * r_refdef.view.up[1]; left[2] = mleft[0] * r_refdef.view.forward[2] + mleft[1] * r_refdef.view.left[2] + mleft[2] * r_refdef.view.up[2]; up[0] = mup[0] * r_refdef.view.forward[0] + mup[1] * r_refdef.view.left[0] + mup[2] * r_refdef.view.up[0]; up[1] = mup[0] * r_refdef.view.forward[1] + mup[1] * r_refdef.view.left[1] + mup[2] * r_refdef.view.up[1]; up[2] = mup[0] * r_refdef.view.forward[2] + mup[1] * r_refdef.view.left[2] + mup[2] * r_refdef.view.up[2]; break; case SPR_OVERHEAD: // Overhead games sprites, have some special hacks to look good VectorScale(r_refdef.view.left, ent->scale * r_overheadsprites_scalex.value, left); VectorScale(r_refdef.view.up, ent->scale * r_overheadsprites_scaley.value, up); VectorSubtract(org, r_refdef.view.origin, middle); VectorNormalize(middle); // offset and rotate dir_angle = r_overheadsprites_perspective.value * (1 - fabs(DotProduct(middle, r_refdef.view.forward))); up[2] = up[2] + dir_angle; VectorNormalize(up); VectorScale(up, ent->scale * r_overheadsprites_scaley.value, up); // offset (move nearer to player, yz is camera plane) org[0] = org[0] - middle[0]*r_overheadsprites_pushback.value; org[1] = org[1] - middle[1]*r_overheadsprites_pushback.value; org[2] = org[2] - middle[2]*r_overheadsprites_pushback.value; // little perspective effect up[2] = up[2] + dir_angle * 0.3; // a bit of counter-camera rotation up[0] = up[0] + r_refdef.view.forward[0] * 0.07; up[1] = up[1] + r_refdef.view.forward[1] * 0.07; up[2] = up[2] + r_refdef.view.forward[2] * 0.07; break; } // LordHavoc: interpolated sprite rendering for (i = 0;i < MAX_FRAMEBLENDS;i++) { if (ent->frameblend[i].lerp >= 0.01f) { mspriteframe_t *frame; texture_t *texture; RSurf_ActiveCustomEntity(&identitymatrix, &identitymatrix, ent->flags, 0, ent->colormod[0], ent->colormod[1], ent->colormod[2], ent->alpha * ent->frameblend[i].lerp, 4, vertex3f, spritetexcoord2f, NULL, NULL, NULL, NULL, 2, polygonelement3i, polygonelement3s, false, false); frame = model->sprite.sprdata_frames + ent->frameblend[i].subframe; texture = R_GetCurrentTexture(model->data_textures + ent->frameblend[i].subframe); // lit sprite by lightgrid if it is not fullbright, lit only ambient if (!(texture->currentmaterialflags & MATERIALFLAG_FULLBRIGHT)) VectorAdd(ent->modellight_ambient, ent->modellight_diffuse, rsurface.modellight_ambient); // sprites dont use lightdirection // SPR_LABEL should not use depth test AT ALL if(model->sprite.sprnum_type == SPR_LABEL || model->sprite.sprnum_type == SPR_LABEL_SCALE) if(texture->currentmaterialflags & MATERIALFLAG_SHORTDEPTHRANGE) texture->currentmaterialflags = (texture->currentmaterialflags & ~MATERIALFLAG_SHORTDEPTHRANGE) | MATERIALFLAG_NODEPTHTEST; if(edge) { // FIXME:: save vectors/origin and re-rotate? necessary if the hotspot can change per frame R_RotateSprite(frame, org, left, up, edge, dir_angle); edge = 0; } R_CalcSprite_Vertex3f(vertex3f, org, left, up, frame->left, frame->right, frame->down, frame->up); R_DrawCustomSurface_Texture(texture, &identitymatrix, texture->currentmaterialflags, 0, 4, 0, 2, false, false); } } rsurface.entity = NULL; }