/**
* @class SpriteSheet
* @memberof SQR
*
* @description Utility to create sprite sheets for 2d animations.
* See avaialble code samples:
* <ul>
* <li><a href="../tutorials/sprite-sheet.html">rendering a sprite sheet on canvas 2d</a></li>
* <li>rendering a sprite sheet in webgl (coming soon)</li>
* </ul>
*
* @property {HTMLCanvasElement} canvas - the canves on which the sprite-sheet is drawn
* @property {Number} numFrames - the number for frames (rows x cols)
* @property {Number} rows - the number of rows as defined in {@link SQR.SpriteSheet#layout}
* @property {Number} columns - the number of columns as defined in {@link SQR.SpriteSheet#layout}
* @property {Number} size - the size of the sprite sheet square as defined in {@link SQR.SpriteSheet#layout}
*/
SQR.SpriteSheet = function() {
var s = {};
var rows, cols, size, options;
var cnv = document.createElement('canvas');
var ctx = cnv.getContext('2d');
s.canvas = cnv;
s.frame = 0;
/**
* Define the layout of the sprite sheet. The number of rows and columns is
* arbitary, but it impacts the number of frames in the animaction, which is
* equal to the number product of those values (cols x rows). Typically it's better
* to create balanced sprite sheets that have roughly the same amount of rows as columns,
* and avoid creating very long sheets iwth ex lots of rows and only one column. Thanks to this
* you can avoid hitting the max canvas size limitation, esp on mobile.
*
* Another limitations is that currently all cells need to be square and are defined by a single
* size value below. Since it's not optimal for rectangular animations, future versions will
* implement both width and height separately.
*
* @param {Number} _rows - the number of rows in the spritesheet
* @param {Number} _cols - the number of columns in the spritesheet
* @param {Number} _size - the size of each cell. All cells in spritesheets are square
*
* @method layout
* @memberof SQR.SpriteSheet.prototype
*/
s.layout = function(_rows, _cols, _size) {
rows = _rows, cols = _cols, size = _size;
cnv.width = cols * size;
cnv.height = rows * size;
s.numFrames = rows * cols;
s.rows = rows;
s.cols = cols;
s.size = size;
return s;
}
/**
* Set misc options for the spritesheet, which include:
* <ul>
* <li>bgcolor - a css color to use as background (default is transparent)</li>
* <li>webglFlipY - set to true if spritesheet is used as webgl texture</li>
* </ul>
*
* @method options
* @memberof SQR.SpriteSheet.prototype
*/
s.options = function(_options) {
options = _options;
return s;
}
/**
* Draws a single frame to a canvas element. Can be used manually, but typically using
* `run` below is recommended. The `run` function returns this function but wraps it in a
* closure with a `setInterval` call for continous animated rendering.
*
* @param {CanvasRenderingContext2D} context - context 2d of the canvas to draw the sprite to
* @param {Number} frame - the frame number to draw
*
* @method renderToCanvas
* @memberof SQR.SpriteSheet.prototype
*/
s.renderToCanvas = function(context, frame) {
var row = cols == 1 ? frame : Math.floor(frame / cols);
var col = frame % cols;
context.translate(size / -2, size / -2);
context.drawImage(cnv,
col * size, row * size, size, size,
0, 0, size, size);
}
/**
* The srite sheet drawing function.
* The drawing function receives the following parameters:
*
* <ul>
* <li>ctx - the context of the sprite sheet canvas to draw on</li>
* <li>frame - the number of the frame to draw</li>
* </ul>
*
* On top of that
*
* The drawing is called for each frame of the sprite sheet and expects that the
* implementing code will draw each consecivute frame at each call.
*
* The context already comes transformed (translated) onto the current spot
* for the given frame, so just start drawing at 0,0. The center of the sprite
* is at size/2 x size/2.
*
* @param {function} callback - the implementation of the drawing function
*
* @method draw
* @memberof SQR.SpriteSheet.prototype
*/
s.draw = function(callback) {
if(options && options.bgcolor !== undefined) {
ctx.fillStyle = options.bgcolor;
ctx.fillRect(0, 0, cols * size, rows * size);
} else {
ctx.fillStyle = 'rgba(0, 0, 0, 0)';
ctx.fillRect(0, 0, cols * size, rows * size);
}
if(!callback) return s;
for(var y = 0; y < rows; y++) {
for(var x = 0; x < cols; x++) {
ctx.save();
var yp = (options && options.webglFlipY) ? (rows-y-1) * size : y * size;
ctx.translate(x * size, yp);
callback.call(this, ctx, y * cols + x);
ctx.restore();
}
}
return s;
}
/**
* Assign the return value of this function to the {@link SQR.Transform2d} shape
* property for rendering. See {@tutorial canvas-rendering} for details.
*
* @method run
* @memberof SQR.SpriteSheet.prototype
*
* @param {Number} framerate - the frame rate of the animation in ms (default 1000/60, i.e. 60FPS)
* @param {Number} loop - number of times the animation should loop. (default -1, i.e. infinite)
*
* @return {Function} rendering function (renderToCanvas above) that can be used as shape property of {@link SQR.Transform2d} instance.
* The function has a propeorty called 'stop' which is also a function and can be called anytime to halt the animation.
*
* @example
// Assumes the sheet is draw and ready to use (see link to example above)
var sheet = SQR.SpriteSheet();
// Create a host transform
var sprite = new SQR.Transform2d();
sprite.position.set(100, 100);
// Run at 30fps, loop 10 times.
sprite.shape = sheet.run(1000/30, 10);
*/
s.run = function(framerate, loop) {
var f = 0, l = loop || -1;
console.log(l);
framerate = framerate || 1000/60;
var runner = function(ctx) {
s.renderToCanvas(ctx, f);
}
var intervalId = setInterval(function() {
f++;
if(f >= s.numFrames) {
if(l != 0) {
f = 0;
l--;
} else {
runner.stop();
}
}
}, framerate);
runner.stop = function(runner) {
clearInterval(intervalId);
}
return runner;
}
return s;
}