Committing experimental code for particle systems; disabled until after 1.6.0.

ver1_6_1
Chris Laurel 2008-08-29 21:29:59 +00:00
parent 1b3a6baa00
commit 9915c4cb39
5 changed files with 1231 additions and 3 deletions

View File

@ -7,6 +7,12 @@
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
// Experimental particle system support
#define PARTICLE_SYSTEM 0
// Experimental support for optionally unnormalized meshes
#define UNNORMALIZED_MESHES 0
#include <iostream>
#include <fstream>
#include <cassert>
@ -19,8 +25,11 @@
#include <celmath/perlin.h>
#include <cel3ds/3dsread.h>
//#include "3dsmesh.h"
#include "modelfile.h"
#if PARTICLE_SYSTEM
#include "particlesystem.h"
#include "particlesystemfile.h"
#endif
#include "vertexlist.h"
#include "parser.h"
#include "spheremesh.h"
@ -51,8 +60,12 @@ string GeometryInfo::resolve(const string& baseDir)
// Ensure that models with different centers get resolved to different objects by
// adding a 'uniquifying' suffix to the filename that encodes the center value.
// This suffix is stripped before the file is actually loaded.
char uniquifyingSuffix[64];
char uniquifyingSuffix[128];
#if UNNORMALIZED_MESHES
sprintf(uniquifyingSuffix, "%c%f,%f,%f,%f,%d", UniqueSuffixChar, center.x, center.y, center.z, scale, (int) isNormalized);
#else
sprintf(uniquifyingSuffix, "%c%f%f%f", UniqueSuffixChar, center.x, center.y, center.z);
#endif
if (!path.empty())
{
@ -88,7 +101,16 @@ Geometry* GeometryInfo::load(const string& resolvedFilename)
model = Convert3DSModel(*scene, path);
else
model = Convert3DSModel(*scene, "");
#if UNNORMALIZED_MESHES
if (isNormalized)
model->normalize(center);
else
model->transform(center, scale);
#else
model->normalize(center);
#endif
delete scene;
}
@ -100,14 +122,33 @@ Geometry* GeometryInfo::load(const string& resolvedFilename)
{
model = LoadModel(in, path);
if (model != NULL)
{
#if UNNORMALIZED_MESHES
if (isNormalized)
model->normalize(center);
else
model->transform(center, scale);
#else
model->normalize(center);
#endif
}
}
}
else if (fileType == Content_CelestiaMesh)
{
model = LoadCelestiaMesh(filename);
}
#if PARTICLE_SYSTEM
else if (fileType == Content_CelestiaParticleSystem)
{
ifstream in(filename.c_str());
if (in.good())
{
return LoadParticleSystem(in, path);
}
}
#endif
// Condition the model for optimal rendering
if (model != NULL)
{

View File

@ -0,0 +1,530 @@
// particlesystem.cpp
//
// Stateless particle system renderer.
//
// Copyright (C) 2008, Chris Laurel <claurel@gmail.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
#include "celutil/util.h"
#include <cmath>
#include <algorithm>
#include <iostream>
#include <fstream>
#include <limits>
#include "particlesystem.h"
#include "gl.h"
#include "vecgl.h"
#include "rendcontext.h"
#include "texmanager.h"
#include <celutil/basictypes.h>
using namespace std;
/* !!! IMPORTANT !!!
* The particle system code is still under development; the complete
* set of particle system features has not been decided and the cpart
* format is not even close time final. There are most certainly bugs.
* DO NOT enable this code and invest a lot of time in creating your
* own particle system files until development is further along.
*/
/* STATELESS PARTICLE SYSTEMS
*
* THEORY
* In a typical particle system, initial particle states are generated
* and stored in array. At each time step, particles have their states
* updated and are then drawn. A typical sequence is:
* - Compute the forces acting on the particles
* - Update particle positions and velocities
* - Age the particles (updating state such as color and size)
* - Render the particles
*
* This process is well-suited to a simulation where the time steps are
* relatively uniform. But, we cannot rely on a uniform time step in Celestia.
* The user may skip ahead instantly to times in the distance past or future,
* change the time rate to over a billion times normal, or reverse time.
* Numerical integration of particle positions is completely impractical.
*
* Instead, Celestia uses 'stateless' particle systems. From the particle
* system description, the particle positions and appearances can be generated
* for any time. Initial states are generated from a pseudorandom sequence
* and state at the current time is computed analytically from those initial
* values. The fact that motions must be calculated analytically means that
* only very simply force models may be used; still, a large variety of
* effects are still practical.
*
* A particle system is just a list of particle emitters. Each emitter has
* a fixed emission rate (particles per second), start time, and end time.
* There are few properties that apply to all particles produced by an emitter:
* texture, lifetime, start/end color, and start/end size
* Color and size are linearly interpolated between start and end values over
* the lifetime of a particle.
* An emitter has two different 'generators': one produces initial particle
* velocities, the other initial positions.
*
* Emitter generators are fed with values from a linear congruential
* generator. Other pseudorandom number generators can produce sequences with
* better distributions and can have better performance at generating values.
* However, seeding these other generators is very slow, and seeding must
* be every time a particle is to be drawn. The well-known defects in
* pseudorandom sequences produced by an LCG are not visible in a particle
* system (and lack of apparent visual artifacts is the *only* requirement here.)
*/
// Same values as rand48()
static const uint64 A = ((uint64) 0x5deece66ul << 4) | 0xd;
static const uint64 C = 0xb;
static const uint64 M = ((uint64) 1 << 48) - 1;
/*! Linear congruential random number generator that emulates
* rand48()
*/
class LCGRandomGenerator
{
public:
LCGRandomGenerator() :
previous(0)
{
}
LCGRandomGenerator(uint64 seed) :
previous(seed)
{
}
uint64 randUint64()
{
previous = (A * previous + C) & M;
return previous;
}
/*! Return a random integer between -2^31 and 2^31 - 1
*/
int32 randInt32()
{
return (int32) (randUint64() >> 16);
}
/*! Return a random integer between 0 and 2^32 - 1
*/
uint32 randUint32()
{
return (uint32) (randUint64() >> 16);
}
/*! Generate a random floating point value in [ 0, 1 )
* This function directly manipulates the bits of a floating
* point number, and will not work properly on a system that
* doesn't use IEEE754 floats.
*/
float randFloat()
{
uint32 randBits = randInt32();
randBits = (randBits & 0x007fffff) | 0x3f800000;
return *reinterpret_cast<float*>(&randBits) - 1.0f;
}
/*! Generate a random floating point value in [ -1, 1 )
* This function directly manipulates the bits of a floating
* point number, and will not work properly on a system that
* doesn't use IEEE754 floats.
*/
float randSfloat()
{
uint32 randBits = (uint32) (randUint64() >> 16);
randBits = (randBits & 0x007fffff) | 0x40000000;
return *reinterpret_cast<float*>(&randBits) - 3.0f;
}
private:
uint64 previous;
};
/**** Generator implementations ****/
Vec3f
ConstantGenerator::generate(LCGRandomGenerator& /* gen */) const
{
return m_value;
}
Vec3f
BoxGenerator::generate(LCGRandomGenerator& gen) const
{
return Vec3f(gen.randSfloat() * m_semiAxes.x,
gen.randSfloat() * m_semiAxes.y,
gen.randSfloat() * m_semiAxes.z) + m_center;
}
Vec3f
LineGenerator::generate(LCGRandomGenerator& gen) const
{
return m_origin + m_direction * gen.randFloat();
}
Vec3f
EllipsoidSurfaceGenerator::generate(LCGRandomGenerator& gen) const
{
float theta = (float) PI * gen.randSfloat();
float cosPhi = gen.randSfloat();
float sinPhi = std::sqrt(1.0f - cosPhi * cosPhi);
if (cosPhi < 0.0f)
sinPhi = -sinPhi;
float s = std::sin(theta);
float c = std::cos(theta);
return Vec3f(sinPhi * c * m_semiAxes.x, sinPhi * s * m_semiAxes.y, cosPhi * m_semiAxes.z) + m_center;
}
Vec3f
ConeGenerator::generate(LCGRandomGenerator& gen) const
{
float theta = (float) PI * gen.randSfloat();
float cosPhi = 1.0f - m_cosMinAngle - gen.randFloat() * m_cosAngleVariance;
float sinPhi = std::sqrt(1.0f - cosPhi * cosPhi);
if (cosPhi < 0.0f)
sinPhi = -sinPhi;
float s = std::sin(theta);
float c = std::cos(theta);
return Vec3f(sinPhi * c, sinPhi * s, cosPhi) * (m_minLength + gen.randFloat() * m_lengthVariance);
}
Vec3f
GaussianDiscGenerator::generate(LCGRandomGenerator& gen) const
{
float r1 = 0.0f;
float r2 = 0.0f;
float s = 0.0f;
do
{
r1 = gen.randSfloat();
r2 = gen.randSfloat();
s = r1 * r1 + r2 * r2;
} while (s > 1.0f);
// Choose angle uniformly distributed in [ 0, 2*PI ), radius
// with a Gaussian distribution. Use the polar form of the
// Box-Muller transform to produce a normally distributed
// random number.
float r = r1 * std::sqrt(-2.0f * std::log(s) / s) * m_sigma;
float theta = r2 * 2.0f * (float) PI;
return Vec3f(r * std::cos(theta), r * std::sin(theta), 0.0f);
}
ParticleEmitter::ParticleEmitter() :
m_startTime(-numeric_limits<double>::infinity()),
m_endTime(-numeric_limits<double>::infinity()),
m_texture(InvalidResource),
m_rate(1.0f),
m_lifetime(1.0f),
m_startColor(1.0f, 1.0f, 1.0f, 0.0f),
m_startSize(1.0f),
m_endColor(1.0f, 1.0f, 1.0f, 0.0f),
m_endSize(1.0f),
m_positionGenerator(NULL),
m_velocityGenerator(NULL),
m_acceleration(0.0f, 0.0f, 0.0f),
m_nonZeroAcceleration(false),
m_minRotationRate(0.0f),
m_rotationRateVariance(0.0f),
m_rotationEnabled(false),
m_blendMode(Mesh::PremultipliedAlphaBlend)
{
}
ParticleEmitter::~ParticleEmitter()
{
delete m_positionGenerator;
delete m_velocityGenerator;
}
void
ParticleEmitter::setLifespan(double startTime, double endTime)
{
m_startTime = startTime;
m_endTime = endTime;
}
void
ParticleEmitter::setRotationRateRange(float minRate, float maxRate)
{
m_rotationEnabled = minRate != 0.0f || maxRate != 0.0f;
m_minRotationRate = minRate;
m_rotationRateVariance = maxRate - minRate;
}
void
ParticleEmitter::setAcceleration(const Vec3f& acceleration)
{
m_acceleration = acceleration;
m_nonZeroAcceleration = m_acceleration != Vec3f(0.0f, 0.0f, 0.0f);
}
void
ParticleEmitter::setBlendMode(Mesh::BlendMode blendMode)
{
m_blendMode = blendMode;
}
static const uint64 scrambleMask = (uint64(0xcccccccc) << 32) | 0xcccccccc;
void
ParticleEmitter::render(double tsec,
RenderContext& rc,
ParticleVertex* particleBuffer,
unsigned int particleBufferCapacity) const
{
double t = tsec;
bool startBounded = m_startTime > -numeric_limits<double>::infinity();
bool endBounded = m_endTime < numeric_limits<double>::infinity();
clog << "particles: " << t << endl;
// Return immediately if we're far enough past the end time that no
// particles remain.
if (endBounded)
{
if (t > m_endTime + m_lifetime)
return;
}
// If a start time is specified, set t to be relative to the start time.
// Return immediately if we haven't reached the start time yet.
if (startBounded)
{
t -= m_startTime;
if (t < 0.0)
return;
}
Mat3f modelViewMatrix = rc.getCameraOrientation().toMatrix3();
rc.setMaterial(&m_material);
Vec3f v0 = Vec3f(-1, -1, 0) * modelViewMatrix;
Vec3f v1 = Vec3f( 1, -1, 0) * modelViewMatrix;
Vec3f v2 = Vec3f( 1, 1, 0) * modelViewMatrix;
Vec3f v3 = Vec3f(-1, 1, 0) * modelViewMatrix;
glDisable(GL_LIGHTING);
Texture* texture = NULL;
if (m_texture != InvalidResource)
{
texture = GetTextureManager()->find(m_texture);
}
if (texture != NULL)
{
glEnable(GL_TEXTURE_2D);
texture->bind();
}
else
{
glDisable(GL_TEXTURE_2D);
}
// Use premultiplied alpha
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glDepthMask(GL_FALSE);
double emissionInterval = 1.0 / m_rate;
double dserial = std::fmod(t * m_rate, (double) (1 << 31));
int serial = (int) (dserial);
double age = (dserial - serial) * emissionInterval;
float invLifetime = (float) (1.0 / m_lifetime);
double maxAge = m_lifetime;
if (startBounded)
{
maxAge = std::min((double) m_lifetime, t);
}
if (endBounded && tsec > m_endTime)
{
int skipParticles = (int) ((tsec - m_endTime) * m_rate);
serial -= skipParticles;
age += skipParticles * emissionInterval;
}
Vec4f startColor(m_startColor.red(), m_startColor.green(), m_startColor.blue(), m_startColor.alpha());
Vec4f endColor(m_endColor.red(), m_endColor.green(), m_endColor.blue(), m_endColor.alpha());
unsigned int particleCount = 0;
while (age < maxAge)
{
// When the particle buffer is full, render the particles and flush it
if (particleCount == particleBufferCapacity)
{
glDrawArrays(GL_QUADS, 0, particleCount * 4);
particleCount = 0;
}
float alpha = (float) age * invLifetime;
float beta = 1.0f - alpha;
float size = alpha * m_endSize + beta * m_startSize;
// Scramble the random number generator seed so that we don't end up with
// artifacts from using regularly incrementing values.
//
// TODO: consider whether the generator could be seeded just once before
// the first particle is drawn. This would entail further restrictions,
// such as no 'branching' (variable number of calls to LCG::generate()) in
// particle state calculation.
LCGRandomGenerator gen(serial * 0x128ef719 ^ scrambleMask);
// Calculate the color of the particle
// TODO: switch to using a lookup table for color and opacity
unsigned char color[4];
color[Color::Red] = (unsigned char) ((alpha * endColor.x + beta * startColor.x) * 255.99f);
color[Color::Green] = (unsigned char) ((alpha * endColor.y + beta * startColor.y) * 255.99f);
color[Color::Blue] = (unsigned char) ((alpha * endColor.z + beta * startColor.z) * 255.99f);
color[Color::Alpha] = (unsigned char) ((alpha * endColor.w + beta * startColor.w) * 255.99f);
Vec3f v = m_velocityGenerator->generate(gen);
Vec3f center = m_positionGenerator->generate(gen) + v * (float) age;
if (m_nonZeroAcceleration)
center += m_acceleration * (float) (age * age);
if (!m_rotationEnabled)
{
particleBuffer[particleCount * 4 + 0].set(center + v0 * size, Vec2f(0.0f, 1.0f), color);
particleBuffer[particleCount * 4 + 1].set(center + v1 * size, Vec2f(1.0f, 1.0f), color);
particleBuffer[particleCount * 4 + 2].set(center + v2 * size, Vec2f(1.0f, 0.0f), color);
particleBuffer[particleCount * 4 + 3].set(center + v3 * size, Vec2f(0.0f, 0.0f), color);
}
else
{
float rotationRate = m_minRotationRate + m_rotationRateVariance * gen.randFloat();
float rotation = rotationRate * (float) age;
float c = std::cos(rotation);
float s = std::sin(rotation);
particleBuffer[particleCount * 4 + 0].set(center + (Vec3f(-c + s, -s - c, 0.0f) * modelViewMatrix) * size, Vec2f(0.0f, 1.0f), color);
particleBuffer[particleCount * 4 + 1].set(center + (Vec3f( c + s, s - c, 0.0f) * modelViewMatrix) * size, Vec2f(1.0f, 1.0f), color);
particleBuffer[particleCount * 4 + 2].set(center + (Vec3f( c - s, s + c, 0.0f) * modelViewMatrix) * size, Vec2f(1.0f, 0.0f), color);
particleBuffer[particleCount * 4 + 3].set(center + (Vec3f(-c - s, -s + c, 0.0f) * modelViewMatrix) * size, Vec2f(0.0f, 0.0f), color);
}
++particleCount;
age += emissionInterval;
serial--;
}
// Render any remaining particles in the buffer
if (particleCount > 0)
{
glDrawArrays(GL_QUADS, 0, particleCount * 4);
}
}
void
ParticleEmitter::createMaterial()
{
m_material.diffuse = Color(0.0f, 0.0f, 0.0f);
m_material.emissive = Color(1.0f, 1.0f, 1.0f);
m_material.blend = m_blendMode;
m_material.opacity = 0.99f;
m_material.maps[0] = m_texture;
}
#define STRUCT_OFFSET(s, memberName) ((uint32) (reinterpret_cast<char*>(&(s).memberName) - reinterpret_cast<char*>(&(s))))
ParticleSystem::ParticleSystem() :
m_vertexData(NULL),
m_particleCapacity(0),
m_particleCount(0),
m_vertexDesc(NULL)
{
m_particleCapacity = 1000;
m_vertexData = new ParticleVertex[m_particleCapacity * 4];
// Create the vertex description; currently, it is the same for all
// particle systems.
ParticleVertex temp;
Mesh::VertexAttribute attributes[3];
attributes[0] = Mesh::VertexAttribute(Mesh::Position, Mesh::Float3, STRUCT_OFFSET(temp, position));
attributes[1] = Mesh::VertexAttribute(Mesh::Texture0, Mesh::Float2, STRUCT_OFFSET(temp, texCoord));
attributes[2] = Mesh::VertexAttribute(Mesh::Color0, Mesh::UByte4, STRUCT_OFFSET(temp, color));
m_vertexDesc = new Mesh::VertexDescription(sizeof(ParticleVertex), 3, attributes);
}
ParticleSystem::~ParticleSystem()
{
for (list<ParticleEmitter*>::const_iterator iter = m_emitterList.begin();
iter != m_emitterList.end(); iter++)
{
delete (*iter);
}
delete[] m_vertexData;
delete m_vertexDesc;
}
void
ParticleSystem::render(RenderContext& rc, double tsec)
{
rc.setVertexArrays(*m_vertexDesc, m_vertexData);
for (list<ParticleEmitter*>::const_iterator iter = m_emitterList.begin();
iter != m_emitterList.end(); iter++)
{
(*iter)->render(tsec, rc, m_vertexData, m_particleCapacity);
}
glEnable(GL_LIGHTING);
glEnable(GL_TEXTURE_2D);
}
bool
ParticleSystem::pick(const Ray3d& /* r */, double& /* distance */) const
{
// Pick selection for particle systems not supported (because it's
// not typically desirable.)
return false;
}
bool
ParticleSystem::isOpaque() const
{
return false;
}
void
ParticleSystem::addEmitter(ParticleEmitter* emitter)
{
m_emitterList.push_back(emitter);
}

View File

@ -0,0 +1,241 @@
// particlesystem.h
//
// Copyright (C) 2008, Chris Laurel <claurel@gmail.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
#ifndef _CELENGINE_PARTICLESYSTEM_H_
#define _CELENGINE_PARTICLESYSTEM_H_
#include <string>
#include <list>
#include "model.h"
class VectorGenerator;
struct ParticleVertex
{
void set(const Vec3f& _position, const Vec2f& _texCoord, const unsigned char* _color)
{
position = _position;
texCoord = _texCoord;
color[Color::Red] = _color[Color::Red];
color[Color::Green] = _color[Color::Green];
color[Color::Blue] = _color[Color::Blue];
color[Color::Alpha] = _color[Color::Alpha];
}
Vec3f position;
Vec2f texCoord;
unsigned char color[4];
};
class ParticleEmitter
{
public:
ParticleEmitter();
~ParticleEmitter();
void render(double tsec, RenderContext& rc, ParticleVertex* particleBuffer, unsigned int bufferCapacity) const;
void setAcceleration(const Vec3f& acceleration);
void createMaterial();
void setLifespan(double startTime, double endTime);
void setRotationRateRange(float minRate, float maxRate);
void setBlendMode(Mesh::BlendMode blendMode);
private:
double m_startTime;
double m_endTime;
public:
ResourceHandle m_texture;
float m_rate;
float m_lifetime;
Color m_startColor;
float m_startSize;
Color m_endColor;
float m_endSize;
VectorGenerator* m_positionGenerator;
VectorGenerator* m_velocityGenerator;
private:
Vec3f m_acceleration;
bool m_nonZeroAcceleration;
float m_minRotationRate;
float m_rotationRateVariance;
bool m_rotationEnabled;
Mesh::BlendMode m_blendMode;
Mesh::Material m_material;
};
class ParticleSystem : public Geometry
{
public:
ParticleSystem();
virtual ~ParticleSystem();
virtual void render(RenderContext& rc, double t = 0.0);
virtual bool pick(const Ray3d& r, double& distance) const;
virtual bool isOpaque() const;
void addEmitter(ParticleEmitter* emitter);
public:
std::list<ParticleEmitter*> m_emitterList;
Mesh::VertexDescription* m_vertexDesc;
ParticleVertex* m_vertexData;
unsigned int m_particleCapacity;
unsigned int m_particleCount;
};
class LCGRandomGenerator;
/*! Generator abstract base class.
* Subclasses must implement generate() method.
*/
class VectorGenerator
{
public:
VectorGenerator() {};
virtual ~VectorGenerator() {};
virtual Vec3f generate(LCGRandomGenerator& gen) const = 0;
};
/*! Simplest generator; produces the exact same value on each call
* to generate().
*/
class ConstantGenerator : public VectorGenerator
{
public:
ConstantGenerator(const Vec3f& value) : m_value(value) {}
virtual Vec3f generate(LCGRandomGenerator& gen) const;
private:
Vec3f m_value;
};
/*! Generates values uniformly distributed within an axis-aligned box.
*/
class BoxGenerator : public VectorGenerator
{
public:
BoxGenerator(const Vec3f& center, const Vec3f& axes) :
m_center(center),
m_semiAxes(axes * 0.5f)
{
}
virtual Vec3f generate(LCGRandomGenerator& gen) const;
private:
Vec3f m_center;
Vec3f m_semiAxes;
};
/*! Generates values uniformly distributed on a line between
* two points.
*/
class LineGenerator : public VectorGenerator
{
public:
LineGenerator(const Vec3f& p0, const Vec3f& p1) :
m_origin(p0),
m_direction(p1 - p0)
{
}
virtual Vec3f generate(LCGRandomGenerator& gen) const;
private:
Vec3f m_origin;
Vec3f m_direction;
};
/*! Generates values uniformly distributed on the surface
* of an ellipsoid.
*/
class EllipsoidSurfaceGenerator : public VectorGenerator
{
public:
EllipsoidSurfaceGenerator(const Vec3f& center, const Vec3f& semiAxes) :
m_center(center),
m_semiAxes(semiAxes)
{
}
virtual Vec3f generate(LCGRandomGenerator& gen) const;
private:
Vec3f m_center;
Vec3f m_semiAxes;
};
/*! Generates values uniformly distributed within a spherical
* section. The section is centered on the z-axis.
*/
class ConeGenerator : public VectorGenerator
{
public:
ConeGenerator(float minAngle, float maxAngle, float minLength, const float maxLength) :
m_cosMinAngle(1.0f - std::cos(minAngle)),
m_cosAngleVariance(std::cos(minAngle) - std::cos(maxAngle)),
m_minLength(minLength),
m_lengthVariance(maxLength - minLength)
{
}
virtual Vec3f generate(LCGRandomGenerator& gen) const;
private:
float m_cosMinAngle;
float m_cosAngleVariance;
float m_minLength;
float m_lengthVariance;
};
/*! Generates points in a 2D gaussian distribution in
* the xy-plane and centered on the origin.
*/
class GaussianDiscGenerator : public VectorGenerator
{
public:
GaussianDiscGenerator(float sigma) :
m_sigma(sigma)
{
}
virtual Vec3f generate(LCGRandomGenerator& gen) const;
private:
float m_sigma;
};
//ParticleSystem* LoadParticleSystem(const std::string& filename, const std::string& resourcePath);
#endif // _CELENGINE_PARTICLESYSTEM_H_

View File

@ -0,0 +1,363 @@
// particlesystem.cpp
//
// Particle system file loader.
//
// Copyright (C) 2008, Chris Laurel <claurel@gmail.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
#include "particlesystemfile.h"
#include "particlesystem.h"
#include "texmanager.h"
#include <sstream>
using namespace std;
/* !!! IMPORTANT !!!
* The particle system code is still under development; the complete
* set of particle system features has not been decided and the cpart
* format is not even close time final. There are most certainly bugs.
* DO NOT enable this code and invest a lot of time in creating your
* own particle system files until development is further along.
*/
ParticleSystemLoader::ParticleSystemLoader(istream& in) :
m_tokenizer(&in),
m_parser(&m_tokenizer)
{
}
ParticleSystemLoader::~ParticleSystemLoader()
{
}
ParticleSystem*
ParticleSystemLoader::load()
{
ParticleSystem* particleSystem = new ParticleSystem();
while (m_tokenizer.nextToken() != Tokenizer::TokenEnd)
{
string objType;
if (m_tokenizer.getTokenType() != Tokenizer::TokenName)
{
raiseError("Error parsing particle system");
delete particleSystem;
return NULL;
}
objType = m_tokenizer.getNameValue();
if (objType != "Emitter")
{
ostringstream stream;
stream << "Unexpected object '" << objType << "' in particle system file";
raiseError(stream.str());
delete particleSystem;
return NULL;
}
Value* objParamsValue = m_parser.readValue();
if (objParamsValue == NULL || objParamsValue->getType() != Value::HashType)
{
raiseError("Error parsing particle system");
delete particleSystem;
return NULL;
}
Hash* objParams = objParamsValue->getHash();
if (objType == "Emitter")
{
ParticleEmitter* emitter = parseEmitter(objParams);
if (emitter == NULL)
{
delete particleSystem;
return NULL;
}
particleSystem->addEmitter(emitter);
}
}
return particleSystem;
}
VectorGenerator*
ParticleSystemLoader::parseGenerator(Hash* params)
{
Vec3f constantValue(0.0f, 0.0f, 0.0f);
if (params->getVector("Constant", constantValue))
{
return new ConstantGenerator(constantValue);
}
Value* generatorValue = NULL;
generatorValue = params->getValue("Box");
if (generatorValue != NULL)
{
if (generatorValue->getType() != Value::HashType)
{
raiseError("Error in Box syntax");
return NULL;
}
Hash* boxParams = generatorValue->getHash();
Vec3f center(0.0f, 0.0f, 0.0f);
Vec3f size(0.0f, 0.0f, 0.0f);
boxParams->getVector("Center", center);
boxParams->getVector("Size", size);
return new BoxGenerator(center, size);
}
generatorValue = params->getValue("Line");
if (generatorValue != NULL)
{
if (generatorValue->getType() != Value::HashType)
{
raiseError("Error in Line syntax");
return NULL;
}
Hash* lineParams = generatorValue->getHash();
Vec3f p0(0.0f, 0.0f, 0.0f);
Vec3f p1(0.0f, 0.0f, 0.0f);
lineParams->getVector("Point1", p0);
lineParams->getVector("Point2", p1);
return new LineGenerator(p0, p1);
}
generatorValue = params->getValue("EllipsoidSurface");
if (generatorValue != NULL)
{
if (generatorValue->getType() != Value::HashType)
{
raiseError("Error in EllipsoidSurface syntax");
return NULL;
}
Hash* ellipsoidSurfaceParams = generatorValue->getHash();
Vec3f center(0.0f, 0.0f, 0.0f);
Vec3f size(2.0f, 2.0f, 2.0f);
ellipsoidSurfaceParams->getVector("Center", center);
ellipsoidSurfaceParams->getVector("Size", size);
return new EllipsoidSurfaceGenerator(center, size * 0.5f);
}
generatorValue = params->getValue("Cone");
if (generatorValue != NULL)
{
if (generatorValue->getType() != Value::HashType)
{
raiseError("Error in Cone syntax");
return NULL;
}
Hash* coneParams = generatorValue->getHash();
double minAngle = 0.0;
double maxAngle = 0.0;
double minSpeed = 0.0;
double maxSpeed = 1.0;
coneParams->getNumber("MinAngle", minAngle);
coneParams->getNumber("MaxAngle", maxAngle);
coneParams->getNumber("MinSpeed", minSpeed);
coneParams->getNumber("MaxSpeed", maxSpeed);
return new ConeGenerator((float) degToRad(minAngle), (float) degToRad(maxAngle), (float) minSpeed, (float) maxSpeed);
}
generatorValue = params->getValue("GaussianDisc");
if (generatorValue != NULL)
{
if (generatorValue->getType() != Value::HashType)
{
raiseError("Error in GaussianDisc syntax");
return NULL;
}
Hash* gaussianDiscParams = generatorValue->getHash();
double sigma = 1.0;
gaussianDiscParams->getNumber("Sigma", sigma);
return new GaussianDiscGenerator((float) sigma);
}
raiseError("Missing generator for emitter");
return NULL;
}
ParticleEmitter*
ParticleSystemLoader::parseEmitter(Hash* params)
{
string textureFileName;
ResourceHandle textureHandle = InvalidResource;
if (params->getString("Texture", textureFileName))
{
textureHandle = GetTextureManager()->getHandle(TextureInfo(textureFileName, getTexturePath(), TextureInfo::BorderClamp));
}
double rate = 1.0;
double lifetime = 1.0;
params->getNumber("Rate", rate);
params->getNumber("Lifetime", lifetime);
double startSize = 1.0;
double endSize = 1.0;
params->getNumber("StartSize", startSize);
params->getNumber("EndSize", endSize);
Color startColor(Color::White);
Color endColor(Color::Black);
float startOpacity = 0.0f;
float endOpacity = 0.0f;
params->getColor("StartColor", startColor);
params->getNumber("StartOpacity", startOpacity);
params->getColor("EndColor", endColor);
params->getNumber("EndOpacity", endOpacity);
VectorGenerator* initialPositionGenerator = NULL;
Value* positionValue = params->getValue("InitialPosition");
if (positionValue == NULL)
{
initialPositionGenerator = new ConstantGenerator(Vec3f(0.0f, 0.0f, 0.0f));
}
else
{
if (positionValue->getType() != Value::HashType)
{
raiseError("Error in InitialPosition syntax");
}
else
{
initialPositionGenerator = parseGenerator(positionValue->getHash());
}
}
if (initialPositionGenerator == NULL)
{
return NULL;
}
VectorGenerator* initialVelocityGenerator = NULL;
Value* velocityValue = params->getValue("InitialVelocity");
if (velocityValue == NULL)
{
initialVelocityGenerator = new ConstantGenerator(Vec3f(0.0f, 0.0f, 0.0f));
}
else
{
if (velocityValue->getType() != Value::HashType)
{
raiseError("Error in InitialVelocity syntax");
}
else
{
initialVelocityGenerator = parseGenerator(velocityValue->getHash());
}
}
if (initialVelocityGenerator == NULL)
{
delete initialPositionGenerator;
return NULL;
}
Vec3f acceleration;
params->getVector("Acceleration", acceleration);
double startTime = -numeric_limits<double>::infinity();
double endTime = numeric_limits<double>::infinity();
params->getNumber("Beginning", startTime);
params->getNumber("Ending", endTime);
double minRotationRate = 0.0;
double maxRotationRate = 0.0;
params->getNumber("MinRotationRate", minRotationRate);
params->getNumber("MaxRotationRate", maxRotationRate);
ParticleEmitter* emitter = new ParticleEmitter();
emitter->m_texture = textureHandle;
emitter->m_rate = (float) rate;
emitter->m_lifetime = (float) lifetime;
emitter->m_startColor = Color(startColor, startOpacity);
emitter->m_endColor = Color(endColor, endOpacity);
emitter->m_startSize = (float) startSize;
emitter->m_endSize = (float) endSize;
emitter->m_positionGenerator = initialPositionGenerator;
emitter->m_velocityGenerator = initialVelocityGenerator;
emitter->createMaterial();
emitter->setAcceleration(acceleration);
emitter->setLifespan(startTime, endTime);
emitter->setRotationRateRange((float) degToRad(minRotationRate), (float) degToRad(maxRotationRate));
return emitter;
}
void
ParticleSystemLoader::raiseError(const string& errorMessage)
{
m_errorMessage = errorMessage;
}
const string&
ParticleSystemLoader::getErrorMessage() const
{
return m_errorMessage;
}
void
ParticleSystemLoader::setTexturePath(const string& texPath)
{
m_texPath = texPath;
}
const string&
ParticleSystemLoader::getTexturePath() const
{
return m_texPath;
}
ParticleSystem*
LoadParticleSystem(istream& in, const string& texPath)
{
ParticleSystemLoader* loader = new ParticleSystemLoader(in);
if (loader == NULL)
return NULL;
loader->setTexturePath(texPath);
ParticleSystem* particleSystem = loader->load();
if (particleSystem == NULL)
cerr << "Error in particle system file: " << loader->getErrorMessage() << '\n';
delete loader;
return particleSystem;
}

View File

@ -0,0 +1,53 @@
// particlesystem.h
//
// Particle system file loader.
//
// Copyright (C) 2008, Chris Laurel <claurel@gmail.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
#ifndef _CELENGINE_PARTICLESYSTEMFILE_H_
#define _CELENGINE_PARTICLESYSTEMFILE_H_
#include <iostream>
#include <string>
#include "tokenizer.h"
#include "parser.h"
class ParticleSystem;
class VectorGenerator;
class ParticleEmitter;
class ParticleSystemLoader
{
public:
ParticleSystemLoader(std::istream&);
~ParticleSystemLoader();
ParticleSystem* load();
VectorGenerator* parseGenerator(Hash* params);
ParticleEmitter* parseEmitter(Hash* params);
const std::string& getErrorMessage() const;
void setTexturePath(const std::string&);
const std::string& getTexturePath() const;
static ParticleSystemLoader* OpenParticleSystemFile(std::istream& in);
private:
virtual void raiseError(const std::string&);
private:
Tokenizer m_tokenizer;
Parser m_parser;
std::string m_errorMessage;
std::string m_texPath;
};
ParticleSystem* LoadParticleSystem(std::istream& in, const std::string& texPath);
#endif // _CELENGINE_PARTICLESYSTEMFILE_H_