/* | |
Copyright (C) 1996-1997 Id Software, Inc. | |
This program is free software; you can redistribute it and/or | |
modify it under the terms of the GNU General Public License | |
as published by the Free Software Foundation; either version 2 | |
of the License, or (at your option) any later version. | |
This program is distributed in the hope that it will be useful, | |
but WITHOUT ANY WARRANTY; without even the implied warranty of | |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | |
See the GNU General Public License for more details. | |
You should have received a copy of the GNU General Public License | |
along with this program; if not, write to the Free Software | |
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |
*/ | |
// r_sprite.c | |
#include "quakedef.h" | |
#include "r_local.h" | |
static int clip_current; | |
static vec5_t clip_verts[2][MAXWORKINGVERTS]; | |
static int sprite_width, sprite_height; | |
spritedesc_t r_spritedesc; | |
/* | |
================ | |
R_RotateSprite | |
================ | |
*/ | |
void R_RotateSprite (float beamlength) | |
{ | |
vec3_t vec; | |
if (beamlength == 0.0) | |
return; | |
VectorScale (r_spritedesc.vpn, -beamlength, vec); | |
VectorAdd (r_entorigin, vec, r_entorigin); | |
VectorSubtract (modelorg, vec, modelorg); | |
} | |
/* | |
============= | |
R_ClipSpriteFace | |
Clips the winding at clip_verts[clip_current] and changes clip_current | |
Throws out the back side | |
============== | |
*/ | |
int R_ClipSpriteFace (int nump, clipplane_t *pclipplane) | |
{ | |
int i, outcount; | |
float dists[MAXWORKINGVERTS+1]; | |
float frac, clipdist, *pclipnormal; | |
float *in, *instep, *outstep, *vert2; | |
clipdist = pclipplane->dist; | |
pclipnormal = pclipplane->normal; | |
// calc dists | |
if (clip_current) | |
{ | |
in = clip_verts[1][0]; | |
outstep = clip_verts[0][0]; | |
clip_current = 0; | |
} | |
else | |
{ | |
in = clip_verts[0][0]; | |
outstep = clip_verts[1][0]; | |
clip_current = 1; | |
} | |
instep = in; | |
for (i=0 ; i<nump ; i++, instep += sizeof (vec5_t) / sizeof (float)) | |
{ | |
dists[i] = DotProduct (instep, pclipnormal) - clipdist; | |
} | |
// handle wraparound case | |
dists[nump] = dists[0]; | |
Q_memcpy (instep, in, sizeof (vec5_t)); | |
// clip the winding | |
instep = in; | |
outcount = 0; | |
for (i=0 ; i<nump ; i++, instep += sizeof (vec5_t) / sizeof (float)) | |
{ | |
if (dists[i] >= 0) | |
{ | |
Q_memcpy (outstep, instep, sizeof (vec5_t)); | |
outstep += sizeof (vec5_t) / sizeof (float); | |
outcount++; | |
} | |
if (dists[i] == 0 || dists[i+1] == 0) | |
continue; | |
if ( (dists[i] > 0) == (dists[i+1] > 0) ) | |
continue; | |
// split it into a new vertex | |
frac = dists[i] / (dists[i] - dists[i+1]); | |
vert2 = instep + sizeof (vec5_t) / sizeof (float); | |
outstep[0] = instep[0] + frac*(vert2[0] - instep[0]); | |
outstep[1] = instep[1] + frac*(vert2[1] - instep[1]); | |
outstep[2] = instep[2] + frac*(vert2[2] - instep[2]); | |
outstep[3] = instep[3] + frac*(vert2[3] - instep[3]); | |
outstep[4] = instep[4] + frac*(vert2[4] - instep[4]); | |
outstep += sizeof (vec5_t) / sizeof (float); | |
outcount++; | |
} | |
return outcount; | |
} | |
/* | |
================ | |
R_SetupAndDrawSprite | |
================ | |
*/ | |
void R_SetupAndDrawSprite () | |
{ | |
int i, nump; | |
float dot, scale, *pv; | |
vec5_t *pverts; | |
vec3_t left, up, right, down, transformed, local; | |
emitpoint_t outverts[MAXWORKINGVERTS+1], *pout; | |
dot = DotProduct (r_spritedesc.vpn, modelorg); | |
// backface cull | |
if (dot >= 0) | |
return; | |
// build the sprite poster in worldspace | |
VectorScale (r_spritedesc.vright, r_spritedesc.pspriteframe->right, right); | |
VectorScale (r_spritedesc.vup, r_spritedesc.pspriteframe->up, up); | |
VectorScale (r_spritedesc.vright, r_spritedesc.pspriteframe->left, left); | |
VectorScale (r_spritedesc.vup, r_spritedesc.pspriteframe->down, down); | |
pverts = clip_verts[0]; | |
pverts[0][0] = r_entorigin[0] + up[0] + left[0]; | |
pverts[0][1] = r_entorigin[1] + up[1] + left[1]; | |
pverts[0][2] = r_entorigin[2] + up[2] + left[2]; | |
pverts[0][3] = 0; | |
pverts[0][4] = 0; | |
pverts[1][0] = r_entorigin[0] + up[0] + right[0]; | |
pverts[1][1] = r_entorigin[1] + up[1] + right[1]; | |
pverts[1][2] = r_entorigin[2] + up[2] + right[2]; | |
pverts[1][3] = sprite_width; | |
pverts[1][4] = 0; | |
pverts[2][0] = r_entorigin[0] + down[0] + right[0]; | |
pverts[2][1] = r_entorigin[1] + down[1] + right[1]; | |
pverts[2][2] = r_entorigin[2] + down[2] + right[2]; | |
pverts[2][3] = sprite_width; | |
pverts[2][4] = sprite_height; | |
pverts[3][0] = r_entorigin[0] + down[0] + left[0]; | |
pverts[3][1] = r_entorigin[1] + down[1] + left[1]; | |
pverts[3][2] = r_entorigin[2] + down[2] + left[2]; | |
pverts[3][3] = 0; | |
pverts[3][4] = sprite_height; | |
// clip to the frustum in worldspace | |
nump = 4; | |
clip_current = 0; | |
for (i=0 ; i<4 ; i++) | |
{ | |
nump = R_ClipSpriteFace (nump, &view_clipplanes[i]); | |
if (nump < 3) | |
return; | |
if (nump >= MAXWORKINGVERTS) | |
Sys_Error("R_SetupAndDrawSprite: too many points"); | |
} | |
// transform vertices into viewspace and project | |
pv = &clip_verts[clip_current][0][0]; | |
r_spritedesc.nearzi = -999999; | |
for (i=0 ; i<nump ; i++) | |
{ | |
VectorSubtract (pv, r_origin, local); | |
TransformVector (local, transformed); | |
if (transformed[2] < NEAR_CLIP) | |
transformed[2] = NEAR_CLIP; | |
pout = &outverts[i]; | |
pout->zi = 1.0 / transformed[2]; | |
if (pout->zi > r_spritedesc.nearzi) | |
r_spritedesc.nearzi = pout->zi; | |
pout->s = pv[3]; | |
pout->t = pv[4]; | |
scale = xscale * pout->zi; | |
pout->u = (xcenter + scale * transformed[0]); | |
scale = yscale * pout->zi; | |
pout->v = (ycenter - scale * transformed[1]); | |
pv += sizeof (vec5_t) / sizeof (*pv); | |
} | |
// draw it | |
r_spritedesc.nump = nump; | |
r_spritedesc.pverts = outverts; | |
D_DrawSprite (); | |
} | |
/* | |
================ | |
R_GetSpriteframe | |
================ | |
*/ | |
mspriteframe_t *R_GetSpriteframe (msprite_t *psprite) | |
{ | |
mspritegroup_t *pspritegroup; | |
mspriteframe_t *pspriteframe; | |
int i, numframes, frame; | |
float *pintervals, fullinterval, targettime, time; | |
frame = currententity->frame; | |
if ((frame >= psprite->numframes) || (frame < 0)) | |
{ | |
Con_Printf ("R_DrawSprite: no such frame %d\n", frame); | |
frame = 0; | |
} | |
if (psprite->frames[frame].type == SPR_SINGLE) | |
{ | |
pspriteframe = psprite->frames[frame].frameptr; | |
} | |
else | |
{ | |
pspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr; | |
pintervals = pspritegroup->intervals; | |
numframes = pspritegroup->numframes; | |
fullinterval = pintervals[numframes-1]; | |
time = cl.time + currententity->syncbase; | |
// when loading in Mod_LoadSpriteGroup, we guaranteed all interval values | |
// are positive, so we don't have to worry about division by 0 | |
targettime = time - ((int)(time / fullinterval)) * fullinterval; | |
for (i=0 ; i<(numframes-1) ; i++) | |
{ | |
if (pintervals[i] > targettime) | |
break; | |
} | |
pspriteframe = pspritegroup->frames[i]; | |
} | |
return pspriteframe; | |
} | |
/* | |
================ | |
R_DrawSprite | |
================ | |
*/ | |
void R_DrawSprite (void) | |
{ | |
int i; | |
msprite_t *psprite; | |
vec3_t tvec; | |
float dot, angle, sr, cr; | |
psprite = currententity->model->cache.data; | |
r_spritedesc.pspriteframe = R_GetSpriteframe (psprite); | |
sprite_width = r_spritedesc.pspriteframe->width; | |
sprite_height = r_spritedesc.pspriteframe->height; | |
// TODO: make this caller-selectable | |
if (psprite->type == SPR_FACING_UPRIGHT) | |
{ | |
// generate the sprite's axes, with vup straight up in worldspace, and | |
// r_spritedesc.vright perpendicular to modelorg. | |
// This will not work if the view direction is very close to straight up or | |
// down, because the cross product will be between two nearly parallel | |
// vectors and starts to approach an undefined state, so we don't draw if | |
// the two vectors are less than 1 degree apart | |
tvec[0] = -modelorg[0]; | |
tvec[1] = -modelorg[1]; | |
tvec[2] = -modelorg[2]; | |
VectorNormalize (tvec); | |
dot = tvec[2]; // same as DotProduct (tvec, r_spritedesc.vup) because | |
// r_spritedesc.vup is 0, 0, 1 | |
if ((dot > 0.999848) || (dot < -0.999848)) // cos(1 degree) = 0.999848 | |
return; | |
r_spritedesc.vup[0] = 0; | |
r_spritedesc.vup[1] = 0; | |
r_spritedesc.vup[2] = 1; | |
r_spritedesc.vright[0] = tvec[1]; | |
// CrossProduct(r_spritedesc.vup, -modelorg, | |
r_spritedesc.vright[1] = -tvec[0]; | |
// r_spritedesc.vright) | |
r_spritedesc.vright[2] = 0; | |
VectorNormalize (r_spritedesc.vright); | |
r_spritedesc.vpn[0] = -r_spritedesc.vright[1]; | |
r_spritedesc.vpn[1] = r_spritedesc.vright[0]; | |
r_spritedesc.vpn[2] = 0; | |
// CrossProduct (r_spritedesc.vright, r_spritedesc.vup, | |
// r_spritedesc.vpn) | |
} | |
else if (psprite->type == SPR_VP_PARALLEL) | |
{ | |
// generate the sprite's axes, completely parallel to the viewplane. There | |
// are no problem situations, because the sprite is always in the same | |
// position relative to the viewer | |
for (i=0 ; i<3 ; i++) | |
{ | |
r_spritedesc.vup[i] = vup[i]; | |
r_spritedesc.vright[i] = vright[i]; | |
r_spritedesc.vpn[i] = vpn[i]; | |
} | |
} | |
else if (psprite->type == SPR_VP_PARALLEL_UPRIGHT) | |
{ | |
// generate the sprite's axes, with vup straight up in worldspace, and | |
// r_spritedesc.vright parallel to the viewplane. | |
// This will not work if the view direction is very close to straight up or | |
// down, because the cross product will be between two nearly parallel | |
// vectors and starts to approach an undefined state, so we don't draw if | |
// the two vectors are less than 1 degree apart | |
dot = vpn[2]; // same as DotProduct (vpn, r_spritedesc.vup) because | |
// r_spritedesc.vup is 0, 0, 1 | |
if ((dot > 0.999848) || (dot < -0.999848)) // cos(1 degree) = 0.999848 | |
return; | |
r_spritedesc.vup[0] = 0; | |
r_spritedesc.vup[1] = 0; | |
r_spritedesc.vup[2] = 1; | |
r_spritedesc.vright[0] = vpn[1]; | |
// CrossProduct (r_spritedesc.vup, vpn, | |
r_spritedesc.vright[1] = -vpn[0]; // r_spritedesc.vright) | |
r_spritedesc.vright[2] = 0; | |
VectorNormalize (r_spritedesc.vright); | |
r_spritedesc.vpn[0] = -r_spritedesc.vright[1]; | |
r_spritedesc.vpn[1] = r_spritedesc.vright[0]; | |
r_spritedesc.vpn[2] = 0; | |
// CrossProduct (r_spritedesc.vright, r_spritedesc.vup, | |
// r_spritedesc.vpn) | |
} | |
else if (psprite->type == SPR_ORIENTED) | |
{ | |
// generate the sprite's axes, according to the sprite's world orientation | |
AngleVectors (currententity->angles, r_spritedesc.vpn, | |
r_spritedesc.vright, r_spritedesc.vup); | |
} | |
else if (psprite->type == SPR_VP_PARALLEL_ORIENTED) | |
{ | |
// generate the sprite's axes, parallel to the viewplane, but rotated in | |
// that plane around the center according to the sprite entity's roll | |
// angle. So vpn stays the same, but vright and vup rotate | |
angle = currententity->angles[ROLL] * (M_PI*2 / 360); | |
sr = sin(angle); | |
cr = cos(angle); | |
for (i=0 ; i<3 ; i++) | |
{ | |
r_spritedesc.vpn[i] = vpn[i]; | |
r_spritedesc.vright[i] = vright[i] * cr + vup[i] * sr; | |
r_spritedesc.vup[i] = vright[i] * -sr + vup[i] * cr; | |
} | |
} | |
else | |
{ | |
Sys_Error ("R_DrawSprite: Bad sprite type %d", psprite->type); | |
} | |
R_RotateSprite (psprite->beamlength); | |
R_SetupAndDrawSprite (); | |
} | |