#include "l4particles.h" #include "../munga/time.h" LPDIRECT3DDEVICE9 ParticleEngine::mDevice = NULL; PARTICLE_EFFECT ParticleEngine::mInstalledEffects[MAX_PARTICLE_EFFECTS]; LPDIRECT3DTEXTURE9 ParticleEngine::mParticleTexture = NULL; bool ParticleEngine::mActiveParticles = false; LPDIRECT3DVERTEXBUFFER9 ParticleEngine::mVertBuffer = NULL; Particle *ParticleEngine::mParticlesHead = NULL; Particle *ParticleEngine::mParticlesTail = NULL; long ParticleEngine::mTotalParticleCount = 0; long ParticleEngine::mMaxParticleCount = 8192; int compare(const Particle *a, const Particle *b, const D3DXMATRIX *view_matrix) { D3DXVECTOR3 aVec(a->mX, a->mY, a->mZ); D3DXVECTOR3 bVec(b->mX, b->mY, b->mZ); D3DXVec3TransformCoord(&aVec, &aVec, view_matrix); D3DXVec3TransformCoord(&bVec, &bVec, view_matrix); float aDist = aVec.x * aVec.x + aVec.y * aVec.y + aVec.z * aVec.z; float bDist = bVec.x * bVec.x + bVec.y * bVec.y + bVec.z * bVec.z; // if both particles are within a certain distance from // each other then we'll sort them by effect instead of // distance so that they will be grouped together when // rendering //if (abs(bDist - aDist) < EFFECT_GROUPING_EPSILON) // return b->mEffect->id - a->mEffect->id; //else return (int)(bDist - aDist); } void mergesort(Particle **headPtr, Particle **tailPtr, const D3DXMATRIX *view_matrix) { Particle *list = *headPtr; if (!list) { (*tailPtr) = NULL; return; } Particle *p, *q, *e, *tail, *oldhead; int insize, nmerges, psize, qsize, i; insize = 1; while (1) { p = list; oldhead = list; list = NULL; tail = NULL; nmerges = 0; // count number of merges we do in this pass while (p) { nmerges++; // there exists a merge to be done // step 'insize' places along from p q = p; psize = 0; for (i=0; iNext(); if (!q) break; } // if q hasn't fallen off end, we have two lists to merge qsize = insize; // now we have two lists; merge them while (psize > 0 || (qsize > 0 && q)) { // decide whether next element of merge comes from p or q if (psize == 0) { // p is empty; e must come from q e = q; q = q->Next(); qsize--; } else if (qsize == 0 || !q) { // q is empty; e must come from p e = p; p = p->Next(); psize--; } else if (compare(p, q, view_matrix) <= 0) { // first elements of p is lower (or same); // e must come from p e = p; p = p->Next(); psize--; } else { // first element of q is lower; e must come from q e = q; q = q->Next(); qsize--; } // add the next element to the merged list if (tail) tail->mNextParticle = e; else list = e; e->mPrevParticle = tail; tail = e; } // now p has stepped 'insize' places along, and q has too p = q; } tail->mNextParticle = NULL; // if we have done only one merge, we're finished if (nmerges <= 1) // allow for nmerges == 0, the empty list case { (*headPtr) = list; (*tailPtr) = tail; return; } // otherwise repeat, merging lists twice the size insize *= 2; } } Particle::Particle(PARTICLE_EFFECT *effect) { mEffect = effect; mAge = 0.0f; mX = 0.0f; mY = 0.0f; mZ = 0.0f; mColor.argb = D3DCOLOR_ARGB(0xFF, 0xFF, 0x00, 0x00); memset(&mVelocity, 0, sizeof(mVelocity)); memset(&mAcceleration, 0, sizeof(mAcceleration)); } void Particle::Execute(Scalar dT) { if (!mEffect) return; if (IsAlive()) { mX += mVelocity.x * dT; mY += mVelocity.y * dT; mZ += mVelocity.z * dT; mVelocity.x += mAcceleration.x * dT; mVelocity.y += mAcceleration.y * dT; mVelocity.z += mAcceleration.z * dT; mAge += dT; Scalar percentComplete = mAge / mEffect->fragLifetime; COLOR_POINT *cp0 = NULL; COLOR_POINT *cp1 = NULL; for (COLOR_POINT *cp = mEffect->colors + COLOR_POINT_COUNT - 2; cp >= mEffect->colors; cp--) { if (cp->active && percentComplete >= cp->time) { cp0 = cp; cp1 = cp + 1; break; } } if (!cp0 || !cp1) { cp0 = mEffect->colors; if (COLOR_POINT_COUNT > 1) { cp1 = mEffect->colors + 1; } else { cp1 = cp0; } } percentComplete = (percentComplete - cp0->time) / (cp1->time - cp0->time); mColor.a = (unsigned char)(cp0->color.a + (cp1->color.a - cp0->color.a) * percentComplete); mColor.r = (unsigned char)(cp0->color.r + (cp1->color.r - cp0->color.r) * percentComplete); mColor.g = (unsigned char)(cp0->color.g + (cp1->color.g - cp0->color.g) * percentComplete); mColor.b = (unsigned char)(cp0->color.b + (cp1->color.b - cp0->color.b) * percentComplete); } } ParticleEmitter::ParticleEmitter() : mEffect(NULL), mActive(false), mPosition(0.0f, 0.0f, 0.0f) { } void ParticleEmitter::SetEffect(int effect) { mEffect = &ParticleEngine::mInstalledEffects[effect]; } void ParticleEmitter::Execute() { if (mEffect == NULL) return; // calculate the number of particles we're going to need if (mActive) { bool fire = false; if (mEffect->id >= 1000) { // this is an independant effect static Scalar lastEmitted = (Scalar)Now(); Scalar now = (Scalar)Now(); INDIE_EFFECT *indieEffect = (INDIE_EFFECT*)mEffect; if ((now - lastEmitted) >= indieEffect->releasePeriod) { lastEmitted = now; fire = true; } if ((now - mActivated) >= indieEffect->duration) mActive = false; } else { fire = true; mActive = false; } if (fire) { for (int i = 0; i < mEffect->fragCount; ++i) ParticleEngine::CreateParticle(mPosition, mEffect); } } } void ParticleEngine::Destroy() { mVertBuffer->Release(); mParticleTexture->Release(); } void ParticleEngine::Initialize(LPDIRECT3DDEVICE9 device) { mDevice = device; memset(mInstalledEffects, 0, sizeof(mInstalledEffects)); ParticleEngine::mMaxParticleCount = atoi(getenv("MAXPARTICLES")); // create the vertex buffer that will store the four vertices we need for the billboards mDevice->CreateVertexBuffer(ParticleEngine::mMaxParticleCount * 6 * sizeof(L4BASICVERTEX), D3DUSAGE_DYNAMIC, L4BASICVERTEX_FVF, D3DPOOL_DEFAULT, &mVertBuffer, NULL); D3DXCreateTextureFromFile(mDevice, L"VIDEO\\particles.png", &mParticleTexture); } void ParticleEngine::InstallEffect(int effectNumber, PARTICLE_EFFECT effect) { mInstalledEffects[effectNumber] = effect; mInstalledEffects[effectNumber].id = effectNumber; } void ParticleEngine::CreateParticle(D3DXVECTOR3 position, PARTICLE_EFFECT *effect) { if (effect == NULL || effect->fragCount <= 0) return; Particle *p = new Particle(effect); // check to make sure we have room for this new particle if (mTotalParticleCount + 1 >= ParticleEngine::mMaxParticleCount) { mTotalParticleCount--; // there isn't room for this particle so we'll delete the oldest particle if (mParticlesHead == mParticlesTail) { delete mParticlesHead; mParticlesHead = NULL; mParticlesTail = NULL; } else { Particle *temp = mParticlesTail; mParticlesTail = mParticlesTail->mPrevParticle; mParticlesTail->mNextParticle = NULL; delete temp; } } // insert the new particle into the linked list if (mParticlesHead) mParticlesHead->mPrevParticle = p; p->mPrevParticle = NULL; p->mNextParticle = mParticlesHead; mParticlesHead = p; if (!mParticlesTail) mParticlesTail = p; mTotalParticleCount++; // now initialize all the particle data based on the effect p->mX = position.x + ((float)rand() * 2.0f / RAND_MAX - 1.0f) * effect->varianceX; p->mY = position.y + ((float)rand() * 2.0f / RAND_MAX - 1.0f) * effect->varianceY; p->mZ = position.z + ((float)rand() * 2.0f / RAND_MAX - 1.0f) * effect->varianceZ; D3DXVECTOR3 vec( (float)rand() * 2.0f / RAND_MAX - 1.0f, (float)rand() * 2.0f / RAND_MAX - 1.0f, (float)rand() * 2.0f / RAND_MAX - 1.0f); D3DXVec3Normalize(&vec, &vec); p->mVelocity.x = vec.x * effect->velocity; p->mVelocity.y = vec.y * effect->velocity; p->mVelocity.z = vec.z * effect->velocity; p->mAcceleration.x = 0.0f; p->mAcceleration.y = effect->gravity; p->mAcceleration.z = 0.0f; // generate a random rotation amount in radians float centerX = effect->textureBounds.left + (effect->textureBounds.right - effect->textureBounds.left) / 2.0f; float centerY = effect->textureBounds.top + (effect->textureBounds.bottom - effect->textureBounds.top) / 2.0f; D3DXVECTOR2 rotCenter(centerX, centerY); D3DXMatrixTransformation2D(&p->mTextureTransform, NULL, 1, NULL, &rotCenter, ((float)rand() / RAND_MAX) * 2.0f * D3DX_PI, NULL); p->mColor.argb = (effect->colors[0].active ? effect->colors[0].color.argb : 0); } int ParticleEngine::BuildParticleVertices(const Particle *p, L4BASICVERTEX *verts, D3DXMATRIX *inverse_view_matrix) { D3DXMATRIX rotation; PARTICLE_EFFECT *effect = p->mEffect; inverse_view_matrix->_41 = p->mX; inverse_view_matrix->_42 = p->mY; inverse_view_matrix->_43 = p->mZ; float fragSize = effect->fragSize; D3DXVECTOR3 vector3s[] = { D3DXVECTOR3(-fragSize, fragSize, 0.0f), // upper left D3DXVECTOR3( fragSize, -fragSize, 0.0f), // lower right D3DXVECTOR3(-fragSize, -fragSize, 0.0f), // lower left D3DXVECTOR3(-fragSize, fragSize, 0.0f), // upper left D3DXVECTOR3( fragSize, fragSize, 0.0f), // upper right D3DXVECTOR3( fragSize, -fragSize, 0.0f) // lower right }; D3DXVECTOR2 vector2s[] = { D3DXVECTOR2(effect->textureBounds.left, effect->textureBounds.top), // upper left D3DXVECTOR2(effect->textureBounds.right, effect->textureBounds.bottom), // lower right D3DXVECTOR2(effect->textureBounds.left, effect->textureBounds.bottom), // lower left D3DXVECTOR2(effect->textureBounds.left, effect->textureBounds.top), // upper left D3DXVECTOR2(effect->textureBounds.right, effect->textureBounds.top), // upper right D3DXVECTOR2(effect->textureBounds.right, effect->textureBounds.bottom) // lower right }; D3DXVECTOR3 normal, inNormal(0.0f, 0.0f, 1.0f); D3DXVec3TransformCoord(&normal, &inNormal, inverse_view_matrix); int count = (sizeof(vector3s) / sizeof(D3DXVECTOR3)); for (int i=0; irotate) D3DXVec2TransformCoord(&outVec2, &vector2s[i], &p->mTextureTransform); else outVec2 = vector2s[i]; verts[i].u = outVec2.x; verts[i].v = outVec2.y; verts[i].color = p->mColor.argb; } return count; } void ParticleEngine::ExecuteParticles(const D3DXMATRIX *view_matrix, Scalar timeSlice) { // if we don't have a device or we have no particles, early exit if (!mDevice || mTotalParticleCount <= 0) return; // we can't run without having been initialized with a device // first we need to execute all the particles to update their positions, color, etc. for (Particle *p = mParticlesHead; p;) { if (p->IsAlive()) { p->Execute(timeSlice); p = p->mNextParticle; } else { // remove from the list if (p->mPrevParticle) p->mPrevParticle->mNextParticle = p->mNextParticle; if (p->mNextParticle) p->mNextParticle->mPrevParticle = p->mPrevParticle; // time to kill the particle if (p == mParticlesHead) { mParticlesHead = p->mNextParticle; } if (p == mParticlesTail) { mParticlesTail = p->mPrevParticle; } Particle *temp = p; p = p->mNextParticle; mTotalParticleCount--; delete temp; } } // now we're going to sort the particle list based on distance to the camera mergesort(&mParticlesHead, &mParticlesTail, view_matrix); //Scalar beforeBuild = (Scalar)Now(); D3DXMATRIX inverse_view_matrix; D3DXMatrixInverse(&inverse_view_matrix, NULL, view_matrix); // and finally, we'll build the vertex list to send to Direct3D L4BASICVERTEX *vertBuffer = NULL; mVertBuffer->Lock(0, mTotalParticleCount * sizeof(L4BASICVERTEX), (void**)&vertBuffer, D3DLOCK_DISCARD); int actualParticles = 0; for (Particle *p = mParticlesHead; p; p = p->mNextParticle) { vertBuffer += BuildParticleVertices(p, vertBuffer, &inverse_view_matrix); actualParticles++; } mVertBuffer->Unlock(); //Scalar afterBuild = (Scalar)Now(); //afterBuild = afterBuild; } void ParticleEngine::RenderParticles(const D3DXMATRIX *view_matrix, Scalar timeSlice) { // we always have to execute otherwise our timing // variables get all screwed up when we don't run for a while ExecuteParticles(view_matrix, timeSlice); if (!mDevice || mTotalParticleCount <= 0) return; // setup stages for particle's texture mDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE); mDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE); mDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE); mDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE); mDevice->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); mDevice->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); mDevice->SetRenderState(D3DRS_DIFFUSEMATERIALSOURCE, D3DMCS_COLOR1); mDevice->SetRenderState(D3DRS_EMISSIVEMATERIALSOURCE, D3DMCS_COLOR1); mDevice->SetTexture(0, mParticleTexture); mDevice->SetTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE); mDevice->SetFVF(L4BASICVERTEX_FVF); mDevice->SetStreamSource(0, mVertBuffer, 0, sizeof(L4BASICVERTEX)); //DWORD wrap_u, wrap_v; //mDevice->GetSamplerState(0, D3DSAMP_ADDRESSU, &wrap_u); //mDevice->GetSamplerState(0, D3DSAMP_ADDRESSV, &wrap_v); //mDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP); //mDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP); D3DXMATRIX world_matrix; D3DXMatrixIdentity(&world_matrix); mDevice->SetTransform(D3DTS_WORLD, &world_matrix); mDevice->DrawPrimitive(D3DPT_TRIANGLELIST, 0, mTotalParticleCount * 2); mDevice->SetRenderState(D3DRS_EMISSIVEMATERIALSOURCE, D3DMCS_MATERIAL); mDevice->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_TFACTOR); //mDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, wrap_u); //mDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, wrap_v); }