📄 particle.cpp
字号:
#include "particle.h"
#include "util.h"
#define MAX_PARTICLES 10000
Vec4D fromARGB(uint32 color)
{
const float a = ((color & 0xFF000000) >> 24) / 255.0f;
const float r = ((color & 0x00FF0000) >> 16) / 255.0f;
const float g = ((color & 0x0000FF00) >> 8) / 255.0f;
const float b = ((color & 0x000000FF) ) / 255.0f;
return Vec4D(r,g,b,a);
}
template<class T>
T lifeRamp(float life, float mid, const T &a, const T &b, const T &c)
{
if (life<=mid)
return interpolate<T>(life / mid,a,b);
else
return interpolate<T>((life-mid) / (1.0f-mid),b,c);
}
void ParticleSystem::init(MPQFile &f, ModelParticleEmitterDef &mta, int *globals)
{
speed.init (mta.params[0], f, globals);
variation.init(mta.params[1], f, globals);
spread.init (mta.params[2], f, globals);
lat.init (mta.params[3], f, globals);
gravity.init (mta.params[4], f, globals);
lifespan.init(mta.params[5], f, globals);
rate.init (mta.params[6], f, globals);
areal.init (mta.params[7], f, globals);
areaw.init (mta.params[8], f, globals);
grav2.init (mta.params[9], f, globals);
enabled.init (mta.en, f, globals);
for (size_t i=0; i<3; i++) {
colors[i] = fromARGB(mta.p.colors[i]);
sizes[i] = mta.p.sizes[i];// * mta.p.scales[i];
}
mid = mta.p.mid;
slowdown = mta.p.slowdown;
rotation = mta.p.rotation;
//pos = fixCoordSystem(mta.pos);
pos = fixCoordSystem(mta.pos);
texture = model->textures[mta.texture];
blend = mta.blend;
rows = mta.rows;
cols = mta.cols;
type = mta.s1;
//order = mta.s2;
order = mta.s1>0 ? -1 : 0;
parent = model->bones + mta.bone;
switch (mta.type) {
case 1:
emitter = new PlaneParticleEmitter(this);
break;
case 2:
emitter = new SphereParticleEmitter(this);
break;
}
//transform = mta.flags & 1024;
billboard = !(mta.flags & 4096);
manim = mtime = 0;
rem = 0;
tofs = frand();
// init tiles
for (int i=0; i<rows*cols; i++) {
TexCoordSet tc;
initTile(tc.tc,i);
tiles.push_back(tc);
}
}
void ParticleSystem::initTile(Vec2D *tc, int num)
{
Vec2D otc[4];
Vec2D a,b;
int x = num % cols;
int y = num / cols;
a.x = x * (1.0f / cols);
b.x = (x+1) * (1.0f / cols);
a.y = y * (1.0f / rows);
b.y = (y+1) * (1.0f / rows);
otc[0] = a;
otc[2] = b;
otc[1].x = b.x;
otc[1].y = a.y;
otc[3].x = a.x;
otc[3].y = b.y;
for (int i=0; i<4; i++) {
tc[(i+4-order) & 3] = otc[i];
}
}
void ParticleSystem::update(float dt)
{
float grav = gravity.getValue(manim, mtime);
// spawn new particles
if (emitter) {
float frate = rate.getValue(manim, mtime);
float flife = 1.0f;
//flife = lifespan.getValue(manim, mtime);
float ftospawn = (dt * frate / flife) + rem;
if (ftospawn < 1.0f) {
rem = ftospawn;
if (rem<0)
rem = 0;
} else {
int tospawn = (int)ftospawn;
rem = ftospawn - (float)tospawn;
//rem = 0;
for (int i=0; i<tospawn; i++) {
bool en = enabled.getValue(manim, mtime)!=0;
if (en) {
Particle p = emitter->newParticle(manim, mtime);
// sanity check:
if (particles.size() < MAX_PARTICLES) particles.push_back(p);
}
}
}
}
float mspeed = 1.0f;
for (ParticleList::iterator it = particles.begin(); it != particles.end(); ) {
Particle &p = *it;
p.speed += p.down * grav * dt;
if (slowdown>0) {
mspeed = expf(-1.0f * slowdown * p.life);
}
p.pos += p.speed * mspeed * dt;
p.life += dt;
float rlife = p.life / p.maxlife;
// calculate size and color based on lifetime
p.size = lifeRamp<float>(rlife, mid, sizes[0], sizes[1], sizes[2]);
p.color = lifeRamp<Vec4D>(rlife, mid, colors[0], colors[1], colors[2]);
// kill off old particles
if (rlife >= 1.0f) particles.erase(it++);
else ++it;
}
}
void ParticleSystem::setup(int anim, int time)
{
manim = anim;
mtime = time;
/*
if (transform) {
// transform every particle by the parent trans matrix - apparently this isn't needed
Matrix m = parent->mat;
for (ParticleList::iterator it = particles.begin(); it != particles.end(); ++it) {
it->tpos = m * it->pos;
}
} else {
for (ParticleList::iterator it = particles.begin(); it != particles.end(); ++it) {
it->tpos = it->pos;
}
}
*/
}
void ParticleSystem::draw()
{
Vec3D bv0,bv1,bv2,bv3;
// setup blend mode
switch (blend) {
case 0:
glDisable(GL_BLEND);
glDisable(GL_ALPHA_TEST);
break;
case 1:
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_COLOR, GL_ONE);
glDisable(GL_ALPHA_TEST);
break;
case 2:
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_ALPHA_TEST);
break;
case 3:
glDisable(GL_BLEND);
glEnable(GL_ALPHA_TEST);
break;
case 4:
glEnable(GL_BLEND);
glDisable(GL_ALPHA_TEST);
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
break;
}
//glDisable(GL_LIGHTING);
//glDisable(GL_CULL_FACE);
//glDepthMask(GL_FALSE);
glBindTexture(GL_TEXTURE_2D, texture);
Matrix mbb; mbb.unit();
if (billboard) {
// get a billboard matrix
Matrix mtrans; glGetFloatv(GL_MODELVIEW_MATRIX, &(mtrans.m[0][0])); mtrans.transpose(); mtrans.invert(); Vec3D camera = mtrans * Vec3D(0,0,0); Vec3D look = (camera - pos).normalize(); Vec3D up = ((mtrans * Vec3D(0,1,0)) - camera).normalize(); Vec3D right = (up % look).normalize(); up = (look % right).normalize(); // calculate the billboard matrix mbb.m[0][1] = right.x; mbb.m[1][1] = right.y; mbb.m[2][1] = right.z; mbb.m[0][2] = up.x; mbb.m[1][2] = up.y; mbb.m[2][2] = up.z; mbb.m[0][0] = look.x; mbb.m[1][0] = look.y; mbb.m[2][0] = look.z; } if (type==0 || type==2) { // TODO: figure out type 2 (deeprun tram subway sign) // - doesn't seem to be any different from 0 -_- // regular particles float f = 0.707106781f; // sqrt(2)/2 if (billboard) { bv0 = mbb * Vec3D(0,-f,+f); bv1 = mbb * Vec3D(0,+f,+f); bv2 = mbb * Vec3D(0,+f,-f); bv3 = mbb * Vec3D(0,-f,-f); } else { bv0 = Vec3D(-f,0,+f); bv1 = Vec3D(+f,0,+f); bv2 = Vec3D(+f,0,-f); bv3 = Vec3D(-f,0,-f); } // TODO: per-particle rotation in a non-expensive way?? :| glBegin(GL_QUADS); for (ParticleList::iterator it = particles.begin(); it != particles.end(); ++it) {
glColor4fv(it->color);
glTexCoord2fv(tiles[it->tile].tc[0]);
glVertex3fv(it->pos + bv0 * it->size);
glTexCoord2fv(tiles[it->tile].tc[1]);
glVertex3fv(it->pos + bv1 * it->size);
glTexCoord2fv(tiles[it->tile].tc[2]);
glVertex3fv(it->pos + bv2 * it->size);
glTexCoord2fv(tiles[it->tile].tc[3]);
glVertex3fv(it->pos + bv3 * it->size);
}
glEnd();
} else if (type==1) {
// particles from origin to position
bv0 = mbb * Vec3D(0,-1.0f,0);
bv1 = mbb * Vec3D(0,1.0f,0);
glBegin(GL_QUADS); for (ParticleList::iterator it = particles.begin(); it != particles.end(); ++it) {
glColor4fv(it->color);
glTexCoord2fv(tiles[it->tile].tc[0]);
glVertex3fv(it->pos + bv0 * it->size);
glTexCoord2fv(tiles[it->tile].tc[1]);
glVertex3fv(it->pos + bv1 * it->size);
glTexCoord2fv(tiles[it->tile].tc[2]);
glVertex3fv(it->origin + bv1 * it->size);
glTexCoord2fv(tiles[it->tile].tc[3]);
glVertex3fv(it->origin + bv0 * it->size);
}
glEnd();
}
//glEnable(GL_LIGHTING);
glEnable(GL_ALPHA_TEST);
glDisable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
//glDepthMask(GL_TRUE);
//glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
}
Particle PlaneParticleEmitter::newParticle(int anim, int time)
{
Particle p;
// TODO: maybe evaluate these outside the spawn function, since they will be common for a given frame?
float spr = sys->spread.getValue(anim, time);
float w = sys->areal.getValue(anim, time) * spr;
float l = sys->areaw.getValue(anim, time) * spr;
float spd = sys->speed.getValue(anim, time);
float var = sys->variation.getValue(anim, time);
// what the butter!
// The new halo of transcendence particle effect has no width nor height
// but its suppose to look like a circular glowing halo
// This would make sense under a SphereParticleEmitter.. but here!?
if (l==0 && w==0 && spr==0 && var==1.0f) { // If all these values are zero, assume its squared ?
float t = randfloat(0,2*PI);
Vec3D z = Vec3D(0.0f, sys->pos.y + 0.15f, sys->pos.z); // Need to manually correct for the halo - why?
p.pos = z + Vec3D(cos(t)/8, 0.0f, sin(t)/8);
var /= 2; // since we're manually correcting all this just for the halo, may aswell do this aswell.
// var isn't being used, which is set to 1.0f, whats the importance of this?
// why does this set of values differ from other particles
} else {
p.pos = sys->pos + Vec3D(randfloat(-l, l), 0.0f, randfloat(-w,w));
if (sys->parent->parent == 0) // Needed for items that have particles attached via visualeffectsdb.
p.pos = sys->parent->pivot * p.pos;
else //if (sys->parent->parent != 0)
p.pos = sys->parent->mat * p.pos;
}
Vec3D dir = Vec3D(0.0f, 1.0f, 0.0f);
if (sys->parent->parent == 0) {
dir = sys->model->bones[sys->parent->parent].mrot * sys->parent->mrot * dir;
p.speed = dir.normalize() * spd * randfloat(0, var*2);
} else {
//dir = sys->parent->mrot * dir;
p.speed = dir.normalize() * (spd / 2);
}
p.down = Vec3D(0.0f, -1.0f, 0.0f); /* dir * -1.0f; */
p.life = 0;
p.maxlife = sys->lifespan.getValue(anim, time);
p.origin = p.pos;
p.tile = randint(0, sys->rows*sys->cols-1);
return p;
}
Particle SphereParticleEmitter::newParticle(int anim, int time)
{
Particle p;
float l = sys->areal.getValue(anim, time);
float w = sys->areaw.getValue(anim, time);
float spd = sys->speed.getValue(anim, time);
float var = sys->variation.getValue(anim, time);
float spr = sys->spread.getValue(anim, time);
// TODO: fix shpere emitters to work properly
// Spread should never be zero for sphere particles
float t;
if (spr == 0)
t = randfloat(0,2*PI);
else
t = randfloat(-spr,spr);
// Length should never technically be zero ?
if (l==0)
l = w;
//Vec3D bdir(0,0,0);
//Vec3D bdir(l*cosf(t), 0, w*sinf(t));
//Vec3D bdir(w*cosf(t), l*sinf(t), 0);
Vec3D bdir(w*cosf(t), 0.0f, l*sinf(t));
/*
float theta_range = sys->spread.getValue(anim, time);
float theta = -0.5f* theta_range + randfloat(0, theta_range);
Vec3D bdir(0, l*cosf(theta), w*sinf(theta));
float phi_range = sys->lat.getValue(anim, time);
float phi = randfloat(0, phi_range);
rotate(0,0, &bdir.z, &bdir.x, phi);
*/
p.pos = sys->pos + bdir;
p.pos = sys->parent->mat * p.pos;
if (bdir.lengthSquared()==0)
p.speed = Vec3D(0.0f, 0.0f, 0.0f);
else {
//Vec3D dir = sys->parent->mrot * (bdir.normalize());
Vec3D dir = bdir.normalize();
p.speed = dir.normalize() * spd * (1.0f + randfloat(-var,var)); // ?
//p.speed = dir.normalize() * spd * (1.0f + randfloat(-var, var));
}
//p.down = sys->parent->mrot * Vec3D(0,-1.0f,0);
p.down = Vec3D(0,-1.0f,0);
p.life = 0;
p.maxlife = sys->lifespan.getValue(anim, time);
p.origin = p.pos;
p.tile = randint(0, sys->rows*sys->cols-1);
return p;
}
void RibbonEmitter::init(MPQFile &f, ModelRibbonEmitterDef &mta, int *globals)
{
color.init(mta.color, f, globals);
opacity.init(mta.opacity, f, globals);
above.init(mta.above, f, globals);
below.init(mta.below, f, globals);
parent = model->bones + mta.bone;
int *texlist = (int*)(f.getBuffer() + mta.ofsTextures);
// just use the first texture for now; most models I've checked only had one
texture = model->textures[texlist[0]];
tpos = pos = fixCoordSystem(mta.pos);
//tpos = pos = (parent->mat * fixCoordSystem(mta.pos));
// TODO: figure out actual correct way to calculate length
// in BFD, res is 60 and len is 0.6, the trails are very short (too long here)
// in CoT, res and len are like 10 but the trails are supposed to be much longer (too short here)
numsegs = (int)mta.res;
seglen = mta.unk;
//length = mta.res * seglen;
length = mta.length;
// create first segment
RibbonSegment rs;
rs.pos = tpos; //parent->mat * tpos;
rs.len = ((parent->mat * pos) - tpos).length();
segs.push_back(rs);
}
void RibbonEmitter::setup(int anim, int time)
{
Vec3D ntpos = parent->mat * pos;
Vec3D ntup = parent->mat * (pos + Vec3D(0,0,1));
//Vec3D ntpos = pos;
//Vec3D ntup = (pos * Vec3D(0,0,1));
ntup -= ntpos;
ntup.normalize();
float dlen = (ntpos-tpos).length();
manim = anim;
mtime = time;
// move first segment
RibbonSegment &first = *segs.begin();
if (first.len > seglen) {
// add new segment
first.back = (tpos-ntpos).normalize();
first.len0 = first.len;
RibbonSegment newseg;
newseg.pos = ntpos;
newseg.up = ntup;
newseg.len = dlen;
segs.push_front(newseg);
} else {
first.up = ntup;
first.pos = ntpos;
first.len += dlen;
}
// kill stuff from the end
float l = 0;
bool erasemode = false;
for (std::list<RibbonSegment>::iterator it = segs.begin(); it != segs.end(); ++it) {
if (!erasemode) {
l += it->len;
//if (l > length) {
if (l > numsegs) {
//it->len = l - length;
//erasemode = true;
segs.clear();
break;
}
//++it;
} else {
segs.erase(it++);
}
}
tpos = ntpos;
tcolor = Vec4D(color.getValue(anim, time), opacity.getValue(anim, time));
tabove = above.getValue(anim, time);
tbelow = below.getValue(anim, time);
}
void RibbonEmitter::draw()
{
/*
// placeholders
glDisable(GL_TEXTURE_2D);
glDisable(GL_LIGHTING);
glColor4f(1,1,1,1);
glBegin(GL_TRIANGLES);
glVertex3fv(tpos);
glVertex3fv(tpos + Vec3D(1,1,0));
glVertex3fv(tpos + Vec3D(-1,1,0));
glEnd();
glEnable(GL_TEXTURE_2D);
glEnable(GL_LIGHTING);
*/
glBindTexture(GL_TEXTURE_2D, texture);
glEnable(GL_BLEND);
//glDisable(GL_LIGHTING);
glDisable(GL_ALPHA_TEST);
//glDisable(GL_CULL_FACE);
//glDepthMask(GL_FALSE);
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
glColor4fv(tcolor);
glBegin(GL_QUAD_STRIP);
std::list<RibbonSegment>::iterator it = segs.begin();
++it;
float l = 0;
for (; it != segs.end(); ++it) {
float u = l/length;
glTexCoord2f(u,0);
glVertex3fv(it->pos + tabove * it->up);
glTexCoord2f(u,1);
glVertex3fv(it->pos - tbelow * it->up);
l += it->len;
}
if (segs.size() > 1) {
// last segment...?
--it;
glTexCoord2f(1.0f, 0.0f);
glVertex3fv(it->pos + tabove * it->up + (it->len/it->len0) * it->back);
glTexCoord2f(1.0f, 1.0f);
glVertex3fv(it->pos - tbelow * it->up + (it->len/it->len0) * it->back);
}
glEnd();
//glColor4f(1.0f,1.0f,1.0f,1.0f);
//glEnable(GL_LIGHTING);
glEnable(GL_ALPHA_TEST);
glDisable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
//glDepthMask(GL_TRUE);
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -