//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);
}
Beispiel #2
0
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();
}