1
0
forked from 0ad/0ad

Add dust particles on under-construction buildings, varying based on number of active builders.

This was SVN commit r9175.
This commit is contained in:
Ykkrosh 2011-04-06 00:11:40 +00:00
parent 2fd4c62cbb
commit d03559f2c9
14 changed files with 247 additions and 23 deletions

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<actor version="1">
<group>
<variant name="Base">
<particles file="construction_dust.xml"/>
</variant>
</group>
<material>basic_trans.xml</material>
</actor>

View File

@ -16,6 +16,7 @@
<variant frequency="1">
<props>
<prop actor="props/structures/decals/dirt_small.xml" attachpoint="root"/>
<prop actor="particle/construction_dust.xml" attachpoint="root"/>
</props>
</variant>
</group>

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<particles>
<texture>art/textures/particles/dust.png</texture>
<blend mode="over"/>
<expr name="emissionrate" from="numbuilders" mul="5.0" max="50.0"/>
<uniform name="lifetime" min="4.0" max="6.0"/>
<uniform name="position.x" min="-8.0" max="8.0"/>
<uniform name="position.z" min="-8.0" max="8.0"/>
<constant name="position.y" value="0.0"/>
<uniform name="angle" min="-3.14" max="3.14"/>
<uniform name="velocity.x" min="-1.5" max="1.5"/>
<uniform name="velocity.y" min="4.0" max="4.5"/>
<uniform name="velocity.z" min="-1.5" max="1.5"/>
<uniform name="velocity.angle" min="-2.0" max="2.0"/>
<uniform name="size" min="2.5" max="3.5"/>
<uniform name="color.r" min="0.5" max="1.0"/>
<copy name="color.g" from="color.r"/>
<copy name="color.b" from="color.r"/>
<force y="-3"/>
</particles>

BIN
binaries/data/mods/public/art/textures/particles/dust.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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);

View File

@ -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<std::string, float> m_EntityVariables;
std::vector<SParticle> 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();

View File

@ -32,6 +32,7 @@
#include <boost/random/uniform_real.hpp>
/**
* 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<SParticle>& 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<SParticle>& 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);
}
}

View File

@ -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<IParticleVar> IParticleVarPtr;
std::vector<IParticleVarPtr> m_Variables;
typedef shared_ptr<IParticleEffector> IParticleEffectorPtr;
std::vector<IParticleEffectorPtr> m_Effectors;
CParticleManager& m_Manager;
};

View File

@ -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)

View File

@ -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)

View File

@ -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