//called to handle updating of a batch of particles given the appropriate properties void CParticleSystemGroup::UpdateParticles(float tmFrame, const LTVector& vGravity, float fFrictionCoef, const LTRigidTransform& tObjTrans) { LTASSERT(m_pProps, "Error: Called UpdateParticles on an uninitialized particle group"); //track our performance CTimedSystemBlock TimingBlock(g_tsClientFXParticles); //get an iterator to our list of particles CParticleReverseIterator itParticles = m_Particles.GetReverseIterator(); //bail if we have no particles if(itParticles.IsDone()) return; //find the coefficient of restitution to use for these particles in case they bounce float fCOR = m_pProps->m_fBounceStrength; //do our particles have infinite lifetime? bool bInfiniteLife = m_pProps->m_bInfiniteLife; //initialize our particle bounding box to extreme extents static const float kfInfinity = FLT_MAX; LTVector vMin = LTVector(kfInfinity, kfInfinity, kfInfinity); LTVector vMax = LTVector(-kfInfinity, -kfInfinity, -kfInfinity); LTVector vDefaultGravity = vGravity * tmFrame; float fDefaultFriction = powf(fFrictionCoef, tmFrame); //the current gravity and friction for us to use LTVector vCurrGravity = vDefaultGravity; float fCurrFriction = fDefaultFriction; float fCurrUpdateTime = tmFrame; //we now need to handle updating the particles. For performance reasons, this is broken apart //into two update loops, one that handles bouncing/splat, another that doesn't if(m_nNumRayTestParticles == 0) { //this is the non-bouncing update loop while(!itParticles.IsDone()) { SParticle* pParticle = (SParticle*)itParticles.GetParticle(); //update the lifetime pParticle->m_fLifetime -= fCurrUpdateTime; // Check for expiration if( pParticle->m_fLifetime <= 0.0f ) { if(pParticle->m_nUserData & PARTICLE_BATCH_MARKER) { //restore our defaults if(pParticle->m_nUserData & PARTICLE_DEFAULT_BATCH) { //restore our defaults vCurrGravity = vDefaultGravity; fCurrFriction = fDefaultFriction; fCurrUpdateTime = tmFrame; } else { //compute new values for us to use vCurrGravity = vGravity * pParticle->m_fTotalLifetime; fCurrFriction = powf(fFrictionCoef, pParticle->m_fTotalLifetime); fCurrUpdateTime = pParticle->m_fTotalLifetime; } //do the direct remove (we know batch markers don't have bounce or splat) itParticles = m_Particles.RemoveParticle(itParticles); continue; } else if(bInfiniteLife) { //this particle has died, but resurrect it since it lives forever pParticle->m_fLifetime = pParticle->m_fTotalLifetime - fmodf(-pParticle->m_fLifetime, pParticle->m_fTotalLifetime); } else { //remove the dead particle (can do direct version since we know we don't have splat or bounce) itParticles = m_Particles.RemoveParticle(itParticles); continue; } } // Give the particle an update //update the velocity, applying gravity and friction pParticle->m_Velocity = pParticle->m_Velocity * fCurrFriction + vCurrGravity; pParticle->m_Pos += pParticle->m_Velocity * fCurrUpdateTime; // Update the angle if appropriate pParticle->m_fAngle += pParticle->m_fAngularVelocity * fCurrUpdateTime; //extend the bounding box vMin.Min(pParticle->m_Pos); vMax.Max(pParticle->m_Pos); if(m_pProps->m_bStreak) { LTVector vStreakPt = pParticle->m_Pos - pParticle->m_Velocity * m_pProps->m_fStreakScale; vMin.Min(vStreakPt); vMax.Max(vStreakPt); } //and move onto the next particle itParticles.Prev(); } } else { //this is the bouncing/splat update loop IntersectQuery iQuery; IntersectInfo iInfo; //get the main world that we are going to test against HOBJECT hMainWorld = g_pLTClient->GetMainWorldModel(); //cache the inverse object transform LTRigidTransform tInvObjTrans = tObjTrans.GetInverse(); while(!itParticles.IsDone()) { SParticle* pParticle = (SParticle*)itParticles.GetParticle(); //update the lifetime pParticle->m_fLifetime -= fCurrUpdateTime; // Check for expiration if( pParticle->m_fLifetime <= 0.0f ) { if(pParticle->m_nUserData & PARTICLE_BATCH_MARKER) { //restore our defaults if(pParticle->m_nUserData & PARTICLE_DEFAULT_BATCH) { //restore our defaults vCurrGravity = vDefaultGravity; fCurrFriction = fDefaultFriction; fCurrUpdateTime = tmFrame; } else { //compute new values for us to use vCurrGravity = vGravity * pParticle->m_fTotalLifetime; fCurrFriction = powf(fFrictionCoef, pParticle->m_fTotalLifetime); fCurrUpdateTime = pParticle->m_fTotalLifetime; } //do the direct remove (we know batch markers don't have bounce or splat) itParticles = m_Particles.RemoveParticle(itParticles); continue; } else if(bInfiniteLife) { //this particle has died, but resurrect it since it lives forever pParticle->m_fLifetime = pParticle->m_fTotalLifetime - fmodf(-pParticle->m_fLifetime, pParticle->m_fTotalLifetime); } else { //remove the dead particle (can't do direct version since it might have splat or bounce) itParticles = RemoveParticle(itParticles); continue; } } // Give the particle an update //update the velocity, applying gravity and friction pParticle->m_Velocity = pParticle->m_Velocity * fCurrFriction + vCurrGravity; // Update the angle if appropriate pParticle->m_fAngle += pParticle->m_fAngularVelocity * fCurrUpdateTime; //determine where the particle should be moving to LTVector vDestPos = pParticle->m_Pos + pParticle->m_Velocity * fCurrUpdateTime; //we now need to compute the new position of the particle if(pParticle->m_nUserData & (PARTICLE_BOUNCE | PARTICLE_SPLAT)) { LTVector vParticlePos = pParticle->m_Pos; LTVector vParticleDest = vDestPos; //do all intersections in world space if(m_pProps->m_bObjectSpace) { tObjTrans.Transform(pParticle->m_Pos, vParticlePos); tObjTrans.Transform(vDestPos, vParticleDest); } iQuery.m_From = vParticlePos; iQuery.m_To = vParticleDest; if( g_pLTClient->IntersectSegmentAgainst( iQuery, &iInfo, hMainWorld ) ) { //handle bounce if(pParticle->m_nUserData & PARTICLE_BOUNCE) { //move our particle to the position of the intersection, but offset based upon //the normal slightly to avoid tunnelling vDestPos = iInfo.m_Point + iInfo.m_Plane.m_Normal * 0.1f; //and handle transforming back into object space if appropriate if(m_pProps->m_bObjectSpace) { vDestPos = tInvObjTrans * vDestPos; } LTVector& vVel = pParticle->m_Velocity; LTVector vNormal = iInfo.m_Plane.m_Normal; if(m_pProps->m_bObjectSpace) { vNormal = tInvObjTrans.m_rRot.RotateVector(vNormal); } //reflect the velocity over the normal vVel -= vNormal * (2.0f * vVel.Dot(vNormal)); //apply the coefficient of restitution vVel *= fCOR; } //handle splat if(pParticle->m_nUserData & PARTICLE_SPLAT) { //alright, we now need to create a splat effect //create a random rotation around the plane that we hit LTRotation rSplatRot(iInfo.m_Plane.m_Normal, LTVector(0.0f, 1.0f, 0.0f)); rSplatRot.Rotate(iInfo.m_Plane.m_Normal, GetRandom(0.0f, MATH_TWOPI)); LTRigidTransform tSplatTrans(iInfo.m_Point, rSplatRot); //now handle if we hit an object, we need to convert spaces and set that as our parent if(iInfo.m_hObject) { //convert the transform into a relative object space transform LTRigidTransform tHitObjTrans; g_pLTClient->GetObjectTransform(iInfo.m_hObject, &tHitObjTrans); tSplatTrans = tHitObjTrans.GetInverse() * tSplatTrans; } //and create the actual new object CLIENTFX_CREATESTRUCT CreateStruct("", 0, iInfo.m_hObject, tSplatTrans); CreateNewFX(m_pFxMgr, m_pProps->m_pszSplatEffect, CreateStruct, true); //we need to kill the particle itParticles = RemoveParticle(itParticles); continue; } } } //move the particle to the destination position that we calculated pParticle->m_Pos = vDestPos; //and update our extents box to match accordingly vMin.Min(pParticle->m_Pos); vMax.Max(pParticle->m_Pos); if(m_pProps->m_bStreak) { LTVector vStreakPt = pParticle->m_Pos - pParticle->m_Velocity * m_pProps->m_fStreakScale; vMin.Min(vStreakPt); vMax.Max(vStreakPt); } //and move onto our next particle itParticles.Prev(); } } //handle the case where we didn't hit any particles and therefore need to clear out our min and //max (note we only check one component for speed) if(vMin.x == kfInfinity) { vMin = tObjTrans.m_vPos; vMax = tObjTrans.m_vPos; } //expand the bounding box out by the largest particle size times the square root of two //to handle rotating particles that are at 45 degrees float fExpandAmount = m_pProps->m_fMaxParticlePadding; vMin -= LTVector(fExpandAmount, fExpandAmount, fExpandAmount); vMax += LTVector(fExpandAmount, fExpandAmount, fExpandAmount); //handle the case of when the particles are in object space, in such a case, we need to convert //the AABB from object space to an AABB in world space if(m_pProps->m_bObjectSpace) { //transform the object-space AABB to a world space AABB by projecting the dims onto the object basis vectors LTVector vRight, vUp, vForward; tObjTrans.m_rRot.GetVectors(vRight, vUp, vForward); LTVector vObjHalfDims = (vMax - vMin) * 0.5f; LTVector vWorldHalfDims; vWorldHalfDims.x = vObjHalfDims.Dot(LTVector(fabsf(vRight.x), fabsf(vUp.x), fabsf(vForward.x))); vWorldHalfDims.y = vObjHalfDims.Dot(LTVector(fabsf(vRight.y), fabsf(vUp.y), fabsf(vForward.y))); vWorldHalfDims.z = vObjHalfDims.Dot(LTVector(fabsf(vRight.z), fabsf(vUp.z), fabsf(vForward.z))); LTVector vObjCenter = (vMin + vMax) * 0.5f; LTVector vWorldCenter; tObjTrans.Transform(vObjCenter, vWorldCenter); //save the transformed results vMin = vWorldCenter - vWorldHalfDims; vMax = vWorldCenter + vWorldHalfDims; } //update the visibility box of the object g_pLTClient->GetCustomRender()->SetVisBoundingBox(m_hCustomRender, vMin - tObjTrans.m_vPos, vMax - tObjTrans.m_vPos); //we also need to update the transform of our object to follow g_pLTClient->SetObjectTransform(m_hCustomRender, tObjTrans); }
void nGame::Process() { static float absoluteTime = 0.0f; static float absoluteTime2 = 0.0f; // Check for pause key if(nGetInstance()->GetInput()->GetKeyboard()->GetNewKey(DIK_PAUSE) || nGetInstance()->GetInput()->GetKeyboard()->GetNewKey(DIK_P)) SetPause(!GetPause()); // Don't process further if paused if(m_Pause) return; absoluteTime += nGetInstance()->GetElapsedTime(); absoluteTime2 += nGetInstance()->GetElapsedTime(); // Create enemy missle if enough time passed if(absoluteTime > m_MissleTime) { // Reset the timer absoluteTime = 0.0f; if(m_MissleTime > 1.0f) m_MissleTime -= (double)nGetInstance()->GetElapsedTime() / 4.0f; // Create a missle at a random top position going to a random bottom position AddMissle(nVector2(randf(0.0f,nGetInstance()->GetGraphics()->GetDisplaySize().cx),0.0f),nVector2(randf(0.0f,nGetInstance()->GetGraphics()->GetDisplaySize().cx),nGetInstance()->GetGraphics()->GetDisplaySize().cy),1.0f,80.0f,GAME_ENEMY_COLOR,false,true); } // Create child enemy missle if enough time passed if(absoluteTime2 > 100.0f) { // Reset the timer absoluteTime2 = 0.0f; if(m_Missles.size()) { // Create a missle at a random trail of a nother missle unsigned long count = 1 + rand() % 3; Missle* missle = &m_Missles[rand() % m_Missles.size()]; // Find a live enemy missle while(missle->frendly || missle->dead > 0.0f) missle = &m_Missles[rand() % m_Missles.size()]; nVector2 dir = missle->position - missle->source; float length = dir.Length(); dir.Normalize(); nVector2 source = missle->source + dir * randf(0.0f,length); for(unsigned long i = 0; i < count; i++) AddMissle(source,nVector2(randf(0.0f,nGetInstance()->GetGraphics()->GetDisplaySize().cx),nGetInstance()->GetGraphics()->GetDisplaySize().cy),1.0f,80.0f,GAME_ENEMY_COLOR,false,true); } } // Check left missle silo launch if(nGetInstance()->GetInput()->GetMouse()->GetNewButton(nMouse::ButtonLeft)) FireSilo(m_Silos[0],nGetInstance()->GetInput()->GetMouse()->GetPosition(),20.0f,80.0f,true); // Check right missle silo launch if(nGetInstance()->GetInput()->GetMouse()->GetButton(nMouse::ButtonRight)) { // Get a random vector in a sphere domain nVector2 miss(randf(-1.0f,1.0f),randf(-1.0f,1.0f)); miss.Normalize(); miss *= randf(0.0f,50.0f); FireSilo(m_Silos[1],nGetInstance()->GetInput()->GetMouse()->GetPosition() + miss,30.0f,20.0f,false); } // Remove silo fire time for(unsigned long i = 0; i < m_Silos.size(); i++) { Silo* silo = &m_Silos[i]; // Remove some fire time silo->time -= nGetInstance()->GetElapsedTime(); // Check if all/too much fire time was removed if(silo->time < 0.0f) silo->time = 0.0f; } // Move missles and remove dead ones for(unsigned long i = 0; i < m_Missles.size(); i++) { Missle* missle = &m_Missles[i]; if(missle->position == missle->destination || missle->dead > 0.0f) { if(missle->position == missle->destination && !missle->frendly && missle->dead == 0.0f) { // Players base got hit SetHealth(GetHealth() - 10); // Check if player dead if(!GetHealth()) { // Game over AddHiScore(m_Score); RestartGame(); return; } } if(missle->dead > 10.0f) { // Remove missle RemoveMissle(i--); continue; } if(missle->dead == 0.0f) { // Explode AddExplosion(missle->position,missle->blast,4.0f,nColor(1.0f,0.1f,1.0f)); } missle->dead += nGetInstance()->GetElapsedTime(); continue; } if(missle->target > 0.0f) missle->target -= nGetInstance()->GetElapsedTime() / 10.0f; else if(missle->target < 0.0f) missle->target = 0.0f; nVector2 velocity = missle->destination - missle->source; velocity.Normalize(); velocity *= missle->speed * nGetInstance()->GetElapsedTime(); if(velocity.x > 0.0f && missle->position.x + velocity.x > missle->destination.x) missle->position.x = missle->destination.x; else if(velocity.x < 0.0f && missle->position.x + velocity.x < missle->destination.x) missle->position.x = missle->destination.x; else missle->position.x += velocity.x; if(velocity.y > 0.0f && missle->position.y + velocity.y > missle->destination.y) missle->position.y = missle->destination.y; else if(velocity.y < 0.0f && missle->position.y + velocity.y < missle->destination.y) missle->position.y = missle->destination.y; else missle->position.y += velocity.y; } // Expand explosions and remove dead ones for(unsigned long i = 0; i < m_Explosions.size(); i++) { Explosion* explosion = &m_Explosions[i]; if(explosion->size == explosion->power) { // Remove it RemoveExplosion(i--); continue; } float expansion = explosion->speed * nGetInstance()->GetElapsedTime(); if(explosion->size + expansion > explosion->power) explosion->size = explosion->power; else explosion->size += expansion; // Check if the explosion destroyed any missles for(unsigned long j = 0; j < m_Missles.size(); j++) { Missle* missle = &m_Missles[j]; // Skip frendly or already dead missles if(missle->frendly || missle->dead > 0.0f) continue; nVector2 dir = missle->position - explosion->position; unsigned long points = 0; if(dir.Length() < explosion->size) { // Set death time missle->dead += nGetInstance()->GetElapsedTime(); // Create some particles for(unsigned long k = 0; k < 50; k++) { // Get a random vector in a sphere domain nVector2 velocity(randf(-1.0f,1.0f),randf(-1.0f,1.0f)); velocity.Normalize(); velocity *= randf(0.0f,5.0f); AddParticle(missle->position,velocity,nVector2(0.0f,0.1f),10.0f,GAME_COLOR_PARTICLES); } // Add player points points++; } // Add score if(points > 1) SetScore(GetScore() + points + points / 2); else SetScore(GetScore() + points); } } // Move and remove particles for(unsigned long i = 0; i < m_Particles.size(); i++) { Particle* particle = &m_Particles[i]; // Remove the particle if it's dead if(particle->lived >= particle->life) { // Remove it RemoveParticle(i--); continue; } particle->lived += nGetInstance()->GetElapsedTime(); particle->velocity += particle->force * nGetInstance()->GetElapsedTime(); particle->position += particle->velocity * nGetInstance()->GetElapsedTime(); } }
// updates all systems void CParticleSystemManager::UpdateSystems( void ) { CParticleSystem *pSystem = NULL; signed int i = 0; signed int iSystems = (signed)m_pParticleSystems.size(); // iterate through all the particle systems, drawing each for (; i < iSystems; i++) { pSystem = m_pParticleSystems[i]; // remove the system if the system requests it if( pSystem && pSystem->DrawSystem() == false) { delete pSystem; m_pParticleSystems.erase((m_pParticleSystems.begin() + i)); i--; iSystems--; } } // we couldn't return earlier as we need to have the sorting before the ps updating // however no sorting when we can't see the particles if(CheckDrawSystem() == false) return; int iParticles = m_pParticles.size(); if(iParticles > 1) { // calculate the fraction of a second between sorts float flTimeSinceLastSort = (gEngfuncs.GetClientTime() - m_flLastSort); // 1 / time between sorts will give us a number like 5 // if it is less than the particlesorts cvar then it is a small value // and therefore a long time since last sort if((((int)(1 / flTimeSinceLastSort)) < g_ParticleSorts->value)) { m_flLastSort = gEngfuncs.GetClientTime(); std::sort(m_pParticles.begin(), m_pParticles.end(), less_than); } } if(iParticles > 0) { // prepare opengl Particle_InitOpenGL(); float flTimeSinceLastDraw = TimeSinceLastDraw(); // loop through all particles drawing them // they have already been tested and updated in their pss' draw sys func // we've reversed the greater than order, so that oldest particles are at 0 // further away particles and recently added particles get drawn first CParticle *pParticle = NULL; for(i = 0; i < iParticles ; i++) { if(m_pParticles[i]) { pParticle = m_pParticles[i]; if(pParticle && pParticle->Test()) { pParticle->Update(flTimeSinceLastDraw); if(g_iUser1 != OBS_MAP_FREE && g_iUser1 != OBS_MAP_CHASE) { // unfortunately we have to prepare every particle now // as we can't prepare for a batch of the same type anymore pParticle->Prepare(); pParticle->Draw(); } } else { RemoveParticle(pParticle); i--; iParticles--; } } } Particle_FinishOpenGL(); } // print out how fast we've been drawing the systems in debug mode if (g_ParticleDebug->value != 0 && ((m_flLastSort + 0.5) <= gEngfuncs.GetClientTime())) { gEngfuncs.Con_Printf("%i Particles Drawn this pass in %i systems ", m_pParticles.size(), m_pParticleSystems.size()); gEngfuncs.Con_Printf("%i Textures in Cache\n\0", m_pTextures.size()); } m_flLastDraw = gEngfuncs.GetClientTime(); }