Files
RP411/MUNGA_L4/L4PARTICLES.cpp
T
CydandClaude Opus 4.8 4abbf8879f Initial import of Red Planet v4.10 Win32 source
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>
2026-06-30 07:59:51 -05:00

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);
}