@sky-foundry/two.js
Version:
A renderer agnostic two-dimensional drawing api for the web.
316 lines (216 loc) • 6.95 kB
JavaScript
(function(Two) {
var _ = Two.Utils;
var Path = Two.Path;
var Rectangle = Two.Rectangle;
var ImageSequence = Two.ImageSequence = function(paths, ox, oy, frameRate) {
Path.call(this, [
new Two.Anchor(),
new Two.Anchor(),
new Two.Anchor(),
new Two.Anchor()
], true);
this._renderer.flagTextures = _.bind(ImageSequence.FlagTextures, this);
this._renderer.bindTextures = _.bind(ImageSequence.BindTextures, this);
this._renderer.unbindTextures = _.bind(ImageSequence.UnbindTextures, this);
this.noStroke();
this.noFill();
this.textures = _.map(paths, ImageSequence.GenerateTexture, this);
this.origin = new Two.Vector();
this._update();
this.translation.set(ox || 0, oy || 0);
if (_.isNumber(frameRate)) {
this.frameRate = frameRate;
} else {
this.frameRate = ImageSequence.DefaultFrameRate;
}
};
_.extend(ImageSequence, {
Properties: [
'frameRate',
'index'
],
DefaultFrameRate: 30,
FlagTextures: function() {
this._flagTextures = true;
},
BindTextures: function(items) {
var i = items.length;
while (i--) {
items[i].bind(Two.Events.change, this._renderer.flagTextures);
}
this._renderer.flagTextures();
},
UnbindTextures: function(items) {
var i = items.length;
while (i--) {
items[i].unbind(Two.Events.change, this._renderer.flagTextures);
}
this._renderer.flagTextures();
},
MakeObservable: function(obj) {
Rectangle.MakeObservable(obj);
_.each(ImageSequence.Properties, Two.Utils.defineProperty, obj);
Object.defineProperty(obj, 'textures', {
enumerable: true,
get: function() {
return this._textures;
},
set: function(textures) {
var updateTextures = this._renderer.flagTextures;
var bindTextures = this._renderer.bindTextures;
var unbindTextures = this._renderer.unbindTextures;
// Remove previous listeners
if (this._textures) {
this._textures
.unbind(Two.Events.insert, bindTextures)
.unbind(Two.Events.remove, unbindTextures);
}
// Create new Collection with copy of vertices
this._textures = new Two.Utils.Collection((textures || []).slice(0));
// Listen for Collection changes and bind / unbind
this._textures
.bind(Two.Events.insert, bindTextures)
.bind(Two.Events.remove, unbindTextures);
// Bind Initial Textures
bindTextures(this._textures);
}
});
},
GenerateTexture: function(obj) {
if (obj instanceof Two.Texture) {
return obj;
} else if (_.isString(obj)) {
return new Two.Texture(obj);
}
}
});
_.extend(ImageSequence.prototype, Rectangle.prototype, {
_flagTextures: false,
_flagFrameRate: false,
_flagIndex: false,
// Private variables
_amount: 1,
_duration: 0,
_index: 0,
_startTime: 0,
_playing: false,
_firstFrame: 0,
_lastFrame: 0,
_loop: true,
// Exposed through getter-setter
_textures: null,
_frameRate: 0,
_origin: null,
constructor: ImageSequence,
play: function(firstFrame, lastFrame, onLastFrame) {
this._playing = true;
this._firstFrame = 0;
this._lastFrame = this.amount - 1;
this._startTime = _.performance.now();
if (_.isNumber(firstFrame)) {
this._firstFrame = firstFrame;
}
if (_.isNumber(lastFrame)) {
this._lastFrame = lastFrame;
}
if (_.isFunction(onLastFrame)) {
this._onLastFrame = onLastFrame;
} else {
delete this._onLastFrame;
}
if (this._index !== this._firstFrame) {
this._startTime -= 1000 * Math.abs(this._index - this._firstFrame)
/ this._frameRate;
}
return this;
},
pause: function() {
this._playing = false;
return this;
},
stop: function() {
this._playing = false;
this._index = 0;
return this;
},
clone: function(parent) {
var clone = new ImageSequence(this.textures, this.translation.x,
this.translation.y, this.frameRate)
clone._loop = this._loop;
if (this._playing) {
clone.play();
}
if (parent) {
parent.add(clone);
}
return clone;
},
_update: function() {
var effects = this._textures;
var width, height, elapsed, amount, duration, texture;
var index, frames;
if (this._flagTextures) {
this._amount = effects.length;
}
if (this._flagFrameRate) {
this._duration = 1000 * this._amount / this._frameRate;
}
if (this._playing && this._frameRate > 0) {
amount = this._amount;
if (_.isNaN(this._lastFrame)) {
this._lastFrame = amount - 1;
}
// TODO: Offload perf logic to instance of `Two`.
elapsed = _.performance.now() - this._startTime;
frames = this._lastFrame + 1;
duration = 1000 * (frames - this._firstFrame) / this._frameRate;
if (this._loop) {
elapsed = elapsed % duration;
} else {
elapsed = Math.min(elapsed, duration);
}
index = _.lerp(this._firstFrame, frames, elapsed / duration);
index = Math.floor(index);
if (index !== this._index) {
this._index = index;
texture = effects[this._index];
if (texture.loaded) {
width = texture.image.width;
height = texture.image.height;
if (this.width !== width) {
this.width = width;
}
if (this.height !== height) {
this.height = height;
}
this.fill = texture;
if (index >= this._lastFrame - 1 && this._onLastFrame) {
this._onLastFrame(); // Shortcut for chainable sprite animations
}
}
}
} else if (this._flagIndex || !(this.fill instanceof Two.Texture)) {
texture = effects[this._index];
if (texture.loaded) {
width = texture.image.width;
height = texture.image.height;
if (this.width !== width) {
this.width = width;
}
if (this.height !== height) {
this.height = height;
}
}
this.fill = texture;
}
Rectangle.prototype._update.call(this);
return this;
},
flagReset: function() {
this._flagTextures = this._flagFrameRate = false;
Rectangle.prototype.flagReset.call(this);
return this;
}
});
ImageSequence.MakeObservable(ImageSequence.prototype);
})((typeof global !== 'undefined' ? global : (this || window)).Two);