1/*
2Copyright (C) 1996-1997 Id Software, Inc.
3
4This program is free software; you can redistribute it and/or
5modify it under the terms of the GNU General Public License
6as published by the Free Software Foundation; either version 2
7of the License, or (at your option) any later version.
8
9This program is distributed in the hope that it will be useful,
10but WITHOUT ANY WARRANTY; without even the implied warranty of
11MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13See the GNU General Public License for more details.
14
15You should have received a copy of the GNU General Public License
16along with this program; if not, write to the Free Software
17Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18
19*/
20// r_sprite.c
21
22#include "quakedef.h"
23#include "r_local.h"
24
25static int				clip_current;
26static vec5_t			clip_verts[2][MAXWORKINGVERTS];
27static int				sprite_width, sprite_height;
28
29spritedesc_t			r_spritedesc;
30
31
32/*
33================
34R_RotateSprite
35================
36*/
37void R_RotateSprite (float beamlength)
38{
39	vec3_t	vec;
40
41	if (beamlength == 0.0)
42		return;
43
44	VectorScale (r_spritedesc.vpn, -beamlength, vec);
45	VectorAdd (r_entorigin, vec, r_entorigin);
46	VectorSubtract (modelorg, vec, modelorg);
47}
48
49
50/*
51=============
52R_ClipSpriteFace
53
54Clips the winding at clip_verts[clip_current] and changes clip_current
55Throws out the back side
56==============
57*/
58int R_ClipSpriteFace (int nump, clipplane_t *pclipplane)
59{
60	int		i, outcount;
61	float	dists[MAXWORKINGVERTS+1];
62	float	frac, clipdist, *pclipnormal;
63	float	*in, *instep, *outstep, *vert2;
64
65	clipdist = pclipplane->dist;
66	pclipnormal = pclipplane->normal;
67
68// calc dists
69	if (clip_current)
70	{
71		in = clip_verts[1][0];
72		outstep = clip_verts[0][0];
73		clip_current = 0;
74	}
75	else
76	{
77		in = clip_verts[0][0];
78		outstep = clip_verts[1][0];
79		clip_current = 1;
80	}
81
82	instep = in;
83	for (i=0 ; i<nump ; i++, instep += sizeof (vec5_t) / sizeof (float))
84	{
85		dists[i] = DotProduct (instep, pclipnormal) - clipdist;
86	}
87
88// handle wraparound case
89	dists[nump] = dists[0];
90	Q_memcpy (instep, in, sizeof (vec5_t));
91
92
93// clip the winding
94	instep = in;
95	outcount = 0;
96
97	for (i=0 ; i<nump ; i++, instep += sizeof (vec5_t) / sizeof (float))
98	{
99		if (dists[i] >= 0)
100		{
101			Q_memcpy (outstep, instep, sizeof (vec5_t));
102			outstep += sizeof (vec5_t) / sizeof (float);
103			outcount++;
104		}
105
106		if (dists[i] == 0 || dists[i+1] == 0)
107			continue;
108
109		if ( (dists[i] > 0) == (dists[i+1] > 0) )
110			continue;
111
112	// split it into a new vertex
113		frac = dists[i] / (dists[i] - dists[i+1]);
114
115		vert2 = instep + sizeof (vec5_t) / sizeof (float);
116
117		outstep[0] = instep[0] + frac*(vert2[0] - instep[0]);
118		outstep[1] = instep[1] + frac*(vert2[1] - instep[1]);
119		outstep[2] = instep[2] + frac*(vert2[2] - instep[2]);
120		outstep[3] = instep[3] + frac*(vert2[3] - instep[3]);
121		outstep[4] = instep[4] + frac*(vert2[4] - instep[4]);
122
123		outstep += sizeof (vec5_t) / sizeof (float);
124		outcount++;
125	}
126
127	return outcount;
128}
129
130
131/*
132================
133R_SetupAndDrawSprite
134================
135*/
136void R_SetupAndDrawSprite ()
137{
138	int			i, nump;
139	float		dot, scale, *pv;
140	vec5_t		*pverts;
141	vec3_t		left, up, right, down, transformed, local;
142	emitpoint_t	outverts[MAXWORKINGVERTS+1], *pout;
143
144	dot = DotProduct (r_spritedesc.vpn, modelorg);
145
146// backface cull
147	if (dot >= 0)
148		return;
149
150// build the sprite poster in worldspace
151	VectorScale (r_spritedesc.vright, r_spritedesc.pspriteframe->right, right);
152	VectorScale (r_spritedesc.vup, r_spritedesc.pspriteframe->up, up);
153	VectorScale (r_spritedesc.vright, r_spritedesc.pspriteframe->left, left);
154	VectorScale (r_spritedesc.vup, r_spritedesc.pspriteframe->down, down);
155
156	pverts = clip_verts[0];
157
158	pverts[0][0] = r_entorigin[0] + up[0] + left[0];
159	pverts[0][1] = r_entorigin[1] + up[1] + left[1];
160	pverts[0][2] = r_entorigin[2] + up[2] + left[2];
161	pverts[0][3] = 0;
162	pverts[0][4] = 0;
163
164	pverts[1][0] = r_entorigin[0] + up[0] + right[0];
165	pverts[1][1] = r_entorigin[1] + up[1] + right[1];
166	pverts[1][2] = r_entorigin[2] + up[2] + right[2];
167	pverts[1][3] = sprite_width;
168	pverts[1][4] = 0;
169
170	pverts[2][0] = r_entorigin[0] + down[0] + right[0];
171	pverts[2][1] = r_entorigin[1] + down[1] + right[1];
172	pverts[2][2] = r_entorigin[2] + down[2] + right[2];
173	pverts[2][3] = sprite_width;
174	pverts[2][4] = sprite_height;
175
176	pverts[3][0] = r_entorigin[0] + down[0] + left[0];
177	pverts[3][1] = r_entorigin[1] + down[1] + left[1];
178	pverts[3][2] = r_entorigin[2] + down[2] + left[2];
179	pverts[3][3] = 0;
180	pverts[3][4] = sprite_height;
181
182// clip to the frustum in worldspace
183	nump = 4;
184	clip_current = 0;
185
186	for (i=0 ; i<4 ; i++)
187	{
188		nump = R_ClipSpriteFace (nump, &view_clipplanes[i]);
189		if (nump < 3)
190			return;
191		if (nump >= MAXWORKINGVERTS)
192			Sys_Error("R_SetupAndDrawSprite: too many points");
193	}
194
195// transform vertices into viewspace and project
196	pv = &clip_verts[clip_current][0][0];
197	r_spritedesc.nearzi = -999999;
198
199	for (i=0 ; i<nump ; i++)
200	{
201		VectorSubtract (pv, r_origin, local);
202		TransformVector (local, transformed);
203
204		if (transformed[2] < NEAR_CLIP)
205			transformed[2] = NEAR_CLIP;
206
207		pout = &outverts[i];
208		pout->zi = 1.0 / transformed[2];
209		if (pout->zi > r_spritedesc.nearzi)
210			r_spritedesc.nearzi = pout->zi;
211
212		pout->s = pv[3];
213		pout->t = pv[4];
214
215		scale = xscale * pout->zi;
216		pout->u = (xcenter + scale * transformed[0]);
217
218		scale = yscale * pout->zi;
219		pout->v = (ycenter - scale * transformed[1]);
220
221		pv += sizeof (vec5_t) / sizeof (*pv);
222	}
223
224// draw it
225	r_spritedesc.nump = nump;
226	r_spritedesc.pverts = outverts;
227	D_DrawSprite ();
228}
229
230
231/*
232================
233R_GetSpriteframe
234================
235*/
236mspriteframe_t *R_GetSpriteframe (msprite_t *psprite)
237{
238	mspritegroup_t	*pspritegroup;
239	mspriteframe_t	*pspriteframe;
240	int				i, numframes, frame;
241	float			*pintervals, fullinterval, targettime, time;
242
243	frame = currententity->frame;
244
245	if ((frame >= psprite->numframes) || (frame < 0))
246	{
247		Con_Printf ("R_DrawSprite: no such frame %d\n", frame);
248		frame = 0;
249	}
250
251	if (psprite->frames[frame].type == SPR_SINGLE)
252	{
253		pspriteframe = psprite->frames[frame].frameptr;
254	}
255	else
256	{
257		pspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr;
258		pintervals = pspritegroup->intervals;
259		numframes = pspritegroup->numframes;
260		fullinterval = pintervals[numframes-1];
261
262		time = cl.time + currententity->syncbase;
263
264	// when loading in Mod_LoadSpriteGroup, we guaranteed all interval values
265	// are positive, so we don't have to worry about division by 0
266		targettime = time - ((int)(time / fullinterval)) * fullinterval;
267
268		for (i=0 ; i<(numframes-1) ; i++)
269		{
270			if (pintervals[i] > targettime)
271				break;
272		}
273
274		pspriteframe = pspritegroup->frames[i];
275	}
276
277	return pspriteframe;
278}
279
280
281/*
282================
283R_DrawSprite
284================
285*/
286void R_DrawSprite (void)
287{
288	int				i;
289	msprite_t		*psprite;
290	vec3_t			tvec;
291	float			dot, angle, sr, cr;
292
293	psprite = currententity->model->cache.data;
294
295	r_spritedesc.pspriteframe = R_GetSpriteframe (psprite);
296
297	sprite_width = r_spritedesc.pspriteframe->width;
298	sprite_height = r_spritedesc.pspriteframe->height;
299
300// TODO: make this caller-selectable
301	if (psprite->type == SPR_FACING_UPRIGHT)
302	{
303	// generate the sprite's axes, with vup straight up in worldspace, and
304	// r_spritedesc.vright perpendicular to modelorg.
305	// This will not work if the view direction is very close to straight up or
306	// down, because the cross product will be between two nearly parallel
307	// vectors and starts to approach an undefined state, so we don't draw if
308	// the two vectors are less than 1 degree apart
309		tvec[0] = -modelorg[0];
310		tvec[1] = -modelorg[1];
311		tvec[2] = -modelorg[2];
312		VectorNormalize (tvec);
313		dot = tvec[2];	// same as DotProduct (tvec, r_spritedesc.vup) because
314						//  r_spritedesc.vup is 0, 0, 1
315		if ((dot > 0.999848) || (dot < -0.999848))	// cos(1 degree) = 0.999848
316			return;
317		r_spritedesc.vup[0] = 0;
318		r_spritedesc.vup[1] = 0;
319		r_spritedesc.vup[2] = 1;
320		r_spritedesc.vright[0] = tvec[1];
321								// CrossProduct(r_spritedesc.vup, -modelorg,
322		r_spritedesc.vright[1] = -tvec[0];
323								//              r_spritedesc.vright)
324		r_spritedesc.vright[2] = 0;
325		VectorNormalize (r_spritedesc.vright);
326		r_spritedesc.vpn[0] = -r_spritedesc.vright[1];
327		r_spritedesc.vpn[1] = r_spritedesc.vright[0];
328		r_spritedesc.vpn[2] = 0;
329					// CrossProduct (r_spritedesc.vright, r_spritedesc.vup,
330					//  r_spritedesc.vpn)
331	}
332	else if (psprite->type == SPR_VP_PARALLEL)
333	{
334	// generate the sprite's axes, completely parallel to the viewplane. There
335	// are no problem situations, because the sprite is always in the same
336	// position relative to the viewer
337		for (i=0 ; i<3 ; i++)
338		{
339			r_spritedesc.vup[i] = vup[i];
340			r_spritedesc.vright[i] = vright[i];
341			r_spritedesc.vpn[i] = vpn[i];
342		}
343	}
344	else if (psprite->type == SPR_VP_PARALLEL_UPRIGHT)
345	{
346	// generate the sprite's axes, with vup straight up in worldspace, and
347	// r_spritedesc.vright parallel to the viewplane.
348	// This will not work if the view direction is very close to straight up or
349	// down, because the cross product will be between two nearly parallel
350	// vectors and starts to approach an undefined state, so we don't draw if
351	// the two vectors are less than 1 degree apart
352		dot = vpn[2];	// same as DotProduct (vpn, r_spritedesc.vup) because
353						//  r_spritedesc.vup is 0, 0, 1
354		if ((dot > 0.999848) || (dot < -0.999848))	// cos(1 degree) = 0.999848
355			return;
356		r_spritedesc.vup[0] = 0;
357		r_spritedesc.vup[1] = 0;
358		r_spritedesc.vup[2] = 1;
359		r_spritedesc.vright[0] = vpn[1];
360										// CrossProduct (r_spritedesc.vup, vpn,
361		r_spritedesc.vright[1] = -vpn[0];	//  r_spritedesc.vright)
362		r_spritedesc.vright[2] = 0;
363		VectorNormalize (r_spritedesc.vright);
364		r_spritedesc.vpn[0] = -r_spritedesc.vright[1];
365		r_spritedesc.vpn[1] = r_spritedesc.vright[0];
366		r_spritedesc.vpn[2] = 0;
367					// CrossProduct (r_spritedesc.vright, r_spritedesc.vup,
368					//  r_spritedesc.vpn)
369	}
370	else if (psprite->type == SPR_ORIENTED)
371	{
372	// generate the sprite's axes, according to the sprite's world orientation
373		AngleVectors (currententity->angles, r_spritedesc.vpn,
374					  r_spritedesc.vright, r_spritedesc.vup);
375	}
376	else if (psprite->type == SPR_VP_PARALLEL_ORIENTED)
377	{
378	// generate the sprite's axes, parallel to the viewplane, but rotated in
379	// that plane around the center according to the sprite entity's roll
380	// angle. So vpn stays the same, but vright and vup rotate
381		angle = currententity->angles[ROLL] * (M_PI*2 / 360);
382		sr = sin(angle);
383		cr = cos(angle);
384
385		for (i=0 ; i<3 ; i++)
386		{
387			r_spritedesc.vpn[i] = vpn[i];
388			r_spritedesc.vright[i] = vright[i] * cr + vup[i] * sr;
389			r_spritedesc.vup[i] = vright[i] * -sr + vup[i] * cr;
390		}
391	}
392	else
393	{
394		Sys_Error ("R_DrawSprite: Bad sprite type %d", psprite->type);
395	}
396
397	R_RotateSprite (psprite->beamlength);
398
399	R_SetupAndDrawSprite ();
400}
401
402