bool CTracerFX::Update(float tmFrameTime) { //track our performance CTimedSystemBlock TimingBlock(g_tsClientFXTracer); // Base class update first BaseUpdate(tmFrameTime); //determine the length of this tracer float fTracerLen = GetTracerLength(); //update the position along the ray m_fRayPosition += GetProps()->m_fVelocity * tmFrameTime; float fTracerHead = m_fRayPosition; float fTracerTail = m_fRayPosition - fTracerLen; //now we need to find the extents of the segment float fSegmentStart = LTCLAMP(fTracerHead, 0.0f, m_fRayLength); float fSegmentEnd = LTCLAMP(fTracerTail, 0.0f, m_fRayLength); //now we generate a bounding box around these points LTVector vCenter = m_vStartPos + m_vDirection * ((fSegmentStart + fSegmentEnd) * 0.5f); //and now the half dimensions that encompass the tracer LTVector vHalfDims = m_vDirection * ((fSegmentStart - fSegmentEnd) * 0.5f); //make sure that this half dims represents the maximum extents vHalfDims.Max(-vHalfDims); //extend the half dims to include the thickness of the tracer float fUnitLifetime = GetUnitLifetime(); float fHalfThickness = GetProps()->m_ffcThickness.GetValue(fUnitLifetime) * 0.5f; vHalfDims += LTVector(fHalfThickness, fHalfThickness, fHalfThickness); //and now setup the object position and visibility g_pLTClient->SetObjectPos(m_hObject, vCenter); g_pLTClient->GetCustomRender()->SetVisBoundingBox(m_hObject, -vHalfDims, vHalfDims); //handle updating the light if we have one if(m_hTracerLight) { //the light needs to be moved to the center of the tracer g_pLTClient->SetObjectPos(m_hTracerLight, vCenter); //and have the intensity controlled by how much of the tracer is visible float fVisible = (fSegmentStart - fSegmentEnd) / fTracerLen; if(fVisible > 0.01f) { g_pLTClient->Common()->SetObjectFlags(m_hTracerLight, OFT_Flags, FLAG_VISIBLE, FLAG_VISIBLE); g_pLTClient->SetLightIntensityScale(m_hTracerLight, fVisible); } else { g_pLTClient->Common()->SetObjectFlags(m_hTracerLight, OFT_Flags, 0, FLAG_VISIBLE); } } //we are done if the tracer is past the end of the ray return (m_fRayPosition - GetTracerLength() < m_fRayLength); }
//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); }