@@ -0,0 +1,150 @@
/*
* gui/ParticleManager.cpp
*
* This file is part of Leges Motus, a networked, 2D shooter set in zero gravity.
*
* Copyright 2009-2011 Andrew Ayer, Nathan Partlan, Jeffrey Pfau
*
* Leges Motus is free and open source software. You may redistribute it and/or
* modify it under the terms of version 2, or (at your option) version 3, of the
* GNU General Public License (GPL), as published by the Free Software Foundation.
*
* Leges Motus is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the full text of the GNU General Public License for
* further detail.
*
* For a full copy of the GNU General Public License, please see the COPYING file
* in the root of the source code tree. You may also retrieve a copy from
* <http://www.gnu.org/licenses/gpl-2.0.txt>, or request a copy by writing to the
* Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*
*/

#include "ParticleManager.hpp"
#include "ParticleEmitter.hpp"
#include "Particle.hpp"

using namespace LM;
using namespace std;

ParticleManager::ParticleManager(Widget* parent, int num_initial_particles, bool can_expand_pool) : Widget(parent) {
for (int i = 0; i < num_initial_particles; i++) {
m_free_particles.push_back(new Particle());
}

m_can_expand_pool = can_expand_pool;

m_total_particles = num_initial_particles;
}

ParticleManager::~ParticleManager() {
list<ParticleEmitter*>::iterator emitterator;
list<Particle*>::iterator it;

// Free all emitters
for (emitterator = m_emitters.begin(); emitterator != m_emitters.end(); emitterator++) {
for (it = (*emitterator)->get_particles().begin(); it != (*emitterator)->get_particles().end(); it++) {
m_free_particles.push_back((*it));
}

(*emitterator)->clear();

delete (*emitterator);
}

m_emitters.clear();

// Free all particles
for (it = m_free_particles.begin(); it != m_free_particles.end(); it++) {
delete (*it);
}
m_free_particles.clear();
}

void ParticleManager::add_emitter(ParticleEmitter* emitter) {
if (!emitter_exists(emitter)) {
m_emitters.push_back(emitter);
}
}

void ParticleManager::remove_emitter(ParticleEmitter* emitter) {
if (emitter_exists(emitter)) {
m_emitters.remove(emitter);

list<Particle*>::iterator it;

for (it = emitter->get_particles().begin(); it != emitter->get_particles().end(); it++) {
m_free_particles.push_back((*it));
}

emitter->clear();
}
}

bool ParticleManager::emitter_exists(ParticleEmitter* emitter) {
list<ParticleEmitter*>::iterator it;

for (it = m_emitters.begin(); it != m_emitters.end(); it++) {
if ((*it) == emitter) {
return true;
}
}

return false;
}

Particle* ParticleManager::request_particle() {
if (m_free_particles.size() == 0) {
if (!m_can_expand_pool) {
return NULL;
}

m_total_particles *= 2;
for (int i = 0; i < m_total_particles; i++) {
m_free_particles.push_back(new Particle());
}
}

Particle* to_return = m_free_particles.back();

m_free_particles.pop_back();

return to_return;
}

void ParticleManager::free_particle(Particle* particle) {
m_free_particles.push_back(particle);
}

void ParticleManager::update(uint64_t timediff) {
list<ParticleEmitter*>::iterator it = m_emitters.begin();

while (it != m_emitters.end()) {
// Update the emitter, then remove it if it is ready
if (!(*it)->update(timediff)) {
list<Particle*>::iterator it2;
for (it2 = (*it)->get_particles().begin(); it2 != (*it)->get_particles().end(); it2++) {
m_free_particles.push_back((*it2));
}

(*it)->clear();

delete *it;
m_emitters.erase(it++);
} else {
it++;
}
}
}

void ParticleManager::draw(DrawContext* context) const {
list<ParticleEmitter*>::const_iterator it;

for (it = m_emitters.begin(); it != m_emitters.end(); it++) {
//m_context->bind_shader_set(m_emitters->get_shader_program());
(*it)->draw(context);
//m_context->unbind_shader_set();
}
}
@@ -0,0 +1,57 @@
/*
* gui/ParticleManager.hpp
*
* This file is part of Leges Motus, a networked, 2D shooter set in zero gravity.
*
* Copyright 2009-2011 Andrew Ayer, Nathan Partlan, Jeffrey Pfau
*
* Leges Motus is free and open source software. You may redistribute it and/or
* modify it under the terms of version 2, or (at your option) version 3, of the
* GNU General Public License (GPL), as published by the Free Software Foundation.
*
* Leges Motus is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the full text of the GNU General Public License for
* further detail.
*
* For a full copy of the GNU General Public License, please see the COPYING file
* in the root of the source code tree. You may also retrieve a copy from
* <http://www.gnu.org/licenses/gpl-2.0.txt>, or request a copy by writing to the
* Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*
*/

#include "DrawContext.hpp"
#include "Widget.hpp"
#include <list>

#ifndef LM_GUI_PARTICLEMANAGER_HPP
#define LM_GUI_PARTICLEMANAGER_HPP

namespace LM {
class Particle;
class ParticleEmitter;

class ParticleManager : public Widget {
private:
std::list<ParticleEmitter*> m_emitters;
std::list<Particle*> m_free_particles;
bool m_can_expand_pool;
int m_total_particles;
public:
ParticleManager(Widget* parent, int num_initial_particles, bool can_expand_pool);
virtual ~ParticleManager();

void add_emitter(ParticleEmitter* emitter);
void remove_emitter(ParticleEmitter* emitter);
bool emitter_exists(ParticleEmitter* emitter);
void update(uint64_t timediff);
virtual void draw(DrawContext* ctx) const;

Particle* request_particle();
void free_particle(Particle* particle);
};
}

#endif
@@ -0,0 +1,134 @@
/*
* gui/SimpleRadialEmitter.cpp
*
* This file is part of Leges Motus, a networked, 2D shooter set in zero gravity.
*
* Copyright 2009-2011 Andrew Ayer, Nathan Partlan, Jeffrey Pfau
*
* Leges Motus is free and open source software. You may redistribute it and/or
* modify it under the terms of version 2, or (at your option) version 3, of the
* GNU General Public License (GPL), as published by the Free Software Foundation.
*
* Leges Motus is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the full text of the GNU General Public License for
* further detail.
*
* For a full copy of the GNU General Public License, please see the COPYING file
* in the root of the source code tree. You may also retrieve a copy from
* <http://www.gnu.org/licenses/gpl-2.0.txt>, or request a copy by writing to the
* Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*
*/

#include "SimpleRadialEmitter.hpp"
#include "Particle.hpp"
#include <math.h>

using namespace LM;
using namespace std;

SimpleRadialEmitter::SimpleRadialEmitter(ParticleManager* manager, Point center, Image* image, DrawContext::BlendMode mode) : ParticleEmitter(manager, center, image, mode) {
m_lifetime = 0;
m_spawned_total = 0;
}

SimpleRadialEmitter::~SimpleRadialEmitter() {
delete m_settings;
}

void SimpleRadialEmitter::init(const SimpleRadialEmitterSettings* settings) {
m_settings = settings;

m_leftover_diff = 0;
m_lifetime = 0;
}

bool SimpleRadialEmitter::update(uint64_t timediff) {
timediff += m_leftover_diff;

if (timediff <= 0) {
return true;
}

m_leftover_diff = 0;
int num_to_spawn = int(m_settings->spawn_per_second * timediff/1000.0 + rand() % m_settings->spawn_variance * timediff/1000.0);

if (num_to_spawn == 0) {
m_leftover_diff += timediff;
}

// Spawn new particles
if ((m_settings->max_spawn <= 0 || m_spawned_total < m_settings->max_spawn) &&
(m_settings->emitter_stop_spawning_millis <= 0 || m_settings->emitter_stop_spawning_millis > m_lifetime)) {
for (int i = 0; i < num_to_spawn; i++) {
Particle* particle = request_particle();

init_particle(particle);

m_particles.push_back(particle);
m_spawned_total++;
if (particle == NULL) {
DEBUG("Could not spawn particle!");
break;
}
}
}

// Update existing particles
list<Particle*>::iterator it = m_particles.begin();
while (it != m_particles.end()) {
Particle* particle = *it;

if (particle->m_energy_left < timediff) {
free_particle(particle, it++);
continue;
}
particle->m_energy_left -= timediff;

particle->m_color.a = particle->m_energy_left / float(particle->m_initial_energy);

particle->m_prev_pos = particle->m_pos;
particle->m_vel += m_settings->global_force * timediff/1000.0f;
particle->m_pos += particle->m_vel * timediff/1000.0f;

it++;
}

// Check if we're done spawning/living
if (m_settings->max_spawn > 0) {
if (m_spawned_total > m_settings->max_spawn && m_particles.size() <= 0) {
return false;
}
}

m_lifetime += timediff;

if (m_settings->emitter_lifetime_millis > 0) {
if (m_lifetime > m_settings->emitter_lifetime_millis) {
return false;
}
}

return true;
}

void SimpleRadialEmitter::init_particle(Particle* particle) {
particle->m_pos = get_center();
particle->m_prev_pos = particle->m_pos;
float speed = m_settings->particle_speed + (rand()/(float)RAND_MAX) * m_settings->speed_variance;
float dir = m_settings->rotation_rads + (rand()/(float)RAND_MAX) * m_settings->rotation_variance - m_settings->rotation_variance/2;

particle->m_vel = Vector(speed * sin(dir), speed * cos(dir));

particle->m_energy_left = m_settings->lifetime_millis + rand() % m_settings->lifetime_variance;
particle->m_initial_energy = particle->m_energy_left;

particle->m_color.r = 255;
particle->m_color.g = 255;
particle->m_color.b = 255;
particle->m_color.a = 255;

particle->m_size = 1.0f;
}
@@ -0,0 +1,65 @@
/*
* gui/SimpleRadialEmitter.hpp
*
* This file is part of Leges Motus, a networked, 2D shooter set in zero gravity.
*
* Copyright 2009-2011 Andrew Ayer, Nathan Partlan, Jeffrey Pfau
*
* Leges Motus is free and open source software. You may redistribute it and/or
* modify it under the terms of version 2, or (at your option) version 3, of the
* GNU General Public License (GPL), as published by the Free Software Foundation.
*
* Leges Motus is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the full text of the GNU General Public License for
* further detail.
*
* For a full copy of the GNU General Public License, please see the COPYING file
* in the root of the source code tree. You may also retrieve a copy from
* <http://www.gnu.org/licenses/gpl-2.0.txt>, or request a copy by writing to the
* Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*
*/

#include "ParticleEmitter.hpp"

#ifndef LM_GUI_SIMPLERADIALEMITTER_HPP
#define LM_GUI_SIMPLERADIALEMITTER_HPP

namespace LM {
struct SimpleRadialEmitterSettings {
float particle_speed;
float speed_variance;
int spawn_per_second;
int spawn_variance;
uint64_t lifetime_millis;
uint64_t lifetime_variance;
float rotation_rads;
float rotation_variance;
Vector global_force;
int max_spawn;
uint64_t emitter_stop_spawning_millis;
uint64_t emitter_lifetime_millis;
};

class SimpleRadialEmitter : public ParticleEmitter{
private:
uint64_t m_lifetime;
uint64_t m_leftover_diff;
int m_spawned_total;

const SimpleRadialEmitterSettings* m_settings;

void init_particle(Particle* particle);
public:
SimpleRadialEmitter(ParticleManager* manager, Point center, Image* image, DrawContext::BlendMode mode = DrawContext::BLEND_ADD);
virtual ~SimpleRadialEmitter();

void init(const SimpleRadialEmitterSettings* settings);

virtual bool update(uint64_t timediff);
};
}

#endif
@@ -2,7 +2,7 @@ BASEDIR = ..
NOBUNDLE = 1
include $(BASEDIR)/common.mk
TESTOBJS = test_primitives test_widgets test_label test_blend test_images test_graphics \
test_rendering test_shaders test_gameview test_input test_sim watch_ai
test_rendering test_shaders test_gameview test_input test_sim watch_ai test_particles
CXXFLAGS += $(CLIENTFLAGS)
LIBS := $(CLIENTLIBS)

@@ -0,0 +1,127 @@
#include "gui/SDLWindow.hpp"
#include "gui/GLESContext.hpp"
#include "gui/ShaderSet.hpp"
#include "gui/ResourceCache.hpp"
#include "gui/Image.hpp"
#include "gui/GraphicRegion.hpp"
#include "gui/GraphicContainer.hpp"
#include "gui/SimpleRadialEmitter.hpp"
#include "gui/ParticleManager.hpp"
#include "common/math.hpp"
#include "gui/DrawContext.hpp"

using namespace LM;
using namespace std;

void add_emitter(ParticleManager* particle_manager, Image* particle) {
SimpleRadialEmitter* simple_emitter = new SimpleRadialEmitter(particle_manager, Point(rand() % 400 + 50, rand() % 400 + 50), particle, DrawContext::BLEND_ADD);

SimpleRadialEmitterSettings* settings = new SimpleRadialEmitterSettings();
settings->particle_speed = 100.0f;
settings->speed_variance = 0.05f;
settings->spawn_per_second = 1000.0f;
settings->spawn_variance = 1.0f;
settings->lifetime_millis = 500;
settings->lifetime_variance = 100;
settings->rotation_rads = 0.0f;
settings->rotation_variance = 2 * M_PI;
settings->global_force = Point(0.0f,0.0f);
settings->max_spawn = -1;
settings->emitter_stop_spawning_millis = 500;
settings->emitter_lifetime_millis = 1000;

simple_emitter->init(settings);

particle_manager->add_emitter(simple_emitter);
}

int main(int argc, char *argv[]) {
SDLWindow* window = SDLWindow::get_instance(512, 512, 24, Window::FLAG_VSYNC);
GLESContext* ctx = window->get_context();
ResourceCache cache("data", ctx);
Image tile("metal_hazard64.png", &cache, true);
Image particle("blue_gate.png", &cache, true);

Widget w0;
w0.set_x(0);
w0.set_y(0);

GraphicRegion tile_s(&tile);
float c[] = { 4.0f, 8.0f, 0.5f, 1.0f };
float s[] = { 9.0f, 14.0f };
ShaderSet* program = ctx->create_shader_set();
PixelShader shader = ctx->load_pixel_shader("data/shaders/gl/impact");
program->attach_shader(shader);

tile_s.set_width(512);
tile_s.set_height(512);
tile_s.set_image_height(32);
tile_s.set_shader_set(program);

GraphicContainer g(true, &w0);
g.add_graphic("tile", &tile_s, -1);

ParticleManager* particle_manager = new ParticleManager(&w0, 100, true);

ctx->set_root_widget(&w0);

SDL_ShowCursor(SDL_TRUE);

bool running = true;
int frame = 0;
uint64_t last_frame_time = get_ticks();

program->link();
ctx->bind_shader_set(program);
program->set_variable("arc", 0.3f);
program->set_variable_2("size", 1, s);
program->set_variable("speed", 2.0f);
program->set_variable("offset", 0.0f);
program->set_variable("waves", 2.0f);
program->set_variable_2("center", 1, c);
program->set_variable("tex", 0);
while(running) {
SDL_Event e;
while(SDL_PollEvent(&e) != 0) {
switch(e.type) {
case SDL_QUIT:
running = 0;
break;
case SDL_KEYDOWN:
switch(e.key.keysym.sym) {
case SDLK_ESCAPE:
running = 0;
break;
default:
break;
}
}
}

//ctx->bind_shader_set(program);
//if (frame < 100) {
//program->set_variable("time", frame / 80.0f);
//} else if (frame < 150) {
// program->set_variable("time", 1.0f);
//} else {
// program->set_variable("time", 0);
//}

frame = (frame + 1) % 80;

if (frame % 20 == 0) {
add_emitter(particle_manager, &particle);
}

particle_manager->update(get_ticks() - last_frame_time);
last_frame_time = get_ticks();

window->redraw();

SDL_Delay(6);
}

delete particle_manager;

return 0;
}
@@ -18,7 +18,7 @@ int main(int argc, char *argv[]) {
float c[] = { 4.0f, 8.0f, 0.5f, 1.0f };
float s[] = { 9.0f, 14.0f };
ShaderSet* program = ctx->create_shader_set();
PixelShader shader = ctx->load_pixel_shader("data/shaders/gl/impact.fs");
PixelShader shader = ctx->load_pixel_shader("data/shaders/gl/impact");
program->attach_shader(shader);

tile_s.set_width(512);