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 }