Files
noggit-red/src/noggit/Particle.cpp
2025-01-17 21:25:24 +00:00

1016 lines
29 KiB
C++
Executable File

// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#include <noggit/Misc.h>
#include <noggit/Model.h>
#include <noggit/Particle.h>
#include <opengl/context.hpp>
#include <opengl/context.inl>
#include <opengl/shader.hpp>
#include <ClientFile.hpp>
#include <glm/vec3.hpp>
#include <list>
static const unsigned int MAX_PARTICLES = 10000;
template<class T>
T lifeRamp(float life, float mid, const T &a, const T &b, const T &c)
{
if (life <= mid) return math::interpolation::linear(life / mid, a, b);
else return math::interpolation::linear((life - mid) / (1.0f - mid), b, c);
}
ParticleSystem::ParticleSystem(Model* model_
, const BlizzardArchive::ClientFile& f
, const ModelParticleEmitterDef &mta
, int *globals
, Noggit::NoggitRenderContext context)
: model (model_)
, emitter_type(mta.EmitterType)
, emitter ( mta.EmitterType == 1 ? std::unique_ptr<ParticleEmitter> (std::make_unique<PlaneParticleEmitter>())
: mta.EmitterType == 2 ? std::unique_ptr<ParticleEmitter> (std::make_unique<SphereParticleEmitter>())
: std::unique_ptr<ParticleEmitter> (std::make_unique<PlaneParticleEmitter>())
)
, speed (mta.EmissionSpeed, f, globals)
, variation (mta.SpeedVariation, f, globals)
, spread (mta.VerticalRange, f, globals)
, lat (mta.HorizontalRange, f, globals)
, gravity (mta.Gravity, f, globals)
, lifespan (mta.Lifespan, f, globals)
, rate (mta.EmissionRate, f, globals)
, areal (mta.EmissionAreaLength, f, globals)
, areaw (mta.EmissionAreaWidth, f, globals)
, deacceleration (mta.Gravity2, f, globals)
, enabled (mta.en, f, globals)
, mid (0.5)
, slowdown (mta.p.slowdown)
, pos (fixCoordSystem(mta.pos))
, _texture_id (mta.texture)
, blend (mta.blend)
, order (mta.ParticleType > 0 ? -1 : 0)
, type (mta.ParticleType)
, manim (0)
, mtime (0)
, rows (mta.rows)
, cols (mta.cols)
, billboard (!(mta.flags & 4096))
, rem(0)
, parent (&model->bones[mta.bone])
, flags(mta.flags)
, tofs (misc::frand())
, _context(context)
{
glm::vec3 colors2[3];
memcpy(colors2, f.getBuffer() + mta.p.colors.ofsKeys, sizeof(glm::vec3) * 3);
for (size_t i = 0; i<3; ++i) {
float opacity = *reinterpret_cast<int16_t const*>(f.getBuffer() + mta.p.opacity.ofsKeys + i * 2);
colors[i] = glm::vec4(colors2[i].x / 255.0f, colors2[i].y / 255.0f, colors2[i].z / 255.0f, opacity / 32767.0f);
sizes[i] = (*reinterpret_cast<float const*>(f.getBuffer() + mta.p.sizes.ofsKeys + i * 4))*mta.p.scales[i];
}
//transform = mta.flags & 1024;
// init tiles
for (int i = 0; i<rows*cols; ++i) {
TexCoordSet tc;
initTile(tc.tc, i);
tiles.push_back(tc);
}
}
ParticleSystem::ParticleSystem(ParticleSystem const& other)
: model(other.model)
, emitter_type(other.emitter_type)
, emitter( emitter_type == 1 ? std::unique_ptr<ParticleEmitter>(std::make_unique<PlaneParticleEmitter>())
: emitter_type == 2 ? std::unique_ptr<ParticleEmitter>(std::make_unique<SphereParticleEmitter>())
: std::unique_ptr<ParticleEmitter>(std::make_unique<PlaneParticleEmitter>())
)
, speed(other.speed)
, variation(other.variation)
, spread(other.spread)
, lat(other.lat)
, gravity(other.gravity)
, lifespan(other.lifespan)
, rate(other.rate)
, areal(other.areal)
, areaw(other.areaw)
, deacceleration(other.deacceleration)
, enabled(other.enabled)
, colors(other.colors)
, sizes(other.sizes)
, mid(other.mid)
, slowdown(other.slowdown)
, pos(other.pos)
, _texture_id(other._texture_id)
, particles(other.particles)
, blend(other.blend)
, order(other.order)
, type(other.type)
, manim(other.manim)
, mtime(other.mtime)
, manimtime(other.manimtime)
, rows(other.rows)
, cols(other.cols)
, tiles(other.tiles)
, billboard(other.billboard)
, rem(other.rem)
, parent(other.parent)
, flags(other.flags)
, tofs(other.tofs)
, _context(other._context)
{
}
ParticleSystem::ParticleSystem(ParticleSystem&& other)
: model(other.model)
, emitter_type(other.emitter_type)
, emitter(std::move(other.emitter))
, speed(other.speed)
, variation(other.variation)
, spread(other.spread)
, lat(other.lat)
, gravity(other.gravity)
, lifespan(other.lifespan)
, rate(other.rate)
, areal(other.areal)
, areaw(other.areaw)
, deacceleration(other.deacceleration)
, enabled(other.enabled)
, colors(other.colors)
, sizes(other.sizes)
, mid(other.mid)
, slowdown(other.slowdown)
, pos(other.pos)
, _texture_id(other._texture_id)
, particles(other.particles)
, blend(other.blend)
, order(other.order)
, type(other.type)
, manim(other.manim)
, mtime(other.mtime)
, manimtime(other.manimtime)
, rows(other.rows)
, cols(other.cols)
, tiles(other.tiles)
, billboard(other.billboard)
, rem(other.rem)
, parent(other.parent)
, flags(other.flags)
, tofs(other.tofs)
, _context(other._context)
{
}
void ParticleSystem::initTile(glm::vec2 *tc, int num)
{
glm::vec2 otc[4];
glm::vec2 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, manimtime);
float deaccel = deacceleration.getValue(manim, mtime, manimtime);
// spawn new particles
if (emitter) {
float frate = rate.getValue(manim, mtime, manimtime);
float flife = 1.0f;
flife = lifespan.getValue(manim, mtime, manimtime);
float ftospawn = (dt * frate / flife) + rem;
if (ftospawn < 1.0f) {
rem = ftospawn;
if (rem<0)
rem = 0;
}
else {
int tospawn = (int)ftospawn;
if ((tospawn + particles.size()) > MAX_PARTICLES) // Error check to prevent the program from trying to load insane amounts of particles.
tospawn = static_cast<int>(particles.size()) - MAX_PARTICLES;
rem = ftospawn - static_cast<float>(tospawn);
float w = areal.getValue(manim, mtime, manimtime) * 0.5f;
float l = areaw.getValue(manim, mtime, manimtime) * 0.5f;
float spd = speed.getValue(manim, mtime, manimtime);
float var = variation.getValue(manim, mtime, manimtime);
float spr = spread.getValue(manim, mtime, manimtime);
float spr2 = lat.getValue(manim, mtime, manimtime);
bool en = true;
if (enabled.uses(manim))
en = enabled.getValue(manim, mtime, manimtime) != 0;
//rem = 0;
if (en) {
for (int i = 0; i<tospawn; ++i) {
Particle p = emitter->newParticle(this, manim, mtime, manimtime, w, l, spd, var, spr, spr2);
// sanity check:
//if (particles.size() < MAX_PARTICLES) // No need to check this every loop iteration. Already checked above.
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 - p.dir * deaccel * 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<glm::vec4>(rlife, mid, colors[0], colors[1], colors[2]);
// kill off old particles
if (rlife >= 1.0f)
{
it = particles.erase (it);
}
else
{
++it;
}
}
}
void ParticleSystem::setup(int anim, int time, int animtime)
{
manim = anim;
mtime = time;
manimtime = animtime;
}
void ParticleSystem::draw( glm::mat4x4 const& model_view
, OpenGL::Scoped::use_program& shader
, GLuint const& transform_vbo
, int instances_count
)
{
if (!_uploaded)
{
upload();
}
// setup blend mode
float alpha_test = -1.f;
switch (blend)
{
case 0:
gl.disable(GL_BLEND);
break;
case 1:
gl.enable(GL_BLEND);
gl.blendFunc(GL_SRC_COLOR, GL_ONE);
break;
case 2:
gl.enable(GL_BLEND);
gl.blendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
break;
case 3:
gl.disable(GL_BLEND);
alpha_test = 0.5f;
break;
case 4:
gl.enable(GL_BLEND);
gl.blendFunc(GL_SRC_ALPHA, GL_ONE);
break;
}
//model->_textures[_texture_id]->bind();
glm::vec3 vRight(1, 0, 0);
glm::vec3 vUp(0, 1, 0);
// position stuff
const float f = 1;//0.707106781f; // sqrt(2)/2
glm::vec3 bv0 = glm::vec3(-f, +f, 0);
glm::vec3 bv1 = glm::vec3(+f, +f, 0);
std::vector<std::uint16_t> indices;
std::vector<glm::vec3> vertices;
std::vector<glm::vec3> offsets;
std::vector<glm::vec4> colors_data;
std::vector<glm::vec2> texcoords;
std::uint16_t indice = 0;
if (billboard)
{
vRight = model_view[0];
vUp = model_view[1];
//vRight = glm::vec3(model_view[0][0], model_view[1][0], model_view[2][0]);
//vUp = glm::vec3(model_view[0][1], model_view[1][1], model_view[2][1]); // Spherical billboarding
//vUp = glm::vec3(0,1,0); // Cylindrical billboarding
}
auto add_quad_indices([] (std::vector<std::uint16_t>& indices, std::uint16_t& start)
{
indices.push_back(start + 0);
indices.push_back(start + 1);
indices.push_back(start + 2);
indices.push_back(start + 2);
indices.push_back(start + 3);
indices.push_back(start + 0);
start += 4;
});
/*
* type:
* 0 "normal" particle
* 1 large quad from the particle's origin to its position (used in Moonwell water effects)
* 2 seems to be the same as 0 (found some in the Deeprun Tram blinky-lights-sign thing)
*/
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
if (billboard)
{
//! \todo per-particle rotation in a non-expensive way?? :|
for (ParticleList::iterator it = particles.begin(); it != particles.end(); ++it)
{
if (tiles.size() - 1 < it->tile) // Alfred, 2009.08.07, error prevent
{
break;
}
const float size = it->size;// / 2;
texcoords.push_back(tiles[it->tile].tc[0]);
vertices.push_back(it->pos);
offsets.push_back(-(vRight + vUp) * size);
colors_data.push_back(it->color);
texcoords.push_back(tiles[it->tile].tc[1]);
vertices.push_back(it->pos);
offsets.push_back((vRight - vUp) * size);
colors_data.push_back(it->color);
texcoords.push_back(tiles[it->tile].tc[2]);
vertices.push_back(it->pos);
offsets.push_back((vRight + vUp) * size);
colors_data.push_back(it->color);
texcoords.push_back(tiles[it->tile].tc[3]);
vertices.push_back(it->pos);
offsets.push_back(-(vRight - vUp) * size);
colors_data.push_back(it->color);
add_quad_indices(indices, indice);
}
}
else
{
for (ParticleList::iterator it = particles.begin(); it != particles.end(); ++it)
{
if (tiles.size() - 1 < it->tile) // Alfred, 2009.08.07, error prevent
{
break;
}
texcoords.push_back(tiles[it->tile].tc[0]);
vertices.push_back(it->pos + it->corners[0] * it->size);
colors_data.push_back(it->color);
texcoords.push_back(tiles[it->tile].tc[1]);
vertices.push_back(it->pos + it->corners[1] * it->size);
colors_data.push_back(it->color);
texcoords.push_back(tiles[it->tile].tc[2]);
vertices.push_back(it->pos + it->corners[2] * it->size);
colors_data.push_back(it->color);
texcoords.push_back(tiles[it->tile].tc[3]);
vertices.push_back(it->pos + it->corners[3] * it->size);
colors_data.push_back(it->color);
add_quad_indices(indices, indice);
}
}
}
else if (type == 1)
{ // Sphere particles
// particles from origin to position
/*
bv0 = mbb * glm::vec3(0,-1.0f,0);
bv1 = mbb * glm::vec3(0,+1.0f,0);
bv0 = mbb * glm::vec3(-1.0f,0,0);
bv1 = mbb * glm::vec3(1.0f,0,0);
*/
for (ParticleList::iterator it = particles.begin(); it != particles.end(); ++it)
{
if (tiles.size() - 1 < it->tile) // Alfred, 2009.08.07, error prevent
{
break;
}
texcoords.push_back(tiles[it->tile].tc[0]);
vertices.push_back(it->pos + bv0 * it->size);
colors_data.push_back(it->color);
texcoords.push_back(tiles[it->tile].tc[1]);
vertices.push_back(it->pos + bv1 * it->size);
colors_data.push_back(it->color);
texcoords.push_back(tiles[it->tile].tc[2]);
vertices.push_back(it->origin + bv1 * it->size);
colors_data.push_back(it->color);
texcoords.push_back(tiles[it->tile].tc[3]);
vertices.push_back(it->origin + bv0 * it->size);
colors_data.push_back(it->color);
add_quad_indices(indices, indice);
}
}
gl.bufferData<GL_ARRAY_BUFFER, glm::vec3>(_vertices_vbo, vertices, GL_STREAM_DRAW);
gl.bufferData<GL_ARRAY_BUFFER, glm::vec4>(_colors_vbo, colors_data, GL_STREAM_DRAW);
gl.bufferData<GL_ARRAY_BUFFER, glm::vec2>(_texcoord_vbo, texcoords, GL_STREAM_DRAW);
gl.bufferData<GL_ELEMENT_ARRAY_BUFFER, std::uint16_t>(_indices_vbo, indices, GL_STREAM_DRAW);
shader.uniform("alpha_test", alpha_test);
shader.uniform("billboard", (int)billboard);
OpenGL::Scoped::vao_binder const _ (_vao);
{
OpenGL::Scoped::buffer_binder<GL_ARRAY_BUFFER> const vertices_binder (_vertices_vbo);
shader.attrib("position", 3, GL_FLOAT, GL_FALSE, 0, 0);
}
if(billboard)
{
gl.bufferData<GL_ARRAY_BUFFER, glm::vec3>(_offsets_vbo, offsets, GL_STREAM_DRAW);
OpenGL::Scoped::buffer_binder<GL_ARRAY_BUFFER> const offset_binder (_offsets_vbo);
shader.attrib("offset", 3, GL_FLOAT, GL_FALSE, 0, 0);
}
{
OpenGL::Scoped::buffer_binder<GL_ARRAY_BUFFER> const texcoord_binder (_texcoord_vbo);
shader.attrib("uv", 2, GL_FLOAT, GL_FALSE, 0, 0);
}
{
OpenGL::Scoped::buffer_binder<GL_ARRAY_BUFFER> const colors_binder (_colors_vbo);
shader.attrib("color", 4, GL_FLOAT, GL_FALSE, 0, 0);
}
{
OpenGL::Scoped::buffer_binder<GL_ARRAY_BUFFER> const transform_binder (transform_vbo);
shader.attrib("transform", 0, 1);
}
OpenGL::Scoped::buffer_binder<GL_ELEMENT_ARRAY_BUFFER> const indices_binder (_indices_vbo);
gl.drawElementsInstanced(GL_TRIANGLES, static_cast<GLsizei>(indices.size()), GL_UNSIGNED_SHORT, nullptr, instances_count);
}
void ParticleSystem::upload()
{
_vertex_array.upload();
_buffers.upload();
_uploaded = true;
}
void ParticleSystem::unload()
{
_vertex_array.unload();
_buffers.unload();
_uploaded = false;
}
namespace
{
//Generates the rotation matrix based on spread
glm::mat4x4 CalcSpreadMatrix(float Spread1, float Spread2, float w, float l)
{
int i, j;
float a[2], c[2], s[2];
glm::mat4x4 SpreadMat = glm::mat4x4(1);
a[0] = misc::randfloat(-Spread1, Spread1) / 2.0f;
a[1] = misc::randfloat(-Spread2, Spread2) / 2.0f;
/*SpreadMat.m[0][0]*=l;
SpreadMat.m[1][1]*=l;
SpreadMat.m[2][2]*=w;*/
for (i = 0; i<2; ++i)
{
c[i] = cos(a[i]);
s[i] = sin(a[i]);
}
{
glm::mat4x4 Temp = glm::mat4x4(1);
Temp[1][1] = c[0];
Temp[2][1] = s[0];
Temp[2][2] = c[0];
Temp[1][2] = -s[0];
SpreadMat = SpreadMat * Temp;
}
{
glm::mat4x4 Temp = glm::mat4x4(1);
Temp[0][0]= c[1];
Temp[1][0]= s[1];
Temp[1][1]= c[1];
Temp[0][1]= -s[1];
SpreadMat = SpreadMat*Temp;
}
float Size = std::abs(c[0])*l + std::abs(s[0])*w;
for (i = 0; i<3; ++i)
for (j = 0; j<3; j++)
SpreadMat[i][j] = SpreadMat[i][j] * Size;
return SpreadMat;
}
}
Particle PlaneParticleEmitter::newParticle(ParticleSystem* sys, int anim, int time, int animtime, float w, float l, float spd, float var, float spr, float /*spr2*/)
{
// Model Flags - *shrug* gotta write this down somewhere.
// 0x1 =
// 0x2 =
// 0x4 =
// 0x8 =
// 0x10 =
// 19 = 0x13 = blue ball in thunderfury = should be billboarded?
// Particle Flags
// 0x0 / 0 = Basilisk has no flags?
// 0x1 / 1 = Pretty much everything I know of except Basilisks have this flag.. Billboard?
// 0x2 / 2 =
// 0x4 / 4 =
// 0x8 / 8 =
// 0x10 / 16 = Position Relative to bone pivot?
// 0x20 / 32 =
// 0x40 / 64 =
// 0x80 / 128 =
// 0x100 / 256 =
// 0x200 / 512 =
// 0x400 / 1024 =
// 0x800 / 2048 =
// 0x1000/ 4096 =
// 0x0000/ 1593 = [1,8,16,32,512,1024]"Warp Storm" - aura type particle effect
// 0x419 / 1049 = [1,8,16,1024] Forest Wind shoulders
// 0x411 / 1041 = [1,16,1024] Halo
// 0x000 / 541 = [1,4,8,16,512] Staff glow
// 0x000 / 537 = "Warp Storm"
// 0x31 / 49 = [1,16,32] particle moving up?
// 0x00 / 41 = [1,8,32] Blood elf broom, dust spread out on the ground (X, Z axis)
// 0x1D / 29 = [1,4,8,16] particle being static
// 0x19 / 25 = [1,8,16] flame on weapon - move up/along the weapon
// 17 = 0x11 = [1,16] glow on weapon - static, random direction. - Aurastone Hammer
// 1 = 0x1 = perdition blade
// 4121 = water ele
// 4097 = water elemental
// 1041 = Transcendance Halo
// 1039 = water ele
Particle p;
//Spread Calculation
auto mrot = sys->parent->mrot*CalcSpreadMatrix(spr, spr, 1.0f, 1.0f);
if (sys->flags == 1041) { // Trans Halo
p.pos = sys->parent->mat * (glm::vec4(sys->pos,0) + glm::vec4(misc::randfloat(-l, l), 0, misc::randfloat(-w, w),0));
const float t = misc::randfloat(0.0f, 2.0f * glm::pi<float>());
p.pos = glm::vec3(0.0f, sys->pos.y + 0.15f, sys->pos.z) + glm::vec3(cos(t) / 8, 0.0f, sin(t) / 8); // Need to manually correct for the halo - why?
// 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
glm::vec3 dir(0.0f, 1.0f, 0.0f);
p.dir = dir;
p.speed = glm::normalize(dir) * spd * misc::randfloat(0, var);
}
else if (sys->flags == 25 && sys->parent->parent<1) { // Weapon Flame
p.pos = sys->parent->pivot + (sys->pos + glm::vec3(misc::randfloat(-l, l), misc::randfloat(-l, l), misc::randfloat(-w, w)));
glm::vec3 dir = mrot * glm::vec4(0.0f, 1.0f, 0.0f,0.0f);
p.dir = glm::normalize(dir);
//glm::vec3 dir = sys->model->bones[sys->parent->parent].mrot * sys->parent->mrot * glm::vec3(0.0f, 1.0f, 0.0f);
//p.speed = dir.normalize() * spd;
}
else if (sys->flags == 25 && sys->parent->parent > 0) { // Weapon with built-in Flame (Avenger lightsaber!)
p.pos = sys->parent->mat * (glm::vec4(sys->pos,0) + glm::vec4(misc::randfloat(-l, l), misc::randfloat(-l, l), misc::randfloat(-w, w),0));
glm::vec3 dir = glm::vec4(sys->parent->mat[1][0], sys->parent->mat[1][1], sys->parent->mat [1][2],0.0f) + glm::vec4(0.0f, 1.0f, 0.0f,0.0f);
p.speed = glm::normalize(dir) * spd * misc::randfloat(0, var * 2);
}
else if (sys->flags == 17 && sys->parent->parent<1) { // Weapon Glow
p.pos = sys->parent->pivot + (sys->pos + glm::vec3(misc::randfloat(-l, l), misc::randfloat(-l, l), misc::randfloat(-w, w)));
glm::vec3 dir = mrot * glm::vec4(0, 1, 0,0);
p.dir = glm::normalize(dir);
}
else {
p.pos = sys->pos + glm::vec3(misc::randfloat(-l, l), 0, misc::randfloat(-w, w));
p.pos = sys->parent->mat * glm::vec4(p.pos,0);
//glm::vec3 dir = mrot * glm::vec3(0,1,0);
glm::vec3 dir = sys->parent->mrot * glm::vec4(0, 1, 0,0);
p.dir = dir;//.normalize();
p.down = glm::vec3(0, -1.0f, 0); // dir * -1.0f;
p.speed = glm::normalize(dir) * spd * (1.0f + misc::randfloat(-var, var));
}
if (!sys->billboard) {
p.corners[0] = mrot * glm::vec4(-1, 0, +1, 0);
p.corners[1] = mrot * glm::vec4(+1, 0, +1, 0);
p.corners[2] = mrot * glm::vec4(+1, 0, -1, 0);
p.corners[3] = mrot * glm::vec4(-1, 0, -1, 0);
}
p.life = 0;
p.maxlife = sys->lifespan.getValue(anim, time, animtime);
p.origin = p.pos;
p.tile = misc::randint(0, sys->rows*sys->cols - 1);
return p;
}
Particle SphereParticleEmitter::newParticle(ParticleSystem* sys, int anim, int time, int animtime, float w, float l, float spd, float var, float spr, float spr2)
{
Particle p;
glm::vec3 dir;
float radius;
radius = misc::randfloat(0, 1);
// Old method
//float t = misc::randfloat(0,2*math::constants::pi);
// New
// Spread should never be zero for sphere particles ?
math::radians t (0);
if (spr == 0)
t._ = misc::randfloat(-glm::pi<float>(), glm::pi<float>());
else
t._ = misc::randfloat(-spr, spr);
//Spread Calculation
auto mrot = sys->parent->mrot*CalcSpreadMatrix(spr * 2, spr2 * 2, w, l);
// New
// Length should never technically be zero ?
//if (l==0)
// l = w;
// New method
// glm::vec3 bdir(w*math::cos(t), 0.0f, l*math::sin(t));
// --
//! \todo fix shpere emitters to work properly
/* // Old Method
//glm::vec3 bdir(l*math::cos(t), 0, w*math::sin(t));
//glm::vec3 bdir(0, w*math::cos(t), l*math::sin(t));
float theta_range = sys->spread.getValue(anim, time, animtime);
float theta = -0.5f* theta_range + misc::randfloat(0, theta_range);
glm::vec3 bdir(0, l*math::cos(theta), w*math::sin(theta));
float phi_range = sys->lat.getValue(anim, time, animtime);
float phi = misc::randfloat(0, phi_range);
rotate(0,0, &bdir.z, &bdir.x, phi);
*/
if (sys->flags == 57 || sys->flags == 313) { // Faith Halo
glm::vec3 bdir(w*glm::cos(t._)*1.6f, 0.0f, l*glm::sin(t._)*1.6f);
p.pos = sys->pos + bdir;
p.pos = sys->parent->mat * glm::vec4(p.pos,0);
if (glm::length(bdir) * glm::length(bdir) == 0)
p.speed = glm::vec3(0, 0, 0);
else {
dir = sys->parent->mrot * glm::vec4((glm::normalize(bdir)),0);//mrot * glm::vec3(0, 1.0f,0);
p.speed = glm::normalize(dir) * spd * (1.0f + misc::randfloat(-var, var)); // ?
}
}
else {
glm::vec3 bdir;
float temp;
bdir = mrot * glm::vec4(0, 1, 0, 0) * radius;
temp = bdir.z;
bdir.z = bdir.y;
bdir.y = temp;
p.pos = sys->parent->mat * glm::vec4(sys->pos,0) + glm::vec4(bdir,0);
//p.pos = sys->pos + bdir;
//p.pos = sys->parent->mat * p.pos;
if (!(glm::length(bdir) * glm::length(bdir)) && !(sys->flags & 0x100))
{
p.speed = glm::vec3(0, 0, 0);
dir = sys->parent->mrot * glm::vec4(0, 1, 0, 0);
}
else
{
if (sys->flags & 0x100)
dir = sys->parent->mrot * glm::vec4(0, 1, 0, 0);
else
dir = glm::normalize(bdir);
p.speed = glm::normalize(dir) * spd * (1.0f + misc::randfloat(-var, var)); // ?
}
}
p.dir = glm::normalize(dir);//mrot * glm::vec3(0, 1.0f,0);
p.down = glm::vec3(0, -1.0f, 0);
p.life = 0;
p.maxlife = sys->lifespan.getValue(anim, time, animtime);
p.origin = p.pos;
p.tile = misc::randint(0, sys->rows*sys->cols - 1);
return p;
}
RibbonEmitter::RibbonEmitter(Model* model_
, const BlizzardArchive::ClientFile &f
, ModelRibbonEmitterDef const& mta
, int *globals
, Noggit::NoggitRenderContext context)
: model (model_)
, color (mta.color, f, globals)
, opacity (mta.opacity, f, globals)
, above (mta.above, f, globals)
, below (mta.below, f, globals)
, parent (&model->bones[mta.bone])
, pos (fixCoordSystem(mta.pos))
, seglen (mta.length)
, length (mta.res * seglen)
// just use the first texture for now; most models I've checked only had one
, tpos (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)
, _context(context)
{
_texture_ids = Model::M2Array<uint16_t>(f, mta.ofsTextures, mta.nTextures);
_material_ids = Model::M2Array<uint16_t>(f, mta.ofsMaterials, mta.nMaterials);
// create first segment
segs.emplace_back(tpos, 0);
}
RibbonEmitter::RibbonEmitter(RibbonEmitter const& other)
: model(other.model)
, color(other.color)
, opacity(other.opacity)
, above(other.above)
, below(other.below)
, parent(other.parent)
, pos(other.pos)
, manim(other.manim)
, mtime(other.mtime)
, seglen(other.seglen)
, length(other.length)
, tpos(other.tpos)
, tcolor(other.tcolor)
, tabove(other.tabove)
, tbelow(other.tbelow)
, _texture_ids(other._texture_ids)
, _material_ids(other._material_ids)
, segs(other.segs)
, _context(other._context)
{
}
RibbonEmitter::RibbonEmitter(RibbonEmitter&& other)
: model(other.model)
, color(other.color)
, opacity(other.opacity)
, above(other.above)
, below(other.below)
, parent(other.parent)
, pos(other.pos)
, manim(other.manim)
, mtime(other.mtime)
, seglen(other.seglen)
, length(other.length)
, tpos(other.tpos)
, tcolor(other.tcolor)
, tabove(other.tabove)
, tbelow(other.tbelow)
, _texture_ids(other._texture_ids)
, _material_ids(other._material_ids)
, segs(other.segs)
, _context(other._context)
{
}
void RibbonEmitter::setup(int anim, int time, int animtime)
{
glm::vec3 ntpos = parent->mat * glm::vec4(pos,0);
glm::vec3 ntup = parent->mat * (glm::vec4(pos, 0) + glm::vec4(0, 0, 1,0));
ntup -= ntpos;
ntup = glm::normalize(ntup);
float dlen = glm::distance(ntpos, tpos);
manim = anim;
mtime = time;
// move first segment
RibbonSegment &first = *segs.begin();
if (first.len > seglen) {
// add new segment
first.back = glm::normalize((tpos - ntpos));
first.len0 = first.len;
RibbonSegment newseg (ntpos, dlen);
newseg.up = ntup;
segs.push_front(newseg);
}
else {
first.up = ntup;
first.pos = ntpos;
first.len += dlen;
}
// kill stuff from the end TODO: occasional crashes here
float l = 0;
bool erasemode = false;
for (std::list<RibbonSegment>::iterator it = segs.begin(); it != segs.end();)
{
if (!erasemode)
{
l += it->len;
if (l > length)
{
it->len = l - length;
erasemode = true;
}
++it;
}
else
{
it = segs.erase(it);
}
}
tpos = ntpos;
auto col = color.getValue(anim, time, animtime);
tcolor = glm::vec4(col.x,col.y,col.z, opacity.getValue(anim, time, animtime));
tabove = above.getValue(anim, time, animtime);
tbelow = below.getValue(anim, time, animtime);
}
void RibbonEmitter::draw( OpenGL::Scoped::use_program& shader
, GLuint const& transform_vbo
, int instances_count
)
{
if (!_uploaded)
{
upload();
}
std::vector<std::uint16_t> indices;
std::vector<glm::vec3> vertices;
std::vector<glm::vec2> texcoords;
//model->_textures[_texture_ids[0]]->bind();
gl.enable(GL_BLEND);
shader.uniform("color", tcolor);
std::uint16_t indice = 0;
auto add_quad_indices([] (std::vector<std::uint16_t>& indices, std::uint16_t& start)
{
indices.push_back(start + 0);
indices.push_back(start + 1);
indices.push_back(start + 2);
indices.push_back(start + 2);
indices.push_back(start + 1);
indices.push_back(start + 3);
start += 2;
});
std::list<RibbonSegment>::iterator it = segs.begin();
float l = 0;
for (; it != segs.end(); ++it)
{
float u = l / length;
texcoords.emplace_back(u, 0);
vertices.push_back(it->pos + tabove * it->up);
texcoords.emplace_back(u, 1);
vertices.push_back(it->pos - tbelow * it->up);
l += it->len;
add_quad_indices(indices, indice);
}
if (segs.size() > 1)
{
// last segment...?
--it;
texcoords.emplace_back(1, 0);
vertices.push_back(it->pos + tabove * it->up + (it->len / it->len0) * it->back);
texcoords.emplace_back(1, 1);
vertices.push_back(it->pos - tbelow * it->up + (it->len / it->len0) * it->back);
}
gl.bufferData<GL_ARRAY_BUFFER, glm::vec3>(_vertices_vbo, vertices, GL_STREAM_DRAW);
gl.bufferData<GL_ARRAY_BUFFER, glm::vec2>(_texcoord_vbo, texcoords, GL_STREAM_DRAW);
gl.bufferData<GL_ELEMENT_ARRAY_BUFFER, std::uint16_t>(_indices_vbo, indices, GL_STREAM_DRAW);
OpenGL::Scoped::vao_binder const _(_vao);
{
OpenGL::Scoped::buffer_binder<GL_ARRAY_BUFFER> const vertices_binder(_vertices_vbo);
shader.attrib("position", 3, GL_FLOAT, GL_FALSE, 0, 0);
}
{
OpenGL::Scoped::buffer_binder<GL_ARRAY_BUFFER> const texcoord_binder(_texcoord_vbo);
shader.attrib("uv", 2, GL_FLOAT, GL_FALSE, 0, 0);
}
{
OpenGL::Scoped::buffer_binder<GL_ARRAY_BUFFER> const transform_binder(transform_vbo);
shader.attrib("transform", 0, 1);
}
OpenGL::Scoped::buffer_binder<GL_ELEMENT_ARRAY_BUFFER> const indices_binder(_indices_vbo);
gl.drawElementsInstanced(GL_TRIANGLES, static_cast<GLsizei>(indices.size()), GL_UNSIGNED_SHORT, nullptr, instances_count);
}
void RibbonEmitter::upload()
{
_vertex_array.upload();
_buffers.upload();
_uploaded = true;
}
void RibbonEmitter::unload()
{
_vertex_array.unload();
_buffers.unload();
_uploaded = false;
}