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