]> icculus.org git repositories - icculus/iodoom3.git/blob - neo/framework/DeclParticle.cpp
hello world
[icculus/iodoom3.git] / neo / framework / DeclParticle.cpp
1 /*
2 ===========================================================================
3
4 Doom 3 GPL Source Code
5 Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. 
6
7 This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).  
8
9 Doom 3 Source Code is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13
14 Doom 3 Source Code 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 Doom 3 Source Code.  If not, see <http://www.gnu.org/licenses/>.
21
22 In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code.  If not, please request a copy in writing from id Software at the address below.
23
24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
25
26 ===========================================================================
27 */
28
29 #include "../idlib/precompiled.h"
30 #pragma hdrstop
31
32 struct ParticleParmDesc {
33         const char *name;
34         int count;
35         const char *desc;
36 };
37
38 const ParticleParmDesc ParticleDistributionDesc[] = {
39         { "rect", 3, "" },
40         { "cylinder", 4, "" },
41         { "sphere", 3, "" }
42 };
43
44 const ParticleParmDesc ParticleDirectionDesc[] = {
45         { "cone", 1, "" },
46         { "outward", 1, "" },
47 };
48
49 const ParticleParmDesc ParticleOrientationDesc[] = {
50         { "view", 0, "" },
51         { "aimed", 2, "" },
52         { "x", 0, "" },
53         { "y", 0, "" },
54         { "z", 0, "" } 
55 };
56
57 const ParticleParmDesc ParticleCustomDesc[] = {
58         { "standard", 0, "Standard" },
59         { "helix", 5, "sizeX Y Z radialSpeed axialSpeed" },
60         { "flies", 3, "radialSpeed axialSpeed size" },
61         { "orbit", 2, "radius speed"},
62         { "drip", 2, "something something" }
63 };
64
65 const int CustomParticleCount = sizeof( ParticleCustomDesc ) / sizeof( const ParticleParmDesc );
66
67 /*
68 =================
69 idDeclParticle::Size
70 =================
71 */
72 size_t idDeclParticle::Size( void ) const {
73         return sizeof( idDeclParticle );
74 }
75
76 /*
77 =====================
78 idDeclParticle::GetStageBounds
79 =====================
80 */
81 void idDeclParticle::GetStageBounds( idParticleStage *stage ) {
82
83         stage->bounds.Clear();
84
85         // this isn't absolutely guaranteed, but it should be close
86
87         particleGen_t g;
88
89         renderEntity_t  renderEntity;
90         memset( &renderEntity, 0, sizeof( renderEntity ) );
91         renderEntity.axis = mat3_identity;
92
93         renderView_t    renderView;
94         memset( &renderView, 0, sizeof( renderView ) );
95         renderView.viewaxis = mat3_identity;
96
97         g.renderEnt = &renderEntity;
98         g.renderView = &renderView;
99         g.origin.Zero();
100         g.axis = mat3_identity;
101
102         idRandom        steppingRandom;
103         steppingRandom.SetSeed( 0 );
104
105         // just step through a lot of possible particles as a representative sampling
106         for ( int i = 0 ; i < 1000 ; i++ ) {
107                 g.random = g.originalRandom = steppingRandom;
108
109                 int     maxMsec = stage->particleLife * 1000;
110                 for ( int inCycleTime = 0 ; inCycleTime < maxMsec ; inCycleTime += 16 ) {
111
112                         // make sure we get the very last tic, which may make up an extreme edge
113                         if ( inCycleTime + 16 > maxMsec ) {
114                                 inCycleTime = maxMsec - 1;
115                         }
116
117                         g.frac = (float)inCycleTime / ( stage->particleLife * 1000 );
118                         g.age = inCycleTime * 0.001f;
119
120                         // if the particle doesn't get drawn because it is faded out or beyond a kill region,
121                         // don't increment the verts
122
123                         idVec3  origin;
124                         stage->ParticleOrigin( &g, origin );                    
125                         stage->bounds.AddPoint( origin );
126                 }
127         }
128
129         // find the max size
130         float   maxSize = 0;
131
132         for ( float f = 0; f <= 1.0f; f += 1.0f / 64 ) {
133                 float size = stage->size.Eval( f, steppingRandom );
134                 float aspect = stage->aspect.Eval( f, steppingRandom );
135                 if ( aspect > 1 ) {
136                         size *= aspect;
137                 }
138                 if ( size > maxSize ) {
139                         maxSize = size;
140                 }
141         }
142
143         maxSize += 8;   // just for good measure
144         // users can specify a per-stage bounds expansion to handle odd cases
145         stage->bounds.ExpandSelf( maxSize + stage->boundsExpansion );
146 }
147
148 /*
149 ================
150 idDeclParticle::ParseParms
151
152 Parses a variable length list of parms on one line
153 ================
154 */
155 void idDeclParticle::ParseParms( idLexer &src, float *parms, int maxParms ) {
156         idToken token;
157
158         memset( parms, 0, maxParms * sizeof( *parms ) );
159         int     count = 0;
160         while( 1 ) {
161                 if ( !src.ReadTokenOnLine( &token ) ) {
162                         return;
163                 }
164                 if ( count == maxParms ) {
165                         src.Error( "too many parms on line" );
166                         return;
167                 }
168                 token.StripQuotes();
169                 parms[count] = atof( token );
170                 count++;
171         }
172 }
173
174 /*
175 ================
176 idDeclParticle::ParseParametric
177 ================
178 */
179 void idDeclParticle::ParseParametric( idLexer &src, idParticleParm *parm ) {
180         idToken token;
181
182         parm->table = NULL;
183         parm->from = parm->to = 0.0f;
184
185         if ( !src.ReadToken( &token ) ) {
186                 src.Error( "not enough parameters" );
187                 return;
188         }
189
190         if ( token.IsNumeric() ) {
191                 // can have a to + 2nd parm
192                 parm->from = parm->to = atof( token );
193                 if ( src.ReadToken( &token ) ) {
194                         if ( !token.Icmp( "to" ) ) {
195                                 if ( !src.ReadToken( &token ) ) {
196                                         src.Error( "missing second parameter" );
197                                         return;
198                                 }
199                                 parm->to = atof( token );
200                         } else {
201                                 src.UnreadToken( &token );
202                         }
203                 }
204         } else {
205                 // table
206                 parm->table = static_cast<const idDeclTable *>( declManager->FindType( DECL_TABLE, token, false ) );
207         }
208
209 }
210
211 /*
212 ================
213 idDeclParticle::ParseParticleStage
214 ================
215 */
216 idParticleStage *idDeclParticle::ParseParticleStage( idLexer &src ) {
217         idToken token;
218
219         idParticleStage *stage = new idParticleStage;
220         stage->Default();
221
222         while (1) {
223                 if ( src.HadError() ) {
224                         break;
225                 }
226                 if ( !src.ReadToken( &token ) ) {
227                         break;
228                 }
229                 if ( !token.Icmp( "}" ) ) {
230                         break;
231                 }
232                 if ( !token.Icmp( "material" ) ) {
233                         src.ReadToken( &token );
234                         stage->material = declManager->FindMaterial( token.c_str() );
235                         continue;
236                 }
237                 if ( !token.Icmp( "count" ) ) {
238                         stage->totalParticles = src.ParseInt();
239                         continue;
240                 }
241                 if ( !token.Icmp( "time" ) ) {
242                         stage->particleLife = src.ParseFloat();
243                         continue;
244                 }
245                 if ( !token.Icmp( "cycles" ) ) {
246                         stage->cycles = src.ParseFloat();
247                         continue;
248                 }
249                 if ( !token.Icmp( "timeOffset" ) ) {
250                         stage->timeOffset = src.ParseFloat();
251                         continue;
252                 }
253                 if ( !token.Icmp( "deadTime" ) ) {
254                         stage->deadTime = src.ParseFloat();
255                         continue;
256                 }
257                 if ( !token.Icmp( "randomDistribution" ) ) {
258                         stage->randomDistribution = src.ParseBool();
259                         continue;
260                 }
261                 if ( !token.Icmp( "bunching" ) ) {
262                         stage->spawnBunching = src.ParseFloat();
263                         continue;
264                 }
265
266                 if ( !token.Icmp( "distribution" ) ) {
267                         src.ReadToken( &token );
268                         if ( !token.Icmp( "rect" ) ) {
269                                 stage->distributionType = PDIST_RECT;
270                         } else if ( !token.Icmp( "cylinder" ) ) {
271                                 stage->distributionType = PDIST_CYLINDER;
272                         } else if ( !token.Icmp( "sphere" ) ) {
273                                 stage->distributionType = PDIST_SPHERE;
274                         } else {
275                                 src.Error( "bad distribution type: %s\n", token.c_str() );
276                         }
277                         ParseParms( src, stage->distributionParms, sizeof( stage->distributionParms ) / sizeof( stage->distributionParms[0] ) );
278                         continue;
279                 }
280
281                 if ( !token.Icmp( "direction" ) ) {
282                         src.ReadToken( &token );
283                         if ( !token.Icmp( "cone" ) ) {
284                                 stage->directionType = PDIR_CONE;
285                         } else if ( !token.Icmp( "outward" ) ) {
286                                 stage->directionType = PDIR_OUTWARD;
287                         } else {
288                                 src.Error( "bad direction type: %s\n", token.c_str() );
289                         }
290                         ParseParms( src, stage->directionParms, sizeof( stage->directionParms ) / sizeof( stage->directionParms[0] ) );
291                         continue;
292                 }
293
294                 if ( !token.Icmp( "orientation" ) ) {
295                         src.ReadToken( &token );
296                         if ( !token.Icmp( "view" ) ) {
297                                 stage->orientation = POR_VIEW;
298                         } else if ( !token.Icmp( "aimed" ) ) {
299                                 stage->orientation = POR_AIMED;
300                         } else if ( !token.Icmp( "x" ) ) {
301                                 stage->orientation = POR_X;
302                         } else if ( !token.Icmp( "y" ) ) {
303                                 stage->orientation = POR_Y;
304                         } else if ( !token.Icmp( "z" ) ) {
305                                 stage->orientation = POR_Z;
306                         } else {
307                                 src.Error( "bad orientation type: %s\n", token.c_str() );
308                         }
309                         ParseParms( src, stage->orientationParms, sizeof( stage->orientationParms ) / sizeof( stage->orientationParms[0] ) );
310                         continue;
311                 }
312
313                 if ( !token.Icmp( "customPath" ) ) {
314                         src.ReadToken( &token );
315                         if ( !token.Icmp( "standard" ) ) {
316                                 stage->customPathType = PPATH_STANDARD;
317                         } else if ( !token.Icmp( "helix" ) ) {
318                                 stage->customPathType = PPATH_HELIX;
319                         } else if ( !token.Icmp( "flies" ) ) {
320                                 stage->customPathType = PPATH_FLIES;
321                         } else if ( !token.Icmp( "spherical" ) ) {
322                                 stage->customPathType = PPATH_ORBIT;
323                         } else {
324                                 src.Error( "bad path type: %s\n", token.c_str() );
325                         }
326                         ParseParms( src, stage->customPathParms, sizeof( stage->customPathParms ) / sizeof( stage->customPathParms[0] ) );
327                         continue;
328                 }
329
330                 if ( !token.Icmp( "speed" ) ) {
331                         ParseParametric( src, &stage->speed );
332                         continue;
333                 }
334                 if ( !token.Icmp( "rotation" ) ) {
335                         ParseParametric( src, &stage->rotationSpeed );
336                         continue;
337                 }
338                 if ( !token.Icmp( "angle" ) ) {
339                         stage->initialAngle = src.ParseFloat();
340                         continue;
341                 }
342                 if ( !token.Icmp( "entityColor" ) ) { 
343                         stage->entityColor = src.ParseBool();
344                         continue;
345                 }
346                 if ( !token.Icmp( "size" ) ) {
347                         ParseParametric( src, &stage->size );
348                         continue;
349                 }
350                 if ( !token.Icmp( "aspect" ) ) {
351                         ParseParametric( src, &stage->aspect );
352                         continue;
353                 }
354                 if ( !token.Icmp( "fadeIn" ) ) {
355                         stage->fadeInFraction = src.ParseFloat();
356                         continue;
357                 }
358                 if ( !token.Icmp( "fadeOut" ) ) {
359                         stage->fadeOutFraction = src.ParseFloat();
360                         continue;
361                 }
362                 if ( !token.Icmp( "fadeIndex" ) ) {
363                         stage->fadeIndexFraction = src.ParseFloat();
364                         continue;
365                 }
366                 if ( !token.Icmp( "color" ) ) {
367                         stage->color[0] = src.ParseFloat();
368                         stage->color[1] = src.ParseFloat();
369                         stage->color[2] = src.ParseFloat();
370                         stage->color[3] = src.ParseFloat();
371                         continue;
372                 }
373                 if ( !token.Icmp( "fadeColor" ) ) {
374                         stage->fadeColor[0] = src.ParseFloat();
375                         stage->fadeColor[1] = src.ParseFloat();
376                         stage->fadeColor[2] = src.ParseFloat();
377                         stage->fadeColor[3] = src.ParseFloat();
378                         continue;
379                 }
380                 if ( !token.Icmp("offset" ) ) {
381                         stage->offset[0] = src.ParseFloat();
382                         stage->offset[1] = src.ParseFloat();
383                         stage->offset[2] = src.ParseFloat();
384                         continue;
385                 }
386                 if ( !token.Icmp( "animationFrames" ) ) {
387                         stage->animationFrames = src.ParseInt();
388                         continue;
389                 }
390                 if ( !token.Icmp( "animationRate" ) ) {
391                         stage->animationRate = src.ParseFloat();
392                         continue;
393                 }
394                 if ( !token.Icmp( "boundsExpansion" ) ) {
395                         stage->boundsExpansion = src.ParseFloat();
396                         continue;
397                 }
398                 if ( !token.Icmp( "gravity" ) ) {
399                         src.ReadToken( &token );
400                         if ( !token.Icmp( "world" ) ) {
401                                 stage->worldGravity = true;
402                         } else {
403                                 src.UnreadToken( &token );
404                         }
405                         stage->gravity = src.ParseFloat();
406                         continue;
407                 }
408
409                 src.Error( "unknown token %s\n", token.c_str() );
410         }
411
412         // derive values
413         stage->cycleMsec = ( stage->particleLife + stage->deadTime ) * 1000;
414
415         return stage;
416 }
417
418 /*
419 ================
420 idDeclParticle::Parse
421 ================
422 */
423 bool idDeclParticle::Parse( const char *text, const int textLength ) {
424         idLexer src;
425         idToken token;
426
427         src.LoadMemory( text, textLength, GetFileName(), GetLineNum() );
428         src.SetFlags( DECL_LEXER_FLAGS );
429         src.SkipUntilString( "{" );
430
431         depthHack = 0.0f;
432
433         while (1) {
434                 if ( !src.ReadToken( &token ) ) {
435                         break;
436                 }
437
438                 if ( !token.Icmp( "}" ) ) {
439                         break;
440                 }
441
442                 if ( !token.Icmp( "{" ) ) {
443                         idParticleStage *stage = ParseParticleStage( src );
444                         if ( !stage ) {
445                                 src.Warning( "Particle stage parse failed" );
446                                 MakeDefault();
447                                 return false;
448                         }
449                         stages.Append( stage );
450                         continue;
451                 }
452
453                 if ( !token.Icmp( "depthHack" ) ) {
454                         depthHack = src.ParseFloat();
455                         continue;
456                 }
457
458                 src.Warning( "bad token %s", token.c_str() );
459                 MakeDefault();
460                 return false;
461         }
462
463         //
464         // calculate the bounds
465         //
466         bounds.Clear();
467         for( int i = 0; i < stages.Num(); i++ ) {
468                 GetStageBounds( stages[i] );
469                 bounds.AddBounds( stages[i]->bounds );
470         }
471
472         if ( bounds.GetVolume() <= 0.1f ) {
473                 bounds = idBounds( vec3_origin ).Expand( 8.0f );
474         }
475
476         return true;
477 }
478
479 /*
480 ================
481 idDeclParticle::FreeData
482 ================
483 */
484 void idDeclParticle::FreeData( void ) {
485         stages.DeleteContents( true );
486 }
487
488 /*
489 ================
490 idDeclParticle::DefaultDefinition
491 ================
492 */
493 const char *idDeclParticle::DefaultDefinition( void ) const {
494         return
495                 "{\n"
496         "\t"    "{\n"
497         "\t\t"          "material\t_default\n"
498         "\t\t"          "count\t20\n"
499         "\t\t"          "time\t\t1.0\n"
500         "\t"    "}\n"
501                 "}";
502 }
503
504 /*
505 ================
506 idDeclParticle::WriteParticleParm
507 ================
508 */
509 void idDeclParticle::WriteParticleParm( idFile *f, idParticleParm *parm, const char *name ) {
510
511         f->WriteFloatString( "\t\t%s\t\t\t\t ", name );
512         if ( parm->table ) {
513                 f->WriteFloatString( "%s\n", parm->table->GetName() );
514         } else {
515                 f->WriteFloatString( "\"%.3f\" ", parm->from );
516                 if ( parm->from == parm->to ) {
517                         f->WriteFloatString( "\n" );
518                 } else {
519                         f->WriteFloatString( " to \"%.3f\"\n", parm->to );
520                 }
521         }
522 }
523
524 /*
525 ================
526 idDeclParticle::WriteStage
527 ================
528 */
529 void idDeclParticle::WriteStage( idFile *f, idParticleStage *stage ) {
530         
531         int i;
532
533         f->WriteFloatString( "\t{\n" );
534         f->WriteFloatString( "\t\tcount\t\t\t\t%i\n", stage->totalParticles );
535         f->WriteFloatString( "\t\tmaterial\t\t\t%s\n", stage->material->GetName() );
536         if ( stage->animationFrames ) {
537                 f->WriteFloatString( "\t\tanimationFrames \t%i\n", stage->animationFrames );
538         }
539         if ( stage->animationRate ) {
540                 f->WriteFloatString( "\t\tanimationRate \t\t%.3f\n", stage->animationRate );
541         }
542         f->WriteFloatString( "\t\ttime\t\t\t\t%.3f\n", stage->particleLife );
543         f->WriteFloatString( "\t\tcycles\t\t\t\t%.3f\n", stage->cycles );
544         if ( stage->timeOffset ) {
545                 f->WriteFloatString( "\t\ttimeOffset\t\t\t%.3f\n", stage->timeOffset );
546         }
547         if ( stage->deadTime ) {
548                 f->WriteFloatString( "\t\tdeadTime\t\t\t%.3f\n", stage->deadTime );
549         }
550         f->WriteFloatString( "\t\tbunching\t\t\t%.3f\n", stage->spawnBunching );
551         
552         f->WriteFloatString( "\t\tdistribution\t\t%s ", ParticleDistributionDesc[stage->distributionType].name );
553         for ( i = 0; i < ParticleDistributionDesc[stage->distributionType].count; i++ ) {
554                 f->WriteFloatString( "%.3f ", stage->distributionParms[i] );
555         }
556         f->WriteFloatString( "\n" );
557
558         f->WriteFloatString( "\t\tdirection\t\t\t%s ", ParticleDirectionDesc[stage->directionType].name );
559         for ( i = 0; i < ParticleDirectionDesc[stage->directionType].count; i++ ) {
560                 f->WriteFloatString( "\"%.3f\" ", stage->directionParms[i] );
561         }
562         f->WriteFloatString( "\n" );
563
564         f->WriteFloatString( "\t\torientation\t\t\t%s ", ParticleOrientationDesc[stage->orientation].name );
565         for ( i = 0; i < ParticleOrientationDesc[stage->orientation].count; i++ ) {
566                 f->WriteFloatString( "%.3f ", stage->orientationParms[i] );
567         }
568         f->WriteFloatString( "\n" );
569
570         if ( stage->customPathType != PPATH_STANDARD ) {
571                 f->WriteFloatString( "\t\tcustomPath %s ", ParticleCustomDesc[stage->customPathType].name );
572                 for ( i = 0; i < ParticleCustomDesc[stage->customPathType].count; i++ ) {
573                         f->WriteFloatString( "%.3f ", stage->customPathParms[i] );
574                 }
575                 f->WriteFloatString( "\n" );
576         }
577
578         if ( stage->entityColor ) {
579                 f->WriteFloatString( "\t\tentityColor\t\t\t1\n" );
580         }
581
582         WriteParticleParm( f, &stage->speed, "speed" );
583         WriteParticleParm( f, &stage->size, "size" );
584         WriteParticleParm( f, &stage->aspect, "aspect" );
585
586         if ( stage->rotationSpeed.from ) {
587                 WriteParticleParm( f, &stage->rotationSpeed, "rotation" );
588         }
589
590         if ( stage->initialAngle ) {
591                 f->WriteFloatString( "\t\tangle\t\t\t\t%.3f\n", stage->initialAngle );
592         }
593
594         f->WriteFloatString( "\t\trandomDistribution\t\t\t\t%i\n", static_cast<int>( stage->randomDistribution ) );
595         f->WriteFloatString( "\t\tboundsExpansion\t\t\t\t%.3f\n", stage->boundsExpansion );
596
597
598         f->WriteFloatString( "\t\tfadeIn\t\t\t\t%.3f\n", stage->fadeInFraction );
599         f->WriteFloatString( "\t\tfadeOut\t\t\t\t%.3f\n", stage->fadeOutFraction );
600         f->WriteFloatString( "\t\tfadeIndex\t\t\t\t%.3f\n", stage->fadeIndexFraction );
601
602         f->WriteFloatString( "\t\tcolor \t\t\t\t%.3f %.3f %.3f %.3f\n", stage->color.x, stage->color.y, stage->color.z, stage->color.w );
603         f->WriteFloatString( "\t\tfadeColor \t\t\t%.3f %.3f %.3f %.3f\n", stage->fadeColor.x, stage->fadeColor.y, stage->fadeColor.z, stage->fadeColor.w );
604
605         f->WriteFloatString( "\t\toffset \t\t\t\t%.3f %.3f %.3f\n", stage->offset.x, stage->offset.y, stage->offset.z );
606         f->WriteFloatString( "\t\tgravity \t\t\t" );
607         if ( stage->worldGravity ) {
608                 f->WriteFloatString( "world " );
609         }
610         f->WriteFloatString( "%.3f\n", stage->gravity );
611         f->WriteFloatString( "\t}\n" );
612 }
613
614 /*
615 ================
616 idDeclParticle::RebuildTextSource
617 ================
618 */
619 bool idDeclParticle::RebuildTextSource( void ) {
620         idFile_Memory f;
621
622         f.WriteFloatString("\n\n/*\n"
623                 "\tGenerated by the Particle Editor.\n"
624                 "\tTo use the particle editor, launch the game and type 'editParticles' on the console.\n"
625                 "*/\n" );
626
627         f.WriteFloatString( "particle %s {\n", GetName() );
628
629         if ( depthHack ) {
630                 f.WriteFloatString( "\tdepthHack\t%f\n", depthHack );
631         }
632
633         for ( int i = 0; i < stages.Num(); i++ ) {
634                 WriteStage( &f, stages[i] );
635         }
636
637         f.WriteFloatString( "}" );
638
639         SetText( f.GetDataPtr() );
640
641         return true;
642 }
643
644 /*
645 ================
646 idDeclParticle::Save
647 ================
648 */
649 bool idDeclParticle::Save( const char *fileName ) {
650         RebuildTextSource();
651         if ( fileName ) {
652                 declManager->CreateNewDecl( DECL_PARTICLE, GetName(), fileName );
653         }
654         ReplaceSourceFileText();
655         return true;
656 }
657
658 /*
659 ====================================================================================
660
661 idParticleParm
662
663 ====================================================================================
664 */
665
666 float idParticleParm::Eval( float frac, idRandom &rand ) const {
667         if ( table ) {
668                 return table->TableLookup( frac );
669         }
670         return from + frac * ( to - from );
671 }
672
673 float idParticleParm::Integrate( float frac, idRandom &rand ) const {
674         if ( table ) {
675                 common->Printf( "idParticleParm::Integrate: can't integrate tables\n" );
676                 return 0;
677         }
678         return ( from + frac * ( to - from ) * 0.5f ) * frac;
679 }
680
681 /*
682 ====================================================================================
683
684 idParticleStage
685
686 ====================================================================================
687 */
688
689 /*
690 ================
691 idParticleStage::idParticleStage
692 ================
693 */
694 idParticleStage::idParticleStage( void ) {
695         material = NULL;
696         totalParticles = 0;
697         cycles = 0.0f;
698         cycleMsec = 0;
699         spawnBunching = 0.0f;
700         particleLife = 0.0f;
701         timeOffset = 0.0f;
702         deadTime = 0.0f;
703         distributionType = PDIST_RECT;
704         distributionParms[0] = distributionParms[1] = distributionParms[2] = distributionParms[3] = 0.0f;
705         directionType = PDIR_CONE;
706         directionParms[0] = directionParms[1] = directionParms[2] = directionParms[3] = 0.0f;
707         // idParticleParm               speed;
708         gravity = 0.0f;
709         worldGravity = false;
710         customPathType = PPATH_STANDARD;
711         customPathParms[0] = customPathParms[1] = customPathParms[2] = customPathParms[3] = 0.0f;
712         customPathParms[4] = customPathParms[5] = customPathParms[6] = customPathParms[7] = 0.0f;
713         offset.Zero();
714         animationFrames = 0;
715         animationRate = 0.0f;
716         randomDistribution = true;
717         entityColor = false;
718         initialAngle = 0.0f;
719         // idParticleParm               rotationSpeed;
720         orientation = POR_VIEW;
721         orientationParms[0] = orientationParms[1] = orientationParms[2] = orientationParms[3] = 0.0f;
722         // idParticleParm               size
723         // idParticleParm               aspect
724         color.Zero();
725         fadeColor.Zero();
726         fadeInFraction = 0.0f;
727         fadeOutFraction = 0.0f;
728         fadeIndexFraction = 0.0f;
729         hidden = false;
730         boundsExpansion = 0.0f;
731         bounds.Clear();
732 }
733
734 /*
735 ================
736 idParticleStage::Default
737
738 Sets the stage to a default state
739 ================
740 */
741 void idParticleStage::Default() {
742         material = declManager->FindMaterial( "_default" );
743         totalParticles = 100;
744         spawnBunching = 1.0f;
745         particleLife = 1.5f;
746         timeOffset = 0.0f;
747         deadTime = 0.0f;
748         distributionType = PDIST_RECT;
749         distributionParms[0] = 8.0f;
750         distributionParms[1] = 8.0f;
751         distributionParms[2] = 8.0f;
752         distributionParms[3] = 0.0f;
753         directionType = PDIR_CONE;
754         directionParms[0] = 90.0f;
755         directionParms[1] = 0.0f;
756         directionParms[2] = 0.0f;
757         directionParms[3] = 0.0f;
758         orientation = POR_VIEW;
759         orientationParms[0] = 0.0f;
760         orientationParms[1] = 0.0f;
761         orientationParms[2] = 0.0f;
762         orientationParms[3] = 0.0f;
763         speed.from = 150.0f;
764         speed.to = 150.0f;
765         speed.table = NULL;
766         gravity = 1.0f;
767         worldGravity = false;
768         customPathType = PPATH_STANDARD;
769         customPathParms[0] = 0.0f;
770         customPathParms[1] = 0.0f;
771         customPathParms[2] = 0.0f;
772         customPathParms[3] = 0.0f;
773         customPathParms[4] = 0.0f;
774         customPathParms[5] = 0.0f;
775         customPathParms[6] = 0.0f;
776         customPathParms[7] = 0.0f;
777         offset.Zero();
778         animationFrames = 0;
779         animationRate = 0.0f;
780         initialAngle = 0.0f;
781         rotationSpeed.from = 0.0f;
782         rotationSpeed.to = 0.0f;
783         rotationSpeed.table = NULL;
784         size.from = 4.0f;
785         size.to = 4.0f;
786         size.table = NULL;
787         aspect.from = 1.0f;
788         aspect.to = 1.0f;
789         aspect.table = NULL;
790         color.x = 1.0f;
791         color.y = 1.0f;
792         color.z = 1.0f;
793         color.w = 1.0f;
794         fadeColor.x = 0.0f;
795         fadeColor.y = 0.0f;
796         fadeColor.z = 0.0f;
797         fadeColor.w = 0.0f;
798         fadeInFraction = 0.1f;
799         fadeOutFraction = 0.25f;
800         fadeIndexFraction = 0.0f;
801         boundsExpansion = 0.0f;
802         randomDistribution = true;
803         entityColor = false;
804         cycleMsec = ( particleLife + deadTime ) * 1000;
805 }
806
807 /*
808 ================
809 idParticleStage::NumQuadsPerParticle
810
811 includes trails and cross faded animations
812 ================
813 */
814 int idParticleStage::NumQuadsPerParticle() const {
815         int     count = 1;
816
817         if ( orientation == POR_AIMED ) {
818                 int     trails = idMath::Ftoi( orientationParms[0] );
819                 // each trail stage will add an extra quad
820                 count *= ( 1 + trails );
821         }
822
823         // if we are doing strip-animation, we need to double the number and cross fade them
824         if ( animationFrames > 1 ) {
825                 count *= 2;
826         }
827
828         return count;
829 }
830
831 /*
832 ===============
833 idParticleStage::ParticleOrigin
834 ===============
835 */
836 void idParticleStage::ParticleOrigin( particleGen_t *g, idVec3 &origin ) const {
837         if ( customPathType == PPATH_STANDARD ) {
838                 //
839                 // find intial origin distribution
840                 //
841                 float radiusSqr, angle1, angle2;
842
843                 switch( distributionType ) {
844                         case PDIST_RECT: {      // ( sizeX sizeY sizeZ )
845                                 origin[0] = ( ( randomDistribution ) ? g->random.CRandomFloat() : 1.0f ) * distributionParms[0];
846                                 origin[1] = ( ( randomDistribution ) ? g->random.CRandomFloat() : 1.0f ) * distributionParms[1];
847                                 origin[2] = ( ( randomDistribution ) ? g->random.CRandomFloat() : 1.0f ) * distributionParms[2];
848                                 break;
849                         }
850                         case PDIST_CYLINDER: {  // ( sizeX sizeY sizeZ ringFraction )
851                                 angle1 = ( ( randomDistribution ) ? g->random.CRandomFloat() : 1.0f ) * idMath::TWO_PI;
852
853                                 idMath::SinCos16( angle1, origin[0], origin[1] );
854                                 origin[2] = ( ( randomDistribution ) ? g->random.CRandomFloat() : 1.0f );
855
856                                 // reproject points that are inside the ringFraction to the outer band
857                                 if ( distributionParms[3] > 0.0f ) {
858                                         radiusSqr = origin[0] * origin[0] + origin[1] * origin[1];
859                                         if ( radiusSqr < distributionParms[3] * distributionParms[3] ) {
860                                                 // if we are inside the inner reject zone, rescale to put it out into the good zone
861                                                 float f = sqrt( radiusSqr ) / distributionParms[3];
862                                                 float invf = 1.0f / f;
863                                                 float newRadius = distributionParms[3] + f * ( 1.0f - distributionParms[3] );
864                                                 float rescale = invf * newRadius;
865
866                                                 origin[0] *= rescale;
867                                                 origin[1] *= rescale;
868                                         }
869                                 }
870                                 origin[0] *= distributionParms[0];
871                                 origin[1] *= distributionParms[1];
872                                 origin[2] *= distributionParms[2];
873                                 break;
874                         }
875                         case PDIST_SPHERE: {    // ( sizeX sizeY sizeZ ringFraction )
876                                 // iterating with rejection is the only way to get an even distribution over a sphere
877                                 if ( randomDistribution ) {
878                                         do {
879                                                 origin[0] = g->random.CRandomFloat();
880                                                 origin[1] = g->random.CRandomFloat();
881                                                 origin[2] = g->random.CRandomFloat();
882                                                 radiusSqr = origin[0] * origin[0] + origin[1] * origin[1] + origin[2] * origin[2];
883                                         } while( radiusSqr > 1.0f );
884                                 } else {
885                                         origin.Set( 1.0f, 1.0f, 1.0f );
886                                         radiusSqr = 3.0f;
887                                 }
888
889                                 if ( distributionParms[3] > 0.0f ) {
890                                         // we could iterate until we got something that also satisfied ringFraction,
891                                         // but for narrow rings that could be a lot of work, so reproject inside points instead
892                                         if ( radiusSqr < distributionParms[3] * distributionParms[3] ) {
893                                                 // if we are inside the inner reject zone, rescale to put it out into the good zone
894                                                 float f = sqrt( radiusSqr ) / distributionParms[3];
895                                                 float invf = 1.0f / f;
896                                                 float newRadius = distributionParms[3] + f * ( 1.0f - distributionParms[3] );
897                                                 float rescale = invf * newRadius;
898
899                                                 origin[0] *= rescale;
900                                                 origin[1] *= rescale;
901                                                 origin[2] *= rescale;
902                                         }
903                                 }
904                                 origin[0] *= distributionParms[0];
905                                 origin[1] *= distributionParms[1];
906                                 origin[2] *= distributionParms[2];
907                                 break;
908                         }
909                 }
910
911                 // offset will effect all particle origin types
912                 // add this before the velocity and gravity additions
913                 origin += offset;
914
915                 //
916                 // add the velocity over time
917                 //
918                 idVec3  dir;
919
920                 switch( directionType ) {
921                         case PDIR_CONE: {
922                                 // angle is the full angle, so 360 degrees is any spherical direction
923                                 angle1 = g->random.CRandomFloat() * directionParms[0] * idMath::M_DEG2RAD;
924                                 angle2 = g->random.CRandomFloat() * idMath::PI;
925                 
926                                 float s1, c1, s2, c2;
927                                 idMath::SinCos16( angle1, s1, c1 );
928                                 idMath::SinCos16( angle2, s2, c2 );
929
930                                 dir[0] = s1 * c2;
931                                 dir[1] = s1 * s2;
932                                 dir[2] = c1;
933                                 break;
934                         }
935                         case PDIR_OUTWARD: {
936                                 dir = origin;
937                                 dir.Normalize();
938                                 dir[2] += directionParms[0];
939                                 break;
940                         }
941                 }
942
943                 // add speed
944                 float iSpeed = speed.Integrate( g->frac, g->random );
945                 origin += dir * iSpeed * particleLife;  
946
947         } else {
948                 //
949                 // custom paths completely override both the origin and velocity calculations, but still
950                 // use the standard gravity
951                 //
952                 float angle1, angle2, speed1, speed2;
953                 switch( customPathType ) {
954                         case PPATH_HELIX: {             // ( sizeX sizeY sizeZ radialSpeed axialSpeed )
955                                 speed1 = g->random.CRandomFloat();
956                                 speed2 = g->random.CRandomFloat();
957                                 angle1 = g->random.RandomFloat() * idMath::TWO_PI + customPathParms[3] * speed1 * g->age;
958
959                                 float s1, c1;
960                                 idMath::SinCos16( angle1, s1, c1 );
961
962                                 origin[0] = c1 * customPathParms[0];
963                                 origin[1] = s1 * customPathParms[1];
964                                 origin[2] = g->random.RandomFloat() * customPathParms[2] + customPathParms[4] * speed2 * g->age;
965                                 break;
966                         }
967                         case PPATH_FLIES: {             // ( radialSpeed axialSpeed size )
968                                 speed1 = idMath::ClampFloat( 0.4f, 1.0f, g->random.CRandomFloat() );
969                                 speed2 = idMath::ClampFloat( 0.4f, 1.0f, g->random.CRandomFloat() );
970                                 angle1 = g->random.RandomFloat() * idMath::PI * 2 + customPathParms[0] * speed1 * g->age;
971                                 angle2 = g->random.RandomFloat() * idMath::PI * 2 + customPathParms[1] * speed1 * g->age;
972
973                                 float s1, c1, s2, c2;
974                                 idMath::SinCos16( angle1, s1, c1 );
975                                 idMath::SinCos16( angle2, s2, c2 );
976
977                                 origin[0] = c1 * c2;
978                                 origin[1] = s1 * c2;
979                                 origin[2] = -s2;
980                                 origin *= customPathParms[2];
981                                 break;
982                         }
983                         case PPATH_ORBIT: {             // ( radius speed axis )
984                                 angle1 = g->random.RandomFloat() * idMath::TWO_PI + customPathParms[1] * g->age;
985
986                                 float s1, c1;
987                                 idMath::SinCos16( angle1, s1, c1 );
988
989                                 origin[0] = c1 * customPathParms[0];
990                                 origin[1] = s1 * customPathParms[0];
991                                 origin.ProjectSelfOntoSphere( customPathParms[0] );
992                                 break;
993                         }
994                         case PPATH_DRIP: {              // ( speed )
995                                 origin[0] = 0.0f;
996                                 origin[1] = 0.0f;
997                                 origin[2] = -( g->age * customPathParms[0] );
998                                 break;
999                         }
1000                         default: {
1001                                 common->Error( "idParticleStage::ParticleOrigin: bad customPathType" );
1002                         }
1003                 }
1004
1005                 origin += offset;
1006         }
1007
1008         // adjust for the per-particle smoke offset
1009         origin *= g->axis;
1010         origin += g->origin;
1011
1012         // add gravity after adjusting for axis
1013         if ( worldGravity ) {
1014                 idVec3 gra( 0, 0, -gravity );
1015                 gra *= g->renderEnt->axis.Transpose();
1016                 origin += gra * g->age * g->age;
1017         } else {
1018                 origin[2] -= gravity * g->age * g->age;
1019         }
1020 }
1021
1022 /*
1023 ==================
1024 idParticleStage::ParticleVerts
1025 ==================
1026 */
1027 int     idParticleStage::ParticleVerts( particleGen_t *g, idVec3 origin, idDrawVert *verts ) const {
1028         float   psize = size.Eval( g->frac, g->random );
1029         float   paspect = aspect.Eval( g->frac, g->random );
1030
1031         float   width = psize;
1032         float   height = psize * paspect;
1033
1034         idVec3  left, up;
1035
1036         if ( orientation == POR_AIMED ) {
1037                 // reset the values to an earlier time to get a previous origin
1038                 idRandom        currentRandom = g->random;
1039                 float           currentAge = g->age;
1040                 float           currentFrac = g->frac;
1041                 idDrawVert *verts_p = verts;
1042                 idVec3          stepOrigin = origin;
1043                 idVec3          stepLeft;
1044                 int                     numTrails = idMath::Ftoi( orientationParms[0] );
1045                 float           trailTime = orientationParms[1];
1046
1047                 if ( trailTime == 0 ) {
1048                         trailTime = 0.5f;
1049                 }
1050
1051                 float height = 1.0f / ( 1 + numTrails );
1052                 float t = 0;
1053
1054                 for ( int i = 0 ; i <= numTrails ; i++ ) {
1055                         g->random = g->originalRandom;
1056                         g->age = currentAge - ( i + 1 ) * trailTime / ( numTrails + 1 );        // time to back up
1057                         g->frac = g->age / particleLife;
1058
1059                         idVec3  oldOrigin;
1060                         ParticleOrigin( g, oldOrigin );
1061
1062                         up = stepOrigin - oldOrigin;    // along the direction of travel
1063
1064                         idVec3  forwardDir;
1065                         g->renderEnt->axis.ProjectVector( g->renderView->viewaxis[0], forwardDir );
1066
1067                         up -= ( up * forwardDir ) * forwardDir;
1068
1069                         up.Normalize();
1070
1071
1072                         left = up.Cross( forwardDir );
1073                         left *= psize;
1074
1075                         verts_p[0] = verts[0];
1076                         verts_p[1] = verts[1];
1077                         verts_p[2] = verts[2];
1078                         verts_p[3] = verts[3];
1079
1080                         if ( i == 0 ) {
1081                                 verts_p[0].xyz = stepOrigin - left;
1082                                 verts_p[1].xyz = stepOrigin + left;
1083                         } else {
1084                                 verts_p[0].xyz = stepOrigin - stepLeft;
1085                                 verts_p[1].xyz = stepOrigin + stepLeft;
1086                         }
1087                         verts_p[2].xyz = oldOrigin - left;
1088                         verts_p[3].xyz = oldOrigin + left;
1089
1090                         // modify texcoords
1091                         verts_p[0].st[0] = verts[0].st[0];
1092                         verts_p[0].st[1] = t;
1093
1094                         verts_p[1].st[0] = verts[1].st[0];
1095                         verts_p[1].st[1] = t;
1096
1097                         verts_p[2].st[0] = verts[2].st[0];
1098                         verts_p[2].st[1] = t+height;
1099
1100                         verts_p[3].st[0] = verts[3].st[0];
1101                         verts_p[3].st[1] = t+height;
1102
1103                         t += height;
1104
1105                         verts_p += 4;
1106
1107                         stepOrigin = oldOrigin;
1108                         stepLeft = left;
1109                 }
1110
1111                 g->random = currentRandom;
1112                 g->age = currentAge;
1113                 g->frac = currentFrac;
1114
1115                 return 4 * (numTrails+1);
1116         }
1117
1118         //
1119         // constant rotation 
1120         //
1121         float   angle;
1122
1123         angle = ( initialAngle ) ? initialAngle : 360 * g->random.RandomFloat();
1124
1125         float   angleMove = rotationSpeed.Integrate( g->frac, g->random ) * particleLife;
1126         // have hald the particles rotate each way
1127         if ( g->index & 1 ) {
1128                 angle += angleMove;
1129         } else {
1130                 angle -= angleMove;
1131         }
1132
1133         angle = angle / 180 * idMath::PI;
1134         float c = idMath::Cos16( angle );
1135         float s = idMath::Sin16( angle );
1136
1137         if ( orientation  == POR_Z ) {
1138                 // oriented in entity space
1139                 left[0] = s;
1140                 left[1] = c;
1141                 left[2] = 0;
1142                 up[0] = c;
1143                 up[1] = -s;
1144                 up[2] = 0;
1145         } else if ( orientation == POR_X ) {
1146                 // oriented in entity space
1147                 left[0] = 0;
1148                 left[1] = c;
1149                 left[2] = s;
1150                 up[0] = 0;
1151                 up[1] = -s;
1152                 up[2] = c;
1153         } else if ( orientation == POR_Y ) {
1154                 // oriented in entity space
1155                 left[0] = c;
1156                 left[1] = 0;
1157                 left[2] = s;
1158                 up[0] = -s;
1159                 up[1] = 0;
1160                 up[2] = c;
1161         } else {
1162                 // oriented in viewer space
1163                 idVec3  entityLeft, entityUp;
1164
1165                 g->renderEnt->axis.ProjectVector( g->renderView->viewaxis[1], entityLeft );
1166                 g->renderEnt->axis.ProjectVector( g->renderView->viewaxis[2], entityUp );
1167
1168                 left = entityLeft * c + entityUp * s;
1169                 up = entityUp * c - entityLeft * s;
1170         }
1171
1172         left *= width;
1173         up *= height;
1174
1175         verts[0].xyz = origin - left + up;
1176         verts[1].xyz = origin + left + up;
1177         verts[2].xyz = origin - left - up;
1178         verts[3].xyz = origin + left - up;
1179
1180         return 4;
1181 }
1182
1183 /*
1184 ==================
1185 idParticleStage::ParticleTexCoords
1186 ==================
1187 */
1188 void idParticleStage::ParticleTexCoords( particleGen_t *g, idDrawVert *verts ) const {
1189         float   s, width;
1190         float   t, height;
1191
1192         if ( animationFrames > 1 ) {
1193                 width = 1.0f / animationFrames;
1194                 float   floatFrame;
1195                 if ( animationRate ) {
1196                         // explicit, cycling animation
1197                         floatFrame = g->age * animationRate;
1198                 } else {
1199                         // single animation cycle over the life of the particle
1200                         floatFrame = g->frac * animationFrames;
1201                 }
1202                 int     intFrame = (int)floatFrame;
1203                 g->animationFrameFrac = floatFrame - intFrame;
1204                 s = width * intFrame;
1205         } else {
1206                 s = 0.0f;
1207                 width = 1.0f;
1208         }
1209
1210         t = 0.0f;
1211         height = 1.0f;
1212
1213         verts[0].st[0] = s;
1214         verts[0].st[1] = t;
1215
1216         verts[1].st[0] = s+width;
1217         verts[1].st[1] = t;
1218
1219         verts[2].st[0] = s;
1220         verts[2].st[1] = t+height;
1221
1222         verts[3].st[0] = s+width;
1223         verts[3].st[1] = t+height;
1224 }
1225
1226 /*
1227 ==================
1228 idParticleStage::ParticleColors
1229 ==================
1230 */
1231 void idParticleStage::ParticleColors( particleGen_t *g, idDrawVert *verts ) const {
1232         float   fadeFraction = 1.0f;
1233
1234         // most particles fade in at the beginning and fade out at the end
1235         if ( g->frac < fadeInFraction ) {
1236                 fadeFraction *= ( g->frac / fadeInFraction );
1237         } 
1238         if ( 1.0f - g->frac < fadeOutFraction ) {
1239                 fadeFraction *= ( ( 1.0f - g->frac ) / fadeOutFraction );
1240         }
1241
1242         // individual gun smoke particles get more and more faded as the
1243         // cycle goes on (note that totalParticles won't be correct for a surface-particle deform)
1244         if ( fadeIndexFraction ) {
1245                 float   indexFrac = ( totalParticles - g->index ) / (float)totalParticles;
1246                 if ( indexFrac < fadeIndexFraction ) {
1247                         fadeFraction *= indexFrac / fadeIndexFraction;
1248                 }
1249         }
1250
1251         for ( int i = 0 ; i < 4 ; i++ ) {
1252                 float   fcolor = ( ( entityColor ) ? g->renderEnt->shaderParms[i] : color[i] ) * fadeFraction + fadeColor[i] * ( 1.0f - fadeFraction );
1253                 int             icolor = idMath::FtoiFast( fcolor * 255.0f );
1254                 if ( icolor < 0 ) {
1255                         icolor = 0;
1256                 } else if ( icolor > 255 ) {
1257                         icolor = 255;
1258                 }
1259                 verts[0].color[i] = 
1260                 verts[1].color[i] = 
1261                 verts[2].color[i] = 
1262                 verts[3].color[i] = icolor;
1263         }
1264 }
1265
1266 /*
1267 ================
1268 idParticleStage::CreateParticle
1269
1270 Returns 0 if no particle is created because it is completely faded out
1271 Returns 4 if a normal quad is created
1272 Returns 8 if two cross faded quads are created
1273
1274 Vertex order is:
1275
1276 0 1
1277 2 3
1278 ================
1279 */
1280 int idParticleStage::CreateParticle( particleGen_t *g, idDrawVert *verts ) const {
1281         idVec3  origin;
1282
1283         verts[0].Clear();
1284         verts[1].Clear();
1285         verts[2].Clear();
1286         verts[3].Clear();
1287
1288         ParticleColors( g, verts );
1289
1290         // if we are completely faded out, kill the particle
1291         if ( verts[0].color[0] == 0 && verts[0].color[1] == 0 && verts[0].color[2] == 0 && verts[0].color[3] == 0 ) {
1292                 return 0;
1293         }
1294
1295         ParticleOrigin( g, origin );
1296
1297         ParticleTexCoords( g, verts );
1298
1299         int     numVerts = ParticleVerts( g, origin, verts );
1300
1301         if ( animationFrames <= 1 ) {
1302                 return numVerts;
1303         }
1304
1305         // if we are doing strip-animation, we need to double the quad and cross fade it
1306         float   width = 1.0f / animationFrames;
1307         float   frac = g->animationFrameFrac;
1308         float   iFrac = 1.0f - frac;
1309         for ( int i = 0 ; i < numVerts ; i++ ) {
1310                 verts[numVerts + i] = verts[i];
1311
1312                 verts[numVerts + i].st[0] += width;
1313
1314                 verts[numVerts + i].color[0] *= frac;
1315                 verts[numVerts + i].color[1] *= frac;
1316                 verts[numVerts + i].color[2] *= frac;
1317                 verts[numVerts + i].color[3] *= frac;
1318
1319                 verts[i].color[0] *= iFrac;
1320                 verts[i].color[1] *= iFrac;
1321                 verts[i].color[2] *= iFrac;
1322                 verts[i].color[3] *= iFrac;
1323         }
1324
1325         return numVerts * 2;
1326 }
1327
1328 /*
1329 ==================
1330 idParticleStage::GetCustomPathName
1331 ==================
1332 */
1333 const char* idParticleStage::GetCustomPathName() {
1334         int index = ( customPathType < CustomParticleCount ) ? customPathType : 0;
1335         return ParticleCustomDesc[index].name;
1336 }
1337
1338 /*
1339 ==================
1340 idParticleStage::GetCustomPathDesc
1341 ==================
1342 */
1343 const char* idParticleStage::GetCustomPathDesc() {
1344         int index = ( customPathType < CustomParticleCount ) ? customPathType : 0;
1345         return ParticleCustomDesc[index].desc;
1346 }
1347
1348 /*
1349 ==================
1350 idParticleStage::NumCustomPathParms
1351 ==================
1352 */
1353 int idParticleStage::NumCustomPathParms() {
1354         int index = ( customPathType < CustomParticleCount ) ? customPathType : 0;
1355         return ParticleCustomDesc[index].count;
1356 }
1357
1358 /*
1359 ==================
1360 idParticleStage::SetCustomPathType
1361 ==================
1362 */
1363 void idParticleStage::SetCustomPathType( const char *p ) {
1364         customPathType = PPATH_STANDARD;
1365         for ( int i = 0; i < CustomParticleCount; i ++ ) {
1366                 if ( idStr::Icmp( p, ParticleCustomDesc[i].name ) == 0 ) {
1367                         customPathType = static_cast<prtCustomPth_t>( i );
1368                         break;
1369                 }
1370         }
1371 }
1372
1373 /*
1374 ==================
1375 idParticleStage::operator=
1376 ==================
1377 */
1378 void idParticleStage::operator=( const idParticleStage &src ) {
1379         material = src.material;
1380         totalParticles = src.totalParticles;
1381         cycles = src.cycles;
1382         cycleMsec = src.cycleMsec;
1383         spawnBunching = src.spawnBunching;
1384         particleLife = src.particleLife;
1385         timeOffset = src.timeOffset;
1386         deadTime = src.deadTime;
1387         distributionType = src.distributionType;
1388         distributionParms[0] = src.distributionParms[0];
1389         distributionParms[1] = src.distributionParms[1];
1390         distributionParms[2] = src.distributionParms[2];
1391         distributionParms[3] = src.distributionParms[3];
1392         directionType = src.directionType;
1393         directionParms[0] = src.directionParms[0];
1394         directionParms[1] = src.directionParms[1];
1395         directionParms[2] = src.directionParms[2];
1396         directionParms[3] = src.directionParms[3];
1397         speed = src.speed;
1398         gravity = src.gravity;
1399         worldGravity = src.worldGravity;
1400         randomDistribution = src.randomDistribution;
1401         entityColor = src.entityColor;
1402         customPathType = src.customPathType;
1403         customPathParms[0] = src.customPathParms[0];
1404         customPathParms[1] = src.customPathParms[1];
1405         customPathParms[2] = src.customPathParms[2];
1406         customPathParms[3] = src.customPathParms[3];
1407         customPathParms[4] = src.customPathParms[4];
1408         customPathParms[5] = src.customPathParms[5];
1409         customPathParms[6] = src.customPathParms[6];
1410         customPathParms[7] = src.customPathParms[7];
1411         offset = src.offset;
1412         animationFrames = src.animationFrames;
1413         animationRate = src.animationRate;
1414         initialAngle = src.initialAngle;
1415         rotationSpeed = src.rotationSpeed;
1416         orientation = src.orientation;
1417         orientationParms[0] = src.orientationParms[0];
1418         orientationParms[1] = src.orientationParms[1];
1419         orientationParms[2] = src.orientationParms[2];
1420         orientationParms[3] = src.orientationParms[3];
1421         size = src.size;
1422         aspect = src.aspect;
1423         color = src.color;
1424         fadeColor = src.fadeColor;
1425         fadeInFraction = src.fadeInFraction;
1426         fadeOutFraction = src.fadeOutFraction;
1427         fadeIndexFraction = src.fadeIndexFraction;
1428         hidden = src.hidden;
1429         boundsExpansion = src.boundsExpansion;
1430         bounds = src.bounds;
1431 }