content/VIDEO/particles.png (128x128 RGBA): the texture L4PARTICLES has tried to load since the engine was written. Boot-verified: [particles] device objects created (max=8192, texture=loaded). Every particle effect now draws textured -- the authored look; the arcade pods displayed untextured quads because the file never shipped. Comments updated to match. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
607 lines
18 KiB
C++
607 lines
18 KiB
C++
#include "l4particles.h"
|
|
#include "../munga/time.h"
|
|
#include "../munga/style.h" // DEBUG_STREAM (#35 diagnostics)
|
|
#include <iostream>
|
|
|
|
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()
|
|
{
|
|
// #35 (the field "Owens crash"). This runs on the DEVICELOST path, and a
|
|
// device can stay lost for several frames -- so this must be idempotent
|
|
// and null-safe. It used to Release() blind: the first lost frame
|
|
// released the buffer, the failed re-create on the still-lost device left
|
|
// both statics NULL, and the SECOND lost frame's Release() read the
|
|
// vtable at 0x0 (8/8 field stacks byte-identical at Destroy +0x11;
|
|
// reproduced on the bench with BT_DEVICELOST_TEST=<n>,crashrepro).
|
|
if (mVertBuffer != NULL)
|
|
{
|
|
mVertBuffer->Release();
|
|
mVertBuffer = NULL;
|
|
}
|
|
if (mParticleTexture != NULL)
|
|
{
|
|
mParticleTexture->Release();
|
|
mParticleTexture = NULL;
|
|
}
|
|
}
|
|
|
|
void ParticleEngine::Initialize(LPDIRECT3DDEVICE9 device)
|
|
{
|
|
// Full STARTUP init only. The device-reset path must use
|
|
// CreateDeviceObjects() below -- coming through here would memset the
|
|
// installed-effects table and kill every particle effect for the rest of
|
|
// the mission (#35's quieter sibling).
|
|
memset(mInstalledEffects, 0, sizeof(mInstalledEffects));
|
|
|
|
const char *max_env = getenv("MAXPARTICLES");
|
|
ParticleEngine::mMaxParticleCount = (max_env != NULL) ? atoi(max_env) : 0;
|
|
if (ParticleEngine::mMaxParticleCount <= 0)
|
|
ParticleEngine::mMaxParticleCount = 8192; // btl4main defaults the env; belt + braces
|
|
|
|
CreateDeviceObjects(device);
|
|
}
|
|
|
|
void ParticleEngine::CreateDeviceObjects(LPDIRECT3DDEVICE9 device)
|
|
{
|
|
// (Re)create the D3D resources. #35: CHECK the HRESULTs -- on a lost
|
|
// device (or an out-of-video-memory iGPU, the field machine is a 128 MB
|
|
// Iris Xe) these FAIL and null their out params; the old code drove
|
|
// straight on, and the NULLs then killed the next Destroy(). On failure
|
|
// we log and leave the engine dormant -- Execute/Render guard on the
|
|
// NULLs, particles drain but don't draw -- and the next successful reset
|
|
// brings it back.
|
|
mDevice = device;
|
|
|
|
mVertBuffer = NULL;
|
|
mParticleTexture = NULL;
|
|
|
|
// create the vertex buffer that will store the six vertices we need for the billboards
|
|
HRESULT hr = mDevice->CreateVertexBuffer(
|
|
ParticleEngine::mMaxParticleCount * 6 * sizeof(L4BASICVERTEX),
|
|
D3DUSAGE_DYNAMIC,
|
|
L4BASICVERTEX_FVF,
|
|
D3DPOOL_DEFAULT,
|
|
&mVertBuffer,
|
|
NULL);
|
|
if (FAILED(hr))
|
|
{
|
|
mVertBuffer = NULL; // belt + braces vs runtime behavior
|
|
DEBUG_STREAM << "[particles] CreateVertexBuffer FAILED hr=0x"
|
|
<< std::hex << (unsigned long)hr << std::dec
|
|
<< " (max=" << ParticleEngine::mMaxParticleCount
|
|
<< ") -- particles dormant until the next successful reset"
|
|
<< std::endl << std::flush;
|
|
}
|
|
|
|
// NB: VIDEO\particles.png SHIPS as of 2026-07-29 (recovered by cyd,
|
|
// issue #79 -- 128x128 RGBA). Before that it had never shipped (absent
|
|
// from the tree, the RES, and all of git history), so this load failed
|
|
// on every machine since the engine was written and particles drew
|
|
// untextured -- which is therefore what the 1995 pods displayed. The failure is expected + logged once; rendering
|
|
// proceeds without the texture (SetTexture(0, NULL) = the shipped look).
|
|
hr = D3DXCreateTextureFromFile(mDevice, L"VIDEO\\particles.png", &mParticleTexture);
|
|
if (FAILED(hr))
|
|
mParticleTexture = NULL;
|
|
|
|
if (mVertBuffer != NULL)
|
|
{
|
|
DEBUG_STREAM << "[particles] device objects created (max="
|
|
<< ParticleEngine::mMaxParticleCount
|
|
<< ", texture=" << (mParticleTexture != NULL ? "loaded" : "MISSING (untextured quads -- the shipped look)")
|
|
<< ")" << std::endl << std::flush;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
// #35: with the device objects torn down (device lost mid-reset) the
|
|
// update/expire loop above must still run -- particles keep draining --
|
|
// but there is no buffer to build into.
|
|
if (mVertBuffer == NULL)
|
|
return;
|
|
|
|
// 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);
|
|
|
|
// (#35: the buffer is legitimately NULL while the device is lost. The
|
|
// TEXTURE is deliberately NOT part of this gate: VIDEO\particles.png was
|
|
// recovered only in 2026 (issue #79) -- before that mParticleTexture was
|
|
// NULL on every machine since the engine was written, and
|
|
// SetTexture(0, NULL) drawing untextured quads WAS the
|
|
// shipped look. Gating on it would silently disable all billboard
|
|
// particles everywhere.)
|
|
if (!mDevice || mVertBuffer == NULL || 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);
|
|
} |