diff --git a/binaries/data/mods/public/art/actors/particle/construction_dust.xml b/binaries/data/mods/public/art/actors/particle/construction_dust.xml new file mode 100644 index 0000000000..c958e7d76c --- /dev/null +++ b/binaries/data/mods/public/art/actors/particle/construction_dust.xml @@ -0,0 +1,9 @@ + + + + + + + + basic_trans.xml + diff --git a/binaries/data/mods/public/art/actors/structures/fndn_3x3.xml b/binaries/data/mods/public/art/actors/structures/fndn_3x3.xml index 2b1078b9d8..ce0951a6da 100644 --- a/binaries/data/mods/public/art/actors/structures/fndn_3x3.xml +++ b/binaries/data/mods/public/art/actors/structures/fndn_3x3.xml @@ -16,6 +16,7 @@ + diff --git a/binaries/data/mods/public/art/particles/construction_dust.xml b/binaries/data/mods/public/art/particles/construction_dust.xml new file mode 100644 index 0000000000..a6e08f9823 --- /dev/null +++ b/binaries/data/mods/public/art/particles/construction_dust.xml @@ -0,0 +1,30 @@ + + + + art/textures/particles/dust.png + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/binaries/data/mods/public/art/textures/particles/dust.png b/binaries/data/mods/public/art/textures/particles/dust.png new file mode 100644 index 0000000000..b4876da062 --- /dev/null +++ b/binaries/data/mods/public/art/textures/particles/dust.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:61cdbb5aab606b47444eb035f8a384835f1bafb549394b8b524b85039d494caa +size 4918 diff --git a/binaries/data/mods/public/simulation/components/Foundation.js b/binaries/data/mods/public/simulation/components/Foundation.js index 5c952b937f..c793fff434 100644 --- a/binaries/data/mods/public/simulation/components/Foundation.js +++ b/binaries/data/mods/public/simulation/components/Foundation.js @@ -13,6 +13,22 @@ Foundation.prototype.Init = function() this.committed = false; this.buildProgress = 0.0; // 0 <= progress <= 1 + + // Set up a timer so we can count the number of builders in a 1-second period. + // (We assume each builder only builds once per second, which is what UnitAI + // implements.) + var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); + this.timer = cmpTimer.SetInterval(this.entity, IID_Foundation, "UpdateTimeout", 1000, 1000, {}); + this.recentBuilders = []; // builder entities since the last timeout + this.numRecentBuilders = 0; // number of builder entities as of the last timeout +}; + +Foundation.prototype.UpdateTimeout = function() +{ + this.numRecentBuilders = this.recentBuilders.length; + this.recentBuilders = []; + + Engine.QueryInterface(this.entity, IID_Visual).SetVariable("numbuilders", this.numRecentBuilders); }; Foundation.prototype.InitialiseConstruction = function(owner, template) @@ -61,6 +77,10 @@ Foundation.prototype.OnDestroy = function() if (scaled) cmpPlayer.AddResource(r, scaled); } + + // Reset the timer + var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); + cmpTimer.CancelTimer(this.timer); }; /** @@ -119,6 +139,9 @@ Foundation.prototype.Build = function(builderEnt, work) var cmpCost = Engine.QueryInterface(this.entity, IID_Cost); var amount = work / cmpCost.GetBuildTime(); + // Record this builder so we can count the total number + this.recentBuilders.push(builderEnt); + // TODO: implement some kind of diminishing returns for multiple builders. // e.g. record the set of entities that build this, then every ~2 seconds // count them (and reset the list), and apply some function to the count to get diff --git a/source/graphics/Model.h b/source/graphics/Model.h index b4c4b86714..e5a12ae374 100644 --- a/source/graphics/Model.h +++ b/source/graphics/Model.h @@ -127,6 +127,12 @@ public: m_Props[i].m_Model->SetTerrainDirty(i0, j0, i1, j1); } + virtual void SetEntityVariable(const std::string& name, float value) + { + for (size_t i = 0; i < m_Props.size(); ++i) + m_Props[i].m_Model->SetEntityVariable(name, value); + } + // calculate object space bounds of this model, based solely on vertex positions void CalcObjectBounds(); // calculate bounds encompassing all vertex positions for given animation diff --git a/source/graphics/ModelAbstract.h b/source/graphics/ModelAbstract.h index 6bc586fac0..038314f790 100644 --- a/source/graphics/ModelAbstract.h +++ b/source/graphics/ModelAbstract.h @@ -69,6 +69,12 @@ public: */ virtual void SetTerrainDirty(ssize_t i0, ssize_t j0, ssize_t i1, ssize_t j1) = 0; + /** + * Called when the entity tries to set some variable to affect the display of this model + * and/or its child objects. + */ + virtual void SetEntityVariable(const std::string& UNUSED(name), float UNUSED(value)) { } + /** * Ensure that both the transformation and the bone * matrices are correct for this model and all its props. diff --git a/source/graphics/ParticleEmitter.cpp b/source/graphics/ParticleEmitter.cpp index f4d022b851..2fb7333632 100644 --- a/source/graphics/ParticleEmitter.cpp +++ b/source/graphics/ParticleEmitter.cpp @@ -172,6 +172,11 @@ void CParticleEmitter::AddParticle(const SParticle& particle) m_NextParticleIdx = (m_NextParticleIdx + 1) % m_Type->m_MaxParticles; } +void CParticleEmitter::SetEntityVariable(const std::string& name, float value) +{ + m_EntityVariables[name] = value; +} + CModelParticleEmitter::CModelParticleEmitter(const CParticleEmitterTypePtr& type) : @@ -185,6 +190,11 @@ CModelParticleEmitter::~CModelParticleEmitter() m_Emitter->Unattach(m_Emitter); } +void CModelParticleEmitter::SetEntityVariable(const std::string& name, float value) +{ + m_Emitter->SetEntityVariable(name, value); +} + CModelAbstract* CModelParticleEmitter::Clone() const { return new CModelParticleEmitter(m_Type); diff --git a/source/graphics/ParticleEmitter.h b/source/graphics/ParticleEmitter.h index 87dc99cd8a..184e8bbfec 100644 --- a/source/graphics/ParticleEmitter.h +++ b/source/graphics/ParticleEmitter.h @@ -107,9 +107,7 @@ public: */ void Unattach(const CParticleEmitterPtr& self); -private: - friend class CParticleEmitterType; - friend struct EmitterHasNoParticles; + void SetEntityVariable(const std::string& name, float value); CParticleEmitterTypePtr m_Type; @@ -118,12 +116,15 @@ private: CVector3D m_Pos; + std::map m_EntityVariables; + std::vector m_Particles; size_t m_NextParticleIdx; float m_LastUpdateTime; float m_EmissionTimer; +private: VertexArray m_VertexArray; VertexArray::Attribute m_AttributePos; VertexArray::Attribute m_AttributeAxis; @@ -157,6 +158,8 @@ public: { } + virtual void SetEntityVariable(const std::string& name, float value); + virtual void CalcBounds(); virtual void ValidatePosition(); virtual void InvalidatePosition(); diff --git a/source/graphics/ParticleEmitterType.cpp b/source/graphics/ParticleEmitterType.cpp index 6a093a9047..76787085a4 100644 --- a/source/graphics/ParticleEmitterType.cpp +++ b/source/graphics/ParticleEmitterType.cpp @@ -32,6 +32,7 @@ #include + /** * Interface for particle state variables, which get evaluated for each newly * constructed particle. @@ -43,9 +44,9 @@ public: virtual ~IParticleVar() {} /// Computes and returns a new value. - float Evaluate(CParticleEmitterType& type) + float Evaluate(CParticleEmitter& emitter) { - m_LastValue = Compute(type); + m_LastValue = Compute(*emitter.m_Type, emitter); return m_LastValue; } @@ -64,7 +65,7 @@ public: virtual float Max(CParticleEmitterType& type) = 0; protected: - virtual float Compute(CParticleEmitterType& type) = 0; + virtual float Compute(CParticleEmitterType& type, CParticleEmitter& emitter) = 0; private: float m_LastValue; @@ -81,7 +82,7 @@ public: { } - virtual float Compute(CParticleEmitterType& UNUSED(type)) + virtual float Compute(CParticleEmitterType& UNUSED(type), CParticleEmitter& UNUSED(emitter)) { return m_Value; } @@ -106,7 +107,7 @@ public: { } - virtual float Compute(CParticleEmitterType& type) + virtual float Compute(CParticleEmitterType& type, CParticleEmitter& UNUSED(emitter)) { return boost::uniform_real<>(m_Min, m_Max)(type.m_Manager.m_RNG); } @@ -133,7 +134,7 @@ public: { } - virtual float Compute(CParticleEmitterType& type) + virtual float Compute(CParticleEmitterType& type, CParticleEmitter& UNUSED(emitter)) { return type.m_Variables[m_From]->LastValue(); } @@ -147,6 +148,75 @@ private: int m_From; }; +/** + * A terrible ad-hoc attempt at handling some particular variable calculation, + * which really needs to be cleaned up and generalised. + */ +class CParticleVarExpr : public IParticleVar +{ +public: + CParticleVarExpr(const CStr& from, float mul, float max) : + m_From(from), m_Mul(mul), m_Max(max) + { + } + + virtual float Compute(CParticleEmitterType& UNUSED(type), CParticleEmitter& emitter) + { + return std::min(m_Max, emitter.m_EntityVariables[m_From] * m_Mul); + } + + virtual float Max(CParticleEmitterType& UNUSED(type)) + { + return m_Max; + } + +private: + CStr m_From; + float m_Mul; + float m_Max; +}; + + + +/** + * Interface for particle effectors, which get evaluated every frame to + * update particles. + */ +class IParticleEffector +{ +public: + IParticleEffector() { } + virtual ~IParticleEffector() {} + + /// Updates all particles. + virtual void Evaluate(std::vector& particles, float dt) = 0; +}; + +/** + * Particle effector that applies a constant acceleration. + */ +class CParticleEffectorForce : public IParticleEffector +{ +public: + CParticleEffectorForce(float x, float y, float z) : + m_Accel(x, y, z) + { + } + + virtual void Evaluate(std::vector& particles, float dt) + { + CVector3D dv = m_Accel * dt; + + for (size_t i = 0; i < particles.size(); ++i) + particles[i].velocity += dv; + } + +private: + CVector3D m_Accel; +}; + + + CParticleEmitterType::CParticleEmitterType(const VfsPath& path, CParticleManager& manager) : m_Manager(manager) @@ -162,6 +232,9 @@ int CParticleEmitterType::GetVariableID(const std::string& name) { if (name == "emissionrate") return VAR_EMISSIONRATE; if (name == "lifetime") return VAR_LIFETIME; + if (name == "position.x") return VAR_POSITION_X; + if (name == "position.y") return VAR_POSITION_Y; + if (name == "position.z") return VAR_POSITION_Z; if (name == "angle") return VAR_ANGLE; if (name == "velocity.x") return VAR_VELOCITY_X; if (name == "velocity.y") return VAR_VELOCITY_Y; @@ -182,6 +255,9 @@ bool CParticleEmitterType::LoadXML(const VfsPath& path) m_Variables.resize(VAR__MAX); m_Variables[VAR_EMISSIONRATE] = IParticleVarPtr(new CParticleVarConstant(10.f)); m_Variables[VAR_LIFETIME] = IParticleVarPtr(new CParticleVarConstant(3.f)); + m_Variables[VAR_POSITION_X] = IParticleVarPtr(new CParticleVarConstant(0.f)); + m_Variables[VAR_POSITION_Y] = IParticleVarPtr(new CParticleVarConstant(0.f)); + m_Variables[VAR_POSITION_Z] = IParticleVarPtr(new CParticleVarConstant(0.f)); m_Variables[VAR_ANGLE] = IParticleVarPtr(new CParticleVarConstant(0.f)); m_Variables[VAR_VELOCITY_X] = IParticleVarPtr(new CParticleVarConstant(0.f)); m_Variables[VAR_VELOCITY_Y] = IParticleVarPtr(new CParticleVarConstant(1.f)); @@ -210,12 +286,18 @@ bool CParticleEmitterType::LoadXML(const VfsPath& path) EL(constant); EL(uniform); EL(copy); + EL(expr); + EL(force); AT(mode); AT(name); AT(value); AT(min); AT(max); + AT(mul); AT(from); + AT(x); + AT(y); + AT(z); #undef AT #undef EL @@ -285,6 +367,22 @@ bool CParticleEmitterType::LoadXML(const VfsPath& path) if (id != -1 && from != -1) m_Variables[id] = IParticleVarPtr(new CParticleVarCopy(from)); } + else if (Child.GetNodeName() == el_expr) + { + int id = GetVariableID(Child.GetAttributes().GetNamedItem(at_name)); + CStr from = Child.GetAttributes().GetNamedItem(at_from); + float mul = Child.GetAttributes().GetNamedItem(at_mul).ToFloat(); + float max = Child.GetAttributes().GetNamedItem(at_max).ToFloat(); + if (id != -1) + m_Variables[id] = IParticleVarPtr(new CParticleVarExpr(from, mul, max)); + } + else if (Child.GetNodeName() == el_force) + { + float x = Child.GetAttributes().GetNamedItem(at_x).ToFloat(); + float y = Child.GetAttributes().GetNamedItem(at_y).ToFloat(); + float z = Child.GetAttributes().GetNamedItem(at_z).ToFloat(); + m_Effectors.push_back(IParticleEffectorPtr(new CParticleEffectorForce(x, y, z))); + } } return true; @@ -299,12 +397,13 @@ void CParticleEmitterType::UpdateEmitter(CParticleEmitter& emitter, float dt) if (emitter.m_Active) { - float emissionRate = m_Variables[VAR_EMISSIONRATE]->Evaluate(*this); + float emissionRate = m_Variables[VAR_EMISSIONRATE]->Evaluate(emitter); // Find how many new particles to spawn, and accumulate any rounding errors into EmissionTimer emitter.m_EmissionTimer += dt; int newParticles = floor(emitter.m_EmissionTimer * emissionRate); - emitter.m_EmissionTimer -= newParticles / emissionRate; + if (newParticles) // avoid divide-by-zero if emissionRate == 0 + emitter.m_EmissionTimer -= newParticles / emissionRate; // If dt was very large, there's no point spawning new particles that // we'll immediately overwrite, so clamp it @@ -314,20 +413,29 @@ void CParticleEmitterType::UpdateEmitter(CParticleEmitter& emitter, float dt) { // Compute new particle state based on variables SParticle particle; - particle.pos = emitter.m_Pos; - particle.velocity.X = m_Variables[VAR_VELOCITY_X]->Evaluate(*this); - particle.velocity.Y = m_Variables[VAR_VELOCITY_Y]->Evaluate(*this); - particle.velocity.Z = m_Variables[VAR_VELOCITY_Z]->Evaluate(*this); - particle.angle = m_Variables[VAR_ANGLE]->Evaluate(*this); - particle.angleSpeed = m_Variables[VAR_VELOCITY_ANGLE]->Evaluate(*this); - particle.size = m_Variables[VAR_SIZE]->Evaluate(*this); + + particle.pos.X = m_Variables[VAR_POSITION_X]->Evaluate(emitter); + particle.pos.Y = m_Variables[VAR_POSITION_Y]->Evaluate(emitter); + particle.pos.Z = m_Variables[VAR_POSITION_Z]->Evaluate(emitter); + particle.pos += emitter.m_Pos; + + particle.velocity.X = m_Variables[VAR_VELOCITY_X]->Evaluate(emitter); + particle.velocity.Y = m_Variables[VAR_VELOCITY_Y]->Evaluate(emitter); + particle.velocity.Z = m_Variables[VAR_VELOCITY_Z]->Evaluate(emitter); + + particle.angle = m_Variables[VAR_ANGLE]->Evaluate(emitter); + particle.angleSpeed = m_Variables[VAR_VELOCITY_ANGLE]->Evaluate(emitter); + + particle.size = m_Variables[VAR_SIZE]->Evaluate(emitter); + RGBColor color; - color.X = m_Variables[VAR_COLOR_R]->Evaluate(*this); - color.Y = m_Variables[VAR_COLOR_G]->Evaluate(*this); - color.Z = m_Variables[VAR_COLOR_B]->Evaluate(*this); + color.X = m_Variables[VAR_COLOR_R]->Evaluate(emitter); + color.Y = m_Variables[VAR_COLOR_G]->Evaluate(emitter); + color.Z = m_Variables[VAR_COLOR_B]->Evaluate(emitter); particle.color = ConvertRGBColorTo4ub(color); + particle.age = 0.f; - particle.maxAge = m_Variables[VAR_LIFETIME]->Evaluate(*this); + particle.maxAge = m_Variables[VAR_LIFETIME]->Evaluate(emitter); emitter.AddParticle(particle); } @@ -349,5 +457,8 @@ void CParticleEmitterType::UpdateEmitter(CParticleEmitter& emitter, float dt) p.color.A = clamp((int)(a*255.f), 0, 255); } - // TODO: maybe we want to add forces like gravity or wind + for (size_t i = 0; i < m_Effectors.size(); ++i) + { + m_Effectors[i]->Evaluate(emitter.m_Particles, dt); + } } diff --git a/source/graphics/ParticleEmitterType.h b/source/graphics/ParticleEmitterType.h index 6e2ad1a422..bee6d79417 100644 --- a/source/graphics/ParticleEmitterType.h +++ b/source/graphics/ParticleEmitterType.h @@ -25,6 +25,7 @@ class CParticleEmitter; class CParticleManager; class IParticleVar; +class IParticleEffector; /** * Particle emitter type - stores the common state data for all emitters of that @@ -53,6 +54,9 @@ private: { VAR_EMISSIONRATE, VAR_LIFETIME, + VAR_POSITION_X, + VAR_POSITION_Y, + VAR_POSITION_Z, VAR_ANGLE, VAR_VELOCITY_X, VAR_VELOCITY_Y, @@ -82,6 +86,9 @@ private: typedef shared_ptr IParticleVarPtr; std::vector m_Variables; + typedef shared_ptr IParticleEffectorPtr; + std::vector m_Effectors; + CParticleManager& m_Manager; }; diff --git a/source/simulation2/components/CCmpVisualActor.cpp b/source/simulation2/components/CCmpVisualActor.cpp index cb147d0dc7..b19b8f4f19 100644 --- a/source/simulation2/components/CCmpVisualActor.cpp +++ b/source/simulation2/components/CCmpVisualActor.cpp @@ -327,6 +327,14 @@ public: UNUSED2(a); // TODO: why is this even an argument? } + virtual void SetVariable(std::string name, float value) + { + if (!m_Unit) + return; + + m_Unit->GetModel().SetEntityVariable(name, value); + } + virtual void Hotload(const VfsPath& name) { if (!m_Unit) diff --git a/source/simulation2/components/ICmpVisual.cpp b/source/simulation2/components/ICmpVisual.cpp index e4697581f4..50e3098977 100644 --- a/source/simulation2/components/ICmpVisual.cpp +++ b/source/simulation2/components/ICmpVisual.cpp @@ -27,4 +27,5 @@ DEFINE_INTERFACE_METHOD_1("SelectMovementAnimation", void, ICmpVisual, SelectMov DEFINE_INTERFACE_METHOD_1("SetAnimationSyncRepeat", void, ICmpVisual, SetAnimationSyncRepeat, float) DEFINE_INTERFACE_METHOD_1("SetAnimationSyncOffset", void, ICmpVisual, SetAnimationSyncOffset, float) DEFINE_INTERFACE_METHOD_4("SetShadingColour", void, ICmpVisual, SetShadingColour, fixed, fixed, fixed, fixed) +DEFINE_INTERFACE_METHOD_2("SetVariable", void, ICmpVisual, SetVariable, std::string, float) END_INTERFACE_WRAPPER(Visual) diff --git a/source/simulation2/components/ICmpVisual.h b/source/simulation2/components/ICmpVisual.h index cf5c31366d..68f1108288 100644 --- a/source/simulation2/components/ICmpVisual.h +++ b/source/simulation2/components/ICmpVisual.h @@ -102,6 +102,12 @@ public: */ virtual void SetShadingColour(fixed r, fixed g, fixed b, fixed a) = 0; + /** + * Set an arbitrarily-named variable that the model may use to alter its appearance + * (e.g. in particle emitter parameter computations). + */ + virtual void SetVariable(std::string name, float value) = 0; + /** * Called when an actor file has been modified and reloaded dynamically. * If this component uses the named actor file, it should regenerate its actor