/* ----------------------------------------------------------------------------- GSFramework Copyright 2001-2013 Emmanuel Julien. All Rights Reserved. ----------------------------------------------------------------------------- */ #include "core/emitter.h" #include "core/camera.h" #include "core/resource_factories.h" #include "core/graphic_resource_factory.h" #include "core/render_resource_factory.h" #include "timing/benchmark.h" #include "rand/rand.h" #include "log/log.h" using namespace GS; using namespace GS::Core; //------------------------------------------------------------------------------ ParticleModel::ParticleModel() { gravity.Set(0, 0, 0); SetColor(Color(1, 1, 1, 1)); // size_curve.SetDefaultValue(1); time_to_live.setSec(4.f); damping = 1.f; } void ParticleModel::SetColor(const Color &color) { // red_curve.SetDefaultValue(color.x); // green_curve.SetDefaultValue(color.y); // blue_curve.SetDefaultValue(color.z); // opacity_curve.SetDefaultValue(color.w); } void ParticleModel::AddColorPoint(const Time &t, const Color &color) { red_curve.Insert(CurvePoint(t, color.x)); green_curve.Insert(CurvePoint(t, color.y)); blue_curve.Insert(CurvePoint(t, color.z)); opacity_curve.Insert(CurvePoint(t, color.w)); } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ void ParticleModel::RenderSetup(ResourceFactories *f) { if ((render_data = new RenderData) != NULL) if (f && f->render) render_data->material = f->render->LoadMaterial(material); } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ void Emitter::ComputeMinMax(MinMax &minmax) const { minmax.mn = minmax.mx = GetMatrix().GetRow(3); if (!GetParticleCount()) return; for (uint n = 0; n < GetParticleCount(); ++n) { Particle *p = GetParticle(n); minmax.Grow(MinMax(p->position - Vector4(p->size, p->size, p->size), p->position + Vector4(p->size, p->size, p->size))); } } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ uint Emitter::GetRenderablePrimitiveList(const Camera &view, const Camera &default_view, Stack &list, Renderable::Context context, bool cull) { if (cull) { MinMax minmax; ComputeMinMax(minmax); if (view.frustum.ClassifyMinMax(minmax) == Frustum::Outside) return 1; } is_seen = true; const Matrix4 &vm = view.GetMatrix(); list.Push(new Render::Primitive(this, this, vm.GetRow(2).Dot(GetMatrix().GetRow(3) - vm.GetRow(3)))); return 1; } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ void Emitter::Sort(const Matrix4 &view) { if (!particle_pool || !is_seen) return; Vector4 view_front = view.GetRow(2).Normalized(), view_pos = view.GetRow(3); alive_count = 0; for (uint n = 0; n < particle_pool.GetCount(); ++n) if (particle_pool[n].IsAlive()) { sort_array[alive_count].v = (particle_pool[n].position - view_pos).Dot(view_front); sort_array[alive_count].o = n; alive_count++; } GS::Sort ::QuickSort(alive_count, sort_array); } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ void Emitter::Update(const Time &dt) { if (!particle_pool || !is_seen) return; // Spawn particles. uint n = 0; for (birth_time += dt * birth_rate * birth_rate_scale; birth_time.toSec() > 1; birth_time -= Time::fromSec(1)) { // Seek next free particle. for ( ; n < particle_pool.GetCount(); ++n) if (particle_pool[n].time.toSec() < 0) break; // Emitter pool exhausted. if (n == particle_pool.GetCount()) break; // Spawn particle. Particle &p = particle_pool[n]; p.time = birth_time / birth_rate; p.angle = 0; p.size = 0; p.color.Set(0, 0, 0, 0); ModelParticle(p, *this, time); } // Update running particles and drop dead ones. if (render_data.IsValid()) if (ParticleModel *model = render_data->particle_model) { float damping = Math::Pow(model->damping, dt.toSec()); for (n = 0; n < particle_pool.GetCount(); ++n) { Particle &p = particle_pool[n]; if (!p.IsAlive()) continue; // Kill particle. p.time += dt; if (p.time >= model->time_to_live) p.time.setSec(-1); else { float k_dt = dt.toSec(); p.position += p.velocity * k_dt; p.velocity += render_data->particle_model->gravity * k_dt; p.velocity *= damping; model->size_curve.Evaluate(p.time, &p.size); model->angle_curve.Evaluate(p.time, &p.angle); model->red_curve.Evaluate(p.time, &p.color.x); model->green_curve.Evaluate(p.time, &p.color.y); model->blue_curve.Evaluate(p.time, &p.color.z); model->opacity_curve.Evaluate(p.time, &p.color.w); p.size *= p.size_scale; p.color.w *= p.opacity_scale; } } } is_seen = false; time += dt; } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ void Emitter::RenderSetup(ResourceFactories *f) { if ((render_data = new RenderData) != NULL) if (f && f->graphic) if ((render_data->particle_model = f->graphic->LoadParticleModel(particle_model)) != NULL) render_data->particle_model->RenderSetup(f); } bool Emitter::Setup() { alive_count = 0; if (!particle_pool.Allocate(pool_size) || !sort_array.Allocate(pool_size)) __ERR__(__LOG_E__ << "Failed to allocate emitter particle pool (size " << pool_size << ").\n", false) for (uint n = 0; n < pool_size; ++n) particle_pool[n].time.setSec(-1); time.setSec(0); birth_time.setSec(0); is_seen = true; return true; } void Emitter::Free() { particle_pool.Free(); sort_array.Free(); alive_count = 0; } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ void Emitter::SetSprayModel(float angle) { model = Model_Spray; spray_angle = angle; } void Emitter::ModelParticle(Particle &p, const Item &i, const Time &/*emitter_time*/) { Vector4 ip = i.GetMatrix().GetRow(3); switch (model) { default: p.position = ip; p.velocity.Set(0, 0, 0); break; case Model_Spray: { p.position = ip; using namespace Random; float a = (FRand(2.f) - 1.f) * spray_angle; Vector4 d(Math::Sin(a), 0, Math::Cos(a)); Matrix3::RotationMatrixZAxis(FRand(Units::Deg(360.f))).Apply(&p.velocity, &d); p.velocity = (p.velocity.Normalized() * FRRand(birth_speed_min, birth_speed_max)) * birth_speed_scale * Matrix3::FromMatrix4(i.GetMatrix()); } break; } p.size_scale = birth_size_scale; p.opacity_scale = birth_opacity_scale; } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ Emitter::Emitter() { is_seen = true; birth_speed_min = 1; birth_speed_max = 1.5; pool_size = 500; birth_rate = (float)pool_size / Units::Sec(5.f); birth_rate_scale = 1; birth_opacity_scale = 1; birth_speed_scale = 1; birth_size_scale = 1; SetSprayModel(Units::Deg(45.f)); alive_count = 0; } //------------------------------------------------------------------------------