1 /*
2 ===========================================================================
3 Copyright (C) 1999 - 2005, Id Software, Inc.
4 Copyright (C) 2000 - 2013, Raven Software, Inc.
5 Copyright (C) 2001 - 2013, Activision, Inc.
6 Copyright (C) 2013 - 2015, OpenJK contributors
7 
8 This file is part of the OpenJK source code.
9 
10 OpenJK is free software; you can redistribute it and/or modify it
11 under the terms of the GNU General Public License version 2 as
12 published by the Free Software Foundation.
13 
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 GNU General Public License for more details.
18 
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, see <http://www.gnu.org/licenses/>.
21 ===========================================================================
22 */
23 
24 // cg_effects.c -- these functions generate localentities
25 
26 #include "cg_local.h"
27 #include "cg_media.h"
28 
29 #if !defined(FX_SCHEDULER_H_INC)
30 	#include "FxScheduler.h"
31 #endif
32 
33 //void DoBolt( vec3_t m_origin, vec3_t m_origin2, float m_scale, float m_deviation );
34 
35 /*
36 ====================
37 CG_MakeExplosion
38 ====================
39 */
40 /*
41 localEntity_t *CG_MakeExplosion( vec3_t origin, vec3_t dir,
42 								qhandle_t hModel, int numFrames, qhandle_t shader,
43 								int msec, qboolean isSprite, float scale, int flags )
44 {
45 	float			ang = 0;
46 	localEntity_t	*ex;
47 	int				offset;
48 	vec3_t			tmpVec, newOrigin;
49 
50 	if ( msec <= 0 ) {
51 		CG_Error( "CG_MakeExplosion: msec = %i", msec );
52 	}
53 
54 	// skew the time a bit so they aren't all in sync
55 	offset = rand() & 63;
56 
57 	ex = CG_AllocLocalEntity();
58 	if ( isSprite ) {
59 		ex->leType = LE_SPRITE_EXPLOSION;
60 		ex->refEntity.rotation = rand() % 360;
61 		ex->radius = scale;
62 		VectorScale( dir, 16, tmpVec );
63 		VectorAdd( tmpVec, origin, newOrigin );
64 	} else {
65 		ex->leType = LE_EXPLOSION;
66 		VectorCopy( origin, newOrigin );
67 
68 		// set axis with random rotate when necessary
69 		if ( !dir )
70 		{
71 			AxisClear( ex->refEntity.axis );
72 		}
73 		else
74 		{
75 			if ( !(flags & LEF_NO_RANDOM_ROTATE) )
76 				ang = rand() % 360;
77 			VectorCopy( dir, ex->refEntity.axis[0] );
78 			RotateAroundDirection( ex->refEntity.axis, ang );
79 		}
80 	}
81 
82 	ex->startTime = cg.time - offset;
83 	ex->endTime = ex->startTime + msec;
84 
85 	// bias the time so all shader effects start correctly
86 	ex->refEntity.shaderTime = ex->startTime / 1000.0f;
87 
88 	ex->refEntity.hModel = hModel;
89 	ex->refEntity.customShader = shader;
90 	ex->lifeRate = (float)numFrames / msec;
91 	ex->leFlags = flags;
92 
93 	//Scale the explosion
94 	if (scale != 1) {
95 		ex->refEntity.nonNormalizedAxes = qtrue;
96 
97 		VectorScale( ex->refEntity.axis[0], scale, ex->refEntity.axis[0] );
98 		VectorScale( ex->refEntity.axis[1], scale, ex->refEntity.axis[1] );
99 		VectorScale( ex->refEntity.axis[2], scale, ex->refEntity.axis[2] );
100 	}
101 	// set origin
102 	VectorCopy ( newOrigin, ex->refEntity.origin);
103 	VectorCopy ( newOrigin, ex->refEntity.oldorigin );
104 
105 	ex->color[0] = ex->color[1] = ex->color[2] = 1.0;
106 
107 	return ex;
108 }
109 */
110 // When calling this version, just pass in a zero for the flags
111 //localEntity_t *CG_MakeExplosion( vec3_t origin, vec3_t dir,
112 //								qhandle_t hModel, int numFrames, qhandle_t shader,
113 //								int msec, qboolean isSprite, float scale ) {
114 //	return CG_MakeExplosion( origin, dir, hModel, numFrames, shader, msec, isSprite, scale, 0 );
115 //}
116 
117 /*
118 ====================
119 CG_AddTempLight
120 ====================
121 */
CG_AddTempLight(vec3_t origin,float scale,vec3_t color,int msec)122 localEntity_t *CG_AddTempLight( vec3_t origin, float scale, vec3_t color, int msec )
123 {
124 	localEntity_t	*ex;
125 
126 	if ( msec <= 0 ) {
127 		CG_Error( "CG_AddTempLight: msec = %i", msec );
128 	}
129 
130 	ex = CG_AllocLocalEntity();
131 
132 	ex->leType = LE_LIGHT;
133 
134 	ex->startTime = cg.time;
135 	ex->endTime = ex->startTime + msec;
136 
137 	// set origin
138 	VectorCopy ( origin, ex->refEntity.origin);
139 	VectorCopy ( origin, ex->refEntity.oldorigin );
140 
141 	VectorCopy( color, ex->lightColor );
142 	ex->light = scale;
143 
144 	return ex;
145 }
146 
147 /*
148 -------------------------
149 CG_ExplosionEffects
150 
151 Used to find the player and shake the camera if close enough
152 intensity ranges from 1 (minor tremble) to 16 (major quake)
153 -------------------------
154 */
155 
CG_ExplosionEffects(vec3_t origin,float intensity,int radius,int time)156 void CG_ExplosionEffects( vec3_t origin, float intensity, int radius, int time )
157 {
158 	//FIXME: When exactly is the vieworg calculated in relation to the rest of the frame?s
159 
160 	vec3_t	dir;
161 	float	dist, intensityScale;
162 	float	realIntensity;
163 
164 	VectorSubtract( cg.refdef.vieworg, origin, dir );
165 	dist = VectorNormalize( dir );
166 
167 	//Use the dir to add kick to the explosion
168 
169 	if ( dist > radius )
170 		return;
171 
172 	intensityScale = 1 - ( dist / (float) radius );
173 	realIntensity = intensity * intensityScale;
174 
175 	CGCam_Shake( realIntensity, time );
176 }
177 
178 
179 /*
180 -------------------------
181 CG_SurfaceExplosion
182 
183 Adds an explosion to a surface
184 -------------------------
185 */
186 
187 /*
188 #define NUM_SPARKS		12
189 #define NUM_PUFFS		1
190 #define NUM_EXPLOSIONS	4
191 
192 void CG_SurfaceExplosion( vec3_t origin, vec3_t normal, float radius, float shake_speed, qboolean smoke )
193 {
194 	localEntity_t	*le;
195 	//FXTrail			*particle;
196 	vec3_t			direction, new_org;
197 	vec3_t			velocity		= { 0, 0, 0 };
198 	vec3_t			temp_org, temp_vel;
199 	float			scale, dscale;
200 	int				i, numSparks;
201 
202 	//Sparks
203 	numSparks = 16 + (Q_flrand(0.0f, 1.0f) * 16.0f);
204 
205 	for ( i = 0; i < numSparks; i++ )
206 	{
207 		scale = 0.25f + (Q_flrand(0.0f, 1.0f) * 2.0f);
208 		dscale = -scale*0.5;
209 
210 		particle = FX_AddTrail( origin,
211 								NULL,
212 								NULL,
213 								32.0f,
214 								-64.0f,
215 								scale,
216 								-scale,
217 								1.0f,
218 								0.0f,
219 								0.25f,
220 								4000.0f,
221 								cgs.media.sparkShader,
222 								rand() & FXF_BOUNCE);
223 		if ( particle == NULL )
224 			return;
225 
226 		FXE_Spray( normal, 500, 150, 1.0f, 768 + (rand() & 255), (FXPrimitive *) particle );
227 	}
228 
229 	//Smoke
230 	//Move this out a little from the impact surface
231 	VectorMA( origin, 4, normal, new_org );
232 	VectorSet( velocity, 0.0f, 0.0f, 16.0f );
233 
234 	for ( i = 0; i < 4; i++ )
235 	{
236 		VectorSet( temp_org, new_org[0] + (Q_flrand(-1.0f, 1.0f) * 16.0f), new_org[1] + (Q_flrand(-1.0f, 1.0f) * 16.0f), new_org[2] + (Q_flrand(0.0f, 1.0f) * 4.0f) );
237 		VectorSet( temp_vel, velocity[0] + (Q_flrand(-1.0f, 1.0f) * 8.0f), velocity[1] + (Q_flrand(-1.0f, 1.0f) * 8.0f), velocity[2] + (Q_flrand(-1.0f, 1.0f) * 8.0f) );
238 
239 		FX_AddSprite(	temp_org,
240 						temp_vel,
241 						NULL,
242 						64.0f + (Q_flrand(0.0f, 1.0f) * 32.0f),
243 						16.0f,
244 						1.0f,
245 						0.0f,
246 						20.0f + (Q_flrand(-1.0f, 1.0f) * 90.0f),
247 						0.5f,
248 						1500.0f,
249 						cgs.media.smokeShader, FXF_USE_ALPHA_CHAN );
250 	}
251 
252 	//Core of the explosion
253 
254 	//Orient the explosions to face the camera
255 	VectorSubtract( cg.refdef.vieworg, origin, direction );
256 	VectorNormalize( direction );
257 
258 	//Tag the last one with a light
259 	le = CG_MakeExplosion( origin, direction, cgs.media.explosionModel, 6, cgs.media.surfaceExplosionShader, 500, qfalse, radius * 0.02f + (Q_flrand(0.0f, 1.0f) * 0.3f) );
260 	le->light = 150;
261 	VectorSet( le->lightColor, 0.9f, 0.8f, 0.5f );
262 
263 	for ( i = 0; i < NUM_EXPLOSIONS-1; i ++)
264 	{
265 		VectorSet( new_org, (origin[0] + (16 + (Q_flrand(-1.0f, 1.0f) * 8))*Q_flrand(-1.0f, 1.0f)), (origin[1] + (16 + (Q_flrand(-1.0f, 1.0f) * 8))*Q_flrand(-1.0f, 1.0f)), (origin[2] + (16 + (Q_flrand(-1.0f, 1.0f) * 8))*Q_flrand(-1.0f, 1.0f)) );
266 		le = CG_MakeExplosion( new_org, direction, cgs.media.explosionModel, 6, cgs.media.surfaceExplosionShader, 300 + (rand() & 99), qfalse, radius * 0.05f + (Q_flrand(-1.0f, 1.0f) *0.3f) );
267 	}
268 
269 	//Shake the camera
270 	CG_ExplosionEffects( origin, shake_speed, 350, 750 );
271 
272 	// The level designers wanted to be able to turn the smoke spawners off.  The rationale is that they
273 	//	want to blow up catwalks and such that fall down...when that happens, it shouldn't really leave a mark
274 	//	and a smoke spewer at the explosion point...
275 	if ( smoke )
276 	{
277 		VectorMA( origin, -8, normal, temp_org );
278 //		FX_AddSpawner( temp_org, normal, NULL, NULL, 100, Q_flrand(0.0f, 1.0f)*25.0f, 5000.0f, (void *) CG_SmokeSpawn );
279 
280 		//Impact mark
281 		//FIXME: Replace mark
282 		//CG_ImpactMark( cgs.media.burnMarkShader, origin, normal, Q_flrand(0.0f, 1.0f)*360, 1,1,1,1, qfalse, 8, qfalse );
283 	}
284 }
285 */
286 /*
287 -------------------------
288 CG_MiscModelExplosion
289 
290 Adds an explosion to a misc model breakables
291 -------------------------
292 */
293 
CG_MiscModelExplosion(vec3_t mins,vec3_t maxs,int size,material_t chunkType)294 void CG_MiscModelExplosion( vec3_t mins, vec3_t maxs, int size, material_t chunkType )
295 {
296 	int		ct = 13;
297 	float	r;
298 	vec3_t	org, mid, dir;
299 	char	*effect = NULL, *effect2 = NULL;
300 
301 	VectorAdd( mins, maxs, mid );
302 	VectorScale( mid, 0.5f, mid );
303 
304 	switch( chunkType )
305 	{
306 	case MAT_GLASS:
307 		effect = "chunks/glassbreak";
308 		ct = 5;
309 		break;
310 	case MAT_GLASS_METAL:
311 		effect = "chunks/glassbreak";
312 		effect2 = "chunks/metalexplode";
313 		ct = 5;
314 		break;
315 	case MAT_ELECTRICAL:
316 	case MAT_ELEC_METAL:
317 		effect = "chunks/sparkexplode";
318 		ct = 5;
319 		break;
320 	case MAT_METAL:
321 	case MAT_METAL2:
322 	case MAT_METAL3:
323 	case MAT_CRATE1:
324 	case MAT_CRATE2:
325 		effect = "chunks/metalexplode";
326 		ct = 2;
327 		break;
328 	case MAT_GRATE1:
329 		effect = "chunks/grateexplode";
330 		ct = 8;
331 		break;
332 	case MAT_ROPE:
333 		ct = 20;
334 		effect = "chunks/ropebreak";
335 		break;
336 	case MAT_WHITE_METAL: //not sure what this crap is really supposed to be..
337 	case MAT_DRK_STONE:
338 	case MAT_LT_STONE:
339 	case MAT_GREY_STONE:
340 		switch( size )
341 		{
342 		case 2:
343 			effect = "chunks/rockbreaklg";
344 			break;
345 		case 1:
346 		default:
347 			effect = "chunks/rockbreakmed";
348 			break;
349 		}
350 	default:
351 		break;
352 	}
353 
354 	if ( !effect )
355 	{
356 		return;
357 	}
358 
359 	ct += 7 * size;
360 
361 	// FIXME: real precache .. VERify that these need to be here...don't think they would because the effects should be registered in g_breakable
362 	theFxScheduler.RegisterEffect( effect );
363 
364 	if ( effect2 )
365 	{
366 		// FIXME: real precache
367 		theFxScheduler.RegisterEffect( effect2 );
368 	}
369 
370 	// spawn chunk roughly in the bbox of the thing..
371 	for ( int i = 0; i < ct; i++ )
372 	{
373 		for( int j = 0; j < 3; j++ )
374 		{
375 			r = Q_flrand(0.0f, 1.0f) * 0.8f + 0.1f;
376 			org[j] = ( r * mins[j] + ( 1 - r ) * maxs[j] );
377 		}
378 
379 		// shoot effect away from center
380 		VectorSubtract( org, mid, dir );
381 		VectorNormalize( dir );
382 
383 		if ( effect2 && ( rand() & 1 ))
384 		{
385 			theFxScheduler.PlayEffect( effect2, org, dir );
386 		}
387 		else
388 		{
389 			theFxScheduler.PlayEffect( effect, org, dir );
390 		}
391 	}
392 }
393 
394 /*
395 -------------------------
396 CG_Chunks
397 
398 Fun chunk spewer
399 -------------------------
400 */
401 
CG_Chunks(int owner,vec3_t origin,const vec3_t normal,const vec3_t mins,const vec3_t maxs,float speed,int numChunks,material_t chunkType,int customChunk,float baseScale)402 void CG_Chunks( int owner, vec3_t origin, const vec3_t normal, const vec3_t mins, const vec3_t maxs,
403 						float speed, int numChunks, material_t chunkType, int customChunk, float baseScale )
404 {
405 	localEntity_t	*le;
406 	refEntity_t		*re;
407 	vec3_t			dir;
408 	int				i, j, k;
409 	int				chunkModel = 0;
410 	leBounceSound_t	bounce = LEBS_NONE;
411 	float			r, speedMod = 1.0f;
412 	qboolean		chunk = qfalse;
413 
414 	if ( chunkType == MAT_NONE )
415 	{
416 		// Well, we should do nothing
417 		return;
418 	}
419 
420 	// Set up our chunk sound info...breaking sounds are done here so they are done once on breaking..some return instantly because the chunks are done with effects instead of models
421 	switch( chunkType )
422 	{
423 	case MAT_GLASS:
424 		cgi_S_StartSound( NULL, owner, CHAN_BODY, cgs.media.glassChunkSound );
425 		return;
426 		break;
427 	case MAT_GRATE1:
428 		cgi_S_StartSound( NULL, owner, CHAN_BODY, cgs.media.grateSound );
429 		return;
430 		break;
431 	case MAT_ELECTRICAL:// (sparks)
432 		cgi_S_StartSound( NULL, owner, CHAN_BODY, cgi_S_RegisterSound (va("sound/ambience/spark%d.wav", Q_irand(1, 6))) );
433 		return;
434 		break;
435 	case MAT_DRK_STONE:
436 	case MAT_LT_STONE:
437 	case MAT_GREY_STONE:
438 	case MAT_WHITE_METAL:  // not quite sure what this stuff is supposed to be...it's for Stu
439 		cgi_S_StartSound( NULL, owner, CHAN_BODY, cgs.media.rockBreakSound );
440 		bounce = LEBS_ROCK;
441 		speedMod = 0.5f; // rock blows up less
442 		break;
443 	case MAT_GLASS_METAL:
444 		cgi_S_StartSound( NULL, owner, CHAN_BODY, cgs.media.glassChunkSound ); // FIXME: should probably have a custom sound
445 		bounce = LEBS_METAL;
446 		break;
447 	case MAT_CRATE1:
448 	case MAT_CRATE2:
449 		cgi_S_StartSound( NULL, owner, CHAN_BODY, cgs.media.crateBreakSound[Q_irand(0,1)] );
450 		break;
451 	case MAT_METAL:
452 	case MAT_METAL2:
453 	case MAT_METAL3:
454 	case MAT_ELEC_METAL:// FIXME: maybe have its own sound?
455 		cgi_S_StartSound( NULL, owner, CHAN_BODY, cgs.media.chunkSound );
456 		bounce = LEBS_METAL;
457 		speedMod = 0.8f; // metal blows up a bit more
458 		break;
459 	case MAT_ROPE:
460 //		cgi_S_StartSound( NULL, owner, CHAN_BODY, cgi_S_RegisterSound( "" ));  FIXME:  needs a sound
461 		return;
462 	default:
463 		break;
464 	}
465 
466 	if ( baseScale <= 0.0f )
467 	{
468 		baseScale = 1.0f;
469 	}
470 
471 	// Chunks
472 	for( i = 0; i < numChunks; i++ )
473 	{
474 		if ( customChunk > 0 )
475 		{
476 			// Try to use a custom chunk.
477 			if ( cgs.model_draw[customChunk] )
478 			{
479 				chunk = qtrue;
480 				chunkModel = cgs.model_draw[customChunk];
481 			}
482 		}
483 
484 		if ( !chunk )
485 		{
486 			// No custom chunk.  Pick a random chunk type at run-time so we don't get the same chunks
487 			switch( chunkType )
488 			{
489 			case MAT_METAL2: //bluegrey
490 				chunkModel = cgs.media.chunkModels[CHUNK_METAL2][Q_irand(0, 3)];
491 				break;
492 			case MAT_GREY_STONE://gray
493 				chunkModel = cgs.media.chunkModels[CHUNK_ROCK1][Q_irand(0, 3)];
494 				break;
495 			case MAT_LT_STONE: //tan
496 				chunkModel = cgs.media.chunkModels[CHUNK_ROCK2][Q_irand(0, 3)];
497 				break;
498 			case MAT_DRK_STONE://brown
499 				chunkModel = cgs.media.chunkModels[CHUNK_ROCK3][Q_irand(0, 3)];
500 				break;
501 			case MAT_WHITE_METAL:
502 				chunkModel = cgs.media.chunkModels[CHUNK_WHITE_METAL][Q_irand(0, 3)];
503 				break;
504 			case MAT_CRATE1://yellow multi-colored crate chunks
505 				chunkModel = cgs.media.chunkModels[CHUNK_CRATE1][Q_irand(0, 3)];
506 				break;
507 			case MAT_CRATE2://red multi-colored crate chunks
508 				chunkModel = cgs.media.chunkModels[CHUNK_CRATE2][Q_irand(0, 3)];
509 				break;
510 			case MAT_ELEC_METAL:
511 			case MAT_GLASS_METAL:
512 			case MAT_METAL://grey
513 				chunkModel = cgs.media.chunkModels[CHUNK_METAL1][Q_irand(0, 3)];
514 				break;
515 			case MAT_METAL3:
516 				if ( rand() & 1 )
517 				{
518 					chunkModel = cgs.media.chunkModels[CHUNK_METAL1][Q_irand(0, 3)];
519 				}
520 				else
521 				{
522 					chunkModel = cgs.media.chunkModels[CHUNK_METAL2][Q_irand(0, 3)];
523 				}
524 				break;
525 			default:
526 				break;
527 			}
528 		}
529 
530 		// It wouldn't look good to throw a bunch of RGB axis models...so make sure we have something to work with.
531 		if ( chunkModel )
532 		{
533 			le = CG_AllocLocalEntity();
534 			re = &le->refEntity;
535 
536 			re->hModel = chunkModel;
537 			le->leType = LE_FRAGMENT;
538 			le->endTime = cg.time + 1300 + Q_flrand(0.0f, 1.0f) * 900;
539 
540 			// spawn chunk roughly in the bbox of the thing...bias towards center in case thing blowing up doesn't complete fill its bbox.
541 			for( j = 0; j < 3; j++ )
542 			{
543 				r = Q_flrand(0.0f, 1.0f) * 0.8f + 0.1f;
544 				re->origin[j] = ( r * mins[j] + ( 1 - r ) * maxs[j] );
545 			}
546 			VectorCopy( re->origin, le->pos.trBase );
547 
548 			// Move out from center of thing, otherwise you can end up things moving across the brush in an undesirable direction.  Visually looks wrong
549 			VectorSubtract( re->origin, origin, dir );
550 			VectorNormalize( dir );
551 			VectorScale( dir, Q_flrand( speed * 0.5f, speed * 1.25f ) * speedMod, le->pos.trDelta );
552 
553 			// Angular Velocity
554 			VectorSet( le->angles.trBase, Q_flrand(0.0f, 1.0f) * 360, Q_flrand(0.0f, 1.0f) * 360, Q_flrand(0.0f, 1.0f) * 360 );
555 
556 			le->angles.trDelta[0] = Q_flrand(-1.0f, 1.0f);
557 			le->angles.trDelta[1] = Q_flrand(-1.0f, 1.0f);
558 			le->angles.trDelta[2] = 0; // don't do roll
559 
560 			VectorScale( le->angles.trDelta, Q_flrand(0.0f, 1.0f) * 600.0f + 200.0f, le->angles.trDelta );
561 
562 			le->pos.trType = TR_GRAVITY;
563 			le->angles.trType = TR_LINEAR;
564 			le->pos.trTime = le->angles.trTime = cg.time;
565 			le->bounceFactor = 0.2f + Q_flrand(0.0f, 1.0f) * 0.2f;
566 			le->leFlags |= LEF_TUMBLE;
567 			le->ownerGentNum = owner;
568 			le->leBounceSoundType = bounce;
569 
570 			// Make sure that we have the desired start size set
571 			le->radius = Q_flrand( baseScale * 0.75f, baseScale * 1.25f );
572 			re->nonNormalizedAxes = qtrue;
573 			AxisCopy( axisDefault, re->axis ); // could do an angles to axis, but this is cheaper and works ok
574 			for( k = 0; k < 3; k++ )
575 			{
576 				VectorScale( re->axis[k], le->radius, re->axis[k] );
577 			}
578 		}
579 	}
580 }
581 
CG_TestLine(vec3_t start,vec3_t end,int time,unsigned int color,int radius)582 void CG_TestLine( vec3_t start, vec3_t end, int time, unsigned int color, int radius )
583 {
584 	localEntity_t	*le;
585 	refEntity_t		*re;
586 
587 	le = CG_AllocLocalEntity();
588 	le->leType = LE_LINE;
589 	le->startTime = cg.time;
590 	le->endTime = cg.time + time;
591 	le->lifeRate = 1.0 / ( le->endTime - le->startTime );
592 
593 	re = &le->refEntity;
594 	VectorCopy( start, re->origin );
595 	VectorCopy( end, re->oldorigin);
596 	re->shaderTime = cg.time / 1000.0f;
597 
598 	re->reType = RT_LINE;
599 	re->radius = 0.5*radius;
600 	re->customShader = cgs.media.whiteShader; //trap_R_RegisterShaderNoMip("textures/colombia/canvas_doublesided");
601 
602 	re->shaderTexCoord[0] = re->shaderTexCoord[1] = 1.0f;
603 
604 	if (color==0)
605 	{
606 		re->shaderRGBA[0] = re->shaderRGBA[1] = re->shaderRGBA[2] = re->shaderRGBA[3] = 0xff;
607 	}
608 	else
609 	{
610 		re->shaderRGBA[0] = color & 0xff;
611 		color >>= 8;
612 		re->shaderRGBA[1] = color & 0xff;
613 		color >>= 8;
614 		re->shaderRGBA[2] = color & 0xff;
615 //		color >>= 8;
616 //		re->shaderRGBA[3] = color & 0xff;
617 		re->shaderRGBA[3] = 0xff;
618 	}
619 
620 	le->color[3] = 1.0;
621 }
622 
623 //----------------------------
624 //
625 // Breaking Glass Technology
626 //
627 //----------------------------
628 
629 // Since we have shared verts when we tesselate the glass sheet, it helps to have a
630 //	random offset table set up up front...so that we can have more random looking breaks.
631 
632 static float offX[20][20],
633 			offZ[20][20];
634 
CG_DoGlassQuad(vec3_t p[4],vec2_t uv[4],bool stick,int time,vec3_t dmgDir)635 static void CG_DoGlassQuad( vec3_t p[4], vec2_t uv[4], bool stick, int time, vec3_t dmgDir )
636 {
637 	float	bounce;
638 	vec3_t	rotDelta;
639 	vec3_t	vel, accel;
640 	vec3_t	rgb1;
641 
642 	VectorSet( vel, Q_flrand(-1.0f, 1.0f) * 12, Q_flrand(-1.0f, 1.0f) * 12, -1 );
643 
644 	if ( !stick )
645 	{
646 		// We aren't a motion delayed chunk, so let us move quickly
647 		VectorMA( vel, 0.3f, dmgDir, vel );
648 	}
649 
650 	// Set up acceleration due to gravity, 800 is standard QuakeIII gravity, so let's use something close
651 	VectorSet( accel, 0.0f, 0.0f, -(600.0f + Q_flrand(0.0f, 1.0f) * 100.0f ) );
652 
653 	VectorSet( rgb1, 1.0f, 1.0f, 1.0f );
654 
655 	// Being glass, we don't want to bounce much
656 	bounce = Q_flrand(0.0f, 1.0f) * 0.2f + 0.15f;
657 
658 	// Set up our random rotate, we only do PITCH and YAW, not ROLL.  This is something like degrees per second
659 	VectorSet( rotDelta, Q_flrand(-1.0f, 1.0f) * 40.0f, Q_flrand(-1.0f, 1.0f) * 40.0f, 0.0f );
660 
661 	CPoly *pol = FX_AddPoly(p, uv, 4,			// verts, ST, vertCount
662 			vel, accel,				// motion
663 			0.15f, 0.0f, 85.0f,		// alpha start, alpha end, alpha parm ( begin alpha fade when 85% of life is complete )
664 			rgb1, rgb1, 0.0f,		// rgb start, rgb end, rgb parm ( not used )
665 			rotDelta, bounce, time,	// rotation amount, bounce, and time to delay motion for ( zero if no delay );
666 			3500 + Q_flrand(0.0f, 1.0f) * 1000,	// life
667 			cgi_R_RegisterShader( "gfx/misc/test_crackle" ),
668 			FX_APPLY_PHYSICS | FX_ALPHA_NONLINEAR | FX_USE_ALPHA );
669 
670 	if ( Q_flrand(0.0f, 1.0f) > 0.95f && pol )
671 	{
672 		pol->AddFlags( FX_IMPACT_RUNS_FX | FX_KILL_ON_IMPACT );
673 		pol->SetImpactFxID( theFxScheduler.RegisterEffect( "glass_impact" ));
674 	}
675 }
676 
CG_CalcBiLerp(vec3_t verts[4],vec3_t subVerts[4],vec2_t uv[4])677 static void CG_CalcBiLerp( vec3_t verts[4], vec3_t subVerts[4], vec2_t uv[4] )
678 {
679 	vec3_t	temp;
680 
681 	// Nasty crap
682 	VectorScale( verts[0],	1.0f - uv[0][0],	subVerts[0] );
683 	VectorMA( subVerts[0],	uv[0][0],			verts[1], subVerts[0] );
684 	VectorScale( subVerts[0], 1.0f - uv[0][1],	temp );
685 	VectorScale( verts[3],	1.0f - uv[0][0],	subVerts[0] );
686 	VectorMA( subVerts[0],	uv[0][0],			verts[2], subVerts[0] );
687 	VectorMA( temp,			uv[0][1],			subVerts[0], subVerts[0] );
688 
689 	VectorScale( verts[0],	1.0f - uv[1][0],	subVerts[1] );
690 	VectorMA( subVerts[1],	uv[1][0],			verts[1], subVerts[1] );
691 	VectorScale( subVerts[1], 1.0f - uv[1][1],	temp );
692 	VectorScale( verts[3],	1.0f - uv[1][0],	subVerts[1] );
693 	VectorMA( subVerts[1],	uv[1][0],			verts[2], subVerts[1] );
694 	VectorMA( temp,			uv[1][1],			subVerts[1], subVerts[1] );
695 
696 	VectorScale( verts[0],	1.0f - uv[2][0],	subVerts[2] );
697 	VectorMA( subVerts[2],	uv[2][0],			verts[1], subVerts[2] );
698 	VectorScale( subVerts[2], 1.0f - uv[2][1],		temp );
699 	VectorScale( verts[3],	1.0f - uv[2][0],	subVerts[2] );
700 	VectorMA( subVerts[2],	uv[2][0],			verts[2], subVerts[2] );
701 	VectorMA( temp,			uv[2][1],			subVerts[2], subVerts[2] );
702 
703 	VectorScale( verts[0],	1.0f - uv[3][0],	subVerts[3] );
704 	VectorMA( subVerts[3],	uv[3][0],			verts[1], subVerts[3] );
705 	VectorScale( subVerts[3], 1.0f - uv[3][1],	temp );
706 	VectorScale( verts[3],	1.0f - uv[3][0],	subVerts[3] );
707 	VectorMA( subVerts[3],	uv[3][0],			verts[2], subVerts[3] );
708 	VectorMA( temp,			uv[3][1],			subVerts[3], subVerts[3] );
709 }
710 // bilinear
711 //f(p',q') = (1 - y) � {[(1 - x) � f(p,q)] + [x � f(p,q+1)]} + y � {[(1 - x) � f(p+1,q)] + [x � f(p+1,q+1)]}.
712 
713 
CG_CalcHeightWidth(vec3_t verts[4],float * height,float * width)714 static void CG_CalcHeightWidth( vec3_t verts[4], float *height, float *width )
715 {
716 	vec3_t	dir1, dir2, cross;
717 
718 	VectorSubtract( verts[3], verts[0], dir1 ); // v
719 	VectorSubtract( verts[1], verts[0], dir2 ); // p-a
720 	CrossProduct( dir1, dir2, cross );
721 	*width = VectorNormalize( cross ) / VectorNormalize( dir1 ); // v
722 	VectorSubtract( verts[2], verts[0], dir2 ); // p-a
723 	CrossProduct( dir1, dir2, cross );
724 	*width += VectorNormalize( cross ) / VectorNormalize( dir1 ); // v
725 	*width *= 0.5f;
726 
727 	VectorSubtract( verts[1], verts[0], dir1 ); // v
728 	VectorSubtract( verts[2], verts[0], dir2 ); // p-a
729 	CrossProduct( dir1, dir2, cross );
730 	*height = VectorNormalize( cross ) / VectorNormalize( dir1 ); // v
731 	VectorSubtract( verts[3], verts[0], dir2 ); // p-a
732 	CrossProduct( dir1, dir2, cross );
733 	*height += VectorNormalize( cross ) / VectorNormalize( dir1 ); // v
734 	*height *= 0.5f;
735 }
736 //Consider a line in 3D with position vector "a" and direction vector "v" and
737 // let "p" be the position vector of an arbitrary point in 3D
738 //dist = len( crossprod(p-a,v) ) / len(v);
739 
CG_InitGlass(void)740 void CG_InitGlass( void )
741 {
742 	int i, t;
743 
744 	// Build a table first, so that we can do a more unpredictable crack scheme
745 	//	do it once, up front to save a bit of time.
746 	for ( i = 0; i < 20; i++ )
747 	{
748 		for ( t = 0; t < 20; t++ )
749 		{
750 			offX[t][i] = Q_flrand(-1.0f, 1.0f) * 0.03f;
751 			offZ[i][t] = Q_flrand(-1.0f, 1.0f) * 0.03f;
752 		}
753 	}
754 }
755 
756 #define TIME_DECAY_SLOW		0.1f
757 #define TIME_DECAY_MED		0.04f
758 #define TIME_DECAY_FAST		0.009f
759 
CG_DoGlass(vec3_t verts[4],vec3_t normal,vec3_t dmgPt,vec3_t dmgDir,float dmgRadius)760 void CG_DoGlass( vec3_t verts[4], vec3_t normal, vec3_t dmgPt, vec3_t dmgDir, float dmgRadius )
761 {
762 	int		i, t;
763 	int		mxHeight, mxWidth;
764 	float	height, width;
765 	float	stepWidth, stepHeight;
766 	float	timeDecay;
767 	float	x, z;
768 	float	xx, zz;
769 	int		time = 0;
770 	bool	stick = true;
771 	vec3_t	subVerts[4];
772 	vec2_t	biPoints[4];
773 
774 	// To do a smarter tesselation, we should figure out the relative height and width of the brush face,
775 	//	then use this to pick a lod value from 1-3 in each axis.  This will give us 1-9 lod levels, which will
776 	//	hopefully be sufficient.
777 	CG_CalcHeightWidth( verts, &height, &width );
778 
779 	cgi_S_StartSound( dmgPt, -1, CHAN_AUTO, cgi_S_RegisterSound("sound/effects/glassbreak1.wav"));
780 
781 	// Pick "LOD" for height
782 	if ( height < 100 )
783 	{
784 		stepHeight = 0.2f;
785 		mxHeight = 5;
786 		timeDecay = TIME_DECAY_SLOW;
787 	}
788 /*	else if ( height > 220 ) // was originally mxHeight = 20....but removing this whole section because it causes huge number of chunks...which is bad
789 	{
790 		stepHeight = 0.075f;
791 		mxHeight = 15;
792 		timeDecay = TIME_DECAY_FAST;
793 	}*/
794 	else
795 	{
796 		stepHeight = 0.1f;
797 		mxHeight = 10;
798 		timeDecay = TIME_DECAY_MED;
799 	}
800 
801 	// Pick "LOD" for width
802 	if ( width < 100 )
803 	{
804 		stepWidth = 0.2f;
805 		mxWidth = 5;
806 		timeDecay = ( timeDecay + TIME_DECAY_SLOW ) * 0.5f;
807 	}
808 /*	else if ( width > 220 ) // don't do this because it causes too much chug with large glass panes...especially when more than one pane can be broken at a time
809 	{
810 		stepWidth = 0.075f;
811 		mxWidth = 15;
812 		timeDecay = ( timeDecay + TIME_DECAY_FAST ) * 0.5f;
813 	}*/
814 	else
815 	{
816 		stepWidth = 0.1f;
817 		mxWidth = 10;
818 		timeDecay = ( timeDecay + TIME_DECAY_MED ) * 0.5f;
819 	}
820 
821 	for ( z = 0.0f, i = 0; z < 1.0f; z += stepHeight, i++ )
822 	{
823 		for ( x = 0.0f, t = 0; x < 1.0f; x += stepWidth, t++ )
824 		{
825 			// This is nasty..we do this because we don't want to add a random offset on the edge of the glass brush
826 			//	...but we do in the center, otherwise the breaking scheme looks way too orderly
827 			if ( t > 0 && t < mxWidth )
828 			{
829 				xx = x - offX[i][t];
830 			}
831 			else
832 			{
833 				xx = x;
834 			}
835 
836 			if ( i > 0 && i < mxHeight )
837 			{
838 				zz = z - offZ[t][i];
839 			}
840 			else
841 			{
842 				zz = z;
843 			}
844 
845 			VectorSet2( biPoints[0], xx, zz );
846 
847 			if ( t + 1 > 0 && t + 1 < mxWidth )
848 			{
849 				xx = x - offX[i][t + 1];
850 			}
851 			else
852 			{
853 				xx = x;
854 			}
855 
856 			if ( i > 0 && i < mxHeight )
857 			{
858 				zz = z - offZ[t + 1][i];
859 			}
860 			else
861 			{
862 				zz = z;
863 			}
864 
865 			VectorSet2( biPoints[1], xx + stepWidth, zz );
866 
867 			if ( t + 1 > 0 && t + 1 < mxWidth )
868 			{
869 				xx = x - offX[i + 1][t + 1];
870 			}
871 			else
872 			{
873 				xx = x;
874 			}
875 
876 			if ( i + 1 > 0 && i + 1 < mxHeight )
877 			{
878 				zz = z - offZ[t + 1][i + 1];
879 			}
880 			else
881 			{
882 				zz = z;
883 			}
884 
885 			VectorSet2( biPoints[2], xx + stepWidth, zz + stepHeight);
886 
887 			if ( t > 0 && t < mxWidth )
888 			{
889 				xx = x - offX[i + 1][t];
890 			}
891 			else
892 			{
893 				xx = x;
894 			}
895 
896 			if ( i + 1 > 0 && i + 1 < mxHeight )
897 			{
898 				zz = z - offZ[t][i + 1];
899 			}
900 			else
901 			{
902 				zz = z;
903 			}
904 
905 			VectorSet2( biPoints[3], xx, zz + stepHeight );
906 
907 			CG_CalcBiLerp( verts, subVerts, biPoints );
908 
909 			float dif = DistanceSquared( subVerts[0], dmgPt ) * timeDecay - Q_flrand(0.0f, 1.0f) * 32;
910 
911 			// If we decrease dif, we are increasing the impact area, making it more likely to blow out large holes
912 			dif -= dmgRadius * dmgRadius;
913 
914 			if ( dif > 1 )
915 			{
916 				stick = true;
917 				time = dif + Q_flrand(0.0f, 1.0f) * 200;
918 			}
919 			else
920 			{
921 				stick = false;
922 				time = 0;
923 			}
924 
925 			CG_DoGlassQuad( subVerts, biPoints, stick, time, dmgDir );
926 		}
927 	}
928 }
929 
930 /*
931 =================
932 CG_Seeker
933 =================
934 */
935 /*void CG_Seeker( centity_t *cent )
936 {
937 	refEntity_t	re;
938 
939 	vec3_t	seekerOrg, viewAng;
940 	float	angle, c;
941 
942 	// must match cg_effects ( CG_Seeker ) & g_weapon ( SeekerAcquiresTarget ) & cg_weapons ( CG_FireSeeker )
943 	angle = cg.time * 0.004f;
944 	c = cos( angle );
945 
946 	seekerOrg[0] = cent->lerpOrigin[0] + 18 * c;
947 	seekerOrg[1] = cent->lerpOrigin[1] + 18 * sin(angle);
948 	seekerOrg[2] = cent->lerpOrigin[2] + cg.predicted_player_state.viewheight + 8 + (3 * cos(cg.time * 0.001));
949 
950 	memset( &re, 0, sizeof( re ) );
951 
952 	re.reType = RT_MODEL;
953 	VectorCopy( seekerOrg, re.origin);
954 	re.hModel = cgi_R_RegisterModel( "models/items/remote.md3" );
955 
956 	VectorCopy( cent->lerpAngles, viewAng ); // so the seeker faces the same direction the player is
957 	viewAng[PITCH] = -90; // but, we don't want the seeker facing up or down, always horizontal
958 	viewAng[YAW] += c * 15.f;
959 
960 	AnglesToAxis( viewAng, re.axis );
961 	VectorScale( re.axis[0], 0.5f, re.axis[0] );
962 	VectorScale( re.axis[1], 0.5f, re.axis[1] );
963 	VectorScale( re.axis[2], 0.5f, re.axis[2] );
964 	re.nonNormalizedAxes = qtrue;
965 
966 	cgi_R_AddRefEntityToScene( &re );
967 }
968 */
969 //------------------------------------------------------------------------------------------
CG_DrawTargetBeam(vec3_t start,vec3_t end,vec3_t norm,const char * beamFx,const char * impactFx)970 void CG_DrawTargetBeam( vec3_t start, vec3_t end, vec3_t norm, const char *beamFx, const char *impactFx )
971 {
972 	int				handle = 0;
973 	vec3_t			dir;
974 	SEffectTemplate	*temp;
975 
976 	// overriding the effect, so give us a copy first
977 	temp = theFxScheduler.GetEffectCopy( beamFx, &handle );
978 
979 	VectorSubtract( start, end, dir );
980 	VectorNormalize( dir );
981 
982 	if ( temp )
983 	{
984 		// have a copy, so get the line element out of there
985 		CPrimitiveTemplate *prim = theFxScheduler.GetPrimitiveCopy( temp, "beam" );
986 
987 		if ( prim )
988 		{
989 			// we have the primitive, so modify the endpoint
990 			prim->mOrigin2X.SetRange( end[0], end[0] );
991 			prim->mOrigin2Y.SetRange( end[1], end[1] );
992 			prim->mOrigin2Z.SetRange( end[2], end[2] );
993 
994 			// have a copy, so get the line element out of there
995 			CPrimitiveTemplate *prim = theFxScheduler.GetPrimitiveCopy( temp, "glow" );
996 
997 			// glow is not required
998 			if ( prim )
999 			{
1000 				// we have the primitive, so modify the endpoint
1001 				prim->mOrigin2X.SetRange( end[0], end[0] );
1002 				prim->mOrigin2Y.SetRange( end[1], end[1] );
1003 				prim->mOrigin2Z.SetRange( end[2], end[2] );
1004 			}
1005 
1006 			// play the modified effect
1007 			theFxScheduler.PlayEffect( handle, start, dir );
1008 		}
1009 	}
1010 
1011 	if ( impactFx )
1012 	{
1013 		theFxScheduler.PlayEffect( impactFx, end, norm );
1014 	}
1015 }
1016