// this include must remain at the top of every CPP file //Anything above this #include will be ignored by the compiler #include "../qcommon/exe_headers.h" #include "client.h" #if !defined(FX_SCHEDULER_H_INC) #include "FxScheduler.h" #endif #if !defined(G2_H_INC) #include "../ghoul2/G2.h" #include "../ghoul2/G2_local.h" #endif #ifdef VV_LIGHTING #include "../renderer/tr_lightmanager.h" #endif extern int drawnFx; //-------------------------- // // Base Effect Class // //-------------------------- CEffect::CEffect(void) : mMatImpactFX(MATIMPACTFX_NONE), mMatImpactParm(-1), mSoundVolume(-1), mSoundRadius(-1), mFlags(0) { memset( &mRefEnt, 0, sizeof( mRefEnt )); } //-------------------------- // // Derived Particle Class // //-------------------------- //---------------------------- void CParticle::Init(void) { mRefEnt.radius = 0.0f; if (mFlags & FX_PLAYER_VIEW) { mOrigin1[0] = irand(0, 639); mOrigin1[1] = irand(0, 479); } } //---------------------------- void CParticle::Die(void) { if ( mFlags & FX_DEATH_RUNS_FX && !(mFlags & FX_KILL_ON_IMPACT) ) { vec3_t norm; // Man, this just seems so, like, uncool and stuff... VectorSet( norm, flrand(-1.0f, 1.0f), flrand(-1.0f, 1.0f), flrand(-1.0f, 1.0f)); VectorNormalize( norm ); theFxScheduler.PlayEffect( mDeathFxID, mOrigin1, norm ); } } //---------------------------- bool CParticle::Cull(void) { vec3_t dir; if (mFlags & FX_PLAYER_VIEW) { // this will be drawn as a 2D effect so don't cull it return false; } // Get the direction to the view VectorSubtract( mOrigin1, theFxHelper.refdef->vieworg, dir ); // Check if it's behind the viewer if ( (DotProduct( theFxHelper.refdef->viewaxis[0], dir )) < 0) // cg.cosHalfFOV * (len - mRadius) ) { return true; } // don't cull if this is hacked to show up close to the inview wpn if (mFlags & FX_DEPTH_HACK) { return false; } // Can't be too close float len = VectorLengthSquared( dir ); if ( len < fx_nearCull->value ) { return true; } return false; } //---------------------------- void CParticle::Draw(void) { if ( mFlags & FX_DEPTH_HACK ) { // Not sure if first person needs to be set, but it can't hurt? mRefEnt.renderfx |= RF_DEPTHHACK; } if (mFlags & FX_PLAYER_VIEW) { vec4_t color; color[0] = mRefEnt.shaderRGBA[0] / 255.0; color[1] = mRefEnt.shaderRGBA[1] / 255.0; color[2] = mRefEnt.shaderRGBA[2] / 255.0; color[3] = mRefEnt.shaderRGBA[3] / 255.0; // add this 2D effect to the proper list. it will get drawn after the cgi.RenderScene call theFxScheduler.Add2DEffect(mOrigin1[0], mOrigin1[1], mRefEnt.radius, mRefEnt.radius, color, mRefEnt.customShader); } else { // Add our refEntity to the scene VectorCopy( mOrigin1, mRefEnt.origin ); theFxHelper.AddFxToScene(&mRefEnt); } drawnFx++; } //---------------------------- // Update //---------------------------- bool CParticle::Update(void) { // Game pausing can cause dumb time things to happen, so kill the effect in this instance if ( mTimeStart > theFxHelper.mTime ) { return false; } if ( mFlags & FX_RELATIVE ) { if ( !mGhoul2.IsValid()) { // the thing we are bolted to is no longer valid, so we may as well just die. return false; } vec3_t org; vec3_t ax[3]; // Get our current position and direction if (!theFxHelper.GetOriginAxisFromBolt(&mGhoul2, mEntNum, mModelNum, mBoltNum, org, ax)) { //could not get bolt return false; } vec3_t realVel, realAccel; VectorMA( org, mOrgOffset[0], ax[0], org ); VectorMA( org, mOrgOffset[1], ax[1], org ); VectorMA( org, mOrgOffset[2], ax[2], org ); const float time = (theFxHelper.mTime - mTimeStart) * 0.001f; // calc the real velocity and accel vectors VectorScale( ax[0], mVel[0], realVel ); VectorMA( realVel, mVel[1], ax[1], realVel ); VectorMA( realVel, mVel[2], ax[2], realVel ); //realVel[2] += 0.5f * mGravity * time; VectorScale( ax[0], mAccel[0], realAccel ); VectorMA( realAccel, mAccel[1], ax[1], realAccel ); VectorMA( realAccel, mAccel[2], ax[2], realAccel ); // Get our real velocity at the current time, taking into account the effects of acceleartion. NOTE: not sure if this is even 100% correct math-wise VectorMA( realVel, time, realAccel, realVel ); // Now move us to where we should be at the given time VectorMA( org, time, realVel, mOrigin1 ); } else if (( mTimeStart < theFxHelper.mTime ) && UpdateOrigin() == false ) { // we are marked for death return false; } if ( !Cull() ) { // Only update these if the thing is visible. UpdateSize(); UpdateRGB(); UpdateAlpha(); UpdateRotation(); Draw(); } return true; } //---------------------------- // Update Origin //---------------------------- bool CParticle::UpdateOrigin(void) { vec3_t new_origin; VectorMA( mVel, theFxHelper.mRealTime, mAccel, mVel ); // Predict the new position new_origin[0] = mOrigin1[0] + (theFxHelper.mRealTime * mVel[0]);// + (theFxHelper.mHalfRealTimeSq * mVel[0]); new_origin[1] = mOrigin1[1] + (theFxHelper.mRealTime * mVel[1]);// + (theFxHelper.mHalfRealTimeSq * mVel[1]); new_origin[2] = mOrigin1[2] + (theFxHelper.mRealTime * mVel[2]);// + (theFxHelper.mHalfRealTimeSq * mVel[2]); // Only perform physics if this object is tagged to do so if ( (mFlags & FX_APPLY_PHYSICS) && !(mFlags & FX_PLAYER_VIEW) ) { bool solid; if ( mFlags & FX_EXPENSIVE_PHYSICS ) { solid = true; // by setting this to true, we force a real trace to happen } else { // if this returns solid, we need to do a trace if (!com_RMG || com_RMG->integer) { // don't do this call for RMG maps TCGPointContents *data = (TCGPointContents *)cl.mSharedMemory; VectorCopy(new_origin, data->mPoint); data->mPassEntityNum = ENTITYNUM_WORLD; // if this returns solid, we need to do a trace solid = !!(VM_Call( cgvm, CG_POINT_CONTENTS ) & MASK_SOLID); } else { solid = false; } } if ( solid ) { trace_t trace; float dot; if ( mFlags & FX_USE_BBOX ) { if (mFlags & FX_GHOUL2_TRACE) { theFxHelper.G2Trace( trace, mOrigin1, mMin, mMax, new_origin, -1, MASK_SOLID ); } else { theFxHelper.Trace( trace, mOrigin1, mMin, mMax, new_origin, -1, MASK_SOLID ); } } else { if (mFlags & FX_GHOUL2_TRACE) { theFxHelper.G2Trace( trace, mOrigin1, NULL, NULL, new_origin, -1, MASK_PLAYERSOLID ); } else { theFxHelper.Trace( trace, mOrigin1, NULL, NULL, new_origin, -1, MASK_SOLID ); } } // Hit something if (trace.startsolid || trace.allsolid) { VectorClear( mVel ); VectorClear( mAccel ); if ((mFlags & FX_GHOUL2_TRACE) && (mFlags & FX_IMPACT_RUNS_FX)) { static vec3_t bsNormal = {0, 1, 0}; theFxScheduler.PlayEffect( mImpactFxID, trace.endpos, bsNormal ); } mFlags &= ~(FX_APPLY_PHYSICS | FX_IMPACT_RUNS_FX); return true; } else if ( trace.fraction < 1.0f )//&& !trace.startsolid && !trace.allsolid ) { if ( mFlags & FX_IMPACT_RUNS_FX && !(trace.surfaceFlags & SURF_NOIMPACT )) { theFxScheduler.PlayEffect( mImpactFxID, trace.endpos, trace.plane.normal ); } // may need to interact with the material type we hit theFxScheduler.MaterialImpact(&trace, (CEffect*)this); if ( mFlags & FX_KILL_ON_IMPACT ) { // time to die return false; } VectorMA( mVel, theFxHelper.mRealTime * trace.fraction, mAccel, mVel ); dot = DotProduct( mVel, trace.plane.normal ); VectorMA( mVel, -2.0f * dot, trace.plane.normal, mVel ); VectorScale( mVel, mElasticity, mVel ); mElasticity *= 0.5f; // If the velocity is too low, make it stop moving, rotating, and turn off physics to avoid // doing expensive operations when they aren't needed //if ( trace.plane.normal[2] > 0.33f && mVel[2] < 10.0f ) if (VectorLengthSquared(mVel) < 100.0f) { VectorClear( mVel ); VectorClear( mAccel ); mFlags &= ~(FX_APPLY_PHYSICS | FX_IMPACT_RUNS_FX); } // Set the origin to the exact impact point VectorMA( trace.endpos, 1.0f, trace.plane.normal, mOrigin1 ); return true; } } } // No physics were done to this object, move it VectorCopy( new_origin, mOrigin1 ); if (mFlags & FX_PLAYER_VIEW) { if (mOrigin1[0] < 0 || mOrigin1[0] > 639 || mOrigin1[1] < 0 || mOrigin1[1] > 479) { return false; } } return true; } //---------------------------- // Update Size //---------------------------- void CParticle::UpdateSize(void) { // completely biased towards start if it doesn't get overridden float perc1 = 1.0f, perc2 = 1.0f; if ( (mFlags & FX_SIZE_LINEAR) ) { // calculate element biasing perc1 = 1.0f - (float)(theFxHelper.mTime - mTimeStart) / (float)(mTimeEnd - mTimeStart); } // We can combine FX_LINEAR with _either_ FX_NONLINEAR, FX_WAVE, or FX_CLAMP if (( mFlags & FX_SIZE_PARM_MASK ) == FX_SIZE_NONLINEAR ) { if ( theFxHelper.mTime > mSizeParm ) { // get percent done, using parm as the start of the non-linear fade perc2 = 1.0f - (float)(theFxHelper.mTime - mSizeParm) / (float)(mTimeEnd - mSizeParm); } if ( mFlags & FX_SIZE_LINEAR ) { // do an even blend perc1 = perc1 * 0.5f + perc2 * 0.5f; } else { // just copy it over...sigh perc1 = perc2; } } else if (( mFlags & FX_SIZE_PARM_MASK ) == FX_SIZE_WAVE ) { // wave gen, with parm being the frequency multiplier perc1 = perc1 * cosf( (theFxHelper.mTime - mTimeStart) * mSizeParm ); } else if (( mFlags & FX_SIZE_PARM_MASK ) == FX_SIZE_CLAMP ) { if ( theFxHelper.mTime < mSizeParm ) { // get percent done, using parm as the start of the non-linear fade perc2 = (float)(mSizeParm - theFxHelper.mTime) / (float)(mSizeParm - mTimeStart); } else { perc2 = 0.0f; // make it full size?? } if ( (mFlags & FX_SIZE_LINEAR) ) { // do an even blend perc1 = perc1 * 0.5f + perc2 * 0.5f; } else { // just copy it over...sigh perc1 = perc2; } } // If needed, RAND can coexist with linear and either non-linear or wave. if ( mFlags & FX_SIZE_RAND ) { // Random simply modulates the existing value perc1 = flrand(0.0f, perc1); } mRefEnt.radius = (mSizeStart * perc1) + (mSizeEnd * (1.0f - perc1)); } inline int VectorToInt(vec3_t vec) { int tmp, retval; #ifdef _MSVC_VER _asm { push edx mov edx, [vec] fld dword ptr[edx + 0] fld dword ptr[edx + 4] fld dword ptr[edx + 8] mov eax, 0xff00 fistp tmp mov al, byte ptr [tmp] shl eax, 16 fistp tmp mov ah, byte ptr [tmp] fistp tmp mov al, byte ptr [tmp] mov [retval], eax pop edx } #else asm("flds %1;\n\t" "flds %2;\n\t" "flds %3;\n\t" "fistp %4;\n\t" "movb %4, %%al;\n\t" "shl $16, %0;\n\t" "fistp %4;\n\t" "movb %4, %%ah;\n\t" "fistp %4;\n\t" "movb %4, %%al;\n\t" : "=a"(retval) : "m"(vec[0]), "m"(vec[1]), "m"(vec[2]), "m"(tmp), "a"(0xff00) ); #endif return(retval); } //---------------------------- // Update RGB //---------------------------- void CParticle::UpdateRGB(void) { // completely biased towards start if it doesn't get overridden float perc1 = 1.0f, perc2 = 1.0f; vec3_t res; if ( (mFlags & FX_RGB_LINEAR) ) { // calculate element biasing perc1 = 1.0f - (float)( theFxHelper.mTime - mTimeStart ) / (float)( mTimeEnd - mTimeStart ); } // We can combine FX_LINEAR with _either_ FX_NONLINEAR, FX_WAVE, or FX_CLAMP if (( mFlags & FX_RGB_PARM_MASK ) == FX_RGB_NONLINEAR ) { if ( theFxHelper.mTime > mRGBParm ) { // get percent done, using parm as the start of the non-linear fade perc2 = 1.0f - (float)( theFxHelper.mTime - mRGBParm ) / (float)( mTimeEnd - mRGBParm ); } if ( (mFlags & FX_RGB_LINEAR) ) { // do an even blend perc1 = perc1 * 0.5f + perc2 * 0.5f; } else { // just copy it over...sigh perc1 = perc2; } } else if (( mFlags & FX_RGB_PARM_MASK ) == FX_RGB_WAVE ) { // wave gen, with parm being the frequency multiplier perc1 = perc1 * cosf(( theFxHelper.mTime - mTimeStart ) * mRGBParm ); } else if (( mFlags & FX_RGB_PARM_MASK ) == FX_RGB_CLAMP ) { if ( theFxHelper.mTime < mRGBParm ) { // get percent done, using parm as the start of the non-linear fade perc2 = (float)(mRGBParm - theFxHelper.mTime) / (float)(mRGBParm - mTimeStart); } else { perc2 = 0.0f; // make it full size?? } if (( mFlags & FX_RGB_LINEAR )) { // do an even blend perc1 = perc1 * 0.5f + perc2 * 0.5f; } else { // just copy it over...sigh perc1 = perc2; } } // If needed, RAND can coexist with linear and either non-linear or wave. if ( mFlags & FX_RGB_RAND ) { // Random simply modulates the existing value perc1 = flrand(0.0f, perc1); } // Now get the correct color VectorScale( mRGBStart, perc1, res ); VectorMA( res, 1.0f - perc1, mRGBEnd, res ); res[0] = Com_Clamp(0.0f, 1.0f, res[0]) * 255.0f; res[1] = Com_Clamp(0.0f, 1.0f, res[1]) * 255.0f; res[2] = Com_Clamp(0.0f, 1.0f, res[2]) * 255.0f; *(int *)mRefEnt.shaderRGBA = VectorToInt(res); } //---------------------------- // Update Alpha //---------------------------- void CParticle::UpdateAlpha(void) { int alpha; // completely biased towards start if it doesn't get overridden float perc1 = 1.0f, perc2 = 1.0f; if ( (mFlags & FX_ALPHA_LINEAR) ) { // calculate element biasing perc1 = 1.0f - (float)(theFxHelper.mTime - mTimeStart) / (float)(mTimeEnd - mTimeStart); } // We can combine FX_LINEAR with _either_ FX_NONLINEAR, FX_WAVE, or FX_CLAMP if (( mFlags & FX_ALPHA_PARM_MASK ) == FX_ALPHA_NONLINEAR ) { if ( theFxHelper.mTime > mAlphaParm ) { // get percent done, using parm as the start of the non-linear fade perc2 = 1.0f - (float)(theFxHelper.mTime - mAlphaParm) / (float)(mTimeEnd - mAlphaParm); } if (( mFlags & FX_ALPHA_LINEAR )) { // do an even blend perc1 = perc1 * 0.5f + perc2 * 0.5f; } else { // just copy it over...sigh perc1 = perc2; } } else if (( mFlags & FX_ALPHA_PARM_MASK ) == FX_ALPHA_WAVE ) { // wave gen, with parm being the frequency multiplier perc1 = perc1 * cosf( (theFxHelper.mTime - mTimeStart) * mAlphaParm ); } else if (( mFlags & FX_ALPHA_PARM_MASK ) == FX_ALPHA_CLAMP ) { if ( theFxHelper.mTime < mAlphaParm ) { // get percent done, using parm as the start of the non-linear fade perc2 = (float)(mAlphaParm - theFxHelper.mTime) / (float)(mAlphaParm - mTimeStart); } else { perc2 = 0.0f; // make it full size?? } if (( mFlags & FX_ALPHA_LINEAR )) { // do an even blend perc1 = perc1 * 0.5f + perc2 * 0.5f; } else { // just copy it over...sigh perc1 = perc2; } } perc1 = (mAlphaStart * perc1) + (mAlphaEnd * (1.0f - perc1)); // We should be in the right range, but clamp to ensure perc1 = Com_Clamp(0.0f, 1.0f, perc1); // If needed, RAND can coexist with linear and either non-linear or wave. if ( mFlags & FX_ALPHA_RAND ) { // Random simply modulates the existing value perc1 = flrand(0.0f, perc1); } alpha = Com_Clamp(0, 255, perc1 * 255.0f); if ( mFlags & FX_USE_ALPHA ) { // should use this when using art that has an alpha channel mRefEnt.shaderRGBA[3] = (byte)alpha; } else { // Modulate the rgb fields by the alpha value to do the fade, works fine for additive blending mRefEnt.shaderRGBA[0] = ((int)mRefEnt.shaderRGBA[0] * alpha) >> 8; mRefEnt.shaderRGBA[1] = ((int)mRefEnt.shaderRGBA[1] * alpha) >> 8; mRefEnt.shaderRGBA[2] = ((int)mRefEnt.shaderRGBA[2] * alpha) >> 8; } } //-------------------------- void CParticle::UpdateRotation(void) { mRefEnt.rotation += theFxHelper.mFrameTime * 0.01f * mRotationDelta; mRotationDelta *= ( 1.0f - ( theFxHelper.mFrameTime * 0.0007f )); // decay rotationDelta } //-------------------------------- // // Derived Oriented Particle Class // //-------------------------------- COrientedParticle::COrientedParticle(void) { mRefEnt.reType = RT_ORIENTED_QUAD; } //---------------------------- bool COrientedParticle::Cull(void) { vec3_t dir; // float len; // Get the direction to the view VectorSubtract( mOrigin1, theFxHelper.refdef->vieworg, dir ); // Check if it's behind the viewer if ( (DotProduct( theFxHelper.refdef->viewaxis[0], dir )) < 0 ) { return true; } // len = VectorLengthSquared( dir ); // don't cull stuff that's associated with inview wpns if ( mFlags & FX_DEPTH_HACK ) { return false; } // Can't be too close // if ( len < fx_nearCull->value * fx_nearCull->value) // { // return true; // } return false; } //---------------------------- void COrientedParticle::Draw(void) { if ( mFlags & FX_DEPTH_HACK ) { // Not sure if first person needs to be set mRefEnt.renderfx |= RF_DEPTHHACK; } // Add our refEntity to the scene VectorCopy( mOrigin1, mRefEnt.origin ); if ( !(mFlags&FX_RELATIVE) ) { VectorCopy( mNormal, mRefEnt.axis[0] ); MakeNormalVectors( mRefEnt.axis[0], mRefEnt.axis[1], mRefEnt.axis[2] ); } theFxHelper.AddFxToScene( &mRefEnt ); drawnFx++; } //---------------------------- // Update //---------------------------- bool COrientedParticle::Update(void) { // Game pausing can cause dumb time things to happen, so kill the effect in this instance if ( mTimeStart > theFxHelper.mTime ) { return false; } if ( mFlags & FX_RELATIVE ) { if ( !mGhoul2.IsValid()) { // the thing we are bolted to is no longer valid, so we may as well just die. return false; } vec3_t org; vec3_t ax[3]; // Get our current position and direction if (!theFxHelper.GetOriginAxisFromBolt(&mGhoul2, mEntNum, mModelNum, mBoltNum, org, ax)) { //could not get bolt return false; } vec3_t realVel, realAccel; VectorMA( org, mOrgOffset[0], ax[0], org ); VectorMA( org, mOrgOffset[1], ax[1], org ); VectorMA( org, mOrgOffset[2], ax[2], org ); const float time = (theFxHelper.mTime - mTimeStart) * 0.001f; // calc the real velocity and accel vectors VectorScale( ax[0], mVel[0], realVel ); VectorMA( realVel, mVel[1], ax[1], realVel ); VectorMA( realVel, mVel[2], ax[2], realVel ); // realVel[2] += 0.5f * mGravity * time; VectorScale( ax[0], mAccel[0], realAccel ); VectorMA( realAccel, mAccel[1], ax[1], realAccel ); VectorMA( realAccel, mAccel[2], ax[2], realAccel ); // Get our real velocity at the current time, taking into account the effects of acceleartion. NOTE: not sure if this is even 100% correct math-wise VectorMA( realVel, time, realAccel, realVel ); // Now move us to where we should be at the given time VectorMA( org, time, realVel, mOrigin1 ); //use the normalOffset and add that to the actual normal of the bolt //NOTE: not tested!!! VectorCopy( ax[0], mRefEnt.axis[0] ); VectorCopy( ax[1], mRefEnt.axis[1] ); VectorCopy( ax[2], mRefEnt.axis[2] ); //vec3_t offsetAngles; //VectorSet( offsetAngles, 0, 90, 90 ); vec3_t offsetAxis[3]; //NOTE: mNormal is actually PITCH YAW and ROLL offsets AnglesToAxis( mNormal, offsetAxis ); MatrixMultiply( offsetAxis, ax, mRefEnt.axis ); } else if (( mTimeStart < theFxHelper.mTime ) && UpdateOrigin() == false ) { // we are marked for death return false; } if ( !Cull() ) { // Only update these if the thing is visible. UpdateSize(); UpdateRGB(); UpdateAlpha(); UpdateRotation(); Draw(); } return true; } //---------------------------- // // Derived Line Class // //---------------------------- CLine::CLine(void) { mRefEnt.reType = RT_LINE; } //---------------------------- void CLine::Draw(void) { if ( mFlags & FX_DEPTH_HACK ) { // Not sure if first person needs to be set, but it can't hurt? mRefEnt.renderfx |= RF_DEPTHHACK; } VectorCopy( mOrigin1, mRefEnt.origin ); VectorCopy( mOrigin2, mRefEnt.oldorigin ); theFxHelper.AddFxToScene(&mRefEnt); drawnFx++; } //---------------------------- bool CLine::Update(void) { // Game pausing can cause dumb time things to happen, so kill the effect in this instance if ( mTimeStart > theFxHelper.mTime ) { return false; } if ( mFlags & FX_RELATIVE ) { if ( !mGhoul2.IsValid()) { // the thing we are bolted to is no longer valid, so we may as well just die. return false; } vec3_t ax[3]; // Get our current position and direction if (!theFxHelper.GetOriginAxisFromBolt(&mGhoul2, mEntNum, mModelNum, mBoltNum, mOrigin1, ax)) { //could not get bolt return false; } VectorAdd(mOrigin1, mOrgOffset, mOrigin1); //add the offset to the bolt point VectorMA( mOrigin1, mVel[0], ax[0], mOrigin2 ); VectorMA( mOrigin2, mVel[1], ax[1], mOrigin2 ); VectorMA( mOrigin2, mVel[2], ax[2], mOrigin2 ); } if ( !Cull()) { // Only update these if the thing is visible. UpdateSize(); UpdateRGB(); UpdateAlpha(); Draw(); } return true; } //---------------------------- // // Derived Electricity Class // //---------------------------- CElectricity::CElectricity(void) { mRefEnt.reType = RT_ELECTRICITY; } //---------------------------- void CElectricity::Initialize(void) { mRefEnt.frame = flrand(0.0f, 1.0f) * 1265536.0f; mRefEnt.axis[0][2] = theFxHelper.mTime + (mTimeEnd - mTimeStart); // endtime if ( mFlags & FX_DEPTH_HACK ) { mRefEnt.renderfx |= RF_DEPTHHACK; } if ( mFlags & FX_BRANCH ) { mRefEnt.renderfx |= RF_FORKED; } if ( mFlags & FX_TAPER ) { mRefEnt.renderfx |= RF_TAPERED; } if ( mFlags & FX_GROW ) { mRefEnt.renderfx |= RF_GROW; } } //---------------------------- void CElectricity::Draw(void) { VectorCopy( mOrigin1, mRefEnt.origin ); VectorCopy( mOrigin2, mRefEnt.oldorigin ); mRefEnt.axis[0][0] = mChaos; mRefEnt.axis[0][1] = mTimeEnd - mTimeStart; theFxHelper.AddFxToScene( &mRefEnt ); drawnFx++; } //---------------------------- bool CElectricity::Update(void) { // Game pausing can cause dumb time things to happen, so kill the effect in this instance if ( mTimeStart > theFxHelper.mTime ) { return false; } if ( mFlags & FX_RELATIVE ) { if ( !mGhoul2.IsValid()) { // the thing we are bolted to is no longer valid, so we may as well just die. return false; } vec3_t ax[3]; // Get our current position and direction if (!theFxHelper.GetOriginAxisFromBolt(&mGhoul2, mEntNum, mModelNum, mBoltNum, mOrigin1, ax)) { //could not get bolt return false; } VectorAdd(mOrigin1, mOrgOffset, mOrigin1); //add the offset to the bolt point VectorMA( mOrigin1, mVel[0], ax[0], mOrigin2 ); VectorMA( mOrigin2, mVel[1], ax[1], mOrigin2 ); VectorMA( mOrigin2, mVel[2], ax[2], mOrigin2 ); } if ( !Cull()) { // Only update these if the thing is visible. UpdateSize(); UpdateRGB(); UpdateAlpha(); Draw(); } return true; } //---------------------------- // // Derived Tail Class // //---------------------------- CTail::CTail(void) { mRefEnt.reType = RT_LINE; } //---------------------------- void CTail::Draw(void) { if ( mFlags & FX_DEPTH_HACK ) { // Not sure if first person needs to be set mRefEnt.renderfx |= RF_DEPTHHACK; } VectorCopy( mOrigin1, mRefEnt.origin ); theFxHelper.AddFxToScene(&mRefEnt); drawnFx++; } //---------------------------- bool CTail::Update(void) { // Game pausing can cause dumb time things to happen, so kill the effect in this instance if ( mTimeStart > theFxHelper.mTime ) { return false; } if ( mFlags & FX_RELATIVE ) { if ( !mGhoul2.IsValid()) { // the thing we are bolted to is no longer valid, so we may as well just die. return false; } vec3_t org; vec3_t ax[3]; if (mModelNum>=0 && mBoltNum>=0) //bolt style { if (!theFxHelper.GetOriginAxisFromBolt(&mGhoul2, mEntNum, mModelNum, mBoltNum, org, ax)) { //could not get bolt return false; } } vec3_t realVel, realAccel; VectorMA( org, mOrgOffset[0], ax[0], org ); VectorMA( org, mOrgOffset[1], ax[1], org ); VectorMA( org, mOrgOffset[2], ax[2], org ); // calc the real velocity and accel vectors // FIXME: if you want right and up movement in addition to the forward movement, you'll have to convert dir into a set of perp. axes and do some extra work VectorScale( ax[0], mVel[0], realVel ); VectorMA( realVel, mVel[1], ax[1], realVel ); VectorMA( realVel, mVel[2], ax[2], realVel ); VectorScale( ax[0], mAccel[0], realAccel ); VectorMA( realAccel, mAccel[1], ax[1], realAccel ); VectorMA( realAccel, mAccel[2], ax[2], realAccel ); const float time = (theFxHelper.mTime - mTimeStart) * 0.001f; // Get our real velocity at the current time, taking into account the effects of acceleration. NOTE: not sure if this is even 100% correct math-wise VectorMA( realVel, time, realAccel, realVel ); // Now move us to where we should be at the given time VectorMA( org, time, realVel, mOrigin1 ); // Just calc an old point some time in the past, doesn't really matter when VectorMA( org, (time - 0.003f), realVel, mOldOrigin ); } #ifdef _SOF2DEV_ else if ( !fx_freeze->integer ) #else else #endif { VectorCopy( mOrigin1, mOldOrigin ); } if (( mTimeStart < theFxHelper.mTime ) && UpdateOrigin() == false ) { // we are marked for death return false; } if ( !Cull() ) { // Only update these if the thing is visible. UpdateSize(); UpdateLength(); UpdateRGB(); UpdateAlpha(); CalcNewEndpoint(); Draw(); } return true; } //---------------------------- void CTail::UpdateLength(void) { // completely biased towards start if it doesn't get overridden float perc1 = 1.0f, perc2 = 1.0f; if ( mFlags & FX_LENGTH_LINEAR ) { // calculate element biasing perc1 = 1.0f - (float)(theFxHelper.mTime - mTimeStart) / (float)(mTimeEnd - mTimeStart); } // We can combine FX_LINEAR with _either_ FX_NONLINEAR or FX_WAVE if (( mFlags & FX_LENGTH_PARM_MASK ) == FX_LENGTH_NONLINEAR ) { if ( theFxHelper.mTime > mLengthParm ) { // get percent done, using parm as the start of the non-linear fade perc2 = 1.0f - (float)(theFxHelper.mTime - mLengthParm) / (float)(mTimeEnd - mLengthParm); } if ( mFlags & FX_LENGTH_LINEAR ) { // do an even blend perc1 = perc1 * 0.5f + perc2 * 0.5f; } else { // just copy it over...sigh perc1 = perc2; } } else if (( mFlags & FX_LENGTH_PARM_MASK ) == FX_LENGTH_WAVE ) { // wave gen, with parm being the frequency multiplier perc1 = perc1 * cosf( (theFxHelper.mTime - mTimeStart) * mLengthParm ); } else if (( mFlags & FX_LENGTH_PARM_MASK ) == FX_LENGTH_CLAMP ) { if ( theFxHelper.mTime < mLengthParm ) { // get percent done, using parm as the start of the non-linear fade perc2 = (float)(mLengthParm - theFxHelper.mTime) / (float)(mLengthParm - mTimeStart); } else { perc2 = 0.0f; // make it full size?? } if ( mFlags & FX_LENGTH_LINEAR ) { // do an even blend perc1 = perc1 * 0.5f + perc2 * 0.5f; } else { // just copy it over...sigh perc1 = perc2; } } // If needed, RAND can coexist with linear and either non-linear or wave. if ( mFlags & FX_LENGTH_RAND ) { // Random simply modulates the existing value perc1 = flrand(0.0f, perc1); } mLength = (mLengthStart * perc1) + (mLengthEnd * (1.0f - perc1)); } //---------------------------- void CTail::CalcNewEndpoint(void) { vec3_t temp; // FIXME: Hmmm, this looks dumb when physics are on and a bounce happens VectorSubtract( mOldOrigin, mOrigin1, temp ); // I wish we didn't have to do a VectorNormalize every frame..... VectorNormalize( temp ); VectorMA( mOrigin1, mLength, temp, mRefEnt.oldorigin ); } //---------------------------- // // Derived Cylinder Class // //---------------------------- CCylinder::CCylinder(void) { mRefEnt.reType = RT_CYLINDER; mTraceEnd = qfalse; } bool CCylinder::Cull(void) { if (mTraceEnd) { //eh, don't cull variable-length cylinders return false; } return CTail::Cull(); } void CCylinder::UpdateLength(void) { if (mTraceEnd) { vec3_t temp; trace_t tr; VectorMA( mOrigin1, FX_MAX_TRACE_DIST, mRefEnt.axis[0], temp ); theFxHelper.Trace( tr, mOrigin1, NULL, NULL, temp, -1, MASK_SOLID ); VectorSubtract( tr.endpos, mOrigin1, temp ); mLength = VectorLength(temp); } else { CTail::UpdateLength(); } } //---------------------------- void CCylinder::Draw(void) { if ( mFlags & FX_DEPTH_HACK ) { // Not sure if first person needs to be set, but it can't hurt? mRefEnt.renderfx |= RF_DEPTHHACK; } VectorCopy( mOrigin1, mRefEnt.origin ); VectorMA( mOrigin1, mLength, mRefEnt.axis[0], mRefEnt.oldorigin ); theFxHelper.AddFxToScene(&mRefEnt); drawnFx++; } //---------------------------- // Update Size2 //---------------------------- void CCylinder::UpdateSize2(void) { // completely biased towards start if it doesn't get overridden float perc1 = 1.0f, perc2 = 1.0f; if ( mFlags & FX_SIZE2_LINEAR ) { // calculate element biasing perc1 = 1.0f - (float)(theFxHelper.mTime - mTimeStart) / (float)(mTimeEnd - mTimeStart); } // We can combine FX_LINEAR with _either_ FX_NONLINEAR or FX_WAVE if (( mFlags & FX_SIZE2_PARM_MASK ) == FX_SIZE2_NONLINEAR ) { if ( theFxHelper.mTime > mSize2Parm ) { // get percent done, using parm as the start of the non-linear fade perc2 = 1.0f - (float)(theFxHelper.mTime - mSize2Parm) / (float)(mTimeEnd - mSize2Parm); } if ( (mFlags & FX_SIZE2_LINEAR) ) { // do an even blend perc1 = perc1 * 0.5f + perc2 * 0.5f; } else { // just copy it over...sigh perc1 = perc2; } } else if (( mFlags & FX_SIZE2_PARM_MASK ) == FX_SIZE2_WAVE ) { // wave gen, with parm being the frequency multiplier perc1 = perc1 * cosf( (theFxHelper.mTime - mTimeStart) * mSize2Parm ); } else if (( mFlags & FX_SIZE2_PARM_MASK ) == FX_SIZE2_CLAMP ) { if ( theFxHelper.mTime < mSize2Parm ) { // get percent done, using parm as the start of the non-linear fade perc2 = (float)(mSize2Parm - theFxHelper.mTime) / (float)(mSize2Parm - mTimeStart); } else { perc2 = 0.0f; // make it full size?? } if ( mFlags & FX_SIZE2_LINEAR ) { // do an even blend perc1 = perc1 * 0.5f + perc2 * 0.5f; } else { // just copy it over...sigh perc1 = perc2; } } // If needed, RAND can coexist with linear and either non-linear or wave. if ( mFlags & FX_SIZE2_RAND ) { // Random simply modulates the existing value perc1 = flrand(0.0f, perc1); } mRefEnt.rotation = (mSize2Start * perc1) + (mSize2End * (1.0f - perc1)); } //---------------------------- bool CCylinder::Update(void) { // Game pausing can cause dumb time things to happen, so kill the effect in this instance if ( mTimeStart > theFxHelper.mTime ) { return false; } if ( mFlags & FX_RELATIVE ) { if ( !mGhoul2.IsValid()) { // the thing we are bolted to is no longer valid, so we may as well just die. return false; } vec3_t ax[3]; // Get our current position and direction if (!theFxHelper.GetOriginAxisFromBolt(&mGhoul2, mEntNum, mModelNum, mBoltNum, mOrigin1, ax)) { //could not get bolt return false; } VectorAdd(mOrigin1, mOrgOffset, mOrigin1); //add the offset to the bolt point VectorCopy( ax[0], mRefEnt.axis[0] ); //FIXME: should mNormal be a modifier on the forward axis? /* VectorMA( mOrigin1, mNormal[0], ax[0], mOrigin2 ); VectorMA( mOrigin2, mNormal[1], ax[1], mOrigin2 ); VectorMA( mOrigin2, mNormal[2], ax[2], mOrigin2 ); */ } if ( !Cull() ) { // Only update these if the thing is visible. UpdateSize(); UpdateSize2(); UpdateLength(); UpdateRGB(); UpdateAlpha(); Draw(); } return true; } //---------------------------- // // Derived Emitter Class // //---------------------------- CEmitter::CEmitter(void) { // There may or may not be a model, but if there isn't one, // we just won't bother adding the refEnt in our Draw func mRefEnt.reType = RT_MODEL; } //---------------------------- CEmitter::~CEmitter(void) { } //---------------------------- // Draw //---------------------------- void CEmitter::Draw(void) { // Emitters don't draw themselves, but they may need to add an attached model if ( mFlags & FX_ATTACHED_MODEL ) { mRefEnt.nonNormalizedAxes = qtrue; VectorCopy( mOrigin1, mRefEnt.origin ); VectorScale( mRefEnt.axis[0], mRefEnt.radius, mRefEnt.axis[0] ); VectorScale( mRefEnt.axis[1], mRefEnt.radius, mRefEnt.axis[1] ); VectorScale( mRefEnt.axis[2], mRefEnt.radius, mRefEnt.axis[2] ); theFxHelper.AddFxToScene((miniRefEntity_t*)0);// I hate having to do this, but this needs to get added as a regular refEntity theFxHelper.AddFxToScene(&mRefEnt); } // If we are emitting effects, we had better be careful because just calling it every cgame frame could // either choke up the effects system on a fast machine, or look really nasty on a low end one. if ( mFlags & FX_EMIT_FX ) { vec3_t org, v; float ftime, time2, step; int t, dif; #define TRAIL_RATE 12 // we "think" at about a 60hz rate // Pick a target step distance and square it step = mDensity + flrand(-mVariance, mVariance); step *= step; dif = 0; for ( t = mOldTime; t <= theFxHelper.mTime; t += TRAIL_RATE ) { dif += TRAIL_RATE; // ?Not sure if it's better to update this before or after updating the origin VectorMA( mOldVelocity, dif * 0.001f, mAccel, v ); // Calc the time differences ftime = dif * 0.001f; time2 = ftime * ftime * 0.5f; // Predict the new position org[0] = mOldOrigin[0] + (ftime * v[0]) + (time2 * v[0]); org[1] = mOldOrigin[1] + (ftime * v[1]) + (time2 * v[1]); org[2] = mOldOrigin[2] + (ftime * v[2]) + (time2 * v[2]); // Is it time to draw an effect? if ( DistanceSquared( org, mOldOrigin ) >= step ) { // Pick a new target step distance and square it step = mDensity + flrand(-mVariance, mVariance); step *= step; // We met the step criteria so, we should add in the effect theFxScheduler.PlayEffect( mEmitterFxID, org, mRefEnt.axis ); VectorCopy( org, mOldOrigin ); VectorCopy( v, mOldVelocity ); dif = 0; mOldTime = t; } } } drawnFx++; } //---------------------------- bool CEmitter::Update(void) { // Game pausing can cause dumb time things to happen, so kill the effect in this instance if ( mTimeStart > theFxHelper.mTime ) { return false; } // Use this to track if we've stopped moving VectorCopy( mOrigin1, mOldOrigin ); VectorCopy( mVel, mOldVelocity ); if ( mFlags & FX_RELATIVE ) { if ( !mGhoul2.IsValid()) { // the thing we are bolted to is no longer valid, so we may as well just die. return false; } assert(0);//need this? } if (( mTimeStart < theFxHelper.mTime ) && UpdateOrigin() == false ) { // we are marked for death return false; } bool moving = false; // If the thing is no longer moving, kill the angle delta, but don't do it too quickly or it will // look very artificial. Don't do it too slowly or it will look like there is no friction. if ( VectorCompare( mOldOrigin, mOrigin1 )) { VectorScale( mAngleDelta, 0.7f, mAngleDelta ); } else { moving = true; } if ( mFlags & FX_PAPER_PHYSICS ) { // do this in a more framerate independant manner float sc = ( 20.0f / theFxHelper.mFrameTime); // bah, evil clamping if ( sc >= 1.0f ) { sc = 1.0f; } if ( moving ) { // scale the velocity down, basically inducing drag. Acceleration ( gravity ) should keep it pulling down, which is what we want. VectorScale( mVel, (sc * 0.8f + 0.2f ) * 0.92f, mVel ); // add some chaotic motion based on the way we are oriented VectorMA( mVel, (1.5f - sc) * 4.0f, mRefEnt.axis[0], mVel ); VectorMA( mVel, (1.5f - sc) * 4.0f, mRefEnt.axis[1], mVel ); } // make us settle so we can lay flat mAngles[0] *= (0.97f * (sc * 0.4f + 0.6f )); mAngles[2] *= (0.97f * (sc * 0.4f + 0.6f )); // decay our angle delta so we don't rotate as quickly VectorScale( mAngleDelta, (0.96f * (sc * 0.1f + 0.9f )), mAngleDelta ); } UpdateAngles(); UpdateSize(); Draw(); return true; } //---------------------------- void CEmitter::UpdateAngles(void) { VectorMA( mAngles, theFxHelper.mFrameTime * 0.01f, mAngleDelta, mAngles ); // was 0.001f, but then you really have to jack up the delta to even notice anything AnglesToAxis( mAngles, mRefEnt.axis ); } //-------------------------- // // Derived Light Class // //-------------------------- //---------------------------- void CLight::Draw(void) { #ifdef VV_LIGHTING VVLightMan.RE_AddLightToScene( mOrigin1, mRefEnt.radius, mRefEnt.origin[0], mRefEnt.origin[1], mRefEnt.origin[2] ); #else theFxHelper.AddLightToScene( mOrigin1, mRefEnt.radius, mRefEnt.origin[0], mRefEnt.origin[1], mRefEnt.origin[2] ); #endif drawnFx++; } //---------------------------- // Update //---------------------------- bool CLight::Update(void) { // Game pausing can cause dumb time things to happen, so kill the effect in this instance if ( mTimeStart > theFxHelper.mTime ) { return false; } if ( mFlags & FX_RELATIVE ) { if ( !mGhoul2.IsValid()) { // the thing we are bolted to is no longer valid, so we may as well just die. return false; } vec3_t ax[3]; // Get our current position and direction if (!theFxHelper.GetOriginAxisFromBolt(&mGhoul2, mEntNum, mModelNum, mBoltNum, mOrigin1, ax)) { //could not get bolt return false; } VectorMA( mOrigin1, mOrgOffset[0], ax[0], mOrigin1 ); VectorMA( mOrigin1, mOrgOffset[1], ax[1], mOrigin1 ); VectorMA( mOrigin1, mOrgOffset[2], ax[2], mOrigin1 ); } UpdateSize(); UpdateRGB(); Draw(); return true; } //---------------------------- // Update Size //---------------------------- void CLight::UpdateSize(void) { // completely biased towards start if it doesn't get overridden float perc1 = 1.0f, perc2 = 1.0f; if ( mFlags & FX_SIZE_LINEAR ) { // calculate element biasing perc1 = 1.0f - (float)(theFxHelper.mTime - mTimeStart) / (float)(mTimeEnd - mTimeStart); } // We can combine FX_LINEAR with _either_ FX_NONLINEAR or FX_WAVE if (( mFlags & FX_SIZE_PARM_MASK ) == FX_SIZE_NONLINEAR ) { if ( theFxHelper.mTime > mSizeParm ) { // get percent done, using parm as the start of the non-linear fade perc2 = 1.0f - (float)(theFxHelper.mTime - mSizeParm) / (float)(mTimeEnd - mSizeParm); } if ( (mFlags & FX_SIZE_LINEAR) ) { // do an even blend perc1 = perc1 * 0.5f + perc2 * 0.5f; } else { // just copy it over...sigh perc1 = perc2; } } else if (( mFlags & FX_SIZE_PARM_MASK ) == FX_SIZE_WAVE ) { // wave gen, with parm being the frequency multiplier perc1 = perc1 * cosf( (theFxHelper.mTime - mTimeStart) * mSizeParm ); } else if (( mFlags & FX_SIZE_PARM_MASK ) == FX_SIZE_CLAMP ) { if ( theFxHelper.mTime < mSizeParm ) { // get percent done, using parm as the start of the non-linear fade perc2 = (float)(mSizeParm - theFxHelper.mTime) / (float)(mSizeParm - mTimeStart); } else { perc2 = 0.0f; // make it full size?? } if ( mFlags & FX_SIZE_LINEAR ) { // do an even blend perc1 = perc1 * 0.5f + perc2 * 0.5f; } else { // just copy it over...sigh perc1 = perc2; } } // If needed, RAND can coexist with linear and either non-linear or wave. if ( mFlags & FX_SIZE_RAND ) { // Random simply modulates the existing value perc1 = flrand(0.0f, perc1); } mRefEnt.radius = (mSizeStart * perc1) + (mSizeEnd * (1.0f - perc1)); } //---------------------------- // Update RGB //---------------------------- void CLight::UpdateRGB(void) { // completely biased towards start if it doesn't get overridden float perc1 = 1.0f, perc2 = 1.0f; vec3_t res; if ( mFlags & FX_RGB_LINEAR ) { // calculate element biasing perc1 = 1.0f - (float)( theFxHelper.mTime - mTimeStart ) / (float)( mTimeEnd - mTimeStart ); } // We can combine FX_LINEAR with _either_ FX_NONLINEAR or FX_WAVE if (( mFlags & FX_RGB_PARM_MASK ) == FX_RGB_NONLINEAR ) { if ( theFxHelper.mTime > mRGBParm ) { // get percent done, using parm as the start of the non-linear fade perc2 = 1.0f - (float)( theFxHelper.mTime - mRGBParm ) / (float)( mTimeEnd - mRGBParm ); } if ( mFlags & FX_RGB_LINEAR ) { // do an even blend perc1 = perc1 * 0.5f + perc2 * 0.5f; } else { // just copy it over...sigh perc1 = perc2; } } else if (( mFlags & FX_RGB_PARM_MASK ) == FX_RGB_WAVE ) { // wave gen, with parm being the frequency multiplier perc1 = perc1 * cosf(( theFxHelper.mTime - mTimeStart ) * mRGBParm ); } else if (( mFlags & FX_RGB_PARM_MASK ) == FX_RGB_CLAMP ) { if ( theFxHelper.mTime < mRGBParm ) { // get percent done, using parm as the start of the non-linear fade perc2 = (float)(mRGBParm - theFxHelper.mTime) / (float)(mRGBParm - mTimeStart); } else { perc2 = 0.0f; // make it full size?? } if ( mFlags & FX_RGB_LINEAR ) { // do an even blend perc1 = perc1 * 0.5f + perc2 * 0.5f; } else { // just copy it over...sigh perc1 = perc2; } } // If needed, RAND can coexist with linear and either non-linear or wave. if ( mFlags & FX_RGB_RAND ) { // Random simply modulates the existing value perc1 = flrand(0.0f, perc1); } // Now get the correct color VectorScale( mRGBStart, perc1, res ); VectorMA(res, ( 1.0f - perc1 ), mRGBEnd, mRefEnt.origin); } //-------------------------- // // Derived Trail Class // //-------------------------- #define NEW_MUZZLE 0 #define NEW_TIP 1 #define OLD_TIP 2 #define OLD_MUZZLE 3 //---------------------------- void CTrail::Draw() { polyVert_t verts[3]; // vec3_t color; // build the first tri out of the new muzzle...new tip...old muzzle VectorCopy( mVerts[NEW_MUZZLE].origin, verts[0].xyz ); VectorCopy( mVerts[NEW_TIP].origin, verts[1].xyz ); VectorCopy( mVerts[OLD_MUZZLE].origin, verts[2].xyz ); // VectorScale( mVerts[NEW_MUZZLE].curRGB, mVerts[NEW_MUZZLE].curAlpha, color ); verts[0].modulate[0] = mVerts[NEW_MUZZLE].rgb[0]; verts[0].modulate[1] = mVerts[NEW_MUZZLE].rgb[1]; verts[0].modulate[2] = mVerts[NEW_MUZZLE].rgb[2]; verts[0].modulate[3] = mVerts[NEW_MUZZLE].alpha; // VectorScale( mVerts[NEW_TIP].curRGB, mVerts[NEW_TIP].curAlpha, color ); verts[1].modulate[0] = mVerts[NEW_TIP].rgb[0]; verts[1].modulate[1] = mVerts[NEW_TIP].rgb[1]; verts[1].modulate[2] = mVerts[NEW_TIP].rgb[2]; verts[1].modulate[3] = mVerts[NEW_TIP].alpha; // VectorScale( mVerts[OLD_MUZZLE].curRGB, mVerts[OLD_MUZZLE].curAlpha, color ); verts[2].modulate[0] = mVerts[OLD_MUZZLE].rgb[0]; verts[2].modulate[1] = mVerts[OLD_MUZZLE].rgb[1]; verts[2].modulate[2] = mVerts[OLD_MUZZLE].rgb[2]; verts[2].modulate[3] = mVerts[OLD_MUZZLE].alpha; verts[0].st[0] = mVerts[NEW_MUZZLE].curST[0]; verts[0].st[1] = mVerts[NEW_MUZZLE].curST[1]; verts[1].st[0] = mVerts[NEW_TIP].curST[0]; verts[1].st[1] = mVerts[NEW_TIP].curST[1]; verts[2].st[0] = mVerts[OLD_MUZZLE].curST[0]; verts[2].st[1] = mVerts[OLD_MUZZLE].curST[1]; // Add this tri theFxHelper.AddPolyToScene( mShader, 3, verts ); // build the second tri out of the old muzzle...old tip...new tip VectorCopy( mVerts[OLD_MUZZLE].origin, verts[0].xyz ); VectorCopy( mVerts[OLD_TIP].origin, verts[1].xyz ); VectorCopy( mVerts[NEW_TIP].origin, verts[2].xyz ); // VectorScale( mVerts[OLD_MUZZLE].curRGB, mVerts[OLD_MUZZLE].curAlpha, color ); verts[0].modulate[0] = mVerts[OLD_MUZZLE].rgb[0]; verts[0].modulate[1] = mVerts[OLD_MUZZLE].rgb[1]; verts[0].modulate[2] = mVerts[OLD_MUZZLE].rgb[2]; verts[0].modulate[3] = mVerts[OLD_MUZZLE].alpha; // VectorScale( mVerts[OLD_TIP].curRGB, mVerts[OLD_TIP].curAlpha, color ); verts[1].modulate[0] = mVerts[OLD_TIP].rgb[0]; verts[1].modulate[1] = mVerts[OLD_TIP].rgb[1]; verts[1].modulate[2] = mVerts[OLD_TIP].rgb[2]; verts[0].modulate[3] = mVerts[OLD_TIP].alpha; // VectorScale( mVerts[NEW_TIP].curRGB, mVerts[NEW_TIP].curAlpha, color ); verts[2].modulate[0] = mVerts[NEW_TIP].rgb[0]; verts[2].modulate[1] = mVerts[NEW_TIP].rgb[1]; verts[2].modulate[2] = mVerts[NEW_TIP].rgb[2]; verts[0].modulate[3] = mVerts[NEW_TIP].alpha; verts[0].st[0] = mVerts[OLD_MUZZLE].curST[0]; verts[0].st[1] = mVerts[OLD_MUZZLE].curST[1]; verts[1].st[0] = mVerts[OLD_TIP].curST[0]; verts[1].st[1] = mVerts[OLD_TIP].curST[1]; verts[2].st[0] = mVerts[NEW_TIP].curST[0]; verts[2].st[1] = mVerts[NEW_TIP].curST[1]; // Add this tri theFxHelper.AddPolyToScene( mShader, 3, verts ); drawnFx++; } //---------------------------- // Update //---------------------------- bool CTrail::Update() { // Game pausing can cause dumb time things to happen, so kill the effect in this instance if ( mTimeStart > theFxHelper.mTime ) { return false; } float perc = (float)(mTimeEnd - theFxHelper.mTime) / (float)(mTimeEnd - mTimeStart); for ( int t = 0; t < 4; t++ ) { // mVerts[t].curAlpha = mVerts[t].alpha * perc + mVerts[t].destAlpha * ( 1.0f - perc ); // if ( mVerts[t].curAlpha < 0.0f ) // { // mVerts[t].curAlpha = 0.0f; // } // VectorScale( mVerts[t].rgb, perc, mVerts[t].curRGB ); // VectorMA( mVerts[t].curRGB, ( 1.0f - perc ), mVerts[t].destrgb, mVerts[t].curRGB ); mVerts[t].curST[0] = mVerts[t].ST[0] * perc + mVerts[t].destST[0] * ( 1.0f - perc ); if ( mVerts[t].curST[0] > 1.0f ) { mVerts[t].curST[0] = 1.0f; } mVerts[t].curST[1] = mVerts[t].ST[1] * perc + mVerts[t].destST[1] * ( 1.0f - perc ); } Draw(); return true; } //-------------------------- // // Derived Poly Class // //-------------------------- //---------------------------- bool CPoly::Cull(void) { vec3_t dir; // Get the direction to the view VectorSubtract( mOrigin1, theFxHelper.refdef->vieworg, dir ); // Check if it's behind the viewer if ( (DotProduct( theFxHelper.refdef->viewaxis[0], dir )) < 0 ) { return true; } float len = VectorLengthSquared( dir ); // Can't be too close if ( len < fx_nearCull->value * fx_nearCull->value) { return true; } return false; } //---------------------------- void CPoly::Draw(void) { polyVert_t verts[MAX_CPOLY_VERTS]; for ( int i = 0; i < mCount; i++ ) { // Add our midpoint and vert offset to get the actual vertex VectorAdd( mOrigin1, mOrg[i], verts[i].xyz ); // Assign the same color to each vert *(int *)verts[i].modulate = *(int *)mRefEnt.shaderRGBA; // Copy the ST coords Vector2Copy( mST[i], verts[i].st ); } // Add this poly theFxHelper.AddPolyToScene( mRefEnt.customShader, mCount, verts ); drawnFx++; } //---------------------------- void CPoly::CalcRotateMatrix(void) { float cosX, cosZ; float sinX, sinZ; float rad; // rotate around Z rad = DEG2RAD( mRotDelta[YAW] * theFxHelper.mFrameTime * 0.01f ); cosZ = cosf( rad ); sinZ = sinf( rad ); // rotate around X rad = DEG2RAD( mRotDelta[PITCH] * theFxHelper.mFrameTime * 0.01f ); cosX = cosf( rad ); sinX = sinf( rad ); /*Pitch - aroundx Yaw - around z 1 0 0 c -s 0 0 c -s s c 0 0 s c 0 0 1 */ mRot[0][0] = cosZ; mRot[1][0] = -sinZ; mRot[2][0] = 0; mRot[0][1] = cosX * sinZ; mRot[1][1] = cosX * cosZ; mRot[2][1] = -sinX; mRot[0][2] = sinX * sinZ; mRot[1][2] = sinX * cosZ; mRot[2][2] = cosX; /* // ROLL is not supported unless anyone complains, if it needs to be added, use this format Roll c 0 s 0 1 0 -s 0 c */ mLastFrameTime = theFxHelper.mFrameTime; } //-------------------------------- void CPoly::Rotate(void) { vec3_t temp[MAX_CPOLY_VERTS]; float dif = fabs( (float)(mLastFrameTime - theFxHelper.mFrameTime) ); if ( dif > 0.1f * mLastFrameTime ) { CalcRotateMatrix(); } // Multiply our rotation matrix by each of the offset verts to get their new position for ( int i = 0; i < mCount; i++ ) { VectorRotate( mOrg[i], mRot, temp[i] ); VectorCopy( temp[i], mOrg[i] ); } } //---------------------------- // Update //---------------------------- bool CPoly::Update(void) { // Game pausing can cause dumb time things to happen, so kill the effect in this instance if ( mTimeStart > theFxHelper.mTime ) { return false; } // If our timestamp hasn't exired yet, we won't even consider doing any kind of motion if ( theFxHelper.mTime > mTimeStamp ) { vec3_t mOldOrigin; VectorCopy( mOrigin1, mOldOrigin ); if (( mTimeStart < theFxHelper.mTime ) && UpdateOrigin() == false ) { // we are marked for death return false; } // Only rotate whilst moving if ( !VectorCompare( mOldOrigin, mOrigin1 )) { Rotate(); } } if ( !Cull()) { // Only update these if the thing is visible. UpdateRGB(); UpdateAlpha(); Draw(); } return true; } //---------------------------- void CPoly::PolyInit(void) { if ( mCount < 3 ) { return; } int i; vec3_t org = {0.0f, 0.0f ,0.0f}; // Find our midpoint for ( i = 0; i < mCount; i++ ) { VectorAdd( org, mOrg[i], org ); } VectorScale( org, (float)(1.0f / mCount), org ); // now store our midpoint for physics purposes VectorCopy( org, mOrigin1 ); // Now we process the passed in points and make it so that they aren't actually the point... // rather, they are the offset from mOrigin1. for ( i = 0; i < mCount; i++ ) { VectorSubtract( mOrg[i], mOrigin1, mOrg[i] ); } CalcRotateMatrix(); } /* ------------------------- CBezier Bezier curve line ------------------------- */ bool CBezier::Cull( void ) { vec3_t dir; VectorSubtract( mOrigin1, theFxHelper.refdef->vieworg, dir ); //Check if it's in front of the viewer if ( (DotProduct( theFxHelper.refdef->viewaxis[0], dir )) >= 0 ) { return false; //don't cull } VectorSubtract( mOrigin2, theFxHelper.refdef->vieworg, dir ); //Check if it's in front of the viewer if ( (DotProduct( theFxHelper.refdef->viewaxis[0], dir )) >= 0 ) { return false; } VectorSubtract( mControl1, theFxHelper.refdef->vieworg, dir ); //Check if it's in front of the viewer if ( (DotProduct( theFxHelper.refdef->viewaxis[0], dir )) >= 0 ) { return false; } return true; //all points behind viewer } //---------------------------- bool CBezier::Update( void ) { float ftime, time2; ftime = theFxHelper.mFrameTime * 0.001f; time2 = ftime * ftime * 0.5f; mControl1[0] = mControl1[0] + (ftime * mControl1Vel[0]) + (time2 * mControl1Vel[0]); mControl2[0] = mControl2[0] + (ftime * mControl2Vel[0]) + (time2 * mControl2Vel[0]); mControl1[1] = mControl1[1] + (ftime * mControl1Vel[1]) + (time2 * mControl1Vel[1]); mControl2[1] = mControl2[1] + (ftime * mControl2Vel[1]) + (time2 * mControl2Vel[1]); mControl1[2] = mControl1[2] + (ftime * mControl1Vel[2]) + (time2 * mControl1Vel[2]); mControl2[2] = mControl2[2] + (ftime * mControl2Vel[2]) + (time2 * mControl2Vel[2]); if ( Cull() == false ) { // Only update these if the thing is visible. UpdateSize(); UpdateRGB(); UpdateAlpha(); Draw(); } return true; } //---------------------------- inline void CBezier::DrawSegment( vec3_t start, vec3_t end, float texcoord1, float texcoord2, float segPercent, float lastSegPercent ) { vec3_t lineDir, cross, viewDir; static vec3_t lastEnd[2]; polyVert_t verts[4]; float scaleBottom = 0.0f, scaleTop = 0.0f; VectorSubtract( end, start, lineDir ); VectorSubtract( end, theFxHelper.refdef->vieworg, viewDir ); CrossProduct( lineDir, viewDir, cross ); VectorNormalize( cross ); // scaleBottom is the width of the bottom edge of the quad, scaleTop is the width of the top edge scaleBottom = (mSizeStart + ((mSizeEnd - mSizeStart) * lastSegPercent)) * 0.5f; scaleTop = (mSizeStart + ((mSizeEnd - mSizeStart) * segPercent)) * 0.5f; //Construct the oriented quad if ( mInit ) { VectorCopy( lastEnd[0], verts[0].xyz ); VectorCopy( lastEnd[1], verts[1].xyz ); } else { VectorMA( start, -scaleBottom, cross, verts[0].xyz ); VectorMA( start, scaleBottom, cross, verts[1].xyz ); } verts[0].st[0] = 0.0f; verts[0].st[1] = texcoord1; verts[0].modulate[0] = mRefEnt.shaderRGBA[0] * ( 1.0f - texcoord1 ); verts[0].modulate[1] = mRefEnt.shaderRGBA[1] * ( 1.0f - texcoord1 ); verts[0].modulate[2] = mRefEnt.shaderRGBA[2] * ( 1.0f - texcoord1 ); verts[0].modulate[3] = mRefEnt.shaderRGBA[3]; verts[1].st[0] = 1.0f; verts[1].st[1] = texcoord1; verts[1].modulate[0] = mRefEnt.shaderRGBA[0] * ( 1.0f - texcoord1 ); verts[1].modulate[1] = mRefEnt.shaderRGBA[1] * ( 1.0f - texcoord1 ); verts[1].modulate[2] = mRefEnt.shaderRGBA[2] * ( 1.0f - texcoord1 ); verts[1].modulate[3] = mRefEnt.shaderRGBA[3]; if ( texcoord1 == 0.0f ) { *(int *)verts[0].modulate = 0; *(int *)verts[1].modulate = 0; } VectorMA( end, scaleTop, cross, verts[2].xyz ); verts[2].st[0] = 1.0f; verts[2].st[1] = texcoord2; verts[2].modulate[0] = mRefEnt.shaderRGBA[0] * ( 1.0f - texcoord2 ); verts[2].modulate[1] = mRefEnt.shaderRGBA[1] * ( 1.0f - texcoord2 ); verts[2].modulate[2] = mRefEnt.shaderRGBA[2] * ( 1.0f - texcoord2 ); verts[2].modulate[3] = mRefEnt.shaderRGBA[3]; VectorMA( end, -scaleTop, cross, verts[3].xyz ); verts[3].st[0] = 0.0f; verts[3].st[1] = texcoord2; verts[3].modulate[0] = mRefEnt.shaderRGBA[0] * ( 1.0f - texcoord2 ); verts[3].modulate[1] = mRefEnt.shaderRGBA[1] * ( 1.0f - texcoord2 ); verts[3].modulate[2] = mRefEnt.shaderRGBA[2] * ( 1.0f - texcoord2 ); verts[3].modulate[3] = mRefEnt.shaderRGBA[3]; theFxHelper.AddPolyToScene( mRefEnt.customShader, 4, verts ); VectorCopy( verts[2].xyz, lastEnd[1] ); VectorCopy( verts[3].xyz, lastEnd[0] ); mInit = true; } const float BEZIER_RESOLUTION = 16.0f; //---------------------------- void CBezier::Draw( void ) { vec3_t pos, old_pos; float mu, mum1; float incr = 1.0f / BEZIER_RESOLUTION, tex = 1.0f, tc1, tc2; int i = 0; VectorCopy( mOrigin1, old_pos ); mInit = false; //Signify a new batch for vert gluing float mum13, mu3, group1, group2; tc1 = 0.0f; for ( mu = incr; mu <= 1.0f; mu += incr) { //Four point curve mum1 = 1 - mu; mum13 = mum1 * mum1 * mum1; mu3 = mu * mu * mu; group1 = 3 * mu * mum1 * mum1; group2 = 3 * mu * mu *mum1; for ( i = 0; i < 3; i++ ) { pos[i] = mum13 * mOrigin1[i] + group1 * mControl1[i] + group2 * mControl2[i] + mu3 * mOrigin2[i]; } tc2 = mu * tex; //Draw it DrawSegment( old_pos, pos, tc1, tc2, mu, mu - incr ); VectorCopy( pos, old_pos ); tc1 = tc2; } drawnFx++; } /* ------------------------- CFlash Full screen flash ------------------------- */ //---------------------------- bool CFlash::Update( void ) { if ( UpdateOrigin() == false ) { // we are marked for death return false; } UpdateSize(); mRefEnt.radius *= mRadiusModifier; UpdateRGB(); UpdateAlpha(); Draw(); return true; } bool FX_WorldToScreen(vec3_t worldCoord, float *x, float *y) { int xcenter, ycenter; vec3_t local, transformed; vec3_t vfwd, vright, vup; //NOTE: did it this way because most draw functions expect virtual 640x480 coords // and adjust them for current resolution xcenter = 640 / 2;//gives screen coords in virtual 640x480, to be adjusted when drawn ycenter = 480 / 2;//gives screen coords in virtual 640x480, to be adjusted when drawn VectorSubtract (worldCoord, theFxHelper.refdef->vieworg, local); AngleVectors (theFxHelper.refdef->viewangles, vfwd, vright, vup); transformed[0] = DotProduct(local,vright); transformed[1] = DotProduct(local,vup); transformed[2] = DotProduct(local,vfwd); // Make sure Z is not negative. if(transformed[2] < 0.01) { return false; } // Simple convert to screen coords. float xzi = xcenter / transformed[2] * (90.0/theFxHelper.refdef->fov_x); float yzi = ycenter / transformed[2] * (90.0/theFxHelper.refdef->fov_y); *x = (xcenter + xzi * transformed[0]); *y = (ycenter - yzi * transformed[1]); return true; } //---------------------------- void CFlash::Init( void ) { // 10/19/01 kef -- maybe we want to do something different here for localized flashes, but right //now I want to be sure that whatever RGB changes occur to a non-localized flash will also occur //to a localized flash (so I'll have the same initial RGBA values for both...I need them sync'd for an effect) vec3_t dif; float mod = 1.0f, dis = 0.0f, maxRange = 900; VectorSubtract( mOrigin1, theFxHelper.refdef->vieworg, dif ); dis = VectorNormalize( dif ); mod = DotProduct( dif, theFxHelper.refdef->viewaxis[0] ); if ( dis > maxRange || ( mod < 0.5f && dis > 100 )) { mod = 0.0f; } else if ( mod < 0.5f && dis <= 100 ) { mod += 1.1f; } mod *= (1.0f - ((dis * dis) / (maxRange * maxRange))); VectorScale( mRGBStart, mod, mRGBStart ); VectorScale( mRGBEnd, mod, mRGBEnd ); if ( mFlags & FX_LOCALIZED_FLASH ) { FX_WorldToScreen(mOrigin1, &mScreenX, &mScreenY); // modify size of localized flash based on distance to effect (but not orientation) if (dis > 100 && dis < maxRange) { mRadiusModifier = (1.0f - ((dis * dis) / (maxRange * maxRange))); } } } //---------------------------- void CFlash::Draw( void ) { mRefEnt.reType = RT_SPRITE; if ( mFlags & FX_LOCALIZED_FLASH ) { vec4_t color; color[0] = mRefEnt.shaderRGBA[0] / 255.0; color[1] = mRefEnt.shaderRGBA[1] / 255.0; color[2] = mRefEnt.shaderRGBA[2] / 255.0; color[3] = mRefEnt.shaderRGBA[3] / 255.0; // add this 2D effect to the proper list. it will get drawn after the cgi.RenderScene call theFxScheduler.Add2DEffect(mScreenX, mScreenY, mRefEnt.radius, mRefEnt.radius, color, mRefEnt.customShader); } else { VectorCopy( theFxHelper.refdef->vieworg, mRefEnt.origin ); VectorMA( mRefEnt.origin, 12, theFxHelper.refdef->viewaxis[0], mRefEnt.origin ); mRefEnt.radius = 11.0f; theFxHelper.AddFxToScene( &mRefEnt ); } drawnFx++; } void FX_AddPrimitive( CEffect **pEffect, int killTime ); void FX_FeedTrail(effectTrailArgStruct_t *a) { CTrail *fx = new CTrail; int i = 0; while (i < 4) { VectorCopy(a->mVerts[i].origin, fx->mVerts[i].origin); VectorCopy(a->mVerts[i].rgb, fx->mVerts[i].rgb); VectorCopy(a->mVerts[i].destrgb, fx->mVerts[i].destrgb); VectorCopy(a->mVerts[i].curRGB, fx->mVerts[i].curRGB); fx->mVerts[i].alpha = a->mVerts[i].alpha; fx->mVerts[i].destAlpha = a->mVerts[i].destAlpha; fx->mVerts[i].curAlpha = a->mVerts[i].curAlpha; fx->mVerts[i].ST[0] = a->mVerts[i].ST[0]; fx->mVerts[i].ST[1] = a->mVerts[i].ST[1]; fx->mVerts[i].destST[0] = a->mVerts[i].destST[0]; fx->mVerts[i].destST[1] = a->mVerts[i].destST[1]; fx->mVerts[i].curST[0] = a->mVerts[i].curST[0]; fx->mVerts[i].curST[1] = a->mVerts[i].curST[1]; i++; } fx->SetFlags(a->mSetFlags); fx->mShader = a->mShader; FX_AddPrimitive((CEffect **)&fx, a->mKillTime); } // end