示例#1
0
文件: view.c 项目: DrBeef/QuakeGVR
/*
 * 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;
}
示例#2
0
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;
}