Esempio n. 1
0
//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);
}