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