Browse Source

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.

Lasse Öörni 12 years ago
parent
commit
3c244f9755

+ 1 - 1
Bin/Data/Particle/Smoke.xml

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

+ 1 - 1
Bin/Data/Particle/SnowExplosion.xml

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

+ 1 - 1
Bin/Data/Particle/SnowExplosionBig.xml

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

+ 1 - 1
Bin/Data/Particle/SnowExplosionFade.xml

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

+ 6 - 1
Docs/Reference.dox

@@ -1035,6 +1035,7 @@ Most of the parameters can take either a single value, or minimum and maximum va
     <activetime value="t" />
     <activetime value="t" />
     <inactivetime value="t" />
     <inactivetime value="t" />
     <interval min="t1" max="t2" />
     <interval min="t1" max="t2" />
+    <emissionrate min="t1" max="t2" />
     <particlesize min="x1 y1" max="x2 y2" />
     <particlesize min="x1 y1" max="x2 y2" />
     <timetolive min="t1" max="t2" />
     <timetolive min="t1" max="t2" />
     <velocity min="x1" max="x2" />
     <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>
 </particleemitter>
 \endcode
 \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
 \page Zones Zones

+ 1 - 1
Engine/Engine/GraphicsAPI.cpp

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

+ 2 - 2
Engine/Graphics/DecalSet.cpp

@@ -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 |
 static const unsigned SKINNED_ELEMENT_MASK = MASK_POSITION | MASK_NORMAL | MASK_TEXCOORD1 | MASK_TANGENT | MASK_BLENDWEIGHTS |
     MASK_BLENDINDICES;
     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)
 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)
 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_BOOL, "Is Enabled", IsEnabled, SetEnabled, bool, true, AM_DEFAULT);
     ACCESSOR_ATTRIBUTE(DecalSet, VAR_RESOURCEREF, "Material", GetMaterialAttr, SetMaterialAttr, ResourceRef, ResourceRef(Material::GetTypeStatic()), AM_DEFAULT);
     ACCESSOR_ATTRIBUTE(DecalSet, VAR_RESOURCEREF, "Material", GetMaterialAttr, SetMaterialAttr, ResourceRef, ResourceRef(Material::GetTypeStatic()), AM_DEFAULT);

+ 125 - 84
Engine/Graphics/ParticleEmitter.cpp

@@ -27,6 +27,7 @@
 #include "ParticleEmitter.h"
 #include "ParticleEmitter.h"
 #include "Profiler.h"
 #include "Profiler.h"
 #include "ResourceCache.h"
 #include "ResourceCache.h"
+#include "ResourceEvents.h"
 #include "Scene.h"
 #include "Scene.h"
 #include "SceneEvents.h"
 #include "SceneEvents.h"
 #include "XMLFile.h"
 #include "XMLFile.h"
@@ -36,7 +37,9 @@
 namespace Urho3D
 namespace Urho3D
 {
 {
 
 
-extern const char* GEOMETRY_CATEGORY;
+const char* EFFECT_CATEGORY = "Effect";
+
+static const unsigned MAX_PARTICLES_IN_FRAME = 100;
 
 
 OBJECTTYPESTATIC(ParticleEmitter);
 OBJECTTYPESTATIC(ParticleEmitter);
 
 
@@ -81,7 +84,7 @@ ParticleEmitter::~ParticleEmitter()
 
 
 void ParticleEmitter::RegisterObject(Context* context)
 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_BOOL, "Is Enabled", IsEnabled, SetEnabled, bool, true, AM_DEFAULT);
     ACCESSOR_ATTRIBUTE(ParticleEmitter, VAR_RESOURCEREF, "Parameter Source", GetParameterSourceAttr, SetParameterSourceAttr, ResourceRef, ResourceRef(XMLFile::GetTypeStatic()), AM_DEFAULT);
     ACCESSOR_ATTRIBUTE(ParticleEmitter, VAR_RESOURCEREF, "Parameter Source", GetParameterSourceAttr, SetParameterSourceAttr, ResourceRef, ResourceRef(XMLFile::GetTypeStatic()), AM_DEFAULT);
@@ -136,17 +139,26 @@ void ParticleEmitter::Update(const FrameInfo& frame)
             emitting_ = true;
             emitting_ = true;
             periodTimer_ -= inactiveTime_;
             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_)
     if (emitting_)
     {
     {
         emissionTimer_ += lastTimeStep_;
         emissionTimer_ += lastTimeStep_;
-        if (emissionTimer_ > 0.0f)
+        unsigned counter = MAX_PARTICLES_IN_FRAME;
+        while (emissionTimer_ > 0.0f && counter)
         {
         {
             emissionTimer_ -= Lerp(intervalMin_, intervalMax_, Random(1.0f));
             emissionTimer_ -= Lerp(intervalMin_, intervalMax_, Random(1.0f));
             if (EmitNewParticle())
             if (EmitNewParticle())
+            {
+                --counter;
                 needCommit = true;
                 needCommit = true;
+            }
+            else
+                break;
         }
         }
     }
     }
     
     
@@ -233,20 +245,107 @@ void ParticleEmitter::Update(const FrameInfo& frame)
         Commit();
         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;
     parameterSource_ = file;
     
     
+    if (parameterSource_)
+        SubscribeToEvent(parameterSource_, E_RELOADFINISHED, HANDLER(ParticleEmitter, HandleParametersReloadFinished));
+    
+    ApplyParameters();
+    MarkNetworkUpdate();
+}
+
+void ParticleEmitter::SetEmitting(bool enable, bool resetPeriod)
+{
+    if (enable != emitting_ || resetPeriod)
+    {
+        emitting_ = enable;
+        periodTimer_ = 0.0f;
+        MarkNetworkUpdate();
+    }
+}
+
+void ParticleEmitter::SetParameterSourceAttr(ResourceRef value)
+{
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+    SetParameters(cache->GetResource<XMLFile>(value.id_));
+}
+
+void ParticleEmitter::SetParticlesAttr(VariantVector value)
+{
+    unsigned index = 0;
+    SetNumParticles(value[index++].GetInt());
+    for (PODVector<Particle>::Iterator i = particles_.Begin(); i != particles_.End() && index < value.Size(); ++i)
+    {
+        i->velocity_ = value[index++].GetVector3();
+        i->size_ = value[index++].GetVector2();
+        i->timer_ = value[index++].GetFloat();
+        i->timeToLive_ = value[index++].GetFloat();
+        i->scale_ = value[index++].GetFloat();
+        i->rotationSpeed_ = value[index++].GetFloat();
+        i->colorIndex_ = value[index++].GetInt();
+        i->texIndex_ = value[index++].GetInt();
+    }
+}
+
+ResourceRef ParticleEmitter::GetParameterSourceAttr() const
+{
+    return GetResourceRef(parameterSource_, XMLFile::GetTypeStatic());
+}
+
+VariantVector ParticleEmitter::GetParticlesAttr() const
+{
+    VariantVector ret;
+    ret.Reserve(particles_.Size() * 8 + 1);
+    ret.Push(particles_.Size());
+    for (PODVector<Particle>::ConstIterator i = particles_.Begin(); i != particles_.End(); ++i)
+    {
+        ret.Push(i->velocity_);
+        ret.Push(i->size_);
+        ret.Push(i->timer_);
+        ret.Push(i->timeToLive_);
+        ret.Push(i->scale_);
+        ret.Push(i->rotationSpeed_);
+        ret.Push(i->colorIndex_);
+        ret.Push(i->texIndex_);
+    }
+    return ret;
+}
+
+void ParticleEmitter::OnNodeSet(Node* node)
+{
+    BillboardSet::OnNodeSet(node);
+    
+    if (node)
+    {
+        Scene* scene = GetScene();
+        if (scene && IsEnabledEffective())
+            SubscribeToEvent(scene, E_SCENEPOSTUPDATE, HANDLER(ParticleEmitter, HandleScenePostUpdate));
+    }
+}
+
+void ParticleEmitter::ApplyParameters()
+{
+    if (!parameterSource_)
+        return;
+    
+    XMLElement rootElem = parameterSource_->GetRoot();
+    
     if (rootElem.HasChild("material"))
     if (rootElem.HasChild("material"))
-        SetMaterial(cache->GetResource<Material>(rootElem.GetChild("material").GetAttribute("name")));
+        SetMaterial(GetSubsystem<ResourceCache>()->GetResource<Material>(rootElem.GetChild("material").GetAttribute("name")));
     
     
     if (rootElem.HasChild("numparticles"))
     if (rootElem.HasChild("numparticles"))
         SetNumParticles(rootElem.GetChild("numparticles").GetInt("value"));
         SetNumParticles(rootElem.GetChild("numparticles").GetInt("value"));
@@ -307,6 +406,15 @@ bool ParticleEmitter::SetParameters(XMLFile* file)
     if (rootElem.HasChild("interval"))
     if (rootElem.HasChild("interval"))
         GetFloatMinMax(rootElem.GetChild("interval"), intervalMin_, intervalMax_);
         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"))
     if (rootElem.HasChild("particlesize"))
         GetVector2MinMax(rootElem.GetChild("particlesize"), sizeMin_, sizeMax_);
         GetVector2MinMax(rootElem.GetChild("particlesize"), sizeMin_, sizeMax_);
     
     
@@ -361,78 +469,6 @@ bool ParticleEmitter::SetParameters(XMLFile* file)
         }
         }
         textureAnimation_ = animations;
         textureAnimation_ = animations;
     }
     }
-    
-    MarkNetworkUpdate();
-    return true;
-}
-
-void ParticleEmitter::SetEmitting(bool enable, bool resetPeriod)
-{
-    if (enable != emitting_ || resetPeriod)
-    {
-        emitting_ = enable;
-        periodTimer_ = 0.0f;
-        MarkNetworkUpdate();
-    }
-}
-
-void ParticleEmitter::SetParameterSourceAttr(ResourceRef value)
-{
-    ResourceCache* cache = GetSubsystem<ResourceCache>();
-    SetParameters(cache->GetResource<XMLFile>(value.id_));
-}
-
-void ParticleEmitter::SetParticlesAttr(VariantVector value)
-{
-    unsigned index = 0;
-    SetNumParticles(value[index++].GetInt());
-    for (PODVector<Particle>::Iterator i = particles_.Begin(); i != particles_.End() && index < value.Size(); ++i)
-    {
-        i->velocity_ = value[index++].GetVector3();
-        i->size_ = value[index++].GetVector2();
-        i->timer_ = value[index++].GetFloat();
-        i->timeToLive_ = value[index++].GetFloat();
-        i->scale_ = value[index++].GetFloat();
-        i->rotationSpeed_ = value[index++].GetFloat();
-        i->colorIndex_ = value[index++].GetInt();
-        i->texIndex_ = value[index++].GetInt();
-    }
-}
-
-ResourceRef ParticleEmitter::GetParameterSourceAttr() const
-{
-    return GetResourceRef(parameterSource_, XMLFile::GetTypeStatic());
-}
-
-VariantVector ParticleEmitter::GetParticlesAttr() const
-{
-    VariantVector ret;
-    ret.Reserve(particles_.Size() * 8 + 1);
-    ret.Push(particles_.Size());
-    for (PODVector<Particle>::ConstIterator i = particles_.Begin(); i != particles_.End(); ++i)
-    {
-        ret.Push(i->velocity_);
-        ret.Push(i->size_);
-        ret.Push(i->timer_);
-        ret.Push(i->timeToLive_);
-        ret.Push(i->scale_);
-        ret.Push(i->rotationSpeed_);
-        ret.Push(i->colorIndex_);
-        ret.Push(i->texIndex_);
-    }
-    return ret;
-}
-
-void ParticleEmitter::OnNodeSet(Node* node)
-{
-    BillboardSet::OnNodeSet(node);
-    
-    if (node)
-    {
-        Scene* scene = GetScene();
-        if (scene && IsEnabledEffective())
-            SubscribeToEvent(scene, E_SCENEPOSTUPDATE, HANDLER(ParticleEmitter, HandleScenePostUpdate));
-    }
 }
 }
 
 
 void ParticleEmitter::SetNumParticles(int num)
 void ParticleEmitter::SetNumParticles(int num)
@@ -598,4 +634,9 @@ void ParticleEmitter::HandleScenePostUpdate(StringHash eventType, VariantMap& ev
     }
     }
 }
 }
 
 
+void ParticleEmitter::HandleParametersReloadFinished(StringHash eventType, VariantMap& eventData)
+{
+    ApplyParameters();
+}
+
 }
 }

+ 6 - 2
Engine/Graphics/ParticleEmitter.h

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