2 ===========================================================================
5 Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
7 This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).
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.
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.
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/>.
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.
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.
26 ===========================================================================
29 #include "../idlib/precompiled.h"
32 struct ParticleParmDesc {
38 const ParticleParmDesc ParticleDistributionDesc[] = {
40 { "cylinder", 4, "" },
44 const ParticleParmDesc ParticleDirectionDesc[] = {
49 const ParticleParmDesc ParticleOrientationDesc[] = {
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" }
65 const int CustomParticleCount = sizeof( ParticleCustomDesc ) / sizeof( const ParticleParmDesc );
72 size_t idDeclParticle::Size( void ) const {
73 return sizeof( idDeclParticle );
78 idDeclParticle::GetStageBounds
81 void idDeclParticle::GetStageBounds( idParticleStage *stage ) {
83 stage->bounds.Clear();
85 // this isn't absolutely guaranteed, but it should be close
89 renderEntity_t renderEntity;
90 memset( &renderEntity, 0, sizeof( renderEntity ) );
91 renderEntity.axis = mat3_identity;
93 renderView_t renderView;
94 memset( &renderView, 0, sizeof( renderView ) );
95 renderView.viewaxis = mat3_identity;
97 g.renderEnt = &renderEntity;
98 g.renderView = &renderView;
100 g.axis = mat3_identity;
102 idRandom steppingRandom;
103 steppingRandom.SetSeed( 0 );
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;
109 int maxMsec = stage->particleLife * 1000;
110 for ( int inCycleTime = 0 ; inCycleTime < maxMsec ; inCycleTime += 16 ) {
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;
117 g.frac = (float)inCycleTime / ( stage->particleLife * 1000 );
118 g.age = inCycleTime * 0.001f;
120 // if the particle doesn't get drawn because it is faded out or beyond a kill region,
121 // don't increment the verts
124 stage->ParticleOrigin( &g, origin );
125 stage->bounds.AddPoint( origin );
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 );
138 if ( size > maxSize ) {
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 );
150 idDeclParticle::ParseParms
152 Parses a variable length list of parms on one line
155 void idDeclParticle::ParseParms( idLexer &src, float *parms, int maxParms ) {
158 memset( parms, 0, maxParms * sizeof( *parms ) );
161 if ( !src.ReadTokenOnLine( &token ) ) {
164 if ( count == maxParms ) {
165 src.Error( "too many parms on line" );
169 parms[count] = atof( token );
176 idDeclParticle::ParseParametric
179 void idDeclParticle::ParseParametric( idLexer &src, idParticleParm *parm ) {
183 parm->from = parm->to = 0.0f;
185 if ( !src.ReadToken( &token ) ) {
186 src.Error( "not enough parameters" );
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" );
199 parm->to = atof( token );
201 src.UnreadToken( &token );
206 parm->table = static_cast<const idDeclTable *>( declManager->FindType( DECL_TABLE, token, false ) );
213 idDeclParticle::ParseParticleStage
216 idParticleStage *idDeclParticle::ParseParticleStage( idLexer &src ) {
219 idParticleStage *stage = new idParticleStage;
223 if ( src.HadError() ) {
226 if ( !src.ReadToken( &token ) ) {
229 if ( !token.Icmp( "}" ) ) {
232 if ( !token.Icmp( "material" ) ) {
233 src.ReadToken( &token );
234 stage->material = declManager->FindMaterial( token.c_str() );
237 if ( !token.Icmp( "count" ) ) {
238 stage->totalParticles = src.ParseInt();
241 if ( !token.Icmp( "time" ) ) {
242 stage->particleLife = src.ParseFloat();
245 if ( !token.Icmp( "cycles" ) ) {
246 stage->cycles = src.ParseFloat();
249 if ( !token.Icmp( "timeOffset" ) ) {
250 stage->timeOffset = src.ParseFloat();
253 if ( !token.Icmp( "deadTime" ) ) {
254 stage->deadTime = src.ParseFloat();
257 if ( !token.Icmp( "randomDistribution" ) ) {
258 stage->randomDistribution = src.ParseBool();
261 if ( !token.Icmp( "bunching" ) ) {
262 stage->spawnBunching = src.ParseFloat();
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;
275 src.Error( "bad distribution type: %s\n", token.c_str() );
277 ParseParms( src, stage->distributionParms, sizeof( stage->distributionParms ) / sizeof( stage->distributionParms[0] ) );
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;
288 src.Error( "bad direction type: %s\n", token.c_str() );
290 ParseParms( src, stage->directionParms, sizeof( stage->directionParms ) / sizeof( stage->directionParms[0] ) );
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;
307 src.Error( "bad orientation type: %s\n", token.c_str() );
309 ParseParms( src, stage->orientationParms, sizeof( stage->orientationParms ) / sizeof( stage->orientationParms[0] ) );
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;
324 src.Error( "bad path type: %s\n", token.c_str() );
326 ParseParms( src, stage->customPathParms, sizeof( stage->customPathParms ) / sizeof( stage->customPathParms[0] ) );
330 if ( !token.Icmp( "speed" ) ) {
331 ParseParametric( src, &stage->speed );
334 if ( !token.Icmp( "rotation" ) ) {
335 ParseParametric( src, &stage->rotationSpeed );
338 if ( !token.Icmp( "angle" ) ) {
339 stage->initialAngle = src.ParseFloat();
342 if ( !token.Icmp( "entityColor" ) ) {
343 stage->entityColor = src.ParseBool();
346 if ( !token.Icmp( "size" ) ) {
347 ParseParametric( src, &stage->size );
350 if ( !token.Icmp( "aspect" ) ) {
351 ParseParametric( src, &stage->aspect );
354 if ( !token.Icmp( "fadeIn" ) ) {
355 stage->fadeInFraction = src.ParseFloat();
358 if ( !token.Icmp( "fadeOut" ) ) {
359 stage->fadeOutFraction = src.ParseFloat();
362 if ( !token.Icmp( "fadeIndex" ) ) {
363 stage->fadeIndexFraction = src.ParseFloat();
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();
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();
380 if ( !token.Icmp("offset" ) ) {
381 stage->offset[0] = src.ParseFloat();
382 stage->offset[1] = src.ParseFloat();
383 stage->offset[2] = src.ParseFloat();
386 if ( !token.Icmp( "animationFrames" ) ) {
387 stage->animationFrames = src.ParseInt();
390 if ( !token.Icmp( "animationRate" ) ) {
391 stage->animationRate = src.ParseFloat();
394 if ( !token.Icmp( "boundsExpansion" ) ) {
395 stage->boundsExpansion = src.ParseFloat();
398 if ( !token.Icmp( "gravity" ) ) {
399 src.ReadToken( &token );
400 if ( !token.Icmp( "world" ) ) {
401 stage->worldGravity = true;
403 src.UnreadToken( &token );
405 stage->gravity = src.ParseFloat();
409 src.Error( "unknown token %s\n", token.c_str() );
413 stage->cycleMsec = ( stage->particleLife + stage->deadTime ) * 1000;
420 idDeclParticle::Parse
423 bool idDeclParticle::Parse( const char *text, const int textLength ) {
427 src.LoadMemory( text, textLength, GetFileName(), GetLineNum() );
428 src.SetFlags( DECL_LEXER_FLAGS );
429 src.SkipUntilString( "{" );
434 if ( !src.ReadToken( &token ) ) {
438 if ( !token.Icmp( "}" ) ) {
442 if ( !token.Icmp( "{" ) ) {
443 idParticleStage *stage = ParseParticleStage( src );
445 src.Warning( "Particle stage parse failed" );
449 stages.Append( stage );
453 if ( !token.Icmp( "depthHack" ) ) {
454 depthHack = src.ParseFloat();
458 src.Warning( "bad token %s", token.c_str() );
464 // calculate the bounds
467 for( int i = 0; i < stages.Num(); i++ ) {
468 GetStageBounds( stages[i] );
469 bounds.AddBounds( stages[i]->bounds );
472 if ( bounds.GetVolume() <= 0.1f ) {
473 bounds = idBounds( vec3_origin ).Expand( 8.0f );
481 idDeclParticle::FreeData
484 void idDeclParticle::FreeData( void ) {
485 stages.DeleteContents( true );
490 idDeclParticle::DefaultDefinition
493 const char *idDeclParticle::DefaultDefinition( void ) const {
497 "\t\t" "material\t_default\n"
499 "\t\t" "time\t\t1.0\n"
506 idDeclParticle::WriteParticleParm
509 void idDeclParticle::WriteParticleParm( idFile *f, idParticleParm *parm, const char *name ) {
511 f->WriteFloatString( "\t\t%s\t\t\t\t ", name );
513 f->WriteFloatString( "%s\n", parm->table->GetName() );
515 f->WriteFloatString( "\"%.3f\" ", parm->from );
516 if ( parm->from == parm->to ) {
517 f->WriteFloatString( "\n" );
519 f->WriteFloatString( " to \"%.3f\"\n", parm->to );
526 idDeclParticle::WriteStage
529 void idDeclParticle::WriteStage( idFile *f, idParticleStage *stage ) {
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 );
539 if ( stage->animationRate ) {
540 f->WriteFloatString( "\t\tanimationRate \t\t%.3f\n", stage->animationRate );
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 );
547 if ( stage->deadTime ) {
548 f->WriteFloatString( "\t\tdeadTime\t\t\t%.3f\n", stage->deadTime );
550 f->WriteFloatString( "\t\tbunching\t\t\t%.3f\n", stage->spawnBunching );
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] );
556 f->WriteFloatString( "\n" );
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] );
562 f->WriteFloatString( "\n" );
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] );
568 f->WriteFloatString( "\n" );
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] );
575 f->WriteFloatString( "\n" );
578 if ( stage->entityColor ) {
579 f->WriteFloatString( "\t\tentityColor\t\t\t1\n" );
582 WriteParticleParm( f, &stage->speed, "speed" );
583 WriteParticleParm( f, &stage->size, "size" );
584 WriteParticleParm( f, &stage->aspect, "aspect" );
586 if ( stage->rotationSpeed.from ) {
587 WriteParticleParm( f, &stage->rotationSpeed, "rotation" );
590 if ( stage->initialAngle ) {
591 f->WriteFloatString( "\t\tangle\t\t\t\t%.3f\n", stage->initialAngle );
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 );
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 );
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 );
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 " );
610 f->WriteFloatString( "%.3f\n", stage->gravity );
611 f->WriteFloatString( "\t}\n" );
616 idDeclParticle::RebuildTextSource
619 bool idDeclParticle::RebuildTextSource( void ) {
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"
627 f.WriteFloatString( "particle %s {\n", GetName() );
630 f.WriteFloatString( "\tdepthHack\t%f\n", depthHack );
633 for ( int i = 0; i < stages.Num(); i++ ) {
634 WriteStage( &f, stages[i] );
637 f.WriteFloatString( "}" );
639 SetText( f.GetDataPtr() );
649 bool idDeclParticle::Save( const char *fileName ) {
652 declManager->CreateNewDecl( DECL_PARTICLE, GetName(), fileName );
654 ReplaceSourceFileText();
659 ====================================================================================
663 ====================================================================================
666 float idParticleParm::Eval( float frac, idRandom &rand ) const {
668 return table->TableLookup( frac );
670 return from + frac * ( to - from );
673 float idParticleParm::Integrate( float frac, idRandom &rand ) const {
675 common->Printf( "idParticleParm::Integrate: can't integrate tables\n" );
678 return ( from + frac * ( to - from ) * 0.5f ) * frac;
682 ====================================================================================
686 ====================================================================================
691 idParticleStage::idParticleStage
694 idParticleStage::idParticleStage( void ) {
699 spawnBunching = 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;
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;
715 animationRate = 0.0f;
716 randomDistribution = true;
719 // idParticleParm rotationSpeed;
720 orientation = POR_VIEW;
721 orientationParms[0] = orientationParms[1] = orientationParms[2] = orientationParms[3] = 0.0f;
722 // idParticleParm size
723 // idParticleParm aspect
726 fadeInFraction = 0.0f;
727 fadeOutFraction = 0.0f;
728 fadeIndexFraction = 0.0f;
730 boundsExpansion = 0.0f;
736 idParticleStage::Default
738 Sets the stage to a default state
741 void idParticleStage::Default() {
742 material = declManager->FindMaterial( "_default" );
743 totalParticles = 100;
744 spawnBunching = 1.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;
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;
779 animationRate = 0.0f;
781 rotationSpeed.from = 0.0f;
782 rotationSpeed.to = 0.0f;
783 rotationSpeed.table = NULL;
798 fadeInFraction = 0.1f;
799 fadeOutFraction = 0.25f;
800 fadeIndexFraction = 0.0f;
801 boundsExpansion = 0.0f;
802 randomDistribution = true;
804 cycleMsec = ( particleLife + deadTime ) * 1000;
809 idParticleStage::NumQuadsPerParticle
811 includes trails and cross faded animations
814 int idParticleStage::NumQuadsPerParticle() const {
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 );
823 // if we are doing strip-animation, we need to double the number and cross fade them
824 if ( animationFrames > 1 ) {
833 idParticleStage::ParticleOrigin
836 void idParticleStage::ParticleOrigin( particleGen_t *g, idVec3 &origin ) const {
837 if ( customPathType == PPATH_STANDARD ) {
839 // find intial origin distribution
841 float radiusSqr, angle1, angle2;
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];
850 case PDIST_CYLINDER: { // ( sizeX sizeY sizeZ ringFraction )
851 angle1 = ( ( randomDistribution ) ? g->random.CRandomFloat() : 1.0f ) * idMath::TWO_PI;
853 idMath::SinCos16( angle1, origin[0], origin[1] );
854 origin[2] = ( ( randomDistribution ) ? g->random.CRandomFloat() : 1.0f );
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;
866 origin[0] *= rescale;
867 origin[1] *= rescale;
870 origin[0] *= distributionParms[0];
871 origin[1] *= distributionParms[1];
872 origin[2] *= distributionParms[2];
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 ) {
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 );
885 origin.Set( 1.0f, 1.0f, 1.0f );
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;
899 origin[0] *= rescale;
900 origin[1] *= rescale;
901 origin[2] *= rescale;
904 origin[0] *= distributionParms[0];
905 origin[1] *= distributionParms[1];
906 origin[2] *= distributionParms[2];
911 // offset will effect all particle origin types
912 // add this before the velocity and gravity additions
916 // add the velocity over time
920 switch( directionType ) {
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;
926 float s1, c1, s2, c2;
927 idMath::SinCos16( angle1, s1, c1 );
928 idMath::SinCos16( angle2, s2, c2 );
938 dir[2] += directionParms[0];
944 float iSpeed = speed.Integrate( g->frac, g->random );
945 origin += dir * iSpeed * particleLife;
949 // custom paths completely override both the origin and velocity calculations, but still
950 // use the standard gravity
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;
960 idMath::SinCos16( angle1, s1, c1 );
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;
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;
973 float s1, c1, s2, c2;
974 idMath::SinCos16( angle1, s1, c1 );
975 idMath::SinCos16( angle2, s2, c2 );
980 origin *= customPathParms[2];
983 case PPATH_ORBIT: { // ( radius speed axis )
984 angle1 = g->random.RandomFloat() * idMath::TWO_PI + customPathParms[1] * g->age;
987 idMath::SinCos16( angle1, s1, c1 );
989 origin[0] = c1 * customPathParms[0];
990 origin[1] = s1 * customPathParms[0];
991 origin.ProjectSelfOntoSphere( customPathParms[0] );
994 case PPATH_DRIP: { // ( speed )
997 origin[2] = -( g->age * customPathParms[0] );
1001 common->Error( "idParticleStage::ParticleOrigin: bad customPathType" );
1008 // adjust for the per-particle smoke offset
1010 origin += g->origin;
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;
1018 origin[2] -= gravity * g->age * g->age;
1024 idParticleStage::ParticleVerts
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 );
1031 float width = psize;
1032 float height = psize * paspect;
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;
1044 int numTrails = idMath::Ftoi( orientationParms[0] );
1045 float trailTime = orientationParms[1];
1047 if ( trailTime == 0 ) {
1051 float height = 1.0f / ( 1 + numTrails );
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;
1060 ParticleOrigin( g, oldOrigin );
1062 up = stepOrigin - oldOrigin; // along the direction of travel
1065 g->renderEnt->axis.ProjectVector( g->renderView->viewaxis[0], forwardDir );
1067 up -= ( up * forwardDir ) * forwardDir;
1072 left = up.Cross( forwardDir );
1075 verts_p[0] = verts[0];
1076 verts_p[1] = verts[1];
1077 verts_p[2] = verts[2];
1078 verts_p[3] = verts[3];
1081 verts_p[0].xyz = stepOrigin - left;
1082 verts_p[1].xyz = stepOrigin + left;
1084 verts_p[0].xyz = stepOrigin - stepLeft;
1085 verts_p[1].xyz = stepOrigin + stepLeft;
1087 verts_p[2].xyz = oldOrigin - left;
1088 verts_p[3].xyz = oldOrigin + left;
1091 verts_p[0].st[0] = verts[0].st[0];
1092 verts_p[0].st[1] = t;
1094 verts_p[1].st[0] = verts[1].st[0];
1095 verts_p[1].st[1] = t;
1097 verts_p[2].st[0] = verts[2].st[0];
1098 verts_p[2].st[1] = t+height;
1100 verts_p[3].st[0] = verts[3].st[0];
1101 verts_p[3].st[1] = t+height;
1107 stepOrigin = oldOrigin;
1111 g->random = currentRandom;
1112 g->age = currentAge;
1113 g->frac = currentFrac;
1115 return 4 * (numTrails+1);
1119 // constant rotation
1123 angle = ( initialAngle ) ? initialAngle : 360 * g->random.RandomFloat();
1125 float angleMove = rotationSpeed.Integrate( g->frac, g->random ) * particleLife;
1126 // have hald the particles rotate each way
1127 if ( g->index & 1 ) {
1133 angle = angle / 180 * idMath::PI;
1134 float c = idMath::Cos16( angle );
1135 float s = idMath::Sin16( angle );
1137 if ( orientation == POR_Z ) {
1138 // oriented in entity space
1145 } else if ( orientation == POR_X ) {
1146 // oriented in entity space
1153 } else if ( orientation == POR_Y ) {
1154 // oriented in entity space
1162 // oriented in viewer space
1163 idVec3 entityLeft, entityUp;
1165 g->renderEnt->axis.ProjectVector( g->renderView->viewaxis[1], entityLeft );
1166 g->renderEnt->axis.ProjectVector( g->renderView->viewaxis[2], entityUp );
1168 left = entityLeft * c + entityUp * s;
1169 up = entityUp * c - entityLeft * s;
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;
1185 idParticleStage::ParticleTexCoords
1188 void idParticleStage::ParticleTexCoords( particleGen_t *g, idDrawVert *verts ) const {
1192 if ( animationFrames > 1 ) {
1193 width = 1.0f / animationFrames;
1195 if ( animationRate ) {
1196 // explicit, cycling animation
1197 floatFrame = g->age * animationRate;
1199 // single animation cycle over the life of the particle
1200 floatFrame = g->frac * animationFrames;
1202 int intFrame = (int)floatFrame;
1203 g->animationFrameFrac = floatFrame - intFrame;
1204 s = width * intFrame;
1216 verts[1].st[0] = s+width;
1220 verts[2].st[1] = t+height;
1222 verts[3].st[0] = s+width;
1223 verts[3].st[1] = t+height;
1228 idParticleStage::ParticleColors
1231 void idParticleStage::ParticleColors( particleGen_t *g, idDrawVert *verts ) const {
1232 float fadeFraction = 1.0f;
1234 // most particles fade in at the beginning and fade out at the end
1235 if ( g->frac < fadeInFraction ) {
1236 fadeFraction *= ( g->frac / fadeInFraction );
1238 if ( 1.0f - g->frac < fadeOutFraction ) {
1239 fadeFraction *= ( ( 1.0f - g->frac ) / fadeOutFraction );
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;
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 );
1256 } else if ( icolor > 255 ) {
1262 verts[3].color[i] = icolor;
1268 idParticleStage::CreateParticle
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
1280 int idParticleStage::CreateParticle( particleGen_t *g, idDrawVert *verts ) const {
1288 ParticleColors( g, verts );
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 ) {
1295 ParticleOrigin( g, origin );
1297 ParticleTexCoords( g, verts );
1299 int numVerts = ParticleVerts( g, origin, verts );
1301 if ( animationFrames <= 1 ) {
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];
1312 verts[numVerts + i].st[0] += width;
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;
1319 verts[i].color[0] *= iFrac;
1320 verts[i].color[1] *= iFrac;
1321 verts[i].color[2] *= iFrac;
1322 verts[i].color[3] *= iFrac;
1325 return numVerts * 2;
1330 idParticleStage::GetCustomPathName
1333 const char* idParticleStage::GetCustomPathName() {
1334 int index = ( customPathType < CustomParticleCount ) ? customPathType : 0;
1335 return ParticleCustomDesc[index].name;
1340 idParticleStage::GetCustomPathDesc
1343 const char* idParticleStage::GetCustomPathDesc() {
1344 int index = ( customPathType < CustomParticleCount ) ? customPathType : 0;
1345 return ParticleCustomDesc[index].desc;
1350 idParticleStage::NumCustomPathParms
1353 int idParticleStage::NumCustomPathParms() {
1354 int index = ( customPathType < CustomParticleCount ) ? customPathType : 0;
1355 return ParticleCustomDesc[index].count;
1360 idParticleStage::SetCustomPathType
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 );
1375 idParticleStage::operator=
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];
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];
1422 aspect = src.aspect;
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;