Allow live-reload of particle emitter parameters file.

Allow also specifying emission rate instead of emission interval.
Allow emitting multiple particles per frame if necessary.
Restored the "effect" component category in the editor. ParticleEmitter & DecalSet belong to it now.
This commit is contained in:
Lasse Öörni 2013-05-30 23:20:40 +00:00
Родитель 7397399c7c
Коммит 3c244f9755
9 изменённых файлов: 188 добавлений и 138 удалений

Просмотреть файл

@ -5,7 +5,7 @@
<numparticles value="10" />
<activetime value="2" />
<inactivetime value="0" />
<interval value="0.075" />
<emissionrate value="13.333" />
<sorting enable="true" />
<rotationspeed min="-60" max="60" />
<direction min="-0.15 1 -0.15" max="0.15 1 0.15" />

Просмотреть файл

@ -4,7 +4,7 @@
<numparticles value="10" />
<activetime value="0.1" />
<inactivetime value="0" />
<interval value="0.02" />
<emissionrate value="50" />
<sorting enable="true" />
<direction min="-1 0 -1" max="1 1 1" />
<velocity min="0.5" max="1" />

Просмотреть файл

@ -6,7 +6,7 @@
<emittersize value="0.3 0.3 0.3" />
<activetime value="0.2" />
<inactivetime value="0" />
<interval value="0.02" />
<emissionrate value="50" />
<sorting enable="true" />
<direction min="-1 0.5 -1" max="1 1 1" />
<velocity min="2" max="3" />

Просмотреть файл

@ -4,7 +4,7 @@
<numparticles value="5" />
<activetime value="0.1" />
<inactivetime value="0" />
<interval value="0.02" />
<emissionrate value="50" />
<sorting enable="true" />
<direction min="-1 0 -1" max="1 1 1" />
<velocity min="0.5" max="1" />

Просмотреть файл

@ -1035,6 +1035,7 @@ Most of the parameters can take either a single value, or minimum and maximum va
<activetime value="t" />
<inactivetime value="t" />
<interval min="t1" max="t2" />
<emissionrate min="t1" max="t2" />
<particlesize min="x1 y1" max="x2 y2" />
<timetolive min="t1" max="t2" />
<velocity min="x1" max="x2" />
@ -1046,7 +1047,11 @@ Most of the parameters can take either a single value, or minimum and maximum va
</particleemitter>
\endcode
Note: zero active or inactive time period means infinite. Instead of defining a single color element, several colorfade elements can be defined in time order to describe how the particles change color over time.
Notes:
- Zero active or inactive time period means infinite.
- Interval is the reciprocal of emission rate. Either can be used to define the rate at which new particles are emitted.
- Instead of defining a single color element, several colorfade elements can be defined in time order to describe how the particles change color over time.
\page Zones Zones

Просмотреть файл

@ -886,7 +886,7 @@ static void RegisterParticleEmitter(asIScriptEngine* engine)
{
RegisterDrawable<ParticleEmitter>(engine, "ParticleEmitter");
engine->RegisterObjectMethod("ParticleEmitter", "void SetEmitting(bool, bool)", asMETHOD(ParticleEmitter, SetEmitting), asCALL_THISCALL);
engine->RegisterObjectMethod("ParticleEmitter", "bool set_parameters(XMLFile@+ file)", asMETHOD(ParticleEmitter, SetParameters), asCALL_THISCALL);
engine->RegisterObjectMethod("ParticleEmitter", "void set_parameters(XMLFile@+ file)", asMETHOD(ParticleEmitter, SetParameters), asCALL_THISCALL);
engine->RegisterObjectMethod("ParticleEmitter", "XMLFile@+ get_parameters() const", asMETHOD(ParticleEmitter, GetParameters), asCALL_THISCALL);
engine->RegisterObjectMethod("ParticleEmitter", "void set_material(Material@+)", asMETHOD(ParticleEmitter, SetMaterial), asCALL_THISCALL);
engine->RegisterObjectMethod("ParticleEmitter", "Material@+ get_material() const", asMETHOD(ParticleEmitter, GetMaterial), asCALL_THISCALL);

Просмотреть файл

@ -55,7 +55,7 @@ static const unsigned STATIC_ELEMENT_MASK = MASK_POSITION | MASK_NORMAL | MASK_T
static const unsigned SKINNED_ELEMENT_MASK = MASK_POSITION | MASK_NORMAL | MASK_TEXCOORD1 | MASK_TANGENT | MASK_BLENDWEIGHTS |
MASK_BLENDINDICES;
extern const char* GEOMETRY_CATEGORY;
extern const char* EFFECT_CATEGORY;
static DecalVertex ClipEdge(const DecalVertex& v0, const DecalVertex& v1, float d0, float d1, bool skinned)
{
@ -180,7 +180,7 @@ DecalSet::~DecalSet()
void DecalSet::RegisterObject(Context* context)
{
context->RegisterFactory<DecalSet>(GEOMETRY_CATEGORY);
context->RegisterFactory<DecalSet>(EFFECT_CATEGORY);
ACCESSOR_ATTRIBUTE(DecalSet, VAR_BOOL, "Is Enabled", IsEnabled, SetEnabled, bool, true, AM_DEFAULT);
ACCESSOR_ATTRIBUTE(DecalSet, VAR_RESOURCEREF, "Material", GetMaterialAttr, SetMaterialAttr, ResourceRef, ResourceRef(Material::GetTypeStatic()), AM_DEFAULT);

Просмотреть файл

@ -27,6 +27,7 @@
#include "ParticleEmitter.h"
#include "Profiler.h"
#include "ResourceCache.h"
#include "ResourceEvents.h"
#include "Scene.h"
#include "SceneEvents.h"
#include "XMLFile.h"
@ -36,7 +37,9 @@
namespace Urho3D
{
extern const char* GEOMETRY_CATEGORY;
const char* EFFECT_CATEGORY = "Effect";
static const unsigned MAX_PARTICLES_IN_FRAME = 100;
OBJECTTYPESTATIC(ParticleEmitter);
@ -81,7 +84,7 @@ ParticleEmitter::~ParticleEmitter()
void ParticleEmitter::RegisterObject(Context* context)
{
context->RegisterFactory<ParticleEmitter>(GEOMETRY_CATEGORY);
context->RegisterFactory<ParticleEmitter>(EFFECT_CATEGORY);
ACCESSOR_ATTRIBUTE(ParticleEmitter, VAR_BOOL, "Is Enabled", IsEnabled, SetEnabled, bool, true, AM_DEFAULT);
ACCESSOR_ATTRIBUTE(ParticleEmitter, VAR_RESOURCEREF, "Parameter Source", GetParameterSourceAttr, SetParameterSourceAttr, ResourceRef, ResourceRef(XMLFile::GetTypeStatic()), AM_DEFAULT);
@ -136,18 +139,27 @@ void ParticleEmitter::Update(const FrameInfo& frame)
emitting_ = true;
periodTimer_ -= inactiveTime_;
}
// If emitter has an indefinite stop interval, keep period timer reset to allow restarting emission in the editor
if (inactiveTime_ == 0.0f)
periodTimer_ = 0.0f;
}
// Check for emitting a new particle
// Check for emitting new particles
if (emitting_)
{
emissionTimer_ += lastTimeStep_;
if (emissionTimer_ > 0.0f)
unsigned counter = MAX_PARTICLES_IN_FRAME;
while (emissionTimer_ > 0.0f && counter)
{
emissionTimer_ -= Lerp(intervalMin_, intervalMax_, Random(1.0f));
if (EmitNewParticle())
{
--counter;
needCommit = true;
}
else
break;
}
}
// Update existing particles
@ -233,137 +245,27 @@ void ParticleEmitter::Update(const FrameInfo& frame)
Commit();
}
bool ParticleEmitter::SetParameters(XMLFile* file)
void ParticleEmitter::SetParameters(XMLFile* file)
{
ResourceCache* cache = GetSubsystem<ResourceCache>();
if (!file || !cache)
return false;
if (file == parameterSource_)
return;
XMLElement rootElem = file->GetRoot();
if (!rootElem)
return false;
if (parameterSource_)
UnsubscribeFromEvent(parameterSource_, E_RELOADFINISHED);
if (file && !file->GetRoot())
{
LOGERROR("Particle emitter parameter file does not have a valid root element");
return;
}
parameterSource_ = file;
if (rootElem.HasChild("material"))
SetMaterial(cache->GetResource<Material>(rootElem.GetChild("material").GetAttribute("name")));
if (rootElem.HasChild("numparticles"))
SetNumParticles(rootElem.GetChild("numparticles").GetInt("value"));
if (rootElem.HasChild("updateinvisible"))
updateInvisible_ = rootElem.GetChild("updateinvisible").GetBool("enable");
if (rootElem.HasChild("relative"))
relative_ = rootElem.GetChild("relative").GetBool("enable");
if (rootElem.HasChild("scaled"))
scaled_ = rootElem.GetChild("scaled").GetBool("enable");
if (rootElem.HasChild("sorted"))
sorted_ = rootElem.GetChild("sorted").GetBool("enable");
if (rootElem.HasChild("animlodbias"))
SetAnimationLodBias(rootElem.GetChild("relative").GetFloat("value"));
if (rootElem.HasChild("emittertype"))
{
String type = rootElem.GetChild("emittertype").GetAttributeLower("value");
if (type == "point")
emitterType_ = EMITTER_POINT;
else if (type == "box")
emitterType_ = EMITTER_BOX;
else if (type == "sphere")
emitterType_ = EMITTER_SPHERE;
else
LOGERROR("Unknown particle emitter type " + type);
}
if (rootElem.HasChild("emittersize"))
emitterSize_ = rootElem.GetChild("emittersize").GetVector3("value");
if (rootElem.HasChild("emitterradius"))
emitterSize_.x_ = rootElem.GetChild("emitterradius").GetFloat("value");
if (rootElem.HasChild("direction"))
GetVector3MinMax(rootElem.GetChild("direction"), directionMin_, directionMax_);
if (rootElem.HasChild("constantforce"))
constanceForce_ = rootElem.GetChild("constantforce").GetVector3("value");
if (rootElem.HasChild("dampingforce"))
dampingForce_ = rootElem.GetChild("dampingforce").GetFloat("value");
if (rootElem.HasChild("activetime"))
activeTime_ = rootElem.GetChild("activetime").GetFloat("value");
if (activeTime_ < 0.0f)
activeTime_ = M_INFINITY;
if (rootElem.HasChild("inactivetime"))
inactiveTime_ = rootElem.GetChild("inactivetime").GetFloat("value");
if (inactiveTime_ < 0.0f)
inactiveTime_ = M_INFINITY;
if (rootElem.HasChild("interval"))
GetFloatMinMax(rootElem.GetChild("interval"), intervalMin_, intervalMax_);
if (rootElem.HasChild("particlesize"))
GetVector2MinMax(rootElem.GetChild("particlesize"), sizeMin_, sizeMax_);
if (rootElem.HasChild("timetolive"))
GetFloatMinMax(rootElem.GetChild("timetolive"), timeToLiveMin_, timeToLiveMax_);
if (rootElem.HasChild("velocity"))
GetFloatMinMax(rootElem.GetChild("velocity"), velocityMin_, velocityMax_);
if (rootElem.HasChild("rotation"))
GetFloatMinMax(rootElem.GetChild("rotation"), rotationMin_, rotationMax_);
if (rootElem.HasChild("rotationspeed"))
GetFloatMinMax(rootElem.GetChild("rotationspeed"), rotationSpeedMin_, rotationSpeedMax_);
if (rootElem.HasChild("sizedelta"))
{
XMLElement deltaElem = rootElem.GetChild("sizedelta");
if (deltaElem.HasAttribute("add"))
sizeAdd_ = deltaElem.GetFloat("add");
if (deltaElem.HasAttribute("mul"))
sizeMul_ = deltaElem.GetFloat("mul");
}
if (rootElem.HasChild("color"))
SetParticleColor(rootElem.GetChild("color").GetColor("value"));
if (rootElem.HasChild("colorfade"))
{
Vector<ColorFade> fades;
XMLElement colorFadeElem = rootElem.GetChild("colorfade");
while (colorFadeElem)
{
fades.Push(ColorFade(colorFadeElem.GetColor("color"), colorFadeElem.GetFloat("time")));
colorFadeElem = colorFadeElem.GetNext("colorfade");
}
SetParticleColors(fades);
}
if (rootElem.HasChild("texanim"))
{
Vector<TextureAnimation> animations;
XMLElement animElem = rootElem.GetChild("texanim");
while (animElem)
{
TextureAnimation animation;
animation.uv_ = animElem.GetRect("uv");
animation.time_ = animElem.GetFloat("time");
animations.Push(animation);
animElem = animElem.GetNext("texanim");
}
textureAnimation_ = animations;
}
if (parameterSource_)
SubscribeToEvent(parameterSource_, E_RELOADFINISHED, HANDLER(ParticleEmitter, HandleParametersReloadFinished));
ApplyParameters();
MarkNetworkUpdate();
return true;
}
void ParticleEmitter::SetEmitting(bool enable, bool resetPeriod)
@ -435,6 +337,140 @@ void ParticleEmitter::OnNodeSet(Node* node)
}
}
void ParticleEmitter::ApplyParameters()
{
if (!parameterSource_)
return;
XMLElement rootElem = parameterSource_->GetRoot();
if (rootElem.HasChild("material"))
SetMaterial(GetSubsystem<ResourceCache>()->GetResource<Material>(rootElem.GetChild("material").GetAttribute("name")));
if (rootElem.HasChild("numparticles"))
SetNumParticles(rootElem.GetChild("numparticles").GetInt("value"));
if (rootElem.HasChild("updateinvisible"))
updateInvisible_ = rootElem.GetChild("updateinvisible").GetBool("enable");
if (rootElem.HasChild("relative"))
relative_ = rootElem.GetChild("relative").GetBool("enable");
if (rootElem.HasChild("scaled"))
scaled_ = rootElem.GetChild("scaled").GetBool("enable");
if (rootElem.HasChild("sorted"))
sorted_ = rootElem.GetChild("sorted").GetBool("enable");
if (rootElem.HasChild("animlodbias"))
SetAnimationLodBias(rootElem.GetChild("relative").GetFloat("value"));
if (rootElem.HasChild("emittertype"))
{
String type = rootElem.GetChild("emittertype").GetAttributeLower("value");
if (type == "point")
emitterType_ = EMITTER_POINT;
else if (type == "box")
emitterType_ = EMITTER_BOX;
else if (type == "sphere")
emitterType_ = EMITTER_SPHERE;
else
LOGERROR("Unknown particle emitter type " + type);
}
if (rootElem.HasChild("emittersize"))
emitterSize_ = rootElem.GetChild("emittersize").GetVector3("value");
if (rootElem.HasChild("emitterradius"))
emitterSize_.x_ = rootElem.GetChild("emitterradius").GetFloat("value");
if (rootElem.HasChild("direction"))
GetVector3MinMax(rootElem.GetChild("direction"), directionMin_, directionMax_);
if (rootElem.HasChild("constantforce"))
constanceForce_ = rootElem.GetChild("constantforce").GetVector3("value");
if (rootElem.HasChild("dampingforce"))
dampingForce_ = rootElem.GetChild("dampingforce").GetFloat("value");
if (rootElem.HasChild("activetime"))
activeTime_ = rootElem.GetChild("activetime").GetFloat("value");
if (activeTime_ < 0.0f)
activeTime_ = M_INFINITY;
if (rootElem.HasChild("inactivetime"))
inactiveTime_ = rootElem.GetChild("inactivetime").GetFloat("value");
if (inactiveTime_ < 0.0f)
inactiveTime_ = M_INFINITY;
if (rootElem.HasChild("interval"))
GetFloatMinMax(rootElem.GetChild("interval"), intervalMin_, intervalMax_);
if (rootElem.HasChild("emissionrate"))
{
float emissionRateMin = 0.0f;
float emissionRateMax = 0.0f;
GetFloatMinMax(rootElem.GetChild("emissionrate"), emissionRateMin, emissionRateMax);
intervalMax_ = 1.0f / emissionRateMin;
intervalMin_ = 1.0f / emissionRateMax;
}
if (rootElem.HasChild("particlesize"))
GetVector2MinMax(rootElem.GetChild("particlesize"), sizeMin_, sizeMax_);
if (rootElem.HasChild("timetolive"))
GetFloatMinMax(rootElem.GetChild("timetolive"), timeToLiveMin_, timeToLiveMax_);
if (rootElem.HasChild("velocity"))
GetFloatMinMax(rootElem.GetChild("velocity"), velocityMin_, velocityMax_);
if (rootElem.HasChild("rotation"))
GetFloatMinMax(rootElem.GetChild("rotation"), rotationMin_, rotationMax_);
if (rootElem.HasChild("rotationspeed"))
GetFloatMinMax(rootElem.GetChild("rotationspeed"), rotationSpeedMin_, rotationSpeedMax_);
if (rootElem.HasChild("sizedelta"))
{
XMLElement deltaElem = rootElem.GetChild("sizedelta");
if (deltaElem.HasAttribute("add"))
sizeAdd_ = deltaElem.GetFloat("add");
if (deltaElem.HasAttribute("mul"))
sizeMul_ = deltaElem.GetFloat("mul");
}
if (rootElem.HasChild("color"))
SetParticleColor(rootElem.GetChild("color").GetColor("value"));
if (rootElem.HasChild("colorfade"))
{
Vector<ColorFade> fades;
XMLElement colorFadeElem = rootElem.GetChild("colorfade");
while (colorFadeElem)
{
fades.Push(ColorFade(colorFadeElem.GetColor("color"), colorFadeElem.GetFloat("time")));
colorFadeElem = colorFadeElem.GetNext("colorfade");
}
SetParticleColors(fades);
}
if (rootElem.HasChild("texanim"))
{
Vector<TextureAnimation> animations;
XMLElement animElem = rootElem.GetChild("texanim");
while (animElem)
{
TextureAnimation animation;
animation.uv_ = animElem.GetRect("uv");
animation.time_ = animElem.GetFloat("time");
animations.Push(animation);
animElem = animElem.GetNext("texanim");
}
textureAnimation_ = animations;
}
}
void ParticleEmitter::SetNumParticles(int num)
{
num = Max(num, 0);
@ -598,4 +634,9 @@ void ParticleEmitter::HandleScenePostUpdate(StringHash eventType, VariantMap& ev
}
}
void ParticleEmitter::HandleParametersReloadFinished(StringHash eventType, VariantMap& eventData)
{
ApplyParameters();
}
}

Просмотреть файл

@ -86,8 +86,8 @@ public:
/// Update before octree reinsertion. Is called from a worker thread. Needs to be requested with MarkForUpdate().
virtual void Update(const FrameInfo& frame);
/// Set emitter parameters from an XML file. Return true if successful.
bool SetParameters(XMLFile* file);
/// Set emitter parameters from an XML file.
void SetParameters(XMLFile* file);
/// Set whether should be emitting and optionally reset emission period.
void SetEmitting(bool enable, bool resetPeriod = false);
@ -111,6 +111,8 @@ protected:
/// Handle node being assigned.
virtual void OnNodeSet(Node* node);
/// Apply parameter file.
void ApplyParameters();
/// Set number of particles.
void SetNumParticles(int num);
/// Set color of particles.
@ -131,6 +133,8 @@ protected:
private:
/// Handle scene post-update event.
void HandleScenePostUpdate(StringHash eventType, VariantMap& eventData);
/// Handle parameter file reload finished.
void HandleParametersReloadFinished(StringHash eventType, VariantMap& eventData);
/// Parameter XML file.
SharedPtr<XMLFile> parameterSource_;