I am building a library that uses animations in a canvas.
I need to apply effects to some objects in the canvas and I came up with an effect stack that contains each effect to be executed on a object. Each object has it's own effect stack, because each effect has some random values to them.
As each effect needs to be executed on a set of frames, I need those to get back in the effect stack to be continued in the next frame.
Here is what I want:
Note that each effect in the stack is consumed once. If the effect is not completed, it is reinserted on the stack.
I came up with a couple of solutions:
- I
pop()
the effect, consume it andunshift()
it in the stack. Reading only the top of the stackn
times (n -> nb of effects). The opposite is also possible:
shift()
->consume()
->push()
The problem with these 2, is the
shift()
and theunshift()
as it copies all the values in memory to another location. I'd prefer to usepop()
orpush()
only.I use 2 stacks. One for the current effects to be consumed and the other for the effects that are consumed. And each frame, I copy the values from #2 to #1, then reset the #2.
But now, I have to create 2 stacks for each object and add a function to process those, which complicates all the code that needs access to these effects.
A basic implementation for the second solution is:
var EffectStack = function() {
this.stack = [];
this.queued = [];
}
EffectStack.prototype.length = function() {
return this.stack.length;
};
EffectStack.prototype.push = function( fx ) {
this.stack.push( fx );
};
EffectStack.prototype.pop = function( fx ) {
return this.stack.pop( fx );
};
EffectStack.prototype.update = function() {
this.stack = this.queued.slice(0);
this.queued = [];
};
EffectStack.prototype.continue = function( fx ) {
this.queued.push( fx );
};
And for the animated object:
/**
* Applies each effect and updates the Effect Stack by queuing
* the effects that weren't complete.
*/
AnimObject.fn.consumeEffects = function() {
var fx
while( this.eStack.length() > 0 ) {
// fx = {
// func: function(){ ...core of the effect... },
// data: { ... }
// }
fx = this.eStack.pop();
fx.func( this, fx );
}
this.eStack.update();
}
Effect Example
/**
* This is an Effect (fx).
*/
changeOpacity: function( value, step ){
return {
// Basic information.
data: {
step: step ? step : Math.random()*0.06 + 0.04,
targetValue: value ? value : Math.random()*0.7 + 0.2
},
// The core of the effect.
func: function( particle, fx ){
if(!FX.opacity_FUNC( animObj, fx.data )) {
// **IMPORTANT**
// Add to stack if the effect isn't over.
animObj.eStack.continue({
data: fx.data,
func: fx.func
});
}
}
}
}
/**
* -- THIS IS A SUB_EFFECT. IT CONTAINS THE CORE LOGIC --
* -- SUB_EFFECTS CAN BE MIXED TOGETHER. --
* Changes the opacity of a AnimObject that is floating.
*
* @param animObj : Affected AnimObject.
* @param data : Transformation data.
*/
opacity_FUNC: function( animObj, data ) {
// Distance to targetValue.
var _delta = (animObj.opacity - data.targetValue);
// If the <animObj> still validates the conditions,
// continue the effect till it's done.
if ((animObj.isFloating || animObj.isMoving) && Math.abs(_delta) > 0.1)
{
animObj.opacity += data.step * (_delta > 0 ? -1 : 1);
return false;
}
// Effect is over.
else
{
animObj.opacity = data.targetValue;
return true;
}
},
Here is a little jsfiddle of what my sandbox looks like.
But, I still need to copy the values. Any better ideas?