276 lines
7.5 KiB
C++
276 lines
7.5 KiB
C++
/* -----------------------------------------------------------------------------
|
|
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 <Render::Primitive *> &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 <float, uint> ::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;
|
|
}
|
|
//------------------------------------------------------------------------------
|