BSE fixes.

This commit is contained in:
Justin Marshall
2026-08-08 06:15:58 -07:00
parent ff55f3d9b3
commit b45d59f299
7 changed files with 502 additions and 58 deletions
+3 -4
View File
@@ -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 ); }
+2 -2
View File
@@ -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],
+337 -20
View File
@@ -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() : "<unknown>",
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() : "<unknown>",
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() : "<unknown>",
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() : "<unknown>",
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() : "<unknown>",
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() : "<unknown>" );
}
} 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() : "<unknown>" );
}
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() : "<unknown>" );
}
} else {
common->Warning( "^4BSE:^1 Invalid timeout parameter '%s' in '%s'",
token.c_str(), effect ? effect->GetName() : "<unknown>" );
}
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() : "<unknown>",
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() : "<unknown>", lexer->GetFileName(), lexer->GetLineNum() );
-1
View File
@@ -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
};
+136 -22
View File
@@ -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() : "<null>" );
}
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 ) {
+1 -2
View File
@@ -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;
}
+23 -7
View File
@@ -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<const byte *>( 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<const byte *>( 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();