diff --git a/README b/README new file mode 100644 index 0000000..e69de29 diff --git a/benchmark.html b/benchmark.html new file mode 100644 index 0000000..0ae0e53 --- /dev/null +++ b/benchmark.html @@ -0,0 +1,130 @@ + + + + + HAF • HTML5 Animation Framework • Benchmark + + + + + + + Size: + Actors: + Actor type:
+ Clear: + Dirty actors: + + + + diff --git a/js/benchmark.js b/js/benchmark.js new file mode 100644 index 0000000..8b55881 --- /dev/null +++ b/js/benchmark.js @@ -0,0 +1,92 @@ +var Benchmark = OZ.Class().extend(HAF.Actor); + +Benchmark.CIRCLE = 0; +Benchmark.SQUARE = 1; +Benchmark.SQUARE_ALIGNED = 2; +Benchmark.SPRITE = 3; +Benchmark.CANVAS = 4; + +Benchmark.image = OZ.DOM.elm("img", {src:"zombie.png"}); +Benchmark.canvas = OZ.DOM.elm("canvas", {width:64, height:64}); +OZ.Event.add(Benchmark.image, "load", function(e) { + var context = Benchmark.canvas.getContext("2d"); + context.drawImage( + Benchmark.image, + 64*4, 0, 64, 64, + 0, 0, 64, 64 + ); +}); + +Benchmark.prototype.init = function(type) { + this._type = null; + this._position = null; + this._size = [64, 64]; + + + var colors = ["red", "green", "blue", "black", "yellow", "cyan", "magenta", "orange"]; + this._color = colors[Math.floor(Math.random()*colors.length)]; + this._frame = 0; + this.setType(type); +} + +Benchmark.prototype.tick = function() { + this._frame++; + return true; +} + +Benchmark.prototype.getBox = function() { + return [ + [this._position[0], this._position[1]], + this._size + ]; +} + +Benchmark.prototype.setType = function(type) { + this._type = type; + this._position = null; + return this; +} + +Benchmark.prototype.draw = function(context) { + if (!this._position || this._type != Benchmark.SPRITE) { + this._position = [ + ~~(Math.random()*context.canvas.width), + ~~(Math.random()*context.canvas.height) + ]; + } + + switch (this._type) { + case Benchmark.CIRCLE: + context.strokeStyle = this._color; + context.beginPath(); + context.arc(this._position[0] + this._size[0]/2, this._position[1] + this._size[1]/2, 10, 0, 2*Math.PI, false); + context.stroke(); + break; + + case Benchmark.SQUARE: + context.strokeStyle = this._color; + context.strokeRect(this._position[0] + this._size[0]/2 - 10, this._position[1] + this._size[1]/2 - 10, 20, 20); + break; + + case Benchmark.SQUARE_ALIGNED: + context.strokeStyle = this._color; + context.strokeRect(this._position[0] + this._size[0]/2 - 10.5, this._position[1] + this._size[1]/2 - 10.5, 20, 20); + break; + + case Benchmark.SPRITE: + var frame = Math.floor(this._frame / 5) % 8; + context.drawImage( + this.constructor.image, + (4 + frame)*this._size[0], 0, this._size[0], this._size[1], + this._position[0], this._position[1], this._size[0], this._size[1] + ); + break; + + case Benchmark.CANVAS: + context.drawImage( + this.constructor.canvas, + this._position[0], this._position[1] + ); + break; + } +} diff --git a/js/haf.js b/js/haf.js new file mode 100644 index 0000000..ce2f5de --- /dev/null +++ b/js/haf.js @@ -0,0 +1,690 @@ +if (!Date.now) { + Date.now = function() { return +(new Date); } +} + +var HAF = {}; + +HAF.CLEAR_ALL = 0; /* clearRect on whole canvas */ +HAF.CLEAR_NONE = 1; /* no clearing */ +HAF.CLEAR_ACTORS = 2; /* clear actors */ + +HAF.DIRTY_ALL = 0; /* (re-)draw all actors */ +HAF.DIRTY_CHANGED = 1; /* (re-)draw only changed actors */ + +/** + * @class Base animation director + */ +HAF.Engine = OZ.Class(); +HAF.Engine.prototype.init = function(size, options) { + this._size = null; + this._options = { + fps: 60, + id: "haf", + debug: false + }; + for (var p in options) { this._options[p] = options[p]; } + + this._ts = { + tick: 0, + draw: 0 + } + + this._running = false; + this._container = OZ.DOM.elm("div", {id:this._options.id, position:"relative"}); + this._layers = {}; + this._schedule = null; + this.draw = this.draw.bind(this); + this.tick = this.tick.bind(this); + this.setSize(size || [0, 0]); + + var prefixes = ["", "moz", "webkit", "ms"]; + for (var i=0;i= box2[0][0]+box2[1][0]) { return false; } /* box1 right of box2 */ + if (box1[0][1]+box1[1][1] <= box2[0][1]) { return false; } /* box1 top of box2 */ + if (box1[0][1] >= box2[0][1]+box2[1][1]) { return false; } /* box1 bottom of box2 */ + return true; +} + +/** + * Abstract FPS monitor + */ +HAF.Monitor = OZ.Class(); +HAF.Monitor.prototype.init = function(engine, size, event, options) { + this._size = size; + this._options = { + textColor: "#000", + chart: true + }; + for (var p in options) { this._options[p] = options[p]; } + this._canvas = OZ.DOM.elm("canvas", {width:size[0], height:size[1], className:"monitor"}); + this._ctx = this._canvas.getContext("2d"); + this._ctx.textBaseline = "top"; + this._ctx.font = "10px monospace"; + + this._data = []; + this._avg = []; + this._paused = false; + + OZ.Event.add(this._canvas, "click", this._click.bind(this)); + OZ.Event.add(engine, "start", this._start.bind(this)); + OZ.Event.add(engine, event, this._event.bind(this)); +} + +HAF.Monitor.prototype.getContainer = function() { + return this._canvas; +} + +HAF.Monitor.prototype._click = function(e) { + this._paused = !this._paused; +} + +HAF.Monitor.prototype._start = function(e) { + this._data = []; +} + +HAF.Monitor.prototype._event = function(e) { + if (this._data.length > this._size[0]) { this._data.shift(); } + this._avg.push(this._data[this._data.length-1]); + if (this._avg.length > 30) { this._avg.shift(); } + if (!this._paused) { this._draw(); } +} + +HAF.Monitor.prototype._draw = function() { + this._ctx.clearRect(0, 0, this._size[0], this._size[1]); +} + +HAF.Monitor.prototype._drawSet = function(index, color) { + if (!this._options.chart) { return; } + this._ctx.beginPath(); + var i = this._data.length; + var w = this._size[0]; + var h = this._size[1]-0.5; + while (i--) { + this._ctx.lineTo(w--, h-this._data[i][index]); + } + this._ctx.strokeStyle = color; + this._ctx.stroke(); +} + +/** + * Draw monitor + */ +HAF.Monitor.Draw = OZ.Class().extend(HAF.Monitor); +HAF.Monitor.Draw.prototype.init = function(engine, size, options) { + HAF.Monitor.prototype.init.call(this, engine, size, "draw", options); +} + +HAF.Monitor.Draw.prototype._event = function(e) { + var frac = e.data.drawn/e.data.all; + frac *= (this._size[1]-1); + this._data.push([e.data.delay, e.data.time, Math.round(frac), e.data.drawn, e.data.all]); + HAF.Monitor.prototype._event.call(this, e); +} + +HAF.Monitor.Draw.prototype._draw = function() { + HAF.Monitor.prototype._draw.call(this); + + this._drawSet(0, "#88f"); + this._drawSet(1, "#00f"); + this._drawSet(2, "#ff0"); + + var avg = [0, 0]; + for (var i=0;i= 360) { rotation -= 360; } + var key = size.join(",") + "," + rotation + "," + (cache ? 1 : 0); + if (item.versions[key]) { return item.versions[key]; } /* we already have this */ + + item.versions[key] = OZ.DOM.elm("canvas", {width:size[0], height:size[1]}); + if (!item.event) { return this._render(url, key); } /* image loaded, render & return */ + + return item.versions[key]; /* not loaded yet, wait please */ +} +HAF.Sprite._cache = {}; +/** + * Render a pre-requested variant of an image + */ +HAF.Sprite._render = function(url, key) { + var item = this._cache[url]; + var canvas = item.versions[key]; + var context = canvas.getContext("2d"); + var parts = key.split(","); + var angle = parseInt(parts[2]); + + if (angle) { /* add rotation if requested */ + context.translate(canvas.width/2, canvas.height/2); + context.rotate(angle * Math.PI / 180); + context.translate(-canvas.width/2, -canvas.height/2); + } + + context.drawImage(item.img, 0, 0, canvas.width, canvas.height); + if (parts[3] == "0") { delete item.versions[key]; } /* not to be cached */ + return canvas; +} + +/** + * Animated image sprite, consists of several frames + */ +HAF.AnimatedSprite = OZ.Class().extend(HAF.Sprite); +HAF.AnimatedSprite.prototype.init = function(image, size, frames) { + HAF.Sprite.prototype.init.call(this, image, size); + this._animation = { + fps: 10, + time: 0, + frame: 0, + frames: frames + } + +} +HAF.AnimatedSprite.prototype.tick = function(dt) { + this._animation.time += dt; + var oldFrame = this._animation.frame; + this._animation.frame = Math.floor(this._animation.time * this._animation.fps / 1000) % this._animation.frames; + return (oldFrame != this._animation.frame); +} + +/** + * Particle, suitable for particle fx + */ +HAF.Particle = OZ.Class().extend(HAF.Actor); +HAF.Particle.SQUARE = 0; +HAF.Particle.CIRCLE = 1; +HAF.Particle.prototype.init = function(position, options) { + options = options || {}; + this._particle = { + position: position, + pxPosition: [0, 0], + velocity: options.velocity || [0, 0], /* pixels per second */ + opacity: options.opacity || 1, + size: options.size || 2, + color: options.color || [0, 0, 0], + type: options.type || HAF.Particle.SQUARE, + decay: options.decay || 0 /* opacity per second */ + } + +} + +HAF.Particle.prototype.tick = function(dt) { + var changed = false; + + /* adjust position */ + for (var i=0;i<2;i++) { + var pos = this._particle.position[i] + this._particle.velocity[i] * dt / 1000; + this._particle.position[i] = pos; + var px = Math.round(pos); + if (px != this._particle.pxPosition[i]) { + this._particle.pxPosition[i] = px; + changed = true; + } + } + + /* adjust opacity */ + if (this._particle.decay) { + var diff = this._particle.decay * dt / 1000; + this._particle.opacity = Math.max(0, this._particle.opacity - diff); + changed = true; + } + + return changed; +} + +HAF.Particle.prototype.draw = function(context) { + context.fillStyle = "rgba("+this._particle.color.join(",")+","+this._particle.opacity+")"; + var half = this._particle.size/2; + switch (this._particle.type) { + case HAF.Particle.SQUARE: + context.fillRect(this._particle.pxPosition[0]-half, this._particle.pxPosition[1]-half, this._particle.size, this._particle.size); + break; + + case HAF.Particle.CIRCLE: + context.beginPath(); + context.arc(this._particle.pxPosition[0], this._particle.pxPosition[1], this._particle.size/2, 0, 2*Math.PI, false); + context.fill(); + break; + } +} + +HAF.Particle.prototype.getBox = function() { + var half = this._particle.size/2; + return [ + [this._particle.pxPosition[0]-half, this._particle.pxPosition[1]-half], + [this._particle.size, this._particle.size] + ]; +} diff --git a/js/oz.js b/js/oz.js new file mode 100644 index 0000000..77029ec --- /dev/null +++ b/js/oz.js @@ -0,0 +1,331 @@ +/* (c) 2007 - now() Ondrej Zara, 1.6 */ +var OZ = { + $:function(x) { return typeof(x) == "string" ? document.getElementById(x) : x; }, + opera:!!window.opera, + ie:!!document.attachEvent && !window.opera, + gecko:!!document.getAnonymousElementByAttribute, + webkit:!!navigator.userAgent.match(/webkit/i), + khtml:!!navigator.userAgent.match(/khtml/i) || !!navigator.userAgent.match(/konqueror/i), + Event:{ + _id:0, + _byName:{}, + _byID:{}, + add:function(elm,event,cb) { + var id = OZ.Event._id++; + var element = OZ.$(elm); + var fnc = cb; + if (element) { + if (element.addEventListener) { + element.addEventListener(event,fnc,false); + } else if (element.attachEvent) { + fnc = function() { return cb.apply(elm,arguments); } + element.attachEvent("on"+event,fnc); + } + } + if (!(event in OZ.Event._byName)) { OZ.Event._byName[event] = {}; } + var obj = OZ.Event._byName[event]; + obj[id] = [element,event,fnc]; + OZ.Event._byID[id] = obj; + return id; + }, + remove:function(id) { + var obj = OZ.Event._byID[id]; + if (!obj) { return; } + var e = obj[id]; + var elm = e[0]; + if (elm) { + if (elm.removeEventListener) { + elm.removeEventListener(e[1],e[2],false); + } else if (elm.detachEvent) { + elm.detachEvent("on"+e[1],e[2]); + } + } + delete OZ.Event._byID[id]; + delete obj[id]; + }, + stop:function(e) { e.stopPropagation ? e.stopPropagation() : e.cancelBubble = true; }, + prevent:function(e) { e.preventDefault ? e.preventDefault() : e.returnValue = false; }, + target:function(e) { return e.target || e.srcElement; } + }, + Class:function() { + var c = function() { + var init = arguments.callee.prototype.init; + if (init) { init.apply(this,arguments); } + }; + c.implement = function(parent) { + for (var p in parent.prototype) { this.prototype[p] = parent.prototype[p]; } + return this; + }; + c.extend = function(parent) { + var tmp = function(){}; + tmp.prototype = parent.prototype; + this.prototype = new tmp(); + this.prototype.constructor = this; + return this; + }; + c.prototype.bind = function(fnc) { return fnc.bind(this); }; + c.prototype.dispatch = function(type, data) { + var obj = { + type:type, + target:this, + timeStamp:(new Date()).getTime(), + data:data + } + var tocall = []; + var list = OZ.Event._byName[type]; + for (var id in list) { + var item = list[id]; + if (!item[0] || item[0] == this) { tocall.push(item[2]); } + } + var len = tocall.length; + for (var i=0;i-1;i--) { + if (i in this && this[i] === item) { return i; } + } + return -1; + } +} +if (!Array.lastIndexOf) { + Array.lastIndexOf = function(obj, item, from) { return Array.prototype.lastIndexOf.call(obj, item, from); } +} + +if (!Array.prototype.forEach) { + Array.prototype.forEach = function(cb, _this) { + var len = this.length; + for (var i=0;i this._area[i]) { this._exactPosition[i] -= this._area[i]; } + if (this._exactPosition[i] < 0) { this._exactPosition[i] += this._area[i]; } + } + + var x = Math.round(this._exactPosition[0]); + var y = Math.round(this._exactPosition[1]); + var positionChange = false; + if (x != this._sprite.position[0] || y != this._sprite.position[1]) { + positionChange = true; + this._sprite.position[0] = x; + this._sprite.position[1] = y; + } + + return (frameChange || positionChange); +} +MovingZombie.prototype._getSourceImagePosition = function() { + return [4 + this._animation.frame, this._direction]; +} diff --git a/particles.html b/particles.html new file mode 100644 index 0000000..7281727 --- /dev/null +++ b/particles.html @@ -0,0 +1,107 @@ + + + + + HAF • HTML5 Animation Framework • Particles + + + + + + + + Particles: + Particle type: + Clear: + Dirty actors: + + + + diff --git a/style.css b/style.css new file mode 100644 index 0000000..8da0258 --- /dev/null +++ b/style.css @@ -0,0 +1,9 @@ +.monitor { + background-color: #eee; + display: block; + margin-bottom: 3px; +} + +#engine { + border:2px solid black; +} diff --git a/zombie-big.png b/zombie-big.png new file mode 100644 index 0000000..6dac876 Binary files /dev/null and b/zombie-big.png differ diff --git a/zombie.png b/zombie.png new file mode 100644 index 0000000..6266f5c Binary files /dev/null and b/zombie.png differ