Imports the current Win32 source for the pod-racing game 'Red Planet', built on the MUNGA engine and its L4 (Win32/DirectX) platform layer: - MUNGA / MUNGA_L4: cross-platform engine core and Win32 backend - RP / RP_L4: Red Planet game logic and Win32 application - DivLoader, Setup1: asset loader and installer project - lib, MUNGA_L4/openal, MUNGA_L4/sos: third-party audio dependencies Removed stale Subversion metadata and added .gitignore/.gitattributes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
528 lines
14 KiB
C++
528 lines
14 KiB
C++
#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; i<insize; i++)
|
|
{
|
|
psize++;
|
|
q = q->Next();
|
|
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; i<count; i++)
|
|
{
|
|
D3DXVECTOR3 outVec3;
|
|
D3DXVECTOR2 outVec2;
|
|
|
|
D3DXVec3TransformCoord(&outVec3, &vector3s[i], inverse_view_matrix);
|
|
|
|
verts[i].x = outVec3.x;
|
|
verts[i].y = outVec3.y;
|
|
verts[i].z = outVec3.z;
|
|
|
|
verts[i].nx = normal.x;
|
|
verts[i].ny = normal.y;
|
|
verts[i].nz = normal.z;
|
|
|
|
if (effect->rotate)
|
|
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);
|
|
} |