nuclear@0: #include nuclear@0: #include "psyspp.h" nuclear@0: #include "sdrman.h" nuclear@0: #include "logger.h" nuclear@0: #include "mesh.h" // just for the attrib enums nuclear@0: #include "unistate.h" nuclear@0: #include "datapath.h" nuclear@0: #include "texman.h" nuclear@0: #include "texgen.h" nuclear@0: nuclear@15: using namespace goatgfx; nuclear@15: nuclear@0: static void pdraw_start(const psys_emitter *em, void *cls); nuclear@0: static void pdraw(const psys_emitter *em, const psys_particle *part, void *cls); nuclear@0: static void pdraw_end(const psys_emitter *em, void *cls); nuclear@0: nuclear@0: static unsigned int psys_load_texture(const char *fname, void *cls); nuclear@0: nuclear@0: ParticleSystemAttributes::ParticleSystemAttributes() nuclear@0: { nuclear@0: tex = 0; nuclear@0: own_psattr = true; nuclear@0: psattr = new psys_attributes; nuclear@0: psys_init_attr(psattr); nuclear@0: } nuclear@0: nuclear@0: ParticleSystemAttributes::ParticleSystemAttributes(psys_attributes *psattr) nuclear@0: { nuclear@0: tex = 0; nuclear@0: own_psattr = false; nuclear@0: this->psattr = psattr; nuclear@0: } nuclear@0: nuclear@0: ParticleSystemAttributes::~ParticleSystemAttributes() nuclear@0: { nuclear@0: if(own_psattr) { nuclear@0: psys_destroy_attr(psattr); nuclear@0: delete psattr; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: ParticleSystemAttributes::ParticleSystemAttributes(const ParticleSystemAttributes &rhs) nuclear@0: { nuclear@0: own_psattr = true; nuclear@0: tex = rhs.tex; nuclear@0: psattr = new psys_attributes; nuclear@0: psys_init_attr(psattr); nuclear@0: nuclear@0: psys_copy_attr(psattr, rhs.psattr); nuclear@0: } nuclear@0: nuclear@0: ParticleSystemAttributes &ParticleSystemAttributes::operator =(const ParticleSystemAttributes &rhs) nuclear@0: { nuclear@0: if(&rhs != this) { nuclear@0: tex = rhs.tex; nuclear@0: psys_copy_attr(psattr, rhs.psattr); nuclear@0: } nuclear@0: return *this; nuclear@0: } nuclear@0: nuclear@0: bool ParticleSystemAttributes::load(const char *fname) nuclear@0: { nuclear@0: psys_texture_loader(psys_load_texture, 0, this); nuclear@15: return psys_load_attr(psattr, goatgfx::datafile_path(fname).c_str()) != -1; nuclear@0: } nuclear@0: nuclear@0: bool ParticleSystemAttributes::load(FILE *fp) nuclear@0: { nuclear@0: psys_texture_loader(psys_load_texture, 0, this); nuclear@0: return psys_load_attr_stream(psattr, fp) != -1; nuclear@0: } nuclear@0: nuclear@0: bool ParticleSystemAttributes::save(const char *fname) const nuclear@0: { nuclear@0: return psys_save_attr(psattr, fname) != -1; nuclear@0: } nuclear@0: nuclear@0: bool ParticleSystemAttributes::save(FILE *fp) const nuclear@0: { nuclear@0: return psys_save_attr_stream(psattr, fp) != -1; nuclear@0: } nuclear@0: nuclear@0: void ParticleSystemAttributes::set_part_color(const Vector3 &color, float t) nuclear@0: { nuclear@0: psys_set_value3(&psattr->part_attr.color, (anm_time_t)(t * 1000.0), v3_cons(color.x, color.y, color.z)); nuclear@0: } nuclear@0: nuclear@0: void ParticleSystemAttributes::set_part_alpha(float alpha, float t) nuclear@0: { nuclear@0: psys_set_value(&psattr->part_attr.alpha, (anm_time_t)(t * 1000.0), alpha); nuclear@0: } nuclear@0: nuclear@0: void ParticleSystemAttributes::set_part_scale(float size, float t) nuclear@0: { nuclear@0: psys_set_value(&psattr->part_attr.size, (anm_time_t)(t * 1000.0), size); nuclear@0: } nuclear@0: nuclear@0: nuclear@0: // emmiter attributes nuclear@0: void ParticleSystemAttributes::set_texture(Texture *tex) nuclear@0: { nuclear@0: this->tex = tex; nuclear@0: psattr->tex = tex->get_id(); nuclear@0: } nuclear@0: nuclear@0: Texture *ParticleSystemAttributes::get_texture() const nuclear@0: { nuclear@0: return tex; nuclear@0: } nuclear@0: nuclear@0: void ParticleSystemAttributes::set_spawn_range(const Vector3 &range, long tm) nuclear@0: { nuclear@0: psys_set_value3(&psattr->spawn_range, ANM_MSEC2TM(tm), v3_cons(range.x, range.y, range.z)); nuclear@0: } nuclear@0: nuclear@0: void ParticleSystemAttributes::set_spawn_rate(float rate, long tm) nuclear@0: { nuclear@0: psys_set_value(&psattr->rate, ANM_MSEC2TM(tm), rate); nuclear@0: } nuclear@0: nuclear@0: float ParticleSystemAttributes::get_spawn_rate(long tm) const nuclear@0: { nuclear@0: return psys_get_value(&psattr->rate, ANM_MSEC2TM(tm)); nuclear@0: } nuclear@0: nuclear@0: void ParticleSystemAttributes::set_life(float life, float range, long tm) nuclear@0: { nuclear@0: psys_set_anm_rnd(&psattr->life, ANM_MSEC2TM(tm), life, range); nuclear@0: } nuclear@0: nuclear@0: void ParticleSystemAttributes::set_size(float sz, float range, long tm) nuclear@0: { nuclear@0: psys_set_anm_rnd(&psattr->size, ANM_MSEC2TM(tm), sz, range); nuclear@0: } nuclear@0: nuclear@0: void ParticleSystemAttributes::set_spawn_dir(const Vector3 &dir, const Vector3 &range, long tm) nuclear@0: { nuclear@0: psys_set_anm_rnd3(&psattr->dir, ANM_MSEC2TM(tm), v3_cons(dir.x, dir.y, dir.z), v3_cons(range.x, range.y, range.z)); nuclear@0: } nuclear@0: nuclear@0: void ParticleSystemAttributes::set_gravity(const Vector3 &grav, long tm) nuclear@0: { nuclear@0: psys_set_value3(&psattr->grav, ANM_MSEC2TM(tm), v3_cons(grav.x, grav.y, grav.z)); nuclear@0: } nuclear@0: nuclear@0: void ParticleSystemAttributes::set_drag(float drag) nuclear@0: { nuclear@0: psattr->drag = drag; nuclear@0: } nuclear@0: nuclear@0: void ParticleSystemAttributes::set_particle_limit(int lim) nuclear@0: { nuclear@0: psattr->max_particles = lim; nuclear@0: } nuclear@0: nuclear@0: nuclear@0: // ---- ParticleSystem ---- nuclear@0: nuclear@0: ParticleSystem::ParticleSystem() nuclear@0: : attr(&psys.attr) nuclear@0: { nuclear@0: psys_init(&psys); nuclear@0: psys_draw_func(&psys, pdraw, pdraw_start, pdraw_end, (void*)this); nuclear@0: start_time = LONG_MIN; nuclear@0: last_upd_time = LONG_MIN; nuclear@0: } nuclear@0: nuclear@0: ParticleSystem::~ParticleSystem() nuclear@0: { nuclear@0: psys_destroy(&psys); nuclear@0: } nuclear@0: nuclear@0: void ParticleSystem::set_start_time(long tm) nuclear@0: { nuclear@0: start_time = tm; nuclear@0: } nuclear@0: nuclear@0: bool ParticleSystem::is_active() const nuclear@0: { nuclear@0: float rate = attr.get_spawn_rate(last_upd_time); nuclear@0: return psys.pcount > 0 || last_upd_time == 0;// || rate > 0.0; nuclear@0: } nuclear@0: nuclear@0: ParticleSystemAttributes *ParticleSystem::get_attr() nuclear@0: { nuclear@0: return &attr; nuclear@0: } nuclear@0: nuclear@0: const ParticleSystemAttributes *ParticleSystem::get_attr() const nuclear@0: { nuclear@0: return &attr; nuclear@0: } nuclear@0: nuclear@0: void ParticleSystem::set_attr(const ParticleSystemAttributes &pattr) nuclear@0: { nuclear@0: attr = pattr; nuclear@0: } nuclear@0: nuclear@0: bool ParticleSystem::load(const char *fname) nuclear@0: { nuclear@0: psys_texture_loader(psys_load_texture, 0, &attr); nuclear@0: return attr.load(fname); nuclear@0: } nuclear@0: nuclear@0: bool ParticleSystem::save(const char *fname) const nuclear@0: { nuclear@0: return attr.save(fname); nuclear@0: } nuclear@0: nuclear@0: void ParticleSystem::update(long tm) nuclear@0: { nuclear@0: if(start_time > LONG_MIN) { nuclear@0: tm -= start_time; nuclear@0: } nuclear@0: nuclear@0: Matrix4x4 xform; nuclear@0: get_xform(tm, &xform); nuclear@0: nuclear@0: Vector3 pos = Vector3(0, 0, 0).transformed(xform); nuclear@0: nuclear@0: psys_set_pos(&psys, v3_cons(pos.x, pos.y, pos.z), 0); nuclear@0: psys_update(&psys, (double)tm / 1000.0); nuclear@0: nuclear@0: last_upd_time = tm; nuclear@0: } nuclear@0: nuclear@0: void ParticleSystem::draw() const nuclear@0: { nuclear@0: psys_draw(&psys); nuclear@0: } nuclear@0: nuclear@0: // ---- particle drawing ---- nuclear@0: struct PVertex { nuclear@0: Vector4 color; nuclear@0: Vector3 pos; nuclear@0: Vector2 texcoord; nuclear@0: }; nuclear@0: nuclear@0: nuclear@0: #define USE_VBO nuclear@0: #define USE_IBO nuclear@0: nuclear@0: nuclear@0: #define MAX_DRAW_PART 256 nuclear@0: #define MAX_PVERTS (MAX_DRAW_PART * 4) nuclear@0: static PVertex *pvarr, *pvptr; nuclear@0: nuclear@0: // double-buffered vbo set, write on one, while drawing from the other nuclear@0: nuclear@0: #ifdef USE_VBO nuclear@0: static unsigned int vbo[2]; nuclear@0: static int cur_buf; // current write vbo nuclear@0: #endif nuclear@0: static int num_buffered; // number of particles bufferd, will flush when >= MAX_DRAW_PART nuclear@0: nuclear@0: // ok so the index array is constant, regardless of the particle system nuclear@0: // so this is a static index buffer created in init_particle_draw which should nuclear@0: // be called once. nuclear@0: #define MAX_PVIDX (MAX_DRAW_PART * 6) nuclear@0: #ifdef USE_IBO nuclear@0: static unsigned int ibo; nuclear@0: #endif nuclear@0: unsigned int *pvidx; nuclear@0: nuclear@0: static ShaderProg *psdr; // particle shader nuclear@0: static Texture2D *blank_tex; nuclear@0: nuclear@0: static inline void init_particle_draw() nuclear@0: { nuclear@0: static bool done_init; nuclear@0: if(done_init) { nuclear@0: return; // once nuclear@0: } nuclear@0: nuclear@0: pvidx = new unsigned int[MAX_PVIDX]; nuclear@0: unsigned int *ptr = pvidx; nuclear@0: nuclear@0: static const unsigned int idxoffs[] = { 0, 1, 2, 0, 2, 3 }; nuclear@0: nuclear@0: for(int i=0; iset_image(*img); nuclear@0: delete img; nuclear@0: nuclear@0: done_init = true; nuclear@0: } nuclear@0: nuclear@0: static void pdraw_flush() nuclear@0: { nuclear@0: #ifdef USE_VBO nuclear@0: // assuming vbo[cur_buf] is bound nuclear@0: glUnmapBuffer(GL_ARRAY_BUFFER); nuclear@0: #endif nuclear@0: nuclear@0: // draw from the bound buffer 6 indices per particle nuclear@0: #ifdef USE_IBO nuclear@0: glDrawElements(GL_TRIANGLES, num_buffered * 6, GL_UNSIGNED_INT, 0); nuclear@0: #else nuclear@0: glDrawElements(GL_TRIANGLES, num_buffered * 6, GL_UNSIGNED_INT, pvidx); nuclear@0: #endif nuclear@0: num_buffered = 0; nuclear@0: nuclear@0: #ifdef USE_VBO nuclear@0: // map the next buffer (write buffer) while the previous is drawing nuclear@0: cur_buf = (cur_buf + 1) & 1; nuclear@0: glBindBuffer(GL_ARRAY_BUFFER, vbo[cur_buf]); nuclear@0: pvarr = (PVertex*)glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY); nuclear@0: #endif nuclear@0: pvptr = pvarr; nuclear@0: } nuclear@0: nuclear@0: static void pdraw_start(const psys_emitter *em, void *cls) nuclear@0: { nuclear@0: ParticleSystem *ps = (ParticleSystem*)cls; nuclear@0: nuclear@0: init_particle_draw(); nuclear@0: nuclear@0: num_buffered = 0; nuclear@0: nuclear@0: #ifdef USE_IBO nuclear@0: // bind the particle index buffer which is static nuclear@0: glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo); nuclear@0: #endif nuclear@0: nuclear@0: #ifdef USE_VBO nuclear@0: // map the current write buffer nuclear@0: glBindBuffer(GL_ARRAY_BUFFER, vbo[cur_buf]); nuclear@0: pvarr = (PVertex*)glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY); nuclear@0: #endif nuclear@0: pvptr = pvarr; nuclear@0: nuclear@0: Texture *tex = ps->get_attr()->get_texture(); nuclear@0: if(tex) { nuclear@0: tex->bind(); nuclear@0: } else { nuclear@0: blank_tex->bind(); nuclear@0: } nuclear@0: psdr->bind(); nuclear@0: nuclear@0: glDisable(GL_DEPTH_TEST); nuclear@0: glDisable(GL_CULL_FACE); nuclear@0: glEnable(GL_BLEND); nuclear@0: glBlendFunc(GL_SRC_ALPHA, GL_ONE); nuclear@0: glDepthMask(0); nuclear@0: nuclear@0: glEnableVertexAttribArray(MESH_ATTR_VERTEX); nuclear@0: glEnableVertexAttribArray(MESH_ATTR_COLOR); nuclear@0: glEnableVertexAttribArray(MESH_ATTR_TEXCOORD); nuclear@0: nuclear@0: #ifdef USE_VBO nuclear@0: glVertexAttribPointer(MESH_ATTR_VERTEX, 3, GL_FLOAT, GL_FALSE, sizeof *pvarr, (void*)((char*)&pvarr->pos - (char*)pvarr)); nuclear@0: glVertexAttribPointer(MESH_ATTR_COLOR, 4, GL_FLOAT, GL_TRUE, sizeof *pvarr, (void*)((char*)&pvarr->color - (char*)pvarr)); nuclear@0: glVertexAttribPointer(MESH_ATTR_TEXCOORD, 2, GL_FLOAT, GL_FALSE, sizeof *pvarr, (void*)((char*)&pvarr->texcoord - (char*)pvarr)); nuclear@0: #else nuclear@0: glVertexAttribPointer(MESH_ATTR_VERTEX, 3, GL_FLOAT, GL_FALSE, sizeof *pvarr, (void*)&pvarr->pos); nuclear@0: glVertexAttribPointer(MESH_ATTR_COLOR, 4, GL_FLOAT, GL_TRUE, sizeof *pvarr, (void*)&pvarr->color); nuclear@0: glVertexAttribPointer(MESH_ATTR_TEXCOORD, 2, GL_FLOAT, GL_FALSE, sizeof *pvarr, (void*)&pvarr->texcoord); nuclear@0: #endif nuclear@0: } nuclear@0: nuclear@0: static void pdraw(const psys_emitter *em, const psys_particle *part, void *cls) nuclear@0: { nuclear@0: ParticleSystem *ps = (ParticleSystem*)cls; nuclear@0: nuclear@0: static const Vector3 pv[] = { nuclear@0: Vector3(-0.5, -0.5, 0), nuclear@0: Vector3(0.5, -0.5, 0), nuclear@0: Vector3(0.5, 0.5, 0), nuclear@0: Vector3(-0.5, 0.5, 0) nuclear@0: }; nuclear@0: static const Vector2 tex[] = { nuclear@0: Vector2(0, 0), Vector2(0, 1), Vector2(1, 1), Vector2(1, 0) nuclear@0: }; nuclear@0: Vector4 color = Vector4(part->color.x, part->color.y, part->color.z, part->alpha); nuclear@0: nuclear@0: for(int i=0; i<4; i++) { nuclear@0: pvptr->color = color; nuclear@0: pvptr->pos = pv[i] * part->size + part->pos; nuclear@0: pvptr->texcoord = tex[i]; nuclear@0: pvptr++; nuclear@0: } nuclear@0: // XXX we don't need billboarding for this game, so don't bother nuclear@0: nuclear@0: // if we reached the maximum number of buffered particles, draw them nuclear@0: if(++num_buffered >= MAX_DRAW_PART) { nuclear@0: pdraw_flush(); // this will reset the counter nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: static void pdraw_end(const psys_emitter *em, void *cls) nuclear@0: { nuclear@0: // if we have leftover particles buffered, draw them before returning nuclear@0: if(num_buffered) { nuclear@0: pdraw_flush(); nuclear@0: } nuclear@0: nuclear@0: // cleanup nuclear@0: glDisableVertexAttribArray(MESH_ATTR_VERTEX); nuclear@0: glDisableVertexAttribArray(MESH_ATTR_COLOR); nuclear@0: glDisableVertexAttribArray(MESH_ATTR_TEXCOORD); nuclear@0: nuclear@0: #ifdef USE_VBO nuclear@0: glUnmapBuffer(GL_ARRAY_BUFFER); nuclear@0: glBindBuffer(GL_ARRAY_BUFFER, 0); nuclear@0: #endif nuclear@0: #ifdef USE_IBO nuclear@0: glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); nuclear@0: #endif nuclear@0: nuclear@0: glDepthMask(1); nuclear@0: glDisable(GL_BLEND); nuclear@0: nuclear@0: glEnable(GL_DEPTH_TEST); nuclear@0: glEnable(GL_CULL_FACE); nuclear@0: } nuclear@0: nuclear@0: static unsigned int psys_load_texture(const char *fname, void *cls) nuclear@0: { nuclear@0: ParticleSystemAttributes *attr = (ParticleSystemAttributes*)cls; nuclear@0: nuclear@0: Texture *tex = texset.get(fname); nuclear@0: if(tex) { nuclear@0: attr->set_texture(tex); nuclear@0: return tex->get_id(); nuclear@0: } nuclear@0: return 0; nuclear@0: }