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