/* r_sprite.c (description) 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: Free Software Foundation, Inc. 59 Temple Place - Suite 330 Boston, MA 02111-1307, USA $Id$ */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #ifdef HAVE_STRING_H # include #endif #ifdef HAVE_STRINGS_H # include #endif #include #include "QF/console.h" #include "QF/sys.h" #include "client.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; 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]; 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) { 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; } void R_SetupAndDrawSprite (void) { 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 (); } 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; } 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 (); }