DOOM-3-BFG

DOOM 3 BFG Edition
Log | Files | Refs

DeclParticle.cpp (45958B)


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