diff --git a/src/bse/BSE.h b/src/bse/BSE.h index 9b553a7..43243dc 100644 --- a/src/bse/BSE.h +++ b/src/bse/BSE.h @@ -218,11 +218,12 @@ enum { STFLAG_ENABLED = BIT( 0 ), STFLAG_ATTENUATE_EMITTER = BIT( 6 ), STFLAG_INVERSE_ATTENUATE = BIT( 7 ), STFLAG_TEMPORARY = BIT( 8 ), - STFLAG_USEMATCOLOR = BIT( 9 ), + // Quake 4 uses bit 9 to select the generic particle service path. + // This covers physics, trails, timeout effects, electricity and lights. + STFLAG_COMPLEX = BIT( 9 ), STFLAG_DEPTH_SORT = BIT( 10 ), STFLAG_INVERSE_DRAWORDER = BIT( 11 ), STFLAG_ORIENTATE_IDENTITY = BIT( 12 ), - STFLAG_COMPLEX = BIT( 13 ), STFLAG_CALCULATE_DURATION = BIT( 14 ), }; @@ -254,7 +255,6 @@ public: bool GetInfiniteDuration( void ) const { return( !!( mFlags & STFLAG_INFINITE_DURATION ) ); } bool GetAttenuateEmitter( void ) const { return( !!( mFlags & STFLAG_ATTENUATE_EMITTER ) ); } bool GetInverseAttenuate( void ) const { return( !!( mFlags & STFLAG_INVERSE_ATTENUATE ) ); } - bool GetUseMaterialColor( void ) const { return( !!( mFlags & STFLAG_USEMATCOLOR ) ); } bool GetTemporary( void ) const { return( !!( mFlags & STFLAG_TEMPORARY ) ); } bool GetDepthSort( void ) const { return( !!( mFlags & STFLAG_DEPTH_SORT ) ); } bool GetInverseDrawOrder( void ) const { return( !!( mFlags & STFLAG_INVERSE_DRAWORDER ) ); } @@ -270,7 +270,6 @@ public: void SetInfiniteDuration( bool infiniteDuration ) { SetFlag( infiniteDuration, STFLAG_INFINITE_DURATION ); } void SetAttenuateEmitter( bool attenuate ) { SetFlag( attenuate, STFLAG_ATTENUATE_EMITTER ); } void SetInverseAttenuate( bool attenuate ) { SetFlag( attenuate, STFLAG_INVERSE_ATTENUATE ); } - void SetUseMaterialColor( bool attenuate ) { SetFlag( attenuate, STFLAG_USEMATCOLOR ); } void SetTemporary( bool persistent ) { SetFlag( persistent, STFLAG_TEMPORARY ); } void SetDepthSort( bool locked ) { SetFlag( locked, STFLAG_DEPTH_SORT ); } void SetInverseDrawOrder( bool inverseDrawOrder ) { SetFlag( inverseDrawOrder, STFLAG_INVERSE_DRAWORDER ); } diff --git a/src/bse/BSE_Light.cpp b/src/bse/BSE_Light.cpp index 5a892ec..4384403 100644 --- a/src/bse/BSE_Light.cpp +++ b/src/bse/BSE_Light.cpp @@ -85,7 +85,7 @@ bool rvLightParticle::InitLight( rvBSE *effect, rvSegmentTemplate *st, float tim EvaluateSize( evalTime, size.ToFloatPtr() ); idVec3 position; - EvaluatePosition( effect, position, evalTime - mMotionStartTime ); + EvaluatePosition( effect, position, time - mMotionStartTime ); mLight.origin = effect->GetCurrentOrigin() + position * effect->GetCurrentAxis(); mLight.lightRadius.Set( size[0] < 1.0f ? 1.0f : size[0], @@ -118,7 +118,7 @@ bool rvLightParticle::PresentLight( rvBSE *effect, float time, bool infinite ) { EvaluateSize( evalTime, size.ToFloatPtr() ); idVec3 position; - EvaluatePosition( effect, position, evalTime - mMotionStartTime ); + EvaluatePosition( effect, position, time - mMotionStartTime ); mLight.origin = effect->GetCurrentOrigin() + position * effect->GetCurrentAxis(); mLight.lightRadius.Set( size[0] < 1.0f ? 1.0f : size[0], diff --git a/src/bse/BSE_ParseParticle2.cpp b/src/bse/BSE_ParseParticle2.cpp index 7989e4e..bffc401 100644 --- a/src/bse/BSE_ParseParticle2.cpp +++ b/src/bse/BSE_ParseParticle2.cpp @@ -45,6 +45,333 @@ bool rvParticleTemplate::GetVector( idLexer *lexer, int count, idVec3 &result ) return true; } +bool rvParticleTemplate::ParseMotionParms( idLexer *lexer, int count, + rvEnvParms &envelope ) { + idToken token; + if ( !lexer->ExpectTokenString( "{" ) || !lexer->ReadToken( &token ) ) { + return false; + } + while ( token.Cmp( "}" ) != 0 ) { + if ( token.Icmp( "envelope" ) == 0 ) { + if ( !lexer->ReadToken( &token ) ) { + return false; + } + envelope.SetType( token.c_str() ); + } else if ( token.Icmp( "rate" ) == 0 ) { + if ( !GetVector( lexer, count, envelope.GetRateRef() ) ) { + return false; + } + envelope.SetIsCount( false ); + } else if ( token.Icmp( "count" ) == 0 ) { + if ( !GetVector( lexer, count, envelope.GetRateRef() ) ) { + return false; + } + envelope.SetIsCount( true ); + } else if ( token.Icmp( "offset" ) == 0 ) { + if ( !GetVector( lexer, count, envelope.GetOffsetRef() ) ) { + return false; + } + } else { + common->Warning( "^4BSE:^1 Invalid motion parameter '%s' (file: %s, line: %d)", + token.c_str(), lexer->GetFileName(), lexer->GetLineNum() ); + } + if ( !lexer->ReadToken( &token ) ) { + return false; + } + } + return true; +} + +bool rvParticleTemplate::ParseMotionDomains( rvDeclEffect *effect, + idLexer *lexer ) { + idToken token; + if ( !lexer->ExpectTokenString( "{" ) || !lexer->ReadToken( &token ) ) { + return false; + } + while ( token.Cmp( "}" ) != 0 ) { + bool parsed = true; + if ( token.Icmp( "tint" ) == 0 ) { + parsed = ParseMotionParms( lexer, 3, mTintEnvelope ); + } else if ( token.Icmp( "fade" ) == 0 ) { + parsed = ParseMotionParms( lexer, 1, mFadeEnvelope ); + } else if ( token.Icmp( "size" ) == 0 ) { + parsed = ParseMotionParms( lexer, mNumSizeParms, mSizeEnvelope ); + } else if ( token.Icmp( "rotate" ) == 0 ) { + parsed = ParseMotionParms( lexer, mNumRotateParms, mRotateEnvelope ); + } else if ( token.Icmp( "angle" ) == 0 ) { + parsed = ParseMotionParms( lexer, 3, mAngleEnvelope ); + } else if ( token.Icmp( "offset" ) == 0 ) { + parsed = ParseMotionParms( lexer, 3, mOffsetEnvelope ); + } else if ( token.Icmp( "length" ) == 0 ) { + parsed = ParseMotionParms( lexer, 3, mLengthEnvelope ); + } else { + common->Warning( "^4BSE:^1 Invalid motion domain '%s' in '%s' (file: %s, line: %d)", + token.c_str(), effect ? effect->GetName() : "", + lexer->GetFileName(), lexer->GetLineNum() ); + lexer->SkipBracedSection( true ); + } + if ( !parsed || !lexer->ReadToken( &token ) ) { + return false; + } + } + return true; +} + +bool rvParticleTemplate::CheckCommonParms( idLexer *lexer, + rvParticleParms &parms ) { + idToken token; + if ( !lexer->ReadToken( &token ) ) { + return false; + } + while ( token.Cmp( "}" ) != 0 ) { + if ( token.Icmp( "surface" ) == 0 ) { + parms.mFlags |= PPFLAG_SURFACE; + } else if ( token.Icmp( "useEndOrigin" ) == 0 ) { + parms.mFlags |= PPFLAG_USEENDORIGIN; + } else if ( token.Icmp( "cone" ) == 0 ) { + parms.mFlags |= PPFLAG_CONE; + } else if ( token.Icmp( "relative" ) == 0 ) { + parms.mFlags |= PPFLAG_RELATIVE; + } else if ( token.Icmp( "linearSpacing" ) == 0 ) { + parms.mFlags |= PPFLAG_LINEARSPACING; + } else if ( token.Icmp( "attenuate" ) == 0 ) { + parms.mFlags |= PPFLAG_ATTENUATE; + } else if ( token.Icmp( "inverseAttenuate" ) == 0 ) { + parms.mFlags |= PPFLAG_INV_ATTENUATE; + } + if ( !lexer->ReadToken( &token ) ) { + return false; + } + } + return true; +} + +bool rvParticleTemplate::ParseSpawnParms( rvDeclEffect *effect, + idLexer *lexer, rvParticleParms &parms, int count ) { + idToken token; + if ( !lexer->ExpectTokenString( "{" ) || !lexer->ReadToken( &token ) ) { + return false; + } + if ( token.Cmp( "}" ) == 0 ) { + return true; + } + + bool fixup = true; + if ( token.Icmp( "point" ) == 0 ) { + parms.mSpawnType = SPF_POINT_0 + count; + if ( !GetVector( lexer, count, parms.mMins ) ) { return false; } + } else if ( token.Icmp( "line" ) == 0 ) { + parms.mSpawnType = SPF_LINEAR_0 + count; + if ( !GetVector( lexer, count, parms.mMins ) || + !lexer->ExpectTokenString( "," ) || + !GetVector( lexer, count, parms.mMaxs ) ) { return false; } + } else if ( token.Icmp( "box" ) == 0 || token.Icmp( "sphere" ) == 0 || + token.Icmp( "cylinder" ) == 0 ) { + const int base = token.Icmp( "box" ) == 0 ? SPF_BOX_0 : + ( token.Icmp( "sphere" ) == 0 ? SPF_SPHERE_0 : SPF_CYLINDER_0 ); + parms.mSpawnType = base + count; + if ( !GetVector( lexer, count, parms.mMins ) || + !lexer->ExpectTokenString( "," ) || + !GetVector( lexer, count, parms.mMaxs ) ) { return false; } + } else if ( token.Icmp( "spiral" ) == 0 ) { + parms.mSpawnType = SPF_SPIRAL_0 + count; + if ( !GetVector( lexer, count, parms.mMins ) || + !lexer->ExpectTokenString( "," ) || + !GetVector( lexer, count, parms.mMaxs ) || + !lexer->ExpectTokenString( "," ) ) { return false; } + parms.mRange = lexer->ParseFloat(); + } else if ( token.Icmp( "model" ) == 0 ) { + parms.mSpawnType = SPF_MODEL_0 + count; + if ( !lexer->ReadToken( &token ) ) { return false; } + idRenderModel *model = renderModelManager->FindModel( token.c_str() ); + if ( model == NULL || model->NumSurfaces() == 0 ) { + common->Warning( "^4BSE:^1 No surfaces defined in model '%s' in '%s' (file: %s, line: %d)", + token.c_str(), effect ? effect->GetName() : "", + lexer->GetFileName(), lexer->GetLineNum() ); + model = renderModelManager->FindModel( "_default" ); + } + parms.mMisc = model; + if ( !lexer->ExpectTokenString( "," ) || + !GetVector( lexer, count, parms.mMins ) || + !lexer->ExpectTokenString( "," ) || + !GetVector( lexer, count, parms.mMaxs ) ) { return false; } + fixup = false; + } else { + common->Warning( "^4BSE:^1 Invalid spawn domain '%s' in '%s' (file: %s, line: %d)", + token.c_str(), effect ? effect->GetName() : "", + lexer->GetFileName(), lexer->GetLineNum() ); + return CheckCommonParms( lexer, parms ); + } + + if ( !CheckCommonParms( lexer, parms ) ) { + return false; + } + if ( parms.mFlags & PPFLAG_SURFACE ) { + if ( parms.mSpawnType >= SPF_BOX_0 && parms.mSpawnType <= SPF_BOX_3 ) { + parms.mSpawnType += SPF_SURFACE_BOX_0 - SPF_BOX_0; + } else if ( parms.mSpawnType >= SPF_SPHERE_0 && parms.mSpawnType <= SPF_SPHERE_3 ) { + parms.mSpawnType += SPF_SURFACE_SPHERE_0 - SPF_SPHERE_0; + } else if ( parms.mSpawnType >= SPF_CYLINDER_0 && parms.mSpawnType <= SPF_CYLINDER_3 ) { + parms.mSpawnType += SPF_SURFACE_CYLINDER_0 - SPF_CYLINDER_0; + } + } + if ( fixup ) { + FixupParms( parms ); + } + return true; +} + +bool rvParticleTemplate::ParseSpawnDomains( rvDeclEffect *effect, + idLexer *lexer ) { + idToken token; + if ( !lexer->ExpectTokenString( "{" ) || !lexer->ReadToken( &token ) ) { + return false; + } + while ( token.Cmp( "}" ) != 0 ) { + bool parsed = true; + if ( token.Icmp( "position" ) == 0 ) { + parsed = ParseSpawnParms( effect, lexer, mSpawnPosition, 3 ); + } else if ( token.Icmp( "direction" ) == 0 ) { + parsed = ParseSpawnParms( effect, lexer, mSpawnDirection, 3 ); + SetCalculatedNormal( true ); + } else if ( token.Icmp( "velocity" ) == 0 ) { + parsed = ParseSpawnParms( effect, lexer, mSpawnVelocity, 3 ); + } else if ( token.Icmp( "acceleration" ) == 0 ) { + parsed = ParseSpawnParms( effect, lexer, mSpawnAcceleration, 3 ); + } else if ( token.Icmp( "friction" ) == 0 ) { + parsed = ParseSpawnParms( effect, lexer, mSpawnFriction, 3 ); + } else if ( token.Icmp( "tint" ) == 0 ) { + parsed = ParseSpawnParms( effect, lexer, mSpawnTint, 3 ); + } else if ( token.Icmp( "fade" ) == 0 ) { + parsed = ParseSpawnParms( effect, lexer, mSpawnFade, 1 ); + } else if ( token.Icmp( "size" ) == 0 ) { + parsed = ParseSpawnParms( effect, lexer, mSpawnSize, mNumSizeParms ); + } else if ( token.Icmp( "rotate" ) == 0 ) { + parsed = ParseSpawnParms( effect, lexer, mSpawnRotate, mNumRotateParms ); + } else if ( token.Icmp( "angle" ) == 0 ) { + parsed = ParseSpawnParms( effect, lexer, mSpawnAngle, 3 ); + } else if ( token.Icmp( "offset" ) == 0 ) { + parsed = ParseSpawnParms( effect, lexer, mSpawnOffset, 3 ); + } else if ( token.Icmp( "length" ) == 0 ) { + parsed = ParseSpawnParms( effect, lexer, mSpawnLength, 3 ); + } else { + common->Warning( "^4BSE:^1 Invalid spawn type '%s' in '%s' (file: %s, line: %d)", + token.c_str(), effect ? effect->GetName() : "", + lexer->GetFileName(), lexer->GetLineNum() ); + lexer->SkipBracedSection( true ); + } + if ( !parsed || !lexer->ReadToken( &token ) ) { + return false; + } + } + return true; +} + +bool rvParticleTemplate::ParseDeathDomains( rvDeclEffect *effect, + idLexer *lexer ) { + idToken token; + if ( !lexer->ExpectTokenString( "{" ) || !lexer->ReadToken( &token ) ) { + return false; + } + while ( token.Cmp( "}" ) != 0 ) { + bool parsed = true; + rvEnvParms *envelope = NULL; + if ( token.Icmp( "tint" ) == 0 ) { + parsed = ParseSpawnParms( effect, lexer, mDeathTint, 3 ); envelope = &mTintEnvelope; + } else if ( token.Icmp( "fade" ) == 0 ) { + parsed = ParseSpawnParms( effect, lexer, mDeathFade, 1 ); envelope = &mFadeEnvelope; + } else if ( token.Icmp( "size" ) == 0 ) { + parsed = ParseSpawnParms( effect, lexer, mDeathSize, mNumSizeParms ); envelope = &mSizeEnvelope; + } else if ( token.Icmp( "rotate" ) == 0 ) { + parsed = ParseSpawnParms( effect, lexer, mDeathRotate, mNumRotateParms ); envelope = &mRotateEnvelope; + } else if ( token.Icmp( "angle" ) == 0 ) { + parsed = ParseSpawnParms( effect, lexer, mDeathAngle, 3 ); envelope = &mAngleEnvelope; + } else if ( token.Icmp( "offset" ) == 0 ) { + parsed = ParseSpawnParms( effect, lexer, mDeathOffset, 3 ); envelope = &mOffsetEnvelope; + } else if ( token.Icmp( "length" ) == 0 ) { + parsed = ParseSpawnParms( effect, lexer, mDeathLength, 3 ); envelope = &mLengthEnvelope; + } else { + common->Warning( "^4BSE:^1 Invalid end type '%s' in '%s' (file: %s, line: %d)", + token.c_str(), effect ? effect->GetName() : "", + lexer->GetFileName(), lexer->GetLineNum() ); + lexer->SkipBracedSection( true ); + } + if ( envelope != NULL ) { + envelope->SetDefaultType(); + } + if ( !parsed || !lexer->ReadToken( &token ) ) { + return false; + } + } + return true; +} + +bool rvParticleTemplate::ParseImpact( rvDeclEffect *effect, idLexer *lexer ) { + idToken token; + if ( !lexer->ExpectTokenString( "{" ) || !lexer->ReadToken( &token ) ) { + return false; + } + SetHasPhysics( true ); + while ( token.Cmp( "}" ) != 0 ) { + if ( token.Icmp( "effect" ) == 0 ) { + if ( !lexer->ReadToken( &token ) ) { return false; } + if ( mNumImpactEffects < BSE_NUM_SPAWNABLE ) { + mImpactEffects[mNumImpactEffects++] = declManager->FindEffect( token.c_str() ); + } else { + common->Warning( "^4BSE:^1 Too many impact effects '%s' in '%s'", + token.c_str(), effect ? effect->GetName() : "" ); + } + } else if ( token.Icmp( "remove" ) == 0 ) { + SetDeleteOnImpact( lexer->ParseInt() != 0 ); + } else if ( token.Icmp( "bounce" ) == 0 ) { + mBounce = lexer->ParseFloat(); + } else { + common->Warning( "^4BSE:^1 Invalid impact parameter '%s' in '%s'", + token.c_str(), effect ? effect->GetName() : "" ); + } + if ( !lexer->ReadToken( &token ) ) { return false; } + } + return true; +} + +bool rvParticleTemplate::ParseTimeout( rvDeclEffect *effect, idLexer *lexer ) { + idToken token; + if ( !lexer->ExpectTokenString( "{" ) || !lexer->ReadToken( &token ) ) { + return false; + } + while ( token.Cmp( "}" ) != 0 ) { + if ( token.Icmp( "effect" ) == 0 ) { + if ( !lexer->ReadToken( &token ) ) { return false; } + if ( mNumTimeoutEffects < BSE_NUM_SPAWNABLE ) { + mTimeoutEffects[mNumTimeoutEffects++] = declManager->FindEffect( token.c_str() ); + } else { + common->Warning( "^4BSE:^1 Too many timeout effects '%s' in '%s'", + token.c_str(), effect ? effect->GetName() : "" ); + } + } else { + common->Warning( "^4BSE:^1 Invalid timeout parameter '%s' in '%s'", + token.c_str(), effect ? effect->GetName() : "" ); + } + if ( !lexer->ReadToken( &token ) ) { return false; } + } + return true; +} + +bool rvParticleTemplate::ParseBlendParms( rvDeclEffect *effect, idLexer *lexer ) { + idToken token; + if ( !lexer->ReadToken( &token ) ) { + return false; + } + if ( token.Icmp( "add" ) == 0 ) { + SetAdditive( true ); + } else { + common->Warning( "^4BSE:^1 Invalid blend type '%s' in '%s' (file: %s, line: %d)", + token.c_str(), effect ? effect->GetName() : "", + lexer->GetFileName(), lexer->GetLineNum() ); + } + return true; +} + void rvParticleTemplate::SetParameterCounts() { int sizeParms; int rotateParms; @@ -63,7 +390,7 @@ void rvParticleTemplate::SetParameterCounts() { sizeParms = 3; rotateParms = 3; sizeSpawnType = SPF_ONE_3; break; case PTYPE_LIGHT: sizeParms = 3; rotateParms = 0; sizeSpawnType = SPF_ONE_3; break; - case PTYPE_ORIENTEDLINKED: + case PTYPE_DEBRIS: sizeParms = 0; rotateParms = 3; sizeSpawnType = SPF_ONE_3; break; default: return; @@ -284,7 +611,7 @@ void rvParticleTemplate::Finish() { mVertexCount = 4 * ( 5 * mNumForks + 5 ); mIndexCount = 60 * ( mNumForks + 1 ); break; - case PTYPE_ORIENTEDLINKED: + case PTYPE_DEBRIS: mVertexCount = mIndexCount = 0; break; default: @@ -318,11 +645,11 @@ bool rvParticleTemplate::Parse( rvDeclEffect *effect, idLexer *lexer ) { } while ( token.Cmp( "}" ) != 0 ) { if ( token.Icmp( "start" ) == 0 ) { - lexer->SkipBracedSection(); + if ( !ParseSpawnDomains( effect, lexer ) ) { return false; } } else if ( token.Icmp( "end" ) == 0 ) { - lexer->SkipBracedSection(); + if ( !ParseDeathDomains( effect, lexer ) ) { return false; } } else if ( token.Icmp( "motion" ) == 0 ) { - lexer->SkipBracedSection(); + if ( !ParseMotionDomains( effect, lexer ) ) { return false; } } else if ( token.Icmp( "generatedNormal" ) == 0 ) { SetGeneratedNormal( true ); } else if ( token.Icmp( "generatedOriginNormal" ) == 0 ) { @@ -374,8 +701,7 @@ bool rvParticleTemplate::Parse( rvDeclEffect *effect, idLexer *lexer ) { lexer->ReadToken( &token ); mJitterTable = declManager->FindTable( token ); } else if ( token.Icmp( "blend" ) == 0 ) { - lexer->ReadToken( &token ); - SetAdditive( token.Icmp( "add" ) == 0 ); + if ( !ParseBlendParms( effect, lexer ) ) { return false; } } else if ( token.Icmp( "shadows" ) == 0 ) { SetShadows( true ); } else if ( token.Icmp( "specular" ) == 0 ) { @@ -384,19 +710,10 @@ bool rvParticleTemplate::Parse( rvDeclEffect *effect, idLexer *lexer ) { lexer->ReadToken( &token ); mModelName = token; renderModelManager->FindModel( token ); - } else if ( token.Icmp( "impact" ) == 0 || token.Icmp( "timeout" ) == 0 ) { - const bool impact = token.Icmp( "impact" ) == 0; - if ( !lexer->ExpectTokenString( "{" ) ) { return false; } - while ( lexer->ReadToken( &token ) && token.Cmp( "}" ) != 0 ) { - if ( token.Icmp( "effect" ) == 0 && lexer->ReadToken( &token ) ) { - if ( impact && mNumImpactEffects < BSE_NUM_SPAWNABLE ) { mImpactEffects[mNumImpactEffects++] = declManager->FindEffect( token ); } - else if ( !impact && mNumTimeoutEffects < BSE_NUM_SPAWNABLE ) { mTimeoutEffects[mNumTimeoutEffects++] = declManager->FindEffect( token ); } - } else if ( impact && token.Icmp( "remove" ) == 0 ) { - SetDeleteOnImpact( lexer->ParseInt() != 0 ); - } else if ( impact && token.Icmp( "bounce" ) == 0 ) { - mBounce = lexer->ParseFloat(); - } - } + } else if ( token.Icmp( "impact" ) == 0 ) { + if ( !ParseImpact( effect, lexer ) ) { return false; } + } else if ( token.Icmp( "timeout" ) == 0 ) { + if ( !ParseTimeout( effect, lexer ) ) { return false; } } else { common->Warning( "^4BSE:^1 Invalid particle keyword '%s' in '%s' (file: %s, line: %d)", token.c_str(), effect ? effect->GetName() : "", lexer->GetFileName(), lexer->GetLineNum() ); diff --git a/src/bse/BSE_Particle.h b/src/bse/BSE_Particle.h index 62ae6a4..8028b94 100644 --- a/src/bse/BSE_Particle.h +++ b/src/bse/BSE_Particle.h @@ -61,7 +61,6 @@ enum PTYPE_LIGHT, // Dynamic light - very expensive PTYPE_ELECTRICITY, // A bolt of electricity PTYPE_LINKED, // A series of linked lines - PTYPE_ORIENTEDLINKED, PTYPE_DEBRIS, // A client side moveable entity spawned in the game PTYPE_COUNT }; diff --git a/src/bse/BSE_Segment.cpp b/src/bse/BSE_Segment.cpp index 401990a..b08c33f 100644 --- a/src/bse/BSE_Segment.cpp +++ b/src/bse/BSE_Segment.cpp @@ -205,7 +205,7 @@ rvParticle *rvSegment::GetFreeParticle( rvBSE *effect ) { void rvSegment::InsertParticle( rvParticle *particle, rvSegmentTemplate *st ) { if ( st->GetTemporary() ) { return; } ++mActiveCount; - if ( st->GetUseMaterialColor() ) { + if ( st->GetComplexParticle() ) { particle->SetNext( mUsedHead ); mUsedHead = particle; return; @@ -225,7 +225,10 @@ rvParticle *rvSegment::SpawnParticle( rvBSE *effect, rvSegmentTemplate *st, floa if ( !GetSegmentTemplate() ) { return NULL; } rvParticle *particle = st->GetTemporary() ? mParticles : GetFreeParticle( effect ); if ( !particle ) { return NULL; } - particle->FinishSpawn( effect, this, birthTime, 0.0f, initPos, initAxis ); + // Quake 4 feeds the single-particle birth time through as the spawn + // fraction as well. Several locked/constant weapon effects use that + // value to seed their linear spacing and envelope evaluation. + particle->FinishSpawn( effect, this, birthTime, birthTime, initPos, initAxis ); InsertParticle( particle, st ); return particle; } @@ -294,29 +297,66 @@ void rvSegment::CalcTrailCounts( rvBSE *effect, rvSegmentTemplate *st, rvParticl void rvSegment::CalcCounts( rvBSE *effect, float time ) { rvSegmentTemplate *st = GetSegmentTemplate(); if ( !st || st->GetType() == SEG_TRAIL || st->GetParticleTemplate()->GetType() == PTYPE_NONE ) { return; } - const float particleDuration = st->GetParticleTemplate()->GetMaxDuration() + BSE_FUTURE; + + rvParticleTemplate *pt = st->GetParticleTemplate(); + const float particleMaxDuration = pt->GetMaxDuration() + BSE_FUTURE; + const float effectMinDuration = mEffectDecl->GetMinDuration(); + int particleCount = 0; + int loopParticleCount = 0; float duration = 0.0f; + switch ( st->GetType() ) { case SEG_EMITTER: - duration = Min( particleDuration, st->mLocalDuration.y ) + BSE_FUTURE; - mParticleCount = mLoopParticleCount = (int)ceilf( duration / mSecondsPerParticle.y ) + 1; + if ( pt->GetType() == PTYPE_DEBRIS ) { + particleCount = loopParticleCount = 1; + break; + } + duration = Min( particleMaxDuration, st->mLocalDuration.y ) + BSE_FUTURE; + particleCount = loopParticleCount = (int)ceilf( duration / mSecondsPerParticle.y ) + 1; + if ( effectMinDuration < particleMaxDuration ) { + loopParticleCount = (int)ceilf( ( (float)particleCount / effectMinDuration ) * particleMaxDuration ) + 1; + } break; + case SEG_SPAWNER: - mParticleCount = mLoopParticleCount = (int)ceilf( mCount.y ); + if ( pt->GetType() == PTYPE_DEBRIS ) { + particleCount = loopParticleCount = 1; + break; + } + particleCount = loopParticleCount = (int)ceilf( mCount.y ); + if ( effectMinDuration != 0.0f && !st->GetInfiniteDuration() && effectMinDuration < particleMaxDuration ) { + loopParticleCount = particleCount * ( (int)ceilf( particleMaxDuration / effectMinDuration ) + 1 ) + 1; + } break; + case SEG_DECAL: case SEG_LIGHT: - mParticleCount = mLoopParticleCount = 1; + particleCount = loopParticleCount = 1; break; + default: - mParticleCount = mLoopParticleCount = 0; break; } - if ( st->GetHasParticles() && ( st->GetType() == SEG_EMITTER || st->GetType() == SEG_SPAWNER ) ) { - CalcTrailCounts( effect, st, st->GetParticleTemplate(), duration ); + + mParticleCount = particleCount; + mLoopParticleCount = loopParticleCount; + + if ( st->GetHasParticles() ) { + if ( particleCount == 0 || loopParticleCount == 0 ) { + common->Warning( "^4BSE:^1 '%s'\nSegment with no particles for effect", mEffectDecl->GetName() ); + } + if ( st->GetType() == SEG_EMITTER || st->GetType() == SEG_SPAWNER ) { + CalcTrailCounts( effect, st, pt, duration ); + } } + if ( !effect->GetLooping() ) { - effect->SetDuration( mSegEndTime - time + st->GetParticleTemplate()->GetMaxDuration() ); + effect->SetDuration( mSegEndTime - time + pt->GetMaxDuration() ); + } + + if ( bse_debug.GetInteger() == 2 ) { + common->Printf( "BSE: Segment %s: Count: %d Looping: %d\n", + rvBSEManagerLocal::mSegmentNames[st->GetType()], particleCount, loopParticleCount ); } } @@ -360,20 +400,34 @@ void rvSegment::Handle( rvBSE *effect, float time ) { } void rvSegment::UpdateGenericParticles( rvBSE *effect, rvSegmentTemplate *st, float time ) { + const bool infinite = st->GetInfiniteDuration(); + const bool smoker = st->GetSmoker(); rvParticle *previous = NULL; rvParticle *particle = mUsedHead; while ( particle ) { rvParticle *next = particle->GetNext(); - bool remove = particle->Expired( time ); - if ( st->GetHasPhysics() ) { remove = particle->RunPhysics( effect, st, time ); } - if ( effect->GetStopped() && !particle->GetPersist() ) { remove = true; } - if ( remove ) { + bool remove = false; + if ( infinite ) { + particle->RunPhysics( effect, st, time ); + remove = effect->GetStopped(); + } else if ( particle->Expired( time ) ) { particle->CheckTimeoutEffect( effect, st, time ); - particle->Destroy(); + remove = true; + } else { + remove = particle->RunPhysics( effect, st, time ); + } + if ( effect->GetStopped() && !particle->GetPersist() ) { remove = true; } + if ( smoker && st->mTrailSegmentIndex >= 0 && + st->mTrailSegmentIndex < effect->mSegments.Num() ) { + particle->EmitSmokeParticles( effect, + &effect->mSegments[st->mTrailSegmentIndex], time ); + } + if ( remove ) { if ( previous ) { previous->SetNext( next ); } else { mUsedHead = next; } - particle->SetNext( mFreeHead ); - mFreeHead = particle; --mActiveCount; + particle->SetNext( mFreeHead ); + particle->Destroy(); + mFreeHead = particle; } else { previous = particle; } @@ -385,7 +439,7 @@ bool rvSegment::UpdateParticles( rvBSE *effect, float time ) { rvSegmentTemplate *st = GetSegmentTemplate(); if ( !st ) { return false; } Handle( effect, time ); - if ( effect->GetStopped() || st->GetHasPhysics() ) { UpdateGenericParticles( effect, st, time ); } + if ( effect->GetStopped() || st->GetComplexParticle() ) { UpdateGenericParticles( effect, st, time ); } else { UpdateSimpleParticles( time ); } if ( st->GetParticleTemplate()->GetType() == PTYPE_ELECTRICITY ) { UpdateElectricityParticles( time ); } if ( com_debugHudActive ) { @@ -403,10 +457,70 @@ void rvSegment::PlayEffect( rvBSE *effect, rvSegmentTemplate *st ) { } void rvSegment::CreateDecal( rvBSE *effect, float time ) { - // Decal geometry is projected by the render world; spawning the temporary - // particle preserves the retail timing and randomized particle parameters. + static const idVec3 decalWinding[4] = { + idVec3( 1.0f, 1.0f, 0.0f ), + idVec3( -1.0f, 1.0f, 0.0f ), + idVec3( -1.0f, -1.0f, 0.0f ), + idVec3( 1.0f, -1.0f, 0.0f ) + }; + + if ( !bse_render.GetBool() || effect == NULL || session == NULL || + session->rw == NULL ) { + return; + } + rvSegmentTemplate *st = GetSegmentTemplate(); - if ( bse_render.GetBool() && st ) { SpawnParticle( effect, st, time ); } + if ( st == NULL ) { + return; + } + + if ( bse_debug.GetBool() ) { + static int decalCount; + const idMaterial *material = st->GetParticleTemplate()->GetMaterial(); + common->Printf( "BSE: Decal %d: %s\n", ++decalCount, + material != NULL ? material->GetName() : "" ); + } + + rvParticle *particle = SpawnParticle( effect, st, time, + vec3_origin, mat3_identity ); + if ( particle == NULL ) { + return; + } + + idVec4 tint; + idVec3 size; + idVec3 rotation; + particle->GetSpawnInfo( tint, size, rotation ); + + float sine; + float cosine; + idMath::SinCos( rotation.x, sine, cosine ); + + // BSE decals project opposite the effect's forward axis. The retail code + // uses a fixed eight-unit projection depth and rotates the winding around + // that direction by the particle's randomized rotation parameter. + idMat3 axis; + axis[2] = -effect->GetCurrentAxis()[0]; + axis[2].Normalize(); + idVec3 tangent0; + idVec3 tangent1; + axis[2].NormalVectors( tangent0, tangent1 ); + axis[0] = tangent0 * cosine + tangent1 * -sine; + axis[1] = tangent0 * -sine + tangent1 * -cosine; + + const idVec3 &origin = effect->GetCurrentOrigin(); + const idVec3 windingOrigin = origin + axis[2] * 8.0f; + const idVec3 projectionOrigin = origin - axis[2] * 8.0f; + + idFixedWinding winding; + for ( int point = 0; point < 4; ++point ) { + winding += idVec5( windingOrigin + ( axis * decalWinding[point] ) * size.x, + idVec2( point == 0 || point == 3 ? 1.0f : 0.0f, + point < 2 ? 1.0f : 0.0f ) ); + } + + session->rw->ProjectDecalOntoWorld( winding, projectionOrigin, true, 8.0f, + st->GetParticleTemplate()->GetMaterial(), idMath::Ftoi( time * 1000.0f ) ); } bool rvSegment::Check( rvBSE *effect, float time ) { diff --git a/src/bse/BSE_SegmentTemplate.cpp b/src/bse/BSE_SegmentTemplate.cpp index 313a0c9..54b24b3 100644 --- a/src/bse/BSE_SegmentTemplate.cpp +++ b/src/bse/BSE_SegmentTemplate.cpp @@ -204,8 +204,7 @@ bool rvSegmentTemplate::Finish( rvDeclEffect *effect ) { mParticleTemplate.GetHasPhysics() || mParticleTemplate.GetNumTimeoutEffects() != 0 || particleType == PTYPE_ELECTRICITY || particleType == PTYPE_LIGHT ) { - // In the retail executable the complex-update flag occupies bit 9. - mFlags |= BIT( 9 ); + mFlags |= STFLAG_COMPLEX; } return true; } diff --git a/src/renderer/draw_common.cpp b/src/renderer/draw_common.cpp index 703322b..16575b9 100644 --- a/src/renderer/draw_common.cpp +++ b/src/renderer/draw_common.cpp @@ -802,7 +802,13 @@ void RB_STD_T_RenderShaderPasses( const drawSurf_t *surf ) { tri->primBatchMesh->SetupForDrawRender( NULL ); } } else { - qglColorPointer( 4, GL_UNSIGNED_BYTE, sizeof( idDrawVert ), (void *)&ac->color ); + if ( surf->mFlags & 1 ) { + const byte *decalColors = reinterpret_cast( ac ) + + tri->numVerts * sizeof( idDrawVert ) + stage * tri->numVerts * 4; + qglColorPointer( 4, GL_UNSIGNED_BYTE, 4, decalColors ); + } else { + qglColorPointer( 4, GL_UNSIGNED_BYTE, sizeof( idDrawVert ), (void *)&ac->color ); + } qglVertexAttribPointerARB( 9, 3, GL_FLOAT, false, sizeof( idDrawVert ), ac->tangents[0].ToFloatPtr() ); qglVertexAttribPointerARB( 10, 3, GL_FLOAT, false, sizeof( idDrawVert ), ac->tangents[1].ToFloatPtr() ); qglNormalPointer( GL_FLOAT, sizeof( idDrawVert ), ac->normal.ToFloatPtr() ); @@ -869,10 +875,14 @@ void RB_STD_T_RenderShaderPasses( const drawSurf_t *surf ) { //-------------------------- // set the color - color[0] = regs[ pStage->color.registers[0] ]; - color[1] = regs[ pStage->color.registers[1] ]; - color[2] = regs[ pStage->color.registers[2] ]; - color[3] = regs[ pStage->color.registers[3] ]; + if ( surf->mFlags & 1 ) { + color[0] = color[1] = color[2] = color[3] = 1.0f; + } else { + color[0] = regs[ pStage->color.registers[0] ]; + color[1] = regs[ pStage->color.registers[1] ]; + color[2] = regs[ pStage->color.registers[2] ]; + color[3] = regs[ pStage->color.registers[3] ]; + } // skip the entire stage if an add would be black if ( ( pStage->drawStateBits & (GLS_SRCBLEND_BITS|GLS_DSTBLEND_BITS) ) == ( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE ) @@ -890,7 +900,13 @@ void RB_STD_T_RenderShaderPasses( const drawSurf_t *surf ) { if ( pStage->vertexColor == SVC_IGNORE ) { qglColor4fv( color ); } else if ( !tri->primBatchMesh ) { - qglColorPointer( 4, GL_UNSIGNED_BYTE, sizeof( idDrawVert ), (void *)&ac->color ); + if ( surf->mFlags & 1 ) { + const byte *decalColors = reinterpret_cast( ac ) + + tri->numVerts * sizeof( idDrawVert ) + stage * tri->numVerts * 4; + qglColorPointer( 4, GL_UNSIGNED_BYTE, 4, decalColors ); + } else { + qglColorPointer( 4, GL_UNSIGNED_BYTE, sizeof( idDrawVert ), (void *)&ac->color ); + } GL_EnableVertexAttribState( 4 ); if ( pStage->vertexColor == SVC_INVERSE_MODULATE ) { @@ -905,7 +921,7 @@ void RB_STD_T_RenderShaderPasses( const drawSurf_t *surf ) { // for vertex color and modulated color, we need to enable a second // texture stage - if ( color[0] != 1 || color[1] != 1 || color[2] != 1 || color[3] != 1 ) { + if ( color[0] != 1 || color[1] != 1 || color[2] != 1 || color[3] != 1 || ( surf->mFlags & 1 ) ) { GL_SelectTexture( 1 ); globalImages->whiteImage->Bind();