diff --git a/dist/crafty-min.js b/dist/crafty-min.js index 9ff848a5d..6c01e7891 100644 --- a/dist/crafty-min.js +++ b/dist/crafty-min.js @@ -1,13 +1,13 @@ /** - * craftyjs 0.7.1-rc2 + * craftyjs 0.8.0-rc1 * http://craftyjs.com/ * - * Copyright 2015, Louis Stowasser - * Dual licensed under the MIT or GPL licenses. + * Copyright 2016, Louis Stowasser + * Licensed under the MIT license. */ -!function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){var c=b[g][1][a];return e(c?c:a)},k,k.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g1)for(var c=1;c0&&(this.vx+=this._directionSpeed[a].x,this.vy+=this._directionSpeed[a].y)},__unapplyActiveDirections:function(){for(var a in this._activeDirections)this._activeDirections[a]>0&&(this.vx-=this._directionSpeed[a].x,this.vy-=this._directionSpeed[a].y)},enableControl:function(){return this.disableControls&&this.__applyActiveDirections(),this.disableControls=!1,this},disableControl:function(){return this.disableControls||this.__unapplyActiveDirections(),this.disableControls=!0,this}}),d.c("Jumper",{_jumpSpeed:300,canJump:!0,init:function(){this.requires("Supportable, Motion, Keyboard"),this.enableControl=this.enableControl||function(){this.disableControls=!1},this.disableControl=this.disableControl||function(){this.disableControls=!0}},remove:function(){this.unbind("KeyDown",this._keydown_jumper)},_keydown_jumper:function(a){if(!this.disableControls&&this._jumpKeys[a.key]){var b=this.ground;this.canJump=!!b,this.trigger("CheckJumping",b),this.canJump&&(this.vy=-this._jumpSpeed)}},jumper:function(a,b){b?this._jumpSpeed=a:b=a,this._jumpKeys={};for(var c=0;c0?1:-1,e={acceleration:b,rawAcceleration:"["+Math.round(b.x)+", "+Math.round(b.y)+", "+Math.round(b.z)+"]",facingUp:c,tiltLR:Math.round(b.x/9.81*-90),tiltFB:Math.round((b.y+9.81)/9.81*90*c)};d.device._deviceMotionCallback(e)},deviceOrientation:function(a){this._deviceOrientationCallback=a,d.support.deviceorientation&&(window.DeviceOrientationEvent?d.addEvent(this,window,"deviceorientation",this._normalizeDeviceOrientation):window.OrientationEvent&&d.addEvent(this,window,"MozOrientation",this._normalizeDeviceOrientation))},deviceMotion:function(a){this._deviceMotionCallback=a,d.support.devicemotion&&window.DeviceMotionEvent&&d.addEvent(this,window,"devicemotion",this._normalizeDeviceMotion)}}})},{"../core/core.js":7}],4:[function(a,b,c){var d=a("../core/core.js"),e=window.document;d.extend({over:null,mouseObjs:0,mousePos:{},lastEvent:null,touchObjs:0,selected:!1,keydown:{},detectBlur:function(a){var b=a.clientX>d.stage.x&&a.clientXd.stage.y&&a.clientYc;c++){var f,g,h,i=!1,j=d.domHelper.translate(b[c].clientX,b[c].clientY),k=a.target?a.target:a.srcElement;b[c].realX=f=j.x,b[c].realY=g=j.y,h=this.findClosestTouchEntity(f,g,k),h&&(h.trigger("TouchStart",b[c]),i=this.fingerDownIndexByEntity(h));var l=this.setTouch(b[c],h);i!==!1&&i>=0?this.fingers[i]=l:this.fingers.push(l)}},handleMove:function(a){for(var b=a.changedTouches,c=0,e=b.length;e>c;c++){var f,g,h,i=this.fingerDownIndexById(b[c].identifier),j=d.domHelper.translate(b[c].clientX,b[c].clientY),k=a.target?a.target:a.srcElement;b[c].realX=f=j.x,b[c].realY=g=j.y,h=this.findClosestTouchEntity(f,g,k),i>=0&&("undefined"!=typeof this.fingers[i].entity&&(this.fingers[i].entity==h?this.fingers[i].entity.trigger("TouchMove",b[c]):("object"==typeof h&&h.trigger("TouchStart",b[c]),this.fingers[i].entity.trigger("TouchEnd"))),this.fingers[i].entity=h,this.fingers[i].realX=f,this.fingers[i].realY=g)}},handleEnd:function(a){for(var b=a.changedTouches,c="touchcancel"==a.type?"TouchCancel":"TouchEnd",d=0,e=b.length;e>d;d++){var f=this.fingerDownIndexById(b[d].identifier);f>=0&&(this.fingers[f].entity&&this.fingers[f].entity.trigger(c),this.fingers.splice(f,1))}},setTouch:function(a,b){return{identifier:a.identifier,realX:a.realX,realY:a.realY,entity:b}},findClosestTouchEntity:function(a,b,c){return d.findClosestEntityByComponent("Touch",a,b,c)},fingerDownIndexById:function(a){for(var b=0,c=this.fingers.length;c>b;b++){var d=this.fingers[b].identifier;if(d==a)return b}return-1},fingerDownIndexByEntity:function(a){for(var b=0,c=this.fingers.length;c>b;b++){var d=this.fingers[b].entity;if(d==a)return b}return-1},mimicMouse:function(a){var b,c=d.lastEvent;"touchstart"===a.type?b="mousedown":"touchmove"===a.type?b="mousemove":"touchend"===a.type?b="mouseup":"touchcancel"===a.type?b="mouseup":"touchleave"===a.type&&(b="mouseup"),a.touches&&a.touches.length?first=a.touches[0]:a.changedTouches&&a.changedTouches.length&&(first=a.changedTouches[0]);var f=e.createEvent("MouseEvent");f.initMouseEvent(b,!0,!0,window,1,first.screenX,first.screenY,first.clientX,first.clientY,!1,!1,!1,!1,0,a.relatedTarget),first.target.dispatchEvent(f),null!==c&&"mousedown"==c.type&&"mouseup"==b&&(b="click",f=e.createEvent("MouseEvent"),f.initMouseEvent(b,!0,!0,window,1,first.screenX,first.screenY,first.clientX,first.clientY,!1,!1,!1,!1,0,a.relatedTarget),first.target.dispatchEvent(f))}},findClosestEntityByComponent:function(a,b,c,e){var f,g,h,i=e?e:d.stage.elem,j=0,k=-1,l={};if("CANVAS"!=i.nodeName){for(;"string"!=typeof i.id&&-1==i.id.indexOf("ent");)i=i.parentNode;var m=d(parseInt(i.id.replace("ent",""),10));m.__c[a]&&m.isAt(b,c)&&(f=m)}if(!f)for(g=d.map.search({_x:b,_y:c,_w:1,_h:1},!1),h=g.length;h>j;++j)if(g[j].__c[a]&&g[j]._visible){var n=g[j],o=!1;if(!l[n[0]]&&(l[n[0]]=!0,n.mapArea?n.mapArea.containsPoint(b,c)&&(o=!0):n.isAt(b,c)&&(o=!0),o&&(n._z>=k||-1===k))){if(n._z===k&&n[0]0?1:-1,d.trigger("MouseWheelScroll",a)},keyboardDispatch:function(a){for(var b=a,c={},e="char charCode keyCode type shiftKey ctrlKey metaKey timestamp".split(" "),f=e.length;f;){var g=e[--f];c[g]=b[g]}return c.which=null!==b.charCode?b.charCode:b.keyCode,c.key=b.keyCode||b.which,c.originalEvent=b,a=c,"keydown"===a.type?d.keydown[a.key]!==!0&&(d.keydown[a.key]=!0,d.trigger("KeyDown",a)):"keyup"===a.type&&(delete d.keydown[a.key],d.trigger("KeyUp",a)),d.selected&&!(8==a.key||a.key>=112&&a.key<=135)?(b.stopPropagation?b.stopPropagation():b.cancelBubble=!0,b.target&&"INPUT"!==b.target.nodeName&&"TEXTAREA"!==b.target.nodeName&&(b.preventDefault?b.preventDefault():b.returnValue=!1),!1):void 0}}),d._preBind("Load",function(){d.addEvent(this,"keydown",d.keyboardDispatch),d.addEvent(this,"keyup",d.keyboardDispatch),d.addEvent(this,d.stage.elem,"mousedown",d.mouseDispatch),d.addEvent(this,d.stage.elem,"mouseup",d.mouseDispatch),d.addEvent(this,e.body,"mouseup",d.detectBlur),d.addEvent(this,window,"blur",d.resetKeyDown),d.addEvent(this,d.stage.elem,"mousemove",d.mouseDispatch),d.addEvent(this,d.stage.elem,"click",d.mouseDispatch),d.addEvent(this,d.stage.elem,"dblclick",d.mouseDispatch),d.addEvent(this,d.stage.elem,"touchstart",d.touchDispatch),d.addEvent(this,d.stage.elem,"touchmove",d.touchDispatch),d.addEvent(this,d.stage.elem,"touchend",d.touchDispatch),d.addEvent(this,d.stage.elem,"touchcancel",d.touchDispatch),d.addEvent(this,d.stage.elem,"touchleave",d.touchDispatch),"Moz"===d.support.prefix?d.addEvent(this,d.stage.elem,"DOMMouseScroll",d.mouseWheelDispatch):d.addEvent(this,d.stage.elem,"mousewheel",d.mouseWheelDispatch)}),d._preBind("CraftyStop",function(){d.removeEvent(this,"keydown",d.keyboardDispatch),d.removeEvent(this,"keyup",d.keyboardDispatch),d.stage&&(d.removeEvent(this,d.stage.elem,"mousedown",d.mouseDispatch),d.removeEvent(this,d.stage.elem,"mouseup",d.mouseDispatch),d.removeEvent(this,d.stage.elem,"mousemove",d.mouseDispatch),d.removeEvent(this,d.stage.elem,"click",d.mouseDispatch),d.removeEvent(this,d.stage.elem,"dblclick",d.mouseDispatch),d.removeEvent(this,d.stage.elem,"touchstart",d.touchDispatch),d.removeEvent(this,d.stage.elem,"touchmove",d.touchDispatch),d.removeEvent(this,d.stage.elem,"touchend",d.touchDispatch),d.removeEvent(this,d.stage.elem,"touchcancel",d.touchDispatch),d.removeEvent(this,d.stage.elem,"touchleave",d.touchDispatch),"Moz"===d.support.prefix?d.removeEvent(this,d.stage.elem,"DOMMouseScroll",d.mouseWheelDispatch):d.removeEvent(this,d.stage.elem,"mousewheel",d.mouseWheelDispatch)),d.removeEvent(this,e.body,"mouseup",d.detectBlur),d.removeEvent(this,window,"blur",d.resetKeyDown)}),d.c("Mouse",{init:function(){d.mouseObjs++,this.requires("AreaMap").bind("Remove",function(){d.mouseObjs--})}}),d.c("Touch",{init:function(){d.touchObjs++,this.requires("AreaMap").bind("Remove",function(){d.touchObjs--})}}),d.c("AreaMap",{init:function(){},areaMap:function(a){if(arguments.length>1){var b=Array.prototype.slice.call(arguments,0);a=new d.polygon(b)}else a=a.constructor===Array?new d.polygon(a.slice()):a.clone();return a.shift(this._x,this._y),this.mapArea=a,this.attach(this.mapArea),this.trigger("NewAreaMap",a),this}}),d.c("Button",{init:function(){var a=!d.mobile||d.mobile&&!d.multitouch()?"Mouse":"Touch";this.requires(a)}}),d.c("MouseDrag",{_dragging:!1,init:function(){this.requires("Mouse"),this.bind("MouseDown",this._ondown)},remove:function(){this.unbind("MouseDown",this._ondown)},_ondown:function(a){a.mouseButton===d.mouseButtons.LEFT&&this.startDrag(a)},_ondrag:function(a){return this._dragging&&0!==a.realX&&0!==a.realY?void this.trigger("Dragging",a):!1},_onup:function(a){a.mouseButton===d.mouseButtons.LEFT&&this.stopDrag(a)},startDrag:function(a){return this._dragging?void 0:(this._dragging=!0,d.addEvent(this,d.stage.elem,"mousemove",this._ondrag),d.addEvent(this,d.stage.elem,"mouseup",this._onup),this.trigger("StartDrag",a||d.lastEvent),this)},stopDrag:function(a){return this._dragging?(this._dragging=!1,d.removeEvent(this,d.stage.elem,"mousemove",this._ondrag),d.removeEvent(this,d.stage.elem,"mouseup",this._onup),this.trigger("StopDrag",a||d.lastEvent),this):void 0}}),d.c("Keyboard",{isDown:function(a){return"string"==typeof a&&(a=d.keys[a]),!!d.keydown[a]}})},{"../core/core.js":7}],5:[function(a,b,c){var d=a("../core/core.js");d.extend({keys:{BACKSPACE:8,TAB:9,ENTER:13,PAUSE:19,CAPS:20,ESC:27,SPACE:32,PAGE_UP:33,PAGE_DOWN:34,END:35,HOME:36,LEFT_ARROW:37,UP_ARROW:38,RIGHT_ARROW:39,DOWN_ARROW:40,INSERT:45,DELETE:46,0:48,1:49,2:50,3:51,4:52,5:53,6:54,7:55,8:56,9:57,A:65,B:66,C:67,D:68,E:69,F:70,G:71,H:72,I:73,J:74,K:75,L:76,M:77,N:78,O:79,P:80,Q:81,R:82,S:83,T:84,U:85,V:86,W:87,X:88,Y:89,Z:90,NUMPAD_0:96,NUMPAD_1:97,NUMPAD_2:98,NUMPAD_3:99,NUMPAD_4:100,NUMPAD_5:101,NUMPAD_6:102,NUMPAD_7:103,NUMPAD_8:104,NUMPAD_9:105,MULTIPLY:106,ADD:107,SUBSTRACT:109,DECIMAL:110,DIVIDE:111,F1:112,F2:113,F3:114,F4:115,F5:116,F6:117,F7:118,F8:119,F9:120,F10:121,F11:122,F12:123,SHIFT:16,CTRL:17,ALT:18,PLUS:187,COMMA:188,MINUS:189,PERIOD:190,PULT_UP:29460,PULT_DOWN:29461,PULT_LEFT:4,PULT_RIGHT:5},mouseButtons:{LEFT:0,MIDDLE:1,RIGHT:2}})},{"../core/core.js":7}],6:[function(a,b,c){var d=function(a,b){this.timePerFrame=1e3/Crafty.timer.FPS(),this.duration=a,"function"==typeof b?this.easing_function=b:"string"==typeof b&&this.standardEasingFunctions[b]?this.easing_function=this.standardEasingFunctions[b]:this.easing_function=this.standardEasingFunctions.linear,this.reset()};d.prototype={duration:0,clock:0,steps:null,complete:!1,paused:!1,reset:function(){this.loops=1,this.clock=0,this.complete=!1,this.paused=!1},repeat:function(a){this.loops=a},setProgress:function(a,b){this.clock=this.duration*a,"undefined"!=typeof b&&(this.loops=b)},pause:function(){this.paused=!0},resume:function(){this.paused=!1,this.complete=!1},tick:function(a){if(!this.paused&&!this.complete)for(this.clock+=a,this.frames=Math.floor(this.clock/this.timePerFrame);this.clock>=this.duration&&this.complete===!1;)this.loops--,this.loops>0?this.clock-=this.duration:this.complete=!0},time:function(){return Math.min(this.clock/this.duration,1)},value:function(){return this.easing_function(this.time())},standardEasingFunctions:{linear:function(a){return a},smoothStep:function(a){return(3-2*a)*a*a},smootherStep:function(a){return(6*a*a-15*a+10)*a*a*a},easeInQuad:function(a){return a*a},easeOutQuad:function(a){return a*(2-a)},easeInOutQuad:function(a){return.5>a?2*a*a:(4-2*a)*a-1}}},b.exports=d},{}],7:[function(a,b,c){function d(){var a=f++;return a in i?d():a}function e(a){if(null===a||"object"!=typeof a)return a;var b=a.constructor();for(var c in a)b[c]=e(a[c]);return b}var f,g,h,i,j,k,l,m,n,o=a("./version"),p=function(a){return new p.fn.init(a)};h={},l=Array.prototype.slice,m=/\s*,\s*/,n=/\s+/;var q=function(){f=1,g=0,i={},j={},k=[]};q(),p.fn=p.prototype={init:function(a){if("string"!=typeof a)return a||(a=0,a in i||(i[a]=this)),a in i?(this[0]=a,this.length=1,this.__c||(this.__c={}),this._callbacks||p._addCallbackMethods(this),i[a]||(i[a]=this),i[a]):(this.length=0,this);var b,c,d,e,f,g,j,k=0,l=!1,o=!1;if("*"===a){g=0;for(b in i)this[g]=+b,g++;return this.length=g,1===g?i[this[0]]:this}-1!==a.indexOf(",")?(o=!0,d=m):-1!==a.indexOf(" ")&&(l=!0,d=n);for(b in i)if(i.hasOwnProperty(b))if(c=i[b],l||o){for(e=a.split(d),g=0,j=e.length,f=0;j>g;g++)c.__c[e[g]]&&f++;(l&&f===j||o&&f>0)&&(this[k++]=+b)}else c.__c[a]&&(this[k++]=+b);if(k>0&&!l&&!o&&this.extend(h[a]),e&&l)for(g=0;j>g;g++)this.extend(h[e[g]]);return this.length=k,1===k?i[this[k-1]]:(p._addCallbackMethods(this),this)},setName:function(a){var b=String(a);return this._entityName=b,this.trigger("NewEntityName",b),this},addComponent:function(a){var b,c,d=0;for(b=1===arguments.length&&-1!==a.indexOf(",")?a.split(m):arguments;d1)for(b=arguments.length;b>d;d++)this.has(arguments[d])?this.removeComponent(arguments[d]):this.addComponent(arguments[d]);else if(-1!==a.indexOf(","))for(c=a.split(m),b=c.length;b>d;d++)this.has(c[d])?this.removeComponent(c[d]):this.addComponent(c[d]);else this.has(a)?this.removeComponent(a):this.addComponent(a);return this},requires:function(a){return this.addComponent(a)},removeComponent:function(a,b){var c=h[a];if(this.trigger("RemoveComponent",a),c&&"events"in c){var d=c.events;for(var e in d){var f="function"==typeof d[e]?d[e]:c[d[e]];this.unbind(e,f)}}if(c&&"remove"in c&&c.remove.call(this,!1),b===!1&&c)for(var g in c)delete this[g];return delete this.__c[a],this},getId:function(){return this[0]},has:function(a){return!!this.__c[a]},attr:function(a,b,c,d){return 1===arguments.length&&"string"==typeof arguments[0]?this._attr_get(a):this._attr_set(a,b,c,d)},_attr_get:function(a,b){var c,d,e;return("undefined"==typeof b||null===b)&&(b=this),a.indexOf(".")>-1?(d=a.split("."),c=d.shift(),e=d.join("."),this._attr_get(d.join("."),b[c])):b[a]},_attr_set:function(){var a,b,c;return"string"==typeof arguments[0]?(a=this._set_create_object(arguments[0],arguments[1]),b=!!arguments[2],c=arguments[3]||arguments[0].indexOf(".")>-1):(a=arguments[0],b=!!arguments[1],c=!!arguments[2]),b||this.trigger("Change",a),c?this._recursive_extend(a,this):this.extend.call(this,a),this},_set_create_object:function(a,b){var c,d,e,f={};return a.indexOf(".")>-1?(c=a.split("."),d=c.shift(),e=c.join("."),f[d]=this._set_create_object(e,b)):f[a]=b,f},_recursive_extend:function(a,b){var c;for(c in a)a[c].constructor===Object?b[c]=this._recursive_extend(a[c],b[c]):b[c]=a[c];return b},toArray:function(){return l.call(this,0)},timeout:function(a,b){return this.each(function(){var c=this;setTimeout(function(){a.call(c)},b)}),this},bind:function(a,b){if(1===this.length)this._bindCallback(a,b);else for(var c=0;cb;b++)i[this[b]]&&a.call(i[this[b]],b);return this},get:function(a){var b=this.length;if("undefined"!=typeof a){if(a>=b||0>a+b)return;return a>=0?i[this[a]]:i[this[a+b]]}for(var c=0,d=[];b>c;c++)i[this[c]]&&d.push(i[this[c]]);return d},clone:function(){var a,b,c=this.__c,d=p.e();for(a in c)d.addComponent(a);for(b in this)"0"!=b&&"_global"!=b&&"_changed"!=b&&"function"!=typeof this[b]&&"object"!=typeof this[b]&&(d[b]=this[b]);return d},setter:function(a,b){return this.defineField(a,function(){},b)},defineField:function(a,b,c){return p.defineField(this,a,b,c),this},destroy:function(){this.each(function(){var a;this.trigger("Remove");for(var b in this.__c)a=h[b],a&&"remove"in a&&a.remove.call(this,!0);this._unbindAll(),delete i[this[0]]})}},p.fn.init.prototype=p.fn,p.extend=p.fn.extend=function(a){var b,c=this;if(!a)return c;for(b in a)c!==a[b]&&(c[b]=a[b]);return c},p._callbackMethods={_bindCallback:function(a,b){var c=this._callbacks[a];c||(c=this._callbacks[a]=(j[a]||(j[a]={}))[this[0]]=[],c.context=this,c.depth=0),c.push(b)},_runCallbacks:function(a,b){if(this._callbacks[a]){var c,d=this._callbacks[a],e=d.length;for(d.depth++,c=0;e>c;c++)"undefined"==typeof d[c]?d.depth<=1&&(d.splice(c,1),c--,e--,0===d.length&&(delete this._callbacks[a],delete j[a][this[0]])):d[c].call(this,b);d.depth--}},_unbindCallbacks:function(a,b){if(this._callbacks[a])for(var c=this._callbacks[a],d=0;d0&&p.trigger("MeasureWaitTime",currentTime-h),c+i>=currentTime)return void(h=currentTime);var m=currentTime-(c+i);m>20*k&&(i+=m-k,m=k),"fixed"===d?(l=Math.ceil(m/k),l=Math.min(l,e),b=k):"variable"===d?(l=1,b=m,b=Math.min(b,f)):"semifixed"===d&&(l=Math.ceil(m/f),b=m/l);for(var n=0;l>n;n++){j=currentTime;var o={frame:g++,dt:b,gameTime:c};p.trigger("EnterFrame",o),p.trigger("ExitFrame",o),c+=b,currentTime=(new Date).getTime(),p.trigger("MeasureFrameTime",currentTime-j)}l>0&&(a=currentTime,p.trigger("PreRender"),p.trigger("RenderScene"),p.trigger("PostRender"),currentTime=(new Date).getTime(),p.trigger("MeasureRenderTime",currentTime-a)),h=currentTime},FPS:function(a){return"undefined"==typeof a?j:(j=a,k=1e3/j,p.trigger("FPSChange",a),void 0)},simulateFrames:function(a,b){for("undefined"==typeof b&&(b=k);a-- >0;){var c={frame:g++,dt:b};p.trigger("EnterFrame",c),p.trigger("ExitFrame",c)}p.trigger("PreRender"),p.trigger("RenderScene"),p.trigger("PostRender")}}}(),e:function(){var a=d();return i[a]=null,i[a]=p(a),arguments.length>0&&i[a].addComponent.apply(i[a],arguments),i[a].setName("Entity #"+a),i[a].addComponent("obj"),p.trigger("NewEntity",{id:a}),i[a]},c:function(a,b){h[a]=b},trigger:function(a,b){var c,d,e=j[a]||(j[a]={});for(c in e)e.hasOwnProperty(c)&&(d=e[c],d&&0!==d.length&&d.context._runCallbacks(a,b))},bind:function(a,b){return this._bindCallback(a,b),b},uniqueBind:function(a,b){return this.unbind(a,b),this.bind(a,b)},one:function(a,b){var c=this,d=function(e){b.call(c,e),c.unbind(a,d)};return c.bind(a,d)},unbind:function(a,b){this._unbindCallbacks(a,b)},frame:function(){return g},components:function(){return h},isComp:function(a){return a in h},debug:function(a){return"handlers"===a?j:i},settings:function(){var a={},b={};return{register:function(a,c){b[a]=c},modify:function(c,d){b[c]&&(b[c].call(a[c],d),a[c]=d)},get:function(b){return a[b]}}}(),defineField:function(a,b,c,d){Object.defineProperty(a,b,{get:c,set:d,configurable:!1,enumerable:!0})},clone:e}),"function"==typeof define&&define("crafty",[],function(){return p}),b.exports=p},{"./version":16}],8:[function(a,b,c){(function(c){var d=a("./core"),e="undefined"!=typeof window&&window.document;!function(){var a=d.support={},b="undefined"!=typeof navigator&&navigator.userAgent.toLowerCase()||"undefined"!=typeof c&&c.version,f=/(webkit)[ \/]([\w.]+)/.exec(b)||/(o)pera(?:.*version)?[ \/]([\w.]+)/.exec(b)||/(ms)ie ([\w.]+)/.exec(b)||/(moz)illa(?:.*? rv:([\w.]+))?/.exec(b)||/(v)\d+\.(\d+)/.exec(b)||[],g=/iPad|iPod|iPhone|Android|webOS|IEMobile/i.exec(b);if(g&&(d.mobile=g[0]),a.defineProperty=function(){if(!("defineProperty"in Object))return!1;try{Object.defineProperty({},"x",{})}catch(a){return!1}return!0}(),a.audio="undefined"!=typeof window&&"canPlayType"in e.createElement("audio"),a.prefix=f[1]||f[0],"moz"===a.prefix&&(a.prefix="Moz"),"o"===a.prefix&&(a.prefix="O"),"v"===a.prefix&&(a.prefix="node"),f[2]&&(a.versionName=f[2],a.version=+f[2].split(".")[0]),a.canvas="undefined"!=typeof window&&"getContext"in e.createElement("canvas"),a.canvas){var h;try{var i=e.createElement("canvas");h=i.getContext("webgl")||i.getContext("experimental-webgl"),h.viewportWidth=a.canvas.width,h.viewportHeight=a.canvas.height}catch(j){}a.webgl=!!h}else a.webgl=!1;a.css3dtransform="undefined"!=typeof window&&("undefined"!=typeof e.createElement("div").style.Perspective||"undefined"!=typeof e.createElement("div").style[a.prefix+"Perspective"]),a.deviceorientation="undefined"!=typeof window&&("undefined"!=typeof window.DeviceOrientationEvent||"undefined"!=typeof window.OrientationEvent),a.devicemotion="undefined"!=typeof window&&"undefined"!=typeof window.DeviceMotionEvent}(),b.exports={_events:{},addEvent:function(a,b,c,d){3===arguments.length&&(d=c,c=b,b=window.document);var e=function(b){d.call(a,b)},f=a[0]||"";this._events[f+b+c+d]||(this._events[f+b+c+d]=e,b.addEventListener(c,e,!1))},removeEvent:function(a,b,c,d){3===arguments.length&&(d=c,c=b,b=window.document);var e=a[0]||"",f=this._events[e+b+c+d];f&&(b.removeEventListener(c,f,!1),delete this._events[e+b+c+d])},background:function(a){d.stage.elem.style.background=a}}}).call(this,a("_process"))},{"./core":7,_process:1}],9:[function(a,b,c){var d=a("../core/core.js");b.exports={assets:{},__paths:{audio:"",images:""},paths:function(a){return"undefined"==typeof a?this.__paths:(a.audio&&(this.__paths.audio=a.audio),void(a.images&&(this.__paths.images=a.images)))},asset:function(a,b){return 1===arguments.length?d.assets[a]:d.assets[a]?void 0:(d.assets[a]=b,this.trigger("NewAsset",{key:a,value:b}),b)},image_whitelist:["jpg","jpeg","gif","png","svg"],load:function(a,b,c,e){function f(){var a=this.src;this.removeEventListener&&this.removeEventListener("canplaythrough",f,!1),m++,c&&c({loaded:m,total:n,percent:m/n*100,src:a}),m===n&&b&&b()}function g(){var a=this.src;e&&e({loaded:m,total:n,percent:m/n*100,src:a}),m++,m===n&&b&&b()}Array.isArray(a)&&d.log("Calling Crafty.load with an array of assets no longer works; see the docs for more details."),a="string"==typeof a?JSON.parse(a):a;var h,i,j,k,l,m=0,n=(a.audio?Object.keys(a.audio).length:0)+(a.images?Object.keys(a.images).length:0)+(a.sprites?Object.keys(a.sprites).length:0),o=d.support.audio,p=d.paths(),q=function(a){ -return a.substr(a.lastIndexOf(".")+1).toLowerCase()},r=function(a,b){return-1===b.search("://")?"audio"==a?p.audio+b:p.images+b:b},s=function(a){return d.asset(a)||null},t=function(a){return d.audio.supports(q(a))},u=function(a){return-1!=d.image_whitelist.indexOf(q(a))},v=function(a,b){a.onload=f,"webkit"===d.support.prefix&&(a.src=""),a.src=b};for(k in a)for(l in a[k])if(a[k].hasOwnProperty(l)){if(h=a[k][l],"audio"===k&&o){if("object"==typeof h){var w=[];for(var x in h)i=r(k,h[x]),!s(i)&&t(h[x])&&w.push(i);j=d.audio.add(l,w).obj}else"string"==typeof h&&t(h)&&(i=r(k,h),s(i)||(j=d.audio.add(l,i).obj));j&&j.addEventListener&&j.addEventListener("canplaythrough",f,!1)}else l="sprites"===k?l:h,i=r(k,l),u(l)&&(j=s(i),j||(j=new Image,"sprites"===k&&d.sprite(h.tile,h.tileh,i,h.map,h.paddingX,h.paddingY,h.paddingAroundBorder),d.asset(i,j)),v(j,i));j?j.onerror=g:--n}0===n&&b()},removeAssets:function(a){a="string"==typeof a?JSON.parse(a):a;var b,c,e,f,g=d.paths(),h=function(a,b){return-1===b.search("://")?"audio"==a?g.audio+b:g.images+b:b};for(e in a)for(f in a[e])if(a[e].hasOwnProperty(f))if(b=a[e][f],"audio"===e)if("object"==typeof b)for(var i in b)c=h(e,b[i]),d.asset(c)&&d.audio.remove(f);else"string"==typeof b&&(c=h(e,b),d.asset(c)&&d.audio.remove(f));else if(f="sprites"===e?f:b,c=h(e,f),d.asset(c)){if("sprites"===e)for(var j in b.map)delete d.components()[j];delete d.assets[c]}}}},{"../core/core.js":7}],10:[function(a,b,c){b.exports={init:function(){this.changed=[],this.bind("Change",this._changed_attributes),this.bind("Change",this._changed_triggers)},_changed_triggers:function(a,b){var c;b=Crafty.extend.call({pre:""},b);for(c in a)this.trigger("Change["+b.pre+c+"]",a[c]),a[c].constructor===Object&&this._changed_triggers(a[c],{pre:b.pre+c+"."})},_changed_attributes:function(a){var b;for(b in a)this.changed.push(b);return this},is_dirty:function(a){return 0===arguments.length?!!this.changed.length:this.changed.indexOf(a)>-1}}},{}],11:[function(a,b,c){var d=a("../core/core.js");b.exports={_scenes:{},_current:null,scene:function(a,b,c){return 1===arguments.length||"function"!=typeof arguments[1]?void d.enterScene(a,arguments[1]):void d.defineScene(a,b,c)},defineScene:function(a,b,c){if("function"!=typeof b)throw"Init function is the wrong type.";this._scenes[a]={},this._scenes[a].initialize=b,"undefined"!=typeof c&&(this._scenes[a].uninitialize=c)},enterScene:function(a,b){if("function"==typeof b)throw"Scene data cannot be a function";d.trigger("SceneDestroy",{newScene:a}),d.viewport.reset(),d("2D").each(function(){this.has("Persist")||this.destroy()}),null!==this._current&&"uninitialize"in this._scenes[this._current]&&this._scenes[this._current].uninitialize.call(this);var c=this._current;this._current=a,d.trigger("SceneChange",{oldScene:c,newScene:a}),this._scenes.hasOwnProperty(a)?this._scenes[a].initialize.call(this,b):d.error('The scene "'+a+'" does not exist')}}},{"../core/core.js":7}],12:[function(a,b,c){var d=a("../core/core.js");try{var e="undefined"!=typeof window&&window.localStorage||new a("node-localstorage").LocalStorage("./localStorage")}catch(f){var e=null}var g=function(a,b){var c=b;if(!e)return d.error("Local storage is not accessible. (Perhaps you are including crafty.js cross-domain?)"),!1;if(1===arguments.length)try{return JSON.parse(e.getItem(a))}catch(f){return e.getItem(a)}else"object"==typeof b&&(c=JSON.stringify(b)),e.setItem(a,c)};g.remove=function(a){return e?void e.removeItem(a):void d.error("Local storage is not accessible. (Perhaps you are including crafty.js cross-domain?)")},b.exports=g},{"../core/core.js":7}],13:[function(a,b,c){var d=a("../core/core.js");d._systems={},d.s=function(a,b,c){return b?void(c===!1?(d._systems[a]=new d.CraftySystem(a,b),d.trigger("SystemLoaded",a)):d._registerLazySystem(a,b)):d._systems[a]},d._registerLazySystem=function(a,b){Object.defineProperty(d._systems,a,{get:function(){return Object.defineProperty(d._systems,a,{value:new d.CraftySystem(a,b),writable:!0,enumerable:!0,configurable:!0}),d.trigger("SystemLoaded",a),d._systems[a]},configurable:!0})},d.CraftySystem=function(){return systemID=1,function(a,b){if(this.name=a,!b)return this;if(this._systemTemplate=b,this.extend(b),d._addCallbackMethods(this),this[0]="system"+systemID++,"function"==typeof this.init&&this.init(a),"events"in b){var c=b.events;for(var e in c){var f="function"==typeof c[e]?c[e]:b[c[e]];this.bind(e,f)}}}}(),d.CraftySystem.prototype={extend:function(a){for(var b in a)"undefined"==typeof this[b]&&(this[b]=a[b])},bind:function(a,b){return this._bindCallback(a,b),this},trigger:function(a,b){return this._runCallbacks(a,b),this},unbind:function(a,b){return this._unbindCallbacks(a,b),this},one:function(a,b){var c=this,d=function(e){b.call(c,e),c.unbind(a,d)};return c.bind(a,d)},uniqueBind:function(a,b){return this.unbind(a,b),this.bind(a,b)},destroy:function(){d.trigger("SystemDestroyed",this),"function"==typeof this.remove&&this.remove(),this._unbindAll(),delete d._systems[this.name]}}},{"../core/core.js":7}],14:[function(a,b,c){b.exports={init:function(){this._delays=[],this.bind("EnterFrame",function(a){for(var b=this._delays.length;--b>=0;){var c=this._delays[b];if(c===!1)this._delays.splice(b,1);else{for(c.accumulator+=a.dt;c.accumulator>=c.delay&&c.repeat>=0;)c.accumulator-=c.delay,c.repeat--,c.callback.call(this);c.repeat<0&&(this._delays.splice(b,1),"function"==typeof c.callbackOff&&c.callbackOff.call(this))}}})},delay:function(a,b,c,d){return this._delays.push({accumulator:0,callback:a,callbackOff:d,delay:b,repeat:(0>c?1/0:c)||0}),this},cancelDelay:function(a){for(var b=this._delays.length;--b>=0;){var c=this._delays[b];c&&c.callback==a&&(this._delays[b]=!1)}return this}}},{}],15:[function(a,b,c){b.exports={init:function(){this.tweenGroup={},this.tweenStart={},this.tweens=[],this.uniqueBind("EnterFrame",this._tweenTick)},_tweenTick:function(a){var b,c,d;for(d=this.tweens.length-1;d>=0;d--)b=this.tweens[d],b.easing.tick(a.dt),c=b.easing.value(),this._doTween(b.props,c),b.easing.complete&&(this.tweens.splice(d,1),this._endTween(b.props))},_doTween:function(a,b){for(var c in a)this[c]=(1-b)*this.tweenStart[c]+b*a[c]},tween:function(a,b,c){var d={props:a,easing:new Crafty.easing(b,c)};for(var e in a)"undefined"!=typeof this.tweenGroup[e]&&this.cancelTween(e),this.tweenStart[e]=this[e],this.tweenGroup[e]=a;return this.tweens.push(d),this},cancelTween:function(a){if("string"==typeof a)"object"==typeof this.tweenGroup[a]&&delete this.tweenGroup[a][a];else if("object"==typeof a)for(var b in a)this.cancelTween(b);return this},pauseTweens:function(){this.tweens.map(function(a){a.easing.pause()})},resumeTweens:function(){this.tweens.map(function(a){a.easing.resume()})},_endTween:function(a){for(var b in a)delete this.tweenGroup[b];this.trigger("TweenEnd",a)}}},{}],16:[function(a,b,c){b.exports="0.7.1-rc2"},{}],17:[function(a,b,c){var d=a("./core/core");d.easing=a("./core/animation"),d.extend(a("./core/extensions")),d.extend(a("./core/loader")),d.c("Model",a("./core/model")),d.extend(a("./core/scenes")),d.storage=a("./core/storage"),d.c("Delay",a("./core/time")),d.c("Tween",a("./core/tween")),a("./core/systems"),a("./spatial/2d"),a("./spatial/collision"),a("./spatial/spatial-grid"),a("./spatial/rect-manager"),a("./spatial/math"),a("./graphics/canvas"),a("./graphics/canvas-layer"),a("./graphics/color"),a("./graphics/dom"),a("./graphics/dom-helper"),a("./graphics/dom-layer"),a("./graphics/drawing"),a("./graphics/gl-textures"),a("./graphics/html"),a("./graphics/image"),a("./graphics/particles"),a("./graphics/sprite-animation"),a("./graphics/sprite"),a("./graphics/text"),a("./graphics/viewport"),a("./graphics/webgl"),a("./isometric/diamond-iso"),a("./isometric/isometric"),a("./controls/inputs"),a("./controls/controls"),a("./controls/device"),a("./controls/keycodes"),a("./sound/sound"),a("./debug/debug-layer"),a("./debug/logging"),window&&(window.Crafty=d),b.exports=d},{"./controls/controls":2,"./controls/device":3,"./controls/inputs":4,"./controls/keycodes":5,"./core/animation":6,"./core/core":7,"./core/extensions":8,"./core/loader":9,"./core/model":10,"./core/scenes":11,"./core/storage":12,"./core/systems":13,"./core/time":14,"./core/tween":15,"./debug/debug-layer":18,"./debug/logging":19,"./graphics/canvas":21,"./graphics/canvas-layer":20,"./graphics/color":22,"./graphics/dom":25,"./graphics/dom-helper":23,"./graphics/dom-layer":24,"./graphics/drawing":26,"./graphics/gl-textures":27,"./graphics/html":28,"./graphics/image":29,"./graphics/particles":30,"./graphics/sprite":32,"./graphics/sprite-animation":31,"./graphics/text":33,"./graphics/viewport":34,"./graphics/webgl":35,"./isometric/diamond-iso":36,"./isometric/isometric":37,"./sound/sound":38,"./spatial/2d":39,"./spatial/collision":40,"./spatial/math":41,"./spatial/rect-manager":42,"./spatial/spatial-grid":43}],18:[function(a,b,c){var d=a("../core/core.js"),e=window.document;d.c("DebugCanvas",{init:function(){this.requires("2D"),d.DebugCanvas.context||d.DebugCanvas.init(),d.DebugCanvas.add(this),this._debug={alpha:1,lineWidth:1},this.bind("RemoveComponent",this.onDebugRemove),this.bind("Remove",this.onDebugDestroy)},onDebugRemove:function(a){"DebugCanvas"===a&&d.DebugCanvas.remove(this)},onDebugDestroy:function(a){d.DebugCanvas.remove(this)},debugAlpha:function(a){return this._debug.alpha=a,this},debugFill:function(a){return"undefined"==typeof a&&(a="red"),this._debug.fillStyle=a,this},debugStroke:function(a){return"undefined"==typeof a&&(a="red"),this._debug.strokeStyle=a,this},debugDraw:function(a){var b=a.globalAlpha,c=this._debug;c.alpha&&(a.globalAlpha=this._debug.alpha),c.strokeStyle&&(a.strokeStyle=c.strokeStyle),c.lineWidth&&(a.lineWidth=c.lineWidth),c.fillStyle&&(a.fillStyle=c.fillStyle),this.trigger("DebugDraw"),a.globalAlpha=b}}),d.c("DebugRectangle",{init:function(){this.requires("2D, DebugCanvas")},debugRectangle:function(a){return this.debugRect=a,this.unbind("DebugDraw",this.drawDebugRect),this.bind("DebugDraw",this.drawDebugRect),this},drawDebugRect:function(){var a=d.DebugCanvas.context,b=this.debugRect;null!==b&&void 0!==b&&b._h&&b._w&&(this._debug.fillStyle&&a.fillRect(b._x,b._y,b._w,b._h),this._debug.strokeStyle&&a.strokeRect(b._x,b._y,b._w,b._h))}}),d.c("VisibleMBR",{init:function(){this.requires("DebugRectangle").debugFill("purple").bind("EnterFrame",this._assignRect)},_assignRect:function(){this._mbr?this.debugRectangle(this._mbr):this.debugRectangle(this)}}),d.c("DebugPolygon",{init:function(){this.requires("2D, DebugCanvas")},debugPolygon:function(a){return this.polygon=a,this.unbind("DebugDraw",this.drawDebugPolygon),this.bind("DebugDraw",this.drawDebugPolygon),this},drawDebugPolygon:function(){if("undefined"!=typeof this.polygon){var a=d.DebugCanvas.context;a.beginPath();for(var b=this.polygon.points,c=b.length,e=0;c>e;e+=2)a.lineTo(b[e],b[e+1]);a.closePath(),this._debug.fillStyle&&a.fill(),this._debug.strokeStyle&&a.stroke()}}}),d.c("WiredHitBox",{init:function(){this.requires("DebugPolygon").debugStroke("red").matchHitBox(),this.bind("NewHitbox",this.matchHitBox)},matchHitBox:function(){this.debugPolygon(this.map)}}),d.c("SolidHitBox",{init:function(){this.requires("Collision, DebugPolygon").debugFill("orange").debugAlpha(.7).matchHitBox(),this.bind("NewHitbox",this.matchHitBox)},matchHitBox:function(){this.debugPolygon(this.map)}}),d.c("WiredAreaMap",{init:function(){this.requires("DebugPolygon").debugStroke("green").matchAreaMap(),this.bind("NewAreaMap",this.matchAreaMap)},matchAreaMap:function(){this.debugPolygon(this.mapArea)}}),d.c("SolidAreaMap",{init:function(){this.requires("DebugPolygon").debugFill("lime").debugAlpha(.7).matchAreaMap(),this.bind("NewAreaMap",this.matchAreaMap)},matchAreaMap:function(){this.debugPolygon(this.mapArea)}}),d.DebugCanvas={context:null,entities:[],onetimeEntities:[],add:function(a){this.entities.push(a)},remove:function(a){for(var b=this.entities,c=b.length-1;c>=0;c--)b[c]==a&&b.splice(c,1)},init:function(){if(!d.DebugCanvas.context){if(!d.support.canvas)return d.trigger("NoCanvas"),void d.stop();var a;a=e.createElement("canvas"),a.width=d.viewport.width,a.height=d.viewport.height,a.style.position="absolute",a.style.left="0px",a.style.top="0px",a.id="debug-canvas",a.style.zIndex=1e5,d.stage.elem.appendChild(a),d.DebugCanvas.context=a.getContext("2d"),d.DebugCanvas._canvas=a}d.unbind("RenderScene",d.DebugCanvas.renderScene),d.bind("RenderScene",d.DebugCanvas.renderScene)},renderScene:function(a){a=a||d.viewport.rect();var b,c=d.DebugCanvas.entities,e=0,f=c.length,g=d.DebugCanvas.context,h=d.viewport;for(g.setTransform(h._scale,0,0,h._scale,Math.round(h._x*h._scale),Math.round(h._y*h._scale)),g.clearRect(a._x,a._y,a._w,a._h);f>e;e++)b=c[e],b.debugDraw(g)}}},{"../core/core.js":7}],19:[function(a,b,c){var d=a("../core/core.js");d.extend({loggingEnabled:!0,log:function(){d.loggingEnabled&&console&&console.log&&console.log.apply(console,arguments)},error:function(){d.loggingEnabled&&console&&console.error&&console.error.apply(console,arguments)}})},{"../core/core.js":7}],20:[function(a,b,c){var d=a("../core/core.js");d.extend({canvasLayer:{_dirtyRects:[],_changedObjs:[],layerCount:0,_dirtyViewport:!1,_sort:function(a,b){return a._globalZ-b._globalZ},add:function(a){this._changedObjs.push(a)},context:null,_canvas:null,init:function(){if(!d.support.canvas)return d.trigger("NoCanvas"),void d.stop();this._dirtyRects=[],this._changedObjs=[],this.layerCount=0;var a;a=document.createElement("canvas"),a.width=d.viewport.width,a.height=d.viewport.height,a.style.position="absolute",a.style.left="0px",a.style.top="0px";d.canvasLayer;d.stage.elem.appendChild(a),this.context=a.getContext("2d"),this._canvas=a;var b=d.viewport._scale;1!=b&&a.scale(b,b),this._setPixelart(d._pixelartEnabled),d.uniqueBind("PixelartSet",this._setPixelart),d.uniqueBind("RenderScene",this._render),d.uniqueBind("ViewportResize",this._resize),d.bind("InvalidateViewport",function(){d.canvasLayer._dirtyViewport=!0})},_render:function(){var a=d.canvasLayer,b=a._dirtyViewport,c=a._changedObjs.length,e=a.context;if(c||b){if(b){var f=d.viewport;e.setTransform(f._scale,0,0,f._scale,Math.round(f._x*f._scale),Math.round(f._y*f._scale))}c/a.layerCount>.6||b?a._drawAll():a._drawDirty(),a._clean()}},_drawDirty:function(){var a,b,c,e,f,g,h=this._changedObjs,i=h.length,j=this._dirtyRects,k=d.rectManager,l=k.overlap,m=this.context,n=[],o=[];for(a=0;i>a;a++)this._createDirty(h[a]);for(k.mergeSet(j),i=j.length,a=0;i>a;++a)if(e=j[a],n.length=0,o.length=0,e){for(e._w=e._x+e._w,e._h=e._y+e._h,e._x=e._x>0?0|e._x:(0|e._x)-1,e._y=e._y>0?0|e._y:(0|e._y)-1,e._w-=e._x,e._h-=e._y,e._w=e._w===(0|e._w)?e._w:(0|e._w)+1,e._h=e._h===(0|e._h)?e._h:(0|e._h)+1,c=d.map.search(e,!1),m.clearRect(e._x,e._y,e._w,e._h),m.save(),m.beginPath(),m.rect(e._x,e._y,e._w,e._h),m.clip(),b=0,f=c.length;f>b;++b)g=c[b],!n[g[0]]&&g._visible&&g.__c.Canvas&&(n[g[0]]=!0,o.push(g));for(o.sort(this._sort),b=0,f=o.length;f>b;++b){g=o[b];var p=g._mbr||g;l(p,e)&&g.draw(),g._changed=!1}m.closePath(),m.restore()}if(d.canvasLayer.debugDirty===!0)for(m.strokeStyle="red",a=0,i=j.length;i>a;++a)e=j[a],m.strokeRect(e._x,e._y,e._w,e._h)},_drawAll:function(a){a=a||d.viewport.rect();var b,c=d.map.search(a),e=0,f=c.length,g=this.context;for(g.clearRect(a._x,a._y,a._w,a._h),c.sort(this._sort);f>e;e++)b=c[e],b._visible&&b.__c.Canvas&&(b.draw(),b._changed=!1)},debug:function(){d.log(this._changedObjs)},_clean:function(){var a,b,c,d,e=this._changedObjs;for(c=0,d=e.length;d>c;c++)b=e[c],a=b._mbr||b,"undefined"==typeof b.staleRect&&(b.staleRect={}),b.staleRect._x=a._x,b.staleRect._y=a._y,b.staleRect._w=a._w,b.staleRect._h=a._h,b._changed=!1;e.length=0,this._dirtyRects.length=0,this._dirtyViewport=!1},_createDirty:function(a){var b=a._mbr||a,c=this._dirtyRects,e=d.rectManager;if(a.staleRect){if(e.overlap(a.staleRect,b))return e.merge(a.staleRect,b,a.staleRect),void c.push(a.staleRect);c.push(a.staleRect)}a.currentRect._x=b._x,a.currentRect._y=b._y,a.currentRect._w=b._w,a.currentRect._h=b._h,c.push(a.currentRect)},_resize:function(){var a=d.canvasLayer._canvas;a.width=d.viewport.width,a.height=d.viewport.height},_setPixelart:function(a){var b=d.canvasLayer.context;b.imageSmoothingEnabled=!a,b.mozImageSmoothingEnabled=!a,b.webkitImageSmoothingEnabled=!a,b.oImageSmoothingEnabled=!a,b.msImageSmoothingEnabled=!a}}})},{"../core/core.js":7}],21:[function(a,b,c){var d=a("../core/core.js");d.c("Canvas",{init:function(){var a=d.canvasLayer;a.context||a.init(),this._drawLayer=a,this._drawContext=a.context,a.layerCount++,this.currentRect={},this._changed=!0,a.add(this),this.bind("Invalidate",function(b){this._changed===!1&&(this._changed=!0,a.add(this))}),this.bind("Remove",function(){this._drawLayer.layerCount--,this._changed=!0,this._drawLayer.add(this)})},drawVars:{type:"canvas",pos:{},ctx:null,coord:[0,0,0,0],co:{x:0,y:0,w:0,h:0}},draw:function(a,b,c,d,e){if(this.ready){4===arguments.length&&(e=d,d=c,c=b,b=a,a=this._drawContext);var f=this.drawVars.pos;f._x=this._x+(b||0),f._y=this._y+(c||0),f._w=d||this._w,f._h=e||this._h,context=a||this._drawContext,coord=this.__coord||[0,0,0,0];var g=this.drawVars.co;g.x=coord[0]+(b||0),g.y=coord[1]+(c||0),g.w=d||coord[2],g.h=e||coord[3],(this._flipX||this._flipY||this._rotation)&&context.save(),0!==this._rotation&&(context.translate(this._origin.x+this._x,this._origin.y+this._y),f._x=-this._origin.x,f._y=-this._origin.y,context.rotate(this._rotation%360*(Math.PI/180))),(this._flipX||this._flipY)&&(context.scale(this._flipX?-1:1,this._flipY?-1:1),this._flipX&&(f._x=-(f._x+f._w)),this._flipY&&(f._y=-(f._y+f._h)));var h;return this._alpha<1&&(h=context.globalAlpha,context.globalAlpha=this._alpha),this.drawVars.ctx=context,this.trigger("Draw",this.drawVars),(0!==this._rotation||this._flipX||this._flipY)&&context.restore(),h&&(context.globalAlpha=h),this}}})},{"../core/core.js":7}],22:[function(a,b,c){var d=a("../core/core.js"),e=window.document;d.extend({assignColor:function(){function a(a){return a._red=a._blue=a._green=0,a}function b(a){var b=a.toString(16);return 1==b.length&&(b="0"+b),b}function c(a,c,d){return"#"+b(a)+b(c)+b(d)}function d(b,c){var d;if(7===b.length)d=2;else{if(4!==b.length)return a(c);d=1}return c._red=parseInt(b.substr(1,d),16),c._green=parseInt(b.substr(1+d,d),16),c._blue=parseInt(b.substr(1+2*d,d),16),c}function f(b,c){var d=l.exec(b);return null===d||4!=d.length&&5!=d.length?a(c):(c._red=Math.round(parseFloat(d[1])),c._green=Math.round(parseFloat(d[2])),c._blue=Math.round(parseFloat(d[3])),d[4]&&(c._strength=parseFloat(d[4])),c)}function g(a,b){if("undefined"==typeof k[a]){j===!1&&(window.document.body.appendChild(i),j=!0),i.style.color=a;var e=window.getComputedStyle(i).color;f(e,b),k[a]=c(b._red,b._green,b._blue)}else d(k[a],b);return b}function h(a){return"rgba("+a._red+", "+a._green+", "+a._blue+", "+a._strength+")"}var i=e.createElement("div");i.style.display="none";var j=!1,k={aqua:"#00ffff",black:"#000000",blue:"#0000ff",fuchsia:"#ff00ff",gray:"#808080",green:"#00ff00",lime:"#00ff00",maroon:"#800000",navy:"#000080",olive:"#808000",orange:"#ffa500",purple:"#800080",red:"#ff0000",silver:"#c0c0c0",teal:"#008080",white:"#ffffff",yellow:"#ffff00"},l=/rgba?\s*\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,?\s*([0-9.]+)?\)/;return function(a,b){b=b||{},a=a.trim().toLowerCase();var c=null;c="#"===a[0]?d(a,b):"r"===a[0]&&"g"===a[1]&&"b"===a[2]?f(a,b):g(a,b),b._strength=b._strength||1,b._color=h(b)}}()});var f="attribute vec2 aPosition;\nattribute vec3 aOrientation;\nattribute vec2 aLayer;\nattribute vec4 aColor;\n\nvarying lowp vec4 vColor;\n\nuniform vec4 uViewport;\n\nmat4 viewportScale = mat4(2.0 / uViewport.z, 0, 0, 0, 0, -2.0 / uViewport.w, 0,0, 0, 0,1,0, -1,+1,0,1);\nvec4 viewportTranslation = vec4(uViewport.xy, 0, 0);\n\nvoid main() {\n vec2 pos = aPosition;\n vec2 entityOrigin = aOrientation.xy;\n mat2 entityRotationMatrix = mat2(cos(aOrientation.z), sin(aOrientation.z), -sin(aOrientation.z), cos(aOrientation.z));\n\n pos = entityRotationMatrix * (pos - entityOrigin) + entityOrigin;\n gl_Position = viewportScale * (viewportTranslation + vec4(pos, 1.0/(1.0+exp(aLayer.x) ), 1) );\n vColor = vec4(aColor.rgb*aColor.a*aLayer.y, aColor.a*aLayer.y);\n}",g="precision mediump float;\nvarying lowp vec4 vColor;\nvoid main(void) {\n gl_FragColor = vColor;\n}",h=[{name:"aPosition",width:2},{name:"aOrientation",width:3},{name:"aLayer",width:2},{name:"aColor",width:4}];d.c("Color",{_red:0,_green:0,_blue:0,_strength:1,_color:"",ready:!0,init:function(){this.bind("Draw",this._drawColor),this.has("WebGL")&&this._establishShader("Color",g,f,h),this.trigger("Invalidate")},remove:function(){this.unbind("Draw",this._drawColor),this.has("DOM")&&(this._element.style.backgroundColor="transparent"),this.trigger("Invalidate")},_drawColor:function(a){this._color&&("DOM"===a.type?(a.style.backgroundColor=this._color,a.style.lineHeight=0):"canvas"===a.type?(a.ctx.fillStyle=this._color,a.ctx.fillRect(a.pos._x,a.pos._y,a.pos._w,a.pos._h)):"webgl"===a.type&&a.program.writeVector("aColor",this._red/255,this._green/255,this._blue/255,this._strength))},color:function(a){return 0===arguments.length?this._color:(arguments.length>=3?(this._red=arguments[0],this._green=arguments[1],this._blue=arguments[2],"number"==typeof arguments[3]&&(this._strength=arguments[3])):(d.assignColor(a,this),"number"==typeof arguments[1]&&(this._strength=arguments[1])),this._color="rgba("+this._red+", "+this._green+", "+this._blue+", "+this._strength+")",this.trigger("Invalidate"),this)}})},{"../core/core.js":7}],23:[function(a,b,c){var d=a("../core/core.js"),e=window.document;d.extend({domHelper:{innerPosition:function(a){var b=a.getBoundingClientRect(),c=b.left+(window.pageXOffset?window.pageXOffset:e.body.scrollLeft),d=b.top+(window.pageYOffset?window.pageYOffset:e.body.scrollTop),f=parseInt(this.getStyle(a,"border-left-width")||0,10)||parseInt(this.getStyle(a,"borderLeftWidth")||0,10)||0,g=parseInt(this.getStyle(a,"border-top-width")||0,10)||parseInt(this.getStyle(a,"borderTopWidth")||0,10)||0;return c+=f,d+=g,{x:c,y:d}},getStyle:function(a,b){var c;return a.currentStyle?c=a.currentStyle[this.camelize(b)]:window.getComputedStyle&&(c=e.defaultView.getComputedStyle(a,null).getPropertyValue(this.csselize(b))),c},camelize:function(a){return a.replace(/-+(.)?/g,function(a,b){return b?b.toUpperCase():""})},csselize:function(a){return a.replace(/[A-Z]/g,function(a){return a?"-"+a.toLowerCase():""})},translate:function(a,b){var c=e.documentElement,f=e.body;return{x:(a-d.stage.x+(c&&c.scrollLeft||f&&f.scrollLeft||0))/d.viewport._scale-d.viewport._x,y:(b-d.stage.y+(c&&c.scrollTop||f&&f.scrollTop||0))/d.viewport._scale-d.viewport._y}}}})},{"../core/core.js":7}],24:[function(a,b,c){var d=a("../core/core.js"),e=window.document;d.extend({domLayer:{_changedObjs:[],_dirtyViewport:!1,_div:null,init:function(){this._changedObjs=[],this._dirtyViewport=!1;var a=this._div=e.createElement("div");d.stage.elem.appendChild(a),a.style.position="absolute",a.style.zIndex="1",a.style.transformStyle="preserve-3d",d.uniqueBind("RenderScene",this._render),d.uniqueBind("PixelartSet",this._setPixelArt),d.uniqueBind("InvalidateViewport",function(){d.domLayer._dirtyViewport=!0})},_setPixelArt:function(a){var b=d.domLayer._div.style,c=d.domHelper.camelize;a?(b[c("image-rendering")]="optimizeSpeed",b[c("image-rendering")]="-moz-crisp-edges",b[c("image-rendering")]="-o-crisp-edges",b[c("image-rendering")]="-webkit-optimize-contrast",b[c("-ms-interpolation-mode")]="nearest-neighbor",b[c("image-rendering")]="optimize-contrast",b[c("image-rendering")]="pixelated",b[c("image-rendering")]="crisp-edges"):(b[c("image-rendering")]="optimizeQuality",b[c("-ms-interpolation-mode")]="bicubic",b[c("image-rendering")]="auto")},debug:function(){d.log(this._changedObjs)},_render:function(){var a=d.domLayer,b=a._changedObjs;if(a._dirtyViewport&&(a._setViewport(),a._dirtyViewport=!1),b.length){for(var c=0,e=b.length;e>c;++c)b[c].draw()._changed=!1;b.length=0}},add:function(a){this._changedObjs.push(a)},_setViewport:function(){var a=d.domLayer._div.style,b=d.viewport;a.transform=a[d.support.prefix+"Transform"]="scale("+b._scale+", "+b._scale+")",a.left=Math.round(b._x*b._scale)+"px",a.top=Math.round(b._y*b._scale)+"px",a.zIndex=10}}})},{"../core/core.js":7}],25:[function(a,b,c){var d=a("../core/core.js"),e=window.document;d.c("DOM",{_element:null,_cssStyles:null,avoidCss3dTransforms:!1,init:function(){var a=d.domLayer;a._div||a.init(),this._drawLayer=a,this._cssStyles={visibility:"",left:"",top:"",width:"",height:"",zIndex:"",opacity:"",transformOrigin:"",transform:""},this._element=e.createElement("div"),a._div.appendChild(this._element),this._element.style.position="absolute",this._element.id="ent"+this[0],this.bind("Invalidate",this._invalidateDOM),this.bind("NewComponent",this._updateClass),this.bind("RemoveComponent",this._removeClass),this._invalidateDOM()},remove:function(){this.undraw(),this.unbind("NewComponent",this._updateClass),this.unbind("RemoveComponent",this._removeClass),this.unbind("Invalidate",this._invalidateDOM)},getDomId:function(){return this._element.id},_removeClass:function(a){var b=0,c=this.__c,d="";for(b in c)b!=a&&(d+=" "+b);d=d.substr(1),this._element.className=d},_updateClass:function(){var a=0,b=this.__c,c="";for(a in b)c+=" "+a;c=c.substr(1),this._element.className=c},_invalidateDOM:function(){this._changed||(this._changed=!0,this._drawLayer.add(this))},DOM:function(a){return a&&a.nodeType&&(this.undraw(),this._element=a,this._element.style.position="absolute"),this},draw:function(){var a=this._element.style,b=this.__coord||[0,0,0,0],c={x:b[0],y:b[1],w:b[2],h:b[3]},e=d.support.prefix,f=[];if(this._cssStyles.visibility!==this._visible&&(this._cssStyles.visibility=this._visible,this._visible?a.visibility="visible":a.visibility="hidden"),d.support.css3dtransform&&!this.avoidCss3dTransforms?f.push("translate3d("+~~this._x+"px,"+~~this._y+"px,0)"):(this._cssStyles.left!==this._x&&(this._cssStyles.left=this._x,a.left=~~this._x+"px"),this._cssStyles.top!==this._y&&(this._cssStyles.top=this._y,a.top=~~this._y+"px")),this._cssStyles.width!==this._w&&(this._cssStyles.width=this._w,a.width=~~this._w+"px"),this._cssStyles.height!==this._h&&(this._cssStyles.height=this._h,a.height=~~this._h+"px"),this._cssStyles.zIndex!==this._z&&(this._cssStyles.zIndex=this._z,a.zIndex=this._z),this._cssStyles.opacity!==this._alpha&&(this._cssStyles.opacity=this._alpha,a.opacity=this._alpha,a[e+"Opacity"]=this._alpha),this._mbr){var g=this._origin.x+"px "+this._origin.y+"px";a.transformOrigin=g,a[e+"TransformOrigin"]=g,d.support.css3dtransform?f.push("rotateZ("+this._rotation+"deg)"):f.push("rotate("+this._rotation+"deg)")}return this._flipX&&f.push("scaleX(-1)"),this._flipY&&f.push("scaleY(-1)"),this._cssStyles.transform!=f.join(" ")&&(this._cssStyles.transform=f.join(" "),a.transform=this._cssStyles.transform,a[e+"Transform"]=this._cssStyles.transform),this.trigger("Draw",{style:a,type:"DOM",co:c}),this},undraw:function(){var a=this._element;return a&&null!==a.parentNode&&a.parentNode.removeChild(a),this},css:function(a,b){var c,e,f=this._element,g=f.style;if("object"==typeof a)for(c in a)a.hasOwnProperty(c)&&(e=a[c],"number"==typeof e&&(e+="px"),g[d.domHelper.camelize(c)]=e);else{if(!b)return d.domHelper.getStyle(f,a);"number"==typeof b&&(b+="px"),g[d.domHelper.camelize(a)]=b}return this.trigger("Invalidate"),this}})},{"../core/core.js":7}],26:[function(a,b,c){var d=a("../core/core.js");d.extend({_pixelartEnabled:!1,pixelart:function(a){d._pixelartEnabled=a,d.trigger("PixelartSet",a)}})},{"../core/core.js":7}],27:[function(a,b,c){var d=a("../core/core.js"),e=d.TextureManager=function(a,b){this.gl=a,this.webgl=b,this.max_units=a.getParameter(a.MAX_COMBINED_TEXTURE_IMAGE_UNITS),this.bound_textures=[],this.registered_textures={},this.active=null};e.prototype={reset:function(){for(var a,b=0;b100?100:a.sharpness<0?0:a.sharpness,a.sizeSmall=~~(a.size/200*a.sharpness);var f=[this.startColour[0]+this.startColourRandom[0]*this.RANDM1TO1(),this.startColour[1]+this.startColourRandom[1]*this.RANDM1TO1(),this.startColour[2]+this.startColourRandom[2]*this.RANDM1TO1(),this.startColour[3]+this.startColourRandom[3]*this.RANDM1TO1()],g=[this.endColour[0]+this.endColourRandom[0]*this.RANDM1TO1(),this.endColour[1]+this.endColourRandom[1]*this.RANDM1TO1(),this.endColour[2]+this.endColourRandom[2]*this.RANDM1TO1(),this.endColour[3]+this.endColourRandom[3]*this.RANDM1TO1()];a.colour=f,a.deltaColour[0]=(g[0]-f[0])/a.timeToLive,a.deltaColour[1]=(g[1]-f[1])/a.timeToLive,a.deltaColour[2]=(g[2]-f[2])/a.timeToLive,a.deltaColour[3]=(g[3]-f[3])/a.timeToLive},update:function(){if(this.active&&this.emissionRate>0){var a=1/this.emissionRate;for(this.emitCounter++;this.particleCounta;)this.addParticle(),this.emitCounter-=a;this.elapsedFrames++,-1!=this.duration&&this.duration0){c.direction=this.vectorHelpers.add(c.direction,this.gravity),c.position=this.vectorHelpers.add(c.position,c.direction),c.position=this.vectorHelpers.add(c.position,this.viewportDelta),this.jitter&&(c.position.x+=this.jitter*this.RANDM1TO1(),c.position.y+=this.jitter*this.RANDM1TO1()),c.timeToLive--;var d=c.colour[0]+=c.deltaColour[0],e=c.colour[1]+=c.deltaColour[1],f=c.colour[2]+=c.deltaColour[2],g=c.colour[3]+=c.deltaColour[3];b=[],b.push("rgba("+(d>255?255:0>d?0:~~d)),b.push(e>255?255:0>e?0:~~e),b.push(f>255?255:0>f?0:~~f),b.push((g>1?1:0>g?0:g.toFixed(2))+")"),c.drawColour=b.join(","),this.fastMode||(b[3]="0)",c.drawColourEnd=b.join(",")),this.particleIndex++}else this.particleIndex!=this.particleCount-1&&(this.particles[this.particleIndex]=this.particles[this.particleCount-1]),this.particleCount--;var h={};h._x=~~c.position.x,h._y=~~c.position.y,h._w=c.size,h._h=c.size,this.register.push(h)}},stop:function(){this.active=!1,this.elapsedFrames=0,this.emitCounter=0,this.parentEntity.trigger("ParticleEnd")},render:function(a){for(var b=0,c=this.particleCount;c>b;b++){var e=this.particles[b],f=e.size,g=f>>1;if(!(e.position.x+f<0||e.position.y+f<0||e.position.x-f>d.viewport.width||e.position.y-f>d.viewport.height)){var h=~~e.position.x,i=~~e.position.y;if(this.fastMode)a.fillStyle=e.drawColour;else{var j=a.createRadialGradient(h+g,i+g,e.sizeSmall,h+g,i+g,g);j.addColorStop(0,e.drawColour),j.addColorStop(.9,e.drawColourEnd),a.fillStyle=j}a.fillRect(h,i,f,f)}}},particle:function(a){this.position=a.create(0,0),this.direction=a.create(0,0),this.size=0,this.sizeSmall=0,this.timeToLive=0,this.colour=[],this.drawColour="",this.deltaColour=[],this.sharpness=0},vectorHelpers:{create:function(a,b){return{x:a,y:b}},multiply:function(a,b){return a.x*=b,a.y*=b,a},add:function(a,b){return a.x+=b.x,a.y+=b.y,a}}}})},{"../core/core.js":7}],31:[function(a,b,c){var d=a("../core/core.js");d.c("SpriteAnimation",{_reels:null,_currentReelId:null,_currentReel:null,_isPlaying:!1,animationSpeed:1,init:function(){this._reels={}},reel:function(a,b,c,e,f){if(0===arguments.length)return this._currentReelId;if(1===arguments.length&&"string"==typeof a){if("undefined"==typeof this._reels[a])throw"The specified reel "+a+" is undefined.";return this.pauseAnimation(),this._currentReelId!==a&&(this._currentReelId=a,this._currentReel=this._reels[a],this._updateSprite(),this.trigger("ReelChange",this._currentReel)),this}var g,h,i;if(g={id:a,frames:[],currentFrame:0,easing:new d.easing(b),defaultLoops:1},g.duration=g.easing.duration,"number"==typeof c)if(h=c,i=e,f>=0)for(;c+f>h;h++)g.frames.push([h,i]);else for(;h>c+f;h--)g.frames.push([h,i]);else{if(3!==arguments.length||"object"!=typeof c)throw"Urecognized arguments. Please see the documentation for 'reel(...)'.";g.frames=c}return this._reels[a]=g,this},animate:function(a,b){"string"==typeof a&&this.reel(a);var c=this._currentReel;if("undefined"==typeof c||null===c)throw"No reel is specified, and there is no currently active reel.";return this.pauseAnimation(),"undefined"==typeof b&&(b="number"==typeof a?a:1),c.easing.reset(),this.loops(b),this._setFrame(0),this.bind("EnterFrame",this._animationTick),this._isPlaying=!0,this.trigger("StartAnimation",c),this},resumeAnimation:function(){return this._isPlaying===!1&&null!==this._currentReel&&(this.bind("EnterFrame",this._animationTick),this._isPlaying=!0,this._currentReel.easing.resume(),this.trigger("StartAnimation",this._currentReel)),this},pauseAnimation:function(){return this._isPlaying===!0&&(this.unbind("EnterFrame",this._animationTick),this._isPlaying=!1,this._reels[this._currentReelId].easing.pause()),this},resetAnimation:function(){var a=this._currentReel;if(null===a)throw"No active reel to reset.";return this.reelPosition(0),a.easing.repeat(a.defaultLoops),this},loops:function(a){return 0===arguments.length?null!==this._currentReel?this._currentReel.easing.loops:0:(null!==this._currentReel&&(0>a&&(a=1/0),this._currentReel.easing.repeat(a),this._currentReel.defaultLoops=a),this)},reelPosition:function(a){if(null===this._currentReel)throw"No active reel.";if(0===arguments.length)return this._currentReel.currentFrame;var b,c=this._currentReel.frames.length;if("end"===a&&(a=c-1),1>a&&a>0)b=a,a=Math.floor(c*b);else{if(a!==Math.floor(a))throw"Position "+a+" is invalid.";0>a&&(a=c-1+a),b=a/c}return a=Math.min(a,c-1),a=Math.max(a,0),this._setProgress(b),this._setFrame(a),this},_animationTick:function(a){var b=this._reels[this._currentReelId];b.easing.tick(a.dt*this.animationSpeed);var c=b.easing.value(),d=Math.min(Math.floor(b.frames.length*c),b.frames.length-1);this._setFrame(d),b.easing.complete===!0&&(this.pauseAnimation(),this.trigger("AnimationEnd",this._currentReel))},_setFrame:function(a){var b=this._currentReel;a!==b.currentFrame&&(b.currentFrame=a,this._updateSprite(),this.trigger("FrameChange",b))},_updateSprite:function(){var a=this._currentReel,b=a.frames[a.currentFrame];this.sprite(b[0],b[1])},_setProgress:function(a,b){this._currentReel.easing.setProgress(a,b)},isPlaying:function(a){return this._isPlaying?a?this._currentReelId===a:!!this._currentReelId:!1},getReel:function(a){if(0===arguments.length){if(!this._currentReelId)return null;a=this._currentReelId}return this._reels[a]}})},{"../core/core.js":7}],32:[function(a,b,c){var d=a("../core/core.js"),e="attribute vec2 aPosition;\nattribute vec3 aOrientation;\nattribute vec2 aLayer;\nattribute vec2 aTextureCoord;\n\nvarying mediump vec3 vTextureCoord;\n\nuniform vec4 uViewport;\nuniform mediump vec2 uTextureDimensions;\n\nmat4 viewportScale = mat4(2.0 / uViewport.z, 0, 0, 0, 0, -2.0 / uViewport.w, 0,0, 0, 0,1,0, -1,+1,0,1);\nvec4 viewportTranslation = vec4(uViewport.xy, 0, 0);\n\nvoid main() {\n vec2 pos = aPosition;\n vec2 entityOrigin = aOrientation.xy;\n mat2 entityRotationMatrix = mat2(cos(aOrientation.z), sin(aOrientation.z), -sin(aOrientation.z), cos(aOrientation.z));\n \n pos = entityRotationMatrix * (pos - entityOrigin) + entityOrigin ;\n gl_Position = viewportScale * (viewportTranslation + vec4(pos, 1.0/(1.0+exp(aLayer.x) ), 1) );\n vTextureCoord = vec3(aTextureCoord, aLayer.y);\n}",f="varying mediump vec3 vTextureCoord;\n \nuniform sampler2D uSampler;\nuniform mediump vec2 uTextureDimensions;\n\nvoid main(void) {\n highp vec2 coord = vTextureCoord.xy / uTextureDimensions;\n mediump vec4 base_color = texture2D(uSampler, coord);\n gl_FragColor = vec4(base_color.rgb*base_color.a*vTextureCoord.z, base_color.a*vTextureCoord.z);\n}",g=[{name:"aPosition",width:2},{name:"aOrientation",width:3},{name:"aLayer",width:2},{name:"aTextureCoord",width:2}];d.extend({sprite:function(a,b,c,h,i,j,k){var l,m,n;"string"==typeof a&&(j=i,i=h,h=b,c=a,a=1,b=1),"string"==typeof b&&(j=i,i=h,h=c,c=b,b=a),!j&&i&&(j=i),i=parseInt(i||0,10),j=parseInt(j||0,10);var o=function(){this.ready=!0,this.trigger("Invalidate")};n=d.asset(c),n||(n=new Image,n.src=c,d.asset(c,n),n.onload=function(){for(var a in h)d(a).each(o)});var p=function(){this.requires("2D, Sprite"),this.__trim=[0,0,0,0],this.__image=c,this.__coord=[this.__coord[0],this.__coord[1],this.__coord[2],this.__coord[3]],this.__tile=a,this.__tileh=b,this.__padding=[i,j],this.__padBorder=k,this.sprite(this.__coord[0],this.__coord[1],this.__coord[2],this.__coord[3]),this.img=n,this.img.complete&&this.img.width>0&&(this.ready=!0,this.trigger("Invalidate")),this.w=this.__coord[2],this.h=this.__coord[3],this.has("WebGL")&&(this._establishShader(this.__image,f,e,g),this.program.setTexture(this.webgl.makeTexture(this.__image,this.img,!1)))};for(l in h)h.hasOwnProperty(l)&&(m=h[l],d.c(l,{ready:!1,__coord:[m[0],m[1],m[2]||1,m[3]||1],init:p}));return this}}),d.c("Sprite",{__image:"",__tile:0,__tileh:0,__padding:null,__trim:null,img:null,ready:!1,init:function(){this.__trim=[0,0,0,0],this.bind("Draw",this._drawSprite)},remove:function(){this.unbind("Draw",this._drawSprite)},_drawSprite:function(a){var b=a.co,c=a.pos,d=a.ctx;if("canvas"===a.type)d.drawImage(this.img,b.x,b.y,b.w,b.h,c._x,c._y,c._w,c._h);else if("DOM"===a.type){var e=this._h/b.h,f=this._w/b.w,g=this._element.style,h=g.backgroundColor;"initial"===h&&(h="");var i=h+" url('"+this.__image+"') no-repeat";i!==g.background&&(g.background=i),g.backgroundPosition="-"+b.x*f+"px -"+b.y*e+"px",(1!=e||1!=f)&&(g.backgroundSize=this.img.width*f+"px "+this.img.height*e+"px")}else"webgl"===a.type&&a.program.writeVector("aTextureCoord",b.x,b.y,b.x,b.y+b.h,b.x+b.w,b.y,b.x+b.w,b.y+b.h)},sprite:function(a,b,c,d){return this.__coord=this.__coord||[0,0,0,0],this.__coord[0]=a*(this.__tile+this.__padding[0])+(this.__padBorder?this.__padding[0]:0)+this.__trim[0],this.__coord[1]=b*(this.__tileh+this.__padding[1])+(this.__padBorder?this.__padding[1]:0)+this.__trim[1],"undefined"!=typeof c&&"undefined"!=typeof d&&(this.__coord[2]=this.__trim[2]||c*this.__tile||this.__tile,this.__coord[3]=this.__trim[3]||d*this.__tileh||this.__tileh),this.trigger("Invalidate"),this},crop:function(a,b,c,d){var e=this._mbr||this.pos();return this.__trim=[],this.__trim[0]=a,this.__trim[1]=b,this.__trim[2]=c,this.__trim[3]=d,this.__coord[0]+=a,this.__coord[1]+=b,this.__coord[2]=c,this.__coord[3]=d,this._w=c,this._h=d,this.trigger("Invalidate",e),this}})},{"../core/core.js":7}],33:[function(a,b,c){var d=a("../core/core.js");d.c("Text",{_text:"",defaultSize:"10px",defaultFamily:"sans-serif",defaultVariant:"normal",defaultLineHeight:"normal",ready:!0,init:function(){this.requires("2D"),this._textFont={type:"",weight:"",size:this.defaultSize,lineHeight:this.defaultLineHeight,family:this.defaultFamily,variant:this.defaultVariant},this.bind("Draw",function(a){var b=this._fontString();if("DOM"===a.type){var c=this._element,d=c.style;d.color=this._textColor,d.font=b,c.innerHTML=this._text}else if("canvas"===a.type){var e=a.ctx;e.save(),e.textBaseline="top",e.fillStyle=this._textColor||"rgb(0,0,0)",e.font=b,e.fillText(this._text,a.pos._x,a.pos._y),e.restore()}})},_getFontHeight:function(){var a=/([a-zA-Z]+)\b/,b={px:1,pt:4/3,pc:16,cm:96/2.54,mm:96/25.4,"in":96,em:void 0,ex:void 0};return function(c){var d=parseFloat(c),e=a.exec(c),f=e?e[1]:"px";return void 0!==b[f]?Math.ceil(d*b[f]):Math.ceil(d)}}(),text:function(a){return"undefined"==typeof a||null===a?this._text:("function"==typeof a?this._text=a.call(this):this._text=a,this.has("Canvas")&&this._resizeForCanvas(),this.trigger("Invalidate"),this)},_resizeForCanvas:function(){var a=this._drawContext;a.font=this._fontString(),this.w=a.measureText(this._text).width;var b=this._textFont.size||this.defaultSize;this.h=1.1*this._getFontHeight(b)},_fontString:function(){return this._textFont.type+" "+this._textFont.variant+" "+this._textFont.weight+" "+this._textFont.size+" / "+this._textFont.lineHeight+" "+this._textFont.family},textColor:function(a){return d.assignColor(a,this),this._textColor="rgba("+this._red+", "+this._green+", "+this._blue+", "+this._strength+")",this.trigger("Invalidate"),this},textFont:function(a,b){if(1===arguments.length){if("string"==typeof a)return this._textFont[a];if("object"==typeof a)for(var c in a)"family"==c?this._textFont[c]="'"+a[c]+"'":this._textFont[c]=a[c]}else this._textFont[a]=b;return this.has("Canvas")&&this._resizeForCanvas(),this.trigger("Invalidate"),this},unselectable:function(){return this.has("DOM")&&(this.css({"-webkit-touch-callout":"none","-webkit-user-select":"none","-khtml-user-select":"none","-moz-user-select":"none","-ms-user-select":"none","user-select":"none",cursor:"default"}),this.trigger("Invalidate")),this}})},{"../core/core.js":7}],34:[function(a,b,c){var d=a("../core/core.js"),e=window.document;d.extend({viewport:{clampToEntities:!0,_width:0,_height:0,_x:0,_y:0,_scale:1,bounds:null,scroll:function(a,b){this[a]=b,d.trigger("ViewportScroll"),d.trigger("InvalidateViewport")},rect_object:{_x:0,_y:0,_w:0,_h:0},rect:function(){return this.rect_object._x=-this._x,this.rect_object._y=-this._y,this.rect_object._w=this._width/this._scale,this.rect_object._h=this._height/this._scale,this.rect_object},pan:function(){function a(a){h.tick(a.dt);var i=h.value();d.viewport.x=(1-i)*f+i*c,d.viewport.y=(1-i)*g+i*e,d.viewport._clamp(),h.complete&&(b(),d.trigger("CameraAnimationDone"))}function b(){d.unbind("EnterFrame",a)}var c,e,f,g,h;return d._preBind("StopCamera",b),function(b,i,j,k){d.trigger("StopCamera"),"reset"!=b&&(f=d.viewport._x,g=d.viewport._y,c=f-b,e=g-i,h=new d.easing(j,k),d.uniqueBind("EnterFrame",a))}}(),follow:function(){function a(){var a=d.viewport._scale;d.viewport.scroll("_x",-(this.x+this.w/2-d.viewport.width/2/a-e*a)),d.viewport.scroll("_y",-(this.y+this.h/2-d.viewport.height/2/a-f*a)),d.viewport._clamp()}function b(){c&&(c.unbind("Move",a),c.unbind("ViewportScale",a),c.unbind("ViewportResize",a))}var c,e,f;return d._preBind("StopCamera",b),function(b,g,h){b&&b.has("2D")&&(d.trigger("StopCamera"),c=b,e="undefined"!=typeof g?g:0,f="undefined"!=typeof h?h:0,b.bind("Move",a),b.bind("ViewportScale",a),b.bind("ViewportResize",a),a.call(b))}}(),centerOn:function(a,b){var c=a.x+d.viewport.x,e=a.y+d.viewport.y,f=a.w/2,g=a.h/2,h=d.viewport.width/2/d.viewport._scale,i=d.viewport.height/2/d.viewport._scale,j=c+f-h,k=e+g-i;d.viewport.pan(j,k,b)},zoom:function(){function a(){d.unbind("EnterFrame",b)}function b(b){var e,l;k.tick(b.dt),e=Math.pow(f,k.value()),l=1===f?k.value():(1/e-1)/(1/f-1),d.viewport.scale(e*c),d.viewport.scroll("_x",g*(1-l)+h*l),d.viewport.scroll("_y",i*(1-l)+j*l),d.viewport._clamp(),k.complete&&(a(),d.trigger("CameraAnimationDone"))}d._preBind("StopCamera",a);var c,e,f,g,h,i,j,k;return function(a,l,m,n,o){return a?(arguments.length<=2&&(n=l,l=d.viewport.x-d.viewport.width,m=d.viewport.y-d.viewport.height),d.trigger("StopCamera"),c=d.viewport._scale,f=a,e=c*f,g=d.viewport.x,i=d.viewport.y,h=-(l-d.viewport.width/(2*e)),j=-(m-d.viewport.height/(2*e)),k=new d.easing(n,o),void d.uniqueBind("EnterFrame",b)):void d.viewport.scale(1)}}(),scale:function(){return function(a){this._scale=a?a:1,d.trigger("InvalidateViewport"),d.trigger("ViewportScale")}}(),mouselook:function(){var a=!1,b=!1,c={};return old={},function(e,f){if("boolean"==typeof e)return a=e,void(a?d.mouseObjs++:d.mouseObjs=Math.max(0,d.mouseObjs-1));if(a)switch(e){case"move":case"drag":if(!b)return;diff={x:f.clientX-c.x,y:f.clientY-c.y},c.x=f.clientX,c.y=f.clientY,d.viewport.x+=diff.x,d.viewport.y+=diff.y,d.viewport._clamp();break;case"start":d.trigger("StopCamera"),c.x=f.clientX,c.y=f.clientY,b=!0;break;case"stop":b=!1}}}(),_clamp:function(){if(this.clampToEntities){var a=d.clone(this.bounds)||d.map.boundaries();a.max.x*=this._scale,a.min.x*=this._scale,a.max.y*=this._scale,a.min.y*=this._scale,a.max.x-a.min.x>d.viewport.width?d.viewport.x<(-a.max.x+d.viewport.width)/this._scale?d.viewport.x=(-a.max.x+d.viewport.width)/this._scale:d.viewport.x>-a.min.x&&(d.viewport.x=-a.min.x):d.viewport.x=-1*(a.min.x+(a.max.x-a.min.x)/2-d.viewport.width/2),a.max.y-a.min.y>d.viewport.height?d.viewport.y<(-a.max.y+d.viewport.height)/this._scale?d.viewport.y=(-a.max.y+d.viewport.height)/this._scale:d.viewport.y>-a.min.y&&(d.viewport.y=-a.min.y):d.viewport.y=-1*(a.min.y+(a.max.y-a.min.y)/2-d.viewport.height/2)}},init:function(a,b,c){this._defineViewportProperties(),this._x=0,this._y=0,this._scale=1,this.bounds=null,this._width=a||window.innerWidth,this._height=b||window.innerHeight,"undefined"==typeof c&&(c="cr-stage");var f;if("string"==typeof c)f=e.getElementById(c);else{if(!("undefined"!=typeof HTMLElement?c instanceof HTMLElement:c instanceof Element))throw new TypeError("stage_elem must be a string or an HTMLElement");f=c}d.stage={x:0,y:0,fullscreen:!1,elem:f?f:e.createElement("div")},a||b||(e.body.style.overflow="hidden",d.stage.fullscreen=!0),d.addEvent(this,window,"resize",d.viewport.reload),d.addEvent(this,window,"blur",function(){d.settings.get("autoPause")&&(d._paused||d.pause())}),d.addEvent(this,window,"focus",function(){d._paused&&d.settings.get("autoPause")&&d.pause()}),d.settings.register("stageSelectable",function(a){d.stage.elem.onselectstart=a?function(){return!0}:function(){return!1}}),d.settings.modify("stageSelectable",!1),d.settings.register("stageContextMenu",function(a){d.stage.elem.oncontextmenu=a?function(){return!0}:function(){return!1}}),d.settings.modify("stageContextMenu",!1),d.settings.register("autoPause",function(){}),d.settings.modify("autoPause",!1),f||(e.body.appendChild(d.stage.elem),d.stage.elem.id=c);var g,h=d.stage.elem.style;if(h.width=this.width+"px",h.height=this.height+"px",h.overflow="hidden",d.bind("ViewportResize",function(){d.trigger("InvalidateViewport")}),d.mobile){void 0!==typeof h.webkitTapHighlightColor&&(h.webkitTapHighlightColor="rgba(0,0,0,0)");var i=e.createElement("meta"),j=e.getElementsByTagName("head")[0];i=e.createElement("meta"),i.setAttribute("name","apple-mobile-web-app-capable"),i.setAttribute("content","yes"),j.appendChild(i),d.addEvent(this,d.stage.elem,"touchmove",function(a){a.preventDefault()})}h.position="relative",g=d.domHelper.innerPosition(d.stage.elem),d.stage.x=g.x,d.stage.y=g.y,d.uniqueBind("ViewportResize",this._resize)},_resize:function(){d.stage.elem.style.width=d.viewport.width+"px",d.stage.elem.style.height=d.viewport.height+"px"},_defineViewportProperties:function(){Object.defineProperty(this,"x",{set:function(a){this.scroll("_x",a)},get:function(){return this._x},configurable:!0}),Object.defineProperty(this,"y",{set:function(a){this.scroll("_y",a)},get:function(){return this._y},configurable:!0}),Object.defineProperty(this,"width",{set:function(a){this._width=a,d.trigger("ViewportResize")},get:function(){return this._width},configurable:!0}),Object.defineProperty(this,"height",{set:function(a){this._height=a,d.trigger("ViewportResize")},get:function(){return this._height},configurable:!0})},reload:function(){var a,b=window.innerWidth,c=window.innerHeight;d.stage.fullscreen&&(this._width=b,this._height=c,d.trigger("ViewportResize")),a=d.domHelper.innerPosition(d.stage.elem),d.stage.x=a.x,d.stage.y=a.y},reset:function(){d.viewport.mouselook("stop"),d.trigger("StopCamera"),d.viewport.scroll("_x",0),d.viewport.scroll("_y",0),d.viewport.scale(1)},onScreen:function(a){return d.viewport._x+a._x+a._w>0&&d.viewport._y+a._y+a._h>0&&d.viewport._x+a._x=this.max_size)){var b=Math.min(a,this.max_size),c=new Float32Array(4*b*this.stride),d=new Uint16Array(6*b);c.set(this._attributeArray),d.set(this._indexArray),this._attributeArray=c,this._indexArray=d,this.array_size=b}},registerEntity:function(a){if(0===this._registryHoles.length){if(this._registrySize>=this.max_size)throw"Number of entities exceeds maximum limit.";this._registrySize>=this.array_size&&this.growArrays(2*this.array_size),a._glBufferIndex=this._registrySize,this._registrySize++}else a._glBufferIndex=this._registryHoles.pop()},unregisterEntity:function(a){"number"==typeof a._glBufferIndex&&this._registryHoles.push(a._glBufferIndex),a._glBufferIndex=null},resetRegistry:function(){this._maxElement=0,this._registryHoles.length=0},setCurrentEntity:function(a){this.ent_offset=4*a._glBufferIndex,this.ent=a},switchTo:function(){var a=this.context;a.useProgram(this.shader),a.bindBuffer(a.ARRAY_BUFFER,this._attributeBuffer);for(var b,c=this.attributes,e=0;ej;j++)for(var k=0;g>k;k++)i[f+e*j+k]=arguments[(g*j+k)%h+1]}},d.c("WebGL",{init:function(){d.webgl.context||d.webgl.init();var a=this.webgl=d.webgl;a.context;this._changed=!0,this.bind("Change",this._glChange)},remove:function(){this._changed=!0,this.unbind(this._glChange),this.program&&this.program.unregisterEntity(this)},_glChange:function(){this._changed===!1&&(this._changed=!0)},drawVars:{type:"webgl",pos:{},ctx:null,coord:[0,0,0,0],co:{x:0,y:0,w:0,h:0}},draw:function(a,b,c,d,e){if(this.ready){4===arguments.length&&(e=d,d=c,c=b,b=a,a=this.webgl.context);var f=this.drawVars.pos;f._x=this._x+(b||0),f._y=this._y+(c||0),f._w=d||this._w,f._h=e||this._h;var g=this.__coord||[0,0,0,0],h=this.drawVars.co;h.x=g[0]+(b||0),h.y=g[1]+(c||0),h.w=d||g[2],h.h=e||g[3],this._flipX&&(h.x=h.x+h.w,h.w=-h.w),this._flipY&&(h.y=h.y+h.h,h.h=-h.h);var i=this.webgl.context;this.drawVars.gl=i;var j=this.drawVars.program=this.program;return j.setCurrentEntity(this),j.writeVector("aPosition",this._x,this._y,this._x,this._y+this._h,this._x+this._w,this._y,this._x+this._w,this._y+this._h),j.writeVector("aOrientation",this._origin.x+this._x,this._origin.y+this._y,this._rotation*Math.PI/180),j.writeVector("aLayer",this._globalZ,this._alpha),this.trigger("Draw",this.drawVars),j.addIndices(j.ent_offset),this}},_establishShader:function(a,b,c,d){this.program=this.webgl.getProgramWrapper(a,b,c,d),this.program.registerEntity(this),this.ready=!0}}),d.extend({webgl:{context:null,changed_objects:[],_compileShader:function(a,b){var c=this.context,d=c.createShader(b);if(c.shaderSource(d,a),c.compileShader(d),!c.getShaderParameter(d,c.COMPILE_STATUS))throw c.getShaderInfoLog(d);return d},_makeProgram:function(a,b){var c=this.context,d=this._compileShader(a,c.FRAGMENT_SHADER),e=this._compileShader(b,c.VERTEX_SHADER),f=c.createProgram();if(c.attachShader(f,e),c.attachShader(f,d),c.linkProgram(f),!c.getProgramParameter(f,c.LINK_STATUS))throw"Could not initialise shaders";return f.viewport=c.getUniformLocation(f,"uViewport"),f},programs:{},getProgramWrapper:function(a,b,c,e){if(void 0===this.programs[a]){var f=this._makeProgram(b,c),g=new RenderProgramWrapper(this.context,f);g.name=a,g.initAttributes(e),g.setViewportUniforms(d.viewport),this.programs[a]=g}return this.programs[a]},makeTexture:function(a,b,c){var d=this;return d.texture_manager.makeTexture(a,b,c)},init:function(){if(!d.support.webgl)return d.trigger("NoWebGL"),void d.stop();this.changed_objects=[];var a;a=e.createElement("canvas"),a.width=d.viewport.width,a.height=d.viewport.height,a.style.position="absolute",a.style.left="0px",a.style.top="0px",d.stage.elem.appendChild(a);var b;try{b=a.getContext("webgl",{premultipliedalpha:!0})||a.getContext("experimental-webgl",{premultipliedalpha:!0}),b.viewportWidth=a.width,b.viewportHeight=a.height}catch(c){return d.trigger("NoWebGL"),void d.stop()}this.context=b,this._canvas=a,b.clearColor(0,0,0,0),b.disable(b.DEPTH_TEST),b.blendFunc(b.ONE,b.ONE_MINUS_SRC_ALPHA),b.enable(b.BLEND);var f=this;d.uniqueBind("RenderScene",f.render),d.uniqueBind("ViewportResize",f._resize),d.uniqueBind("InvalidateViewport",function(){f.dirtyViewport=!0}),d.uniqueBind("PixelartSet",f._setPixelart),f._setPixelart(d._pixelartEnabled),this.dirtyViewport=!0,this.texture_manager=new d.TextureManager(b,this)},_resize:function(){var a=d.webgl._canvas;a.width=d.viewport.width,a.height=d.viewport.height;var b=d.webgl.context;b.viewportWidth=a.width,b.viewportHeight=a.height},_setPixelart:function(a){var b=d.webgl.context;a?d.webgl.texture_filter=b.NEAREST:d.webgl.texture_filter=b.LINEAR},zsort:function(a,b){return a._globalZ-b._globalZ},visible_gl:[],render:function(a){a=a||d.viewport.rect();var b=d.webgl,c=b.context;c.viewport(0,0,c.viewportWidth,c.viewportHeight),c.clear(c.COLOR_BUFFER_BIT|c.DEPTH_BUFFER_BIT);var e=b.programs;if(b.dirtyViewport){for(var f in e)e[f].setViewportUniforms(d.viewport);b.dirtyViewport=!1}var g,h=d.map.search(a),i=0,j=h.length,k=b.visible_gl;for(k.length=0,i=0;j>i;i++)g=h[i],g._visible&&g.__c.WebGL&&g.program&&k.push(g);k.sort(b.zsort),j=k.length;var l=null;for(i=0;j>i;i++)g=k[i],l!==g.program&&(null!==l&&l.renderBatch(),l=g.program,l.index_pointer=0,l.switchTo()),g.draw(),g._changed=!1;null!==l&&l.renderBatch()}}})},{"../core/core.js":7}],36:[function(a,b,c){var d=a("../core/core.js");d.extend({diamondIso:{_tile:{width:0,height:0},getTileDimensions:function(){return{w:this._tile.width,h:this._tile.height}},_map:{width:0,height:0},_origin:{x:0,y:0},_tiles:[],getTile:function(a,b,c){return this._tiles[a][b][c]},init:function(a,b,c,d,e,f){this._tile.width=parseInt(a,10),this._tile.height=parseInt(b,10)||parseInt(a,10)/2,this._tile.r=this._tile.width/this._tile.height,this._map.width=parseInt(c,10),this._map.height=parseInt(d,10)||parseInt(c,10);for(var g=0;c>g;g++){this._tiles[g]=Array();for(var h=0;d>h;h++)this._tiles[g][h]=Array()}return this.x=parseInt(e,10)||0,this.y=parseInt(f,10)||0,this.layerZLevel=c+d+1,this},place:function(a,b,c,d){var e=this.pos2px(b,c),f=(a.tileHeight,a.h/this._tile.height);a.x=e.x,a.y=e.y-(f-2)*this._tile.height-this._tile.height*d, -a.z=this.getZAtLoc(b,c,d);for(var g=0;f-2>=g;g++){var h=this._tiles[b][c][d+g];h&&h!==a&&h.destroy(),this._tiles[b][c][d+g]=a}return this},detachTile:function(a){for(var b=0;be;e++)if(this._tiles[b][c][e]&&a===this._tiles[b][c][e]){tHeight=a.h/this._tile.height;for(var f=0;f0&&e0&&fk;k++){var l=this._tiles[e+k][f+k][k];l&&d.push(l)}return d},polygon:function(a){a.requires("Collision");var b=0,c=0,e=[b-0,a.h-c-this._tile.height/2,b-this._tile.width/2,a.h-c-0,b-this._tile.width,a.h-c-this._tile.height/2,b-this._tile.width/2,a.h-c-this._tile.height],f=new d.polygon(e);return f}}})},{"../core/core.js":7}],37:[function(a,b,c){var d=a("../core/core.js");d.extend({isometric:{_tile:{width:0,height:0},_elements:{},_pos:{x:0,y:0},_z:0,size:function(a,b){return this._tile.width=a,this._tile.height=b>0?b:a/2,this},place:function(a,b,c,e){var f=this.pos2px(a,b);return f.top-=c*(this._tile.height/2),e.attr({x:f.left+d.viewport._x,y:f.top+d.viewport._y}).z+=c,this},pos2px:function(a,b){return{left:a*this._tile.width+(1&b)*(this._tile.width/2),top:b*this._tile.height/2}},px2pos:function(a,b){return{x:-Math.ceil(-a/this._tile.width-.5*(1&b)),y:b/this._tile.height*2}},centerAt:function(a,b){if("number"==typeof a&&"number"==typeof b){var c=this.pos2px(a,b);return d.viewport._x=-c.left+d.viewport.width/2-this._tile.width/2,d.viewport._y=-c.top+d.viewport.height/2-this._tile.height/2,this}return{top:-d.viewport._y+d.viewport.height/2-this._tile.height/2,left:-d.viewport._x+d.viewport.width/2-this._tile.width/2}},area:function(){var a=this.centerAt(),b=this.px2pos(-a.left+d.viewport.width/2,-a.top+d.viewport.height/2),c=this.px2pos(-a.left-d.viewport.width/2,-a.top-d.viewport.height/2);return{x:{start:b.x,end:c.x},y:{start:b.y,end:c.y}}}}})},{"../core/core.js":7}],38:[function(a,b,d){var e=a("../core/core.js"),f=window.document;e.extend({audio:{sounds:{},supported:null,codecs:{ogg:'audio/ogg; codecs="vorbis"',wav:'audio/wav; codecs="1"',webma:'audio/webm; codecs="vorbis"',mp3:'audio/mpeg; codecs="mp3"',m4a:'audio/mp4; codecs="mp4a.40.2"'},volume:1,muted:!1,paused:!1,playCheck:null,_canPlay:function(){if(this.supported={},e.support.audio){var a,b=this.audioElement();for(var c in this.codecs)a=b.canPlayType(this.codecs[c]),""!==a&&"no"!==a?this.supported[c]=!0:this.supported[c]=!1}},supports:function(a){return null===this.supported&&this._canPlay(),this.supported[a]?!0:!1},audioElement:function(){return"undefined"!=typeof Audio?new Audio(""):f.createElement("audio")},create:function(a,b){var c=b.substr(b.lastIndexOf(".")+1).toLowerCase();if(!this.supports(c))return!1;var d=this.audioElement();return d.id=a,d.preload="auto",d.volume=e.audio.volume,d.src=b,e.asset(b,d),this.sounds[a]={obj:d,played:0,volume:e.audio.volume},this.sounds[a]},add:function(a,b){if(e.support.audio){var c,d;if(1===arguments.length&&"object"==typeof a)for(var f in a)for(c in a[f])if(d=e.audio.create(f,a[f][c]))break;if("string"==typeof a&&("string"==typeof b&&(d=e.audio.create(a,b)),"object"==typeof b))for(c in b)if(d=e.audio.create(a,b[c]))break;return d}},play:function(a,b,c){if(0!==b&&e.support.audio&&this.sounds[a]){var d=this.sounds[a],f=this.getOpenChannel();if(!f)return null;f.id=a,f.repeat=b;var g=f.obj;return f.volume=d.volume=d.obj.volume=c||e.audio.volume,g.volume=d.volume,g.src=d.obj.src,this.muted&&(g.volume=0),g.play(),d.played++,f.onEnd=function(){d.played0&&this._cascade(a)}),this.bind("Rotate",function(a){var b=this._cbr||this._mbr||this;this._entry.update(b),this._children.length>0&&this._cascade(a)}),this.bind("Remove",function(){if(this._children){for(var a=0;ai&&i>-1e-10?0:i,j=1e-10>j&&j>-1e-10?0:j;var k=d*i+f*j,l=-d*j+f*i,m=e*i+f*j,n=-e*j+f*i,o=e*i+g*j,p=-e*j+g*i,q=d*i+g*j,r=-d*j+g*i,s=Math.floor(Math.min(k,m,o,q)+a),t=Math.floor(Math.min(l,n,p,r)+b),u=Math.ceil(Math.max(k,m,o,q)+a),v=Math.ceil(Math.max(l,n,p,r)+b);if(this._mbr?(this._mbr._x=s,this._mbr._y=t,this._mbr._w=u-s,this._mbr._h=v-t):this._mbr={_x:s,_y:t,_w:u-s,_h:v-t},this._cbr){var w=this._cbr,x=w.cx,y=w.cy,z=w.r,A=a+(x+this._x-a)*i+(y+this._y-b)*j,B=b-(x+this._x-a)*j+(y+this._y-b)*i;w._x=Math.min(A-z,s),w._y=Math.min(B-z,t),w._w=Math.max(A+z,u)-w._x,w._h=Math.max(B+z,v)-w._y}},_rotate:function(a){var b=this._rotation-a;if(0!==b){this._rotation=a;var c={x:this._origin.x+this._x,y:this._origin.y+this._y};this._calculateMBR();var d=b*h,e=Math.cos(d),f=Math.sin(d);this.trigger("Rotate",{cos:e>-1e-10&&1e-10>e?0:e,sin:f>-1e-10&&1e-10>f?0:f,deg:b,rad:d,o:c})}},area:function(){return this._w*this._h},intersect:function(a,b,c,d){var e,f=this._mbr||this;return e="object"==typeof a?a:{_x:a,_y:b,_w:c,_h:d},f._xe._x&&f._ye._y},within:function(a,b,c,d){var e,f=this._mbr||this;return e="object"==typeof a?a:{_x:a,_y:b,_w:c,_h:d},e._x<=f._x&&e._x+e._w>=f._x+f._w&&e._y<=f._y&&e._y+e._h>=f._y+f._h},contains:function(a,b,c,d){var e,f=this._mbr||this;return e="object"==typeof a?a:{_x:a,_y:b,_w:c,_h:d},e._x>=f._x&&e._x+e._w<=f._x+f._w&&e._y>=f._y&&e._y+e._h<=f._y+f._h},pos:function(a){return a=a||{},a._x=this._x,a._y=this._y,a._w=this._w,a._h=this._h,a},mbr:function(a){return a=a||{},this._mbr?(a._x=this._mbr._x,a._y=this._mbr._y,a._w=this._mbr._w,a._h=this._mbr._h,a):this.pos(a)},isAt:function(a,b){if(this.mapArea)return this.mapArea.containsPoint(a,b);if(this.map)return this.map.containsPoint(a,b);var c=this._mbr||this;return c._x<=a&&c._x+c._w>=a&&c._y<=b&&c._y+c._h>=b},move:function(a,b){return"n"===a.charAt(0)&&(this.y-=b),"s"===a.charAt(0)&&(this.y+=b),("e"===a||"e"===a.charAt(1))&&(this.x+=b),("w"===a||"w"===a.charAt(1))&&(this.x-=b),this},shift:function(a,b,c,d){return a&&(this.x+=a),b&&(this.y+=b),c&&(this.w+=c),d&&(this.h+=d),this},_cascade:function(a){if(a){var b,c=0,d=this._children,e=d.length;if("cos"in a||"sin"in a)for(;e>c;++c)b=d[c],"rotate"in b&&b.rotate(a);else for(var f=this._x-a._x,g=this._y-a._y,h=this._w-a._w,i=this._h-a._h;e>c;++c)b=d[c],b.shift(f,g,h,i)}},attach:function(){for(var a,b=0,c=arguments,d=arguments.length;d>b;++b)a=c[b],a._parent&&a._parent.detach(a),a._parent=this,this._children.push(a);return this},detach:function(a){var b;if(!a){for(b=0;bk;++k)if(h=j[k],i=h._cbr||h._mbr||h,h!==this&&h.__c[a]&&c(i,f)&&(this.canLand=!0,this.trigger("CheckLanding",h),this.canLand)){this._ground=b=h,this.y=h._y-this._h,this.trigger("LandedOnGround",b);break}}}),d.c("GroundAttacher",{_groundAttach:function(a){a.attach(this)},_groundDetach:function(a){a.detach(this)},init:function(){this.requires("Supportable"),this.bind("LandedOnGround",this._groundAttach),this.bind("LiftedOffGround",this._groundDetach)},remove:function(a){this.unbind("LandedOnGround",this._groundAttach),this.unbind("LiftedOffGround",this._groundDetach)}}),d.c("Gravity",{_gravityConst:500,init:function(){this.requires("2D, Supportable, Motion"),this.bind("LiftedOffGround",this._startGravity),this.bind("LandedOnGround",this._stopGravity)},remove:function(a){this.unbind("LiftedOffGround",this._startGravity),this.unbind("LandedOnGround",this._stopGravity)},_gravityCheckLanding:function(a){this._dy<0&&(this.canLand=!1)},gravity:function(a){return this.bind("CheckLanding",this._gravityCheckLanding),this.startGroundDetection(a),this._startGravity(),this},antigravity:function(){return this._stopGravity(),this.stopGroundDetection(),this.unbind("CheckLanding",this._gravityCheckLanding),this},gravityConst:function(a){return this._gravityActive&&(this.ay-=this._gravityConst,this.ay+=a),this._gravityConst=a,this},_startGravity:function(){this._gravityActive=!0,this.ay+=this._gravityConst},_stopGravity:function(){this.ay=0,this.vy=0,this._gravityActive=!1}});var i=function(a,b,c,e){var f=b+c,g="_"+f,h={key:"",oldValue:0};e?d.defineField(a,f,function(){return this[g]},function(a){var b=this[g];a!==b&&(this[g]=a,h.key=f,h.oldValue=b,this.trigger("MotionChange",h))}):d.defineField(a,f,function(){return this[g]},function(a){}),Object.defineProperty(a,g,{value:0,writable:!0,enumerable:!1,configurable:!1})},j=function(a,b,c,e){var f=b+"x",g=b+"y",h="_"+f,i="_"+g;return c?(d.defineField(e,"x",function(){return a[h]},function(b){a[f]=b}),d.defineField(e,"y",function(){return a[i]},function(b){a[g]=b})):(d.defineField(e,"x",function(){return a[h]},function(a){}),d.defineField(e,"y",function(){return a[i]},function(a){})),Object.seal&&Object.seal(e),e};d.c("AngularMotion",{_vrotation:0,_arotation:0,_drotation:0,init:function(){this.requires("2D"),i(this,"v","rotation",!0),i(this,"a","rotation",!0),i(this,"d","rotation",!1),this.__oldRotationDirection=0,this.bind("EnterFrame",this._angularMotionTick)},remove:function(a){this.unbind("EnterFrame",this._angularMotionTick)},resetAngularMotion:function(){return this._drotation=0,this.vrotation=0,this.arotation=0,this},_angularMotionTick:function(a){var b=a.dt/1e3,c=this._rotation,d=this._vrotation,e=this._arotation,f=c+d*b+.5*e*b*b;this.vrotation=d+e*b;var g=this._vrotation,h=g?0>g?-1:1:0;this.__oldRotationDirection!==h&&(this.__oldRotationDirection=h,this.trigger("NewRotationDirection",h)),this._drotation=f-c,0!==this._drotation&&(this.rotation=f,this.trigger("Rotated",c))}}),d.c("Motion",{_vx:0,_vy:0,_ax:0,_ay:0,_dx:0,_dy:0,init:function(){this.requires("2D"),i(this,"v","x",!0),i(this,"v","y",!0),this._velocity=j(this,"v",!0,new d.math.Vector2D),i(this,"a","x",!0),i(this,"a","y",!0),this._acceleration=j(this,"a",!0,new d.math.Vector2D),i(this,"d","x",!1),i(this,"d","y",!1),this._motionDelta=j(this,"d",!1,new d.math.Vector2D),this.__movedEvent={axis:"",oldValue:0},this.__oldDirection={x:0,y:0},this.bind("EnterFrame",this._linearMotionTick)},remove:function(a){this.unbind("EnterFrame",this._linearMotionTick)},resetMotion:function(){return this.vx=0,this.vy=0,this.ax=0,this.ay=0,this._dx=0,this._dy=0,this},motionDelta:function(){return this._motionDelta},velocity:function(){return this._velocity},acceleration:function(){return this._acceleration},_linearMotionTick:function(a){var b=a.dt/1e3,c=this._x,d=this._vx,e=this._ax,f=this._y,g=this._vy,h=this._ay,i=c+d*b+.5*e*b*b,j=f+g*b+.5*h*b*b;this.vx=d+e*b,this.vy=g+h*b;var k=this.__oldDirection,l=this._vx,m=l?0>l?-1:1:0,n=this._vy,o=n?0>n?-1:1:0;(k.x!==m||k.y!==o)&&(k.x=m,k.y=o,this.trigger("NewDirection",k));var p=this.__movedEvent;this._dx=i-c,this._dy=j-f,0!==this._dx&&(this.x=i,p.axis="x",p.oldValue=c,this.trigger("Moved",p)),0!==this._dy&&(this.y=j,p.axis="y",p.oldValue=f,this.trigger("Moved",p))}}),d.polygon=function(a){arguments.length>1&&(a=Array.prototype.slice.call(arguments,0)),this.points=a},d.polygon.prototype={containsPoint:function(a,b){var c,d,e=this.points,f=e.length/2,g=!1;for(c=0,d=f-1;f>c;d=c++)e[2*c+1]>b!=e[2*d+1]>b&&a<(e[2*d]-e[2*c])*(b-e[2*c+1])/(e[2*d+1]-e[2*c+1])+e[2*c]&&(g=!g);return g},shift:function(a,b){for(var c=0,d=this.points,e=d.length;e>c;c+=2)d[c]+=a,d[c+1]+=b},clone:function(){return new d.polygon(this.points.slice(0))},rotate:function(a){for(var b,c,d=0,e=this.points,f=e.length;f>d;d+=2)b=a.o.x+(e[d]-a.o.x)*a.cos+(e[d+1]-a.o.y)*a.sin,c=a.o.y-(e[d]-a.o.x)*a.sin+(e[d+1]-a.o.y)*a.cos,e[d]=b,e[d+1]=c}},d.circle=function(a,b,c){this.x=a,this.y=b,this.radius=c,this.points=[];for(var d,e=0;16>e;e+=2)d=e*Math.PI/8,this.points[e]=this.x+Math.sin(d)*c,this.points[e+1]=this.y+Math.cos(d)*c},d.circle.prototype={containsPoint:function(a,b){var c=this.radius,d=(Math.sqrt,this.x-a),e=this.y-b;return c*c>d*d+e*e},shift:function(a,b){this.x+=a,this.y+=b;for(var c=0,d=this.points,e=d.length;e>c;c+=2)d[c]+=a,d[c+1]+=b},rotate:function(){}},d.matrix=function(a){this.mtx=a,this.width=a[0].length,this.height=a.length},d.matrix.prototype={x:function(a){if(this.width==a.height){for(var b=[],c=0;ca||a>this.mtx.length||1>b||b>this.mtx[0].length?null:this.mtx[a-1][b-1]}}},{"../core/core.js":7,"./spatial-grid.js":43}],40:[function(a,b,c){var d=a("../core/core.js"),e=Math.PI/180;d.c("Collision",{init:function(){this.requires("2D"),this._collisionData={},this.collision()},remove:function(){this._cbr=null,this.unbind("Resize",this._resizeMap),this.unbind("Resize",this._checkBounds)},collision:function(a){if(this.unbind("Resize",this._resizeMap),this.unbind("Resize",this._checkBounds),a){if(arguments.length>1){var b=Array.prototype.slice.call(arguments,0);a=new d.polygon(b)}else a=a.constructor===Array?new d.polygon(a.slice()):a.clone();this._findBounds(a.points)}else a=new d.polygon([0,0,this._w,0,this._w,this._h,0,this._h]),this.bind("Resize",this._resizeMap),this._cbr=null;return this.rotation&&a.rotate({cos:Math.cos(-this.rotation*e),sin:Math.sin(-this.rotation*e),o:{x:this._origin.x,y:this._origin.y}}),this.map=a,this.attach(this.map),this.map.shift(this._x,this._y),this.trigger("NewHitbox",a),this},_findBounds:function(a){for(var b=1/0,c=-(1/0),d=1/0,e=-(1/0),f=a.length,g=0;f>g;g+=2)a[g]c&&(c=a[g]),a[g+1]e&&(e=a[g+1]);var h={cx:(b+c)/2,cy:(d+e)/2,r:Math.sqrt((c-b)*(c-b)+(e-d)*(e-d))/2};return b>=0&&d>=0&&(this._checkBounds=function(){null===this._cbr&&this._w=0&&d>=0&&c<=this._w&&e<=this._h?(this._cbr=null,!1):(this._cbr=h,this._calculateMBR(),!0)},_resizeMap:function(a){var b,c,d=this.rotation*e,f=this.map.points;"w"===a.axis?(d?(b=a.amount*Math.cos(d),c=a.amount*Math.sin(d)):(b=a.amount,c=0),f[2]+=b,f[3]+=c):(d?(c=a.amount*Math.cos(d),b=-a.amount*Math.sin(d)):(b=0,c=a.amount),f[6]+=b,f[7]+=c),f[4]+=b,f[5]+=c},hit:function(a){var b,c,e,f,g=this._cbr||this._mbr||this,h=d.map.search(g,!1),i=0,j=h.length,k={},l=d.rectManager.overlap,m="map"in this&&"containsPoint"in this.map,n=[];if(!j)return!1;for(;j>i;++i)c=h[i],e=c._cbr||c._mbr||c,c&&(b=c[0],!k[b]&&this[0]!==b&&c.__c[a]&&l(e,g)&&(k[b]=c));for(f in k)if(c=k[f],m&&"map"in c){var o=this._SAT(this.map,c.map);o.obj=c,o.type="SAT",o&&n.push(o)}else n.push({obj:c,type:"MBR"});return n.length?n:!1},onHit:function(a,b,c){var d=!1;return this.bind("EnterFrame",function(){var e=this.hit(a);e?(d=!0,b.call(this,e)):d&&("function"==typeof c&&c.call(this),d=!1)}),this},_createCollisionHandler:function(a,b){return function(){var c=this.hit(a);if(b.occurring===!0){if(c!==!1)return;b.occurring=!1,this.trigger("HitOff",a)}else c!==!1&&(b.occurring=!0,this.trigger("HitOn",c))}},checkHits:function(){var a=arguments,b=0;for(1===a.length&&(a=a[0].split(/\s*,\s*/));bl;l++){for(k=l==o-1?0:l+1,q=-(m[2*l+1]-m[2*k+1]),r=m[2*l]-m[2*k],d=Math.sqrt(q*q+r*r),q/=d,r/=d,e=f=1/0,g=h=-(1/0),c=0;o>c;++c)j=m[2*c]*q+m[2*c+1]*r,j>g&&(g=j),e>j&&(e=j);for(c=0;p>c;++c)j=n[2*c]*q+n[2*c+1]*r,j>h&&(h=j),f>j&&(f=j);if(f>e?(i=f-g,q=-q,r=-r):i=e-h,i>=0)return!1;i>s&&(s=i,t=q,u=r)}for(l=0;p>l;l++){for(k=l==p-1?0:l+1,q=-(n[2*l+1]-n[2*k+1]),r=n[2*l]-n[2*k],d=Math.sqrt(q*q+r*r),q/=d,r/=d,e=f=1/0,g=h=-(1/0),c=0;o>c;++c)j=m[2*c]*q+m[2*c+1]*r,j>g&&(g=j),e>j&&(e=j);for(c=0;p>c;++c)j=n[2*c]*q+n[2*c+1]*r,j>h&&(h=j),f>j&&(f=j);if(f>e?(i=f-g,q=-q,r=-r):i=e-h,i>=0)return!1;i>s&&(s=i,t=q,u=r)}return{overlap:s,normal:{x:t,y:u}}}})},{"../core/core.js":7}],41:[function(a,b,c){var d=a("../core/core.js");d.math={abs:function(a){return 0>a?-a:a},amountOf:function(a,b,c){return c>b?(a-b)/(c-b):(a-c)/(b-c)},clamp:function(a,b,c){return a>c?c:b>a?b:a},degToRad:function(a){return a*Math.PI/180},distance:function(a,b,c,e){var f=d.math.squaredDistance(a,b,c,e);return Math.sqrt(parseFloat(f))},lerp:function(a,b,c){return a+(b-a)*c},negate:function(a){return Math.random()=b&&c>=a}},d.math.Vector2D=function(){function a(b,c){if(b instanceof a)this.x=b.x,this.y=b.y;else if(2===arguments.length)this.x=b,this.y=c;else if(arguments.length>0)throw"Unexpected number of arguments for Vector2D()"}return a.prototype.x=0,a.prototype.y=0,a.prototype.add=function(a){return this.x+=a.x,this.y+=a.y,this},a.prototype.angleBetween=function(a){return Math.atan2(this.x*a.y-this.y*a.x,this.x*a.x+this.y*a.y)},a.prototype.angleTo=function(a){return Math.atan2(a.y-this.y,a.x-this.x)},a.prototype.clone=function(){return new a(this)},a.prototype.distance=function(a){return Math.sqrt((a.x-this.x)*(a.x-this.x)+(a.y-this.y)*(a.y-this.y))},a.prototype.distanceSq=function(a){return(a.x-this.x)*(a.x-this.x)+(a.y-this.y)*(a.y-this.y)},a.prototype.divide=function(a){return this.x/=a.x,this.y/=a.y,this},a.prototype.dotProduct=function(a){return this.x*a.x+this.y*a.y},a.prototype.crossProduct=function(a){return this.x*a.y-this.y*a.x},a.prototype.equals=function(b){return b instanceof a&&this.x==b.x&&this.y==b.y},a.prototype.perpendicular=function(b){return b=b||new a,b.setValues(-this.y,this.x)},a.prototype.getNormal=function(b,c){return c=c||new a,c.setValues(b.y-this.y,this.x-b.x).normalize()},a.prototype.isZero=function(){return 0===this.x&&0===this.y},a.prototype.magnitude=function(){return Math.sqrt(this.x*this.x+this.y*this.y)},a.prototype.magnitudeSq=function(){return this.x*this.x+this.y*this.y},a.prototype.multiply=function(a){return this.x*=a.x,this.y*=a.y,this},a.prototype.negate=function(){return this.x=-this.x,this.y=-this.y,this},a.prototype.normalize=function(){var a=Math.sqrt(this.x*this.x+this.y*this.y);return 0===a?(this.x=1,this.y=0):(this.x/=a,this.y/=a),this},a.prototype.scale=function(a,b){return void 0===b&&(b=a),this.x*=a,this.y*=b,this},a.prototype.scaleToMagnitude=function(a){var b=a/this.magnitude();return this.x*=b,this.y*=b,this},a.prototype.setValues=function(b,c){return b instanceof a?(this.x=b.x,this.y=b.y):(this.x=b,this.y=c),this},a.prototype.subtract=function(a){return this.x-=a.x,this.y-=a.y,this},a.prototype.toString=function(){return"Vector2D("+this.x+", "+this.y+")"},a.prototype.translate=function(a,b){return void 0===b&&(b=a),this.x+=a,this.y+=b,this},a.tripleProduct=function(a,b,c,e){e=e||new d.math.Vector2D;var f=a.dotProduct(c),g=b.dotProduct(c);return e.setValues(b.x*f-a.x*g,b.y*f-a.y*g)},a}(),d.math.Matrix2D=function(){return Matrix2D=function(a,b,c,d,e,f){if(a instanceof Matrix2D)this.a=a.a,this.b=a.b,this.c=a.c,this.d=a.d,this.e=a.e,this.f=a.f;else if(6===arguments.length)this.a=a,this.b=b,this.c=c,this.d=d,this.e=e,this.f=f;else if(arguments.length>0)throw"Unexpected number of arguments for Matrix2D()"},Matrix2D.prototype.a=1,Matrix2D.prototype.b=0,Matrix2D.prototype.c=0,Matrix2D.prototype.d=1,Matrix2D.prototype.e=0,Matrix2D.prototype.f=0,Matrix2D.prototype.apply=function(a){var b=a.x;return a.x=b*this.a+a.y*this.c+this.e,a.y=b*this.b+a.y*this.d+this.f,a},Matrix2D.prototype.clone=function(){return new Matrix2D(this)},Matrix2D.prototype.combine=function(a){var b=this.a;return this.a=b*a.a+this.b*a.c,this.b=b*a.b+this.b*a.d,b=this.c,this.c=b*a.a+this.d*a.c,this.d=b*a.b+this.d*a.d,b=this.e,this.e=b*a.a+this.f*a.c+a.e,this.f=b*a.b+this.f*a.d+a.f,this},Matrix2D.prototype.equals=function(a){return a instanceof Matrix2D&&this.a==a.a&&this.b==a.b&&this.c==a.c&&this.d==a.d&&this.e==a.e&&this.f==a.f},Matrix2D.prototype.determinant=function(){return this.a*this.d-this.b*this.c},Matrix2D.prototype.invert=function(){var a=this.determinant();if(0!==a){var b={a:this.a,b:this.b,c:this.c,d:this.d,e:this.e,f:this.f};this.a=b.d/a,this.b=-b.b/a,this.c=-b.c/a,this.d=b.a/a,this.e=(b.c*b.f-b.e*b.d)/a,this.f=(b.e*b.b-b.a*b.f)/a}return this},Matrix2D.prototype.isIdentity=function(){return 1===this.a&&0===this.b&&0===this.c&&1===this.d&&0===this.e&&0===this.f},Matrix2D.prototype.isInvertible=function(){return 0!==this.determinant()},Matrix2D.prototype.preRotate=function(a){var b=Math.cos(a),c=Math.sin(a),d=this.a;return this.a=b*d-c*this.b,this.b=c*d+b*this.b,d=this.c,this.c=b*d-c*this.d,this.d=c*d+b*this.d,this},Matrix2D.prototype.preScale=function(a,b){return void 0===b&&(b=a),this.a*=a,this.b*=b,this.c*=a,this.d*=b,this},Matrix2D.prototype.preTranslate=function(a,b){return"number"==typeof a?(this.e+=a,this.f+=b):(this.e+=a.x,this.f+=a.y),this},Matrix2D.prototype.rotate=function(a){var b=Math.cos(a),c=Math.sin(a),d=this.a;return this.a=b*d-c*this.b,this.b=c*d+b*this.b,d=this.c,this.c=b*d-c*this.d,this.d=c*d+b*this.d,d=this.e,this.e=b*d-c*this.f,this.f=c*d+b*this.f,this},Matrix2D.prototype.scale=function(a,b){return void 0===b&&(b=a),this.a*=a,this.b*=b,this.c*=a,this.d*=b,this.e*=a,this.f*=b,this},Matrix2D.prototype.setValues=function(a,b,c,d,e,f){return a instanceof Matrix2D?(this.a=a.a,this.b=a.b,this.c=a.c,this.d=a.d,this.e=a.e,this.f=a.f):(this.a=a,this.b=b,this.c=c,this.d=d,this.e=e,this.f=f),this},Matrix2D.prototype.toString=function(){return"Matrix2D(["+this.a+", "+this.c+", "+this.e+"] ["+this.b+", "+this.d+", "+this.f+"] [0, 0, 1])"},Matrix2D.prototype.translate=function(a,b){return"number"==typeof a?(this.e+=this.a*a+this.c*b,this.f+=this.b*a+this.d*b):(this.e+=this.a*a.x+this.c*a.y,this.f+=this.b*a.x+this.d*a.y),this},Matrix2D}()},{"../core/core.js":7}],42:[function(a,b,c){var d=a("../core/core.js");d.extend({rectManager:{merge:function(a,b,c){return"undefined"==typeof c&&(c={}),c._h=Math.max(a._y+a._h,b._y+b._h),c._w=Math.max(a._x+a._w,b._x+b._w),c._x=Math.min(a._x,b._x),c._y=Math.min(a._y,b._y),c._w-=c._x,c._h-=c._y,c},overlap:function(a,b){return a._xb._x&&a._yb._y},mergeSet:function(a){for(var b=0;b0&&b--):b++;return a},boundingRect:function(a){if(a&&a.length){var b,c,d=1,e=a.length,f=a[0];for(f=[f._x,f._y,f._x+f._w,f._y+f._h];e>d;)b=a[d],c=[b._x,b._y,b._x+b._w,b._y+b._h],c[0]f[2]&&(f[2]=c[2]),c[3]>f[3]&&(f[3]=c[3]),d++;return c=f,f={_x:c[0],_y:c[1],_w:c[2]-c[0],_h:c[3]-c[1]}}},_pool:function(){var a=[],b=0;return{get:function(c,d,e,f){a.length<=b&&a.push({});var g=a[b++];return g._x=c,g._y=d,g._w=e,g._h=f,g},copy:function(c){a.length<=b&&a.push({});var d=a[b++];return d._x=c._x,d._y=c._y,d._w=c._w,d._h=c._h,d},recycle:function(a){b--}}}()}})},{"../core/core.js":7}],43:[function(a,b,c){function d(a,b,c){this.keys=a,this.map=c,this.obj=b}var e,f=(a("../core/core.js"),function(a){e=a||64,this.map={}}),g=" ",h={};f.prototype={insert:function(a){var b,c,e=f.key(a),g=new d(e,a,this),h=0;for(h=e.x1;h<=e.x2;h++)for(b=e.y1;b<=e.y2;b++)c=h<<16^b,this.map[c]||(this.map[c]=[]),this.map[c].push(a);return g},search:function(a,b){var c,d,e,g,i,j=f.key(a,h),k=[];for(void 0===b&&(b=!0),c=j.x1;c<=j.x2;c++)for(d=j.y1;d<=j.y2;d++)if(i=this.map[c<<16^d])for(e=0;ec;c++)l=k[c],l&&(m=l[0],l=l._mbr||l,!o[m]&&l._xa._x&&l._ya._y&&(o[m]=k[c]));for(l in o)n.push(o[l]);return n}return k},remove:function(a,b){var c,d,e=0;for(1==arguments.length&&(b=a, -a=f.key(b,h)),e=a.x1;e<=a.x2;e++)for(c=a.y1;c<=a.y2;c++)if(d=e<<16^c,this.map[d]){var g,i=this.map[d],j=i.length;for(g=0;j>g;g++)i[g]&&i[g][0]===b[0]&&i.splice(g,1)}},refresh:function(a){var b,c,d,e,g,h=a.keys,i=a.obj;for(c=h.x1;c<=h.x2;c++)for(d=h.y1;d<=h.y2;d++)if(b=this.map[c<<16^d])for(g=b.length,e=0;g>e;e++)b[e]&&b[e][0]===i[0]&&b.splice(e,1);for(f.key(i,h),c=h.x1;c<=h.x2;c++)for(d=h.y1;d<=h.y2;d++)b=this.map[c<<16^d],b||(b=this.map[c<<16^d]=[]),b.push(i);return a},boundaries:function(){var a,b,c={max:{x:-(1/0),y:-(1/0)},min:{x:1/0,y:1/0}},d={max:{x:-(1/0),y:-(1/0)},min:{x:1/0,y:1/0}};for(var e in this.map)if(this.map[e].length){var f=e>>16,g=e<<16>>16;if(0>g&&(f=-1^f),f>=c.max.x){c.max.x=f;for(a in this.map[e])b=this.map[e][a],"object"==typeof b&&"requires"in b&&(d.max.x=Math.max(d.max.x,b.x+b.w))}if(f<=c.min.x){c.min.x=f;for(a in this.map[e])b=this.map[e][a],"object"==typeof b&&"requires"in b&&(d.min.x=Math.min(d.min.x,b.x))}if(g>=c.max.y){c.max.y=g;for(a in this.map[e])b=this.map[e][a],"object"==typeof b&&"requires"in b&&(d.max.y=Math.max(d.max.y,b.y+b.h))}if(g<=c.min.y){c.min.y=g;for(a in this.map[e])b=this.map[e][a],"object"==typeof b&&"requires"in b&&(d.min.y=Math.min(d.min.y,b.y))}}return d}},f.key=function(a,b){return a._mbr&&(a=a._mbr),b||(b={}),b.x1=Math.floor(a._x/e),b.y1=Math.floor(a._y/e),b.x2=Math.floor((a._w+a._x)/e),b.y2=Math.floor((a._h+a._y)/e),b},f.hash=function(a){return a.x1+g+a.y1+g+a.x2+g+a.y2},d.prototype={update:function(a){f.hash(f.key(a,h))!=f.hash(this.keys)&&this.map.refresh(this)}},b.exports=f},{"../core/core.js":7}]},{},[17]); \ No newline at end of file +!function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){var c=b[g][1][a];return e(c?c:a)},k,k.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g1)for(var c=1;c0&&(a.x=a.x/e,a.y=a.y/e)}},updateTriggerInput:function(a){a.active?a.input.isActive()||(a.active=!1,g.trigger("TriggerInputUp",a),a.downFor=0):a.input.isActive()&&(a.downFor=Date.now()-a.input.timeDown,a.active=!0,g.trigger("TriggerInputDown",a))},updateDpadInput:function(a,b){var c,d,e;for(c in a.directions)d=a.directions[c],d.active=!1,d.input.isActive()&&("all"===b?d.active=!0:e?("first"===b&&e.input.timeDown>d.input.timeDown&&(e=d),"last"===b&&e.input.timeDown0?1:-1,e={acceleration:b,rawAcceleration:"["+Math.round(b.x)+", "+Math.round(b.y)+", "+Math.round(b.z)+"]",facingUp:c,tiltLR:Math.round(b.x/9.81*-90),tiltFB:Math.round((b.y+9.81)/9.81*90*c)};d.device._deviceMotionCallback(e)},deviceOrientation:function(a){this._deviceOrientationCallback=a,d.support.deviceorientation&&(window.DeviceOrientationEvent?d.addEvent(this,window,"deviceorientation",this._normalizeDeviceOrientation):window.OrientationEvent&&d.addEvent(this,window,"MozOrientation",this._normalizeDeviceOrientation))},deviceMotion:function(a){this._deviceMotionCallback=a,d.support.devicemotion&&window.DeviceMotionEvent&&d.addEvent(this,window,"devicemotion",this._normalizeDeviceMotion)}}})},{"../core/core.js":8}],5:[function(a,b,c){var d=a("../core/core.js"),e=window.document;d.extend({over:null,mouseObjs:0,mousePos:{},touchObjs:0,lastEvent:null,keydown:{},selected:!1,detectBlur:function(a){var b=a.clientX>d.stage.x&&a.clientXd.stage.y&&a.clientY=0?this.fingers[f]=h:this.fingers.push(h)}},handleMove:function(a){for(var b=a.changedTouches,c=0,d=b.length;c=0){var h=this.fingers[e];"undefined"!=typeof h.entity&&(h.entity===g?h.entity.trigger("TouchMove",b[c]):("object"==typeof g&&g.trigger("TouchStart",b[c]),h.entity.trigger("TouchEnd"))),h.entity=g,h.realX=b[c].realX,h.realY=b[c].realY}}},handleEnd:function(a){for(var b=a.changedTouches,c="touchcancel"===a.type?"TouchCancel":"TouchEnd",d=0,e=b.length;d=0&&(this.fingers[f].entity&&this.fingers[f].entity.trigger(c),this.fingers.splice(f,1))}},setTouch:function(a,b){return{identifier:a.identifier,realX:a.realX,realY:a.realY,entity:b}},findClosestTouchEntity:function(a,b){return d.findPointerEventTargetByComponent("Touch",a,b)},fingerDownIndexById:function(a){for(var b=0,c=this.fingers.length;bm&&f.__c[a]&&f.isAt(j.x,j.y)&&(m=f._globalZ,e=f,k=j)}return k||(k=d.domHelper.translate(n,o)),b.realX=k.x,b.realY=k.y,e},mouseWheelDispatch:function(a){a.direction=a.detail<0||a.wheelDelta>0?1:-1,d.trigger("MouseWheelScroll",a)},keyboardDispatch:function(a){for(var b=a,c={},e="char charCode keyCode type shiftKey ctrlKey metaKey timestamp".split(" "),f=e.length;f;){var g=e[--f];c[g]=b[g]}if(c.which=null!==b.charCode?b.charCode:b.keyCode,c.key=b.keyCode||b.which,c.originalEvent=b,a=c,"keydown"===a.type?d.keydown[a.key]!==!0&&(d.keydown[a.key]=!0,d.trigger("KeyDown",a)):"keyup"===a.type&&(delete d.keydown[a.key],d.trigger("KeyUp",a)),d.selected&&!(8===a.key||a.key>=112&&a.key<=135))return b.stopPropagation?b.stopPropagation():b.cancelBubble=!0,b.target&&"INPUT"!==b.target.nodeName&&"TEXTAREA"!==b.target.nodeName&&(b.preventDefault?b.preventDefault():b.returnValue=!1),!1}}),d._preBind("Load",function(){d.addEvent(this,"keydown",d.keyboardDispatch),d.addEvent(this,"keyup",d.keyboardDispatch),d.addEvent(this,d.stage.elem,"mousedown",d.mouseDispatch),d.addEvent(this,d.stage.elem,"mouseup",d.mouseDispatch),d.addEvent(this,e.body,"mouseup",d.detectBlur),d.addEvent(this,window,"blur",d.resetKeyDown),d.addEvent(this,d.stage.elem,"mousemove",d.mouseDispatch),d.addEvent(this,d.stage.elem,"click",d.mouseDispatch),d.addEvent(this,d.stage.elem,"dblclick",d.mouseDispatch),d.addEvent(this,d.stage.elem,"touchstart",d.touchDispatch),d.addEvent(this,d.stage.elem,"touchmove",d.touchDispatch),d.addEvent(this,d.stage.elem,"touchend",d.touchDispatch),d.addEvent(this,d.stage.elem,"touchcancel",d.touchDispatch),d.addEvent(this,d.stage.elem,"touchleave",d.touchDispatch),"Moz"===d.support.prefix?d.addEvent(this,d.stage.elem,"DOMMouseScroll",d.mouseWheelDispatch):d.addEvent(this,d.stage.elem,"mousewheel",d.mouseWheelDispatch)}),d._preBind("CraftyStop",function(){d.removeEvent(this,"keydown",d.keyboardDispatch),d.removeEvent(this,"keyup",d.keyboardDispatch),d.stage&&(d.removeEvent(this,d.stage.elem,"mousedown",d.mouseDispatch),d.removeEvent(this,d.stage.elem,"mouseup",d.mouseDispatch),d.removeEvent(this,d.stage.elem,"mousemove",d.mouseDispatch),d.removeEvent(this,d.stage.elem,"click",d.mouseDispatch),d.removeEvent(this,d.stage.elem,"dblclick",d.mouseDispatch),d.removeEvent(this,d.stage.elem,"touchstart",d.touchDispatch),d.removeEvent(this,d.stage.elem,"touchmove",d.touchDispatch),d.removeEvent(this,d.stage.elem,"touchend",d.touchDispatch),d.removeEvent(this,d.stage.elem,"touchcancel",d.touchDispatch),d.removeEvent(this,d.stage.elem,"touchleave",d.touchDispatch),"Moz"===d.support.prefix?d.removeEvent(this,d.stage.elem,"DOMMouseScroll",d.mouseWheelDispatch):d.removeEvent(this,d.stage.elem,"mousewheel",d.mouseWheelDispatch)),d.removeEvent(this,e.body,"mouseup",d.detectBlur),d.removeEvent(this,window,"blur",d.resetKeyDown)}),d.c("Mouse",{init:function(){d.mouseObjs++,this.requires("AreaMap").bind("Remove",function(){d.mouseObjs--})}}),d.c("Touch",{init:function(){d.touchObjs++,this.requires("AreaMap").bind("Remove",function(){d.touchObjs--})}}),d.c("AreaMap",{init:function(){this.has("Renderable")&&this._drawLayer&&this._drawLayer._pointerEntities++},remove:function(){this.has("Renderable")&&this._drawLayer&&this._drawLayer._pointerEntities--},events:{LayerAttached:function(a){a._pointerEntities++},LayerDetached:function(a){a._pointerEntities--}},areaMap:function(a){if(arguments.length>1){var b=Array.prototype.slice.call(arguments,0);a=new d.polygon(b)}else a=a.constructor===Array?new d.polygon(a.slice()):a.clone();return a.shift(this._x,this._y),this.mapArea=a,this.attach(this.mapArea),this.trigger("NewAreaMap",a),this}}),d.c("Button",{init:function(){var a=!d.mobile||d.mobile&&!d.multitouch()?"Mouse":"Touch";this.requires(a)}}),d.c("MouseDrag",{_dragging:!1,init:function(){this.requires("Mouse"),this.bind("MouseDown",this._ondown)},remove:function(){this.unbind("MouseDown",this._ondown)},_ondown:function(a){a.mouseButton===d.mouseButtons.LEFT&&this.startDrag(a)},_ondrag:function(a){return!(!this._dragging||0===a.realX||0===a.realY)&&void this.trigger("Dragging",a)},_onup:function(a){a.mouseButton===d.mouseButtons.LEFT&&this.stopDrag(a)},startDrag:function(a){if(!this._dragging)return this._dragging=!0,d.addEvent(this,d.stage.elem,"mousemove",this._ondrag),d.addEvent(this,d.stage.elem,"mouseup",this._onup),this.trigger("StartDrag",a||d.lastEvent),this},stopDrag:function(a){if(this._dragging)return this._dragging=!1,d.removeEvent(this,d.stage.elem,"mousemove",this._ondrag),d.removeEvent(this,d.stage.elem,"mouseup",this._onup),this.trigger("StopDrag",a||d.lastEvent),this}}),d.c("Keyboard",{isDown:function(a){return"string"==typeof a&&(a=d.keys[a]),!!d.keydown[a]}})},{"../core/core.js":8}],6:[function(a,b,c){var d=a("../core/core.js");d.extend({keys:{BACKSPACE:8,TAB:9,ENTER:13,PAUSE:19,CAPS:20,ESC:27,SPACE:32,PAGE_UP:33,PAGE_DOWN:34,END:35,HOME:36,LEFT_ARROW:37,UP_ARROW:38,RIGHT_ARROW:39,DOWN_ARROW:40,INSERT:45,DELETE:46,0:48,1:49,2:50,3:51,4:52,5:53,6:54,7:55,8:56,9:57,A:65,B:66,C:67,D:68,E:69,F:70,G:71,H:72,I:73,J:74,K:75,L:76,M:77,N:78,O:79,P:80,Q:81,R:82,S:83,T:84,U:85,V:86,W:87,X:88,Y:89,Z:90,NUMPAD_0:96,NUMPAD_1:97,NUMPAD_2:98,NUMPAD_3:99,NUMPAD_4:100,NUMPAD_5:101,NUMPAD_6:102,NUMPAD_7:103,NUMPAD_8:104,NUMPAD_9:105,MULTIPLY:106,ADD:107,SUBSTRACT:109,DECIMAL:110,DIVIDE:111,F1:112,F2:113,F3:114,F4:115,F5:116,F6:117,F7:118,F8:119,F9:120,F10:121,F11:122,F12:123,SHIFT:16,CTRL:17,ALT:18,PLUS:187,COMMA:188,MINUS:189,PERIOD:190,PULT_UP:29460,PULT_DOWN:29461,PULT_LEFT:4,PULT_RIGHT:5},mouseButtons:{LEFT:0,MIDDLE:1,RIGHT:2}})},{"../core/core.js":8}],7:[function(a,b,c){var d=a("../core/core.js"),e=function(a,b){this.timePerFrame=1e3/d.timer.FPS(),this.duration=a,"function"==typeof b?this.easing_function=b:"string"==typeof b&&this.standardEasingFunctions[b]?this.easing_function=this.standardEasingFunctions[b]:this.easing_function=this.standardEasingFunctions.linear,this.reset()};e.prototype={duration:0,clock:0,steps:null,complete:!1,paused:!1,reset:function(){this.loops=1,this.clock=0,this.complete=!1,this.paused=!1},repeat:function(a){this.loops=a},setProgress:function(a,b){this.clock=this.duration*a,"undefined"!=typeof b&&(this.loops=b)},pause:function(){this.paused=!0},resume:function(){this.paused=!1,this.complete=!1},tick:function(a){if(!this.paused&&!this.complete)for(this.clock+=a,this.frames=Math.floor(this.clock/this.timePerFrame);this.clock>=this.duration&&this.complete===!1;)this.loops--,this.loops>0?this.clock-=this.duration:this.complete=!0},time:function(){return Math.min(this.clock/this.duration,1)},value:function(){return this.easing_function(this.time())},standardEasingFunctions:{linear:function(a){return a},smoothStep:function(a){return(3-2*a)*a*a},smootherStep:function(a){return(6*a*a-15*a+10)*a*a*a},easeInQuad:function(a){return a*a},easeOutQuad:function(a){return a*(2-a)},easeInOutQuad:function(a){return a<.5?2*a*a:(4-2*a)*a-1}}},b.exports=e},{"../core/core.js":8}],8:[function(a,b,c){function d(){var a=f++;return a in i?d():a}function e(a){if(null===a||"object"!=typeof a)return a;var b=a.constructor();for(var c in a)b[c]=e(a[c]);return b}var f,g,h,i,j,k,l,m,n,o=a("./version"),p=function(a){return new p.fn.init(a)};h={},l=Array.prototype.slice,m=/\s*,\s*/,n=/\s+/;var q=function(){f=1,g=0,i={},j={},k=[]};q(),p.fn=p.prototype={init:function(a){if("string"!=typeof a)return a||(a=0,a in i||(i[a]=this)),a in i?(this[0]=a,this.length=1,this.__c||(this.__c={}),this._callbacks||p._addCallbackMethods(this),i[a]||(i[a]=this),i[a]):(this.length=0,this);var b,c,d,e,f,g,j,k=0,l=!1,o=!1;if("*"===a){g=0;for(b in i)this[g]=+b,g++;return this.length=g,1===g?i[this[0]]:this}a.indexOf(",")!==-1?(o=!0,d=m):a.indexOf(" ")!==-1&&(l=!0,d=n);for(b in i)if(i.hasOwnProperty(b))if(c=i[b],l||o){for(e=a.split(d),g=0,j=e.length,f=0;g0)&&(this[k++]=+b)}else c.__c[a]&&(this[k++]=+b);if(k>0&&!l&&!o&&this.extend(h[a]),e&&l)for(g=0;g1)for(b=arguments.length;d-1?(d=a.split("."),c=d.shift(),e=d.join("."),this._attr_get(d.join("."),b[c])):b[a]},_attr_set:function(){var a,b,c;return"string"==typeof arguments[0]?(a=this._set_create_object(arguments[0],arguments[1]),b=!!arguments[2],c=arguments[3]||arguments[0].indexOf(".")>-1):(a=arguments[0],b=!!arguments[1],c=!!arguments[2]),b||this.trigger("Change",a),c?this._recursive_extend(a,this):this.extend.call(this,a),this},_set_create_object:function(a,b){var c,d,e,f={};return a.indexOf(".")>-1?(c=a.split("."),d=c.shift(),e=c.join("."),f[d]=this._set_create_object(e,b)):f[a]=b,f},_recursive_extend:function(a,b){var c;for(c in a)a[c].constructor===Object?b[c]=this._recursive_extend(a[c],b[c]):b[c]=a[c];return b},toArray:function(){return l.call(this,0)},timeout:function(a,b){return this.each(function(){var c=this;setTimeout(function(){a.call(c)},b)}),this},bind:function(a,b){if(1===this.length)this._bindCallback(a,b);else for(var c=0;c=b||a+b<0)return;return a>=0?i[this[a]]:i[this[a+b]]}for(var c=0,d=[];c0&&p.trigger("MeasureWaitTime",m-h),c+i>=m)return void(h=m);var n=m-(c+i);n>20*k&&(i+=n-k,n=k),"fixed"===d?(l=Math.ceil(n/k),l=Math.min(l,e),b=k):"variable"===d?(l=1,b=n,b=Math.min(b,f)):"semifixed"===d&&(l=Math.ceil(n/f),b=n/l);for(var o=0;o0&&(a=m,p.trigger("PreRender"),p.trigger("RenderScene"),p.trigger("PostRender"),m=(new Date).getTime(),p.trigger("MeasureRenderTime",m-a)),h=m},FPS:function(a){return"undefined"==typeof a?j:(j=a,k=1e3/j,p.trigger("FPSChange",a),void 0)},simulateFrames:function(a,b){for(b=b||k;a-- >0;){var c={frame:g++,dt:b};p.trigger("EnterFrameInput",c),p.trigger("EnterFrame",c),p.trigger("ExitFrame",c)}p.trigger("PreRender"),p.trigger("RenderScene"),p.trigger("PostRender")}}}(),e:function(){var a=d();return i[a]=null,i[a]=p(a),arguments.length>0&&i[a].addComponent.apply(i[a],arguments),i[a].setName("Entity #"+a),i[a].addComponent("obj"),p.trigger("NewEntity",{id:a}),i[a]},c:function(a,b){h[a]=b},trigger:function(a,b){ +var c,d,e=j[a]||(j[a]={});for(c in e)e.hasOwnProperty(c)&&(d=e[c],d&&0!==d.length&&d.context._runCallbacks(a,b))},bind:function(a,b){return this._bindCallback(a,b),b},uniqueBind:function(a,b){return this.unbind(a,b),this.bind(a,b)},one:function(a,b){var c=this,d=function(e){b.call(c,e),c.unbind(a,d)};return c.bind(a,d)},unbind:function(a,b){this._unbindCallbacks(a,b)},frame:function(){return g},components:function(){return h},isComp:function(a){return a in h},debug:function(a){return"handlers"===a?j:i},settings:function(){var a={},b={};return{register:function(a,c){b[a]=c},modify:function(c,d){b[c]&&(b[c].call(a[c],d),a[c]=d)},get:function(b){return a[b]}}}(),defineField:function(a,b,c,d){Object.defineProperty(a,b,{get:c,set:d,configurable:!1,enumerable:!0})},clone:e}),"function"==typeof define&&define("crafty",[],function(){return p}),b.exports=p},{"./version":17}],9:[function(a,b,c){(function(c){var d=a("../core/core.js"),e="undefined"!=typeof window&&window.document;!function(){var a=d.support={},b="undefined"!=typeof navigator&&navigator.userAgent.toLowerCase()||"undefined"!=typeof c&&c.version,f=/(webkit)[ \/]([\w.]+)/.exec(b)||/(o)pera(?:.*version)?[ \/]([\w.]+)/.exec(b)||/(ms)ie ([\w.]+)/.exec(b)||/(moz)illa(?:.*? rv:([\w.]+))?/.exec(b)||/(v)\d+\.(\d+)/.exec(b)||[],g=/iPad|iPod|iPhone|Android|webOS|IEMobile/i.exec(b);if(g&&(d.mobile=g[0]),a.defineProperty=function(){if(!("defineProperty"in Object))return!1;try{Object.defineProperty({},"x",{})}catch(a){return!1}return!0}(),a.audio="undefined"!=typeof window&&"canPlayType"in e.createElement("audio"),a.prefix=f[1]||f[0],"moz"===a.prefix&&(a.prefix="Moz"),"o"===a.prefix&&(a.prefix="O"),"v"===a.prefix&&(a.prefix="node"),f[2]&&(a.versionName=f[2],a.version=+f[2].split(".")[0]),a.canvas="undefined"!=typeof window&&"getContext"in e.createElement("canvas"),a.canvas){var h;try{var i=e.createElement("canvas");h=i.getContext("webgl")||i.getContext("experimental-webgl"),h.viewportWidth=a.canvas.width,h.viewportHeight=a.canvas.height}catch(a){}a.webgl=!!h}else a.webgl=!1;a.css3dtransform="undefined"!=typeof window&&("undefined"!=typeof e.createElement("div").style.Perspective||"undefined"!=typeof e.createElement("div").style[a.prefix+"Perspective"]),a.deviceorientation="undefined"!=typeof window&&("undefined"!=typeof window.DeviceOrientationEvent||"undefined"!=typeof window.OrientationEvent),a.devicemotion="undefined"!=typeof window&&"undefined"!=typeof window.DeviceMotionEvent}(),b.exports={_events:{},addEvent:function(a,b,c,d){3===arguments.length&&(d=c,c=b,b=window.document);var e=function(b){d.call(a,b)},f=a[0]||"";this._events[f+b+c+d]||(this._events[f+b+c+d]=e,b.addEventListener(c,e,!1))},removeEvent:function(a,b,c,d){3===arguments.length&&(d=c,c=b,b=window.document);var e=a[0]||"",f=this._events[e+b+c+d];f&&(b.removeEventListener(c,f,!1),delete this._events[e+b+c+d])},background:function(a){d.stage.elem.style.background=a}}}).call(this,a("_process"))},{"../core/core.js":8,_process:1}],10:[function(a,b,c){var d=a("../core/core.js");b.exports={assets:{},__paths:{audio:"",images:""},paths:function(a){return"undefined"==typeof a?this.__paths:(a.audio&&(this.__paths.audio=a.audio),void(a.images&&(this.__paths.images=a.images)))},asset:function(a,b){return 1===arguments.length?d.assets[a]:d.assets[a]?void 0:(d.assets[a]=b,this.trigger("NewAsset",{key:a,value:b}),b)},image_whitelist:["jpg","jpeg","gif","png","svg"],load:function(a,b,c,e){function f(){var a=this.src;this.removeEventListener&&this.removeEventListener("canplaythrough",f,!1),m++,c&&c({loaded:m,total:n,percent:m/n*100,src:a}),m===n&&b&&b()}function g(){var a=this.src;e&&e({loaded:m,total:n,percent:m/n*100,src:a}),m++,m===n&&b&&b()}if(Array.isArray(a))return void d.log("Calling Crafty.load with an array of assets no longer works; see the docs for more details.");a="string"==typeof a?JSON.parse(a):a;var h,i,j,k,l,m=0,n=(a.audio?Object.keys(a.audio).length:0)+(a.images?Object.keys(a.images).length:0)+(a.sprites?Object.keys(a.sprites).length:0),o=d.paths(),p=function(a){return a.substr(a.lastIndexOf(".")+1).toLowerCase()},q=function(a,b){return b.search("://")===-1?"audio"===a?o.audio+b:o.images+b:b},r=function(a){return d.asset(a)||null},s=function(a){return d.support.audio&&d.audio.supports(p(a))},t=function(a){return d.image_whitelist.indexOf(p(a))!==-1},u=function(a,b){a.onload=f,"webkit"===d.support.prefix&&(a.src=""),a.src=b};for(k in a)for(l in a[k])if(a[k].hasOwnProperty(l)){if(h=a[k][l],j=null,"audio"===k){if("object"==typeof h){var v=[];for(var w in h)i=q(k,h[w]),r(i)||!s(h[w])||d.audio.sounds[l]||v.push(i);v.length>0&&(j=d.audio.add(l,v))}else"string"==typeof h&&(i=q(k,h),r(i)||!s(h)||d.audio.sounds[l]||(j=d.audio.add(l,i)));j&&(j=j.obj),j&&j.addEventListener&&j.addEventListener("canplaythrough",f,!1)}else l="sprites"===k?l:h,i=q(k,l),!r(i)&&t(l)&&(j=new Image,"sprites"===k&&d.sprite(h.tile,h.tileh,i,h.map,h.paddingX,h.paddingY,h.paddingAroundBorder),d.asset(i,j),u(j,i));j?j.onerror=g:g.call({src:i})}0===n&&b&&b()},removeAssets:function(a){a="string"==typeof a?JSON.parse(a):a;var b,c,e,f,g=d.paths(),h=function(a,b){return b.search("://")===-1?"audio"===a?g.audio+b:g.images+b:b};for(e in a)for(f in a[e])if(a[e].hasOwnProperty(f))if(b=a[e][f],"audio"===e)if("object"==typeof b)for(var i in b)c=h(e,b[i]),d.asset(c)&&d.audio.remove(f);else"string"==typeof b&&(c=h(e,b),d.asset(c)&&d.audio.remove(f));else if(f="sprites"===e?f:b,c=h(e,f),d.asset(c)){if("sprites"===e)for(var j in b.map)delete d.components()[j];delete d.assets[c]}}}},{"../core/core.js":8}],11:[function(a,b,c){var d=a("../core/core.js");b.exports={init:function(){this.changed=[],this.bind("Change",this._changed_attributes),this.bind("Change",this._changed_triggers)},_changed_triggers:function(a,b){var c;b=d.extend.call({pre:""},b);for(c in a)this.trigger("Change["+b.pre+c+"]",a[c]),a[c].constructor===Object&&this._changed_triggers(a[c],{pre:b.pre+c+"."})},_changed_attributes:function(a){var b;for(b in a)this.changed.push(b);return this},is_dirty:function(a){return 0===arguments.length?!!this.changed.length:this.changed.indexOf(a)>-1}}},{"../core/core.js":8}],12:[function(a,b,c){var d=a("../core/core.js");b.exports={_scenes:{},_current:null,scene:function(a,b,c){return 1===arguments.length||"function"!=typeof arguments[1]?void d.enterScene(a,arguments[1]):void d.defineScene(a,b,c)},defineScene:function(a,b,c){if("function"!=typeof b)throw"Init function is the wrong type.";this._scenes[a]={},this._scenes[a].initialize=b,"undefined"!=typeof c&&(this._scenes[a].uninitialize=c)},enterScene:function(a,b){if("function"==typeof b)throw"Scene data cannot be a function";d.trigger("SceneDestroy",{newScene:a}),d.viewport.reset(),d("2D").each(function(){this.has("Persist")||this.destroy()}),null!==this._current&&"uninitialize"in this._scenes[this._current]&&this._scenes[this._current].uninitialize.call(this);var c=this._current;this._current=a,d.trigger("SceneChange",{oldScene:c,newScene:a}),this._scenes.hasOwnProperty(a)?this._scenes[a].initialize.call(this,b):d.error('The scene "'+a+'" does not exist')}}},{"../core/core.js":8}],13:[function(a,b,c){var d=a("../core/core.js");try{var e="undefined"!=typeof window&&window.localStorage||new a("node-localstorage").LocalStorage("./localStorage")}catch(a){var e=null}var f=function(a,b){var c=b;if(!e)return d.error("Local storage is not accessible. (Perhaps you are including crafty.js cross-domain?)"),!1;if(1===arguments.length)try{return JSON.parse(e.getItem(a))}catch(b){return e.getItem(a)}else"object"==typeof b&&(c=JSON.stringify(b)),e.setItem(a,c)};f.remove=function(a){return e?void e.removeItem(a):void d.error("Local storage is not accessible. (Perhaps you are including crafty.js cross-domain?)")},b.exports=f},{"../core/core.js":8}],14:[function(a,b,c){function d(a,b){var c={};for(var d in b)c[d]=b[d];for(d in a)d in b||(c[d]=a[d]);return c}var e=a("../core/core.js");e._systems={},e.s=function(a,b,c,d){return b?("boolean"==typeof c&&(d=c,c=null),void(d===!1?(e._systems[a]=new e.CraftySystem(a,b,c),e.trigger("SystemLoaded",a)):e._registerLazySystem(a,b,c))):e._systems[a]},e._registerLazySystem=function(a,b,c){Object.defineProperty(e._systems,a,{get:function(){return Object.defineProperty(e._systems,a,{value:new e.CraftySystem(a,b,c),writable:!0,enumerable:!0,configurable:!0}),e.trigger("SystemLoaded",a),e._systems[a]},configurable:!0})},e.CraftySystem=function(){var a=1;return function(b,c,f){if(this.name=b,!c)return this;if(this._systemTemplate=c,this.extend(c),this.options=d(this.options,f),e._addCallbackMethods(this),this[0]="system"+a++,"function"==typeof this.init&&this.init(b),"events"in c){var g=c.events;for(var h in g){var i="function"==typeof g[h]?g[h]:c[g[h]];this.bind(h,i)}}}}(),e.CraftySystem.prototype={extend:function(a){for(var b in a)"undefined"==typeof this[b]&&(this[b]=a[b])},bind:function(a,b){return this._bindCallback(a,b),this},trigger:function(a,b){return this._runCallbacks(a,b),this},unbind:function(a,b){return this._unbindCallbacks(a,b),this},one:function(a,b){var c=this,d=function(e){b.call(c,e),c.unbind(a,d)};return c.bind(a,d)},uniqueBind:function(a,b){return this.unbind(a,b),this.bind(a,b)},destroy:function(){e.trigger("SystemDestroyed",this),"function"==typeof this.remove&&this.remove(),this._unbindAll(),delete e._systems[this.name]}}},{"../core/core.js":8}],15:[function(a,b,c){b.exports={init:function(){this._delays=[],this._delaysPaused=!1,this.bind("EnterFrame",function(a){if(!this._delaysPaused)for(var b=this._delays.length;--b>=0;){var c=this._delays[b];if(c===!1)this._delays.splice(b,1);else{for(c.accumulator+=a.dt;c.accumulator>=c.delay&&c.repeat>=0;)c.accumulator-=c.delay,c.repeat--,c.callback.call(this);c.repeat<0&&(this._delays.splice(b,1),"function"==typeof c.callbackOff&&c.callbackOff.call(this))}}})},delay:function(a,b,c,d){return this._delays.push({accumulator:0,callback:a,callbackOff:d,delay:b,repeat:(c<0?1/0:c)||0}),this},cancelDelay:function(a){for(var b=this._delays.length;--b>=0;){var c=this._delays[b];c&&c.callback===a&&(this._delays[b]=!1)}return this},pauseDelays:function(){this._delaysPaused=!0},resumeDelays:function(){this._delaysPaused=!1}}},{}],16:[function(a,b,c){var d=a("../core/core.js");b.exports={init:function(){this.tweenGroup={},this.tweenStart={},this.tweens=[],this.uniqueBind("EnterFrame",this._tweenTick)},_tweenTick:function(a){var b,c,d;for(d=this.tweens.length-1;d>=0;d--)b=this.tweens[d],b.easing.tick(a.dt),c=b.easing.value(),this._doTween(b.props,c),b.easing.complete&&(this.tweens.splice(d,1),this._endTween(b.props))},_doTween:function(a,b){for(var c in a)this[c]=(1-b)*this.tweenStart[c]+b*a[c]},tween:function(a,b,c){var e={props:a,easing:new d.easing(b,c)};for(var f in a)"undefined"!=typeof this.tweenGroup[f]&&this.cancelTween(f),this.tweenStart[f]=this[f],this.tweenGroup[f]=a;return this.tweens.push(e),this},cancelTween:function(a){if("string"==typeof a)"object"==typeof this.tweenGroup[a]&&delete this.tweenGroup[a][a];else if("object"==typeof a)for(var b in a)this.cancelTween(b);return this},pauseTweens:function(){this.tweens.map(function(a){a.easing.pause()})},resumeTweens:function(){this.tweens.map(function(a){a.easing.resume()})},_endTween:function(a){for(var b in a)delete this.tweenGroup[b];this.trigger("TweenEnd",a)}}},{"../core/core.js":8}],17:[function(a,b,c){b.exports="0.8.0-rc1"},{}],18:[function(a,b,c){var d=a("./core/core");d.easing=a("./core/animation"),d.extend(a("./core/extensions")),d.extend(a("./core/loader")),d.c("Model",a("./core/model")),d.extend(a("./core/scenes")),d.storage=a("./core/storage"),d.c("Delay",a("./core/time")),d.c("Tween",a("./core/tween")),a("./core/systems"),a("./spatial/2d"),a("./spatial/motion"),a("./spatial/platform"),a("./spatial/collision"),a("./spatial/spatial-grid"),a("./spatial/rect-manager"),a("./spatial/math"),a("./graphics/layers"),a("./graphics/canvas"),a("./graphics/canvas-layer"),a("./graphics/webgl"),a("./graphics/webgl-layer"),a("./graphics/color"),a("./graphics/dom"),a("./graphics/dom-helper"),a("./graphics/dom-layer"),a("./graphics/drawing"),a("./graphics/gl-textures"),a("./graphics/renderable"),a("./graphics/html"),a("./graphics/image"),a("./graphics/particles"),a("./graphics/sprite-animation"),a("./graphics/sprite"),a("./graphics/text"),a("./graphics/viewport"),a("./isometric/diamond-iso"),a("./isometric/isometric"),a("./controls/inputs"),a("./controls/controls-system"),a("./controls/controls"),a("./controls/device"),a("./controls/keycodes"),a("./sound/sound"),a("./debug/debug-layer"),a("./debug/logging"),window&&(window.Crafty=d),b.exports=d},{"./controls/controls":3,"./controls/controls-system":2,"./controls/device":4,"./controls/inputs":5,"./controls/keycodes":6,"./core/animation":7,"./core/core":8,"./core/extensions":9,"./core/loader":10,"./core/model":11,"./core/scenes":12,"./core/storage":13,"./core/systems":14,"./core/time":15,"./core/tween":16,"./debug/debug-layer":19,"./debug/logging":20,"./graphics/canvas":22,"./graphics/canvas-layer":21,"./graphics/color":23,"./graphics/dom":26,"./graphics/dom-helper":24,"./graphics/dom-layer":25,"./graphics/drawing":27,"./graphics/gl-textures":28,"./graphics/html":29,"./graphics/image":30,"./graphics/layers":31,"./graphics/particles":32,"./graphics/renderable":33,"./graphics/sprite":35,"./graphics/sprite-animation":34,"./graphics/text":36,"./graphics/viewport":37,"./graphics/webgl":39,"./graphics/webgl-layer":38,"./isometric/diamond-iso":40,"./isometric/isometric":41,"./sound/sound":42,"./spatial/2d":43,"./spatial/collision":44,"./spatial/math":45,"./spatial/motion":46,"./spatial/platform":47,"./spatial/rect-manager":48,"./spatial/spatial-grid":49}],19:[function(a,b,c){var d=a("../core/core.js"),e=window.document;d.c("DebugCanvas",{init:function(){this.requires("2D"),d.DebugCanvas.context||d.DebugCanvas.init(),d.DebugCanvas.add(this),this._debug={alpha:1,lineWidth:1},this.bind("RemoveComponent",this.onDebugRemove),this.bind("Remove",this.onDebugDestroy)},onDebugRemove:function(a){"DebugCanvas"===a&&d.DebugCanvas.remove(this)},onDebugDestroy:function(a){d.DebugCanvas.remove(this)},debugAlpha:function(a){return this._debug.alpha=a,this},debugFill:function(a){return"undefined"==typeof a&&(a="red"),this._debug.fillStyle=a,this},debugStroke:function(a){return"undefined"==typeof a&&(a="red"),this._debug.strokeStyle=a,this},debugDraw:function(a){var b=a.globalAlpha,c=this._debug;c.alpha&&(a.globalAlpha=this._debug.alpha),c.strokeStyle&&(a.strokeStyle=c.strokeStyle),c.lineWidth&&(a.lineWidth=c.lineWidth),c.fillStyle&&(a.fillStyle=c.fillStyle),this.trigger("DebugDraw",a),a.globalAlpha=b}}),d.c("DebugRectangle",{init:function(){this.requires("2D, DebugCanvas")},debugRectangle:function(a){return this.debugRect=a,this.unbind("DebugDraw",this.drawDebugRect),this.bind("DebugDraw",this.drawDebugRect),this},drawDebugRect:function(a){var b=this.debugRect;null!==b&&void 0!==b&&b._h&&b._w&&(this._debug.fillStyle&&a.fillRect(b._x,b._y,b._w,b._h),this._debug.strokeStyle&&a.strokeRect(b._x,b._y,b._w,b._h))}}),d.c("VisibleMBR",{init:function(){this.requires("DebugRectangle").debugFill("purple").bind("EnterFrame",this._assignRect)},_assignRect:function(){this._mbr?this.debugRectangle(this._mbr):this.debugRectangle(this)}}),d.c("DebugPolygon",{init:function(){this.requires("2D, DebugCanvas")},debugPolygon:function(a){return this.polygon=a,this.unbind("DebugDraw",this.drawDebugPolygon),this.bind("DebugDraw",this.drawDebugPolygon),this},drawDebugPolygon:function(a){if("undefined"!=typeof this.polygon){a.beginPath();for(var b=this.polygon.points,c=b.length,d=0;d=0;c--)b[c]===a&&b.splice(c,1)},init:function(){if(!d.DebugCanvas.context){if(!d.support.canvas)return d.trigger("NoCanvas"),void d.stop();var a;a=e.createElement("canvas"),a.width=d.viewport.width,a.height=d.viewport.height,a.style.position="absolute",a.style.left="0px",a.style.top="0px",a.id="debug-canvas",a.style.zIndex=1e5,d.stage.elem.appendChild(a),d.DebugCanvas.context=a.getContext("2d"),d.DebugCanvas._canvas=a}d.unbind("RenderScene",d.DebugCanvas.renderScene),d.bind("RenderScene",d.DebugCanvas.renderScene)},renderScene:function(a){a=a||d.viewport.rect();var b,c=d.DebugCanvas.entities,e=0,f=c.length,g=d.DebugCanvas.context,h=d.viewport;g.setTransform(h._scale,0,0,h._scale,Math.round(h._x*h._scale),Math.round(h._y*h._scale)),g.clearRect(a._x,a._y,a._w,a._h);for(var i=null;e.6||a?this._drawAll():this._drawDirty(),this._clean()}},_drawDirty:function(a){a=a||this._viewportRect();var b,c,e,f,g,h,i=this._changedObjs,j=i.length,k=this._dirtyRects,l=d.rectManager,m=l.overlap,n=this.context,o=[],p=[];for(a=l.integerBounds(a),b=0;b=3?(this._red=arguments[0],this._green=arguments[1],this._blue=arguments[2],"number"==typeof arguments[3]&&(this._strength=arguments[3])):(d.assignColor(a,this),"number"==typeof arguments[1]&&(this._strength=arguments[1])),this._color="rgba("+this._red+", "+this._green+", "+this._blue+", "+this._strength+")",this.trigger("Invalidate"),this)}})},{"../core/core.js":8}],24:[function(a,b,c){var d=a("../core/core.js"),e=window.document;d.extend({domHelper:{innerPosition:function(a){var b=a.getBoundingClientRect(),c=b.left+(window.pageXOffset?window.pageXOffset:e.body.scrollLeft),d=b.top+(window.pageYOffset?window.pageYOffset:e.body.scrollTop),f=parseInt(this.getStyle(a,"border-left-width")||0,10)||parseInt(this.getStyle(a,"borderLeftWidth")||0,10)||0,g=parseInt(this.getStyle(a,"border-top-width")||0,10)||parseInt(this.getStyle(a,"borderTopWidth")||0,10)||0;return c+=f,d+=g,{x:c,y:d}},getStyle:function(a,b){var c;return a.currentStyle?c=a.currentStyle[this.camelize(b)]:window.getComputedStyle&&(c=e.defaultView.getComputedStyle(a,null).getPropertyValue(this.csselize(b))),c},camelize:function(a){return a.replace(/-+(.)?/g,function(a,b){return b?b.toUpperCase():""})},csselize:function(a){return a.replace(/[A-Z]/g,function(a){return a?"-"+a.toLowerCase():""})},translate:function(a,b,c){var f,g=e.documentElement,h=e.body;return c?(f=c._viewportRect(),{x:(a-d.stage.x+(g&&g.scrollLeft||h&&h.scrollLeft||0))/f._scale+f._x,y:(b-d.stage.y+(g&&g.scrollTop||h&&h.scrollTop||0))/f._scale+f._y}):(f=d.viewport,{x:(a-d.stage.x+(g&&g.scrollLeft||h&&h.scrollLeft||0))/f._scale-f._x,y:(b-d.stage.y+(g&&g.scrollTop||h&&h.scrollTop||0))/f._scale-f._y})}}})},{"../core/core.js":8}],25:[function(a,b,c){var d=a("../core/core.js"),e=window.document;d._registerLayerTemplate("DOM",{type:"DOM",options:{xResponse:1,yResponse:1,scaleResponse:1,z:0},_changedObjs:[],_dirtyViewport:!1,_div:null,init:function(){this._changedObjs=[];var a=this._div=e.createElement("div");d.stage.elem.appendChild(a),a.style.position="absolute",a.style.zIndex=this.options.z,a.style.transformStyle="preserve-3d",this.uniqueBind("RenderScene",this._render),this.uniqueBind("PixelartSet",this._setPixelArt),this.uniqueBind("InvalidateViewport",function(){this._dirtyViewport=!0}),d._addDrawLayerInstance(this)},remove:function(){this._div.parentNode.removeChild(this._div),d._removeDrawLayerInstance(this)},_setPixelArt:function(a){var b=this._div.style,c=d.domHelper.camelize;a?(b[c("image-rendering")]="optimizeSpeed",b[c("image-rendering")]="-moz-crisp-edges",b[c("image-rendering")]="-o-crisp-edges",b[c("image-rendering")]="-webkit-optimize-contrast",b[c("-ms-interpolation-mode")]="nearest-neighbor",b[c("image-rendering")]="optimize-contrast",b[c("image-rendering")]="pixelated",b[c("image-rendering")]="crisp-edges"):(b[c("image-rendering")]="optimizeQuality",b[c("-ms-interpolation-mode")]="bicubic",b[c("image-rendering")]="auto")},debug:function(){d.log(this._changedObjs)},_render:function(){var a=this._changedObjs;if(this._dirtyViewport&&(this._setViewport(),this._dirtyViewport=!1),a.length){for(var b=0,c=a.length;b=0&&this._drawLayers.splice(b,1),this._drawLayers.sort(function(a,b){return a.options.z-b.options.z})},_registerLayerTemplate:function(a,b){this._drawLayerTemplates[a]=b;var c=this._commonLayerProperties;for(var d in c)b[d]||(b[d]=c[d]);b._viewportRectHolder={}},_commonLayerProperties:{_viewportRect:function(){var a=this.options,b=this._viewportRectHolder,c=Math.pow(d.viewport._scale,a.scaleResponse),e=d.viewport;return b._scale=c,b._w=e._width/c,b._h=e._height/c,b._x=a.xResponse*-e._x-.5*(a.xResponse-1)*(1-1/c)*e._width,b._y=a.yResponse*-e._y-.5*(a.yResponse-1)*(1-1/c)*e._height,b},_pointerEntities:0},createLayer:function(a,b,c){var e=this._drawLayerTemplates[b];d.s(a,e,c),d.c(a,{init:function(){this.requires("Renderable"),this._customLayer=!0,this.requires(e.type),this._attachToLayer(d.s(a))},remove:function(){this._detachFromLayer()}})}})},{"../core/core.js":8}],32:[function(a,b,c){var d=a("../core/core.js"),e=window.document;d.c("Particles",{init:function(){this._Particles=d.clone(this._Particles),this._Particles.parentEntity=this,this._particlesPaused=!1},particles:function(a){if(!d.support.canvas||d.deactivateParticles)return this;var b,c,f,g,h;b=e.createElement("canvas"),b.width=d.viewport.width,b.height=d.viewport.height,b.style.position="absolute",b.style.left="0px",b.style.top="0px",d.stage.elem.appendChild(b),c=b.getContext("2d"),this._Particles.init(a),this.bind("Remove",function(){d.stage.elem.removeChild(b)}).bind("RemoveComponent",function(a){"particles"===a&&d.stage.elem.removeChild(b)}),f=this.x+d.viewport.x,g=this.y+d.viewport.y,this._Particles.position=this._Particles.vectorHelpers.create(f,g);var i={x:d.viewport.x,y:d.viewport.y};return this.bind("EnterFrame",function(){this._particlesPaused||(f=this.x+d.viewport.x,g=this.y+d.viewport.y,this._Particles.viewportDelta={x:d.viewport.x-i.x,y:d.viewport.y-i.y},i={x:d.viewport.x,y:d.viewport.y},this._Particles.position=this._Particles.vectorHelpers.create(f,g),"function"==typeof d.rectManager.boundingRect?(h=d.rectManager.boundingRect(this._Particles.register),h&&c.clearRect(h._x,h._y,h._w,h._h)):c.clearRect(0,0,d.viewport.width,d.viewport.height),this._Particles.update(),this._Particles.render(c))}),this},_Particles:{presets:{maxParticles:150,size:18,sizeRandom:4,speed:1,speedRandom:1.2,lifeSpan:29,lifeSpanRandom:7,angle:65,angleRandom:34,startColour:[255,131,0,1],startColourRandom:[48,50,45,0],endColour:[245,35,0,0],endColourRandom:[60,60,60,0],sharpness:20,sharpnessRandom:10,spread:10,duration:-1,fastMode:!1,gravity:{x:0,y:.1},jitter:0,originOffset:{x:0,y:0},particles:[],active:!0,particleCount:0,elapsedFrames:0,emissionRate:0,emitCounter:0,particleIndex:0},init:function(a){this.position=this.vectorHelpers.create(0,0),"undefined"==typeof a&&(a={});for(var b in this.presets)"undefined"!=typeof a[b]?this[b]=a[b]:this[b]=this.presets[b];this.emissionRate=this.maxParticles/this.lifeSpan,this.positionRandom=this.vectorHelpers.create(this.spread,this.spread)},addParticle:function(){if(this.particleCount===this.maxParticles)return!1;var a=new this.particle(this.vectorHelpers);return this.initParticle(a),this.particles[this.particleCount]=a,this.particleCount++,!0},RANDM1TO1:function(){return 2*Math.random()-1},initParticle:function(a){a.position.x=d.viewport._scale*(this.position.x+this.originOffset.x+this.positionRandom.x*this.RANDM1TO1()),a.position.y=d.viewport._scale*(this.position.y+this.originOffset.y+this.positionRandom.y*this.RANDM1TO1());var b=(this.angle+this.angleRandom*this.RANDM1TO1())*(Math.PI/180),c=this.vectorHelpers.create(Math.sin(b),-Math.cos(b)),e=this.speed+this.speedRandom*this.RANDM1TO1();a.direction=this.vectorHelpers.multiply(c,e),a.size=d.viewport._scale*(this.size+this.sizeRandom*this.RANDM1TO1()),a.size=a.size<0?0:~~a.size,a.timeToLive=this.lifeSpan+this.lifeSpanRandom*this.RANDM1TO1(),a.sharpness=this.sharpness+this.sharpnessRandom*this.RANDM1TO1(),a.sharpness=a.sharpness>100?100:a.sharpness<0?0:a.sharpness,a.sizeSmall=~~(a.size/200*a.sharpness);var f=[this.startColour[0]+this.startColourRandom[0]*this.RANDM1TO1(),this.startColour[1]+this.startColourRandom[1]*this.RANDM1TO1(),this.startColour[2]+this.startColourRandom[2]*this.RANDM1TO1(),this.startColour[3]+this.startColourRandom[3]*this.RANDM1TO1()],g=[this.endColour[0]+this.endColourRandom[0]*this.RANDM1TO1(),this.endColour[1]+this.endColourRandom[1]*this.RANDM1TO1(),this.endColour[2]+this.endColourRandom[2]*this.RANDM1TO1(),this.endColour[3]+this.endColourRandom[3]*this.RANDM1TO1()];a.colour=f,a.deltaColour[0]=(g[0]-f[0])/a.timeToLive,a.deltaColour[1]=(g[1]-f[1])/a.timeToLive,a.deltaColour[2]=(g[2]-f[2])/a.timeToLive,a.deltaColour[3]=(g[3]-f[3])/a.timeToLive},update:function(){if(this.active&&this.emissionRate>0){var a=1/this.emissionRate;for(this.emitCounter++;this.particleCounta;)this.addParticle(),this.emitCounter-=a;this.elapsedFrames++,this.duration!==-1&&this.duration0){c.direction=this.vectorHelpers.add(c.direction,this.gravity),c.position=this.vectorHelpers.add(c.position,c.direction),c.position=this.vectorHelpers.add(c.position,this.viewportDelta),this.jitter&&(c.position.x+=this.jitter*this.RANDM1TO1(),c.position.y+=this.jitter*this.RANDM1TO1()),c.timeToLive--;var d=c.colour[0]+=c.deltaColour[0],e=c.colour[1]+=c.deltaColour[1],f=c.colour[2]+=c.deltaColour[2],g=c.colour[3]+=c.deltaColour[3];b=[],b.push("rgba("+(d>255?255:d<0?0:~~d)),b.push(e>255?255:e<0?0:~~e),b.push(f>255?255:f<0?0:~~f),b.push((g>1?1:g<0?0:g.toFixed(2))+")"),c.drawColour=b.join(","),this.fastMode||(b[3]="0)",c.drawColourEnd=b.join(",")),this.particleIndex++}else this.particleIndex!==this.particleCount-1&&(this.particles[this.particleIndex]=this.particles[this.particleCount-1]),this.particleCount--;var h={};h._x=~~c.position.x,h._y=~~c.position.y,h._w=c.size,h._h=c.size,this.register.push(h)}},stop:function(){this.active=!1,this.elapsedFrames=0,this.emitCounter=0,this.parentEntity.trigger("ParticleEnd")},render:function(a){for(var b=0,c=this.particleCount;b>1;if(!(e.position.x+f<0||e.position.y+f<0||e.position.x-f>d.viewport.width||e.position.y-f>d.viewport.height)){var h=~~e.position.x,i=~~e.position.y;if(this.fastMode)a.fillStyle=e.drawColour;else{var j=a.createRadialGradient(h+g,i+g,e.sizeSmall,h+g,i+g,g);j.addColorStop(0,e.drawColour),j.addColorStop(.9,e.drawColourEnd),a.fillStyle=j}a.fillRect(h,i,f,f)}}},particle:function(a){this.position=a.create(0,0),this.direction=a.create(0,0),this.size=0,this.sizeSmall=0,this.timeToLive=0,this.colour=[],this.drawColour="",this.deltaColour=[],this.sharpness=0},vectorHelpers:{create:function(a,b){return{x:a,y:b}},multiply:function(a,b){return a.x*=b,a.y*=b,a},add:function(a,b){return a.x+=b.x,a.y+=b.y,a}}},pauseParticles:function(){this._particlesPaused=!0},resumeParticles:function(){this._particlesPaused=!1}})},{"../core/core.js":8}],33:[function(a,b,c){var d=a("../core/core.js");d.c("Renderable",{_changed:!1,_alpha:1,_visible:!0,_setterRenderable:function(a,b){this[a]!==b&&(this[a]=b,this.trigger("Invalidate"))},_graphics_property_definitions:{alpha:{set:function(a){this._setterRenderable("_alpha",a)},get:function(){return this._alpha},configurable:!0,enumerable:!0},_alpha:{enumerable:!1},visible:{set:function(a){this._setterRenderable("_visible",a)},get:function(){return this._visible},configurable:!0,enumerable:!0},_visible:{enumerable:!1}},_defineRenderableProperites:function(){for(var a in this._graphics_property_definitions)Object.defineProperty(this,a,this._graphics_property_definitions[a])},init:function(){this._defineRenderableProperites()},_invalidateRenderable:function(){this._changed===!1&&(this._changed=!0,this._drawLayer.dirty(this))},_attachToLayer:function(a){this._drawLayer&&this._detachFromLayer(),this._drawLayer=a,a.attach(this),this.bind("Invalidate",this._invalidateRenderable),this.trigger("LayerAttached",a),this.trigger("Invalidate")},_detachFromLayer:function(){this._drawLayer&&(this._drawLayer.detach(this),this.unbind("Invalidate",this._invalidateRenderable),this.trigger("LayerDetached",this._drawLayer),delete this._drawLayer)},flip:function(a){return a=a||"X",this["_flip"+a]||(this["_flip"+a]=!0,this.trigger("Invalidate")),this},unflip:function(a){return a=a||"X",this["_flip"+a]&&(this["_flip"+a]=!1,this.trigger("Invalidate")),this}})},{"../core/core.js":8}],34:[function(a,b,c){var d=a("../core/core.js");d.c("SpriteAnimation",{_reels:null,_currentReelId:null,_currentReel:null,_isPlaying:!1,animationSpeed:1,init:function(){this._reels={}},reel:function(a,b,c,e,f,g){if(0===arguments.length)return this._currentReelId;if(1===arguments.length&&"string"==typeof a){if("undefined"==typeof this._reels[a])throw"The specified reel "+a+" is undefined.";return this.pauseAnimation(),this._currentReelId!==a&&(this._currentReelId=a,this._currentReel=this._reels[a],this._updateSprite(),this.trigger("ReelChange",this._currentReel)),this}var h,i;if(h={id:a,frames:[],currentFrame:0,easing:new d.easing(b),defaultLoops:1},h.duration=h.easing.duration,"number"==typeof c)if(g=g||1/0,f>=0)for(i=0;i=g&&(c=0,e++);else for(i=0;i>f;--i)h.frames.push([c,e]),--c<0&&(c=g-1,e--);else{if(3!==arguments.length||"object"!=typeof c)throw"Unrecognized arguments. Please see the documentation for 'reel(...)'.";h.frames=c}return this._reels[a]=h,this},animate:function(a,b){"string"==typeof a&&this.reel(a);var c=this._currentReel;if("undefined"==typeof c||null===c)throw"No reel is specified, and there is no currently active reel.";return this.pauseAnimation(),"undefined"==typeof b&&(b="number"==typeof a?a:1),c.easing.reset(),this.loops(b),this._setFrame(0),this.bind("EnterFrame",this._animationTick),this._isPlaying=!0,this.trigger("StartAnimation",c),this},resumeAnimation:function(){return this._isPlaying===!1&&null!==this._currentReel&&(this.bind("EnterFrame",this._animationTick),this._isPlaying=!0,this._currentReel.easing.resume(),this.trigger("StartAnimation",this._currentReel)),this},pauseAnimation:function(){return this._isPlaying===!0&&(this.unbind("EnterFrame",this._animationTick),this._isPlaying=!1,this._reels[this._currentReelId].easing.pause()),this},resetAnimation:function(){var a=this._currentReel;if(null===a)throw"No active reel to reset.";return this.reelPosition(0),a.easing.repeat(a.defaultLoops),this},loops:function(a){return 0===arguments.length?null!==this._currentReel?this._currentReel.easing.loops:0:(null!==this._currentReel&&(a<0&&(a=1/0),this._currentReel.easing.repeat(a),this._currentReel.defaultLoops=a),this)},reelPosition:function(a){if(null===this._currentReel)throw"No active reel.";if(0===arguments.length)return this._currentReel.currentFrame;var b,c=this._currentReel.frames.length;if("end"===a&&(a=c-1),a<1&&a>0)b=a,a=Math.floor(c*b);else{if(a!==Math.floor(a))throw"Position "+a+" is invalid.";a<0&&(a=c-1+a),b=a/c}return a=Math.min(a,c-1),a=Math.max(a,0),this._setProgress(b),this._setFrame(a),this},_animationTick:function(a){var b=this._reels[this._currentReelId];b.easing.tick(a.dt*this.animationSpeed);var c=b.easing.value(),d=Math.min(Math.floor(b.frames.length*c),b.frames.length-1);this._setFrame(d),b.easing.complete===!0&&(this.pauseAnimation(),this.trigger("AnimationEnd",this._currentReel))},_setFrame:function(a){var b=this._currentReel;a!==b.currentFrame&&(b.currentFrame=a,this._updateSprite(),this.trigger("FrameChange",b))},_updateSprite:function(){var a=this._currentReel,b=a.frames[a.currentFrame];this.sprite(b[0],b[1])},_setProgress:function(a,b){this._currentReel.easing.setProgress(a,b)},isPlaying:function(a){return!!this._isPlaying&&(a?this._currentReelId===a:!!this._currentReelId)},getReel:function(a){if(0===arguments.length){if(!this._currentReelId)return null;a=this._currentReelId}return this._reels[a]}})},{"../core/core.js":8}],35:[function(a,b,c){var d=a("../core/core.js");d.defaultShader("Sprite",new d.WebGLShader("attribute vec2 aPosition;\nattribute vec3 aOrientation;\nattribute vec2 aLayer;\nattribute vec2 aTextureCoord;\n\nvarying mediump vec3 vTextureCoord;\n\nuniform vec4 uViewport;\nuniform mediump vec2 uTextureDimensions;\n\nmat4 viewportScale = mat4(2.0 / uViewport.z, 0, 0, 0, 0, -2.0 / uViewport.w, 0,0, 0, 0,1,0, -1,+1,0,1);\nvec4 viewportTranslation = vec4(uViewport.xy, 0, 0);\n\nvoid main() {\n vec2 pos = aPosition;\n vec2 entityOrigin = aOrientation.xy;\n mat2 entityRotationMatrix = mat2(cos(aOrientation.z), sin(aOrientation.z), -sin(aOrientation.z), cos(aOrientation.z));\n \n pos = entityRotationMatrix * (pos - entityOrigin) + entityOrigin ;\n gl_Position = viewportScale * (viewportTranslation + vec4(pos, 1.0/(1.0+exp(aLayer.x) ), 1) );\n vTextureCoord = vec3(aTextureCoord, aLayer.y);\n}","varying mediump vec3 vTextureCoord;\n \nuniform sampler2D uSampler;\nuniform mediump vec2 uTextureDimensions;\n\nvoid main(void) {\n highp vec2 coord = vTextureCoord.xy / uTextureDimensions;\n mediump vec4 base_color = texture2D(uSampler, coord);\n gl_FragColor = vec4(base_color.rgb*base_color.a*vTextureCoord.z, base_color.a*vTextureCoord.z);\n}",[{name:"aPosition",width:2},{name:"aOrientation",width:3},{name:"aLayer",width:2},{name:"aTextureCoord",width:2}],function(a,b){var c=a.co;a.program.writeVector("aTextureCoord",c.x,c.y,c.x,c.y+c.h,c.x+c.w,c.y,c.x+c.w,c.y+c.h)})),d.extend({sprite:function(a,b,c,e,f,g,h){var i,j,k;"string"==typeof a&&(g=f,f=e,e=b,c=a,a=1,b=1),"string"==typeof b&&(g=f,f=e,e=c,c=b,b=a),!g&&f&&(g=f),f=parseInt(f||0,10),g=parseInt(g||0,10);var l=function(){this.ready=!0,this.trigger("Invalidate")};k=d.asset(c),k||(k=new Image,k.src=c,d.asset(c,k),k.onload=function(){for(var a in e)d(a).each(l)});var m=function(){this.requires("2D, Sprite"),this.__trim=[0,0,0,0],this.__image=c,this.__map=e,this.__coord=[this.__coord[0],this.__coord[1],this.__coord[2],this.__coord[3]],this.__tile=a,this.__tileh=b,this.__padding=[f,g],this.__padBorder=h,this.sprite(this.__coord[0],this.__coord[1],this.__coord[2],this.__coord[3]),this.img=k,this.img.complete&&this.img.width>0&&(this.ready=!0,this.trigger("Invalidate")),this.w=this.__coord[2],this.h=this.__coord[3],this._setupSpriteImage(this._drawLayer)};for(i in e)e.hasOwnProperty(i)&&(j=e[i],d.c(i,{ready:!1,__coord:[j[0],j[1],j[2]||1,j[3]||1],init:m}));return this}}),d.c("Sprite",{__image:"",__tile:0,__tileh:0,__padding:null,__trim:null,img:null,ready:!1,init:function(){this.__trim=[0,0,0,0],this.bind("Draw",this._drawSprite),this.bind("LayerAttached",this._setupSpriteImage)},remove:function(){this.unbind("Draw",this._drawSprite),this.unbind("LayerAttached",this._setupSpriteImage)},_setupSpriteImage:function(a){this.__image&&this.img&&a&&"WebGL"===a.type&&(this._establishShader(this.__image,d.defaultShader("Sprite")),this.program.setTexture(a.makeTexture(this.__image,this.img,!1)))},_drawSprite:function(a){var b=a.co,c=a.pos,d=a.ctx;if("canvas"===a.type)d.drawImage(this.img,b.x,b.y,b.w,b.h,c._x,c._y,c._w,c._h);else if("DOM"===a.type){var e=this._h/b.h,f=this._w/b.w,g=this._element.style,h=g.backgroundColor;"initial"===h&&(h="");var i=h+" url('"+this.__image+"') no-repeat";i!==g.background&&(g.background=i),g.backgroundPosition="-"+b.x*f+"px -"+b.y*e+"px",1===e&&1===f||(g.backgroundSize=this.img.width*f+"px "+this.img.height*e+"px")}else"webgl"===a.type&&a.program.draw(a,this)},sprite:function(a,b,c,d){if("string"==typeof a){var e=this.__map[a];if(!e)return this;a=e[0],b=e[1],c=e[2]||1,d=e[3]||1}return this.__coord=this.__coord||[0,0,0,0],this.__coord[0]=a*(this.__tile+this.__padding[0])+(this.__padBorder?this.__padding[0]:0)+this.__trim[0],this.__coord[1]=b*(this.__tileh+this.__padding[1])+(this.__padBorder?this.__padding[1]:0)+this.__trim[1],"undefined"!=typeof c&&"undefined"!=typeof d&&(this.__coord[2]=this.__trim[2]||c*this.__tile||this.__tile,this.__coord[3]=this.__trim[3]||d*this.__tileh||this.__tileh),this.trigger("Invalidate"),this},crop:function(a,b,c,d){var e=this._mbr||this.pos();return this.__trim=[],this.__trim[0]=a,this.__trim[1]=b,this.__trim[2]=c,this.__trim[3]=d,this.__coord[0]+=a,this.__coord[1]+=b,this.__coord[2]=c,this.__coord[3]=d,this._w=c,this._h=d,this.trigger("Invalidate",e),this}})},{"../core/core.js":8}],36:[function(a,b,c){var d=a("../core/core.js");d.c("Text",{_text:"",defaultSize:"10px",defaultFamily:"sans-serif",defaultVariant:"normal",defaultLineHeight:"normal",defaultTextAlign:"left",ready:!0,init:function(){this.requires("2D"),this._textFont={type:"",weight:"",size:this.defaultSize,lineHeight:this.defaultLineHeight,family:this.defaultFamily,variant:this.defaultVariant},this._textAlign=this.defaultTextAlign},events:{Draw:function(a){var b=this._fontString();if("DOM"===a.type){var c=this._element,d=c.style;d.color=this._textColor,d.font=b,d.textAlign=this._textAlign,c.innerHTML=this._text}else if("canvas"===a.type){var e=a.ctx;e.save(),e.textBaseline="top",e.fillStyle=this._textColor||"rgb(0,0,0)",e.font=b,e.textAlign=this._textAlign,e.fillText(this._text,a.pos._x,a.pos._y),e.restore()}}},remove:function(){this.unbind(this._textUpdateEvent,this._dynamicTextUpdate)},_getFontHeight:function(){var a=/([a-zA-Z]+)\b/,b={px:1,pt:4/3,pc:16,cm:96/2.54,mm:96/25.4,in:96,em:void 0,ex:void 0};return function(c){var d=parseFloat(c),e=a.exec(c),f=e?e[1]:"px";return void 0!==b[f]?Math.ceil(d*b[f]):Math.ceil(d)}}(),_textGenerator:null,text:function(a){return"undefined"==typeof a||null===a?this._text:("function"==typeof a?(this._text=a.call(this),this._textGenerator=a):(this._text=a,this._textGenerator=null),this.has("Canvas")&&this._resizeForCanvas(),this.trigger("Invalidate"),this)},_dynamicTextOn:!1,_textUpdateEvent:null,_dynamicTextUpdate:function(){this._textGenerator&&this.text(this._textGenerator)},dynamicTextGeneration:function(a,b){return this.unbind(this._textUpdateEvent,this._dynamicTextUpdate),a&&(this._textUpdateEvent=b||"EnterFrame",this.bind(this._textUpdateEvent,this._dynamicTextUpdate)),this},_resizeForCanvas:function(){var a=this._drawContext;a.font=this._fontString(),this.w=a.measureText(this._text).width;var b=this._textFont.size||this.defaultSize;this.h=1.1*this._getFontHeight(b),"left"===this._textAlign||"start"===this._textAlign?this.offsetBoundary(0,0,0,0):"center"===this._textAlign?this.offsetBoundary(this.w/2,0,-this.w/2,0):"end"!==this._textAlign&&"right"!==this._textAlign||this.offsetBoundary(this.w,0,-this.w,0)},_fontString:function(){return this._textFont.type+" "+this._textFont.variant+" "+this._textFont.weight+" "+this._textFont.size+" / "+this._textFont.lineHeight+" "+this._textFont.family},textColor:function(a){return d.assignColor(a,this),this._textColor="rgba("+this._red+", "+this._green+", "+this._blue+", "+this._strength+")",this.trigger("Invalidate"),this},textAlign:function(a){return this._textAlign=a,this.has("Canvas")&&this._resizeForCanvas(),this.trigger("Invalidate"),this},textFont:function(a,b){if(1===arguments.length){if("string"==typeof a)return this._textFont[a];if("object"==typeof a)for(var c in a)"family"===c?this._textFont[c]="'"+a[c]+"'":this._textFont[c]=a[c]}else this._textFont[a]=b;return this.has("Canvas")&&this._resizeForCanvas(),this.trigger("Invalidate"),this},unselectable:function(){return this.has("DOM")&&(this.css({"-webkit-touch-callout":"none","-webkit-user-select":"none","-khtml-user-select":"none","-moz-user-select":"none","-ms-user-select":"none","user-select":"none",cursor:"default"}),this.trigger("Invalidate")),this}})},{"../core/core.js":8}],37:[function(a,b,c){var d=a("../core/core.js"),e=window.document;d.extend({viewport:{clampToEntities:!0,_width:0,_height:0,_x:0,_y:0,_scale:1,bounds:null,scroll:function(a,b){this[a]=b,d.trigger("ViewportScroll"),d.trigger("InvalidateViewport")},rect_object:{_x:0,_y:0,_w:0,_h:0},rect:function(a){return a=a||this.rect_object,a._x=-this._x,a._y=-this._y,a._w=this._width/this._scale,a._h=this._height/this._scale,a},pan:function(){function a(a){h.tick(a.dt);var i=h.value();d.viewport.x=(1-i)*f+i*c,d.viewport.y=(1-i)*g+i*e,d.viewport._clamp(),h.complete&&(b(),d.trigger("CameraAnimationDone"))}function b(){d.unbind("EnterFrame",a)}var c,e,f,g,h;return d._preBind("StopCamera",b),function(b,i,j,k){d.trigger("StopCamera"),"reset"!==b&&(f=d.viewport._x,g=d.viewport._y,c=f-b,e=g-i,h=new d.easing(j,k),d.uniqueBind("EnterFrame",a))}}(),follow:function(){function a(){var a=d.viewport._scale;d.viewport.scroll("_x",-(this.x+this.w/2-d.viewport.width/2/a-e*a)),d.viewport.scroll("_y",-(this.y+this.h/2-d.viewport.height/2/a-f*a)),d.viewport._clamp()}function b(){c&&(c.unbind("Move",a),c.unbind("ViewportScale",a),c.unbind("ViewportResize",a))}var c,e,f;return d._preBind("StopCamera",b),function(b,g,h){b&&b.has("2D")&&(d.trigger("StopCamera"),c=b,e="undefined"!=typeof g?g:0,f="undefined"!=typeof h?h:0,b.bind("Move",a),b.bind("ViewportScale",a),b.bind("ViewportResize",a),a.call(b))}}(),centerOn:function(a,b){var c=a.x+d.viewport.x,e=a.y+d.viewport.y,f=a.w/2,g=a.h/2,h=d.viewport.width/2/d.viewport._scale,i=d.viewport.height/2/d.viewport._scale,j=c+f-h,k=e+g-i;d.viewport.pan(j,k,b)},zoom:function(){function a(){d.unbind("EnterFrame",b)}function b(b){var e,l;k.tick(b.dt),e=Math.pow(f,k.value()),l=1===f?k.value():(1/e-1)/(1/f-1),d.viewport.scale(e*c),d.viewport.scroll("_x",g*(1-l)+h*l),d.viewport.scroll("_y",i*(1-l)+j*l),d.viewport._clamp(),k.complete&&(a(),d.trigger("CameraAnimationDone"))}d._preBind("StopCamera",a);var c,e,f,g,h,i,j,k;return function(a,l,m,n,o){return a?(arguments.length<=2&&(n=l,l=d.viewport.x-d.viewport.width,m=d.viewport.y-d.viewport.height),d.trigger("StopCamera"),c=d.viewport._scale,f=a,e=c*f,g=d.viewport.x,i=d.viewport.y,h=-(l-d.viewport.width/(2*e)),j=-(m-d.viewport.height/(2*e)),k=new d.easing(n,o),void d.uniqueBind("EnterFrame",b)):void d.viewport.scale(1)}}(),scale:function(){return function(a){this._scale=a?a:1,d.trigger("InvalidateViewport"),d.trigger("ViewportScale")}}(),mouselook:function(){var a=!1,b=!1,c={};return function(e,f){if("boolean"==typeof e)return a=e,void(a?d.mouseObjs++:d.mouseObjs=Math.max(0,d.mouseObjs-1));if(a)switch(e){case"move":case"drag":if(!b)return;var g={x:f.clientX-c.x,y:f.clientY-c.y};c.x=f.clientX,c.y=f.clientY,d.viewport.x+=g.x,d.viewport.y+=g.y,d.viewport._clamp();break;case"start":d.trigger("StopCamera"),c.x=f.clientX,c.y=f.clientY,b=!0;break;case"stop":b=!1}}}(),_clamp:function(){if(this.clampToEntities){var a=d.clone(this.bounds)||d.clone(d.map.boundaries());a.max.x*=this._scale,a.min.x*=this._scale,a.max.y*=this._scale,a.min.y*=this._scale,a.max.x-a.min.x>d.viewport.width?d.viewport.x<(-a.max.x+d.viewport.width)/this._scale?d.viewport.x=(-a.max.x+d.viewport.width)/this._scale:d.viewport.x>-a.min.x&&(d.viewport.x=-a.min.x):d.viewport.x=-1*(a.min.x+(a.max.x-a.min.x)/2-d.viewport.width/2),a.max.y-a.min.y>d.viewport.height?d.viewport.y<(-a.max.y+d.viewport.height)/this._scale?d.viewport.y=(-a.max.y+d.viewport.height)/this._scale:d.viewport.y>-a.min.y&&(d.viewport.y=-a.min.y):d.viewport.y=-1*(a.min.y+(a.max.y-a.min.y)/2-d.viewport.height/2)}},init:function(a,b,c){d.createLayer("DefaultCanvasLayer","Canvas",{z:20}),d.createLayer("DefaultDOMLayer","DOM",{z:30}),d.createLayer("DefaultWebGLLayer","WebGL",{z:10}),this._defineViewportProperties(),this._x=0,this._y=0,this._scale=1,this.bounds=null,this._width=a||window.innerWidth,this._height=b||window.innerHeight,"undefined"==typeof c&&(c="cr-stage");var f;if("string"==typeof c)f=e.getElementById(c);else{if(!("undefined"!=typeof HTMLElement?c instanceof HTMLElement:c instanceof Element))throw new TypeError("stage_elem must be a string or an HTMLElement");f=c}d.stage={x:0,y:0,fullscreen:!1,elem:f?f:e.createElement("div")},a||b||(e.body.style.overflow="hidden",d.stage.fullscreen=!0),d.addEvent(this,window,"resize",d.viewport.reload),d.addEvent(this,window,"blur",function(){d.settings.get("autoPause")&&(d._paused||d.pause())}),d.addEvent(this,window,"focus",function(){d._paused&&d.settings.get("autoPause")&&d.pause()}),d.settings.register("stageSelectable",function(a){d.stage.elem.onselectstart=a?function(){return!0}:function(){return!1}}),d.settings.modify("stageSelectable",!1), +d.settings.register("stageContextMenu",function(a){d.stage.elem.oncontextmenu=a?function(){return!0}:function(){return!1}}),d.settings.modify("stageContextMenu",!1),d.settings.register("autoPause",function(){}),d.settings.modify("autoPause",!1),f||(e.body.appendChild(d.stage.elem),d.stage.elem.id=c);var g,h=d.stage.elem.style;if(h.width=this.width+"px",h.height=this.height+"px",h.overflow="hidden",d.bind("ViewportResize",function(){d.trigger("InvalidateViewport")}),d.mobile){void 0!==typeof h.webkitTapHighlightColor&&(h.webkitTapHighlightColor="rgba(0,0,0,0)");var i=e.createElement("meta"),j=e.getElementsByTagName("head")[0];i=e.createElement("meta"),i.setAttribute("name","apple-mobile-web-app-capable"),i.setAttribute("content","yes"),j.appendChild(i),d.addEvent(this,d.stage.elem,"touchmove",function(a){a.preventDefault()})}h.position="relative",g=d.domHelper.innerPosition(d.stage.elem),d.stage.x=g.x,d.stage.y=g.y,d.uniqueBind("ViewportResize",this._resize)},_resize:function(){d.stage.elem.style.width=d.viewport.width+"px",d.stage.elem.style.height=d.viewport.height+"px"},_defineViewportProperties:function(){Object.defineProperty(this,"x",{set:function(a){this.scroll("_x",a)},get:function(){return this._x},configurable:!0}),Object.defineProperty(this,"y",{set:function(a){this.scroll("_y",a)},get:function(){return this._y},configurable:!0}),Object.defineProperty(this,"width",{set:function(a){this._width=a,d.trigger("ViewportResize")},get:function(){return this._width},configurable:!0}),Object.defineProperty(this,"height",{set:function(a){this._height=a,d.trigger("ViewportResize")},get:function(){return this._height},configurable:!0})},reload:function(){var a,b=window.innerWidth,c=window.innerHeight;d.stage.fullscreen&&(this._width=b,this._height=c,d.trigger("ViewportResize")),a=d.domHelper.innerPosition(d.stage.elem),d.stage.x=a.x,d.stage.y=a.y},reset:function(){d.viewport.mouselook("stop"),d.trigger("StopCamera"),d.viewport.scroll("_x",0),d.viewport.scroll("_y",0),d.viewport.scale(1)},onScreen:function(a){return d.viewport._x+a._x+a._w>0&&d.viewport._y+a._y+a._h>0&&d.viewport._x+a._x=this.max_size)){var b=Math.min(a,this.max_size),c=new Float32Array(4*b*this.stride),d=new Uint16Array(6*b);c.set(this._attributeArray),d.set(this._indexArray),this._attributeArray=c,this._indexArray=d,this.array_size=b}},registerEntity:function(a){if(0===this._registryHoles.length){if(this._registrySize>=this.max_size)throw"Number of entities exceeds maximum limit.";this._registrySize>=this.array_size&&this.growArrays(2*this.array_size),a._glBufferIndex=this._registrySize,this._registrySize++}else a._glBufferIndex=this._registryHoles.pop()},unregisterEntity:function(a){"number"==typeof a._glBufferIndex&&this._registryHoles.push(a._glBufferIndex),a._glBufferIndex=null},resetRegistry:function(){this._maxElement=0,this._registryHoles.length=0},setCurrentEntity:function(a){this.ent_offset=4*a._glBufferIndex,this.ent=a},switchTo:function(){var a=this.context;a.useProgram(this.shader),a.bindBuffer(a.ARRAY_BUFFER,this._attributeBuffer);for(var b,c=this.attributes,d=0;d0&&e0&&f0?b:a/2,this},place:function(a,b,c,e){var f=this.pos2px(a,b);return f.top-=c*(this._tile.height/2),e.x=f.left+d.viewport._x,e.y=f.top+d.viewport._y,e.z+=c,this},pos2px:function(a,b){return{left:a*this._tile.width+(1&b)*(this._tile.width/2),top:b*this._tile.height/2}},px2pos:function(a,b){return{x:-Math.ceil(-a/this._tile.width-.5*(1&b)),y:b/this._tile.height*2}},centerAt:function(a,b){if("number"==typeof a&&"number"==typeof b){var c=this.pos2px(a,b);return d.viewport._x=-c.left+d.viewport.width/2-this._tile.width/2,d.viewport._y=-c.top+d.viewport.height/2-this._tile.height/2,this}return{top:-d.viewport._y+d.viewport.height/2-this._tile.height/2,left:-d.viewport._x+d.viewport.width/2-this._tile.width/2}},area:function(){var a=this.centerAt(),b=this.px2pos(-a.left+d.viewport.width/2,-a.top+d.viewport.height/2),c=this.px2pos(-a.left-d.viewport.width/2,-a.top-d.viewport.height/2);return{x:{start:b.x,end:c.x},y:{start:b.y,end:c.y}}}}})},{"../core/core.js":8}],42:[function(a,b,c){var d=a("../core/core.js"),e=window.document;d.extend({audio:{sounds:{},supported:null,codecs:{ogg:'audio/ogg; codecs="vorbis"',wav:'audio/wav; codecs="1"',webma:'audio/webm; codecs="vorbis"',mp3:'audio/mpeg; codecs="mp3"',m4a:'audio/mp4; codecs="mp4a.40.2"'},volume:1,muted:!1,paused:!1,playCheck:null,_canPlay:function(){if(this.supported={},d.support.audio){var a,b=this.audioElement();for(var c in this.codecs)a=b.canPlayType(this.codecs[c]),""!==a&&"no"!==a?this.supported[c]=!0:this.supported[c]=!1}},supports:function(a){return null===this.supported&&this._canPlay(),!!this.supported[a]},audioElement:function(){return"undefined"!=typeof Audio?new Audio(""):e.createElement("audio")},create:function(a,b){var c=b.substr(b.lastIndexOf(".")+1).toLowerCase();if(!this.supports(c))return!1;var e=this.audioElement();return e.id=a,e.preload="auto",e.volume=d.audio.volume,e.src=b,d.asset(b,e),this.sounds[a]={obj:e,played:0,volume:d.audio.volume},this.sounds[a]},add:function(a,b){if(d.support.audio){var c,e;if(1===arguments.length&&"object"==typeof a)for(var f in a)for(c in a[f])if(e=d.audio.create(f,a[f][c]))break;if("string"==typeof a&&("string"==typeof b&&(e=d.audio.create(a,b)),"object"==typeof b))for(c in b)if(e=d.audio.create(a,b[c]))break;return e}},play:function(a,b,c){if(0!==b&&d.support.audio&&this.sounds[a]){var e=this.sounds[a],f=this.getOpenChannel();if(!f)return null;f.id=a,f.repeat=b;var g=f.obj;return f.volume=e.volume=e.obj.volume=c||d.audio.volume,g.volume=e.volume,g.src=e.obj.src,this.muted&&(g.volume=0),g.play(),e.played++,f.onEnd=function(){e.played0&&this._cascade(a)}),this.bind("Rotate",function(a){var b=this._cbr||this._mbr||this;this._entry.update(b),this._children.length>0&&this._cascade(a)}),this.bind("Remove",function(){if(this._children){for(var a=0;a-1e-10?0:i,j=j<1e-10&&j>-1e-10?0:j;var k=d*i+f*j,l=-d*j+f*i,m=e*i+f*j,n=-e*j+f*i,o=e*i+g*j,p=-e*j+g*i,q=d*i+g*j,r=-d*j+g*i,s=Math.floor(Math.min(k,m,o,q)+a),t=Math.floor(Math.min(l,n,p,r)+b),u=Math.ceil(Math.max(k,m,o,q)+a),v=Math.ceil(Math.max(l,n,p,r)+b);if(this._mbr?(this._mbr._x=s,this._mbr._y=t,this._mbr._w=u-s,this._mbr._h=v-t):this._mbr={_x:s,_y:t,_w:u-s,_h:v-t},this._cbr){var w=this._cbr,x=w.cx,y=w.cy,z=w.r,A=a+(x+this._x-a)*i+(y+this._y-b)*j,B=b-(x+this._x-a)*j+(y+this._y-b)*i;w._x=Math.min(A-z,s),w._y=Math.min(B-z,t),w._w=Math.max(A+z,u)-w._x,w._h=Math.max(B+z,v)-w._y}},_rotate:function(a){var b=this._rotation-a;if(0!==b){this._rotation=a;var c={x:this._origin.x+this._x,y:this._origin.y+this._y};this._calculateMBR();var d=b*h,e=Math.cos(d),f=Math.sin(d);this.trigger("Rotate",{cos:-1e-10e._x&&f._ye._y},within:function(a,b,c,d){var e,f=this._mbr||this;return e="object"==typeof a?a:{_x:a,_y:b,_w:c,_h:d},e._x<=f._x&&e._x+e._w>=f._x+f._w&&e._y<=f._y&&e._y+e._h>=f._y+f._h},contains:function(a,b,c,d){var e,f=this._mbr||this;return e="object"==typeof a?a:{_x:a,_y:b,_w:c,_h:d},e._x>=f._x&&e._x+e._w<=f._x+f._w&&e._y>=f._y&&e._y+e._h<=f._y+f._h},pos:function(a){return a=a||{},a._x=this._x,a._y=this._y,a._w=this._w,a._h=this._h,a},mbr:function(a){return a=a||{},this._mbr?(a._x=this._mbr._x,a._y=this._mbr._y,a._w=this._mbr._w,a._h=this._mbr._h,a):this.pos(a)},isAt:function(a,b){if(this.mapArea)return this.mapArea.containsPoint(a,b);if(this.map)return this.map.containsPoint(a,b);var c=this._mbr||this;return c._x<=a&&c._x+c._w>=a&&c._y<=b&&c._y+c._h>=b},move:function(a,b){return"n"===a.charAt(0)&&(this.y-=b),"s"===a.charAt(0)&&(this.y+=b),"e"!==a&&"e"!==a.charAt(1)||(this.x+=b),"w"!==a&&"w"!==a.charAt(1)||(this.x-=b),this},shift:function(a,b,c,d){return a&&(this.x+=a),b&&(this.y+=b),c&&(this.w+=c),d&&(this.h+=d),this},_cascade:function(a){if(a){var b,c=0,d=this._children,e=d.length;if("cos"in a||"sin"in a)for(;c1&&(a=Array.prototype.slice.call(arguments,0)),this.points=a},d.polygon.prototype={containsPoint:function(a,b){var c,d,e=this.points,f=e.length/2,g=!1;for(c=0,d=f-1;cb!=e[2*d+1]>b&&a<(e[2*d]-e[2*c])*(b-e[2*c+1])/(e[2*d+1]-e[2*c+1])+e[2*c]&&(g=!g);return g},shift:function(a,b){for(var c=0,d=this.points,e=d.length;c=0&&e<=1&&c>=0&&c=0&&c=0&&cthis.mtx.length||b<1||b>this.mtx[0].length?null:this.mtx[a-1][b-1]}}},{"../core/core.js":8,"./spatial-grid.js":49}],44:[function(a,b,c){var d=a("../core/core.js"),e=Math.PI/180,f=1e-6;d.extend({raycast:function(a,b){for(var c,e,g="obj",h=1/0,i=!0,j=2,k=arguments.length;jh)return!0;if(c.map&&c.__c[g]&&!p[c[0]]){p[c[0]]=!0;var e=c.map.intersectRay(a,b);e1){var b=Array.prototype.slice.call(arguments,0);a=new d.polygon(b)}else a=a.constructor===Array?new d.polygon(a.slice()):a.clone();this._findBounds(a.points)}else a=new d.polygon([0,0,this._w,0,this._w,this._h,0,this._h]),this.bind("Resize",this._resizeMap),this._cbr=null;return this.rotation&&a.rotate({cos:Math.cos(-this.rotation*e),sin:Math.sin(-this.rotation*e),o:{x:this._origin.x,y:this._origin.y}}),this.map=a,this.attach(this.map),this.map.shift(this._x,this._y),this.trigger("NewHitbox",a),this},cbr:function(a){return a=a||{},this._cbr?(a._x=this._cbr._x,a._y=this._cbr._y,a._w=this._cbr._w,a._h=this._cbr._h,a):this.mbr(a)},_findBounds:function(a){for(var b=1/0,c=-(1/0),d=1/0,e=-(1/0),f=a.length,g=0;gc&&(c=a[g]),a[g+1]e&&(e=a[g+1]);var h={cx:(b+c)/2,cy:(d+e)/2,r:Math.sqrt((c-b)*(c-b)+(e-d)*(e-d))/2};return b>=0&&d>=0&&(this._checkBounds=function(){null===this._cbr&&this._w=0&&d>=0&&c<=this._w&&e<=this._h?(this._cbr=null,!1):(this._cbr=h,this._calculateMBR(),!0)},_resizeMap:function(a){var b,c,d=this.rotation*e,f=this.map.points;"w"===a.axis?(d?(b=a.amount*Math.cos(d),c=a.amount*Math.sin(d)):(b=a.amount,c=0),f[2]+=b,f[3]+=c):(d?(c=a.amount*Math.cos(d),b=-a.amount*Math.sin(d)):(b=0,c=a.amount),f[6]+=b,f[7]+=c),f[4]+=b,f[5]+=c},hit:function(a){var b,c,e,f,g=this._cbr||this._mbr||this,h=d.map.search(g,!1),i=0,j=h.length,k={},l=d.rectManager.overlap,m="map"in this&&"containsPoint"in this.map,n=[];if(!j)return null;for(;ig&&(g=j),jh&&(h=j),j=0)return!1;i>s&&(s=i,t=q,u=r)}for(l=0;lg&&(g=j),jh&&(h=j),j=0)return!1;i>s&&(s=i,t=q,u=r)}return{overlap:s,normal:{x:t,y:u}}}})},{"../core/core.js":8}],45:[function(a,b,c){var d=a("../core/core.js");d.math={abs:function(a){return a<0?-a:a},amountOf:function(a,b,c){return bc?c:a=b&&a<=c}},d.math.Vector2D=function(){function a(b,c){if(b instanceof a)this.x=b.x,this.y=b.y;else if(2===arguments.length)this.x=b,this.y=c;else if(arguments.length>0)throw"Unexpected number of arguments for Vector2D()"}return a.prototype.x=0,a.prototype.y=0,a.prototype.add=function(a){return this.x+=a.x,this.y+=a.y,this},a.prototype.angleBetween=function(a){return Math.atan2(this.x*a.y-this.y*a.x,this.x*a.x+this.y*a.y)},a.prototype.angleTo=function(a){return Math.atan2(a.y-this.y,a.x-this.x)},a.prototype.clone=function(){return new a(this); +},a.prototype.distance=function(a){return Math.sqrt((a.x-this.x)*(a.x-this.x)+(a.y-this.y)*(a.y-this.y))},a.prototype.distanceSq=function(a){return(a.x-this.x)*(a.x-this.x)+(a.y-this.y)*(a.y-this.y)},a.prototype.divide=function(a){return this.x/=a.x,this.y/=a.y,this},a.prototype.dotProduct=function(a){return this.x*a.x+this.y*a.y},a.prototype.crossProduct=function(a){return this.x*a.y-this.y*a.x},a.prototype.equals=function(b){return b instanceof a&&this.x===b.x&&this.y===b.y},a.prototype.perpendicular=function(b){return b=b||new a,b.setValues(-this.y,this.x)},a.prototype.getNormal=function(b,c){return c=c||new a,c.setValues(b.y-this.y,this.x-b.x).normalize()},a.prototype.isZero=function(){return 0===this.x&&0===this.y},a.prototype.magnitude=function(){return Math.sqrt(this.x*this.x+this.y*this.y)},a.prototype.magnitudeSq=function(){return this.x*this.x+this.y*this.y},a.prototype.multiply=function(a){return this.x*=a.x,this.y*=a.y,this},a.prototype.negate=function(){return this.x=-this.x,this.y=-this.y,this},a.prototype.normalize=function(){var a=Math.sqrt(this.x*this.x+this.y*this.y);return 0===a?(this.x=1,this.y=0):(this.x/=a,this.y/=a),this},a.prototype.scale=function(a,b){return void 0===b&&(b=a),this.x*=a,this.y*=b,this},a.prototype.scaleToMagnitude=function(a){var b=a/this.magnitude();return this.x*=b,this.y*=b,this},a.prototype.setValues=function(b,c){return b instanceof a?(this.x=b.x,this.y=b.y):(this.x=b,this.y=c),this},a.prototype.subtract=function(a){return this.x-=a.x,this.y-=a.y,this},a.prototype.toString=function(){return"Vector2D("+this.x+", "+this.y+")"},a.prototype.translate=function(a,b){return void 0===b&&(b=a),this.x+=a,this.y+=b,this},a.tripleProduct=function(a,b,c,e){e=e||new d.math.Vector2D;var f=a.dotProduct(c),g=b.dotProduct(c);return e.setValues(b.x*f-a.x*g,b.y*f-a.y*g)},a}(),d.math.Matrix2D=function(){function a(b,c,d,e,f,g){if(b instanceof a)this.a=b.a,this.b=b.b,this.c=b.c,this.d=b.d,this.e=b.e,this.f=b.f;else if(6===arguments.length)this.a=b,this.b=c,this.c=d,this.d=e,this.e=f,this.f=g;else if(arguments.length>0)throw"Unexpected number of arguments for Matrix2D()"}return a.prototype.a=1,a.prototype.b=0,a.prototype.c=0,a.prototype.d=1,a.prototype.e=0,a.prototype.f=0,a.prototype.apply=function(a){var b=a.x;return a.x=b*this.a+a.y*this.c+this.e,a.y=b*this.b+a.y*this.d+this.f,a},a.prototype.clone=function(){return new a(this)},a.prototype.combine=function(a){var b=this.a;return this.a=b*a.a+this.b*a.c,this.b=b*a.b+this.b*a.d,b=this.c,this.c=b*a.a+this.d*a.c,this.d=b*a.b+this.d*a.d,b=this.e,this.e=b*a.a+this.f*a.c+a.e,this.f=b*a.b+this.f*a.d+a.f,this},a.prototype.equals=function(b){return b instanceof a&&this.a===b.a&&this.b===b.b&&this.c===b.c&&this.d===b.d&&this.e===b.e&&this.f===b.f},a.prototype.determinant=function(){return this.a*this.d-this.b*this.c},a.prototype.invert=function(){var a=this.determinant();if(0!==a){var b={a:this.a,b:this.b,c:this.c,d:this.d,e:this.e,f:this.f};this.a=b.d/a,this.b=-b.b/a,this.c=-b.c/a,this.d=b.a/a,this.e=(b.c*b.f-b.e*b.d)/a,this.f=(b.e*b.b-b.a*b.f)/a}return this},a.prototype.isIdentity=function(){return 1===this.a&&0===this.b&&0===this.c&&1===this.d&&0===this.e&&0===this.f},a.prototype.isInvertible=function(){return 0!==this.determinant()},a.prototype.preRotate=function(a){var b=Math.cos(a),c=Math.sin(a),d=this.a;return this.a=b*d-c*this.b,this.b=c*d+b*this.b,d=this.c,this.c=b*d-c*this.d,this.d=c*d+b*this.d,this},a.prototype.preScale=function(a,b){return void 0===b&&(b=a),this.a*=a,this.b*=b,this.c*=a,this.d*=b,this},a.prototype.preTranslate=function(a,b){return"number"==typeof a?(this.e+=a,this.f+=b):(this.e+=a.x,this.f+=a.y),this},a.prototype.rotate=function(a){var b=Math.cos(a),c=Math.sin(a),d=this.a;return this.a=b*d-c*this.b,this.b=c*d+b*this.b,d=this.c,this.c=b*d-c*this.d,this.d=c*d+b*this.d,d=this.e,this.e=b*d-c*this.f,this.f=c*d+b*this.f,this},a.prototype.scale=function(a,b){return void 0===b&&(b=a),this.a*=a,this.b*=b,this.c*=a,this.d*=b,this.e*=a,this.f*=b,this},a.prototype.setValues=function(b,c,d,e,f,g){return b instanceof a?(this.a=b.a,this.b=b.b,this.c=b.c,this.d=b.d,this.e=b.e,this.f=b.f):(this.a=b,this.b=c,this.c=d,this.d=e,this.e=f,this.f=g),this},a.prototype.toString=function(){return"Matrix2D(["+this.a+", "+this.c+", "+this.e+"] ["+this.b+", "+this.d+", "+this.f+"] [0, 0, 1])"},a.prototype.translate=function(a,b){return"number"==typeof a?(this.e+=this.a*a+this.c*b,this.f+=this.b*a+this.d*b):(this.e+=this.a*a.x+this.c*a.y,this.f+=this.b*a.x+this.d*a.y),this},a}()},{"../core/core.js":8}],46:[function(a,b,c){var d=a("../core/core.js"),e=function(a,b,c,e){var f=b+c,g="_"+f,h={key:"",oldValue:0};e?d.defineField(a,f,function(){return this[g]},function(a){var b=this[g];a!==b&&(this[g]=a,h.key=f,h.oldValue=b,this.trigger("MotionChange",h))}):d.defineField(a,f,function(){return this[g]},function(a){}),Object.defineProperty(a,g,{value:0,writable:!0,enumerable:!1,configurable:!1})},f=function(a,b,c,e){var f=b+"x",g=b+"y",h="_"+f,i="_"+g;return c?(d.defineField(e,"x",function(){return a[h]},function(b){a[f]=b}),d.defineField(e,"y",function(){return a[i]},function(b){a[g]=b})):(d.defineField(e,"x",function(){return a[h]},function(a){}),d.defineField(e,"y",function(){return a[i]},function(a){})),Object.seal&&Object.seal(e),e};d.c("AngularMotion",{_vrotation:0,_arotation:0,_drotation:0,init:function(){this.requires("2D"),e(this,"v","rotation",!0),e(this,"a","rotation",!0),e(this,"d","rotation",!1),this.__oldRotationDirection=0,this.bind("EnterFrame",this._angularMotionTick)},remove:function(a){this.unbind("EnterFrame",this._angularMotionTick)},resetAngularMotion:function(){return this._drotation=0,this.vrotation=0,this.arotation=0,this},_angularMotionTick:function(a){var b=a.dt/1e3,c=this._rotation,d=this._vrotation,e=this._arotation,f=c+d*b+.5*e*b*b;this.vrotation=d+e*b;var g=this._vrotation,h=g?g<0?-1:1:0;this.__oldRotationDirection!==h&&(this.__oldRotationDirection=h,this.trigger("NewRotationDirection",h)),this._drotation=f-c,0!==this._drotation&&(this.rotation=f,this.trigger("Rotated",c))}}),d.c("Motion",{_vx:0,_vy:0,_ax:0,_ay:0,_dx:0,_dy:0,init:function(){this.requires("2D"),e(this,"v","x",!0),e(this,"v","y",!0),this._velocity=f(this,"v",!0,new d.math.Vector2D),e(this,"a","x",!0),e(this,"a","y",!0),this._acceleration=f(this,"a",!0,new d.math.Vector2D),e(this,"d","x",!1),e(this,"d","y",!1),this._motionDelta=f(this,"d",!1,new d.math.Vector2D),this.__movedEvent={axis:"",oldValue:0},this.__oldDirection={x:0,y:0},this.bind("EnterFrame",this._linearMotionTick)},remove:function(a){this.unbind("EnterFrame",this._linearMotionTick)},resetMotion:function(){return this.vx=0,this.vy=0,this.ax=0,this.ay=0,this._dx=0,this._dy=0,this},motionDelta:function(){return this._motionDelta},velocity:function(){return this._velocity},acceleration:function(){return this._acceleration},ccdbr:function(a){var b=this._cbr||this._mbr||this,c=this._dx,d=this._dy,e=0,f=0,g=c>0?e=c:-c,h=d>0?f=d:-d;return a=a||{},a._x=b._x-e,a._y=b._y-f,a._w=b._w+g,a._h=b._h+h,a},_linearMotionTick:function(a){var b=a.dt/1e3,c=this._x,d=this._vx,e=this._ax,f=this._y,g=this._vy,h=this._ay,i=c+d*b+.5*e*b*b,j=f+g*b+.5*h*b*b;this.vx=d+e*b,this.vy=g+h*b;var k=this.__oldDirection,l=this._vx,m=l?l<0?-1:1:0,n=this._vy,o=n?n<0?-1:1:0;k.x===m&&k.y===o||(k.x=m,k.y=o,this.trigger("NewDirection",k));var p=this.__movedEvent;this._dx=i-c,this._dy=j-f,0!==this._dx&&(this.x=i,p.axis="x",p.oldValue=c,this.trigger("Moved",p)),0!==this._dy&&(this.y=j,p.axis="y",p.oldValue=f,this.trigger("Moved",p))}})},{"../core/core.js":8}],47:[function(a,b,c){var d=a("../core/core.js");d.c("Supportable",{_ground:null,_groundComp:null,_preventGroundTunneling:!1,canLand:!0,init:function(){this.requires("2D"),this.__area={_x:0,_y:0,_w:0,_h:0},this.defineField("ground",function(){return this._ground},function(a){})},remove:function(a){this.unbind("EnterFrame",this._detectGroundTick)},startGroundDetection:function(a){return a&&(this._groundComp=a),this.uniqueBind("EnterFrame",this._detectGroundTick),this},stopGroundDetection:function(){return this.unbind("EnterFrame",this._detectGroundTick),this},preventGroundTunneling:function(a){return"undefined"==typeof a&&(a=!0),a&&this.requires("Motion"),this._preventGroundTunneling=a,this},_detectGroundTick:function(){var a,b=this._groundComp,c=this._ground,e=d.rectManager.overlap;if(this._preventGroundTunneling)a=this.ccdbr(this.__area);else{var f=this._cbr||this._mbr||this;a=this.__area,a._x=f._x,a._y=f._y,a._w=f._w,a._h=f._h}if(a._h++,c){var g=c._cbr||c._mbr||c;c.__c[b]&&d(c[0])===c&&e(g,a)||(this._ground=null,this.trigger("LiftedOffGround",c),c=null)}if(!c)for(var h,i,j=d.map.search(a,!1),k=0,l=j.length;kc._x+c._w?this.x=c._x+c._w-1:this._x+this._wb._x&&a._yb._y},integerBounds:function(a){return a._w=a._x+a._w,a._h=a._y+a._h,a._x=a._x>0?0|a._x:(0|a._x)-1,a._y=a._y>0?0|a._y:(0|a._y)-1,a._w-=a._x,a._h-=a._y,a._w=a._w===(0|a._w)?a._w:(0|a._w)+1,a._h=a._h===(0|a._h)?a._h:(0|a._h)+1,a},mergeSet:function(a){for(var b=0;b0&&b--):b++;return a},boundingRect:function(a){if(a&&a.length){var b,c,d=1,e=a.length,f=a[0];for(f=[f._x,f._y,f._x+f._w,f._y+f._h];df[2]&&(f[2]=c[2]),c[3]>f[3]&&(f[3]=c[3]),d++;return c=f,f={_x:c[0],_y:c[1],_w:c[2]-c[0],_h:c[3]-c[1]}}},_pool:function(){var a=[],b=0;return{get:function(c,d,e,f){a.length<=b&&a.push({});var g=a[b++];return g._x=c,g._y=d,g._w=e,g._h=f,g},copy:function(c){a.length<=b&&a.push({});var d=a[b++];return d._x=c._x,d._y=c._y,d._w=c._w,d._h=c._h,d},recycle:function(a){b--}}}()}})},{"../core/core.js":8}],49:[function(a,b,c){function d(a,b,c){this.keys=a,this.map=c,this.obj=b}var e,f=function(a){e=a||64,this.map={},this.boundsDirty=!1,this.boundsHash={max:{x:-(1/0),y:-(1/0)},min:{x:1/0,y:1/0}},this.boundsCoords={max:{x:-(1/0),y:-(1/0)},min:{x:1/0,y:1/0}}},g=" ",h={};f.prototype={insert:function(a){var b,c,e=f.key(a),g=new d(e,a,this),h=0;for(h=e.x1;h<=e.x2;h++)for(b=e.y1;b<=e.y2;b++)c=h<<16^b,this.map[c]||(this.map[c]=[]),this.map[c].push(a);return this.boundsDirty=!0,g},search:function(a,b){var c,d,e,g,i,j=f.key(a,h),k=[];for(void 0===b&&(b=!0),c=j.x1;c<=j.x2;c++)for(d=j.y1;d<=j.y2;d++)if(i=this.map[c<<16^d])for(e=0;ea._x&&l._ya._y&&(o[m]=k[c]));for(l in o)n.push(o[l]);return n}return k},remove:function(a){var b,c,d=a.keys,e=a.obj,f=0;for(f=d.x1;f<=d.x2;f++)for(b=d.y1;b<=d.y2;b++)if(c=f<<16^b,this.map[c]){var g,h=this.map[c],i=h.length;for(g=0;g>16,g=e<<16>>16;if(g<0&&(f^=-1),f>=a.max.x){a.max.x=f;for(c in this.map[e])d=this.map[e][c],"object"==typeof d&&"requires"in d&&(b.max.x=Math.max(b.max.x,d.x+d.w))}if(f<=a.min.x){a.min.x=f;for(c in this.map[e])d=this.map[e][c],"object"==typeof d&&"requires"in d&&(b.min.x=Math.min(b.min.x,d.x))}if(g>=a.max.y){a.max.y=g;for(c in this.map[e])d=this.map[e][c],"object"==typeof d&&"requires"in d&&(b.max.y=Math.max(b.max.y,d.y+d.h))}if(g<=a.min.y){a.min.y=g;for(c in this.map[e])d=this.map[e][c],"object"==typeof d&&"requires"in d&&(b.min.y=Math.min(b.min.y,d.y))}}this.boundsDirty=!1}},traverseRay:function(a,b,c){var d=b.x,g=b.y;a={_x:a._x,_y:a._y,_w:0,_h:0};var i,j=this._keyBoundaries(),k=f.key(a,h),l=k.x1,m=k.y1,n=j.min.x,o=j.min.y,p=j.max.x,q=j.max.y,r=d>0?1:d<0?-1:0,s=g>0?1:g<0?-1:0,t=d>=0?(l+1)*e:l*e,u=g>=0?(m+1)*e:m*e,v=-(1/0),w=0,x=0,y=1/0,z=1/0;for(0!==d&&(i=1/d,y=(t-a._x)*i,w=e*r*i),0!==g&&(i=1/g,z=(u-a._y)*i,x=e*s*i);1===r&&lp&&p!==-(1/0)||1===s&&mq&&q!==-(1/0);)y 0) { + dpad.x = dpad.x / m; + dpad.y = dpad.y / m; + } + } + }, + + updateTriggerInput: function (trigger) { + if (!trigger.active) { + if (trigger.input.isActive()) { + trigger.downFor = Date.now() - trigger.input.timeDown; + trigger.active = true; + Crafty.trigger("TriggerInputDown", trigger); + } + } else { + if (!trigger.input.isActive()) { + trigger.active = false; + Crafty.trigger("TriggerInputUp", trigger); + trigger.downFor = 0; + } + } + }, + + // Has to handle three cases concerning multiple active input groups: + // - "all": all directions are active + // - "last": one direction at a time, new directions replace old ones + // - "first": one direction at a time, new directions are ignored while old ones are still active + updateDpadInput: function (dpad, multiBehavior) { + var d, dir; + var winner; + + for (d in dpad.directions) { + dir = dpad.directions[d]; + dir.active = false; + + if (dir.input.isActive()) { + if (multiBehavior === "all") { + dir.active = true; + } else { + if (!winner) { + winner = dir; + } else { + if (multiBehavior === "first") { + if (winner.input.timeDown > dir.input.timeDown) { + winner = dir; + } + } + if (multiBehavior === "last") { + if (winner.input.timeDown < dir.input.timeDown) { + winner = dir; + } + } + } + } + } + } + // If we picked a winner, set it active + if (winner) winner.active = true; + } +}); +},{"../core/core.js":8}],3:[function(require,module,exports){ +var Crafty = require('../core/core.js'); + /**@ * #Draggable * @category Controls + * @kind Component * Enable drag and drop of the entity. Listens to events from `MouseDrag` and moves entity accordingly. * * @see MouseDrag @@ -130,7 +581,7 @@ Crafty.c("Draggable", { /**@ * #.enableDrag - * @comp Draggable + * @comp Draggable * @sign public this .enableDrag(void) * * Reenable dragging of entity. Use if `.disableDrag` has been called. @@ -188,7 +639,7 @@ Crafty.c("Draggable", { dragDirection: function (dir) { if (typeof dir === 'undefined') { this._dir = null; - } else if (("" + parseInt(dir, 10)) == dir) { //dir is a number + } else if (+dir === dir) { //dir is a number this._dir = { x: Math.cos(dir / 180 * Math.PI), y: Math.sin(dir / 180 * Math.PI) @@ -214,6 +665,7 @@ Crafty.c("Draggable", { this._oldY = this._y; }, + //Note: the code is not tested with zoom, etc., that may distort the direction between the viewport and the coordinate on the canvas. _drag: function(e) { if (this._dir) { if (this._dir.x !== 0 || this._dir.y !== 0) { @@ -228,242 +680,269 @@ Crafty.c("Draggable", { } }); + /**@ - * #Multiway + * #Controllable * @category Controls - * @trigger NewDirection - When entity has changed direction due to velocity on either x or y axis a NewDirection event is triggered. The event is triggered once, if direction is different from last frame. - { x: -1 | 0 | 1, y: -1 | 0 | 1 } - New direction - * @trigger Moved - When entity has moved due to velocity/acceleration on either x or y axis a Moved event is triggered. If the entity has moved on both axes for diagonal movement the event is triggered twice. - { axis: 'x' | 'y', oldValue: Number } - Old position + * @kind Component * - * Used to bind keys to directions and have the entity move accordingly. + * Used to bind methods to generalized input events. + * + * Currently supports the events "DirectionalInput", "TriggerInputDown", and "TriggerInputUp". * - * @see Motion, Keyboard */ -Crafty.c("Multiway", { - _speed: null, - +Crafty.c("Controllable", { init: function () { - this.requires("Motion, Keyboard"); - - this._keyDirection = {}; // keyCode -> direction - this._activeDirections = {}; // direction -> # of keys pressed for that direction - this._directionSpeed = {}; // direction -> {x: x_speed, y: y_speed} - this._speed = { x: 150, y: 150 }; - - this.bind("KeyDown", this._keydown) - .bind("KeyUp", this._keyup); + this._inputBindings = { + "DirectionalInput": {}, + "TriggerInputDown": {}, + "TriggerInputUp": {} + }; }, + + events: { + // We don't want to use dot notation here for the property names + /* jshint -W069 */ + "DirectionalInput": function (e) { + if (this._inputBindings["DirectionalInput"][e.name]) { + this._inputBindings["DirectionalInput"][e.name].call(this, e); + } + }, - remove: function() { - this.unbind("KeyDown", this._keydown) - .unbind("KeyUp", this._keyup); - - // unapply movement of pressed keys - this.__unapplyActiveDirections(); - }, + "TriggerInputDown": function (e) { + if (this._inputBindings["TriggerInputDown"][e.name]) { + this._inputBindings["TriggerInputDown"][e.name].call(this, e); + } + }, - _keydown: function (e) { - var direction = this._keyDirection[e.key]; - if (direction !== undefined) { // if this is a key we are interested in - if (this._activeDirections[direction] === 0 && !this.disableControls) { // if key is first one pressed for this direction - this.vx += this._directionSpeed[direction].x; - this.vy += this._directionSpeed[direction].y; + "TriggerInputUp": function (e) { + if (this._inputBindings["TriggerInputUp"][e.name]) { + this._inputBindings["TriggerInputUp"][e.name].call(this, e); } - this._activeDirections[direction]++; } + /* jshint +W069 */ }, - _keyup: function (e) { - var direction = this._keyDirection[e.key]; - if (direction !== undefined) { // if this is a key we are interested in - this._activeDirections[direction]--; - if (this._activeDirections[direction] === 0 && !this.disableControls) { // if key is last one unpressed for this direction - this.vx -= this._directionSpeed[direction].x; - this.vy -= this._directionSpeed[direction].y; - } - } + /**@ + * #.linkInput + * @comp Controllable + * @sign public this linkInput(string event, string name, function fn) + * @param event - the name of the input event + * @param name - the name of the input + * @param fn - the function that will be called with the event object + * + * Binds the function to the particular named event trigger. + * + * Currently supports three types of input events. Each event will have a `name` property. + * - `DirectionalInput`: The event will have `x` and `y` properties representing the directional input vector, often normalized to a unit vector. Triggered when the input changes. + * - `TriggerInputDown`: Occurs when the input is triggered. + * - `TriggerInputDown`: Occurs when the trigger is released. The event will have a `downFor` property, indicating how long it had been active. + * + * @example + * ~~~~ + * // Create a trigger bound to the `b` key + * Crafty.s("Controls").defineTriggerInput("BlushTrigger", {keys:['b']}); + * // Create a blue square that turns pink when the trigger is pressed + * Crafty.e("2D, Canvas, Color, Controllable") + * .attr({x:10, y:10, h:10, w:10}).color("blue") + * .linkInput("TriggerInputDown", "BlushTrigger", function(){this.color('pink');}); + * ~~~ + * + * @see .unlinkInput + */ + linkInput: function(event, name, fn) { + this._inputBindings[event][name] = fn; + }, + + /**@ + * #.unlinkInput + * @comp Controllable + * @sign public this linkInput(string event, string name) + * @param event - the name of the input event + * @param name - the name of the input + * + * Removes a binding setup by linkInput + * + * @see .linkInput + */ + unlinkInput: function(event, name) { + delete this._inputBindings[event][name]; }, + disableControls: false, + /**@ - * #.multiway - * @comp Multiway - * @sign public this .multiway([Number speed,] Object keyBindings) - * @param speed - A speed in pixels per second - * @param keyBindings - What keys should make the entity go in which direction. Direction is specified in degrees - * - * Constructor to initialize the speed and keyBindings. Component will listen to key events and move the entity appropriately. - * Can be called while a key is pressed to change direction & speed on the fly. + * #.enableControl + * @comp Controllable + * @sign public this .enableControl() * - * Multiway acts by adding a velocity on key press and removing the same velocity when the respective key is released. - * This works well in most cases, but can cause undesired behavior if you manipulate velocities by yourself while this component is in effect. - * If you need to resolve collisions, it's advised to correct the position directly rather than to manipulate the velocity. If you still need to reset the velocity once a collision happens, make sure to re-add the previous velocity once the collision is resolved. + * Enable the component to listen to input events. * * @example * ~~~ - * this.multiway(150, {UP_ARROW: -90, DOWN_ARROW: 90, RIGHT_ARROW: 0, LEFT_ARROW: 180}); - * this.multiway({x:150,y:75}, {UP_ARROW: -90, DOWN_ARROW: 90, RIGHT_ARROW: 0, LEFT_ARROW: 180}); - * this.multiway({W: -90, S: 90, D: 0, A: 180}); + * this.enableControl(); * ~~~ - * - * @see Motion, Keyboard */ - multiway: function (speed, keys) { - if (keys) { - if (speed.x !== undefined && speed.y !== undefined) { - this._speed.x = speed.x; - this._speed.y = speed.y; - } else { - this._speed.x = speed; - this._speed.y = speed; - } - } else { - keys = speed; - } - - - if (!this.disableControls) { - this.__unapplyActiveDirections(); - } - - this._updateKeys(keys); - this._updateSpeed(this._speed); - - if (!this.disableControls) { - this.__applyActiveDirections(); - } - + enableControl: function () { + this.disableControls = false; return this; }, /**@ - * #.speed - * @comp Multiway - * @sign public this .speed(Object speed) - * @param speed - New speed the entity has, for x and y axis. - * - * Change the speed that the entity moves with, in units of pixels per second. + * #.disableControl + * @comp Controllable + * @sign public this .disableControl() * - * Can be called while a key is pressed to change speed on the fly. + * Disable the component from responding to input events. * * @example * ~~~ - * this.speed({ x: 150, y: 50 }); + * this.disableControl(); * ~~~ */ - speed: function (speed) { - if (!this.disableControls) { - this.__unapplyActiveDirections(); - } - - this._updateSpeed(speed); - - if (!this.disableControls) { - this.__applyActiveDirections(); - } - + disableControl: function () { + this.disableControls = true; return this; - }, + } +}); - _updateKeys: function(keys) { - // reset data - this._keyDirection = {}; - this._activeDirections = {}; - for (var k in keys) { - var keyCode = Crafty.keys[k] || k; - // add new data - var direction = this._keyDirection[keyCode] = keys[k]; - this._activeDirections[direction] = this._activeDirections[direction] || 0; - if (this.isDown(keyCode)) // add directions of already pressed keys - this._activeDirections[direction]++; - } +/**@ + * #Multiway + * @category Controls + * @kind Component + * + * Used to bind keys to directions and have the entity move accordingly. + * + * Multiway acts by listening to directional events, and then setting the velocity each frame based on the current direction and the current speed. + * + * If a speed is not defined for a particular axis (x or y), then the velocity along that axis will not be set. + * + * This behavior works in most cases, but can cause undesired behavior if you manipulate velocities by yourself while this component is in effect. + * If you need to resolve collisions, it's advised to correct the position directly rather than to manipulate the velocity. + * If you still need to reset the velocity once a collision happens, make sure to re-add the previous velocity once the collision is resolved. + * + * Additionally, this component provides the entity with `Motion` methods & events. + * + * @see Motion + */ +Crafty.c("Multiway", { + _speed: null, + + init: function () { + this.requires("Motion, Controllable"); + this._dpadName = "MultiwayDpad" + this[0]; + this._speed = { x: 150, y: 150 }; + this._direction = {x:0, y:0}; }, - _updateSpeed: function(speed) { - // reset data - this._directionSpeed = {}; - - var direction; - for (var keyCode in this._keyDirection) { - direction = this._keyDirection[keyCode]; - // add new data - this._directionSpeed[direction] = { - x: Math.round(Math.cos(direction * (Math.PI / 180)) * 1000 * speed.x) / 1000, - y: Math.round(Math.sin(direction * (Math.PI / 180)) * 1000 * speed.y) / 1000 - }; - } + remove: function() { + if (!this.disableControls) this.vx = this.vy = 0; }, - __applyActiveDirections: function() { - for (var direction in this._activeDirections) { - if (this._activeDirections[direction] > 0) { - this.vx += this._directionSpeed[direction].x; - this.vy += this._directionSpeed[direction].y; + events: { + "EnterFrame": function() { + if (!this.disableControls) { + if (typeof this._speed.x !== 'undefined' && this._speed.x !== null){ + this.vx = this._speed.x * this._direction.x; + } + if (typeof this._speed.y !== 'undefined' && this._speed.y !== null) { + this.vy = this._speed.y * this._direction.y; + } } } }, - - __unapplyActiveDirections: function() { - for (var direction in this._activeDirections) { - if (this._activeDirections[direction] > 0) { - this.vx -= this._directionSpeed[direction].x; - this.vy -= this._directionSpeed[direction].y; - } - } + + // Rather than update the velocity directly in response to changing input, track the input direction separately + // That makes it easier to enable/disable control + _updateDirection: function(e) { + this._direction.x = e.x; + this._direction.y = e.y; }, /**@ - * #.enableControl + * #.multiway * @comp Multiway - * @sign public this .enableControl() + * @sign public this .multiway([Number speed,] Object keyBindings[, Object options]) + * @param speed - A speed in pixels per second + * @param keyBindings - What keys should make the entity go in which direction. Direction is specified in degrees + * @param options - An object with options for `normalize` and `multipleDirectionBehavior`. * - * Enable the component to listen to key events. + * Constructor to initialize the speed and keyBindings. + * Component will listen to key events and move the entity appropriately. + * Can be called while a key is pressed to change direction & speed on the fly. * - * @example + * The options parameter controls the behavior of the component, and has the following defaults: + * + * - `"normalize": false`. When set to true, the directional input always has a magnitude of 1 + * - `"multipleDirectionBehavior": "all"` How to resolve multiple active directions. + * Set to "first" or "last" to allow only one active direction at a time. + * + * @example * ~~~ - * this.enableControl(); + * this.multiway(150, {UP_ARROW: -90, DOWN_ARROW: 90, RIGHT_ARROW: 0, LEFT_ARROW: 180}); + * this.multiway({x:150,y:75}, {UP_ARROW: -90, DOWN_ARROW: 90, RIGHT_ARROW: 0, LEFT_ARROW: 180}); + * this.multiway({W: -90, S: 90, D: 0, A: 180}); * ~~~ - */ - enableControl: function () { - if (this.disableControls) { - this.__applyActiveDirections(); + * + * @see Crafty.keys + */ + multiway: function (speed, keys, options) { + var inputSystem = Crafty.s("Controls"); + + if (keys) { + this.speed(speed); + } else { + keys = speed; } - this.disableControls = false; + inputSystem.defineDpad(this._dpadName, keys, options); + this.linkInput("DirectionalInput", this._dpadName, this._updateDirection); return this; }, /**@ - * #.disableControl + * #.speed * @comp Multiway - * @sign public this .disableControl() + * @sign public this .speed(Object speed) + * @param speed - New speed the entity has, for x and y axis. * - * Disable the component to listen to key events. + * Change the speed that the entity moves with, in units of pixels per second. + * Can be called while a key is pressed to change speed on the fly. + * + * If the passed object has only an x or y property, only the velocity along that axis will be controlled. * * @example * ~~~ - * this.disableControl(); + * this.speed({ x: 150, y: 50 }); * ~~~ */ - disableControl: function () { - if (!this.disableControls) { - this.__unapplyActiveDirections(); + speed: function (speed) { + if (typeof speed === 'object') { + this._speed.x = speed.x; + this._speed.y = speed.y; + } else { + this._speed.x = speed; + this._speed.y = speed; } - this.disableControls = true; - return this; - } + }, + + }); /**@ * #Jumper * @category Controls - * @trigger NewDirection - When entity has changed direction due to velocity on either x or y axis a NewDirection event is triggered. The event is triggered once, if direction is different from last frame. - { x: -1 | 0 | 1, y: -1 | 0 | 1 } - New direction - * @trigger Moved - When entity has moved due to velocity/acceleration on either x or y axis a Moved event is triggered. If the entity has moved on both axes for diagonal movement the event is triggered twice. - { axis: 'x' | 'y', oldValue: Number } - Old position + * @kind Component * @trigger CheckJumping - When entity is about to jump. This event is triggered with the object the entity is about to jump from (if it exists). Third parties can respond to this event and enable the entity to jump. * - * Make an entity jump in response to key events. + * Make the entity jump in response to key events. + * Simulates jumping and falling when used with the `Gravity` component. + * + * Additionally, this component provides the entity with `Supportable`, `Motion` and `Keyboard` methods & events. * * @see Supportable, Motion, Keyboard, Gravity */ @@ -522,27 +1001,36 @@ Crafty.c("Jumper", { */ init: function () { - this.requires("Supportable, Motion, Keyboard"); - // don't overwrite methods from Multiway if they exist - this.enableControl = this.enableControl || function() { this.disableControls = false; }; - this.disableControl = this.disableControl || function() { this.disableControls = true; }; + this.requires("Supportable, Motion, Controllable"); }, + + remove: function() { - this.unbind("KeyDown", this._keydown_jumper); + this.unlinkInput("TriggerInputDown", this._jumpTriggerName); }, _keydown_jumper: function (e) { if (this.disableControls) return; + this.jump(); + }, - if (this._jumpKeys[e.key]) { - var ground = this.ground; - this.canJump = !!ground; - this.trigger("CheckJumping", ground); - if (this.canJump) { - this.vy = -this._jumpSpeed; - } + /**@ + * #.jump + * @comp Jumper + * @sign public this .jump() + * + * Directly trigger the entity to jump. + * + */ + jump: function() { + var ground = this.ground; + this.canJump = !!ground; + this.trigger("CheckJumping", ground); + if (this.canJump) { + this.vy = -this._jumpSpeed; } + return this; }, /**@ @@ -551,10 +1039,15 @@ Crafty.c("Jumper", { * @sign public this .jumper([Number jumpSpeed,] Array jumpKeys) * @param jumpSpeed - Vertical jump speed in pixels per second * @param jumpKeys - Keys to listen for and make entity jump in response + * + * @sign public this .jumper([Number jumpSpeed,] Object jumpInputs) + * @param jumpSpeed - Vertical jump speed in pixels per second + * @param jumpInputs - An object with two properties, `keys` and `mouseButtons`. * - * Constructor to initialize the power of jump and keys to listen to. Component will - * listen for key events and move the entity appropriately. Used with the - * `gravity` component will simulate jumping. + * Constructor to initialize the power of jump and keys to listen to. + * Component will listen for key events and make the entity jump appropriately. + * + * If second argument is an object, the properties `keys` and `mouseButtons` will be used as triggers. * * @example * ~~~ @@ -562,7 +1055,7 @@ Crafty.c("Jumper", { * this.jumper(['UP_ARROW', 'W']); * ~~~ * - * @see Supportable, Motion, Keyboard, Gravity + * @see Crafty.keys */ jumper: function (jumpSpeed, jumpKeys) { if (jumpKeys) { @@ -570,15 +1063,22 @@ Crafty.c("Jumper", { } else { jumpKeys = jumpSpeed; } - - this._jumpKeys = {}; - for (var i = 0; i < jumpKeys.length; ++i) { - var key = jumpKeys[i]; - var keyCode = Crafty.keys[key] || key; - this._jumpKeys[keyCode] = true; + this._jumpTriggerName = "JumpTrigger" + this[0]; + if (Array.isArray(jumpKeys)) { + var keys = []; + for (var i = 0; i < jumpKeys.length; ++i) { + var key = jumpKeys[i]; + var keyCode = Crafty.keys[key] || key; + keys.push(keyCode); + } + Crafty.s("Controls") + .defineTriggerGroup(this._jumpTriggerName, {keys:keys}); + } else { + Crafty.s("Controls") + .defineTriggerGroup(this._jumpTriggerName, jumpKeys); } - - this.uniqueBind("KeyDown", this._keydown_jumper); + + this.linkInput("TriggerInputDown", this._jumpTriggerName, this._keydown_jumper); return this; }, @@ -605,11 +1105,13 @@ Crafty.c("Jumper", { /**@ * #Fourway * @category Controls - * @trigger NewDirection - When entity has changed direction due to velocity on either x or y axis a NewDirection event is triggered. The event is triggered once, if direction is different from last frame. - { x: -1 | 0 | 1, y: -1 | 0 | 1 } - New direction - * @trigger Moved - When entity has moved due to velocity/acceleration on either x or y axis a Moved event is triggered. If the entity has moved on both axes for diagonal movement the event is triggered twice. - { axis: 'x' | 'y', oldValue: Number } - Old position + * @kind Component * * Move an entity in four directions by using the - * arrow keys or `W`, `A`, `S`, `D`. + * `Up Arrow`, `Left Arrow`, `Down Arrow`, `Right Arrow` keys or `W`, `A`, `S`, `D`. + * + * This component is a thin wrapper around the `Multiway` component and sets the appropriate key bindings. + * It is a well suited for games with a top-down (birds-eye) perspective. * * @see Multiway */ @@ -625,12 +1127,9 @@ Crafty.c("Fourway", { * @sign public this .fourway([Number speed]) * @param speed - The speed of motion in pixels per second. * - * Constructor to initialize the speed. Component will listen for key events and move the entity appropriately. - * This includes `Up Arrow`, `Right Arrow`, `Down Arrow`, `Left Arrow` as well as `W`, `A`, `S`, `D`. - * - * The key presses will move the entity in that direction by the speed passed in the argument. - * - * @see Multiway + * Constructor to initialize the speed. + * Component will listen for key events and move the entity + * in the respective direction by the speed passed in the argument. */ fourway: function (speed) { this.multiway(speed || this._speed, { @@ -653,11 +1152,14 @@ Crafty.c("Fourway", { /**@ * #Twoway * @category Controls - * @trigger NewDirection - When entity has changed direction due to velocity on either x or y axis a NewDirection event is triggered. The event is triggered once, if direction is different from last frame. - { x: -1 | 0 | 1, y: -1 | 0 | 1 } - New direction - * @trigger Moved - When entity has moved due to velocity/acceleration on either x or y axis a Moved event is triggered. If the entity has moved on both axes for diagonal movement the event is triggered twice. - { axis: 'x' | 'y', oldValue: Number } - Old position - * @trigger CheckJumping - When entity is about to jump. This event is triggered with the object the entity is about to jump from (if it exists). Third parties can respond to this event and enable the entity to jump. + * @kind Component + * + * Move an entity left or right using the `Left Arrow`, `Right Arrow` keys or `D` and `A` + * and make it jump using `Up Arrow` or `W`. + * Simulates jumping and falling when used with the `Gravity` component. * - * Move an entity left or right using the arrow keys or `D` and `A` and jump using up arrow or `W`. + * This component is a thin wrapper around the `Multiway` and `Jumper` components and sets the appropriate key bindings. + * It is a well suited for side-scrolling platformer type games. * * @see Multiway, Jumper */ @@ -674,19 +1176,15 @@ Crafty.c("Twoway", { * @param speed - A speed in pixels per second * @param jumpSpeed - Vertical jump speed in pixels per second * - * Constructor to initialize the speed and power of jump. Component will - * listen for key events and move the entity appropriately. This includes - * `Up Arrow`, `Right Arrow`, `Left Arrow` as well as `W`, `A`, `D`. Used with the - * `gravity` component to simulate jumping. - * - * The key presses will move the entity in that direction by the speed passed in - * the argument. Pressing the `Up Arrow` or `W` will cause the entity to jump. - * - * @see Multiway, Jumper + * Constructor to initialize the speed and power of jump. + * Component will listen for key events and move the entity + * in the respective direction by the speed passed in the argument. + * Pressing the jump key will cause the entity to jump with the supplied power. */ twoway: function (speed, jumpSpeed) { - - this.multiway(speed || this._speed, { + // Set multiway with horizontal speed only + var hSpeed = speed || this._speed; + this.multiway({x: hSpeed}, { RIGHT_ARROW: 0, LEFT_ARROW: 180, D: 0, @@ -704,7 +1202,7 @@ Crafty.c("Twoway", { } }); -},{"../core/core.js":7}],3:[function(require,module,exports){ +},{"../core/core.js":8}],4:[function(require,module,exports){ var Crafty = require('../core/core.js'); @@ -712,6 +1210,7 @@ Crafty.extend({ /**@ * #Crafty.device * @category Misc + * @kind Property * * Methods relating to devices such as tablets or phones */ @@ -790,6 +1289,8 @@ Crafty.extend({ /**@ * #Crafty.device.deviceOrientation * @comp Crafty.device + * @kind Method + * * @sign public Crafty.device.deviceOrientation(Function callback) * @param callback - Callback method executed once as soon as device orientation is change * @@ -829,6 +1330,8 @@ Crafty.extend({ /**@ * #Crafty.device.deviceMotion * @comp Crafty.device + * @kind Method + * * @sign public Crafty.device.deviceMotion(Function callback) * @param callback - Callback method executed once as soon as device motion is change * @@ -865,37 +1368,71 @@ Crafty.extend({ } }); -},{"../core/core.js":7}],4:[function(require,module,exports){ +},{"../core/core.js":8}],5:[function(require,module,exports){ var Crafty = require('../core/core.js'), document = window.document; Crafty.extend({ over: null, //object mouseover, waiting for out mouseObjs: 0, - mousePos: {}, - lastEvent: null, + mousePos: {}, touchObjs: 0, - selected: false, + /**@ + * #Crafty.lastEvent + * @category Input + * @kind Property + * Check which mouse event occured most recently (useful for determining mouse position in every frame). + * + * The native [`MouseEvent`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent) is augmented with additional properties. + * @example + * ~~~ + * // (x,y) coordinates of newest mouse event in web-browser (screen) space + * Crafty.lastEvent.clientX + * Crafty.lastEvent.clientY + * + * //(x,y) coordinates of newest mouse event in world (default viewport) space + * Crafty.lastEvent.realX + * Crafty.lastEvent.realY + * + * // Normalized mouse button according to Crafty.mouseButtons: + * // Crafty.mouseButtons.LEFT, Crafty.mouseButtons.RIGHT or Crafty.mouseButtons.MIDDLE + * Crafty.lastEvent.mouseButton + * ~~~ + * @see Mouse, Crafty.mouseButtons, Crafty.mouseDispatch + */ + + lastEvent: null, /**@ * #Crafty.keydown * @category Input - * Check which keys (referred by Unicode values) are currently down. + * @kind Property + * Check which keys (referred by `Crafty.keys` key codes) are currently down. * * @example * ~~~ - * Crafty.c("Keyboard", { - * isDown: function (key) { - * if (typeof key === "string") { - * key = Crafty.keys[key]; - * } - * return !!Crafty.keydown[key]; - * } - * }); + * // is "Shift" currently pressed? + * var shiftDown = !!Crafty.keydown[Crafty.keys.SHIFT]; * ~~~ - * @see Keyboard, Crafty.keys + * @see Keyboard, Crafty.keys, Crafty.keyboardDispatch + */ + keydown: {}, + + /**@ + * #Crafty.selected + * @category Input + * @kind Property + * @trigger CraftyFocus - is triggered when Crafty's stage gets selected + * @trigger CraftyBlur - is triggered when Crafty's stage is no longer selected + * + * Check whether Crafty's stage (`Crafty.stage.elem`) is currently selected. + * + * After a click occurs inside Crafty's stage, this property is set to `true`. + * After a click occurs outside Crafty's stage, this property is set to `false`. + * + * @see Crafty.stage#Crafty.stage.elem */ - keydown: {}, + selected: false, detectBlur: function (e) { var selected = ((e.clientX > Crafty.stage.x && e.clientX < Crafty.stage.x + Crafty.viewport.width) && @@ -904,17 +1441,18 @@ Crafty.extend({ if (!Crafty.selected && selected) { Crafty.trigger("CraftyFocus"); } - + if (Crafty.selected && !selected) { Crafty.trigger("CraftyBlur"); } - + Crafty.selected = selected; }, /**@ * #Crafty.multitouch * @category Input + * @kind Method * @sign public this .multitouch(Boolean bool) * @param bool - Turns multitouch on and off. The initial state is off (false). * @@ -946,84 +1484,72 @@ Crafty.extend({ * Crafty.log("multitouch is "+Crafty.multitouch()); * ~~~ * @see Crafty.touchDispatch + * @see Touch */ multitouch: function (bool) { if (typeof bool !== "boolean") return this._touchHandler.multitouch; this._touchHandler.multitouch = bool; }, - - resetKeyDown: function() { + + resetKeyDown: function () { // Tell all the keys they're no longer held down for (var k in Crafty.keys) { - if (Crafty.keydown[Crafty.keys[k]]) { - this.trigger("KeyUp", { - key: Crafty.keys[k] - }); - } + if (Crafty.keydown[Crafty.keys[k]]) { + this.trigger("KeyUp", { + key: Crafty.keys[k] + }); + } } - + Crafty.keydown = {}; }, - + /**@ * #Crafty.mouseDispatch * @category Input + * @private + * @kind Method * - * Internal method which dispatches mouse events received by Crafty (crafty.stage.elem). - * The mouse events get dispatched to the closest entity to the source of the event (if available). + * Internal method which dispatches mouse events received by Crafty. * - * You can read more about the MouseEvent, which is the parameter passed to the callback. - * https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent + * This method processes a native [`MouseEvent`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent) received by `Crafty.stage.elem`, + * augments it with additional properties and + * dispatches it to the closest (visible & `Mouse`-enhanced) entity to the source of the event (if available). * - * This method also sets a global property Crafty.lastEvent, which holds the most recent event that - * occured (useful for determining mouse position in every frame). - * - * ~~~ - * @example - * ~~~ - * var newestX = Crafty.lastEvent.realX, - * newestY = Crafty.lastEvent.realY; - * ~~~ - * - * Notable properties of a MouseEvent e: - * ~~~ - * //(x,y) coordinates of mouse event in web browser screen space - * e.clientX, e.clientY - * //(x,y) coordinates of mouse event in world/viewport space - * e.realX, e.realY - * // Normalized mouse button according to Crafty.mouseButtons - * e.mouseButton - * ~~~ - * @see Crafty.touchDispatch - * @see Crafty.multitouch + * This method also updates `Crafty.lastEvent`. + * + * @see Crafty.mouseButtons, Crafty.lastEvent, Mouse */ + mouseButtonsDown: { }, mouseDispatch: function (e) { if (!Crafty.mouseObjs) return; Crafty.lastEvent = e; - var maxz = -1, - tar = e.target ? e.target : e.srcElement, + var tar = e.target ? e.target : e.srcElement, closest, - q, - i = 0, - l, pos = Crafty.domHelper.translate(e.clientX, e.clientY), - x, y, - dupes = {}, - type = e.type; + type = e.type; //Normalize button according to http://unixpapa.com/js/mouse.html if (typeof e.which === 'undefined') { - e.mouseButton = (e.button < 2) ? Crafty.mouseButtons.LEFT : ((e.button == 4) ? Crafty.mouseButtons.MIDDLE : Crafty.mouseButtons.RIGHT); + e.mouseButton = (e.button < 2) ? Crafty.mouseButtons.LEFT : ((e.button === 4) ? Crafty.mouseButtons.MIDDLE : Crafty.mouseButtons.RIGHT); } else { - e.mouseButton = (e.which < 2) ? Crafty.mouseButtons.LEFT : ((e.which == 2) ? Crafty.mouseButtons.MIDDLE : Crafty.mouseButtons.RIGHT); + e.mouseButton = (e.which < 2) ? Crafty.mouseButtons.LEFT : ((e.which === 2) ? Crafty.mouseButtons.MIDDLE : Crafty.mouseButtons.RIGHT); } - e.realX = x = Crafty.mousePos.x = pos.x; - e.realY = y = Crafty.mousePos.y = pos.y; + // Set the mouse position based on standard viewport coordinates + Crafty.mousePos.x = pos.x; + Crafty.mousePos.y = pos.y; - closest = Crafty.findClosestEntityByComponent("Mouse", x, y, tar); + // Track button state + if (type === "mousedown") { + this.mouseButtonsDown[e.mouseButton] = true; + } + if (type === "mouseup") { + delete this.mouseButtonsDown[e.mouseButton]; + } + closest = Crafty.findPointerEventTargetByComponent("Mouse", e, tar); //found closest object to mouse if (closest) { //click must mousedown and out on tile @@ -1031,14 +1557,14 @@ Crafty.extend({ closest.trigger("MouseDown", e); } else if (type === "mouseup") { closest.trigger("MouseUp", e); - } else if (type == "dblclick") { + } else if (type === "dblclick") { closest.trigger("DoubleClick", e); - } else if (type == "click") { + } else if (type === "click") { closest.trigger("Click", e); } else if (type === "mousemove") { closest.trigger("MouseMove", e); if (this.over !== closest) { //if new mousemove, it is over - if (this.over) { + if (this.over) { this.over.trigger("MouseOut", e); //if over wasn't null, send mouseout this.over = null; } @@ -1055,9 +1581,20 @@ Crafty.extend({ Crafty.viewport.mouselook('start', e); } else if (type === "mousemove") { Crafty.viewport.mouselook('drag', e); - } else if (type == "mouseup") { + } else if (type === "mouseup") { Crafty.viewport.mouselook('stop'); } + + // If nothing in particular was clicked, the controls system should get fed the event + if (type === "mousedown") { + Crafty.s("Controls").trigger("MouseDown", e); + } else if (type === "mouseup") { + Crafty.s("Controls").trigger("MouseUp", e); + } else if (type === "dblclick") { + Crafty.s("Controls").trigger("DoubleClick", e); + } else if (type === "click") { + Crafty.s("Controls").trigger("Click", e); + } } if (type === "mousemove") { @@ -1070,6 +1607,8 @@ Crafty.extend({ /**@ * #Crafty.touchDispatch * @category Input + * @kind Method + * @private * * Internal method which dispatches touch events received by Crafty (crafty.stage.elem). * The touch events get dispatched to the closest entity to the source of the event (if available). @@ -1091,11 +1630,11 @@ Crafty.extend({ * http://www.w3.org/TR/touch-events/#dfn-active-touch-point * * @see Crafty.multitouch - * @see Crafty.mouseDispatch + * @see Touch */ touchDispatch: function (e) { if (!Crafty.touchObjs && !Crafty.mouseObjs) return; - + if (this._touchHandler.multitouch) switch (e.type) { case "touchstart": @@ -1121,22 +1660,19 @@ Crafty.extend({ e.returnValue = false; } }, - + _touchHandler: { fingers: [], // keeps track of touching fingers multitouch: false, - + handleStart: function (e) { var touches = e.changedTouches; for (var i = 0, l = touches.length; i < l; i++) { var idx = false, - pos = Crafty.domHelper.translate(touches[i].clientX, touches[i].clientY), - tar = e.target ? e.target : e.srcElement, - x, y, closest; - touches[i].realX = x = pos.x; - touches[i].realY = y = pos.y; - closest = this.findClosestTouchEntity(x, y, tar); - + tar = e.target ? e.target : e.srcElement, + closest; + closest = this.findClosestTouchEntity(touches[i], tar); + if (closest) { closest.trigger("TouchStart", touches[i]); // In case the entity was already being pressed, get the finger index @@ -1151,71 +1687,67 @@ Crafty.extend({ } } }, - + handleMove: function (e) { var touches = e.changedTouches; for (var i = 0, l = touches.length; i < l; i++) { var idx = this.fingerDownIndexById(touches[i].identifier), - pos = Crafty.domHelper.translate(touches[i].clientX, touches[i].clientY), - tar = e.target ? e.target : e.srcElement, - x, y, closest; - touches[i].realX = x = pos.x; - touches[i].realY = y = pos.y; - closest = this.findClosestTouchEntity(x, y, tar); - + tar = e.target ? e.target : e.srcElement; + var closest = this.findClosestTouchEntity(touches[i], tar); + if (idx >= 0) { - if(typeof this.fingers[idx].entity !== "undefined") - if (this.fingers[idx].entity == closest) { - this.fingers[idx].entity.trigger("TouchMove", touches[i]); + var finger = this.fingers[idx]; + if(typeof finger.entity !== "undefined") + if (finger.entity === closest) { + finger.entity.trigger("TouchMove", touches[i]); } else { if (typeof closest === "object") closest.trigger("TouchStart", touches[i]); - this.fingers[idx].entity.trigger("TouchEnd"); + finger.entity.trigger("TouchEnd"); } - this.fingers[idx].entity = closest; - this.fingers[idx].realX = x; - this.fingers[idx].realY = y; + finger.entity = closest; + finger.realX = touches[i].realX; + finger.realY = touches[i].realY; } } }, - + handleEnd: function (e) { var touches = e.changedTouches, - eventName = e.type == "touchcancel" ? "TouchCancel" : "TouchEnd"; + eventName = e.type === "touchcancel" ? "TouchCancel" : "TouchEnd"; for (var i = 0, l = touches.length; i < l; i++) { var idx = this.fingerDownIndexById(touches[i].identifier); - + if (idx >= 0) { - if (this.fingers[idx].entity) - this.fingers[idx].entity.trigger(eventName); - this.fingers.splice(idx, 1); + if (this.fingers[idx].entity) + this.fingers[idx].entity.trigger(eventName); + this.fingers.splice(idx, 1); } } }, - + setTouch: function (touch, entity) { return { identifier: touch.identifier, realX: touch.realX, realY: touch.realY, entity: entity }; }, - - findClosestTouchEntity: function (x, y, tar) { - return Crafty.findClosestEntityByComponent("Touch", x, y, tar); + + findClosestTouchEntity: function (touchEvent, tar) { + return Crafty.findPointerEventTargetByComponent("Touch", touchEvent, tar); }, - - fingerDownIndexById: function(idToFind) { + + fingerDownIndexById: function (idToFind) { for (var i = 0, l = this.fingers.length; i < l; i++) { var id = this.fingers[i].identifier; - - if (id == idToFind) { - return i; - } + if (id === idToFind) { + return i; } + } return -1; }, - - fingerDownIndexByEntity: function(entityToFind) { + + fingerDownIndexByEntity: function (entityToFind) { for (var i = 0, l = this.fingers.length; i < l; i++) { var ent = this.fingers[i].entity; - - if (ent == entityToFind) { + + if (ent === entityToFind) { return i; } } @@ -1223,7 +1755,7 @@ Crafty.extend({ }, mimicMouse: function (e) { - var type, + var type, first, lastEvent = Crafty.lastEvent; if (e.type === "touchstart") type = "mousedown"; else if (e.type === "touchmove") type = "mousemove"; @@ -1237,107 +1769,113 @@ Crafty.extend({ } var simulatedEvent = document.createEvent("MouseEvent"); simulatedEvent.initMouseEvent(type, true, true, window, 1, - first.screenX, - first.screenY, - first.clientX, - first.clientY, - false, false, false, false, 0, e.relatedTarget + first.screenX, + first.screenY, + first.clientX, + first.clientY, + false, false, false, false, 0, e.relatedTarget ); first.target.dispatchEvent(simulatedEvent); // trigger click when it should be triggered - if (lastEvent !== null && lastEvent.type == 'mousedown' && type == 'mouseup') { + if (lastEvent !== null && lastEvent.type === 'mousedown' && type === 'mouseup') { type = 'click'; simulatedEvent = document.createEvent("MouseEvent"); simulatedEvent.initMouseEvent(type, true, true, window, 1, - first.screenX, - first.screenY, - first.clientX, - first.clientY, - false, false, false, false, 0, e.relatedTarget + first.screenX, + first.screenY, + first.clientX, + first.clientY, + false, false, false, false, 0, e.relatedTarget ); first.target.dispatchEvent(simulatedEvent); } }, }, - + /**@ - * #Crafty.findClosestEntityByComponent + * #Crafty.findPointerEventTargetByComponent * @category Input + * @kind Method + * @private * - * @sign public this .findClosestEntityByComponent(String comp, Number x, Number y[, Object target]) - * Finds closest entity with certain component at given coordinates. + * @sign public this .findPointerEventTargetByComponent(String comp, Event e[, Object target]) + * Finds closest entity with certain component at a given event. * @param comp - Component name - * @param x - `x` position where to look for entities - * @param y - `y` position where to look for entities + * @param e - The pointer event, which will be modifed to add `realX` and `realY` properties * @param target - Target element wherein to look for entities * * This method is used internally by the .mouseDispatch and .touchDispatch methods, but can be used otherwise for * Canvas entities. * - * Finds the top most entity (with the highest z) with a given component at a given point (x, y). + * Finds the top most entity (with the highest z) with a given component at a given point (x, y) associated with the event. * For having a detection area specified for the enity, add the AreaMap component to the entity expected to be found. * * The 'target' argument is only meant to be used by .mouseDispatch and touchDispatch; defaults to Crafty.stage.elem, * thus using this function directly is only worth anything for canvas entities. * - * Returns the found entity, or undefined if no entity was found. + * Returns the found entity, or undefined if no entity was found. + * Updates the event object to have two additional properties, `realX` and `realY`, which correspond to the point in the Crafty layer that the event targeted. * - * @example - * ~~~ - * var coords = { x: 455, y: 267 }, - * closestText = Crafty.findClosestEntityByComponent("Text", coords.x, coords.y); - * ~~~ */ - findClosestEntityByComponent: function (comp, x, y, target) { + findPointerEventTargetByComponent: function (comp, e, target) { var tar = target ? target : Crafty.stage.elem, - closest, q, l, i = 0, maxz = -1, dupes = {}; - + closest, current, q, l, i, pos, layerPos, maxz = -Infinity; + var x = e.clientX; + var y = e.clientY; + //if it's a DOM element with component we are done - if (tar.nodeName != "CANVAS") { - while (typeof (tar.id) != 'string' && tar.id.indexOf('ent') == -1) { + if (tar.nodeName !== "CANVAS") { + while (typeof (tar.id) !== 'string' && tar.id.indexOf('ent') === -1) { tar = tar.parentNode; } var ent = Crafty(parseInt(tar.id.replace('ent', ''), 10)); - if (ent.__c[comp] && ent.isAt(x, y)){ + pos = Crafty.domHelper.translate(x, y, ent._drawLayer); + if (ent.__c[comp] && ent.isAt(pos.x, pos.y)) { closest = ent; + layerPos = pos; } } - //else we search for an entity with component - if (!closest) { - q = Crafty.map.search({ - _x: x, - _y: y, - _w: 1, - _h: 1 - }, false); - - for (l = q.length; i < l; ++i) { - - if (!q[i].__c[comp] || !q[i]._visible){ continue; } - - var current = q[i], - flag = false; - //weed out duplicates - if (dupes[current[0]]){ continue; } - else dupes[current[0]] = true; - - if (current.mapArea) { - if (current.mapArea.containsPoint(x, y)) { - flag = true; - } - } else if (current.isAt(x, y)) flag = true; + //else we search for an entity with component + if (!closest) { - if (flag && (current._z >= maxz || maxz === -1)) { - //if the Z is the same, select the closest GUID - if (current._z === maxz && current[0] < closest[0]) { - continue; + // Loop through each layer + for (var layerIndex in Crafty._drawLayers) { + var layer = Crafty._drawLayers[layerIndex]; + + // Skip a layer if it has no entities listening for pointer events + if (layer._pointerEntities <= 0) continue; + + // Get the position in this layer + pos = Crafty.domHelper.translate(x, y, layer); + q = Crafty.map.search({ + _x: pos.x, + _y: pos.y, + _w: 1, + _h: 1 + }, false); + + for (i = 0, l = q.length; i < l; ++i) { + current = q[i]; + if (current._visible && current._drawLayer === layer && current._globalZ > maxz && + current.__c[comp] && current.isAt(pos.x, pos.y)) { + maxz = current._globalZ; + closest = current; + layerPos = pos; } - maxz = current._z; - closest = current; } } } + + // If the pointer event isn't related to a specific layer, + // find the Crafty position in the default coordinate set + if (!layerPos) { + layerPos = Crafty.domHelper.translate(x, y); + } + + // Update the event coordinates and return the event target + e.realX = layerPos.x; + e.realY = layerPos.y; return closest; }, @@ -1345,73 +1883,113 @@ Crafty.extend({ /**@ * #Crafty.mouseWheelDispatch * @category Input - * Mouse wheel event triggered by Crafty. + * @kind Method + * @private * + * Internal method which dispatches mouse wheel events received by Crafty. * @trigger MouseWheelScroll - is triggered when mouse is scrolled on stage - { direction: +1 | -1} - Scroll direction (up | down) * - * Internal method which dispatches mouse wheel events received by Crafty (crafty.stage.elem). - * The mouse wheel events get dispatched to Crafty, as well as all entities. - * - * The native event parameter is passed to the callback. - * You can read more about the native `mousewheel` event (all browsers except Firefox) https://developer.mozilla.org/en-US/docs/Web/Events/mousewheel - * or the native `DOMMouseScroll` event (Firefox only) https://developer.mozilla.org/en-US/docs/Web/Events/DOMMouseScroll . + * This method processes a native [`mousewheel` event](https://developer.mozilla.org/en-US/docs/Web/Events/mousewheel) (all browsers except Firefox) + * or a native [`DOMMouseScroll` event](https://developer.mozilla.org/en-US/docs/Web/Events/DOMMouseScroll) (Firefox only) received by `Crafty.stage.elem`, + * augments it with the additional `.direction` property (see below) and dispatches it to the global Crafty object and thus to every entity. * * Note that the wheel delta properties of the event vary in magnitude across browsers, thus it is recommended to check for `.direction` instead. - * The `.direction` equals `+1` if wheel was scrolled up, `-1` if wheel was scrolled down. - * See http://stackoverflow.com/questions/5527601/normalizing-mousewheel-speed-across-browsers . + * The `.direction` equals `+1` if wheel was scrolled up, `-1` if wheel was scrolled down + * (see [details](http://stackoverflow.com/questions/5527601/normalizing-mousewheel-speed-across-browsers)). * * @example + * Zoom the viewport (camera) in response to mouse scroll events. * ~~~ * Crafty.bind("MouseWheelScroll", function(evt) { * Crafty.viewport.scale(Crafty.viewport._scale * (1 + evt.direction * 0.1)); * }); * ~~~ + * + * @example + * Interactive, map-like zooming of the viewport (camera) in response to mouse scroll events. + * ~~~ + * // sign public void zoomTowards(Number amt, Number posX, Number posY, Number time[, String|function easingFn]) + * // param Number amt - amount to zoom in on the target by (eg. `2`, `4`, `0.5`) + * // param Number posX - the x coordinate to zoom towards + * // param Number posY - the y coordinate to zoom towards + * // param Number time - the duration in ms of the entire zoom operation + * // param easingFn - A string or custom function specifying an easing. + * // (Defaults to linear behavior.) + * // See `Crafty.easing` for more information. + * // + * // Zooms the camera towards a given point, preserving the current center. + * // `amt > 1` will bring the camera closer to the subject, + * // `amt < 1` will bring it farther away, + * // `amt = 0` will reset to the default zoom level. + * // Zooming is multiplicative. To reset the zoom amount, pass `0`. + * // + * // + * // // Make the entities appear twice as large by zooming in towards (100,100) over the duration of 3 seconds using linear easing behavior + * // zoomTowards(2, 100, 100, 3000); + * // + * // + * function zoomTowards (amt, posX, posY, time, easingFn) { + * var scale = Crafty.viewport._scale, + * // current viewport center + * centX = -Crafty.viewport._x + Crafty.viewport._width / 2 / scale, + * centY = -Crafty.viewport._y + Crafty.viewport._height / 2 / scale, + * // direction vector from viewport center to position + * deltaX = posX - centX, + * deltaY = posY - centY; + * var f = amt - 1; + * + * Crafty.viewport.zoom(amt, centX + deltaX * f, centY + deltaY * f, time, easingFn); + * } + * + * // don't restrict panning of viewport in any way + * Crafty.viewport.clampToEntities = false; + * + * // enable panning of viewport by dragging the mouse + * Crafty.viewport.mouselook(true); + * + * // enable interactive map-like zooming by scrolling the mouse + * Crafty.bind("MouseWheelScroll", function (evt) { + * var pos = Crafty.domHelper.translate(evt.clientX, evt.clientY); + * zoomTowards(1 + evt.direction/10, pos.x, pos.y, 5); + * }); + * ~~~ */ - mouseWheelDispatch: function(e) { + mouseWheelDispatch: function (e) { e.direction = (e.detail < 0 || e.wheelDelta > 0) ? 1 : -1; Crafty.trigger("MouseWheelScroll", e); - }, + }, /**@ - * #KeyboardEvent + * #Crafty.keyboardDispatch * @category Input - * Keyboard Event triggered by Crafty Core - * @trigger KeyDown - is triggered for each entity when the DOM 'keydown' event is triggered. - * @trigger KeyUp - is triggered for each entity when the DOM 'keyup' event is triggered. + * @kind Method + * @private + * + * Internal method which dispatches keyboard events received by Crafty. + * @trigger KeyDown - is triggered for each entity when the DOM 'keydown' event is triggered. - { key: `Crafty.keys` keyCode (Number), originalEvent: original KeyboardEvent } - Crafty's KeyboardEvent + * @trigger KeyUp - is triggered for each entity when the DOM 'keyup' event is triggered. - { key: `Crafty.keys` keyCode (Number), originalEvent: original KeyboardEvent } - Crafty's KeyboardEvent + * + * This method processes a native [`KeyboardEvent`](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent) received by `window.document`, + * wraps it in a custom event object (for cross-browser compatibility) and dispatches it to the global Crafty object and thus to every entity. + * + * This method also updates `Crafty.keydown`. * * @example * ~~~ - * Crafty.e("2D, DOM, Color") - * .attr({x: 100, y: 100, w: 50, h: 50}) - * .color("red") - * .bind('KeyDown', function(e) { - * if(e.key == Crafty.keys.LEFT_ARROW) { - * this.x = this.x-1; - * } else if (e.key == Crafty.keys.RIGHT_ARROW) { - * this.x = this.x+1; - * } else if (e.key == Crafty.keys.UP_ARROW) { - * this.y = this.y-1; - * } else if (e.key == Crafty.keys.DOWN_ARROW) { - * this.y = this.y+1; + * Crafty.bind('KeyDown', function(e) { + * if (e.key === Crafty.keys.LEFT_ARROW) { + * Crafty.viewport.x++; + * } else if (e.key === Crafty.keys.RIGHT_ARROW) { + * Crafty.viewport.x--; + * } else if (e.key === Crafty.keys.UP_ARROW) { + * Crafty.viewport.y++; + * } else if (e.key === Crafty.keys.DOWN_ARROW) { + * Crafty.viewport.y--; * } * }); * ~~~ * - * @see Crafty.keys - */ - - /**@ - * #Crafty.eventObject - * @category Input - * - * Event Object used in Crafty for cross browser compatibility - */ - - /**@ - * #.key - * @comp Crafty.eventObject - * - * Unicode of the key pressed + * @see Crafty.keys, Crafty.keydown, Keyboard */ keyboardDispatch: function (e) { // Use a Crafty-standard event object to avoid cross-browser issues @@ -1441,7 +2019,7 @@ Crafty.extend({ //prevent bubbling up for all keys except backspace and F1-F12. //Among others this prevent the arrow keys from scrolling the parent page //of an iframe hosting the game - if (Crafty.selected && !(e.key == 8 || e.key >= 112 && e.key <= 135)) { + if (Crafty.selected && !(e.key === 8 || e.key >= 112 && e.key <= 135)) { if (original.stopPropagation) original.stopPropagation(); else original.cancelBubble = true; @@ -1513,8 +2091,11 @@ Crafty._preBind("CraftyStop", function () { /**@ * #Mouse * @category Input + * @kind Component * - * Provides the entity with mouse related events + * Provides the entity with mouse related events. + * + * If you do not add this component, mouse events will not be triggered on the entity. * * @trigger MouseOver - when the mouse enters - MouseEvent * @trigger MouseOut - when the mouse leaves - MouseEvent @@ -1524,16 +2105,21 @@ Crafty._preBind("CraftyStop", function () { * @trigger DoubleClick - when the user double clicks - MouseEvent * @trigger MouseMove - when the mouse is over and moves - MouseEvent * - * If you do not add this component, mouse events will not be triggered on an entity. - * - * You can read more about the MouseEvent, which is the parameter passed to the callback. - * https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent + * The event callbacks are triggered with a native [`MouseEvent`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent) parameter, + * which is further augmented with additional properties: + * ~~~ + * //(x,y) coordinates of mouse event in web-browser (screen) space + * e.clientX + * e.clientY * - * Crafty will add the mouseButton property to MouseEvents that match one of + * //(x,y) coordinates of mouse event in world (default viewport) space + * e.realX + * e.realY * - * - Crafty.mouseButtons.LEFT - * - Crafty.mouseButtons.RIGHT - * - Crafty.mouseButtons.MIDDLE + * // Normalized mouse button according to Crafty.mouseButtons: + * // Crafty.mouseButtons.LEFT, Crafty.mouseButtons.RIGHT or Crafty.mouseButtons.MIDDLE + * e.mouseButton + * ~~~ * * @note If you're targeting mobile, you should know that by default Crafty turns touch events into mouse events, * making mouse dependent components work with touch. However, if you need multitouch, you'll have @@ -1553,10 +2139,10 @@ Crafty._preBind("CraftyStop", function () { * Crafty.log("Clicked right button"); * }) * ~~~ + * @see Crafty.mouseButtons * @see Crafty.mouseDispatch * @see Crafty.multitouch * @see Crafty.touchDispatch - * @see Crafty.mouseButtons */ Crafty.c("Mouse", { init: function () { @@ -1571,6 +2157,7 @@ Crafty.c("Mouse", { /**@ * #Touch * @category Input + * @kind Component * Provides the entity with touch related events * @trigger TouchStart - when entity is touched - TouchPoint * @trigger TouchMove - when finger is moved over entity - TouchPoint @@ -1603,8 +2190,6 @@ Crafty.c("Mouse", { * ~~~ * @see Crafty.multitouch * @see Crafty.touchDispatch - * @see Crafty.mouseDispatch - * @see Crafty.mouseButtons */ Crafty.c("Touch", { init: function () { @@ -1619,21 +2204,40 @@ Crafty.c("Touch", { /**@ * #AreaMap * @category Input + * @kind Component + * * Component used by Mouse and Touch. * Can be added to other entities for use with the Crafty.findClosestEntityByComponent method. * - * @see Crafty.mouseDispatch - * @see Crafty.touchDispatch - * @see Crafty.mouseButtons + * @see Button * @see Crafty.polygon */ Crafty.c("AreaMap", { init: function () { + if (this.has("Renderable") && this._drawLayer) { + this._drawLayer._pointerEntities++; + } + }, + + remove: function () { + if (this.has("Renderable") && this._drawLayer) { + this._drawLayer._pointerEntities--; + } + }, + + events: { + "LayerAttached": function (layer) { + layer._pointerEntities++; + }, + "LayerDetached": function (layer) { + layer._pointerEntities--; + } }, /**@ * #.areaMap * @comp AreaMap + * @kind Method * * @trigger NewAreaMap - when a new areaMap is assigned - Crafty.polygon * @@ -1690,9 +2294,13 @@ Crafty.c("AreaMap", { /**@ * #Button * @category Input + * @kind Component + * * Provides the entity with touch or mouse functionality, depending on whether this is a pc * or mobile device, and also on multitouch configuration. - * + * + * @see Mouse + * @see Touch * @see Crafty.multitouch */ Crafty.c("Button", { @@ -1705,6 +2313,8 @@ Crafty.c("Button", { /**@ * #MouseDrag * @category Input + * @kind Component + * * Provides the entity with drag and drop mouse events. * @trigger Dragging - is triggered each frame the entity is being dragged - MouseEvent * @trigger StartDrag - is triggered when dragging begins - MouseEvent @@ -1715,13 +2325,12 @@ Crafty.c("Button", { Crafty.c("MouseDrag", { _dragging: false, - //Note: the code is not tested with zoom, etc., that may distort the direction between the viewport and the coordinate on the canvas. init: function () { this.requires("Mouse"); this.bind("MouseDown", this._ondown); }, - remove: function() { + remove: function () { this.unbind("MouseDown", this._ondown); }, @@ -1747,6 +2356,8 @@ Crafty.c("MouseDrag", { /**@ * #.startDrag * @comp MouseDrag + * @kind Method + * * @sign public this .startDrag(void) * * Make the entity produce drag events, essentially making the entity follow the mouse positions. @@ -1768,6 +2379,8 @@ Crafty.c("MouseDrag", { /**@ * #.stopDrag * @comp MouseDrag + * @kind Method + * * @sign public this .stopDrag(void) * * Stop the entity from producing drag events, essentially reproducing the drop. @@ -1790,22 +2403,42 @@ Crafty.c("MouseDrag", { /**@ * #Keyboard * @category Input + * @kind Component * - * Give entities keyboard events (`Keydown` and `Keyup`). - * - * In particular, changes to the key state are broadcasted by `KeyboardEvent`s; interested entities can bind to these events. + * Provides entity with keyboard events. + * @trigger KeyDown - is triggered for each entity when the DOM 'keydown' event is triggered. - { key: `Crafty.keys` keyCode (Number), originalEvent: original KeyboardEvent } - Crafty's KeyboardEvent + * @trigger KeyUp - is triggered for each entity when the DOM 'keyup' event is triggered. - { key: `Crafty.keys` keyCode (Number), originalEvent: original KeyboardEvent } - Crafty's KeyboardEvent * - * The current state (pressed/released) of a key can also be queried using the `.isDown` method. + * In addition to binding to these events, the current state (pressed/released) of a key can also be queried using the `.isDown` method. * - * All available key codes are described in `Crafty.keys`. + * @example + * ~~~ + * Crafty.e("2D, DOM, Color, Keyboard") + * .attr({x: 100, y: 100, w: 50, h: 50}) + * .color("red") + * .bind('KeyDown', function(e) { + * if (e.key == Crafty.keys.LEFT_ARROW) { + * this.x = this.x-1; + * } else if (e.key == Crafty.keys.RIGHT_ARROW) { + * this.x = this.x+1; + * } else if (e.key == Crafty.keys.UP_ARROW) { + * this.y = this.y-1; + * } else if (e.key == Crafty.keys.DOWN_ARROW) { + * this.y = this.y+1; + * } + * }); + * ~~~ * - * @see KeyboardEvent * @see Crafty.keys + * @see Crafty.keydown + * @see Crafty.keyboardDispatch */ Crafty.c("Keyboard", { /**@ * #.isDown * @comp Keyboard + * @kind Method + * * @sign public Boolean isDown(String keyName) * @param keyName - Name of the key to check. See `Crafty.keys`. * @sign public Boolean isDown(Number keyCode) @@ -1815,7 +2448,11 @@ Crafty.c("Keyboard", { * * @example * ~~~ - * entity.requires('Keyboard').bind('KeyDown', function () { if (this.isDown('SPACE')) jump(); }); + * ent.requires('Keyboard') + * .bind('EnterFrame', function() { + * if (this.isDown('SPACE')) + * this.y--; + * }); * ~~~ * * @see Crafty.keys @@ -1827,9 +2464,7 @@ Crafty.c("Keyboard", { return !!Crafty.keydown[key]; } }); - - -},{"../core/core.js":7}],5:[function(require,module,exports){ +},{"../core/core.js":8}],6:[function(require,module,exports){ var Crafty = require('../core/core.js'); @@ -1837,7 +2472,9 @@ Crafty.extend({ /**@ * #Crafty.keys * @category Input - * Object of key names and the corresponding key code. + * @kind Property + * + * Object of key names and the corresponding Unicode key code. * * ~~~ * BACKSPACE: 8, @@ -2031,6 +2668,8 @@ Crafty.extend({ /**@ * #Crafty.mouseButtons * @category Input + * @kind Property + * * An object mapping mouseButton names to the corresponding button ID. * In all mouseEvents, we add the `e.mouseButton` property with a value normalized to match e.button of modern webkit browsers: * @@ -2046,10 +2685,14 @@ Crafty.extend({ RIGHT: 2 } }); -},{"../core/core.js":7}],6:[function(require,module,exports){ +},{"../core/core.js":8}],7:[function(require,module,exports){ +var Crafty = require('../core/core.js'); + + /**@ * #Crafty.easing * @category Animation + * @kind Class * * * An object for tracking transitions. Typically used indirectly through "SpriteAnimation", "Tween", or viewport animations. @@ -2165,12 +2808,13 @@ easing.prototype = { }; module.exports = easing; -},{}],7:[function(require,module,exports){ +},{"../core/core.js":8}],8:[function(require,module,exports){ var version = require('./version'); /**@ * #Crafty * @category Core + * @kind CoreObject * * `Crafty` is both an object, and a function for selecting entities. * Its many methods and properties are discussed individually. @@ -2223,7 +2867,7 @@ var Crafty = function (selector) { }; // Internal variables var GUID, frame, components, entities, handlers, onloads, -slice, rlist, rspace, milliSecPerFrame; +slice, rlist, rspace; components = {}; // Map of components and their functions @@ -2245,6 +2889,8 @@ initState(); /**@ * #Crafty Core * @category Core + * @kind CoreObject + * * @trigger NewEntityName - After setting new name for entity - String - entity name * @trigger NewComponent - when a new component is added to the entity - String - Component * @trigger RemoveComponent - when a component is removed from the entity - String - Component @@ -2358,26 +3004,54 @@ Crafty.fn = Crafty.prototype = { /**@ * #.setName * @comp Crafty Core + * @kind Method + * * @sign public this .setName(String name) * @param name - A human readable name for debugging purposes. * + * Set a human readable name for debugging purposes. + * * @example * ~~~ - * this.setName("Player"); + * var ent = Crafty.e().setName("Player"); * ~~~ + * + * @see Crafty Core#.getName */ setName: function (name) { var entityName = String(name); - this._entityName = entityName; - this.trigger("NewEntityName", entityName); return this; }, + /**@ + * #.getName + * @comp Crafty Core + * @kind Method + * + * @sign public this .getName(String name) + * @returns A human readable name for debugging purposes. + * + * Get the human readable name for debugging purposes. + * + * @example + * ~~~ + * var ent = Crafty.e().setName("Player"); + * var name = ent.getName(); + * ~~~ + * + * @see Crafty Core#.setName + */ + getName: function (name) { + return this._entityName; + }, + /**@ * #.addComponent * @comp Crafty Core + * @kind Method + * * @sign public this .addComponent(String componentList) * @param componentList - A string of components to add separated by a comma `,` * @sign public this .addComponent(String Component1[, .., String ComponentN]) @@ -2449,6 +3123,8 @@ Crafty.fn = Crafty.prototype = { /**@ * #.toggleComponent * @comp Crafty Core + * @kind Method + * * @sign public this .toggleComponent(String ComponentList) * @param ComponentList - A string of components to add or remove separated by a comma `,` * @sign public this .toggleComponent(String Component1[, .., String componentN]) @@ -2510,6 +3186,8 @@ Crafty.fn = Crafty.prototype = { /**@ * #.requires * @comp Crafty Core + * @kind Method + * * @sign public this .requires(String componentList) * @param componentList - List of components that must be added * @@ -2531,6 +3209,8 @@ Crafty.fn = Crafty.prototype = { /**@ * #.removeComponent * @comp Crafty Core + * @kind Method + * * @sign public this .removeComponent(String Component[, soft]) * @param component - Component to remove * @param soft - Whether to soft remove it (defaults to `true`) @@ -2573,6 +3253,8 @@ Crafty.fn = Crafty.prototype = { /**@ * #.getId * @comp Crafty Core + * @kind Method + * * @sign public Number .getId(void) * @returns the ID of this entity. * @@ -2593,6 +3275,8 @@ Crafty.fn = Crafty.prototype = { /**@ * #.has * @comp Crafty Core + * @kind Method + * * @sign public Boolean .has(String component) * @param component - The name of the component to check * @returns `true` or `false` depending on if the @@ -2609,6 +3293,8 @@ Crafty.fn = Crafty.prototype = { /**@ * #.attr * @comp Crafty Core + * @kind Method + * * @trigger Change - when properties change - {key: value} * * @sign public this .attr(String property, Any value[, Boolean silent[, Boolean recursive]]) @@ -2768,6 +3454,8 @@ Crafty.fn = Crafty.prototype = { /**@ * #.toArray * @comp Crafty Core + * @kind Method + * * @sign public this .toArray(void) * * This method will simply return the found entities as an array of ids. To get an array of the actual entities, use `get()`. @@ -2780,6 +3468,8 @@ Crafty.fn = Crafty.prototype = { /**@ * #.timeout * @comp Crafty Core + * @kind Method + * @sign public this .timeout(Function callback, Number delay) * @param callback - Method to execute after given amount of milliseconds * @param delay - Amount of milliseconds to execute the method @@ -2809,6 +3499,8 @@ Crafty.fn = Crafty.prototype = { /**@ * #.bind * @comp Crafty Core + * @kind Method + * * @sign public this .bind(String eventName, Function callback) * @param eventName - Name of the event to bind to * @param callback - Method to execute when the event is triggered @@ -2860,6 +3552,8 @@ Crafty.fn = Crafty.prototype = { /**@ * #.uniqueBind * @comp Crafty Core + * @kind Method + * * @sign public Number .uniqueBind(String eventName, Function callback) * @param eventName - Name of the event to bind to * @param callback - Method to execute upon event triggered @@ -2878,6 +3572,8 @@ Crafty.fn = Crafty.prototype = { /**@ * #.one * @comp Crafty Core + * @kind Method + * * @sign public Number one(String eventName, Function callback) * @param eventName - Name of the event to bind to * @param callback - Method to execute upon event triggered @@ -2900,6 +3596,8 @@ Crafty.fn = Crafty.prototype = { /**@ * #.unbind * @comp Crafty Core + * @kind Method + * * @sign public this .unbind(String eventName[, Function callback]) * @param eventName - Name of the event to unbind * @param callback - Function to unbind @@ -2926,6 +3624,8 @@ Crafty.fn = Crafty.prototype = { /**@ * #.trigger * @comp Crafty Core + * @kind Method + * * @sign public this .trigger(String eventName[, Object data]) * @param eventName - Event to trigger * @param data - Arbitrary data that will be passed into every callback as an argument @@ -2938,7 +3638,7 @@ Crafty.fn = Crafty.prototype = { * The first argument is the event name to trigger and the optional * second argument is the arbitrary event data. This can be absolutely anything. * - * Unlike DOM events, Crafty events are exectued synchronously. + * Unlike DOM events, Crafty events are executed synchronously. */ trigger: function (event, data) { // To learn how the event system functions, see the comments for Crafty._callbackMethods @@ -2959,6 +3659,8 @@ Crafty.fn = Crafty.prototype = { /**@ * #.each * @comp Crafty Core + * @kind Method + * * @sign public this .each(Function method) * @param method - Method to call on each iteration * @@ -2992,6 +3694,8 @@ Crafty.fn = Crafty.prototype = { /**@ * #.get * @comp Crafty Core + * @kind Method + * * @sign public Array .get() * @returns An array of entities corresponding to the active selector * @@ -3039,6 +3743,8 @@ Crafty.fn = Crafty.prototype = { /**@ * #.clone * @comp Crafty Core + * @kind Method + * * @sign public Entity .clone(void) * @returns Cloned entity of the current entity * @@ -3055,7 +3761,7 @@ Crafty.fn = Crafty.prototype = { clone.addComponent(comp); } for (prop in this) { - if (prop != "0" && prop != "_global" && prop != "_changed" && typeof this[prop] != "function" && typeof this[prop] != "object") { + if (prop !== "0" && prop !== "_global" && prop !== "_changed" && typeof this[prop] !== "function" && typeof this[prop] !== "object") { clone[prop] = this[prop]; } } @@ -3067,6 +3773,8 @@ Crafty.fn = Crafty.prototype = { /**@ * #.setter * @comp Crafty Core + * @kind Method + * * @sign public this .setter(String property, Function callback) * @param property - Property to watch for modification * @param callback - Method to execute if the property is modified @@ -3084,6 +3792,8 @@ Crafty.fn = Crafty.prototype = { /**@ * #.defineField * @comp Crafty Core + * @kind Method + * * @sign public this .defineField(String property, Function getCallback, Function setCallback) * @param property - Property name to assign getter & setter to * @param getCallback - Method to execute if the property is accessed @@ -3116,6 +3826,8 @@ Crafty.fn = Crafty.prototype = { /**@ * #.destroy * @comp Crafty Core + * @kind Method + * * @sign public this .destroy(void) * Will remove all event listeners and delete all properties as well as removing from the stage */ @@ -3142,6 +3854,8 @@ Crafty.fn.init.prototype = Crafty.fn; /**@ * #Crafty.extend * @category Core + * @kind Method + * * @sign public this Crafty.extend(Object obj) * @param obj - An object whose fields will be copied onto Crafty. This is a shallow copy. * @@ -3266,7 +3980,7 @@ Crafty._callbackMethods = { // They are spliced out when _runCallbacks is invoked, not here // (This function might be called in the middle of a callback, which complicates the logic) for (var i = 0; i < callbacks.length; i++) { - if (!fn || callbacks[i] == fn) { + if (!fn || callbacks[i] === fn) { delete callbacks[i]; } } @@ -3301,6 +4015,8 @@ Crafty.extend({ /**@ * #Crafty.init * @category Core + * @kind Method + * * @trigger Load - Just after the viewport is initialised. Before the EnterFrame loops is started * @sign public this Crafty.init([Number width, Number height, String stage_elem]) * @sign public this Crafty.init([Number width, Number height, HTMLElement stage_elem]) @@ -3330,6 +4046,7 @@ Crafty.extend({ } } + // The viewport will init things like the default graphics layers as well Crafty.viewport.init(w, h, stage_elem); //call all arbitrary functions attached to onload @@ -3353,6 +4070,8 @@ Crafty.extend({ /**@ * #Crafty.getVersion * @category Core + * @kind Method + * * @sign public String Crafty.getVersion() * @returns Current version of Crafty as a string * @@ -3370,6 +4089,8 @@ Crafty.extend({ /**@ * #Crafty.stop * @category Core + * @kind Method + * * @trigger CraftyStop - when the game is stopped - {bool clearState} * @sign public this Crafty.stop([bool clearState]) * @param clearState - if true the stage and all game state is cleared. @@ -3378,7 +4099,7 @@ Crafty.extend({ * * To restart, use `Crafty.init()`. * @see Crafty.init - */ + */ stop: function (clearState) { Crafty.trigger("CraftyStop", clearState); @@ -3387,6 +4108,11 @@ Crafty.extend({ // Remove audio Crafty.audio.remove(); + //Destroy all systems + for (var s in Crafty._systems) { + Crafty._systems[s].destroy(); + } + // Remove the stage element, and re-add a div with the same id if (Crafty.stage && Crafty.stage.elem.parentNode) { var newCrStage = document.createElement('div'); @@ -3394,11 +4120,6 @@ Crafty.extend({ Crafty.stage.elem.parentNode.replaceChild(newCrStage, Crafty.stage.elem); } - // Reset references to the now destroyed graphics layers - delete Crafty.canvasLayer.context; - delete Crafty.domLayer._div; - delete Crafty.webgl.context; - // reset callbacks, and indicate that prebound functions need to be bound on init again Crafty._unbindAll(); Crafty._addCallbackMethods(Crafty); @@ -3412,6 +4133,8 @@ Crafty.extend({ /**@ * #Crafty.pause * @category Core + * @kind Method + * * @trigger Pause - when the game is paused * @trigger Unpause - when the game is unpaused * @sign public this Crafty.pause(void) @@ -3450,6 +4173,8 @@ Crafty.extend({ /**@ * #Crafty.isPaused * @category Core + * @kind Method + * * @sign public Boolean Crafty.isPaused() * @returns Whether the game is currently paused. * @@ -3465,6 +4190,8 @@ Crafty.extend({ /**@ * #Crafty.timer * @category Game Loop + * @kind CoreObject + * * Handles game ticks */ timer: (function () { @@ -3547,37 +4274,53 @@ Crafty.extend({ /**@ * #Crafty.timer.steptype * @comp Crafty.timer + * @kind Method + * + * @trigger NewSteptype - when the current steptype changes - { mode, maxTimeStep } - New steptype + * + * Can be called to set the type of timestep the game loop uses. * @sign public void Crafty.timer.steptype(mode [, maxTimeStep]) - * Can be called to set the type of timestep the game loop uses * @param mode - the type of time loop. Allowed values are "fixed", "semifixed", and "variable". Crafty defaults to "fixed". * @param maxTimeStep - For "fixed", sets the max number of frames per step. For "variable" and "semifixed", sets the maximum time step allowed. * + * Can be called to get the type of timestep the game loop uses. + * @sign public Object Crafty.timer.steptype(void) + * @returns Object containing the current timestep's properties { mode, maxTimeStep } + * * * In "fixed" mode, each frame is sent the same value of `dt`, and to achieve the target game speed, mulitiple frame events are triggered before each render. * * In "variable" mode, there is only one frame triggered per render. This recieves a value of `dt` equal to the actual elapsed time since the last frame. * * In "semifixed" mode, multiple frames per render are processed, and the total time since the last frame is divided evenly between them. * + * @see Crafty.timer.FPS */ - steptype: function (newmode, option) { + // setters if (newmode === "variable" || newmode === "semifixed") { mode = newmode; if (option) maxTimestep = option; - + Crafty.trigger("NewSteptype", {mode: mode, maxTimeStep: maxTimestep}); } else if (newmode === "fixed") { mode = "fixed"; if (option) maxFramesPerStep = option; - } else { + Crafty.trigger("NewSteptype", {mode: mode, maxTimeStep: maxFramesPerStep}); + } else if (newmode !== undefined) { throw "Invalid step type specified"; + // getter + } else { + return { + mode: mode, + maxTimeStep: (mode === "variable" || mode === "semifixed") ? maxTimestep : maxFramesPerStep + }; } - - }, /**@ * #Crafty.timer.step * @comp Crafty.timer + * @kind Method + * * @sign public void Crafty.timer.step() * @trigger EnterFrame - Triggered on each frame. Passes the frame number, and the amount of time since the last frame. If the time is greater than maxTimestep, that will be used instead. (The default value of maxTimestep is 50 ms.) - { frame: Number, dt:Number } * @trigger ExitFrame - Triggered after each frame. Passes the frame number, and the amount of time since the last frame. If the time is greater than maxTimestep, that will be used instead. (The default value of maxTimestep is 50 ms.) - { frame: Number, dt:Number } @@ -3592,11 +4335,12 @@ Crafty.extend({ * Specifically it triggers `EnterFrame` & `ExitFrame` events for each frame and `PreRender`, `RenderScene` & `PostRender` events for each render. * * @see Crafty.timer.steptype + * @see Crafty.timer.FPS */ step: function () { var drawTimeStart, dt, lastFrameTime, loops = 0; - currentTime = new Date().getTime(); + var currentTime = new Date().getTime(); if (endTime > 0) Crafty.trigger("MeasureWaitTime", currentTime - endTime); @@ -3641,6 +4385,8 @@ Crafty.extend({ dt: dt, gameTime: gameTime }; + // Handle any changes due to user input + Crafty.trigger("EnterFrameInput", frameData); // Everything that changes over time hooks into this event Crafty.trigger("EnterFrame", frameData); // Event that happens after "EnterFrame", e.g. for resolivng collisions applied through movement during "EnterFrame" events @@ -3666,6 +4412,8 @@ Crafty.extend({ /**@ * #Crafty.timer.FPS * @comp Crafty.timer + * @kind Method + * * @sign public void Crafty.timer.FPS() * Returns the target frames per second. This is not an actual frame rate. * @sign public void Crafty.timer.FPS(Number value) @@ -3674,9 +4422,11 @@ Crafty.extend({ * * Sets the target frames per second. This is not an actual frame rate. * The default rate is 50. + * + * @see Crafty.timer.steptype */ FPS: function (value) { - if (typeof value == "undefined") + if (typeof value === "undefined") return FPS; else { FPS = value; @@ -3688,19 +4438,21 @@ Crafty.extend({ /**@ * #Crafty.timer.simulateFrames * @comp Crafty.timer + * @kind Method + * * @sign public this Crafty.timer.simulateFrames(Number frames[, Number timestep]) * Advances the game state by a number of frames and draws the resulting stage at the end. Useful for tests and debugging. * @param frames - number of frames to simulate * @param timestep - the duration to pass each frame. Defaults to milliSecPerFrame (20 ms) if not specified. */ simulateFrames: function (frames, timestep) { - if (typeof timestep === "undefined") - timestep = milliSecPerFrame; + timestep = timestep || milliSecPerFrame; while (frames-- > 0) { var frameData = { frame: frame++, dt: timestep }; + Crafty.trigger("EnterFrameInput", frameData); Crafty.trigger("EnterFrame", frameData); Crafty.trigger("ExitFrame", frameData); } @@ -3715,6 +4467,8 @@ Crafty.extend({ /**@ * #Crafty.e * @category Core + * @kind Method + * * @trigger NewEntity - When the entity is created and all components are added - { id:Number } * @sign public Entity Crafty.e(String componentList) * @param componentList - List of components to assign to new entity @@ -3755,6 +4509,8 @@ Crafty.extend({ /**@ * #Crafty.c * @category Core + * @kind Method + * * @sign public void Crafty.c(String name, Object component) * @param name - Name of the component * @param component - Object with the component's properties and methods @@ -3831,6 +4587,8 @@ Crafty.extend({ /**@ * #Crafty.trigger * @category Core, Events + * @kind Method + * * @sign public void Crafty.trigger(String eventName, * data) * @param eventName - Name of the event to trigger * @param data - Arbitrary data to pass into the callback as an argument @@ -3859,6 +4617,8 @@ Crafty.extend({ /**@ * #Crafty.bind * @category Core, Events + * @kind Method + * * @sign public Function bind(String eventName, Function callback) * @param eventName - Name of the event to bind to * @param callback - Method to execute upon event triggered @@ -3880,6 +4640,8 @@ Crafty.extend({ /**@ * #Crafty.uniqueBind * @category Core, Events + * @kind Method + * * @sign public Function uniqueBind(String eventName, Function callback) * @param eventName - Name of the event to bind to * @param callback - Method to execute upon event triggered @@ -3897,6 +4659,8 @@ Crafty.extend({ /**@ * #Crafty.one * @category Core, Events + * @kind Method + * * @sign public Function one(String eventName, Function callback) * @param eventName - Name of the event to bind to * @param callback - Method to execute upon event triggered @@ -3918,6 +4682,8 @@ Crafty.extend({ /**@ * #Crafty.unbind * @category Core, Events + * @kind Method + * * @sign public Boolean Crafty.unbind(String eventName, Function callback) * @param eventName - Name of the event to unbind * @param callback - Function to unbind @@ -3949,6 +4715,8 @@ Crafty.extend({ /**@ * #Crafty.frame * @category Core + * @kind Method + * * @sign public Number Crafty.frame(void) * @returns the current frame number */ @@ -3975,6 +4743,8 @@ Crafty.extend({ /**@ * #Crafty.settings * @category Core + * @kind CoreObject + * * Modify the inner workings of Crafty through the settings. */ settings: (function () { @@ -3985,6 +4755,8 @@ Crafty.extend({ /**@ * #Crafty.settings.register * @comp Crafty.settings + * @kind Method + * * @sign public void Crafty.settings.register(String settingName, Function callback) * @param settingName - Name of the setting * @param callback - Function to execute when use modifies setting @@ -4000,6 +4772,8 @@ Crafty.extend({ /**@ * #Crafty.settings.modify * @comp Crafty.settings + * @kind Method + * * @sign public void Crafty.settings.modify(String settingName, * value) * @param settingName - Name of the setting * @param value - Value to set the setting to @@ -4017,6 +4791,8 @@ Crafty.extend({ /**@ * #Crafty.settings.get * @comp Crafty.settings + * @kind Method + * * @sign public * Crafty.settings.get(String settingName) * @param settingName - Name of the setting * @returns Current value of the setting @@ -4034,6 +4810,8 @@ Crafty.extend({ /**@ * #Crafty.defineField * @category Core + * @kind Method + * * @sign public void Crafty.defineField(Object object, String property, Function getCallback, Function setCallback) * @param object - Object to define property on * @param property - Property name to assign getter & setter to @@ -4088,10 +4866,13 @@ function UID() { /**@ * #Crafty.clone * @category Core + * @kind Method + * * @sign public Object .clone(Object obj) * @param obj - an object * * Deep copy (a.k.a clone) of an object. + * @note This function should be used for plain objects with no cyclic references. To clone an entity use its `.clone` method instead. * * @example * ~~~ @@ -4122,10 +4903,12 @@ function UID() { * obj1.log(); // prints "2/2" to the log * obj2.log(); // prints "1/2" to the log * ~~~ + * + * @see Crafty Core#.clone */ function clone(obj) { - if (obj === null || typeof (obj) != 'object') + if (obj === null || typeof (obj) !== 'object') return obj; var temp = obj.constructor(); // changed @@ -4137,21 +4920,22 @@ function clone(obj) { // export Crafty if (typeof define === 'function') { // AMD - define('crafty', [], function () { + define('crafty', [], function () { // jshint ignore:line return Crafty; }); } module.exports = Crafty; - -},{"./version":16}],8:[function(require,module,exports){ +},{"./version":17}],9:[function(require,module,exports){ (function (process){ -var Crafty = require('./core'); +var Crafty = require('../core/core.js'); var document = (typeof window !== "undefined") && window.document; /**@ * #Crafty.support * @category Misc, Core + * @kind CoreObject + * * Determines feature support for what Crafty can do. */ (function testSupport() { @@ -4167,6 +4951,7 @@ var document = (typeof window !== "undefined") && window.document; /**@ * #Crafty.mobile * @comp Crafty.device + * @kind Property * * Determines if Crafty is running on mobile device. * @@ -4184,6 +4969,8 @@ var document = (typeof window !== "undefined") && window.document; /**@ * #Crafty.support.defineProperty * @comp Crafty.support + * @kind Property + * * Is `Object.defineProperty` supported? */ support.defineProperty = (function () { @@ -4199,6 +4986,8 @@ var document = (typeof window !== "undefined") && window.document; /**@ * #Crafty.support.audio * @comp Crafty.support + * @kind Property + * * Is HTML5 `Audio` supported? */ support.audio = (typeof window !== "undefined") && ('canPlayType' in document.createElement('audio')); @@ -4206,6 +4995,8 @@ var document = (typeof window !== "undefined") && window.document; /**@ * #Crafty.support.prefix * @comp Crafty.support + * @kind Property + * * Returns the browser specific prefix (`Moz`, `O`, `ms`, `webkit`, `node`). */ support.prefix = (match[1] || match[0]); @@ -4219,6 +5010,8 @@ var document = (typeof window !== "undefined") && window.document; /**@ * #Crafty.support.versionName * @comp Crafty.support + * @kind Property + * * Version of the browser */ support.versionName = match[2]; @@ -4226,6 +5019,8 @@ var document = (typeof window !== "undefined") && window.document; /**@ * #Crafty.support.version * @comp Crafty.support + * @kind Property + * * Version number of the browser as an Integer (first number) */ support.version = +(match[2].split("."))[0]; @@ -4234,6 +5029,8 @@ var document = (typeof window !== "undefined") && window.document; /**@ * #Crafty.support.canvas * @comp Crafty.support + * @kind Property + * * Is the `canvas` element supported? */ support.canvas = (typeof window !== "undefined") && ('getContext' in document.createElement("canvas")); @@ -4241,6 +5038,8 @@ var document = (typeof window !== "undefined") && window.document; /**@ * #Crafty.support.webgl * @comp Crafty.support + * @kind Property + * * Is WebGL supported on the canvas element? */ if (support.canvas) { @@ -4259,6 +5058,8 @@ var document = (typeof window !== "undefined") && window.document; /**@ * #Crafty.support.css3dtransform * @comp Crafty.support + * @kind Property + * * Is css3Dtransform supported by browser. */ support.css3dtransform = (typeof window !== "undefined") && ((typeof document.createElement("div").style.Perspective !== "undefined") || (typeof document.createElement("div").style[support.prefix + "Perspective"] !== "undefined")); @@ -4266,6 +5067,7 @@ var document = (typeof window !== "undefined") && window.document; /**@ * #Crafty.support.deviceorientation * @comp Crafty.support + * @kind Property * Is deviceorientation event supported by browser. */ support.deviceorientation = (typeof window !== "undefined") && ((typeof window.DeviceOrientationEvent !== "undefined") || (typeof window.OrientationEvent !== "undefined")); @@ -4273,6 +5075,8 @@ var document = (typeof window !== "undefined") && window.document; /**@ * #Crafty.support.devicemotion * @comp Crafty.support + * @kind Property + * * Is devicemotion event supported by browser. */ support.devicemotion = (typeof window !== "undefined") && (typeof window.DeviceMotionEvent !== "undefined"); @@ -4285,6 +5089,8 @@ module.exports = { /**@ * #Crafty.addEvent * @category Events, Misc + * @kind Method + * * @sign public this Crafty.addEvent(Object ctx, HTMLElement obj, String event, Function callback) * @param ctx - Context of the callback or the value of `this` * @param obj - Element to add the DOM event to @@ -4342,6 +5148,8 @@ module.exports = { /**@ * #Crafty.removeEvent * @category Events, Misc + * @kind Method + * * @sign public this Crafty.removeEvent(Object ctx, HTMLElement obj, String event, Function callback) * @param ctx - Context of the callback or the value of `this` * @param obj - Element the event is on @@ -4374,6 +5182,8 @@ module.exports = { /**@ * #Crafty.background * @category Graphics, Stage + * @kind Method + * * @sign public void Crafty.background(String style) * @param style - Modify the background with a color or image * @@ -4394,13 +5204,15 @@ module.exports = { }; }).call(this,require('_process')) -},{"./core":7,"_process":1}],9:[function(require,module,exports){ +},{"../core/core.js":8,"_process":1}],10:[function(require,module,exports){ var Crafty = require('../core/core.js'); module.exports = { /**@ * #Crafty.assets * @category Assets + * @kind Property + * * An object containing every asset used in the current Crafty game. * The key is the URL and the value is the `Audio` or `Image` object. * @@ -4417,6 +5229,8 @@ module.exports = { /**@ * #Crafty.paths * @category Assets + * @kind Method + * * @sign public void Crafty.paths([Object paths]) * @param paths - Object containing paths for audio and images folders * @@ -4461,6 +5275,8 @@ module.exports = { /**@ * #Crafty.asset * @category Assets + * @kind Method + * * @trigger NewAsset - After setting new asset - Object - key and value of new added asset. * @sign public void Crafty.asset(String key, Object asset) * @param key - asset url. @@ -4499,6 +5315,7 @@ module.exports = { /**@ * #Crafty.image_whitelist * @category Assets + * @kind Method * * A list of file extensions that can be loaded as images by Crafty.load * @@ -4541,6 +5358,8 @@ module.exports = { /**@ * #Crafty.load * @category Assets + * @kind Method + * * @sign public void Crafty.load(Object assets, Function onLoad[, Function onProgress[, Function onError]]) * @param assets - Object JSON formatted (or JSON string), with assets to load (accepts sounds, images and sprites) * @param onLoad - Callback when the assets are loaded @@ -4627,32 +5446,32 @@ module.exports = { if (Array.isArray(data)) { Crafty.log("Calling Crafty.load with an array of assets no longer works; see the docs for more details."); + return; } data = (typeof data === "string" ? JSON.parse(data) : data); var j = 0, total = (data.audio ? Object.keys(data.audio).length : 0) + - (data.images ? Object.keys(data.images).length : 0) + - (data.sprites ? Object.keys(data.sprites).length : 0), + (data.images ? Object.keys(data.images).length : 0) + + (data.sprites ? Object.keys(data.sprites).length : 0), current, fileUrl, obj, type, asset, - audSupport = Crafty.support.audio, paths = Crafty.paths(), getExt = function(f) { return f.substr(f.lastIndexOf('.') + 1).toLowerCase(); }, getFilePath = function(type,f) { - return (f.search("://") === -1 ? (type == "audio" ? paths.audio + f : paths.images + f) : f); + return (f.search("://") === -1 ? (type === "audio" ? paths.audio + f : paths.images + f) : f); }, // returns null if 'a' is not already a loaded asset, obj otherwise isAsset = function(a) { return Crafty.asset(a) || null; }, isSupportedAudio = function(f) { - return Crafty.audio.supports(getExt(f)); + return Crafty.support.audio && Crafty.audio.supports(getExt(f)); }, isValidImage = function(f) { - return Crafty.image_whitelist.indexOf(getExt(f)) != -1; + return Crafty.image_whitelist.indexOf(getExt(f)) !== -1; }, onImgLoad = function(obj,url) { obj.onload = pro; @@ -4704,22 +5523,26 @@ module.exports = { continue; // maintain compatibility to other frameworks while iterating array current = data[type][asset]; + obj = null; - if (type === "audio" && audSupport) { + if (type === "audio") { if (typeof current === "object") { var files = []; for (var i in current) { fileUrl = getFilePath(type, current[i]); - if (!isAsset(fileUrl) && isSupportedAudio(current[i])) + if (!isAsset(fileUrl) && isSupportedAudio(current[i]) && !Crafty.audio.sounds[asset]) files.push(fileUrl); } - obj = Crafty.audio.add(asset, files).obj; - } - else if (typeof current === "string" && isSupportedAudio(current)) { + if (files.length > 0) + obj = Crafty.audio.add(asset, files); + } else if (typeof current === "string") { fileUrl = getFilePath(type, current); - if (!isAsset(fileUrl)) - obj = Crafty.audio.add(asset, fileUrl).obj; + if (!isAsset(fileUrl) && isSupportedAudio(current) && !Crafty.audio.sounds[asset]) + obj = Crafty.audio.add(asset, fileUrl); } + //extract actual audio obj if audio creation was successfull + if (obj) + obj = obj.obj; //addEventListener is supported on IE9 , Audio as well if (obj && obj.addEventListener) @@ -4727,33 +5550,32 @@ module.exports = { } else { asset = (type === "sprites" ? asset : current); fileUrl = getFilePath(type, asset); - if (isValidImage(asset)) { - obj = isAsset(fileUrl); - if (!obj) { - obj = new Image(); - if (type === "sprites") - Crafty.sprite(current.tile, current.tileh, fileUrl, current.map, - current.paddingX, current.paddingY, current.paddingAroundBorder); - Crafty.asset(fileUrl, obj); - } + if (!isAsset(fileUrl) && isValidImage(asset)) { + obj = new Image(); + if (type === "sprites") + Crafty.sprite(current.tile, current.tileh, fileUrl, current.map, + current.paddingX, current.paddingY, current.paddingAroundBorder); + Crafty.asset(fileUrl, obj); onImgLoad(obj, fileUrl); } } - if (obj) + + if (obj) { obj.onerror = err; - else - --total; + } else { + err.call({src: fileUrl}); + } } } // If we aren't trying to handle *any* of the files, that's as complete as it gets! - if (total === 0) - oncomplete(); + if (total === 0 && oncomplete) oncomplete(); }, /**@ * #Crafty.removeAssets * @category Assets + * @kind Method * * @sign public void Crafty.removeAssets(Object assets) * @param data - Object JSON formatted (or JSON string), with assets to remove (accepts sounds, images and sprites) @@ -4806,7 +5628,7 @@ module.exports = { var current, fileUrl, type, asset, paths = Crafty.paths(), getFilePath = function(type,f) { - return (f.search("://") === -1 ? (type == "audio" ? paths.audio + f : paths.images + f) : f); + return (f.search("://") === -1 ? (type === "audio" ? paths.audio + f : paths.images + f) : f); }; for (type in data) { @@ -4844,10 +5666,15 @@ module.exports = { } }; -},{"../core/core.js":7}],10:[function(require,module,exports){ +},{"../core/core.js":8}],11:[function(require,module,exports){ +var Crafty = require('../core/core.js'); + + /**@ * #Model * @category Model + * @kind Component + * * Model is a component that offers new features for isolating business * logic in your application. It offers default values, dirty values, * and deep events on your data. @@ -4893,7 +5720,7 @@ module.exports = { * update the name data attribute on the model. */ _changed_triggers: function(data, options) { - var key, trigger_data; + var key; options = Crafty.extend.call({pre: ''}, options); for (key in data) { this.trigger('Change[' + options.pre + key + ']', data[key]); @@ -4920,6 +5747,8 @@ module.exports = { /**@ * #.is_dirty * @comp Model + * @kind Method + * * Helps determine when data or the entire component is "dirty" or has changed attributes. * * @example @@ -4945,7 +5774,7 @@ module.exports = { }; -},{}],11:[function(require,module,exports){ +},{"../core/core.js":8}],12:[function(require,module,exports){ var Crafty = require('../core/core.js'); @@ -4956,6 +5785,8 @@ module.exports = { /**@ * #Crafty.scene * @category Scenes, Stage + * @kind Method + * * @trigger SceneChange - just before a new scene is initialized - { oldScene:String, newScene:String } * @trigger SceneDestroy - just before the current scene is destroyed - { newScene:String } * @@ -5042,6 +5873,7 @@ module.exports = { /* * #Crafty.defineScene * @category Scenes, Stage + * @kind Method * * @sign public void Crafty.enterScene(String name[, Data]) * @param name - Name of the scene to run. @@ -5065,6 +5897,8 @@ module.exports = { /* * #Crafty.enterScene * @category Scenes, Stage + * @kind Method + * * @trigger SceneChange - just before a new scene is initialized - { oldScene:String, newScene:String } * @trigger SceneDestroy - just before the current scene is destroyed - { newScene:String } * @@ -5116,7 +5950,7 @@ module.exports = { } }; -},{"../core/core.js":7}],12:[function(require,module,exports){ +},{"../core/core.js":8}],13:[function(require,module,exports){ var Crafty = require('../core/core.js'); try { @@ -5129,6 +5963,8 @@ try { /**@ * #Storage * @category Utilities + * @kind Component + * * Very simple way to get and set values, which will persist when the browser is closed also. * Storage wraps around HTML5 Web Storage, which is well-supported across browsers and platforms, but limited to 5MB total storage per domain. * Storage is also available for node, which is permanently persisted to the `./localStorage` folder - take care of removing entries. Note that multiple Crafty instances use the same storage, so care has to be taken not to overwrite existing entries. @@ -5136,6 +5972,8 @@ try { /**@ * #Crafty.storage * @comp Storage + * @kind Method + * * @sign Crafty.storage(String key) * @param key - a key you would like to get from the storage. * @returns The stored value, or `null` if none saved under that key exists @@ -5210,6 +6048,8 @@ var store = function(key, value) { /**@ * #Crafty.storage.remove * @comp Storage + * @kind Method + * * @sign Crafty.storage.remove(String key) * @param key - a key where you will like to delete the value of. * @@ -5234,7 +6074,7 @@ store.remove = function(key) { module.exports = store; -},{"../core/core.js":7}],13:[function(require,module,exports){ +},{"../core/core.js":8}],14:[function(require,module,exports){ var Crafty = require('../core/core.js'); @@ -5244,16 +6084,18 @@ Crafty._systems = {}; /**@ * #Crafty.s * @category Core + * @kind Method * * Registers a system. * * @trigger SystemLoaded - When the system has initialized itself - obj - system object * @trigger SystemDestroyed - Right before the system is destroyed - obj - system object * - * @sign void Crafty.s(String name, Obj template[, Boolean lazy]) + * @sign void Crafty.s(String name, Obj template[, Obj options][, Boolean lazy]) * Register a system * @param name - The name of the system * @param template - an object whose methods and properties will be copied to the new system + * @param options - an object whose properties will be deep copied to the new system's options property * @param lazy - a flag that indicates whether the system should be initialized right away or the first time it is referenced * * @sign System Crafty.s(String name) @@ -5261,134 +6103,156 @@ Crafty._systems = {}; * @param name - The system to return * @returns The referenced system. If the system has not been initialized, it will be before it is returned. * - * Objects which handle entities might want to subscribe to the event system without being entities themselves. + * Objects which handle entities might want to subscribe to the event system without being entities themselves. * When you declare a system with a template object, all the methods and properties of that template are copied to a new object. * This new system will automatically have the following event related methods, which function like those of components: `.bind()`, `unbind()`, `trigger()`, `one()`, `uniqueBind()`, `destroy()`. * Much like components, you can also provide `init()` and `remove()` methods, as well as an `events` parameter for automatically binding to events. * - * *Note*: The `init()` method is for setting up the internal state of the system -- if you create entities in it that then reference the system, that'll create an infinite loop. + * @note The `init()` method is for setting up the internal state of the system -- if you create entities in it that then reference the system, that'll create an infinite loop. */ -Crafty.s = function(name, obj, lazy) { - if (obj) { - if (lazy === false ) { - Crafty._systems[name] = new Crafty.CraftySystem(name, obj); - Crafty.trigger("SystemLoaded", name); - } else { - Crafty._registerLazySystem(name, obj); - } - } else { - return Crafty._systems[name]; - } +Crafty.s = function(name, obj, options, lazy) { + if (obj) { + if (typeof options === "boolean") { + lazy = options; + options = null; + } + if (lazy === false) { + Crafty._systems[name] = new Crafty.CraftySystem(name, obj, options); + Crafty.trigger("SystemLoaded", name); + } else { + Crafty._registerLazySystem(name, obj, options); + } + } else { + return Crafty._systems[name]; + } }; +function optionMerge(defaults, specific){ + var options = {}; + // Copy all the specified keys, then all the default keys that aren't specified + for (var key in specific) { + options[key] = specific[key]; + } + for (key in defaults) { + if (!(key in specific)) { + options[key] = defaults[key]; + } + } + return options; +} -Crafty._registerLazySystem = function(name, obj) { - // This is a bit of magic to only init a system if it's requested at least once. - // We define a getter for _systems[name] that will first initialize the system, - // and then redefine _systems[name] to ` that getter. - Object.defineProperty(Crafty._systems, name, { - get: function() { - Object.defineProperty(Crafty._systems, name, { - value: new Crafty.CraftySystem(name, obj), - writable: true, - enumerable: true, - configurable: true - }); - Crafty.trigger("SystemLoaded", name); - return Crafty._systems[name]; - }, - configurable: true - }); +Crafty._registerLazySystem = function(name, obj, options) { + // This is a bit of magic to only init a system if it's requested at least once. + // We define a getter for _systems[name] that will first initialize the system, + // and then redefine _systems[name] to remove that getter. + Object.defineProperty(Crafty._systems, name, { + get: function() { + Object.defineProperty(Crafty._systems, name, { + value: new Crafty.CraftySystem(name, obj, options), + writable: true, + enumerable: true, + configurable: true + }); + Crafty.trigger("SystemLoaded", name); + return Crafty._systems[name]; + }, + configurable: true + }); }; // Each system has its properties and methods copied onto an object of this type -Crafty.CraftySystem = (function(){ - systemID = 1; - return function(name, template) { - this.name = name; - if (!template) return this; - this._systemTemplate = template; - this.extend(template); - - // Add the "low leveL" callback methods - Crafty._addCallbackMethods(this); - - // Give this object a global ID. Used for event handlers. - this[0] = "system" + (systemID++); - // Run any instantiation code - if (typeof this.init === "function") { - this.init(name); - } - // If an events object is provided, bind the listed event handlers - if ("events" in template){ - var auto = template.events; - for (var eventName in auto){ - var fn = typeof auto[eventName] === "function" ? auto[eventName] : template[auto[eventName]]; - this.bind(eventName, fn); - } - } - }; +Crafty.CraftySystem = (function() { + var systemID = 1; + return function(name, template, options) { + this.name = name; + if (!template) return this; + this._systemTemplate = template; + this.extend(template); + + // Overwrite any default options with the passed options object + // This does a deep copy on the objects, and treats null as a specified value + this.options = optionMerge(this.options, options); + + // Add the "low leveL" callback methods + Crafty._addCallbackMethods(this); + + // Give this object a global ID. Used for event handlers. + this[0] = "system" + (systemID++); + // Run any instantiation code + if (typeof this.init === "function") { + this.init(name); + } + // If an events object is provided, bind the listed event handlers + if ("events" in template) { + var auto = template.events; + for (var eventName in auto) { + var fn = typeof auto[eventName] === "function" ? auto[eventName] : template[auto[eventName]]; + this.bind(eventName, fn); + } + } + }; })(); Crafty.CraftySystem.prototype = { - extend: function(obj) { - // Copy properties and methods of obj - for (var key in obj) { - if (typeof this[key] === "undefined") { - this[key] = obj[key]; - } - } - }, + extend: function(obj) { + // Copy properties and methods of obj + for (var key in obj) { + if (typeof this[key] === "undefined") { + this[key] = obj[key]; + } + } + }, - // Event methods - bind: function(event, callback) { - this._bindCallback(event, callback); - return this; - }, + // Event methods + bind: function(event, callback) { + this._bindCallback(event, callback); + return this; + }, - trigger: function(event, data) { - this._runCallbacks(event, data); - return this; - }, + trigger: function(event, data) { + this._runCallbacks(event, data); + return this; + }, - unbind: function(event, callback) { - this._unbindCallbacks(event, callback); - return this; - }, + unbind: function(event, callback) { + this._unbindCallbacks(event, callback); + return this; + }, - one: function (event, callback) { - var self = this; - var oneHandler = function (data) { - callback.call(self, data); - self.unbind(event, oneHandler); - }; - return self.bind(event, oneHandler); - }, + one: function(event, callback) { + var self = this; + var oneHandler = function(data) { + callback.call(self, data); + self.unbind(event, oneHandler); + }; + return self.bind(event, oneHandler); + }, - uniqueBind: function(event, callback) { - this.unbind(event, callback); - return this.bind(event, callback); - }, + uniqueBind: function(event, callback) { + this.unbind(event, callback); + return this.bind(event, callback); + }, - destroy: function() { - Crafty.trigger("SystemDestroyed", this); - // Check the template itself - if (typeof this.remove === "function") { - this.remove(); - } - this._unbindAll(); - delete Crafty._systems[this.name]; - } + destroy: function() { + Crafty.trigger("SystemDestroyed", this); + // Check the template itself + if (typeof this.remove === "function") { + this.remove(); + } + this._unbindAll(); + delete Crafty._systems[this.name]; + } }; -},{"../core/core.js":7}],14:[function(require,module,exports){ +},{"../core/core.js":8}],15:[function(require,module,exports){ /**@ * #Delay * @category Utilities + * @kind Component * * A component for triggering functions after a given amount of time. * @@ -5397,7 +6261,9 @@ Crafty.CraftySystem.prototype = { module.exports = { init: function () { this._delays = []; + this._delaysPaused = false; this.bind("EnterFrame", function (frameData) { + if (this._delaysPaused) return; var index = this._delays.length; while (--index >= 0) { var item = this._delays[index]; @@ -5426,6 +6292,7 @@ module.exports = { /**@ * #.delay * @comp Delay + * @kind Method * @sign public this.delay(Function callback, Number delay[, Number repeat[, Function callbackOff]]) * @param callback - Method to execute after given amount of milliseconds. If reference of a * method is passed, there's possibility to cancel the delay. @@ -5478,6 +6345,8 @@ module.exports = { /**@ * #.cancelDelay * @comp Delay + * @kind Method + * * @sign public this.cancelDelay(Function callback) * @param callback - Method reference passed to .delay * @@ -5500,18 +6369,79 @@ module.exports = { var index = this._delays.length; while (--index >= 0) { var item = this._delays[index]; - if(item && item.callback == callback){ + if(item && item.callback === callback){ this._delays[index] = false; } } return this; + }, + /**@ + * #.pauseDelays + * @comp Delay + * @kind Method + * + * @sign public this.pauseDelays() + * + * The pauseDelays method will pause all delays of this + * entity until resumed. + * + * @example + * ~~~ + * var doSomething = function(){ + * Crafty.log("doing something"); + * }; + * + * // execute doSomething each 100 miliseconds indefinetely + * var ent = Crafty.e("Delay").delay(doSomething, 100, -1); + * + * // and some time later, the gameplay is paused + * ent.pauseDelays(); + * ~~~ + */ + pauseDelays: function() { + this._delaysPaused = true; + }, + /**@ + * #.resumeDelays + * @comp Delay + * @kind Method + * + * @sign public this.resumeDelays() + * + * The resumeDelays method will resume earlier paused delays for this + * entity + * + * @example + * ~~~ + * var doSomething = function(){ + * Crafty.log("doing something"); + * }; + * + * // execute doSomething each 100 miliseconds indefinetely + * var ent = Crafty.e("Delay").delay(doSomething, 100, -1); + * + * // and some time later, the gameplay is paused (or only + * // a part of it is frozen) + * ent.pauseDelays(); + * + * // the player resumes gameplay + * ent.resumeDelays(); + * ~~~ + */ + resumeDelays: function() { + this._delaysPaused = false; } }; -},{}],15:[function(require,module,exports){ +},{}],16:[function(require,module,exports){ +var Crafty = require('../core/core.js'); + + /**@ * #Tween * @category Animation + * @kind Component + * * @trigger TweenEnd - when a tween finishes - String - property * * Component to animate the change in 2D properties over time. @@ -5551,6 +6481,8 @@ module.exports = { /**@ * #.tween * @comp Tween + * @kind Method + * * @sign public this .tween(Object properties, Number duration[, String|function easingFn]) * @param properties - Object of numeric properties and what they should animate to * @param duration - Duration to animate the properties over, in milliseconds. @@ -5606,6 +6538,8 @@ module.exports = { /**@ * #.cancelTween * @comp Tween + * @kind Method + * * @sign public this .cancelTween(String target) * @param target - The property to cancel * @@ -5617,7 +6551,7 @@ module.exports = { */ cancelTween: function(target){ if (typeof target === "string"){ - if (typeof this.tweenGroup[target] == "object" ) + if (typeof this.tweenGroup[target] === "object" ) delete this.tweenGroup[target][target]; } else if (typeof target === "object") { for (var propname in target) @@ -5631,6 +6565,8 @@ module.exports = { /**@ * #.pauseTweens * @comp Tween + * @kind Method + * * @sign public this .pauseTweens() * * Pauses all tweens associated with the entity @@ -5640,8 +6576,10 @@ module.exports = { }, /**@ - * #.resumeTWeens + * #.resumeTweens * @comp Tween + * @kind Method + * * @sign public this .resumeTweens() * * Resumes all paused tweens associated with the entity @@ -5661,9 +6599,9 @@ module.exports = { } }; -},{}],16:[function(require,module,exports){ -module.exports = "0.7.1-rc2"; -},{}],17:[function(require,module,exports){ +},{"../core/core.js":8}],17:[function(require,module,exports){ +module.exports = "0.8.0-rc1"; +},{}],18:[function(require,module,exports){ var Crafty = require('./core/core'); Crafty.easing = require('./core/animation'); @@ -5678,19 +6616,28 @@ Crafty.c('Tween', require('./core/tween')); require('./core/systems'); require('./spatial/2d'); +require('./spatial/motion'); +require('./spatial/platform'); require('./spatial/collision'); require('./spatial/spatial-grid'); require('./spatial/rect-manager'); require('./spatial/math'); +// Needs to be required before any specific layers are +require('./graphics/layers'); + require('./graphics/canvas'); require('./graphics/canvas-layer'); +require('./graphics/webgl'); +require('./graphics/webgl-layer'); + require('./graphics/color'); require('./graphics/dom'); require('./graphics/dom-helper'); require('./graphics/dom-layer'); require('./graphics/drawing'); require('./graphics/gl-textures'); +require('./graphics/renderable'); require('./graphics/html'); require('./graphics/image'); require('./graphics/particles'); @@ -5698,12 +6645,12 @@ require('./graphics/sprite-animation'); require('./graphics/sprite'); require('./graphics/text'); require('./graphics/viewport'); -require('./graphics/webgl'); require('./isometric/diamond-iso'); require('./isometric/isometric'); require('./controls/inputs'); +require('./controls/controls-system'); require('./controls/controls'); require('./controls/device'); require('./controls/keycodes'); @@ -5717,14 +6664,16 @@ if(window) window.Crafty = Crafty; module.exports = Crafty; -},{"./controls/controls":2,"./controls/device":3,"./controls/inputs":4,"./controls/keycodes":5,"./core/animation":6,"./core/core":7,"./core/extensions":8,"./core/loader":9,"./core/model":10,"./core/scenes":11,"./core/storage":12,"./core/systems":13,"./core/time":14,"./core/tween":15,"./debug/debug-layer":18,"./debug/logging":19,"./graphics/canvas":21,"./graphics/canvas-layer":20,"./graphics/color":22,"./graphics/dom":25,"./graphics/dom-helper":23,"./graphics/dom-layer":24,"./graphics/drawing":26,"./graphics/gl-textures":27,"./graphics/html":28,"./graphics/image":29,"./graphics/particles":30,"./graphics/sprite":32,"./graphics/sprite-animation":31,"./graphics/text":33,"./graphics/viewport":34,"./graphics/webgl":35,"./isometric/diamond-iso":36,"./isometric/isometric":37,"./sound/sound":38,"./spatial/2d":39,"./spatial/collision":40,"./spatial/math":41,"./spatial/rect-manager":42,"./spatial/spatial-grid":43}],18:[function(require,module,exports){ +},{"./controls/controls":3,"./controls/controls-system":2,"./controls/device":4,"./controls/inputs":5,"./controls/keycodes":6,"./core/animation":7,"./core/core":8,"./core/extensions":9,"./core/loader":10,"./core/model":11,"./core/scenes":12,"./core/storage":13,"./core/systems":14,"./core/time":15,"./core/tween":16,"./debug/debug-layer":19,"./debug/logging":20,"./graphics/canvas":22,"./graphics/canvas-layer":21,"./graphics/color":23,"./graphics/dom":26,"./graphics/dom-helper":24,"./graphics/dom-layer":25,"./graphics/drawing":27,"./graphics/gl-textures":28,"./graphics/html":29,"./graphics/image":30,"./graphics/layers":31,"./graphics/particles":32,"./graphics/renderable":33,"./graphics/sprite":35,"./graphics/sprite-animation":34,"./graphics/text":36,"./graphics/viewport":37,"./graphics/webgl":39,"./graphics/webgl-layer":38,"./isometric/diamond-iso":40,"./isometric/isometric":41,"./sound/sound":42,"./spatial/2d":43,"./spatial/collision":44,"./spatial/math":45,"./spatial/motion":46,"./spatial/platform":47,"./spatial/rect-manager":48,"./spatial/spatial-grid":49}],19:[function(require,module,exports){ var Crafty = require('../core/core.js'), document = window.document; /**@ * #DebugCanvas * @category Debug - * @trigger Draw - when the entity is ready to be drawn to the stage + * @kind Component + * + * @trigger DebugDraw - when the entity is ready to be drawn to the stage * @trigger NoCanvas - if the browser does not support canvas * * When this component is added to an entity it will be drawn by the DebugCanvas layer. @@ -5763,6 +6712,8 @@ Crafty.c("DebugCanvas", { /**@ * #.debugAlpha * @comp DebugCanvas + * @kind Method + * * @sign public .debugAlpha(Number alpha) * @param alpha - The alpha level the component will be drawn with */ @@ -5774,6 +6725,8 @@ Crafty.c("DebugCanvas", { /**@ * #.debugFill * @comp DebugCanvas + * @kind Method + * * @sign public .debugFill([String fillStyle]) * @param fillStyle - The color the component will be filled with. Defaults to "red". Pass the boolean false to turn off filling. * @example @@ -5791,6 +6744,8 @@ Crafty.c("DebugCanvas", { /**@ * #.debugStroke * @comp DebugCanvas + * @kind Method + * * @sign public .debugStroke([String strokeStyle]) * @param strokeStyle - The color the component will be outlined with. Defaults to "red". Pass the boolean false to turn this off. * @example @@ -5821,7 +6776,7 @@ Crafty.c("DebugCanvas", { if (props.fillStyle) ctx.fillStyle = props.fillStyle; - this.trigger("DebugDraw"); + this.trigger("DebugDraw", ctx); ctx.globalAlpha = ga; @@ -5835,6 +6790,7 @@ Crafty.c("DebugCanvas", { /**@ * #DebugRectangle * @category Debug + * @kind Component * * A component for rendering an object with a position and dimensions to the debug canvas. * @@ -5855,6 +6811,8 @@ Crafty.c("DebugRectangle", { /**@ * #.debugRectangle * @comp DebugRectangle + * @kind Method + * * @sign public .debugRectangle(Object rect) * @param rect - an object with _x, _y, _w, and _h to draw * @@ -5869,9 +6827,8 @@ Crafty.c("DebugRectangle", { }, - drawDebugRect: function () { + drawDebugRect: function (ctx) { - var ctx = Crafty.DebugCanvas.context; var rect = this.debugRect; if (rect === null || rect === undefined) return; @@ -5893,6 +6850,7 @@ Crafty.c("DebugRectangle", { /**@ * #VisibleMBR * @category Debug + * @kind Component * * Adding this component to an entity will cause it's MBR to be drawn to the debug canvas. * @@ -5922,6 +6880,7 @@ Crafty.c("VisibleMBR", { /**@ * #DebugPolygon * @category Debug + * @kind Component * * For drawing a polygon to the debug canvas * @@ -5940,6 +6899,8 @@ Crafty.c("DebugPolygon", { /**@ * #.debugPolygon * @comp DebugPolygon + * @kind Method + * * @sign public .debugPolygon(Polygon poly) * @param poly - a polygon to render * @@ -5953,11 +6914,10 @@ Crafty.c("DebugPolygon", { return this; }, - drawDebugPolygon: function () { + drawDebugPolygon: function (ctx) { if (typeof this.polygon === "undefined") return; - var ctx = Crafty.DebugCanvas.context; ctx.beginPath(); var p = this.polygon.points, l = p.length; for (var i=0; i= 0; i--) - if (list[i] == ent) + if (list[i] === ent) list.splice(i, 1); }, @@ -6127,11 +7091,17 @@ Crafty.DebugCanvas = { ctx.clearRect(rect._x, rect._y, rect._w, rect._h); - - //sort the objects by the global Z - //q.sort(zsort); + var lastLayer = null; for (; i < l; i++) { current = q[i]; + + // If necessary, update the view transform to match the current entities layer + if (lastLayer !== current._drawlayer){ + view = current._drawLayer._viewportRect(); + ctx.setTransform(view._scale, 0, 0, view._scale, Math.round(-view._x*view._scale), Math.round(-view._y*view._scale)); + lastLayer = current._drawLayer; + } + current.debugDraw(ctx); } @@ -6139,13 +7109,14 @@ Crafty.DebugCanvas = { }; -},{"../core/core.js":7}],19:[function(require,module,exports){ +},{"../core/core.js":8}],20:[function(require,module,exports){ var Crafty = require('../core/core.js'); /**@ * #Crafty.log * @category Debug + * @kind Method * * @sign Crafty.log( arguments ) * @param arguments - arguments which are passed to `console.log` @@ -6156,6 +7127,7 @@ var Crafty = require('../core/core.js'); /**@ * #Crafty.error * @category Debug + * @kind Method * * @sign Crafty.error( arguments ) * @param arguments - arguments which are passed to `console.error` @@ -6168,370 +7140,432 @@ Crafty.extend({ loggingEnabled: true, // In some cases console.log doesn't exist, so provide a wrapper for it log: function() { - if (Crafty.loggingEnabled && console && console.log) { + if (Crafty.loggingEnabled && (typeof window !== "undefined" ? window.console : console) && console.log) { console.log.apply(console, arguments); } }, error: function() { - if (Crafty.loggingEnabled && console && console.error) { + if (Crafty.loggingEnabled && (typeof window !== "undefined" ? window.console : console) && console.error) { console.error.apply(console, arguments); } } }); -},{"../core/core.js":7}],20:[function(require,module,exports){ +},{"../core/core.js":8}],21:[function(require,module,exports){ var Crafty = require('../core/core.js'); /**@ - * #Crafty.canvasLayer + * #CanvasLayer * @category Graphics + * @kind System + * + * An object for creating the canvas layer system. * - * Collection of mostly private methods to draw entities on a canvas element. + * Mostly contains private methods to draw entities on a canvas element. */ -Crafty.extend({ - canvasLayer: { - _dirtyRects: [], - _changedObjs: [], - layerCount: 0, - _dirtyViewport: false, - - // Sort function for rendering in the correct order - _sort: function(a, b) { - return a._globalZ - b._globalZ; - }, +Crafty._registerLayerTemplate("Canvas", { + type: "Canvas", + + options: { + xResponse: 1, + yResponse: 1, + scaleResponse: 1, + z: 0 + }, + + _dirtyRects: [], + _changedObjs: [], + layerCount: 0, + _dirtyViewport: false, + + // Sort function for rendering in the correct order + _sort: function(a, b) { + return a._globalZ - b._globalZ; + }, - /**@ - * #Crafty.canvasLayer.add - * @comp Crafty.canvasLayer - * @sign public Crafty.canvasLayer.add(ent) - * @param ent - The entity to add - * - * Add an entity to the list of Canvas objects to draw - */ - add: function add(ent) { - this._changedObjs.push(ent); - }, - /**@ - * #Crafty.canvasLayer.context - * @comp Crafty.canvasLayer - * - * This will return the 2D context of the main canvas element. - * The value returned from `Crafty.canvasLayer._canvas.getContext('2d')`. - */ - context: null, - /**@ - * #Crafty.canvasLayer._canvas - * @comp Crafty.canvasLayer - * - * Main Canvas element - */ - _canvas: null, + /**@ + * #.dirty + * @comp CanvasLayer + * @kind Method + * @private + * + * @sign public .dirty(ent) + * @param ent - The entity to add + * + * Add an entity to the list of Canvas objects that need redrawing + */ + dirty: function dirty(ent) { + this._changedObjs.push(ent); + }, + + /**@ + * #.attach + * @comp CanvasLayer + * @kind Method + * @private + * + * @sign public .attach(ent) + * @param ent - The entity to add + * + * Sets the entity's draw context to this layer + */ + attach: function attach(ent) { + ent._drawContext = this.context; + //increment the number of canvas objs + this.layerCount++; + }, + + /**@ + * #.detach + * @comp CanvasLayer + * @kind Method + * @private + * + * @sign public .detach(ent) + * @param ent - The entity to detach + * + * Removes an entity to the list of Canvas objects to draw + */ + detach: function detach(ent) { + this.dirty(ent); + ent._drawContext = null; + //decrement the number of canvas objs + this.layerCount--; + }, + - /**@ - * #Crafty.canvasLayer.init - * @comp Crafty.canvasLayer - * @sign public void Crafty.canvasLayer.init(void) - * @trigger NoCanvas - triggered if `Crafty.support.canvas` is false - * - * Creates a `canvas` element inside `Crafty.stage.elem`. Must be called - * before any entities with the Canvas component can be drawn. - * - * This method will automatically be called if no `Crafty.canvasLayer.context` is - * found. - */ - init: function () { - //check if canvas is supported - if (!Crafty.support.canvas) { - Crafty.trigger("NoCanvas"); - Crafty.stop(); - return; - } + /**@ + * #.context + * @comp CanvasLayer + * @kind Property + * + * This will return the 2D context associated with the canvas layer's canvas element. + */ + context: null, - // set properties to initial values -- necessary on a restart - this._dirtyRects = []; - this._changedObjs = []; - this.layerCount = 0; + /**@ + * #._canvas + * @comp CanvasLayer + * @kind Property + * @private + * + * The canvas element associated with the canvas layer. + */ + _canvas: null, - //create an empty canvas element - var c; - c = document.createElement("canvas"); - c.width = Crafty.viewport.width; - c.height = Crafty.viewport.height; - c.style.position = 'absolute'; - c.style.left = "0px"; - c.style.top = "0px"; + // When the system is first created, create the necessary canvas element and initial state + // Bind to the necessary events + init: function () { + //check if canvas is supported + if (!Crafty.support.canvas) { + Crafty.trigger("NoCanvas"); + Crafty.stop(); + return; + } - var canvas = Crafty.canvasLayer; + // set referenced objects to initial values -- necessary to avoid shared state between systems + this._dirtyRects = []; + this._changedObjs = []; - Crafty.stage.elem.appendChild(c); - this.context = c.getContext('2d'); - this._canvas = c; + //create an empty canvas element + var c; + c = document.createElement("canvas"); + c.width = Crafty.viewport.width; + c.height = Crafty.viewport.height; + c.style.position = 'absolute'; + c.style.left = "0px"; + c.style.top = "0px"; + c.style.zIndex = this.options.z; - //Set any existing transformations - var zoom = Crafty.viewport._scale; - if (zoom != 1) - c.scale(zoom, zoom); + Crafty.stage.elem.appendChild(c); + this.context = c.getContext('2d'); + this._canvas = c; - // Set pixelart to current status, and listen for changes - this._setPixelart(Crafty._pixelartEnabled); - Crafty.uniqueBind("PixelartSet", this._setPixelart); + //Set any existing transformations + var zoom = Crafty.viewport._scale; + if (zoom !== 1) + this.context.scale(zoom, zoom); - //Bind rendering of canvas context (see drawing.js) - Crafty.uniqueBind("RenderScene", this._render); - - Crafty.uniqueBind("ViewportResize", this._resize); + // Set pixelart to current status, and listen for changes + this._setPixelart(Crafty._pixelartEnabled); + this.uniqueBind("PixelartSet", this._setPixelart); - Crafty.bind("InvalidateViewport", function () { - Crafty.canvasLayer._dirtyViewport = true; - }); - }, + //Bind rendering of canvas context (see drawing.js) + this.uniqueBind("RenderScene", this._render); + + this.uniqueBind("ViewportResize", this._resize); + this.bind("InvalidateViewport", function () { + this._dirtyViewport = true; + }); + + Crafty._addDrawLayerInstance(this); + }, - _render: function() { - var layer = Crafty.canvasLayer, - dirtyViewport = layer._dirtyViewport, - l = layer._changedObjs.length, - ctx = layer.context; - if (!l && !dirtyViewport) { - return; - } + // When the system is destroyed, remove related resources + remove: function() { - if (dirtyViewport) { - var view = Crafty.viewport; - ctx.setTransform(view._scale, 0, 0, view._scale, Math.round(view._x*view._scale), Math.round(view._y*view._scale) ); - } + this._canvas.parentNode.removeChild(this._canvas); + Crafty._removeDrawLayerInstance(this); + }, - //if the amount of changed objects is over 60% of the total objects - //do the naive method redrawing - // TODO: I'm not sure this condition really makes that much sense! - if (l / layer.layerCount > 0.6 || dirtyViewport) { - layer._drawAll(); - } else { - layer._drawDirty(); - } - //Clean up lists etc - layer._clean(); - }, + _render: function() { + var dirtyViewport = this._dirtyViewport, + l = this._changedObjs.length, + ctx = this.context; + if (!l && !dirtyViewport) { + return; + } + + // Set the camera transforms from the combination of the current viewport parameters and this layers + var cameraOptions = this.options; + if (dirtyViewport && cameraOptions) { + var view = this._viewportRect(); + var scale = view._scale; + var dx = -view._x * scale; + var dy = -view._y * scale; + ctx.setTransform(scale, 0, 0, scale, Math.round(dx), Math.round(dy) ); + } - /**@ - * #Crafty.canvasLayer.drawDirty - * @comp Crafty.canvasLayer - * @sign public Crafty.canvasLayer.drawDirty() - * - * - Triggered by the "RenderScene" event - * - If the number of rects is over 60% of the total number of objects - * do the naive method redrawing `Crafty.canvasLayer.drawAll` instead - * - Otherwise, clear the dirty regions, and redraw entities overlapping the dirty regions. - * - * @see Canvas#.draw - */ - _drawDirty: function () { + //if the amount of changed objects is over 60% of the total objects + //do the naive method redrawing + // TODO: I'm not sure this condition really makes that much sense! + if (l / this.layerCount > 0.6 || dirtyViewport) { + this._drawAll(); + } else { + this._drawDirty(); + } + //Clean up lists etc + this._clean(); + }, + + /**@ + * #._drawDirty + * @comp CanvasLayer + * @kind Method + * @private + * + * @sign public ._drawDirty() + * + * - Triggered by the "RenderScene" event + * - If the number of rects is over 60% of the total number of objects + * do the naive method redrawing `CanvasLayer.drawAll` instead + * - Otherwise, clear the dirty regions, and redraw entities overlapping the dirty regions. + * + * @see Canvas#.draw + */ + _drawDirty: function (view) { + view = view || this._viewportRect(); + var i, j, q, rect,len, obj, + changed = this._changedObjs, + l = changed.length, + dirty = this._dirtyRects, + rectManager = Crafty.rectManager, + overlap = rectManager.overlap, + ctx = this.context, + dupes = [], + objs = []; + + // Canvas works better with integral coordinates where possible + view = rectManager.integerBounds(view); + + // Calculate _dirtyRects from all changed objects, then merge some overlapping regions together + for (i = 0; i < l; i++) { + this._createDirty(changed[i]); + } + rectManager.mergeSet(dirty); - var i, j, q, rect,len, obj, ent, - changed = this._changedObjs, - l = changed.length, - dirty = this._dirtyRects, - rectManager = Crafty.rectManager, - overlap = rectManager.overlap, - ctx = this.context, - dupes = [], - objs = []; - // Calculate _dirtyRects from all changed objects, then merge some overlapping regions together - for (i = 0; i < l; i++) { - this._createDirty(changed[i]); - } - rectManager.mergeSet(dirty); + l = dirty.length; + // For each dirty rectangle, find entities near it, and draw the overlapping ones + for (i = 0; i < l; ++i) { //loop over every dirty rect + rect = dirty[i]; + dupes.length = 0; + objs.length = 0; + if (!rect) continue; - l = dirty.length; + // Find the smallest rectangle with integer coordinates that encloses rect + rect = rectManager.integerBounds(rect); - // For each dirty rectangle, find entities near it, and draw the overlapping ones - for (i = 0; i < l; ++i) { //loop over every dirty rect - rect = dirty[i]; - dupes.length = 0; - objs.length = 0; - if (!rect) continue; - - // Find the smallest rectangle with integer coordinates that encloses rect - rect._w = rect._x + rect._w; - rect._h = rect._y + rect._h; - rect._x = (rect._x > 0) ? (rect._x|0) : (rect._x|0) - 1; - rect._y = (rect._y > 0) ? (rect._y|0) : (rect._y|0) - 1; - rect._w -= rect._x; - rect._h -= rect._y; - rect._w = (rect._w === (rect._w|0)) ? rect._w : (rect._w|0) + 1; - rect._h = (rect._h === (rect._h|0)) ? rect._h : (rect._h|0) + 1; - - //search for ents under dirty rect - q = Crafty.map.search(rect, false); - - //clear the rect from the main canvas - ctx.clearRect(rect._x, rect._y, rect._w, rect._h); - - //Then clip drawing region to dirty rectangle - ctx.save(); - ctx.beginPath(); - ctx.rect(rect._x, rect._y, rect._w, rect._h); - ctx.clip(); - - // Loop over found objects removing dupes and adding visible canvas objects to array - for (j = 0, len = q.length; j < len; ++j) { - obj = q[j]; - - if (dupes[obj[0]] || !obj._visible || !obj.__c.Canvas) - continue; - dupes[obj[0]] = true; - objs.push(obj); - } + // If a dirty rect doesn't overlap with the viewport, skip to the next one + if (!overlap(rect, view)) continue; - // Sort objects by z level - objs.sort(this._sort); + //search for ents under dirty rect + q = Crafty.map.search(rect, false); - // Then draw each object in that order - for (j = 0, len = objs.length; j < len; ++j) { - obj = objs[j]; - var area = obj._mbr || obj; - if (overlap(area, rect)) - obj.draw(); - obj._changed = false; - } + //clear the rect from the main canvas + ctx.clearRect(rect._x, rect._y, rect._w, rect._h); - // Close rectangle clipping - ctx.closePath(); - ctx.restore(); + //Then clip drawing region to dirty rectangle + ctx.save(); + ctx.beginPath(); + ctx.rect(rect._x, rect._y, rect._w, rect._h); + ctx.clip(); + // Loop over found objects removing dupes and adding visible canvas objects to array + for (j = 0, len = q.length; j < len; ++j) { + obj = q[j]; + + if (dupes[obj[0]] || !obj._visible || (obj._drawLayer !== this) ) + continue; + dupes[obj[0]] = true; + objs.push(obj); } - // Draw dirty rectangles for debugging, if that flag is set - if (Crafty.canvasLayer.debugDirty === true) { - ctx.strokeStyle = 'red'; - for (i = 0, l = dirty.length; i < l; ++i) { - rect = dirty[i]; - ctx.strokeRect(rect._x, rect._y, rect._w, rect._h); - } + // Sort objects by z level + objs.sort(this._sort); + + // Then draw each object in that order + for (j = 0, len = objs.length; j < len; ++j) { + obj = objs[j]; + var area = obj._mbr || obj; + if (overlap(area, rect)) + obj.draw(); + obj._changed = false; } - }, + // Close rectangle clipping + ctx.closePath(); + ctx.restore(); - /**@ - * #Crafty.canvasLayer.drawAll - * @comp Crafty.canvasLayer - * @sign public Crafty.canvasLayer.drawAll([Object rect]) - * @param rect - a rectangular region {_x: x_val, _y: y_val, _w: w_val, _h: h_val} - * - * - If rect is omitted, redraw within the viewport - * - If rect is provided, redraw within the rect - */ - _drawAll: function (rect) { - rect = rect || Crafty.viewport.rect(); - var q = Crafty.map.search(rect), - i = 0, - l = q.length, - ctx = this.context, - current; + } - ctx.clearRect(rect._x, rect._y, rect._w, rect._h); + // Draw dirty rectangles for debugging, if that flag is set + if (this.debugDirty === true) { + ctx.strokeStyle = 'red'; + for (i = 0, l = dirty.length; i < l; ++i) { + rect = dirty[i]; + ctx.strokeRect(rect._x, rect._y, rect._w, rect._h); + } + } - //sort the objects by the global Z - q.sort(this._sort); - for (; i < l; i++) { - current = q[i]; - if (current._visible && current.__c.Canvas) { - current.draw(); - current._changed = false; - } + }, + + /**@ + * #._drawAll + * @comp CanvasLayer + * @kind Method + * @private + * + * @sign public CanvasLayer.drawAll([Object rect]) + * @param rect - a rectangular region {_x: x_val, _y: y_val, _w: w_val, _h: h_val} + * + * - If rect is omitted, redraw within the viewport + * - If rect is provided, redraw within the rect + */ + _drawAll: function (rect) { + rect = rect || this._viewportRect(); + rect = Crafty.rectManager.integerBounds(rect); + var q = Crafty.map.search(rect), + i = 0, + l = q.length, + ctx = this.context, + current; + + ctx.clearRect(rect._x, rect._y, rect._w, rect._h); + + //sort the objects by the global Z + q.sort(this._sort); + for (; i < l; i++) { + current = q[i]; + if (current._visible && current._drawContext === this.context) { + current.draw(this.context); + current._changed = false; } - }, + } + }, - debug: function() { - Crafty.log(this._changedObjs); - }, + debug: function() { + Crafty.log(this._changedObjs); + }, - /** cleans up current dirty state, stores stale state for future passes */ - _clean: function () { - var rect, obj, i, l, - changed = this._changedObjs; - for (i = 0, l = changed.length; i < l; i++) { - obj = changed[i]; - rect = obj._mbr || obj; - if (typeof obj.staleRect === 'undefined') - obj.staleRect = {}; - obj.staleRect._x = rect._x; - obj.staleRect._y = rect._y; - obj.staleRect._w = rect._w; - obj.staleRect._h = rect._h; - - obj._changed = false; - } - changed.length = 0; - this._dirtyRects.length = 0; - this._dirtyViewport = false; + /** cleans up current dirty state, stores stale state for future passes */ + _clean: function () { + var rect, obj, i, l, + changed = this._changedObjs; + for (i = 0, l = changed.length; i < l; i++) { + obj = changed[i]; + rect = obj._mbr || obj; + if (typeof obj.staleRect === 'undefined') + obj.staleRect = {}; + obj.staleRect._x = rect._x; + obj.staleRect._y = rect._y; + obj.staleRect._w = rect._w; + obj.staleRect._h = rect._h; + + obj._changed = false; + } + changed.length = 0; + this._dirtyRects.length = 0; + this._dirtyViewport = false; - }, + }, - /** Takes the current and previous position of an object, and pushes the dirty regions onto the stack - * If the entity has only moved/changed a little bit, the regions are squashed together */ - _createDirty: function (obj) { + /** Takes the current and previous position of an object, and pushes the dirty regions onto the stack + * If the entity has only moved/changed a little bit, the regions are squashed together */ + _createDirty: function (obj) { - var rect = obj._mbr || obj, - dirty = this._dirtyRects, - rectManager = Crafty.rectManager; + var rect = obj._mbr || obj, + dirty = this._dirtyRects, + rectManager = Crafty.rectManager; - if (obj.staleRect) { - //If overlap, merge stale and current position together, then return - //Otherwise just push stale rectangle - if (rectManager.overlap(obj.staleRect, rect)) { - rectManager.merge(obj.staleRect, rect, obj.staleRect); - dirty.push(obj.staleRect); - return; - } else { - dirty.push(obj.staleRect); - } + if (obj.staleRect) { + //If overlap, merge stale and current position together, then return + //Otherwise just push stale rectangle + if (rectManager.overlap(obj.staleRect, rect)) { + rectManager.merge(obj.staleRect, rect, obj.staleRect); + dirty.push(obj.staleRect); + return; + } else { + dirty.push(obj.staleRect); } + } - // We use the intermediate "currentRect" so it can be modified without messing with obj - obj.currentRect._x = rect._x; - obj.currentRect._y = rect._y; - obj.currentRect._w = rect._w; - obj.currentRect._h = rect._h; - dirty.push(obj.currentRect); - - }, + // We use the intermediate "currentRect" so it can be modified without messing with obj + obj.currentRect._x = rect._x; + obj.currentRect._y = rect._y; + obj.currentRect._w = rect._w; + obj.currentRect._h = rect._h; + dirty.push(obj.currentRect); + }, - // Resize the canvas element to the current viewport - _resize: function() { - var c = Crafty.canvasLayer._canvas; - c.width = Crafty.viewport.width; - c.height = Crafty.viewport.height; - }, + // Resize the canvas element to the current viewport + _resize: function() { + var c = this._canvas; + c.width = Crafty.viewport.width; + c.height = Crafty.viewport.height; - _setPixelart: function(enabled) { - var context = Crafty.canvasLayer.context; - context.imageSmoothingEnabled = !enabled; - context.mozImageSmoothingEnabled = !enabled; - context.webkitImageSmoothingEnabled = !enabled; - context.oImageSmoothingEnabled = !enabled; - context.msImageSmoothingEnabled = !enabled; - } + }, + _setPixelart: function(enabled) { + var context = this.context; + context.imageSmoothingEnabled = !enabled; + context.mozImageSmoothingEnabled = !enabled; + context.webkitImageSmoothingEnabled = !enabled; + context.oImageSmoothingEnabled = !enabled; + context.msImageSmoothingEnabled = !enabled; } + }); -},{"../core/core.js":7}],21:[function(require,module,exports){ + +},{"../core/core.js":8}],22:[function(require,module,exports){ var Crafty = require('../core/core.js'); /**@ * #Canvas * @category Graphics + * @kind Component + * * @trigger Draw - when the entity is ready to be drawn to the stage - {type: "canvas", pos, co, ctx} * @trigger NoCanvas - if the browser does not support canvas * * When this component is added to an entity it will be drawn to the global canvas element. The canvas element (and hence all Canvas entities) is always rendered below any DOM entities. * - * Crafty.canvasLayer.init() will be automatically called if it is not called already to initialize the canvas element. + * The canvas layer will be automatically initialized if it has not been created yet. * * Create a canvas entity like this * ~~~ @@ -6543,40 +7577,27 @@ var Crafty = require('../core/core.js'); Crafty.c("Canvas", { init: function () { - var canvasLayer = Crafty.canvasLayer; - if (!canvasLayer.context) { - canvasLayer.init(); - } - this._drawLayer = canvasLayer; - this._drawContext = canvasLayer.context; - - //increment the amount of canvas objs - canvasLayer.layerCount++; + this.requires("Renderable"); + //Allocate an object to hold this components current region this.currentRect = {}; - this._changed = true; - canvasLayer.add(this); - - this.bind("Invalidate", function (e) { - //flag if changed - if (this._changed === false) { - this._changed = true; - canvasLayer.add(this); - } - - }); - + + // Add the default canvas layer if we aren't attached to a custom one + if (!this._customLayer){ + this._attachToLayer( Crafty.s("DefaultCanvasLayer")); + } + + }, - this.bind("Remove", function () { - this._drawLayer.layerCount--; - this._changed = true; - this._drawLayer.add(this); - }); + remove: function() { + this._detachFromLayer(); }, /**@ * #.draw * @comp Canvas + * @kind Method + * * @sign public this .draw([[Context ctx, ]Number x, Number y, Number w, Number h]) * @param ctx - Canvas 2D context if drawing on another canvas is required * @param x - X offset for drawing a segment @@ -6599,8 +7620,6 @@ Crafty.c("Canvas", { w: 0, h: 0 } - - }, draw: function (ctx, x, y, w, h) { @@ -6620,8 +7639,8 @@ Crafty.c("Canvas", { pos._h = (h || this._h); - context = ctx || this._drawContext; - coord = this.__coord || [0, 0, 0, 0]; + var context = ctx || this._drawContext; + var coord = this.__coord || [0, 0, 0, 0]; var co = this.drawVars.co; co.x = coord[0] + (x || 0); co.y = coord[1] + (y || 0); @@ -6674,7 +7693,7 @@ Crafty.c("Canvas", { } }); -},{"../core/core.js":7}],22:[function(require,module,exports){ +},{"../core/core.js":8}],23:[function(require,module,exports){ var Crafty = require('../core/core.js'), document = window.document; @@ -6684,6 +7703,10 @@ var Crafty = require('../core/core.js'), /**@ * #Crafty.assignColor * @category Graphics + * @kind Method + * + * Maps a wide vareity of color representations to a set of simple rgb(a) properties. + * * @sign Crafty.assignColor(color[, assignee]) * @param color - a string represenation of the color to assign, in any valid HTML format * @param assignee - an object to use instead of creating one from scratch @@ -6693,7 +7716,7 @@ var Crafty = require('../core/core.js'), */ Crafty.extend({ assignColor: (function(){ - + // Create phantom element to assess color var element = document.createElement("div"); element.style.display = "none"; @@ -6726,7 +7749,7 @@ Crafty.extend({ function hexComponent(component) { var hex = component.toString(16); - if (hex.length==1) + if (hex.length === 1) hex = "0" + hex; return hex; } @@ -6736,17 +7759,24 @@ Crafty.extend({ } function parseHexString(hex, c) { - var l; - if (hex.length === 7){ - l=2; - } else if (hex.length === 4){ - l=1; + var r, g, b, + l = hex.length; + + if (l === 7) { + r = hex.substr(1, 2); + g = hex.substr(3, 2); + b = hex.substr(5, 2); + } else if (l === 4) { + r = hex.substr(1, 1); r += r; + g = hex.substr(2, 1); g += g; + b = hex.substr(3, 1); b += b; } else { return default_value(c); } - c._red = parseInt(hex.substr(1, l), 16); - c._green = parseInt(hex.substr(1+l, l), 16); - c._blue = parseInt(hex.substr(1+2*l, l), 16); + c._red = parseInt(r, 16); + c._green = parseInt(g, 16); + c._blue = parseInt(b, 16); + return c; } @@ -6754,8 +7784,8 @@ Crafty.extend({ function parseRgbString(rgb, c) { var values = rgb_regex.exec(rgb); - if( values===null || (values.length != 4 && values.length != 5)) { - return default_value(c); // return bad result? + if( values === null || (values.length !== 4 && values.length !== 5)) { + return default_value(c); // return bad result? } c._red = Math.round(parseFloat(values[1])); c._green = Math.round(parseFloat(values[2])); @@ -6812,20 +7842,31 @@ Crafty.extend({ // Define some variables required for webgl -var COLOR_VERTEX_SHADER = "attribute vec2 aPosition;\nattribute vec3 aOrientation;\nattribute vec2 aLayer;\nattribute vec4 aColor;\n\nvarying lowp vec4 vColor;\n\nuniform vec4 uViewport;\n\nmat4 viewportScale = mat4(2.0 / uViewport.z, 0, 0, 0, 0, -2.0 / uViewport.w, 0,0, 0, 0,1,0, -1,+1,0,1);\nvec4 viewportTranslation = vec4(uViewport.xy, 0, 0);\n\nvoid main() {\n vec2 pos = aPosition;\n vec2 entityOrigin = aOrientation.xy;\n mat2 entityRotationMatrix = mat2(cos(aOrientation.z), sin(aOrientation.z), -sin(aOrientation.z), cos(aOrientation.z));\n\n pos = entityRotationMatrix * (pos - entityOrigin) + entityOrigin;\n gl_Position = viewportScale * (viewportTranslation + vec4(pos, 1.0/(1.0+exp(aLayer.x) ), 1) );\n vColor = vec4(aColor.rgb*aColor.a*aLayer.y, aColor.a*aLayer.y);\n}"; -var COLOR_FRAGMENT_SHADER = "precision mediump float;\nvarying lowp vec4 vColor;\nvoid main(void) {\n\tgl_FragColor = vColor;\n}"; -var COLOR_ATTRIBUTE_LIST = [ - {name:"aPosition", width: 2}, - {name:"aOrientation", width: 3}, - {name:"aLayer", width:2}, - {name:"aColor", width: 4} -]; - +Crafty.defaultShader("Color", new Crafty.WebGLShader( + "attribute vec2 aPosition;\nattribute vec3 aOrientation;\nattribute vec2 aLayer;\nattribute vec4 aColor;\n\nvarying lowp vec4 vColor;\n\nuniform vec4 uViewport;\n\nmat4 viewportScale = mat4(2.0 / uViewport.z, 0, 0, 0, 0, -2.0 / uViewport.w, 0,0, 0, 0,1,0, -1,+1,0,1);\nvec4 viewportTranslation = vec4(uViewport.xy, 0, 0);\n\nvoid main() {\n vec2 pos = aPosition;\n vec2 entityOrigin = aOrientation.xy;\n mat2 entityRotationMatrix = mat2(cos(aOrientation.z), sin(aOrientation.z), -sin(aOrientation.z), cos(aOrientation.z));\n\n pos = entityRotationMatrix * (pos - entityOrigin) + entityOrigin;\n gl_Position = viewportScale * (viewportTranslation + vec4(pos, 1.0/(1.0+exp(aLayer.x) ), 1) );\n vColor = vec4(aColor.rgb*aColor.a*aLayer.y, aColor.a*aLayer.y);\n}", + "precision mediump float;\nvarying lowp vec4 vColor;\nvoid main(void) {\n\tgl_FragColor = vColor;\n}", + [ + { name: "aPosition", width: 2 }, + { name: "aOrientation", width: 3 }, + { name: "aLayer", width: 2 }, + { name: "aColor", width: 4 } + ], + function(e, entity) { + e.program.writeVector("aColor", + entity._red/255, + entity._green/255, + entity._blue/255, + entity._strength + ); + } +)); /**@ * #Color * @category Graphics + * @kind Component + * * Draw a colored rectangle. */ Crafty.c("Color", { @@ -6838,12 +7879,16 @@ Crafty.c("Color", { init: function () { this.bind("Draw", this._drawColor); - if (this.has("WebGL")){ - this._establishShader("Color", COLOR_FRAGMENT_SHADER, COLOR_VERTEX_SHADER, COLOR_ATTRIBUTE_LIST); + if (this._drawLayer) { + this._setupColor(this._drawLayer); } this.trigger("Invalidate"); }, + events: { + "LayerAttached": "_setupColor" + }, + remove: function(){ this.unbind("Draw", this._drawColor); if (this.has("DOM")){ @@ -6852,6 +7897,12 @@ Crafty.c("Color", { this.trigger("Invalidate"); }, + _setupColor: function(layer) { + if (layer.type === "WebGL") { + this._establishShader("Color", Crafty.defaultShader("Color")); + } + }, + // draw function for "Color" _drawColor: function(e){ if (!this._color) { return; } @@ -6862,18 +7913,15 @@ Crafty.c("Color", { e.ctx.fillStyle = this._color; e.ctx.fillRect(e.pos._x, e.pos._y, e.pos._w, e.pos._h); } else if (e.type === "webgl"){ - e.program.writeVector("aColor", - this._red/255, - this._green/255, - this._blue/255, - this._strength - ); + e.program.draw(e, this); } }, /**@ * #.color * @comp Color + * @kind Method + * * @trigger Invalidate - when the color changes * * Will assign the color and opacity, either through a string shorthand, or through explicit rgb values. @@ -6885,7 +7933,7 @@ Crafty.c("Color", { * @param r - value for the red channel * @param g - value for the green channel * @param b - value for the blue channel - * @param strength - the opacity of the rectangle + * @param strength - the opacity of the rectangle * * @sign public String .color() * @return A string representing the current color as a CSS property. @@ -6896,7 +7944,7 @@ Crafty.c("Color", { * c.color("#FF0000"); * c.color("red"); * c.color(255, 0, 0); - * c.color("rgb(255, 0, 0") + * c.color("rgb(255, 0, 0)"); * ``` * Three different ways of assign the color red. * ``` @@ -6920,7 +7968,7 @@ Crafty.c("Color", { Crafty.assignColor(color, this); // Second argument, if present, is strength of color // Note that assignColor will give a default strength of 1.0 if none exists. - if (typeof arguments[1] == "number") + if (typeof arguments[1] === "number") this._strength = arguments[1]; } this._color = "rgba(" + this._red + ", " + this._green + ", " + this._blue + ", " + this._strength + ")"; @@ -6930,13 +7978,14 @@ Crafty.c("Color", { }); -},{"../core/core.js":7}],23:[function(require,module,exports){ +},{"../core/core.js":8}],24:[function(require,module,exports){ var Crafty = require('../core/core.js'), document = window.document; Crafty.extend({ /**@ * #Crafty.domHelper + * @kind Property * @category Graphics * * Collection of utilities for using the DOM. @@ -6973,6 +8022,8 @@ Crafty.extend({ /**@ * #Crafty.domHelper.getStyle * @comp Crafty.domHelper + * @kind Method + * * @sign public Object Crafty.domHelper.getStyle(HTMLElement obj, String property) * @param obj - HTML element to find the style * @param property - Style to return @@ -7012,169 +8063,255 @@ Crafty.extend({ /**@ * #Crafty.domHelper.translate * @comp Crafty.domHelper - * @sign public Object Crafty.domHelper.translate(Number clientX, Number clientY) + * @kind Method + * + * @sign public Object Crafty.domHelper.translate(Number clientX, Number clientY[, DrawLayer layer]) * @param clientX - clientX position in the browser screen * @param clientY - clientY position in the browser screen + * @param layer - a Crafty draw layer * @return Object `{x: ..., y: ...}` with Crafty coordinates. * * The parameters clientX and clientY are pixel coordinates within the visible * browser window. This function translates those to Crafty coordinates (i.e., * the coordinates that you might apply to an entity), by taking into account * where the stage is within the screen, what the current viewport is, etc. + * + * If a draw layer is specified, the returned object will take into account any special scaling rules for that object. */ - translate: function (clientX, clientY) { + translate: function (clientX, clientY, layer) { var doc = document.documentElement; var body = document.body; - - return { - x: (clientX - Crafty.stage.x + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 )) / Crafty.viewport._scale - Crafty.viewport._x, - y: (clientY - Crafty.stage.y + ( doc && doc.scrollTop || body && body.scrollTop || 0 )) / Crafty.viewport._scale - Crafty.viewport._y - }; + var view; + // The branch here is to deal with the fact that the viewport position is the distance TO the origin, not from + // But the _viewportRect is the opposite -- it uses the same convention as a rectangle that matches the viewport in that layer + // At some point this should be simplified, probably by altering the viewport to use the more intuitive coordinates + if (layer) { + view = layer._viewportRect(); + return { + x: (clientX - Crafty.stage.x + (doc && doc.scrollLeft || body && body.scrollLeft || 0)) / view._scale + view._x, + y: (clientY - Crafty.stage.y + (doc && doc.scrollTop || body && body.scrollTop || 0)) / view._scale + view._y + }; + } else { + view = Crafty.viewport; + return { + x: (clientX - Crafty.stage.x + (doc && doc.scrollLeft || body && body.scrollLeft || 0)) / view._scale - view._x, + y: (clientY - Crafty.stage.y + (doc && doc.scrollTop || body && body.scrollTop || 0)) / view._scale - view._y + }; + } } } }); -},{"../core/core.js":7}],24:[function(require,module,exports){ +},{"../core/core.js":8}],25:[function(require,module,exports){ var Crafty = require('../core/core.js'), document = window.document; /**@ - * #Crafty.domLayer + * #DomLayer * @category Graphics + * @kind System * * Collection of mostly private methods to represent entities using the DOM. */ -Crafty.extend({ - domLayer: { - _changedObjs: [], - _dirtyViewport: false, - _div: null, - - init: function () { - // Set properties to initial values -- necessary on a restart - this._changedObjs = []; - this._dirtyViewport = false; +Crafty._registerLayerTemplate("DOM", { + type: "DOM", + options: { + xResponse: 1, + yResponse: 1, + scaleResponse: 1, + z: 0 + }, - // Create the div that will contain DOM elements - var div = this._div = document.createElement("div"); + _changedObjs: [], + _dirtyViewport: false, - Crafty.stage.elem.appendChild(div); - div.style.position = "absolute"; - div.style.zIndex = "1"; - div.style.transformStyle = "preserve-3d"; // Seems necessary for Firefox to preserve zIndexes? + /**@ + * #._div + * @comp DomLayer + * @kind Property + * @private + * + * A div inside the `#cr-stage` div that holds all DOM entities. + */ + _div: null, - // Bind scene rendering (see drawing.js) - Crafty.uniqueBind("RenderScene", this._render); + init: function () { + // Avoid shared state between systems + this._changedObjs = []; - // Layers should generally listen for resize events, but the DOM layers automatically inherit the stage's dimensions + // Create the div that will contain DOM elements + var div = this._div = document.createElement("div"); - // Listen for changes in pixel art settings - // Since window is inited before stage, can't set right away, but shouldn't need to! - Crafty.uniqueBind("PixelartSet", this._setPixelArt); + Crafty.stage.elem.appendChild(div); + div.style.position = "absolute"; + div.style.zIndex = this.options.z; + div.style.transformStyle = "preserve-3d"; // Seems necessary for Firefox to preserve zIndexes? - Crafty.uniqueBind("InvalidateViewport", function() { - Crafty.domLayer._dirtyViewport = true; - }); - }, + // Bind scene rendering (see drawing.js) + this.uniqueBind("RenderScene", this._render); - // Handle whether images should be smoothed or not - _setPixelArt: function(enabled) { - var style = Crafty.domLayer._div.style; - var camelize = Crafty.domHelper.camelize; - if (enabled) { - style[camelize("image-rendering")] = "optimizeSpeed"; /* legacy */ - style[camelize("image-rendering")] = "-moz-crisp-edges"; /* Firefox */ - style[camelize("image-rendering")] = "-o-crisp-edges"; /* Opera */ - style[camelize("image-rendering")] = "-webkit-optimize-contrast"; /* Webkit (Chrome & Safari) */ - style[camelize("-ms-interpolation-mode")] = "nearest-neighbor"; /* IE */ - style[camelize("image-rendering")] = "optimize-contrast"; /* CSS3 proposed */ - style[camelize("image-rendering")] = "pixelated"; /* CSS4 proposed */ - style[camelize("image-rendering")] = "crisp-edges"; /* CSS4 proposed */ - } else { - style[camelize("image-rendering")] = "optimizeQuality"; /* legacy */ - style[camelize("-ms-interpolation-mode")] = "bicubic"; /* IE */ - style[camelize("image-rendering")] = "auto"; /* CSS3 */ - } - }, + // Layers should generally listen for resize events, but the DOM layers automatically inherit the stage's dimensions - /**@ - * #Crafty.domLayer.debug - * @comp Crafty.domLayer - * @sign public Crafty.domLayer.debug() - */ - debug: function () { - Crafty.log(this._changedObjs); - }, + // Listen for changes in pixel art settings + // Since window is inited before stage, can't set right away, but shouldn't need to! + this.uniqueBind("PixelartSet", this._setPixelArt); + this.uniqueBind("InvalidateViewport", function() { + this._dirtyViewport = true; + }); + Crafty._addDrawLayerInstance(this); + }, - /**@ - * #Crafty.domLayer._render - * @comp Crafty.domLayer - * @sign public Crafty.domLayer.render() - * - * When "RenderScene" is triggered, draws all DOM entities that have been flagged - * - * @see DOM#.draw - */ - _render: function () { - var layer = Crafty.domLayer; - var changed = layer._changedObjs; - // Adjust the viewport - if (layer._dirtyViewport) { - layer._setViewport(); - layer._dirtyViewport = false; - } + // Cleanup the DOM when the layer is destroyed + remove: function() { + this._div.parentNode.removeChild(this._div); + Crafty._removeDrawLayerInstance(this); + }, - //if no objects have been changed, stop - if (!changed.length) return; + // Handle whether images should be smoothed or not + _setPixelArt: function(enabled) { + var style = this._div.style; + var camelize = Crafty.domHelper.camelize; + if (enabled) { + style[camelize("image-rendering")] = "optimizeSpeed"; /* legacy */ + style[camelize("image-rendering")] = "-moz-crisp-edges"; /* Firefox */ + style[camelize("image-rendering")] = "-o-crisp-edges"; /* Opera */ + style[camelize("image-rendering")] = "-webkit-optimize-contrast"; /* Webkit (Chrome & Safari) */ + style[camelize("-ms-interpolation-mode")] = "nearest-neighbor"; /* IE */ + style[camelize("image-rendering")] = "optimize-contrast"; /* CSS3 proposed */ + style[camelize("image-rendering")] = "pixelated"; /* CSS4 proposed */ + style[camelize("image-rendering")] = "crisp-edges"; /* CSS4 proposed */ + } else { + style[camelize("image-rendering")] = "optimizeQuality"; /* legacy */ + style[camelize("-ms-interpolation-mode")] = "bicubic"; /* IE */ + style[camelize("image-rendering")] = "auto"; /* CSS3 */ + } + }, - var i = 0, - k = changed.length; - //loop over all DOM elements needing updating - for (; i < k; ++i) { - changed[i].draw()._changed = false; - } + /**@ + * #.debug + * @comp DomLayer + * @kind Method + * + * @sign public .debug() + * + * Logs the current list of entities that have been invalidated in this layer. + */ + debug: function () { + Crafty.log(this._changedObjs); + }, - //reset DOM array - changed.length = 0; - }, + /**@ + * #._render + * @comp DomLayer + * @kind Method + * @private + * + * @sign public .render() + * + * When "RenderScene" is triggered, draws all DOM entities that have been flagged + * + * @see DOM#.draw + */ + _render: function () { + var changed = this._changedObjs; + // Adjust the viewport + if (this._dirtyViewport) { + this._setViewport(); + this._dirtyViewport = false; + } - /**@ - * #Crafty.domLayer.add - * @comp Crafty.domLayer - * @sign public Crafty.domLayer.add(ent) - * @param ent - The entity to add - * - * Add an entity to the list of DOM object to draw - */ - add: function add(ent) { - this._changedObjs.push(ent); - }, + //if no objects have been changed, stop + if (!changed.length) return; - // Sets the viewport position and scale - // Called by render when the dirtyViewport flag is set - _setViewport: function() { - var style = Crafty.domLayer._div.style, - view = Crafty.viewport; + var i = 0, + k = changed.length; + //loop over all DOM elements needing updating + for (; i < k; ++i) { + changed[i].draw()._changed = false; + } - style.transform = style[Crafty.support.prefix + "Transform"] = "scale(" + view._scale + ", " + view._scale + ")"; - style.left = Math.round(view._x * view._scale) + "px"; - style.top = Math.round(view._y * view._scale) + "px"; - style.zIndex = 10; + //reset DOM array + changed.length = 0; + }, - } + /**@ + * #.dirty + * @comp DomLayer + * @kind Method + * @private + * + * @sign public .dirty(ent) + * @param ent - The entity to mark as dirty + * + * Add an entity to the list of DOM object to draw + */ + dirty: function add(ent) { + this._changedObjs.push(ent); + }, + + /**@ + * #.attach + * @comp DomLayer + * @kind Method + * @private + * + * @sign public .attach(ent) + * @param ent - The entity to add + * + * Add an entity to the layer + */ + attach: function attach(ent) { + ent._drawContext = this.context; + // attach the entity's div element to the dom layer + this._div.appendChild(ent._element); + // set position style and entity id + ent._element.style.position = "absolute"; + ent._element.id = "ent" + ent[0]; + }, + + /**@ + * #.detach + * @comp DomLayer + * @kind Method + * @private + * + * @sign public .detach(ent) + * @param ent - The entity to remove + * + * Removes an entity from the layer + */ + detach: function detach(ent) { + this._div.removeChild(ent._element); + }, + + // Sets the viewport position and scale + // Called by render when the dirtyViewport flag is set + _setViewport: function() { + var style = this._div.style, + view = this._viewportRect(); + + var scale = view._scale; + var dx = -view._x * scale; + var dy = -view._y * scale; + style.transform = style[Crafty.support.prefix + "Transform"] = "scale(" + scale + ", " + scale + ")"; + style.left = Math.round(dx) + "px"; + style.top = Math.round(dy) + "px"; + style.zIndex = this.options.z; } + }); -},{"../core/core.js":7}],25:[function(require,module,exports){ +},{"../core/core.js":8}],26:[function(require,module,exports){ var Crafty = require('../core/core.js'), document = window.document; /**@ * #DOM * @category Graphics + * @kind Component * * A component which renders entities as DOM nodes, specifically `
`s. */ @@ -7182,6 +8319,7 @@ Crafty.c("DOM", { /**@ * #._element * @comp DOM + * @kind Property * The DOM element used to represent the entity. */ _element: null, @@ -7191,17 +8329,15 @@ Crafty.c("DOM", { /**@ * #.avoidCss3dTransforms * @comp DOM + * @kind Property + * * Avoids using of CSS 3D Transform for positioning when true. Default value is false. */ avoidCss3dTransforms: false, init: function () { - var domLayer = Crafty.domLayer; - if (!domLayer._div) { - domLayer.init(); - } - this._drawLayer = domLayer; - + this.requires("Renderable"); + this._cssStyles = { visibility: '', left: '', @@ -7214,28 +8350,27 @@ Crafty.c("DOM", { transform: '' }; this._element = document.createElement("div"); - domLayer._div.appendChild(this._element); - this._element.style.position = "absolute"; - this._element.id = "ent" + this[0]; - this.bind("Invalidate", this._invalidateDOM); + // Attach the entity to the dom layer + if (!this._customLayer){ + this._attachToLayer( Crafty.s("DefaultDOMLayer") ); + } + this.bind("NewComponent", this._updateClass); this.bind("RemoveComponent", this._removeClass); - - this._invalidateDOM(); - }, remove: function(){ - this.undraw(); + this._detachFromLayer(); this.unbind("NewComponent", this._updateClass); this.unbind("RemoveComponent", this._removeClass); - this.unbind("Invalidate", this._invalidateDOM); }, /**@ * #.getDomId * @comp DOM + * @kind Method + * * @sign public this .getId() * * Get the Id of the DOM element used to represent the entity. @@ -7246,13 +8381,13 @@ Crafty.c("DOM", { // removes a component on RemoveComponent events _removeClass: function(removedComponent) { - var i = 0, + var comp, c = this.__c, str = ""; - for (i in c) { - if(i != removedComponent) { - str += ' ' + i; - } + for (comp in c) { + if (comp !== removedComponent) { + str += ' ' + comp; + } } str = str.substr(1); this._element.className = str; @@ -7260,37 +8395,35 @@ Crafty.c("DOM", { // adds a class on NewComponent events _updateClass: function() { - var i = 0, + var comp, c = this.__c, str = ""; - for (i in c) { - str += ' ' + i; + for (comp in c) { + str += ' ' + comp; } str = str.substr(1); this._element.className = str; }, - _invalidateDOM: function(){ - if (!this._changed) { - this._changed = true; - this._drawLayer.add(this); - } - }, - /**@ * #.DOM * @comp DOM + * @kind Method + * * @trigger Draw - when the entity is ready to be drawn to the stage - { style:String, type:"DOM", co} * @sign public this .DOM(HTMLElement elem) * @param elem - HTML element that will replace the dynamically created one * * Pass a DOM element to use rather than one created. Will set `._element` to this value. Removes the old element. + * + * Will reattach the entity to the current draw layer */ DOM: function (elem) { if (elem && elem.nodeType) { - this.undraw(); + var layer = this._drawLayer; + this._detachFromLayer(); this._element = elem; - this._element.style.position = 'absolute'; + this._attachToLayer(layer); } return this; }, @@ -7298,6 +8431,9 @@ Crafty.c("DOM", { /**@ * #.draw * @comp DOM + * @kind Method + * @private + * * @sign public this .draw(void) * * Updates the CSS properties of the node to draw on the stage. @@ -7372,7 +8508,7 @@ Crafty.c("DOM", { trans.push("scaleY(-1)"); } - if (this._cssStyles.transform != trans.join(" ")) { + if (this._cssStyles.transform !== trans.join(" ")) { this._cssStyles.transform = trans.join(" "); style.transform = this._cssStyles.transform; style[prefix + "Transform"] = this._cssStyles.transform; @@ -7387,24 +8523,11 @@ Crafty.c("DOM", { return this; }, - /**@ - * #.undraw - * @comp DOM - * @sign public this .undraw(void) - * - * Removes the element from the stage. - */ - undraw: function () { - var el = this._element; - if (el && el.parentNode !== null) { - el.parentNode.removeChild(el); - } - return this; - }, - /**@ * #.css * @comp DOM + * @kind Method + * * @sign public css(String property, String value) * @param property - CSS property to modify * @param value - Value to give the CSS property @@ -7463,13 +8586,15 @@ Crafty.c("DOM", { } }); -},{"../core/core.js":7}],26:[function(require,module,exports){ +},{"../core/core.js":8}],27:[function(require,module,exports){ var Crafty = require('../core/core.js'); Crafty.extend({ /**@ * #Crafty.pixelart * @category Graphics + * @kind Method + * * @sign public void Crafty.pixelart(Boolean enabled) * @param enabled - whether to preserve sharp edges when rendering images * @@ -7496,7 +8621,6 @@ Crafty.extend({ * @example * This is the preferred way to draw pixel art with the best cross-browser compatibility. * ~~~ - * Crafty.canvasLayer.init(); * Crafty.pixelart(true); * * Crafty.sprite(imgWidth, imgHeight, "spriteMap.png", {sprite1:[0,0]}); @@ -7510,42 +8634,45 @@ Crafty.extend({ } }); -},{"../core/core.js":7}],27:[function(require,module,exports){ +},{"../core/core.js":8}],28:[function(require,module,exports){ var Crafty = require('../core/core.js'); // An object for wrangling textures // An assumption here is that doing anything with textures is fairly expensive, so the code should be expressive rather than performant -var TextureManager = Crafty.TextureManager = function(gl, webgl) { - this.gl = gl; - this.webgl = webgl; - // The maximum number of units the environment says it supports - this.max_units = gl.getParameter(gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS); - // An array of textures bound to a texture unit; position corresponds to the unit in question - this.bound_textures = []; - // A dictionary of registered textures, so that multiple copies of the same texture aren't generated - this.registered_textures = {}; - // Try to track which texture is active - this.active = null; -}; +Crafty.TextureManager = TextureManager; + +function TextureManager (gl, webgl) { + this.gl = gl; + this.webgl = webgl; + // The maximum number of units the environment says it supports + this.max_units = gl.getParameter(gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS); + // An array of textures bound to a texture unit; position corresponds to the unit in question + this.bound_textures = []; + // A dictionary of registered textures, so that multiple copies of the same texture aren't generated + this.registered_textures = {}; + // Try to track which texture is active + this.active = null; +} TextureManager.prototype = { - // Clear out the bound textures and other existing state - reset: function(){ - var t; - for (var i = 0; i < this.bound_textures.length; i++){ - t = this.bound_textures[i]; - t.unbind(); - } - this.bound_textures = []; - this.active = null; - }, + // Clear out the bound textures and other existing state + reset: function(){ + var t; + for (var i = 0; i < this.bound_textures.length; i++){ + t = this.bound_textures[i]; + t.unbind(); + } + this.bound_textures = []; + this.active = null; + }, - // creates a texture out of the given image and repeating state - // The url is just used to generate a unique id for the texture - makeTexture: function(url, image, repeating) { - // gl is the context, webgl the Crafty object containing prefs/etc - var gl = this.gl, webgl = this.webgl; + // creates a texture out of the given image and repeating state + // The url is just used to generate a unique id for the texture + makeTexture: function(url, image, repeating) { + // gl is the context, webgl is the Crafty object containing prefs/etc + // var gl = this.gl; + var webgl = this.webgl; // Check whether a texture that matches the one requested already exists var id = "texture-(r:" + repeating + ")-" + url; @@ -7567,139 +8694,142 @@ TextureManager.prototype = { // Returns the bound texture of smallest size // If we have more textures than available units, we should preferentially leave the larger textures bound? - smallest: function() { - var min_size = Infinity; - var index = null; - for (var i=0; i= 0) { + this._drawLayers.splice(i, 1); + } + this._drawLayers.sort(function (a, b) { return a.options.z - b.options.z; }); + }, + + _registerLayerTemplate: function (type, layerTemplate) { + this._drawLayerTemplates[type] = layerTemplate; + var common = this._commonLayerProperties; + for (var key in common) { + if (layerTemplate[key]) continue; + layerTemplate[key] = common[key]; + } + // A marker to avoid creating temporary objects + layerTemplate._viewportRectHolder = {}; + }, + + _commonLayerProperties: { + // Based on the camera options, find the Crafty coordinates corresponding to the layer's position in the viewport + _viewportRect: function () { + var options = this.options; + var rect = this._viewportRectHolder; + var scale = Math.pow(Crafty.viewport._scale, options.scaleResponse); + var viewport = Crafty.viewport; + rect._scale = scale; + rect._w = viewport._width / scale; + rect._h = viewport._height / scale; + + + // This particular transformation is designed such that, + // if a combination pan/scale keeps the center of the screen fixed for a layer with x/y response of 1, + // then it will also be fixed for layers with other values for x/y response + // (note that the second term vanishes when either the response or scale are 1) + rect._x = options.xResponse * (-viewport._x) - + 0.5 * (options.xResponse - 1) * (1 - 1 / scale) * viewport._width; + rect._y = options.yResponse * (-viewport._y) - + 0.5 * (options.yResponse - 1) * (1 - 1 / scale) * viewport._height; + return rect; + }, + // A tracker for whether any elements in this layer need to listen to mouse/touch events + _pointerEntities: 0 + }, + + /**@ + * #Crafty.createLayer + * @kind Method + * @category Graphics + * + * @sign public void Crafty.createLayer(string name, string type[, object options]) + * @param name - the name that will refer to the layer + * @param type - the type of the draw layer to create ('DOM', 'Canvas', or 'WebGL') + * @param options - this will override the default values of each layer + * + * Creates a new instance of the specified type of layer. The options (and their default values) are + * + * ``` + * { + * xResponse: 1, // How the layer will pan in response to the viewport x position + * yResponse: 1, // How the layer will pan in response to the viewport y position + * scaleResponse: 1, // How the layer will scale in response to the viewport scale. (Layer scale will be scale^scaleResponse.) + * z: 0 // The zIndex of the layer relative to other layers + * } + * ``` + * + * Crafty will automatically define three built-in layers: "DefaultDOMLayer", DefaultCanvasLayer", and "DefaultWebGLLayer". + * They will have `z` values of `30`, `20`, and `10` respectively, and will be initialized if a "DOM", "Canvas" or "WebGL" component + * is used with an entity not attached to any user-specified layer. + * + * @example + * ``` + * Crafty.s("MyCanvasLayer", "Canvas") + * Crafty.e("2D, MyCanvasLayer, Color"); + * ``` + * Define a custom canvas layer, then create an entity that uses the custom layer to render. + * + * @example + * ``` + * Crafty.s("UILayer", "DOM", {scaleResponse: 0, xResponse: 0, yResponse: 0}) + * Crafty.e("2D, UILayer, Text"); + * ``` + * Define a custom DOM layer that will not move with the camera. (Useful for static UI elements!) + */ + createLayer: function createLayer(name, type, options) { + var layerTemplate = this._drawLayerTemplates[type]; + Crafty.s(name, layerTemplate, options); + Crafty.c(name, { + init: function () { + this.requires("Renderable"); + + // Flag to indicate that the base component doesn't need to attach a layer + this._customLayer = true; + this.requires(layerTemplate.type); + this._attachToLayer(Crafty.s(name)); + }, -},{"../core/core.js":7}],30:[function(require,module,exports){ + remove: function () { + this._detachFromLayer(); + } + }); + } +}); +},{"../core/core.js":8}],32:[function(require,module,exports){ var Crafty = require('../core/core.js'), document = window.document; /**@ * #Particles * @category Graphics + * @kind Component + * * @trigger ParticleEnd - when the particle animation has finished * * Based on Parcycle by Mr. Speaker, licensed under the MIT, Ported by Leo Koppelkamm @@ -7940,11 +9202,14 @@ Crafty.c("Particles", { //We need to clone it this._Particles = Crafty.clone(this._Particles); this._Particles.parentEntity = this; + this._particlesPaused = false; }, /**@ * #.particles * @comp Particles + * @kind Method + * * @sign public this .particles(Object options) * @param options - Map of options that specify the behavior and look of the particles. * @@ -8026,6 +9291,7 @@ Crafty.c("Particles", { }; this.bind('EnterFrame', function () { + if (this._particlesPaused) return; relativeX = this.x + Crafty.viewport.x; relativeY = this.y + Crafty.viewport.y; this._Particles.viewportDelta = { @@ -8041,7 +9307,7 @@ Crafty.c("Particles", { this._Particles.position = this._Particles.vectorHelpers.create(relativeX, relativeY); //Selective clearing - if (typeof Crafty.rectManager.boundingRect == 'function') { + if (typeof Crafty.rectManager.boundingRect === 'function') { bounding = Crafty.rectManager.boundingRect(this._Particles.register); if (bounding) ctx.clearRect(bounding._x, bounding._y, bounding._w, bounding._h); } else { @@ -8104,11 +9370,11 @@ Crafty.c("Particles", { init: function (options) { this.position = this.vectorHelpers.create(0, 0); - if (typeof options == 'undefined') options = {}; + if (typeof options === 'undefined') options = {}; //Create current config by merging given options and presets. for (var key in this.presets) { - if (typeof options[key] != 'undefined') this[key] = options[key]; + if (typeof options[key] !== 'undefined') this[key] = options[key]; else this[key] = this.presets[key]; } @@ -8117,7 +9383,7 @@ Crafty.c("Particles", { }, addParticle: function () { - if (this.particleCount == this.maxParticles) { + if (this.particleCount === this.maxParticles) { return false; } @@ -8179,7 +9445,7 @@ Crafty.c("Particles", { this.emitCounter -= rate; } this.elapsedFrames++; - if (this.duration != -1 && this.duration < this.elapsedFrames) { + if (this.duration !== -1 && this.duration < this.elapsedFrames) { this.stop(); } } @@ -8226,7 +9492,7 @@ Crafty.c("Particles", { this.particleIndex++; } else { // Replace particle with the last active - if (this.particleIndex != this.particleCount - 1) { + if (this.particleIndex !== this.particleCount - 1) { this.particles[this.particleIndex] = this.particles[this.particleCount - 1]; } this.particleCount--; @@ -8303,590 +9569,848 @@ Crafty.c("Particles", { return vector1; } } + }, + /**@ + * #.pauseParticles + * @comp Particles + * @kind Method + * + * @sign public this.pauseParticles() + * + * The pauseParticles will freeze these particles in execution. + * + * @example + * ~~~ + * // start particle animation + * var ent = Crafty.e("Particles").particles(someParticleConfig); + * + * // and some time later, the gameplay is paused (or only + * // a part of it is frozen) + * ent.pauseParticles(); + * ~~~ + */ + pauseParticles: function() { + this._particlesPaused = true; + }, + /**@ + * #.resumeParticles + * @comp Particles + * @kind Method + * + * @sign public this.resumeParticles() + * + * The resumeParticles will resume earlier paused particles + * + * @example + * ~~~ + * // start particle animation + * var ent = Crafty.e("Particles").particles(someParticleConfig); + * + * // and some time later, the gameplay is paused (or only + * // a part of it is frozen) + * ent.pauseParticles(); + * + * // and we resume the particles again + * ent.resumeParticles(); + * ~~~ + */ + resumeParticles: function() { + this._particlesPaused = false; } }); -},{"../core/core.js":7}],31:[function(require,module,exports){ +},{"../core/core.js":8}],33:[function(require,module,exports){ var Crafty = require('../core/core.js'); /**@ -* #SpriteAnimation -* @category Animation -* @trigger StartAnimation - When an animation starts playing, or is resumed from the paused state - {Reel} -* @trigger AnimationEnd - When the animation finishes - { Reel } -* @trigger FrameChange - Each time the frame of the current reel changes - { Reel } -* @trigger ReelChange - When the reel changes - { Reel } -* -* Used to animate sprites by treating a sprite map as a set of animation frames. -* Must be applied to an entity that has a sprite-map component. -* -* To define an animation, see the `reel` method. To play an animation, see the `animate` method. -* -* A reel is an object that contains the animation frames and current state for an animation. The reel object has the following properties: -* @param id: (String) - the name of the reel -* @param frames: (Array) - A list of frames in the format [xpos, ypos] -* @param currentFrame: (Number) - The index of the current frame -* @param easing: (Crafty.easing object) - The object that handles the internal progress of the animation. -* @param duration: (Number) - The duration in milliseconds. -* -* Many animation related events pass a reel object as data. As typical with events, this should be treated as read only data that might be later altered by the entity. If you wish to preserve the data, make a copy of it. -* -* @see Crafty.sprite -*/ -Crafty.c("SpriteAnimation", { - /* - * - * A map in which the keys are the names assigned to animations defined using - * the component (also known as reelIDs), and the values are objects describing - * the animation and its state. - */ - _reels: null, - - /* - * The reelID of the currently active reel (which is one of the elements in `this._reels`). - * This value is `null` if no reel is active. Some of the component's actions can be invoked - * without specifying a reel, in which case they will work on the active reel. - */ - _currentReelId: null, - - /* - * The currently active reel. - * This value is `null` if no reel is active. - */ - _currentReel: null, - - /* - * Whether or not an animation is currently playing. - */ - _isPlaying: false, - - /**@ - * #.animationSpeed - * @comp SpriteAnimation - * - * The playback rate of the animation. This property defaults to 1. - */ - animationSpeed: 1, - - - init: function () { - this._reels = {}; - }, - - /**@ - * #.reel - * @comp SpriteAnimation - * Used to define reels, to change the active reel, and to fetch the id of the active reel. - * - * @sign public this .reel(String reelId, Duration duration, Number fromX, Number fromY, Number frameCount) - * Defines a reel by starting and ending position on the sprite sheet. - * @param reelId - ID of the animation reel being created - * @param duration - The length of the animation in milliseconds. - * @param fromX - Starting `x` position on the sprite map (x's unit is the horizontal size of the sprite in the sprite map). - * @param fromY - `y` position on the sprite map (y's unit is the horizontal size of the sprite in the sprite map). Remains constant through the animation. - * @param frameCount - The number of sequential frames in the animation. If negative, the animation will play backwards. - * - * @sign public this .reel(String reelId, Duration duration, Array frames) - * Defines a reel by an explicit list of frames - * @param reelId - ID of the animation reel being created - * @param duration - The length of the animation in milliseconds. - * @param frames - An array of arrays containing the `x` and `y` values of successive frames: [[x1,y1],[x2,y2],...] (the values are in the unit of the sprite map's width/height respectively). - * - * @sign public this .reel(String reelId) - * Switches to the specified reel. The sprite will be updated to that reel's current frame - * @param reelID - the ID to switch to - * - * @sign public Reel .reel() - * @return The id of the current reel - * - * - * A method to handle animation reels. Only works for sprites built with the Crafty.sprite methods. - * See the Tween component for animation of 2D properties. - * - * To setup an animation reel, pass the name of the reel (used to identify the reel later), and either an - * array of absolute sprite positions or the start x on the sprite map, the y on the sprite map and then the end x on the sprite map. - * - * - * @example - * ~~~ - * // Define a sprite-map component - * Crafty.sprite(16, "images/sprite.png", { - * PlayerSprite: [0,0] - * }); - * - * // Define an animation on the second row of the sprite map (fromY = 1) - * // from the left most sprite (fromX = 0) to the fourth sprite - * // on that row (frameCount = 4), with a duration of 1 second - * Crafty.e("2D, DOM, SpriteAnimation, PlayerSprite").reel('PlayerRunning', 1000, 0, 1, 4); - * - * // This is the same animation definition, but using the alternative method - * Crafty.e("2D, DOM, SpriteAnimation, PlayerSprite").reel('PlayerRunning', 1000, [[0, 1], [1, 1], [2, 1], [3, 1]]); - * ~~~ - */ - reel: function (reelId, duration, fromX, fromY, frameCount) { - // @sign public this .reel() - if (arguments.length === 0) - return this._currentReelId; - - // @sign public this .reel(String reelID) - if (arguments.length === 1 && typeof reelId === "string"){ - if (typeof this._reels[reelId] === "undefined") - throw("The specified reel " + reelId + " is undefined."); - this.pauseAnimation(); - if (this._currentReelId !== reelId) { - this._currentReelId = reelId; - this._currentReel = this._reels[reelId]; - // Change the visible sprite - this._updateSprite(); - // Trigger event - this.trigger("ReelChange", this._currentReel); - } - return this; - } + * #Renderable + * @category Graphics + * @kind Component + * + * Component for any entity that has a position on the stage. + * @trigger Invalidate - when the entity needs to be redrawn + */ +Crafty.c("Renderable", { + // Flag for tracking whether the entity is dirty or not + _changed: false, + + /**@ + * #.alpha + * @comp Renderable + * @kind Property + * + * Transparency of an entity. Must be a decimal value between 0.0 being fully transparent to 1.0 being fully opaque. + */ + _alpha: 1.0, - var reel, i, y; - - reel = { - id: reelId, - frames: [], - currentFrame: 0, - easing: new Crafty.easing(duration), - defaultLoops: 1 - }; - - reel.duration = reel.easing.duration; - - // @sign public this .reel(String reelId, Number duration, Number fromX, Number fromY, Number frameDuration) - if (typeof fromX === "number") { - i = fromX; - y = fromY; - if (frameCount >= 0) { - for (; i < fromX + frameCount ; i++) { - reel.frames.push([i, y]); - } - } - else { - for (; i > fromX + frameCount; i--) { - reel.frames.push([i, y]); - } - } - } - // @sign public this .reel(String reelId, Number duration, Array frames) - else if (arguments.length === 3 && typeof fromX === "object") { - reel.frames = fromX; - } - else { - throw "Urecognized arguments. Please see the documentation for 'reel(...)'."; - } + /**@ + * #.visible + * @comp Renderable + * @kind Property + * + * If the entity is visible or not. Accepts a true or false value. + * Can be used for optimization by setting an entities visibility to false when not needed to be drawn. + * + * The entity will still exist and can be collided with but just won't be drawn. + */ + _visible: true, - this._reels[reelId] = reel; + _setterRenderable: function(name, value) { + if (this[name] === value) { + return; + } - return this; - }, + //everything will assume the value + this[name] = value; - /**@ - * #.animate - * @comp SpriteAnimation - * @sign public this .animate([String reelId] [, Number loopCount]) - * @param reelId - ID of the animation reel to play. Defaults to the current reel if none is specified. - * @param loopCount - Number of times to repeat the animation. Use -1 to repeat indefinitely. Defaults to 1. - * - * Play one of the reels previously defined through `.reel(...)`. Simply pass the name of the reel. If you wish the - * animation to play multiple times in succession, pass in the amount of times as an additional parameter. - * To have the animation repeat indefinitely, pass in `-1`. - * - * If another animation is currently playing, it will be paused. - * - * This will always play an animation from the beginning. If you wish to resume from the current state of a reel, use `resumeAnimation()`. - * - * Once an animation ends, it will remain at its last frame. - * - * - * @example - * ~~~ - * // Define a sprite-map component - * Crafty.sprite(16, "images/sprite.png", { - * PlayerSprite: [0,0] - * }); - * - * // Play the animation across 20 frames (so each sprite in the 4 sprite animation should be seen for 5 frames) and repeat indefinitely - * Crafty.e("2D, DOM, SpriteAnimation, PlayerSprite") - * .reel('PlayerRunning', 20, 0, 0, 3) // setup animation - * .animate('PlayerRunning', -1); // start animation - * ~~~ - */ - animate: function(reelId, loopCount) { - - var pos; - - - // switch to the specified reel if necessary - if (typeof reelId === "string") - this.reel(reelId); - - var currentReel = this._currentReel; - - if (typeof currentReel === "undefined" || currentReel === null) - throw("No reel is specified, and there is no currently active reel."); - - this.pauseAnimation(); // This will pause the current animation, if one is playing - - // Handle repeats; if loopCount is undefined and reelID is a number, calling with that signature - if (typeof loopCount === "undefined") - if (typeof reelId === "number") - loopCount = reelId; - else - loopCount = 1; + // flag for redraw + this.trigger("Invalidate"); + }, - // set the animation to the beginning - currentReel.easing.reset(); + // Setup all the properties that we need to define + _graphics_property_definitions: { + alpha: { + set: function (v) { + this._setterRenderable('_alpha', v); + }, + get: function () { + return this._alpha; + }, + configurable: true, + enumerable: true + }, + _alpha: {enumerable:false}, + visible: { + set: function (v) { + this._setterRenderable('_visible', v); + }, + get: function () { + return this._visible; + }, + configurable: true, + enumerable: true + }, + _visible: {enumerable:false} + }, - // user provided loop count. - this.loops(loopCount); + _defineRenderableProperites: function () { + for (var prop in this._graphics_property_definitions){ + Object.defineProperty(this, prop, this._graphics_property_definitions[prop]); + } + }, - // trigger the necessary events and switch to the first frame - this._setFrame(0); + init: function () { + // create setters and getters that associate properties such as alpha/_alpha + this._defineRenderableProperites(); + }, - // Start the anim - this.bind("EnterFrame", this._animationTick); - this._isPlaying = true; + // Renderable assumes that a draw layer has 3 important methods: attach, detach, and dirty - this.trigger("StartAnimation", currentReel); - return this; - }, + // Dirty the entity when it's invalidated + _invalidateRenderable: function() { + //flag if changed + if (this._changed === false) { + this._changed = true; + this._drawLayer.dirty(this); + } + }, - /**@ - * #.resumeAnimation - * @comp SpriteAnimation - * @sign public this .resumeAnimation() - * - * This will resume animation of the current reel from its current state. - * If a reel is already playing, or there is no current reel, there will be no effect. - */ - resumeAnimation: function() { - if (this._isPlaying === false && this._currentReel !== null) { - this.bind("EnterFrame", this._animationTick); - this._isPlaying = true; - this._currentReel.easing.resume(); - this.trigger("StartAnimation", this._currentReel); - } - return this; - }, + // Attach the entity to a layer to be rendered + _attachToLayer: function(layer) { + if (this._drawLayer) { + this._detachFromLayer(); + } + this._drawLayer = layer; + layer.attach(this); + this.bind("Invalidate", this._invalidateRenderable); + this.trigger("LayerAttached", layer); + this.trigger("Invalidate"); + }, - /**@ - * #.pauseAnimation - * @comp SpriteAnimation - * @sign public this .pauseAnimation(void) - * - * Pauses the currently playing animation, or does nothing if no animation is playing. - */ - pauseAnimation: function () { - if (this._isPlaying === true) { - this.unbind("EnterFrame", this._animationTick); - this._isPlaying = false; - this._reels[this._currentReelId].easing.pause(); - } - return this; - }, + // Detach the entity from a layer + _detachFromLayer: function() { + if (!this._drawLayer) { + return; + } + this._drawLayer.detach(this); + this.unbind("Invalidate", this._invalidateRenderable); + this.trigger("LayerDetached", this._drawLayer); + delete this._drawLayer; + }, - /**@ - * #.resetAnimation - * @comp SpriteAnimation - * @sign public this .resetAnimation() - * - * Resets the current animation to its initial state. Resets the number of loops to the last specified value, which defaults to 1. - * - * Neither pauses nor resumes the current animation. - */ - resetAnimation: function(){ - var currentReel = this._currentReel; - if (currentReel === null) - throw("No active reel to reset."); - this.reelPosition(0); - currentReel.easing.repeat(currentReel.defaultLoops); - return this; - }, + /**@ + * #.flip + * @comp Renderable + * @kind Method + * + * @trigger Invalidate - when the entity has flipped + * @sign public this .flip(String dir) + * @param dir - Flip direction + * + * Flip entity on passed direction + * + * @example + * ~~~ + * this.flip("X") + * ~~~ + */ + flip: function (dir) { + dir = dir || "X"; + if (!this["_flip" + dir]) { + this["_flip" + dir] = true; + this.trigger("Invalidate"); + } + return this; + }, + /**@ + * #.unflip + * @comp Renderable + * @kind Method + * + * @trigger Invalidate - when the entity has unflipped + * @sign public this .unflip(String dir) + * @param dir - Unflip direction + * + * Unflip entity on passed direction (if it's flipped) + * + * @example + * ~~~ + * this.unflip("X") + * ~~~ + */ + unflip: function (dir) { + dir = dir || "X"; + if (this["_flip" + dir]) { + this["_flip" + dir] = false; + this.trigger("Invalidate"); + } + return this; + } +}); +},{"../core/core.js":8}],34:[function(require,module,exports){ +var Crafty = require('../core/core.js'); - /**@ - * #.loops - * @comp SpriteAnimation - * @sign public this .loops(Number loopCount) - * @param loopCount - The number of times to play the animation - * - * Sets the number of times the animation will loop for. - * If called while an animation is in progress, the current state will be considered the first loop. - * - * @sign public Number .loops() - * @returns The number of loops left. Returns 0 if no reel is active. - */ - loops: function(loopCount) { - if (arguments.length === 0){ - if (this._currentReel !== null) - return this._currentReel.easing.loops; - else - return 0; - } - if (this._currentReel !== null){ - if (loopCount < 0) - loopCount = Infinity; - this._currentReel.easing.repeat(loopCount); - this._currentReel.defaultLoops = loopCount; - } - return this; +/**@ + * #SpriteAnimation + * @category Animation + * @kind Component + * + * @trigger StartAnimation - When an animation starts playing, or is resumed from the paused state - {Reel} + * @trigger AnimationEnd - When the animation finishes - { Reel } + * @trigger FrameChange - Each time the frame of the current reel changes - { Reel } + * @trigger ReelChange - When the reel changes - { Reel } + * + * Used to animate sprites by treating a sprite map as a set of animation frames. + * Must be applied to an entity that has a sprite-map component. + * + * To define an animation, see the `reel` method. To play an animation, see the `animate` method. + * + * A reel is an object that contains the animation frames and current state for an animation. The reel object has the following properties: + * @param id: (String) - the name of the reel + * @param frames: (Array) - A list of frames in the format [xpos, ypos] + * @param currentFrame: (Number) - The index of the current frame + * @param easing: (Crafty.easing object) - The object that handles the internal progress of the animation. + * @param duration: (Number) - The duration in milliseconds. + * + * Many animation related events pass a reel object as data. As typical with events, this should be treated as read only data that might be later altered by the entity. If you wish to preserve the data, make a copy of it. + * + * @see Crafty.sprite + */ +Crafty.c("SpriteAnimation", { + /* + * A map in which the keys are the names assigned to animations defined using + * the component (also known as reelIDs), and the values are objects describing + * the animation and its state. + */ + _reels: null, - }, + /* + * The reelID of the currently active reel (which is one of the elements in `this._reels`). + * This value is `null` if no reel is active. Some of the component's actions can be invoked + * without specifying a reel, in which case they will work on the active reel. + */ + _currentReelId: null, - /**@ - * #.reelPosition - * @comp SpriteAnimation - * - * @sign public this .reelPosition(Integer position) - * Sets the position of the current reel by frame number. - * @param position - the frame to jump to. This is zero-indexed. A negative values counts back from the last frame. - * - * @sign public this .reelPosition(Number position) - * Sets the position of the current reel by percent progress. - * @param position - a non-integer number between 0 and 1 - * - * @sign public this .reelPosition(String position) - * Jumps to the specified position. The only currently accepted value is "end", which will jump to the end of the reel. - * - * @sign public Number .reelPosition() - * @returns The current frame number - * - */ - reelPosition: function(position) { - if (this._currentReel === null) - throw("No active reel."); - - if (arguments.length === 0) - return this._currentReel.currentFrame; - - var progress, - l = this._currentReel.frames.length; - if (position === "end") - position = l - 1; - - if (position < 1 && position > 0) { - progress = position; - position = Math.floor(l * progress); - } else { - if (position !== Math.floor(position)) - throw("Position " + position + " is invalid."); - if (position < 0) - position = l - 1 + position; - progress = position / l; - } - // cap to last frame - position = Math.min(position, l-1); - position = Math.max(position, 0); - this._setProgress(progress); - this._setFrame(position); + /* + * The currently active reel. + * This value is `null` if no reel is active. + */ + _currentReel: null, - return this; + /* + * Whether or not an animation is currently playing. + */ + _isPlaying: false, - }, + /**@ + * #.animationSpeed + * @comp SpriteAnimation + * + * The playback rate of the animation. This property defaults to 1. + */ + animationSpeed: 1, - // Bound to "EnterFrame". Progresses the animation by dt, changing the frame if necessary. - // dt is multiplied by the animationSpeed property - _animationTick: function(frameData) { - var currentReel = this._reels[this._currentReelId]; - currentReel.easing.tick(frameData.dt * this.animationSpeed); - var progress = currentReel.easing.value(); - var frameNumber = Math.min( Math.floor(currentReel.frames.length * progress), currentReel.frames.length - 1); + init: function () { + this._reels = {}; + }, - this._setFrame(frameNumber); + /**@ + * #.reel + * @comp SpriteAnimation + * @kind Method + * + * Used to define reels, to change the active reel, and to fetch the id of the active reel. + * + * @sign public this .reel(String reelId, Duration duration, Number fromX, Number fromY, Number frameCount[, Number rowLength]) + * Defines a reel by starting and ending position on the sprite sheet. + * @param reelId - ID of the animation reel being created + * @param duration - The length of the animation in milliseconds. + * @param fromX - Starting `x` position on the sprite map (x's unit is the horizontal size of the sprite in the sprite map). + * @param fromY - `y` position on the sprite map (y's unit is the horizontal size of the sprite in the sprite map). Remains constant through the animation. + * @param frameCount - The number of sequential frames in the animation. If negative, the animation will play backwards. + * @param rowLength - The number of frames in a sprite sheet row. + * The sequential frames will auto-wrap to a new row when they reach the end of the current row. + * This is an optional argument that defaults to `Infinity`. + * + * @sign public this .reel(String reelId, Duration duration, Array frames) + * Defines a reel by an explicit list of frames + * @param reelId - ID of the animation reel being created + * @param duration - The length of the animation in milliseconds. + * @param frames - An array of arrays containing the `x` and `y` values of successive frames: [[x1,y1],[x2,y2],...] (the values are in the unit of the sprite map's width/height respectively). + * + * @sign public this .reel(String reelId) + * Switches to the specified reel. The sprite will be updated to that reel's current frame + * @param reelID - the ID to switch to + * + * @sign public Reel .reel() + * @return The id of the current reel + * + * + * A method to handle animation reels. Only works for sprites built with the Crafty.sprite methods. + * See the Tween component for animation of 2D properties. + * + * To setup an animation reel, pass the name of the reel (used to identify the reel later), and either an + * array of absolute sprite positions or the start x on the sprite map, the y on the sprite map and then the end x on the sprite map. + * + * + * @example + * ~~~ + * // Define a sprite-map component + * Crafty.sprite(16, "images/sprite.png", { + * PlayerSprite: [0,0] + * }); + * + * // Define an animation on the second row of the sprite map (fromY = 1) + * // from the left most sprite (fromX = 0) to the fourth sprite + * // on that row (frameCount = 4), with a duration of 1 second + * Crafty.e("2D, DOM, SpriteAnimation, PlayerSprite").reel('PlayerRunning', 1000, 0, 1, 4); + * + * // This is the same animation definition, but using the alternative method + * Crafty.e("2D, DOM, SpriteAnimation, PlayerSprite").reel('PlayerRunning', 1000, [[0, 1], [1, 1], [2, 1], [3, 1]]); + * ~~~ + */ + reel: function (reelId, duration, fromX, fromY, frameCount, rowLength) { + // @sign public this .reel() + if (arguments.length === 0) + return this._currentReelId; + + // @sign public this .reel(String reelID) + if (arguments.length === 1 && typeof reelId === "string"){ + if (typeof this._reels[reelId] === "undefined") + throw("The specified reel " + reelId + " is undefined."); + this.pauseAnimation(); + if (this._currentReelId !== reelId) { + this._currentReelId = reelId; + this._currentReel = this._reels[reelId]; + // Change the visible sprite + this._updateSprite(); + // Trigger event + this.trigger("ReelChange", this._currentReel); + } + return this; + } - if(currentReel.easing.complete === true){ - this.pauseAnimation(); - this.trigger("AnimationEnd", this._currentReel); - } - }, + var reel, i; + reel = { + id: reelId, + frames: [], + currentFrame: 0, + easing: new Crafty.easing(duration), + defaultLoops: 1 + }; + reel.duration = reel.easing.duration; + // @sign public this .reel(String reelId, Number duration, Number fromX, Number fromY, Number frameDuration) + if (typeof fromX === "number") { + rowLength = rowLength || Infinity; - // Set the current frame and update the displayed sprite - // The actual progress for the animation must be set seperately. - _setFrame: function(frameNumber) { - var currentReel = this._currentReel; - if (frameNumber === currentReel.currentFrame) - return; - currentReel.currentFrame = frameNumber; - this._updateSprite(); - this.trigger("FrameChange", currentReel); - }, + if (frameCount >= 0) { // forward animation + for (i = 0; i < frameCount; ++i) { + reel.frames.push([fromX, fromY]); - // Update the displayed sprite. - _updateSprite: function() { - var currentReel = this._currentReel; - var pos = currentReel.frames[currentReel.currentFrame]; - this.sprite(pos[0], pos[1]); // .sprite will trigger redraw + if (++fromX >= rowLength) { + fromX = 0; + fromY++; + } + } + } else { // backward animation + for (i = 0; i > frameCount; --i) { + reel.frames.push([fromX, fromY]); - }, + if (--fromX < 0) { + fromX = rowLength - 1; + fromY--; + } + } + } + } + // @sign public this .reel(String reelId, Number duration, Array frames) + else if (arguments.length === 3 && typeof fromX === "object") { + reel.frames = fromX; + } + else { + throw "Unrecognized arguments. Please see the documentation for 'reel(...)'."; + } + this._reels[reelId] = reel; - // Sets the internal state of the current reel's easing object - _setProgress: function(progress, repeats) { - this._currentReel.easing.setProgress(progress, repeats); + return this; + }, - }, + /**@ + * #.animate + * @comp SpriteAnimation + * @kind Method + * + * @sign public this .animate([String reelId] [, Number loopCount]) + * @param reelId - ID of the animation reel to play. Defaults to the current reel if none is specified. + * @param loopCount - Number of times to repeat the animation. Use -1 to repeat indefinitely. Defaults to 1. + * + * Play one of the reels previously defined through `.reel(...)`. Simply pass the name of the reel. If you wish the + * animation to play multiple times in succession, pass in the amount of times as an additional parameter. + * To have the animation repeat indefinitely, pass in `-1`. + * + * If another animation is currently playing, it will be paused. + * + * This will always play an animation from the beginning. If you wish to resume from the current state of a reel, use `resumeAnimation()`. + * + * Once an animation ends, it will remain at its last frame. + * + * + * @example + * ~~~ + * // Define a sprite-map component + * Crafty.sprite(16, "images/sprite.png", { + * PlayerSprite: [0,0] + * }); + * + * // Play the animation across 20 frames (so each sprite in the 4 sprite animation should be seen for 5 frames) and repeat indefinitely + * Crafty.e("2D, DOM, SpriteAnimation, PlayerSprite") + * .reel('PlayerRunning', 20, 0, 0, 3) // setup animation + * .animate('PlayerRunning', -1); // start animation + * ~~~ + */ + animate: function(reelId, loopCount) { + // switch to the specified reel if necessary + if (typeof reelId === "string") + this.reel(reelId); + var currentReel = this._currentReel; - /**@ - * #.isPlaying - * @comp SpriteAnimation - * @sign public Boolean .isPlaying([String reelId]) - * @param reelId - The reelId of the reel we wish to examine - * @returns The current animation state - * - * Determines if the specified animation is currently playing. If no reelId is specified, - * checks if any animation is playing. - * - * @example - * ~~~ - * myEntity.isPlaying() // is any animation playing - * myEntity.isPlaying('PlayerRunning') // is the PlayerRunning animation playing - * ~~~ - */ - isPlaying: function (reelId) { - if (!this._isPlaying) return false; - - if (!reelId) return !!this._currentReelId; - return this._currentReelId === reelId; - }, + if (typeof currentReel === "undefined" || currentReel === null) + throw("No reel is specified, and there is no currently active reel."); - /**@ - * #.getReel - * @comp SpriteAnimation - * @sign public Reel .getReel() - * @returns The current reel, or null if there is no active reel - * - * @sign public Reel .getReel(reelId) - * @param reelId - The id of the reel to fetch. - * @returns The specified reel, or `undefined` if no such reel exists. - * - */ - getReel: function (reelId) { - if (arguments.length === 0){ - if (!this._currentReelId) return null; - reelId = this._currentReelId; - } + this.pauseAnimation(); // This will pause the current animation, if one is playing - return this._reels[reelId]; - } -}); + // Handle repeats; if loopCount is undefined and reelID is a number, calling with that signature + if (typeof loopCount === "undefined") + if (typeof reelId === "number") + loopCount = reelId; + else + loopCount = 1; -},{"../core/core.js":7}],32:[function(require,module,exports){ -var Crafty = require('../core/core.js'); + // set the animation to the beginning + currentReel.easing.reset(); + // user provided loop count. + this.loops(loopCount); -// Define some variables required for webgl + // trigger the necessary events and switch to the first frame + this._setFrame(0); -var SPRITE_VERTEX_SHADER = "attribute vec2 aPosition;\nattribute vec3 aOrientation;\nattribute vec2 aLayer;\nattribute vec2 aTextureCoord;\n\nvarying mediump vec3 vTextureCoord;\n\nuniform vec4 uViewport;\nuniform mediump vec2 uTextureDimensions;\n\nmat4 viewportScale = mat4(2.0 / uViewport.z, 0, 0, 0, 0, -2.0 / uViewport.w, 0,0, 0, 0,1,0, -1,+1,0,1);\nvec4 viewportTranslation = vec4(uViewport.xy, 0, 0);\n\nvoid main() {\n vec2 pos = aPosition;\n vec2 entityOrigin = aOrientation.xy;\n mat2 entityRotationMatrix = mat2(cos(aOrientation.z), sin(aOrientation.z), -sin(aOrientation.z), cos(aOrientation.z));\n \n pos = entityRotationMatrix * (pos - entityOrigin) + entityOrigin ;\n gl_Position = viewportScale * (viewportTranslation + vec4(pos, 1.0/(1.0+exp(aLayer.x) ), 1) );\n vTextureCoord = vec3(aTextureCoord, aLayer.y);\n}"; -var SPRITE_FRAGMENT_SHADER = "varying mediump vec3 vTextureCoord;\n \nuniform sampler2D uSampler;\nuniform mediump vec2 uTextureDimensions;\n\nvoid main(void) {\n highp vec2 coord = vTextureCoord.xy / uTextureDimensions;\n mediump vec4 base_color = texture2D(uSampler, coord);\n gl_FragColor = vec4(base_color.rgb*base_color.a*vTextureCoord.z, base_color.a*vTextureCoord.z);\n}"; -var SPRITE_ATTRIBUTE_LIST = [ - {name:"aPosition", width: 2}, - {name:"aOrientation", width: 3}, - {name:"aLayer", width:2}, - {name:"aTextureCoord", width: 2} -]; + // Start the anim + this.bind("EnterFrame", this._animationTick); + this._isPlaying = true; + this.trigger("StartAnimation", currentReel); -Crafty.extend({ + return this; + }, /**@ - * #Crafty.sprite - * @category Graphics - * @sign public this Crafty.sprite([Number tile, [Number tileh]], String url, Object map[, Number paddingX[, Number paddingY[, Boolean paddingAroundBorder]]]) - * @param tile - Tile size of the sprite map, defaults to 1 - * @param tileh - Height of the tile; if provided, tile is interpreted as the width - * @param url - URL of the sprite image - * @param map - Object where the key is what becomes a new component and the value points to a position on the sprite map - * @param paddingX - Horizontal space in between tiles. Defaults to 0. - * @param paddingY - Vertical space in between tiles. Defaults to paddingX. - * @param paddingAroundBorder - If padding should be applied around the border of the sprite sheet. If enabled the first tile starts at (paddingX,paddingY) instead of (0,0). Defaults to false. - * - * Generates components based on positions in a sprite image to be applied to entities. + * #.resumeAnimation + * @comp SpriteAnimation + * @kind Method + * + * @sign public this .resumeAnimation() * - * Accepts a tile size, URL and map for the name of the sprite and its position. + * This will resume animation of the current reel from its current state. + * If a reel is already playing, or there is no current reel, there will be no effect. + */ + resumeAnimation: function() { + if (this._isPlaying === false && this._currentReel !== null) { + this.bind("EnterFrame", this._animationTick); + this._isPlaying = true; + this._currentReel.easing.resume(); + this.trigger("StartAnimation", this._currentReel); + } + + return this; + }, + + /**@ + * #.pauseAnimation + * @comp SpriteAnimation + * @kind Method + * + * @sign public this .pauseAnimation(void) * - * The position must be an array containing the position of the sprite where index `0` - * is the `x` position, `1` is the `y` position and optionally `2` is the width and `3` - * is the height. If the sprite map has padding, pass the values for the `x` padding - * or `y` padding. If they are the same, just add one value. + * Pauses the currently playing animation, or does nothing if no animation is playing. + */ + pauseAnimation: function () { + if (this._isPlaying === true) { + this.unbind("EnterFrame", this._animationTick); + this._isPlaying = false; + this._reels[this._currentReelId].easing.pause(); + } + + return this; + }, + + /**@ + * #.resetAnimation + * @comp SpriteAnimation + * @kind Method + * + * @sign public this .resetAnimation() * - * If the sprite image has no consistent tile size, `1` or no argument need be - * passed for tile size. + * Resets the current animation to its initial state. Resets the number of loops to the last specified value, which defaults to 1. * - * Entities that add the generated components are also given the `2D` component, and - * a component called `Sprite`. + * Neither pauses nor resumes the current animation. + */ + resetAnimation: function(){ + var currentReel = this._currentReel; + if (currentReel === null) + throw("No active reel to reset."); + this.reelPosition(0); + currentReel.easing.repeat(currentReel.defaultLoops); + + return this; + }, + + + /**@ + * #.loops + * @comp SpriteAnimation + * @kind Method + * + * @sign public this .loops(Number loopCount) + * @param loopCount - The number of times to play the animation * - * @example - * ~~~ - * Crafty.sprite("imgs/spritemap6.png", {flower:[0,0,20,30]}); - * var flower_entity = Crafty.e("2D, DOM, flower"); - * ~~~ - * The first line creates a component called `flower` associated with the sub-image of - * spritemap6.png with top-left corner (0,0), width 20 pixels, and height 30 pixels. - * The second line creates an entity with that image. (Note: The `2D` is not really - * necessary here, because adding the `flower` component automatically also adds the - * `2D` component.) - * ~~~ - * Crafty.sprite(50, "imgs/spritemap6.png", {flower:[0,0], grass:[0,1,3,1]}); - * ~~~ - * In this case, the `flower` component is pixels 0 <= x < 50, 0 <= y < 50, and the - * `grass` component is pixels 0 <= x < 150, 50 <= y < 100. (The `3` means grass has a - * width of 3 tiles, i.e. 150 pixels.) - * ~~~ - * Crafty.sprite(50, 100, "imgs/spritemap6.png", {flower:[0,0], grass:[0,1]}, 10); - * ~~~ - * In this case, each tile is 50x100, and there is a spacing of 10 pixels between - * consecutive tiles. So `flower` is pixels 0 <= x < 50, 0 <= y < 100, and `grass` is - * pixels 0 <= x < 50, 110 <= y < 210. + * Sets the number of times the animation will loop for. + * If called while an animation is in progress, the current state will be considered the first loop. * - * @see Sprite + * @sign public Number .loops() + * @returns The number of loops left. Returns 0 if no reel is active. */ - sprite: function (tile, tileh, url, map, paddingX, paddingY, paddingAroundBorder) { - var spriteName, temp, x, y, w, h, img; - - //if no tile value, default to 1. - //(if the first passed argument is a string, it must be the url.) - if (typeof tile === "string") { - paddingY = paddingX; - paddingX = map; - map = tileh; - url = tile; - tile = 1; - tileh = 1; + loops: function(loopCount) { + if (arguments.length === 0){ + if (this._currentReel !== null) + return this._currentReel.easing.loops; + else + return 0; } - if (typeof tileh == "string") { - paddingY = paddingX; - paddingX = map; - map = url; - url = tileh; - tileh = tile; + if (this._currentReel !== null){ + if (loopCount < 0) + loopCount = Infinity; + this._currentReel.easing.repeat(loopCount); + this._currentReel.defaultLoops = loopCount; } - //if no paddingY, use paddingX - if (!paddingY && paddingX) paddingY = paddingX; - paddingX = parseInt(paddingX || 0, 10); //just incase - paddingY = parseInt(paddingY || 0, 10); - - var markSpritesReady = function() { - this.ready = true; - this.trigger("Invalidate"); - }; + return this; + }, - img = Crafty.asset(url); - if (!img) { + /**@ + * #.reelPosition + * @kind Method + * + * @comp SpriteAnimation + * + * @sign public this .reelPosition(Integer position) + * Sets the position of the current reel by frame number. + * @param position - the frame to jump to. This is zero-indexed. A negative values counts back from the last frame. + * + * @sign public this .reelPosition(Number position) + * Sets the position of the current reel by percent progress. + * @param position - a non-integer number between 0 and 1 + * + * @sign public this .reelPosition(String position) + * Jumps to the specified position. The only currently accepted value is "end", which will jump to the end of the reel. + * + * @sign public Number .reelPosition() + * @returns The current frame number + * + */ + reelPosition: function(position) { + if (this._currentReel === null) + throw("No active reel."); + + if (arguments.length === 0) + return this._currentReel.currentFrame; + + var progress, + l = this._currentReel.frames.length; + if (position === "end") + position = l - 1; + + if (position < 1 && position > 0) { + progress = position; + position = Math.floor(l * progress); + } else { + if (position !== Math.floor(position)) + throw("Position " + position + " is invalid."); + if (position < 0) + position = l - 1 + position; + progress = position / l; + } + // cap to last frame + position = Math.min(position, l-1); + position = Math.max(position, 0); + this._setProgress(progress); + this._setFrame(position); + + return this; + }, + + + // Bound to "EnterFrame". Progresses the animation by dt, changing the frame if necessary. + // dt is multiplied by the animationSpeed property + _animationTick: function(frameData) { + var currentReel = this._reels[this._currentReelId]; + currentReel.easing.tick(frameData.dt * this.animationSpeed); + var progress = currentReel.easing.value(); + var frameNumber = Math.min( Math.floor(currentReel.frames.length * progress), currentReel.frames.length - 1); + + this._setFrame(frameNumber); + + if(currentReel.easing.complete === true){ + this.pauseAnimation(); + this.trigger("AnimationEnd", this._currentReel); + } + }, + + + + + + // Set the current frame and update the displayed sprite + // The actual progress for the animation must be set seperately. + _setFrame: function(frameNumber) { + var currentReel = this._currentReel; + if (frameNumber === currentReel.currentFrame) + return; + currentReel.currentFrame = frameNumber; + this._updateSprite(); + this.trigger("FrameChange", currentReel); + }, + + // Update the displayed sprite. + _updateSprite: function() { + var currentReel = this._currentReel; + var pos = currentReel.frames[currentReel.currentFrame]; + this.sprite(pos[0], pos[1]); // .sprite will trigger redraw + + }, + + + // Sets the internal state of the current reel's easing object + _setProgress: function(progress, repeats) { + this._currentReel.easing.setProgress(progress, repeats); + }, + + + /**@ + * #.isPlaying + * @comp SpriteAnimation + * @kind Method + * + * @sign public Boolean .isPlaying([String reelId]) + * @param reelId - The reelId of the reel we wish to examine + * @returns The current animation state + * + * Determines if the specified animation is currently playing. If no reelId is specified, + * checks if any animation is playing. + * + * @example + * ~~~ + * myEntity.isPlaying() // is any animation playing + * myEntity.isPlaying('PlayerRunning') // is the PlayerRunning animation playing + * ~~~ + */ + isPlaying: function (reelId) { + if (!this._isPlaying) return false; + if (!reelId) return !!this._currentReelId; + return this._currentReelId === reelId; + }, + + /**@ + * #.getReel + * @comp SpriteAnimation + * @kind Method + * + * @sign public Reel .getReel() + * @returns The current reel, or null if there is no active reel + * + * @sign public Reel .getReel(reelId) + * @param reelId - The id of the reel to fetch. + * @returns The specified reel, or `undefined` if no such reel exists. + * + */ + getReel: function (reelId) { + if (arguments.length === 0){ + if (!this._currentReelId) return null; + reelId = this._currentReelId; + } + + return this._reels[reelId]; + } +}); + +},{"../core/core.js":8}],35:[function(require,module,exports){ +var Crafty = require('../core/core.js'); + +// Define some variables required for webgl + + +Crafty.defaultShader("Sprite", new Crafty.WebGLShader( + "attribute vec2 aPosition;\nattribute vec3 aOrientation;\nattribute vec2 aLayer;\nattribute vec2 aTextureCoord;\n\nvarying mediump vec3 vTextureCoord;\n\nuniform vec4 uViewport;\nuniform mediump vec2 uTextureDimensions;\n\nmat4 viewportScale = mat4(2.0 / uViewport.z, 0, 0, 0, 0, -2.0 / uViewport.w, 0,0, 0, 0,1,0, -1,+1,0,1);\nvec4 viewportTranslation = vec4(uViewport.xy, 0, 0);\n\nvoid main() {\n vec2 pos = aPosition;\n vec2 entityOrigin = aOrientation.xy;\n mat2 entityRotationMatrix = mat2(cos(aOrientation.z), sin(aOrientation.z), -sin(aOrientation.z), cos(aOrientation.z));\n \n pos = entityRotationMatrix * (pos - entityOrigin) + entityOrigin ;\n gl_Position = viewportScale * (viewportTranslation + vec4(pos, 1.0/(1.0+exp(aLayer.x) ), 1) );\n vTextureCoord = vec3(aTextureCoord, aLayer.y);\n}", + "varying mediump vec3 vTextureCoord;\n \nuniform sampler2D uSampler;\nuniform mediump vec2 uTextureDimensions;\n\nvoid main(void) {\n highp vec2 coord = vTextureCoord.xy / uTextureDimensions;\n mediump vec4 base_color = texture2D(uSampler, coord);\n gl_FragColor = vec4(base_color.rgb*base_color.a*vTextureCoord.z, base_color.a*vTextureCoord.z);\n}", + [ + { name: "aPosition", width: 2 }, + { name: "aOrientation", width: 3 }, + { name: "aLayer", width: 2 }, + { name: "aTextureCoord", width: 2 } + ], + function(e, _entity) { + var co = e.co; + // Write texture coordinates + e.program.writeVector("aTextureCoord", + co.x, co.y, + co.x, co.y + co.h, + co.x + co.w, co.y, + co.x + co.w, co.y + co.h + ); + } +)); + +Crafty.extend({ + /**@ + * #Crafty.sprite + * @kind Method + * + * @category Graphics + * @sign public this Crafty.sprite([Number tile, [Number tileh]], String url, Object map[, Number paddingX[, Number paddingY[, Boolean paddingAroundBorder]]]) + * @param tile - Tile size of the sprite map, defaults to 1 + * @param tileh - Height of the tile; if provided, tile is interpreted as the width + * @param url - URL of the sprite image + * @param map - Object where the key is what becomes a new component and the value points to a position on the sprite map + * @param paddingX - Horizontal space in between tiles. Defaults to 0. + * @param paddingY - Vertical space in between tiles. Defaults to paddingX. + * @param paddingAroundBorder - If padding should be applied around the border of the sprite sheet. If enabled the first tile starts at (paddingX,paddingY) instead of (0,0). Defaults to false. + * + * Generates components based on positions in a sprite image to be applied to entities. + * + * Accepts a tile size, URL and map for the name of the sprite and its position. + * + * The position must be an array containing the position of the sprite where index `0` + * is the `x` position, `1` is the `y` position and optionally `2` is the width and `3` + * is the height. If the sprite map has padding, pass the values for the `x` padding + * or `y` padding. If they are the same, just add one value. + * + * If the sprite image has no consistent tile size, `1` or no argument need be + * passed for tile size. + * + * Entities that add the generated components are also given the `2D` component, and + * a component called `Sprite`. + * + * @example + * ~~~ + * Crafty.sprite("imgs/spritemap6.png", {flower:[0,0,20,30]}); + * var flower_entity = Crafty.e("2D, DOM, flower"); + * ~~~ + * The first line creates a component called `flower` associated with the sub-image of + * spritemap6.png with top-left corner (0,0), width 20 pixels, and height 30 pixels. + * The second line creates an entity with that image. (Note: The `2D` is not really + * necessary here, because adding the `flower` component automatically also adds the + * `2D` component.) + * ~~~ + * Crafty.sprite(50, "imgs/spritemap6.png", {flower:[0,0], grass:[0,1,3,1]}); + * ~~~ + * In this case, the `flower` component is pixels 0 <= x < 50, 0 <= y < 50, and the + * `grass` component is pixels 0 <= x < 150, 50 <= y < 100. (The `3` means grass has a + * width of 3 tiles, i.e. 150 pixels.) + * ~~~ + * Crafty.sprite(50, 100, "imgs/spritemap6.png", {flower:[0,0], grass:[0,1]}, 10); + * ~~~ + * In this case, each tile is 50x100, and there is a spacing of 10 pixels between + * consecutive tiles. So `flower` is pixels 0 <= x < 50, 0 <= y < 100, and `grass` is + * pixels 0 <= x < 50, 110 <= y < 210. + * + * @see Sprite + */ + sprite: function (tile, tileh, url, map, paddingX, paddingY, paddingAroundBorder) { + var spriteName, temp, img; + + //if no tile value, default to 1. + //(if the first passed argument is a string, it must be the url.) + if (typeof tile === "string") { + paddingY = paddingX; + paddingX = map; + map = tileh; + url = tile; + tile = 1; + tileh = 1; + } + + if (typeof tileh === "string") { + paddingY = paddingX; + paddingX = map; + map = url; + url = tileh; + tileh = tile; + } + + //if no paddingY, use paddingX + if (!paddingY && paddingX) paddingY = paddingX; + paddingX = parseInt(paddingX || 0, 10); //just incase + paddingY = parseInt(paddingY || 0, 10); + + var markSpritesReady = function() { + this.ready = true; + this.trigger("Invalidate"); + }; + + img = Crafty.asset(url); + if (!img) { img = new Image(); img.src = url; Crafty.asset(url, img); @@ -8902,13 +10426,14 @@ Crafty.extend({ this.requires("2D, Sprite"); this.__trim = [0, 0, 0, 0]; this.__image = url; + this.__map = map; this.__coord = [this.__coord[0], this.__coord[1], this.__coord[2], this.__coord[3]]; this.__tile = tile; this.__tileh = tileh; this.__padding = [paddingX, paddingY]; this.__padBorder = paddingAroundBorder; this.sprite(this.__coord[0], this.__coord[1], this.__coord[2], this.__coord[3]); - + this.img = img; //draw now if (this.img.complete && this.img.width > 0) { @@ -8919,11 +10444,7 @@ Crafty.extend({ //set the width and height to the sprite size this.w = this.__coord[2]; this.h = this.__coord[3]; - - if (this.has("WebGL")){ - this._establishShader(this.__image, SPRITE_FRAGMENT_SHADER, SPRITE_VERTEX_SHADER, SPRITE_ATTRIBUTE_LIST); - this.program.setTexture( this.webgl.makeTexture(this.__image, this.img, false) ); - } + this._setupSpriteImage(this._drawLayer); }; for (spriteName in map) { @@ -8947,9 +10468,11 @@ Crafty.extend({ /**@ * #Sprite * @category Graphics + * @kind Component + * * @trigger Invalidate - when the sprites change * - * A component for using tiles in a sprite map. + * A component for using tiles in a sprite map. * * This is automatically added to entities which use the components created by `Crafty.sprite` or `Crafty.load`. * Since these are also used to define tile size, you'll rarely need to use this components methods directly. @@ -8981,10 +10504,20 @@ Crafty.c("Sprite", { init: function () { this.__trim = [0, 0, 0, 0]; this.bind("Draw", this._drawSprite); + this.bind("LayerAttached", this._setupSpriteImage); }, remove: function(){ this.unbind("Draw", this._drawSprite); + this.unbind("LayerAttached", this._setupSpriteImage); + }, + + _setupSpriteImage: function(layer) { + if (!this.__image || !this.img || !layer) return; + if (layer.type === "WebGL"){ + this._establishShader(this.__image, Crafty.defaultShader("Sprite")); + this.program.setTexture( layer.makeTexture(this.__image, this.img, false) ); + } }, _drawSprite: function(e){ @@ -9016,43 +10549,49 @@ Crafty.c("Sprite", { // Don't change background if it's not necessary -- this can cause some browsers to reload the image // See [this chrome issue](https://code.google.com/p/chromium/issues/detail?id=102706) - var newBackground = bgColor + " url('" + this.__image + "') no-repeat"; + var newBackground = bgColor + " url('" + this.__image + "') no-repeat"; if (newBackground !== style.background) { style.background = newBackground; } style.backgroundPosition = "-" + co.x * hscale + "px -" + co.y * vscale + "px"; // style.backgroundSize must be set AFTER style.background! - if (vscale != 1 || hscale != 1) { + if (vscale !== 1 || hscale !== 1) { style.backgroundSize = (this.img.width * hscale) + "px" + " " + (this.img.height * vscale) + "px"; } } else if (e.type === "webgl") { // Write texture coordinates - e.program.writeVector("aTextureCoord", - co.x, co.y, - co.x, co.y + co.h, - co.x + co.w, co.y, - co.x + co.w, co.y + co.h - ); + e.program.draw(e, this); } }, /**@ * #.sprite * @comp Sprite + * * @sign public this .sprite(Number x, Number y[, Number w, Number h]) * @param x - X cell position * @param y - Y cell position * @param w - Width in cells. Optional. * @param h - Height in cells. Optional. * - * Uses a new location on the sprite map as its sprite. If w or h are ommitted, the width and height are not changed. - * + * Uses a new location on the sprite map as its sprite. + * If w or h are ommitted, the width and height are not changed. * Values should be in tiles or cells (not pixels). * + * @sign public this .sprite(String tileName) + * @param tileName - the name of a tile specified in the sprite map + * + * Uses a new location on the sprite map as its sprite. + * The location is retrieved by name from the previously supplied sprite map. + * An invalid name will be silently ignored. + * * @example * ~~~ * Crafty.e("2D, DOM, Sprite") * .sprite(0, 0, 2, 2); + * + * Crafty.e("2D, DOM, flower") + * .sprite('grass'); * ~~~ */ @@ -9063,6 +10602,16 @@ Crafty.c("Sprite", { * The coordinate of the slide within the sprite in the format of [x, y, w, h]. */ sprite: function (x, y, w, h) { + if (typeof x === 'string') { // retrieve location from sprite map by name + var temp = this.__map[x]; + if (!temp) return this; + + x = temp[0]; + y = temp[1]; + w = temp[2] || 1; + h = temp[3] || 1; + } + this.__coord = this.__coord || [0, 0, 0, 0]; this.__coord[0] = x * (this.__tile + this.__padding[0]) + (this.__padBorder ? this.__padding[0] : 0) + this.__trim[0]; @@ -9115,13 +10664,15 @@ Crafty.c("Sprite", { } }); -},{"../core/core.js":7}],33:[function(require,module,exports){ +},{"../core/core.js":8}],36:[function(require,module,exports){ var Crafty = require('../core/core.js'); /**@ * #Text * @category Graphics + * @kind Component + * * @trigger Invalidate - when the text is changed * @requires Canvas or DOM * Component to make a text entity. @@ -9148,6 +10699,7 @@ Crafty.c("Text", { defaultFamily: "sans-serif", defaultVariant: "normal", defaultLineHeight: "normal", + defaultTextAlign: "left", ready: true, init: function () { @@ -9160,8 +10712,11 @@ Crafty.c("Text", { "family": this.defaultFamily, "variant": this.defaultVariant }; + this._textAlign = this.defaultTextAlign; + }, - this.bind("Draw", function (e) { + events: { + "Draw": function (e) { var font = this._fontString(); if (e.type === "DOM") { @@ -9170,6 +10725,7 @@ Crafty.c("Text", { style.color = this._textColor; style.font = font; + style.textAlign = this._textAlign; el.innerHTML = this._text; } else if (e.type === "canvas") { var context = e.ctx; @@ -9179,12 +10735,18 @@ Crafty.c("Text", { context.textBaseline = "top"; context.fillStyle = this._textColor || "rgb(0,0,0)"; context.font = font; + context.textAlign = this._textAlign; context.fillText(this._text, e.pos._x, e.pos._y); context.restore(); } - }); + } + }, + + remove: function(){ + // Clean up the dynamic text update + this.unbind(this._textUpdateEvent, this._dynamicTextUpdate); }, // takes a CSS font-size string and gets the height of the resulting font in px @@ -9216,6 +10778,8 @@ Crafty.c("Text", { /**@ * #.text * @comp Text + * @kind Method + * * @sign public this .text(String text) * @param text - String of text that will be inserted into the DOM or Canvas element. * @@ -9226,6 +10790,10 @@ Crafty.c("Text", { * This method will update the text inside the entity. * * If you need to reference attributes on the entity itself you can pass a function instead of a string. + * + * If dynamic text generation is turned on, the function will then be reevaluated as necessary. + * + * @see .dynamicTextGeneration * * @example * ~~~ @@ -9240,12 +10808,16 @@ Crafty.c("Text", { * .text(function () { return "My position is " + this._x }); * ~~~ */ + _textGenerator: null, text: function (text) { if (!(typeof text !== "undefined" && text !== null)) return this._text; - if (typeof (text) == "function") + if (typeof (text) === "function"){ this._text = text.call(this); - else + this._textGenerator = text; + } else { this._text = text; + this._textGenerator = null; + } if (this.has("Canvas") ) this._resizeForCanvas(); @@ -9254,6 +10826,45 @@ Crafty.c("Text", { return this; }, + /**@ + * #.dynamicTextGeneration + * @comp Text + * @kind Method + * + * @sign public this .dynamicTextGeneration(bool dynamicTextOn[, string textUpdateEvent]) + * @param dynamicTextOn - A flag that indicates whether dyanamic text should be on or off. + * @param textUpdateEvent - The name of the event which will trigger text to be updated. Defaults to "EnterFrame". (This parameter does nothing if dynamicTextOn is false.) + * + * Turns on (or off) dynamic text generation for this entity. While dynamic text generation is on, + * if the `.text()` method is called with a text generating function, the text will be updated each frame. + * + * If textUpdateEvent is provided, text generation will be bound to that event instead of "EnterFrame". + * + * @note Dynamic text generation could cause performance issues when the entity is attached to a Canvas layer. + * + * @example + * ~~~ + * Crafty.e("2D, DOM, Text, Motion").attr({ x: 100, y: 100, vx: 10 }) + * .text(function () { return "My position is " + this._x }) + * .dynamicTextGeneration(true) + * ~~~ + * The above example will update the text with the entities position as it changes. + */ + _dynamicTextOn: false, + _textUpdateEvent: null, + _dynamicTextUpdate: function(){ + if (!this._textGenerator) return; + this.text(this._textGenerator); + }, + dynamicTextGeneration: function(dynamicTextOn, textUpdateEvent) { + this.unbind(this._textUpdateEvent, this._dynamicTextUpdate); + if (dynamicTextOn) { + this._textUpdateEvent = textUpdateEvent || "EnterFrame"; + this.bind(this._textUpdateEvent, this._dynamicTextUpdate); + } + return this; + }, + // Calculates the height and width of text on the canvas // Width is found by using the canvas measureText function // Height is only estimated -- it calculates the font size in pixels, and sets the height to 110% of that. @@ -9264,6 +10875,15 @@ Crafty.c("Text", { var size = (this._textFont.size || this.defaultSize); this.h = 1.1 * this._getFontHeight(size); + + /* Offset the MBR for text alignment*/ + if (this._textAlign === 'left' || this._textAlign === 'start') { + this.offsetBoundary(0, 0, 0, 0); + } else if (this._textAlign === 'center') { + this.offsetBoundary(this.w/2, 0, -this.w/2, 0); + } else if (this._textAlign === 'end' || this._textAlign === 'right') { + this.offsetBoundary(this.w, 0, -this.w, 0); + } }, // Returns the font string to use @@ -9273,6 +10893,8 @@ Crafty.c("Text", { /**@ * #.textColor * @comp Text + * @kind Method + * * @sign public this .textColor(String color) * @param color - The color in name, hex, rgb or rgba * @@ -9300,9 +10922,28 @@ Crafty.c("Text", { return this; }, + /**@ + * @comp Text + * @kind Method + * + * @sign public this .textAlign(String alignment) + * @param alignment - The new alignment of the text. + * + * Change the alignment of the text. Valid values are 'start', 'end, 'left', 'center', or 'right'. + */ + textAlign: function(alignment) { + this._textAlign = alignment; + if (this.has("Canvas")) + this._resizeForCanvas(); + this.trigger("Invalidate"); + return this; + }, + /**@ * #.textFont * @comp Text + * @kind Method + * * @triggers Invalidate * @sign public this .textFont(String key, * value) * @param key - Property of the entity to modify @@ -9333,7 +10974,7 @@ Crafty.c("Text", { if (typeof key === "object") { for (var propertyKey in key) { - if(propertyKey == 'family'){ + if(propertyKey === 'family'){ this._textFont[propertyKey] = "'" + key[propertyKey] + "'"; } else { this._textFont[propertyKey] = key[propertyKey]; @@ -9353,6 +10994,8 @@ Crafty.c("Text", { /**@ * #.unselectable * @comp Text + * @kind Method + * * @triggers Invalidate * @sign public this .unselectable() * @@ -9385,7 +11028,8 @@ Crafty.c("Text", { } }); -},{"../core/core.js":7}],34:[function(require,module,exports){ + +},{"../core/core.js":8}],37:[function(require,module,exports){ var Crafty = require('../core/core.js'), document = window.document; @@ -9393,6 +11037,8 @@ Crafty.extend({ /**@ * #Crafty.viewport * @category Stage + * @kind Property + * * @trigger ViewportScroll - when the viewport's x or y coordinates change * @trigger ViewportScale - when the viewport's scale changes * @trigger ViewportResize - when the viewport's dimension's change @@ -9433,6 +11079,7 @@ Crafty.extend({ /**@ * #Crafty.viewport.clampToEntities * @comp Crafty.viewport + * @kind Property * * Decides if the viewport functions should clamp to game entities. * When set to `true` functions such as Crafty.viewport.mouselook() will not allow you to move the @@ -9445,6 +11092,7 @@ Crafty.extend({ /**@ * #Crafty.viewport.x * @comp Crafty.viewport + * @kind Property * * Will move the stage and therefore every visible entity along the `x` * axis in the opposite direction. @@ -9457,6 +11105,7 @@ Crafty.extend({ /**@ * #Crafty.viewport.y * @comp Crafty.viewport + * @kind Property * * Will move the stage and therefore every visible entity along the `y` * axis in the opposite direction. @@ -9470,6 +11119,7 @@ Crafty.extend({ /**@ * #Crafty.viewport._scale * @comp Crafty.viewport + * @kind Property * * This value is the current scale (zoom) of the viewport. When the value is bigger than 1, everything * looks bigger (zoomed in). When the value is less than 1, everything looks smaller (zoomed out). This @@ -9484,6 +11134,7 @@ Crafty.extend({ /**@ * #Crafty.viewport.bounds * @comp Crafty.viewport + * @kind Property * * A rectangle which defines the bounds of the viewport. * It should be an object with two properties, `max` and `min`, @@ -9508,6 +11159,8 @@ Crafty.extend({ /**@ * #Crafty.viewport.scroll * @comp Crafty.viewport + * @kind Method + * * @sign Crafty.viewport.scroll(String axis, Number val) * @param axis - 'x' or 'y' * @param val - The new absolute position on the axis @@ -9530,18 +11183,46 @@ Crafty.extend({ rect_object: { _x: 0, _y: 0, _w: 0, _h: 0}, - rect: function () { - this.rect_object._x = -this._x; - this.rect_object._y = -this._y; - this.rect_object._w = this._width / this._scale; - this.rect_object._h = this._height / this._scale; - return this.rect_object; + /**@ + * #Crafty.viewport.rect + * @comp Crafty.viewport + * @kind Method + * + * @sign public Object Crafty.viewport.rect([Object out]) + * @param Object out - an optional Object to write the `rect` to + * @return a rectangle encompassing the currently visible viewport region. + * Contains the `_x`,`_y`,`_w`,`_h` properties. + * + * Convenience method which returns a `rect` of the currently visible viewport region. + * With no supplied `out` parameter, this method returns an internally reused object across invocations. + * If you want to save the viewport region for later use, pass an `out` argument instead, where the region will be written to. + * + * @example + * The `rect` is equivalent to the following properties: + * ~~~ + * var rect = Crafty.viewport.rect(); + * + * rect._x === -Crafty.viewport._x + * rect._y === -Crafty.viewport._y + * rect._w === Crafty.viewport._width / Crafty.viewport._scale + * rect._h === Crafty.viewport._height / Crafty.viewport._scale + * ~~~ + */ + rect: function (out) { + out = out || this.rect_object; + out._x = -this._x; + out._y = -this._y; + out._w = this._width / this._scale; + out._h = this._height / this._scale; + return out; }, /**@ * #Crafty.viewport.pan * @comp Crafty.viewport + * @kind Method + * * @sign public void Crafty.viewport.pan(Number dx, Number dy, Number time[, String|function easingFn]) * @param Number dx - The distance along the x axis * @param Number dy - The distance along the y axis @@ -9557,7 +11238,6 @@ Crafty.extend({ * ~~~ */ pan: (function () { - var tweens = {}, i, bound = false; var targetX, targetY, startingX, startingY, easing; function enterFrame(e) { @@ -9584,7 +11264,7 @@ Crafty.extend({ Crafty.trigger("StopCamera"); // Handle request to reset - if (dx == 'reset') { + if (dx === 'reset') { return; } @@ -9604,6 +11284,8 @@ Crafty.extend({ /**@ * #Crafty.viewport.follow * @comp Crafty.viewport + * @kind Method + * * @sign public void Crafty.viewport.follow(Object target, Number offsetx, Number offsety) * @param Object target - An entity with the 2D component * @param Number offsetx - Follow target's center should be offsetx pixels away from viewport's center. Positive values puts target to the right of the screen. @@ -9644,8 +11326,8 @@ Crafty.extend({ Crafty.trigger("StopCamera"); oldTarget = target; - offx = (typeof offsetx != 'undefined') ? offsetx : 0; - offy = (typeof offsety != 'undefined') ? offsety : 0; + offx = (typeof offsetx !== 'undefined') ? offsetx : 0; + offy = (typeof offsety !== 'undefined') ? offsety : 0; target.bind('Move', change); target.bind('ViewportScale', change); @@ -9657,6 +11339,8 @@ Crafty.extend({ /**@ * #Crafty.viewport.centerOn * @comp Crafty.viewport + * @kind Method + * * @sign public void Crafty.viewport.centerOn(Object target, Number time) * @param Object target - An entity with the 2D component * @param Number time - The duration in ms of the camera motion @@ -9685,6 +11369,8 @@ Crafty.extend({ /**@ * #Crafty.viewport.zoom * @comp Crafty.viewport + * @kind Method + * * @sign public void Crafty.viewport.zoom(Number amt, Number cent_x, Number cent_y, Number time[, String|function easingFn]) * @param Number amt - amount to zoom in on the target by (eg. 2, 4, 0.5) * @param Number cent_x - the center to zoom on @@ -9781,6 +11467,8 @@ Crafty.extend({ /**@ * #Crafty.viewport.scale * @comp Crafty.viewport + * @kind Method + * * @sign public void Crafty.viewport.scale(Number amt) * @param Number amt - amount to zoom/scale in on the elements * @@ -9808,9 +11496,12 @@ Crafty.extend({ }; })(), + /**@ * #Crafty.viewport.mouselook * @comp Crafty.viewport + * @kind Method + * * @sign public void Crafty.viewport.mouselook(Boolean active) * @param Boolean active - Activate or deactivate mouselook * @@ -9824,14 +11515,9 @@ Crafty.extend({ var active = false, dragging = false, lastMouse = {}; - old = {}; - function stopLook(){ - dragging = false; - } - return function (op, arg) { - if (typeof op == 'boolean') { + if (typeof op === 'boolean') { active = op; if (active) { Crafty.mouseObjs++; @@ -9845,7 +11531,7 @@ Crafty.extend({ case 'move': case 'drag': if (!dragging) return; - diff = { + var diff = { x: arg.clientX - lastMouse.x, y: arg.clientY - lastMouse.y }; @@ -9873,7 +11559,7 @@ Crafty.extend({ // clamps the viewport to the viewable area // under no circumstances should the viewport see something outside the boundary of the 'world' if (!this.clampToEntities) return; - var bound = Crafty.clone(this.bounds) || Crafty.map.boundaries(); + var bound = Crafty.clone(this.bounds) || Crafty.clone(Crafty.map.boundaries()); bound.max.x *= this._scale; bound.min.x *= this._scale; bound.max.y *= this._scale; @@ -9900,20 +11586,29 @@ Crafty.extend({ /**@ * #Crafty.viewport.init - * @comp Crafty.viewport + * @comp Crafty.stage + * @kind Method + * * @sign public void Crafty.viewport.init([Number width, Number height, String stage_elem]) * @sign public void Crafty.viewport.init([Number width, Number height, HTMLElement stage_elem]) * @param Number width - Width of the viewport * @param Number height - Height of the viewport * @param String or HTMLElement stage_elem - the element to use as the stage (either its id or the actual element). * - * Initialize the viewport. If the arguments 'width' or 'height' are missing, use `window.innerWidth` and `window.innerHeight` (full screen model). - * + * Initialize the viewport. + * If the arguments 'width' or 'height' are missing, use `window.innerWidth` and `window.innerHeight` (full screen model). * The argument 'stage_elem' is used to specify a stage element other than the default, and can be either a string or an HTMLElement. If a string is provided, it will look for an element with that id and, if none exists, create a div. If an HTMLElement is provided, that is used directly. Omitting this argument is the same as passing an id of 'cr-stage'. * - * @see Crafty.device, Crafty.domHelper, Crafty.stage + * Usually you don't have to initialize the viewport by yourself, it's automatically initialized by calling `Crafty.init()`. Multiple `init`s will create redundant stage elements. Use `Crafty.viewport.width`, `Crafty.viewport.height` or `Crafty.viewport.reload` to adjust the current viewport's dimensions. + * + * @see Crafty.device, Crafty.domHelper, Crafty.stage, Crafty.viewport.reload */ init: function (w, h, stage_elem) { + // Define default graphics layers with default z-layers + Crafty.createLayer("DefaultCanvasLayer", "Canvas", {z: 20}); + Crafty.createLayer("DefaultDOMLayer", "DOM", {z: 30}); + Crafty.createLayer("DefaultWebGLLayer", "WebGL", {z: 10}); + // setters+getters for the viewport this._defineViewportProperties(); @@ -9942,29 +11637,19 @@ Crafty.extend({ /**@ * #Crafty.stage * @category Core + * @kind CoreObject + * * The stage where all the DOM entities will be placed. */ /**@ * #Crafty.stage.elem * @comp Crafty.stage + * @kind Property + * * The `#cr-stage` div element. */ - /**@ - * #Crafty.domLayer._div - * @comp Crafty.domLayer - * `Crafty.domLayer._div` is a div inside the `#cr-stage` div that holds all DOM entities. - * If you use canvas, a `canvas` element is created at the same level in the dom - * as the the `Crafty.domLayer._div` div. So the hierarchy in the DOM is - * - * ~~~ - * Crafty.stage.elem - * - Crafty.domLayer._div (a div HTMLElement) - * - Crafty.canvasLayer._canvas (a canvas HTMLElement) - * ~~~ - */ - //create stage div to contain everything Crafty.stage = { x: 0, @@ -10115,6 +11800,7 @@ Crafty.extend({ /**@ * #Crafty.viewport.reload * @comp Crafty.stage + * @kind Method * * @sign public Crafty.viewport.reload() * @@ -10143,6 +11829,8 @@ Crafty.extend({ /**@ * #Crafty.viewport.reset * @comp Crafty.stage + * @kind Method + * * @trigger StopCamera - called to cancel camera animations * * @sign public Crafty.viewport.reset() @@ -10162,6 +11850,8 @@ Crafty.extend({ /**@ * #Crafty.viewport.onScreen * @comp Crafty.viewport + * @kind Method + * * @sign public Crafty.viewport.onScreen(Object rect) * @param rect - A rectangle with field {_x: x_val, _y: y_val, _w: w_val, _h: h_val} * @@ -10170,32 +11860,34 @@ Crafty.extend({ onScreen: function (rect) { return Crafty.viewport._x + rect._x + rect._w > 0 && Crafty.viewport._y + rect._y + rect._h > 0 && Crafty.viewport._x + rect._x < Crafty.viewport.width && Crafty.viewport._y + rect._y < Crafty.viewport.height; - }, + } } }); -},{"../core/core.js":7}],35:[function(require,module,exports){ +},{"../core/core.js":8}],38:[function(require,module,exports){ var Crafty = require('../core/core.js'), document = window.document; // Object for abstracting out all the gl calls to handle rendering entities with a particular program -RenderProgramWrapper = function(context, shader){ +function RenderProgramWrapper(layer, shader){ this.shader = shader; - this.context = context; + this.layer = layer; + this.context = layer.context; + this.draw = function() { }; this.array_size = 16; this.max_size = 1024; this._indexArray = new Uint16Array(6 * this.array_size); - this._indexBuffer = context.createBuffer(); -}; + this._indexBuffer = layer.context.createBuffer(); +} RenderProgramWrapper.prototype = { - // Takes an array of attributes; see Crafty.webgl.getProgramWrapper - initAttributes: function(attributes){ + // Takes an array of attributes; see WebGLLayer's getProgramWrapper method + initAttributes: function(attributes) { this.attributes = attributes; this._attribute_table = {}; var offset = 0; - for (var i=0; i= this.max_size) return; + growArrays: function(size) { + if (this.array_size >= this.max_size) return; var newsize = Math.min(size, this.max_size); - var newAttributeArray = new Float32Array(newsize*4*this.stride); + var newAttributeArray = new Float32Array(newsize * 4 * this.stride); var newIndexArray = new Uint16Array(6 * newsize); newAttributeArray.set(this._attributeArray); @@ -10239,12 +11931,12 @@ RenderProgramWrapper.prototype = { // Add an entity that needs to be rendered by this program // Needs to be assigned an index in the buffer - registerEntity: function(e){ + registerEntity: function(e) { if (this._registryHoles.length === 0) { - if (this._registrySize >= this.max_size){ - throw("Number of entities exceeds maximum limit."); + if (this._registrySize >= this.max_size) { + throw ("Number of entities exceeds maximum limit."); } else if (this._registrySize >= this.array_size) { - this.growArrays(2*this.array_size); + this.growArrays(2 * this.array_size); } e._glBufferIndex = this._registrySize; this._registrySize++; @@ -10254,39 +11946,39 @@ RenderProgramWrapper.prototype = { }, // remove an entity; allow its buffer index to be reused - unregisterEntity: function(e){ + unregisterEntity: function(e) { if (typeof e._glBufferIndex === "number") this._registryHoles.push(e._glBufferIndex); e._glBufferIndex = null; }, - resetRegistry: function(){ + resetRegistry: function() { this._maxElement = 0; this._registryHoles.length = 0; }, - setCurrentEntity: function(ent){ + setCurrentEntity: function(ent) { // offset is 4 * buffer index, because each entity has 4 vertices - this.ent_offset = ent._glBufferIndex*4; + this.ent_offset = ent._glBufferIndex * 4; this.ent = ent; }, // Called before a batch of entities is prepped for rendering - switchTo: function(){ + switchTo: function() { var gl = this.context; gl.useProgram(this.shader); gl.bindBuffer(gl.ARRAY_BUFFER, this._attributeBuffer); var a, attributes = this.attributes; // Process every attribute - for (var i=0; i0 && x0 && y0 && x0 && y 1) { + poly = Array.prototype.slice.call(arguments, 0); + } + this.points = poly; +}; +Crafty.polygon.prototype = { /**@ - * #.canLand - * @comp Supportable + * #.containsPoint + * @comp Crafty.polygon + * @kind Method + * + * @sign public Boolean .containsPoint(Number x, Number y) + * @param x - X position of the point + * @param y - Y position of the point * - * The canLand boolean determines if the entity is allowed to land or not (e.g. perhaps the entity should not land if it's not falling). - * The Supportable component will trigger a "CheckLanding" event. - * Interested parties can listen to this event and prevent the entity from landing by setting `canLand` to false. + * Method is used to determine if a given point is contained by the polygon. * * @example * ~~~ - * var player = Crafty.e("2D, Gravity"); - * player.bind("CheckLanding", function(ground) { - * if (player.y + player.h > ground.y + player.dy) { // forbid landing, if player's feet are not above ground - * player.canLand = false; - * } - * }); + * var poly = new Crafty.polygon([50, 0, 100, 100, 0, 100]); + * poly.containsPoint(50, 50); //TRUE + * poly.containsPoint(0, 0); //FALSE * ~~~ */ - canLand: true, + containsPoint: function (x, y) { + var p = this.points, l = p.length/2, + i, j, c = false; - init: function () { - this.requires("2D"); - this.__area = {_x: 0, _y: 0, _w: 0, _h: 0}; - this.defineField("ground", function() { return this._ground; }, function(newValue) {}); - }, - remove: function(destroyed) { - this.unbind("EnterFrame", this._detectGroundTick); + for (i = 0, j = l - 1; i < l; j = i++) { + if (((p[2*i+1] > y) !== (p[2*j+1] > y)) && (x < (p[2*j] - p[2*i]) * (y - p[2*i+1]) / (p[2*j+1] - p[2*i+1]) + p[2*i])) { + c = !c; + } + } + + return c; }, - /*@ - * #.startGroundDetection - * @comp Supportable - * @sign private this .startGroundDetection([comp]) - * @param comp - The name of a component that will be treated as ground + /**@ + * #.shift + * @comp Crafty.polygon + * @kind Method + * + * @sign public void .shift(Number x, Number y) + * @param x - Amount to shift the `x` axis + * @param y - Amount to shift the `y` axis * - * This method is automatically called by the Gravity component and should not be called by the user. + * Shifts every single point in the polygon by the specified amount. * - * Enable ground detection for this entity no matter whether comp parameter is specified or not. - * If comp parameter is specified all entities with that component will stop this entity from falling. - * For a player entity in a platform game this would be a component that is added to all entities - * that the player should be able to walk on. - * * @example * ~~~ - * Crafty.e("2D, DOM, Color, Gravity") - * .color("red") - * .attr({ w: 100, h: 100 }) - * .gravity("platform"); + * var poly = new Crafty.polygon([50, 0, 100, 100, 0, 100]); + * poly.shift(5,5); + * //[[55, 5, 105, 5, 5, 105]; * ~~~ - * - * @see Gravity */ - startGroundDetection: function(ground) { - if (ground) this._groundComp = ground; - this.uniqueBind("EnterFrame", this._detectGroundTick); - - return this; + shift: function (x, y) { + var i = 0, p =this.points, + l = p.length; + for (; i < l; i+=2) { + p[i] += x; + p[i+1] += y; + } }, - /*@ - * #.stopGroundDetection - * @comp Supportable - * @sign private this .stopGroundDetection() - * - * This method is automatically called by the Gravity component and should not be called by the user. + + /**@ + * #.clone + * @comp Crafty.polygon + * @kind Method + * + * @sign public void .clone() + * + * Returns a clone of the polygon. * - * Disable ground detection for this component. It can be reenabled by calling .startGroundDetection() + * @example + * ~~~ + * var poly = new Crafty.polygon([50, 0, 100, 100, 0, 100]); + * var shiftedpoly = poly.clone().shift(5,5); + * //[55, 5, 105, 5, 5, 105], but the original polygon is unchanged + * ~~~ */ - stopGroundDetection: function() { - this.unbind("EnterFrame", this._detectGroundTick); - - return this; + clone: function() { + //Shallow clone, but points should be full of Number primitives that are copied + return new Crafty.polygon(this.points.slice(0)); }, - _detectGroundTick: function() { - var groundComp = this._groundComp, - ground = this._ground, - overlap = Crafty.rectManager.overlap; + rotate: function (e) { + var i = 0, p = this.points, + l = p.length, + x, y; - var pos = this._cbr || this._mbr || this, - area = this.__area; - area._x = pos._x; - area._y = pos._y + 1; // Increase by 1 to make sure map.search() finds the floor - area._w = pos._w; - area._h = pos._h; - // Decrease width by 1px from left and 1px from right, to fall more gracefully - // area._x++; area._w--; + for (; i < l; i+=2) { - if (ground) { - var garea = ground._cbr || ground._mbr || ground; - if (!(ground.__c[groundComp] && overlap(garea, area))) { - this._ground = null; - this.trigger("LiftedOffGround", ground); // no collision with ground was detected for first time - ground = null; - } - } + x = e.o.x + (p[i] - e.o.x) * e.cos + (p[i+1] - e.o.y) * e.sin; + y = e.o.y - (p[i] - e.o.x) * e.sin + (p[i+1] - e.o.y) * e.cos; - if (!ground) { - var obj, oarea, - results = Crafty.map.search(area, false), - i = 0, - l = results.length; + p[i] = x; + p[i+1] = y; + } + }, - for (; i < l; ++i) { - obj = results[i]; - oarea = obj._cbr || obj._mbr || obj; - // check for an intersection with the player - if (obj !== this && obj.__c[groundComp] && overlap(oarea, area)) { - this.canLand = true; - this.trigger("CheckLanding", obj); // is entity allowed to land? - if (this.canLand) { - this._ground = ground = obj; - this.y = obj._y - this._h; // snap entity to ground object - this.trigger("LandedOnGround", ground); // collision with ground was detected for first time + /**@ + * #.intersectRay + * @comp Crafty.polygon + * @kind Method + * + * @sign public Number .intersectRay(Object origin, Object direction) + * @param origin - the point of origin from which the ray will be cast. The object must contain the properties `_x` and `_y`. + * @param direction - the direction the ray will be cast. It must be normalized. The object must contain the properties `x` and `y`. + * @returns a Number indicating the distance from the ray's origin to the closest intersection point of the polygon. + * Returns `Infinity` if there is no intersection. + * + * Find the distance to the closest intersection point of the supplied ray with any of this polygon's segments. + * + * @example + * ~~~ + * var poly = new Crafty.polygon([0,0, 50,0, 50,50, 0,50]); + * + * var origin = {_x: -1, _y: 25}; + * var direction = new Crafty.math.Vector2D(1, 0).normalize();; + * + * var distance = poly.intersectRay(origin, direction); + * Crafty.log('Distance from origin to closest intersection point', distance); // logs '1' + * ~~~ + */ - break; - } - } + // Note that for the algorithm to work, the points of the polygon have to be defined + // either clock-wise or counter-clock-wise + // + // Segment-segment intersection is described here: http://stackoverflow.com/a/565282/3041008 + // see dot projection: http://www.wildbunny.co.uk/blog/vector-maths-a-primer-for-games-programmers/vector/#Projection + // + // origin = {_x, _y} + // direction = {x, y}, must be normalized + // edge = end - start (of segment) + // + // + // # Segment - segment intersection equation + // origin + d * direction = start + e * edge + // + // ## Solving for d + // (origin + d * direction) x edge = (start + e * edge) x edge + // edge x edge == 0 + // d = (start − origin) × edge / (direction × edge) + // d_nominator = (start - origin) x edge = + // (start.x - origin.x, start.y - origin.y) x (edge.x, edge.y) = + // (start.x - origin.x) * edge.y - (start.y - origin.y) * edge.x + // d_denominator = direction x edge = + // (direction.x, direction.y) x (edge.x, edge.y) = + // direction.x * edge.y - direction.y * edge.x + // + // ## Solving for e + // (origin + d * direction) x direction = (start + e * edge) x direction + // direction x direction == 0 + // edge factor must be in interval [0, 1] + // e = (start − origin) × direction / (direction × edge) + // e_nominator = (start − origin) × direction = + // (start.x - origin.x) * direction.y - (start.y - origin.y) * direction.x + // e_denominator = d_denominator + // + // + // # If segments are colinear (both nominator and denominator == 0), + // then minDistance is min(d0, d1) >= 0, + // get d0, d1 by doing dot projection onto normalized direction vector + // + // origin + d0*direction = start + // d0*direction = (start - origin) + // -> d0 = (start - origin) • direction = + // (start.x - origin.x, start.y - origin.y) • (direction.x, direction.y) = + // (start.x - origin.x) * direction.x + (start.y - origin.y) * direction.y + // + // origin + d1*direction = end + // d1*direction = end - origin + // -> d1 = (end - origin) • direction = + // (end.x - origin.x, end.y - origin.y) • (direction.x, direction.y) = + // (end.x - origin.x) * direction.x + (end.y - origin.y) * direction.y + intersectRay: function (origin, direction) { + var points = this.points, + minDistance = Infinity; + var d, d_nom, + e, e_nom, + denom; + + var originX = origin._x, directionX = direction.x, + originY = origin._y, directionY = direction.y; + + var i = 0, l = points.length; + var startX = points[l - 2], endX, edgeX, + startY = points[l - 1], endY, edgeY; + for (; i < l; i += 2) { + endX = points[i]; + endY = points[i+1]; + edgeX = endX - startX; + edgeY = endY - startY; + + d_nom = (startX - originX) * edgeY - (startY - originY) * edgeX; + e_nom = (startX - originX) * directionY - (startY - originY) * directionX; + denom = directionX * edgeY - directionY * edgeX; + + if (denom !== 0) { + d = d_nom / denom; + e = e_nom / denom; + + if (e >= 0 && e <= 1 && d >= 0 && d < minDistance) + minDistance = d; + + } else if (d_nom === 0 || e_nom === 0) { + + d = (startX - originX) * directionX + (startY - originY) * directionY; + if (d >= 0 && d < minDistance) + minDistance = d; + + d = (endX - originX) * directionX + (endY - originY) * directionY; + if (d >= 0 && d < minDistance) + minDistance = d; } + + startX = endX; + startY = endY; } + + return minDistance; } -}); +}; /**@ - * #GroundAttacher + * #Crafty.circle * @category 2D + * @kind Class + * + * Circle object used for hitboxes and click maps. Must pass a `x`, a `y` and a `radius` value. * - * Component that attaches the entity to the ground when it lands. Useful for platformers with moving platforms. - * Remove the component to disable the functionality. - * - * @see Supportable, Gravity - * - * @example + *@example * ~~~ - * Crafty.e("2D, Gravity, GroundAttacher") - * .gravity("Platform"); // entity will land on and move with entites that have the "Platform" component + * var centerX = 5, + * centerY = 10, + * radius = 25; + * + * new Crafty.circle(centerX, centerY, radius); * ~~~ - */ -Crafty.c("GroundAttacher", { - _groundAttach: function(ground) { - ground.attach(this); - }, - _groundDetach: function(ground) { - ground.detach(this); - }, - - init: function () { - this.requires("Supportable"); - - this.bind("LandedOnGround", this._groundAttach); - this.bind("LiftedOffGround", this._groundDetach); - }, - remove: function(destroyed) { - this.unbind("LandedOnGround", this._groundAttach); - this.unbind("LiftedOffGround", this._groundDetach); - } -}); - - -/**@ - * #Gravity - * @category 2D - * @trigger Moved - When entity has moved due to velocity/acceleration on either x or y axis a Moved event is triggered. If the entity has moved on both axes for diagonal movement the event is triggered twice. - { axis: 'x' | 'y', oldValue: Number } - Old position - * @trigger NewDirection - When entity has changed direction due to velocity on either x or y axis a NewDirection event is triggered. The event is triggered once, if direction is different from last frame. - { x: -1 | 0 | 1, y: -1 | 0 | 1 } - New direction - * - * Adds gravitational pull to the entity. * - * @see Supportable, Motion + * When creating a circle for an entity, each point should be offset or relative from the entities `x` and `y` + * (don't include the absolute values as it will automatically calculate this). */ -Crafty.c("Gravity", { - _gravityConst: 500, - - init: function () { - this.requires("2D, Supportable, Motion"); +Crafty.circle = function (x, y, radius) { + this.x = x; + this.y = y; + this.radius = radius; - this.bind("LiftedOffGround", this._startGravity); // start gravity if we are off ground - this.bind("LandedOnGround", this._stopGravity); // stop gravity once landed - }, - remove: function(removed) { - this.unbind("LiftedOffGround", this._startGravity); - this.unbind("LandedOnGround", this._stopGravity); - }, + // Creates an octagon that approximate the circle for backward compatibility. + this.points = []; + var theta; - _gravityCheckLanding: function(ground) { - if (this._dy < 0) - this.canLand = false; - }, + for (var i = 0; i < 16; i+=2) { + theta = i * Math.PI / 8; + this.points[i] = this.x + (Math.sin(theta) * radius); + this.points[i+1] = this.y + (Math.cos(theta) * radius); + } +}; +Crafty.circle.prototype = { /**@ - * #.gravity - * @comp Gravity - * @sign public this .gravity([comp]) - * @param comp - The name of a component that will stop this entity from falling + * #.containsPoint + * @comp Crafty.circle + * @kind Method + * + * @sign public Boolean .containsPoint(Number x, Number y) + * @param x - X position of the point + * @param y - Y position of the point * - * Enable gravity for this entity no matter whether comp parameter is specified or not. - * If comp parameter is specified all entities with that component will stop this entity from falling. - * For a player entity in a platform game this would be a component that is added to all entities - * that the player should be able to walk on. - * See the Supportable component documentation for additional methods & events that are available. + * Method is used to determine if a given point is contained by the circle. * * @example * ~~~ - * Crafty.e("2D, DOM, Color, Gravity") - * .color("red") - * .attr({ w: 100, h: 100 }) - * .gravity("platform"); + * var circle = new Crafty.circle(0, 0, 10); + * circle.containsPoint(0, 0); //TRUE + * circle.containsPoint(50, 50); //FALSE * ~~~ - * - * @see Supportable, Motion - */ - gravity: function (comp) { - this.bind("CheckLanding", this._gravityCheckLanding); - this.startGroundDetection(comp); - this._startGravity(); - - return this; - }, - /**@ - * #.antigravity - * @comp Gravity - * @sign public this .antigravity() - * Disable gravity for this component. It can be reenabled by calling .gravity() */ - antigravity: function () { - this._stopGravity(); - this.stopGroundDetection(); - this.unbind("CheckLanding", this._gravityCheckLanding); + containsPoint: function (x, y) { + var radius = this.radius, + deltaX = this.x - x, + deltaY = this.y - y; - return this; + return (deltaX * deltaX + deltaY * deltaY) < (radius * radius); }, /**@ - * #.gravityConst - * @comp Gravity - * @sign public this .gravityConst(g) - * @param g - gravitational constant in pixels per second squared + * #.shift + * @comp Crafty.circle + * @kind Method + * + * @sign public void .shift(Number x, Number y) + * @param x - Amount to shift the `x` axis + * @param y - Amount to shift the `y` axis * - * Set the gravitational constant to g for this entity. The default is 500. The greater g, the stronger the downwards acceleration. + * Shifts the circle by the specified amount. * * @example * ~~~ - * Crafty.e("2D, DOM, Color, Gravity") - * .color("red") - * .attr({ w: 100, h: 100 }) - * .gravityConst(5) - * .gravity("platform"); + * var circle = new Crafty.circle(0, 0, 10); + * circle.shift(5,5); + * //{x: 5, y: 5, radius: 10}; * ~~~ */ - gravityConst: function (g) { - if (this._gravityActive) { // gravity active, change acceleration - this.ay -= this._gravityConst; - this.ay += g; - } - this._gravityConst = g; + shift: function (x, y) { + this.x += x; + this.y += y; - return this; - }, - _startGravity: function() { - this._gravityActive = true; - this.ay += this._gravityConst; + var i = 0, p = this.points, + l = p.length; + for (; i < l; i+=2) { + p[i] += x; + p[i+1] += y; + } }, - _stopGravity: function() { - this.ay = 0; - this.vy = 0; - this._gravityActive = false; + + rotate: function () { + // We are a circle, we don't have to rotate :) } -}); +}; -// This is used to define getters and setters for Motion properties -// For instance -// __motionProp(entity, "a", "x", true) -// will define a getter for `ax` which accesses an underlying private property `_ax` -// If the `setter` property is false, setting a value will be a null-op -var __motionProp = function(self, prefix, prop, setter) { - var publicProp = prefix + prop; - var privateProp = "_" + publicProp; - var motionEvent = { key: "", oldValue: 0}; - // getters & setters for public property - if (setter) { - Crafty.defineField(self, publicProp, function() { return this[privateProp]; }, function(newValue) { - var oldValue = this[privateProp]; - if (newValue !== oldValue) { - this[privateProp] = newValue; +Crafty.matrix = function (m) { + this.mtx = m; + this.width = m[0].length; + this.height = m.length; +}; - motionEvent.key = publicProp; - motionEvent.oldValue = oldValue; - this.trigger("MotionChange", motionEvent); - } - }); - } else { - Crafty.defineField(self, publicProp, function() { return this[privateProp]; }, function(newValue) {}); - } +Crafty.matrix.prototype = { + x: function (other) { + if (this.width !== other.height) { + return; + } - // hide private property - Object.defineProperty(self, privateProp, { - value : 0, - writable : true, - enumerable : false, - configurable : false - }); -}; + var result = []; + for (var i = 0; i < this.height; i++) { + result[i] = []; + for (var j = 0; j < other.width; j++) { + var sum = 0; + for (var k = 0; k < this.width; k++) { + sum += this.mtx[i][k] * other.mtx[k][j]; + } + result[i][j] = sum; + } + } + return new Crafty.matrix(result); + }, -// This defines an alias for a pair of underlying properties which represent the components of a vector -// It takes an object with vector methods, and redefines its x/y properties as getters and setters to properties of self -// This allows you to use the vector's special methods to manipulate the entity's properties, -// while still allowing you to manipulate those properties directly if performance matters -var __motionVector = function(self, prefix, setter, vector) { - var publicX = prefix + "x", - publicY = prefix + "y", - privateX = "_" + publicX, - privateY = "_" + publicY; - if (setter) { - Crafty.defineField(vector, "x", function() { return self[privateX]; }, function(v) { self[publicX] = v; }); - Crafty.defineField(vector, "y", function() { return self[privateY]; }, function(v) { self[publicY] = v; }); - } else { - Crafty.defineField(vector, "x", function() { return self[privateX]; }, function(v) {}); - Crafty.defineField(vector, "y", function() { return self[privateY]; }, function(v) {}); + e: function (row, col) { + //test if out of bounds + if (row < 1 || row > this.mtx.length || col < 1 || col > this.mtx[0].length) return null; + return this.mtx[row - 1][col - 1]; } - if (Object.seal) { Object.seal(vector); } - - return vector; }; -/**@ - * #AngularMotion - * @category 2D - * @trigger Rotated - When entity has rotated due to angular velocity/acceleration a Rotated event is triggered. - Number - Old rotation - * @trigger NewRotationDirection - When entity has changed rotational direction due to rotational velocity a NewRotationDirection event is triggered. The event is triggered once, if direction is different from last frame. - -1 | 0 | 1 - New direction - * @trigger MotionChange - When a motion property has changed a MotionChange event is triggered. - { key: String, oldValue: Number } - Motion property name and old value - * - * Component that allows rotating an entity by applying angular velocity and acceleration. - * All angular motion values are expressed in degrees per second (e.g. an entity with `vrotation` of 10 will rotate 10 degrees each second). - */ -Crafty.c("AngularMotion", { +},{"../core/core.js":8,"./spatial-grid.js":49}],44:[function(require,module,exports){ +var Crafty = require('../core/core.js'), + DEG_TO_RAD = Math.PI / 180, + EPSILON = 1e-6; + +Crafty.extend({ /**@ - * #.vrotation - * @comp AngularMotion + * #Crafty.raycast + * @category 2D + * @kind Method * - * A property for accessing/modifying the angular(rotational) velocity. - * The velocity remains constant over time, unless the acceleration increases the velocity. + * @sign public Array .raycast(Object origin, Object direction[, Number maxDistance][, String comp][, Boolean sort]) + * @param origin - the point of origin from which the ray will be cast. The object must contain the properties `_x` and `_y`. + * @param direction - the direction the ray will be cast. It must be normalized. The object must contain the properties `x` and `y`. + * @param maxDistance - the maximum distance up to which intersections will be found. + * This is an optional parameter defaulting to `Infinity`. + * If it's `Infinity` find all intersections. + * If it's negative find only first intersection (if there is one). + * If it's positive find all intersections up to that distance. + * @param comp - check for intersection with entities that have this component applied to them. + * This is an optional parameter that is disabled by default. + * @param sort - whether to sort the returned array by increasing distance. + * May be disabled to slightly improve performance if sorted results are not needed. + * Defaults to `true`. + * @returns an array of raycast-results that may be empty, if no intersection has been found. + * Otherwise, each raycast-result looks like `{obj: Entity, distance: Number, x: Number, y: Number}`, + * describing which `obj` entity has intersected the ray at intersection point `x`,`y`, `distance` px away from `origin`. + * + * Cast a ray from its `origin` in the `direction` and + * report entities that intersect with it, given the parameter constraints. + * + * Raycasting only reports entities, that have the `Collision` component applied to them. * * @example * ~~~ - * var ent = Crafty.e("2D, AngularMotion"); + * Crafty.e("2D, Collision") + * .setName('First entity') + * .attr({x: 0, y: 0, w: 10, h: 10}); * - * var vrotation = ent.vrotation; // retrieve the angular velocity - * ent.vrotation += 1; // increase the angular velocity - * ent.vrotation = 0; // reset the angular velocity - * ~~~ - */ - _vrotation: 0, - - /**@ - * #.arotation - * @comp AngularMotion - * - * A property for accessing/modifying the angular(rotational) acceleration. - * The acceleration increases the velocity over time, resulting in ever increasing speed. + * Crafty.e("2D, Collision") + * .setName('Second entity') + * .attr({x: 20, y: 20, w: 10, h: 10}); * - * @example - * ~~~ - * var ent = Crafty.e("2D, AngularMotion"); + * var origin = {_x: -25, _y: -25}; + * var direction = new Crafty.math.Vector2D(1, 1).normalize(); * - * var arotation = ent.arotation; // retrieve the angular acceleration - * ent.arotation += 1; // increase the angular acceleration - * ent.arotation = 0; // reset the angular acceleration - * ~~~ - */ - _arotation: 0, - - /**@ - * #.drotation - * @comp AngularMotion - * - * A number that reflects the change in rotation (difference between the old & new rotation) that was applied in the last frame. + * var results = Crafty.raycast(origin, direction, -1); // find only 1st intersection + * Crafty.log('Intersections found', results.length); // logs '1' * - * @example + * var result = results[0]; + * Crafty.log('1st intersection:'); + * Crafty.log('Entity name:', result.obj.getName()); // logs 'First entity' + * Crafty.log('Distance from origin to intersection point', result.distance); // logs '25 * Math.sqrt(2)' + * Crafty.log('Intersection point:', result.x, result.y); // logs '0' '0' * ~~~ - * var ent = Crafty.e("2D, AngularMotion"); * - * var drotation = ent.drotation; // the change of rotation in the last frame - * ~~~ + * @see Crafty.polygon#.intersectRay + * @see Crafty.map#Crafty.map.traverseRay */ - _drotation: 0, - init: function () { - this.requires("2D"); + // origin = {_x, _y} + // direction = {x, y}, must be normalized + // + // Add approximate ray intersection with bounding rectangle, + // before doing exact ray intersection if needed in future. + // https://gist.github.com/mucaho/77846e9fc0cd3c8b600c + raycast: function(origin, direction) { + // default parameters + var comp = 'obj', + maxDistance = Infinity, + sort = true; + // optional arguments + var argument, type; + for (var i = 2, l = arguments.length; i < l; ++i) { + argument = arguments[i]; + type = typeof argument; + if (type === 'number') maxDistance = argument + EPSILON; // make it inclusive + else if (type === 'string') comp = argument; + else if (type === 'boolean') sort = argument; + } - __motionProp(this, "v", "rotation", true); - __motionProp(this, "a", "rotation", true); - __motionProp(this, "d", "rotation", false); + var ox = origin._x, + oy = origin._y, + dx = direction.x, + dy = direction.y; - this.__oldRotationDirection = 0; - this.bind("EnterFrame", this._angularMotionTick); - }, - remove: function(destroyed) { - this.unbind("EnterFrame", this._angularMotionTick); - }, + var alreadyChecked = {}, + results = []; - /**@ - * #.resetAngularMotion - * @comp AngularMotion - * @sign public this .resetAngularMotion() - * - * Reset all motion (resets velocity, acceleration, motionDelta). - */ - resetAngularMotion: function() { - this._drotation = 0; - this.vrotation = 0; - this.arotation = 0; - return this; - }, + if (maxDistance < 0) { // find first intersection - /* - * s += v * Δt + (0.5 * a) * Δt * Δt - * v += a * Δt - */ - _angularMotionTick: function(frameData) { - var dt = frameData.dt / 1000; // Time in s - var oldR = this._rotation, - vr = this._vrotation, - ar = this._arotation; + var closestObj = null, + minDistance = Infinity; - // s += v * Δt + (0.5 * a) * Δt * Δt - var newR = oldR + vr * dt + 0.5 * ar * dt * dt; - // v += a * Δt - this.vrotation = vr + ar * dt; + // traverse map + Crafty.map.traverseRay(origin, direction, function(obj, previousCellDistance) { + // check if we advanced to next cell + // then report closest object from previous cell + // if intersection point is in previous cell + if (closestObj && minDistance < previousCellDistance) { + results.push({ + obj: closestObj, + distance: minDistance, + x: ox + minDistance * dx, + y: oy + minDistance * dy + }); + closestObj = null; + minDistance = Infinity; - // Check if direction of velocity has changed - var _vr = this._vrotation, dvr = _vr ? (_vr<0 ? -1:1):0; // Quick implementation of Math.sign - if (this.__oldRotationDirection !== dvr) { - this.__oldRotationDirection = dvr; - this.trigger('NewRotationDirection', dvr); - } + return true; + } - // Check if velocity has changed - // Δs = s[t] - s[t-1] - this._drotation = newR - oldR; - if (this._drotation !== 0) { - this.rotation = newR; - this.trigger('Rotated', oldR); + // object must contain polygon hitbox, the specified component and must not already be checked + if (!obj.map || !obj.__c[comp] || alreadyChecked[obj[0]]) return; + alreadyChecked[obj[0]] = true; + + // do exact intersection test + var distance = obj.map.intersectRay(origin, direction); + if (distance < minDistance) { + closestObj = obj; + minDistance = distance; + } + }); + + // in case traversal ended and we haven't yet pushed nearest intersecting object + if (closestObj) { + results.push({ + obj: closestObj, + distance: minDistance, + x: ox + minDistance * dx, + y: oy + minDistance * dy + }); + } + + } else { // find intersections up to max distance + + // traverse map + Crafty.map.traverseRay(origin, direction, function(obj, previousCellDistance) { + // check if we advanced to next cell + // then cancel traversal if previousCellDistance > maxDistance + if (previousCellDistance > maxDistance) { + return true; + } + + // object must contain polygon hitbox, the specified component and must not already be checked + if (!obj.map || !obj.__c[comp] || alreadyChecked[obj[0]]) return; + alreadyChecked[obj[0]] = true; + + // do exact intersection test + var distance = obj.map.intersectRay(origin, direction); + if (distance < maxDistance) { + results.push({ + obj: obj, + distance: distance, + x: ox + distance * dx, + y: oy + distance * dy + }); + } + }); } + + + if (sort) results.sort(function(a, b) { return a.distance - b.distance; }); + + + return results; } }); /**@ - * #Motion + * #Collision * @category 2D - * @trigger Moved - When entity has moved due to velocity/acceleration on either x or y axis a Moved event is triggered. If the entity has moved on both axes for diagonal movement the event is triggered twice. - { axis: 'x' | 'y', oldValue: Number } - Old position - * @trigger NewDirection - When entity has changed direction due to velocity on either x or y axis a NewDirection event is triggered. The event is triggered once, if direction is different from last frame. - { x: -1 | 0 | 1, y: -1 | 0 | 1 } - New direction - * @trigger MotionChange - When a motion property has changed a MotionChange event is triggered. - { key: String, oldValue: Number } - Motion property name and old value + * @kind Component + * + * @trigger HitOn - Triggered when collisions occur. Will not trigger again until collisions of this type cease, or an event is requested once more (using `resetHitChecks(component)`). - { hitData } + * @trigger HitOff - Triggered when collision with a specific component type ceases - String - componentName * - * Component that allows moving an entity by applying linear velocity and acceleration. - * All linear motion values are expressed in pixels per second (e.g. an entity with `vx` of 1 will move 1px on the x axis each second). + * Component to detect collision between any two convex polygons. * - * @note Several methods return Vector2D objects that dynamically reflect the entity's underlying properties. If you want a static copy instead, use the vector's `clone()` method. + * If collision checks are registered for multiple component and collisions with + * multiple types occur simultaniously, each collision will cause an individual + * event to fire. + * + * @note All data received from events is only valid for the duration of the event's callback. + * If you wish to preserve the data, make a copy of it. + * + * For a description of collision event data (hitData above), see the documentation for + * `.hit()`. + * + * @see 2D */ -Crafty.c("Motion", { +Crafty.c("Collision", { + init: function () { + this.requires("2D"); + this._collisionData = {}; + + this.collision(); + }, + + // Run by Crafty when the component is removed + remove: function() { + this._cbr = null; + this.unbind("Resize", this._resizeMap); + this.unbind("Resize", this._checkBounds); + }, + /**@ - * #.vx - * @comp Motion - * - * A property for accessing/modifying the linear velocity in the x axis. - * The velocity remains constant over time, unless the acceleration changes the velocity. + * #.collision + * @comp Collision + * @kind Method * - * @example - * ~~~ - * var ent = Crafty.e("2D, Motion"); + * @trigger NewHitbox - when a new hitbox is assigned - Crafty.polygon * - * var vx = ent.vx; // retrieve the linear velocity in the x axis - * ent.vx += 1; // increase the linear velocity in the x axis - * ent.vx = 0; // reset the linear velocity in the x axis - * ~~~ - */ - _vx: 0, - - /**@ - * #.vy - * @comp Motion - * - * A property for accessing/modifying the linear velocity in the y axis. - * The velocity remains constant over time, unless the acceleration changes the velocity. + * @sign public this .collision([Crafty.polygon polygon]) + * @param polygon - Optional Crafty.polygon object that will act as the hit area. * - * @example - * ~~~ - * var ent = Crafty.e("2D, Motion"); + * @sign public this .collision([Array coordinatePairs]) + * @param coordinatePairs - Optional array of x, y coordinate pairs to generate a hit area polygon. * - * var vy = ent.vy; // retrieve the linear velocity in the y axis - * ent.vy += 1; // increase the linear velocity in the y axis - * ent.vy = 0; // reset the linear velocity in the y axis - * ~~~ - */ - _vy: 0, - - /**@ - * #.ax - * @comp Motion - * - * A property for accessing/modifying the linear acceleration in the x axis. - * The acceleration changes the velocity over time. + * @sign public this .collision([x1, y1,.., xN, yN]) + * @param point# - Optional list of x, y coordinate pairs to generate a hit area polygon. * - * @example - * ~~~ - * var ent = Crafty.e("2D, Motion"); + * Constructor that takes a polygon, an array of points or a list of points to use as the hit area, + * with points being relative to the object's position in its unrotated state. * - * var ax = ent.ax; // retrieve the linear acceleration in the x axis - * ent.ax += 1; // increase the linear acceleration in the x axis - * ent.ax = 0; // reset the linear acceleration in the x axis - * ~~~ - */ - _ax: 0, - - /**@ - * #.ay - * @comp Motion - * - * A property for accessing/modifying the linear acceleration in the y axis. - * The acceleration changes the velocity over time. + * The hit area must be a convex shape and not concave for collision detection to work properly. * - * @example - * ~~~ - * var ent = Crafty.e("2D, Motion"); + * If no parameter is passed, the x, y, w, h properties of the entity will be used, and the hitbox will be resized when the entity is. * - * var ay = ent.ay; // retrieve the linear acceleration in the y axis - * ent.ay += 1; // increase the linear acceleration in the y axis - * ent.ay = 0; // reset the linear acceleration in the y axis - * ~~~ - */ - _ay: 0, - - /**@ - * #.dx - * @comp Motion - * - * A number that reflects the change in x (difference between the old & new x) that was applied in the last frame. + * If a hitbox is set that is outside of the bounds of the entity itself, there will be a small performance penalty as it is tracked separately. + * + * In order for your custom hitbox to have any effect, you have to add the `Collision` component to all other entities this entity needs to collide with using this custom hitbox. + * On the contrary the collisions will be resolved using the default hitbox. See `.hit()` - `MBR` represents default hitbox collision, `SAT` represents custom hitbox collision. * * @example * ~~~ - * var ent = Crafty.e("2D, Motion"); + * Crafty.e("2D, Collision").collision( + * new Crafty.polygon([50, 0, 100, 100, 0, 100]) + * ); * - * var dx = ent.dx; // the change of x in the last frame + * Crafty.e("2D, Collision").collision([50, 0, 100, 100, 0, 100]); + * + * Crafty.e("2D, Collision").collision(50, 0, 100, 100, 0, 100); * ~~~ + * + * @see Crafty.polygon */ - _dx: 0, - + collision: function (polygon) { + // Unbind anything bound to "Resize" + this.unbind("Resize", this._resizeMap); + this.unbind("Resize", this._checkBounds); + + if (!polygon) { + // If no polygon is specified, then a polygon is created that matches the bounds of the entity + // It will be adjusted on a "Resize" event + polygon = new Crafty.polygon([0, 0, this._w, 0, this._w, this._h, 0, this._h]); + this.bind("Resize", this._resizeMap); + this._cbr = null; + } else { + // Otherwise, we set the specified hitbox, converting from a list of arguments to a polygon if necessary + if (arguments.length > 1) { + //convert args to array to create polygon + var args = Array.prototype.slice.call(arguments, 0); + polygon = new Crafty.polygon(args); + // Otherwise, we set the specified hitbox, converting from an array of points to a polygon if necessary + } else if (polygon.constructor === Array) { + //Clone the array so we don't modify it for anything else that might be using it + polygon = new Crafty.polygon(polygon.slice()); + // Otherwise, we set the specified hitbox + } else { + //Clone the polygon so we don't modify it for anything else that might be using it + polygon = polygon.clone(); + } + // Check to see if the polygon sits outside the entity, and set _cbr appropriately + // On resize, the new bounds will be checked if necessary + this._findBounds(polygon.points); + } + + // If the entity is currently rotated, the points in the hitbox must also be rotated + if (this.rotation) { + polygon.rotate({ + cos: Math.cos(-this.rotation * DEG_TO_RAD), + sin: Math.sin(-this.rotation * DEG_TO_RAD), + o: { + x: this._origin.x, + y: this._origin.y + } + }); + } + + // Finally, assign the hitbox, and attach it to the "Collision" entity + this.map = polygon; + this.attach(this.map); + this.map.shift(this._x, this._y); + this.trigger("NewHitbox", polygon); + return this; + }, + /**@ - * #.dy - * @comp Motion + * #.cbr + * @comp Collision + * @kind Method * - * A number that reflects the change in y (difference between the old & new y) that was applied in the last frame. + * @sign public Object .cbr([Object cbr]) + * @param cbr - an object to use as output + * @returns an object with `_x`, `_y`, `_w`, and `_h` properties; if an object is passed in, it will be reused rather than creating a new object. * - * @example - * ~~~ - * var ent = Crafty.e("2D, Motion"); + * Return an object containing a copy of this entity's collision bounding rectangle. + * The CBR encompasses both the entity's custom collision hitbox and its MBR. + * If the custom collision hitbox does not sit outside the entity it will return the entity's minimum bounding rectangle (`.mbr()`) instead. * - * var dy = ent.dy; // the change of y in the last frame - * ~~~ + * @note The keys have an underscore prefix. This is due to the x, y, w, h properties + * being setters and getters that wrap the underlying properties with an underscore (_x, _y, _w, _h). + * + * @see 2D#.mbr */ - _dy: 0, + cbr: function (cbr) { + cbr = cbr || {}; + if (!this._cbr) { + return this.mbr(cbr); + } else { + cbr._x = (this._cbr._x); + cbr._y = (this._cbr._y); + cbr._w = (this._cbr._w); + cbr._h = (this._cbr._h); + return cbr; + } + }, - init: function () { - this.requires("2D"); + // If the hitbox is set by hand, it might extend beyond the entity. + // In such a case, we need to track this separately. + // This function finds a (non-minimal) bounding circle around the hitbox. + // + // It uses a pretty naive algorithm to do so, for more complicated options see [wikipedia](http://en.wikipedia.org/wiki/Bounding_sphere). + _findBounds: function(points) { + var minX = Infinity, maxX = -Infinity, minY=Infinity, maxY=-Infinity; + var l = points.length; - __motionProp(this, "v", "x", true); - __motionProp(this, "v", "y", true); - this._velocity = __motionVector(this, "v", true, new Crafty.math.Vector2D()); - __motionProp(this, "a", "x", true); - __motionProp(this, "a", "y", true); - this._acceleration = __motionVector(this, "a", true, new Crafty.math.Vector2D()); - __motionProp(this, "d", "x", false); - __motionProp(this, "d", "y", false); - this._motionDelta = __motionVector(this, "d", false, new Crafty.math.Vector2D()); + // Calculate the MBR of the points by finding the min/max x and y + for (var i=0; i maxX) + maxX = points[i]; + if (points[i+1] < minY) + minY = points[i+1]; + if (points[i+1] > maxY) + maxY = points[i+1]; + } - this.__movedEvent = {axis: '', oldValue: 0}; - this.__oldDirection = {x: 0, y: 0}; + // This describes a circle centered on the MBR of the points, with a diameter equal to its diagonal + // It will be used to find a rough bounding box round the points, even if they've been rotated + var cbr = { + cx: (minX + maxX) / 2, + cy: (minY + maxY) / 2, + r: Math.sqrt((maxX - minX)*(maxX - minX) + (maxY - minY)*(maxY - minY)) / 2 + }; - this.bind("EnterFrame", this._linearMotionTick); - }, - remove: function(destroyed) { - this.unbind("EnterFrame", this._linearMotionTick); + // We need to worry about resizing, but only if resizing could possibly change whether the hitbox is in or out of bounds + // Thus if the upper-left corner is out of bounds, then there's no need to recheck on resize + if (minX >= 0 && minY >= 0) { + this._checkBounds = function() { + if (this._cbr === null && this._w < maxX || this._h < maxY) { + this._cbr = cbr; + this._calculateMBR(); + } else if (this._cbr) { + this._cbr = null; + this._calculateMBR(); + } + }; + this.bind("Resize", this._checkBounds); + } + + // If the hitbox is within the entity, _cbr is null + // Otherwise, set it, and immediately calculate the bounding box. + if (minX >= 0 && minY >= 0 && maxX <= this._w && maxY <= this._h) { + this._cbr = null; + return false; + } else { + this._cbr = cbr; + this._calculateMBR(); + return true; + } }, - /**@ - * #.resetMotion - * @comp Motion - * @sign public this .resetMotion() - * @return this - * - * Reset all linear motion (resets velocity, acceleration, motionDelta). - */ - resetMotion: function() { - this.vx = 0; this.vy = 0; - this.ax = 0; this.ay = 0; - this._dx = 0; this._dy = 0; + // The default behavior is to match the hitbox to the entity. + // This function will change the hitbox when a "Resize" event triggers. + _resizeMap: function (e) { + var dx, dy, rot = this.rotation * DEG_TO_RAD, + points = this.map.points; - return this; + // Depending on the change of axis, move the corners of the rectangle appropriately + if (e.axis === 'w') { + if (rot) { + dx = e.amount * Math.cos(rot); + dy = e.amount * Math.sin(rot); + } else { + dx = e.amount; + dy = 0; + } + + // "top right" point shifts on change of w + points[2] += dx; + points[3] += dy; + } else { + if (rot) { + dy = e.amount * Math.cos(rot); + dx = -e.amount * Math.sin(rot); + } else { + dx = 0; + dy = e.amount; + } + + // "bottom left" point shifts on change of h + points[6] += dx; + points[7] += dy; + } + + // "bottom right" point shifts on either change + points[4] += dx; + points[5] += dy; }, /**@ - * #.motionDelta - * @comp Motion - * @sign public Vector2D .motionDelta() - * @return A Vector2D with the properties {x, y} that reflect the change in x & y. + * #.hit + * @comp Collision + * @kind Method * - * Returns the difference between the old & new position that was applied in the last frame. + * @sign public Array .hit(String component) + * @param component - Check collision with entities that have this component + * applied to them. + * @return `null` if there is no collision. If a collision is detected, + * returns an Array of collision data objects (see below). * - * @example - * ~~~ - * var ent = Crafty.e("2D, Motion"); + * Tests for collisions with entities that have the specified component + * applied to them. + * If a collision is detected, data regarding the collision will be present in + * the array returned by this method. + * If no collisions occur, this method returns `null`. * - * var deltaY = ent.motionDelta().y; // the change of y in the last frame + * Following is a description of a collision data object that this method may + * return: The returned collision data will be an Array of Objects with the + * type of collision used, the object collided and if the type used was SAT (a polygon was used as the hitbox) then an amount of overlap. + * ~~~ + * [{ + * obj: [entity], + * type: ["MBR" or "SAT"], + * overlap: [number] + * }] * ~~~ - * @see Crafty.math.Vector2D - */ - motionDelta: function() { - return this._motionDelta; - }, - - /**@ - * #.velocity - * @comp Motion - * Method for accessing/modifying the linear(x,y) velocity. - * The velocity remains constant over time, unless the acceleration increases the velocity. * - * @sign public Vector2D .velocity() - * @return The velocity Vector2D with the properties {x, y} that reflect the velocities in the direction of the entity. + * - **obj:** The entity with which the collision occured. + * - **type:** Collision detection method used. One of: + * - *MBR:* Standard axis aligned rectangle intersection (`.intersect` in the 2D component). + * - *SAT:* Collision between any two convex polygons. Used when both colliding entities have the `Collision` component applied to them. + * - **overlap:** If SAT collision was used, this will signify the overlap percentage between the colliding entities. + * + * Keep in mind that both entities need to have the `Collision` component, if you want to check for `SAT` (custom hitbox) collisions between them. + * + * If you want more fine-grained control consider using `Crafty.map.search()`. * - * Returns the current velocity. You can access/modify the properties in order to retrieve/change the velocity. - * @example + * Resolving collisions with static colliders (walls) for moving entity (player). * ~~~ - * var ent = Crafty.e("2D, Motion"); - * - * var vel = ent.velocity(); //returns the velocity vector - * vel.x; // retrieve the velocity in the x direction - * vel.x = 0; // set the velocity in the x direction - * vel.x += 4 // add to the velocity in the x direction + * Crafty.e("2D, Fourway, Collision, player") + * .attr({x: 32, y: 32, w: 32, h: 32}) + * .collision([0, 16, 16, 0, 32, 16, 16, 32]) + * .fourway() + * .bind('Moved', function(evt) { // after player moved + * var hitDatas, hitData; + * if ((hitDatas = this.hit('wall'))) { // check for collision with walls + * hitData = hitDatas[0]; // resolving collision for just one collider + * if (hitData.type === 'SAT') { // SAT, advanced collision resolution + * // move player back by amount of overlap + * this.x -= hitData.overlap * hitData.normal.x; + * this.y -= hitData.overlap * hitData.normal.y; + * } else { // MBR, simple collision resolution + * // move player to position before he moved (on respective axis) + * this[evt.axis] = evt.oldValue; + * } + * } + * }); * ~~~ - * @see Crafty.math.Vector2D + * + * @see Crafty.map#Crafty.map.search */ - velocity: function() { - return this._velocity; - }, - + hit: function (component) { + var area = this._cbr || this._mbr || this, + results = Crafty.map.search(area, false), + i = 0, + l = results.length, + dupes = {}, + id, obj, oarea, key, + overlap = Crafty.rectManager.overlap, + hasMap = ('map' in this && 'containsPoint' in this.map), + finalresult = []; - /**@ - * #.acceleration - * @comp Motion - * Method for accessing/modifying the linear(x,y) acceleration. - * The acceleration increases the velocity over time, resulting in ever increasing speed. - * - * @sign public Vector2D .acceleration() - * @return The acceleration Vector2D with the properties {x, y} that reflects the acceleration in the direction of the entity. - * - * Returns the current acceleration. You can access/modify the properties in order to retrieve/change the acceleration. - * - * @example - * ~~~ - * var ent = Crafty.e("2D, Motion"); - * - * var acc = ent.acceleration(); //returns the acceleration object - * acc.x; // retrieve the acceleration in the x direction - * acc.x = 0; // set the acceleration in the x direction - * acc.x += 4 // add to the acceleration in the x direction - * ~~~ - * @see Crafty.math.Vector2D - */ - acceleration: function() { - return this._acceleration; - }, + if (!l) { + return null; + } - /* - * s += v * Δt + (0.5 * a) * Δt * Δt - * v += a * Δt - */ - _linearMotionTick: function(frameData) { - var dt = frameData.dt / 1000; // time in s - var oldX = this._x, vx = this._vx, ax = this._ax, - oldY = this._y, vy = this._vy, ay = this._ay; + for (; i < l; ++i) { + obj = results[i]; + oarea = obj._cbr || obj._mbr || obj; //use the mbr - // s += v * Δt + (0.5 * a) * Δt * Δt - var newX = oldX + vx * dt + 0.5 * ax * dt * dt; - var newY = oldY + vy * dt + 0.5 * ay * dt * dt; - // v += a * Δt - this.vx = vx + ax * dt; - this.vy = vy + ay * dt; + if (!obj) continue; + id = obj[0]; - // Check if direction of velocity has changed - var oldDirection = this.__oldDirection, - _vx = this._vx, dvx = _vx ? (_vx<0 ? -1:1):0, // A quick implementation of Math.sign - _vy = this._vy, dvy = _vy ? (_vy<0 ? -1:1):0; - if (oldDirection.x !== dvx || oldDirection.y !== dvy) { - oldDirection.x = dvx; - oldDirection.y = dvy; - this.trigger('NewDirection', oldDirection); + //check if not added to hash and that actually intersects + if (!dupes[id] && this[0] !== id && obj.__c[component] && overlap(oarea, area)) + dupes[id] = obj; } - // Check if velocity has changed - var movedEvent = this.__movedEvent; - // Δs = s[t] - s[t-1] - this._dx = newX - oldX; - this._dy = newY - oldY; - if (this._dx !== 0) { - this.x = newX; - movedEvent.axis = 'x'; - movedEvent.oldValue = oldX; - this.trigger('Moved', movedEvent); + for (key in dupes) { + obj = dupes[key]; + + if (hasMap && 'map' in obj) { + var SAT = this._SAT(this.map, obj.map); + SAT.obj = obj; + SAT.type = "SAT"; + if (SAT) finalresult.push(SAT); + } else { + finalresult.push({ + obj: obj, + type: "MBR" + }); + } } - if (this._dy !== 0) { - this.y = newY; - movedEvent.axis = 'y'; - movedEvent.oldValue = oldY; - this.trigger('Moved', movedEvent); + + if (!finalresult.length) { + return null; } - } -}); -/**@ - * #Crafty.polygon - * @category 2D - * - * The constructor for a polygon object used for hitboxes and click maps. Takes a set of points as an - * argument, giving alternately the x and y coordinates of the polygon's vertices in order. - * - * The constructor accepts the coordinates as either a single array or as a set of individual arguments. - * If passed an array, the current implementation will use that array internally -- do not attempt to reuse it. - * - * When creating a polygon for an entity, each point should be offset or relative from the entities `x` and `y` - * (don't include the absolute values as it will automatically calculate this). - * - * - * @example - * Two ways to create a triangle with vertices at `(50, 0)`, `(100, 100)` and `(0, 100)`. - * ~~~ - * new Crafty.polygon([50, 0, 100, 100, 0, 100]); - * new Crafty.polygon(50, 0, 100, 100, 0, 100); - * ~~~ - */ -Crafty.polygon = function (poly) { - if (arguments.length > 1) { - poly = Array.prototype.slice.call(arguments, 0); - } - this.points = poly; -}; + return finalresult; + }, -Crafty.polygon.prototype = { /**@ - * #.containsPoint - * @comp Crafty.polygon - * @sign public Boolean .containsPoint(Number x, Number y) - * @param x - X position of the point - * @param y - Y position of the point + * #.onHit + * @comp Collision + * @kind Method + * + * @sign public this .onHit(String component, Function callbackOn[, Function callbackOff]) + * @param component - Component to check collisions for. + * @param callbackOn - Callback method to execute upon collision with the component. + * The first argument passed will be the results of the collision check in the same format documented for `hit()`. + * The second argument passed will be a Boolean indicating whether the collision with a component occurs for the first time. + * @param callbackOff - Callback method executed once as soon as collision stops. * - * Method is used to determine if a given point is contained by the polygon. + * Creates an EnterFrame event calling `.hit()` each frame. When a collision is detected the `callbackOn` will be invoked. + * + * Note that the `callbackOn` will be invoked every frame the collision is active, not just the first time the collision occurs. + * Use the second argument passed to `callbackOn` to differentiate that, which will be `true` if it's the first time the collision occurs. + * + * If you want more fine-grained control consider using `.checkHits()`, `.hit()` or even `Crafty.map.search()`. * * @example + * Respond to collisions between player and bullets. * ~~~ - * var poly = new Crafty.polygon([50, 0, 100, 100, 0, 100]); - * poly.containsPoint(50, 50); //TRUE - * poly.containsPoint(0, 0); //FALSE + * Crafty.e("2D, Collision, player") + * .attr({ health: 100 }) + * .onHit('bullet', function(hitDatas) { // on collision with bullets + * for (var i = 0, l = hitDatas.length; i < l; ++i) { // for each bullet hit + * hitDatas[i].obj.destroy(); // destroy the bullet + * this.health -= 25; // player looses health + * if (this.health <= 0) // once player's health depletes + * this.destroy(); // player dies + * } + * }); * ~~~ + * + * @see .checkHits + * @see .hit + * @see Crafty.map#Crafty.map.search */ - containsPoint: function (x, y) { - var p = this.points, l = p.length/2, - i, j, c = false; - - for (i = 0, j = l - 1; i < l; j = i++) { - if (((p[2*i+1] > y) != (p[2*j+1] > y)) && (x < (p[2*j] - p[2*i]) * (y - p[2*i+1]) / (p[2*j+1] - p[2*i+1]) + p[2*i])) { - c = !c; + onHit: function (component, callbackOn, callbackOff) { + var justHit = false; + this.bind("EnterFrame", function () { + var hitData = this.hit(component); + if (hitData) { + callbackOn.call(this, hitData, !justHit); + justHit = true; + } else if (justHit) { + if (typeof callbackOff === 'function') { + callbackOff.call(this); + } + justHit = false; } - } - - return c; + }); + return this; }, - /**@ - * #.shift - * @comp Crafty.polygon - * @sign public void .shift(Number x, Number y) - * @param x - Amount to shift the `x` axis - * @param y - Amount to shift the `y` axis + /** + * This is a helper method for creating collisions handlers set up by `checkHits`. Do not call this directly. * - * Shifts every single point in the polygon by the specified amount. + * @param {String} component - The name of the component for which this handler checks for collisions. + * @param {Object} collisionData - Collision data object used to track collisions with the specified component. * - * @example - * ~~~ - * var poly = new Crafty.polygon([50, 0, 100, 100, 0, 100]); - * poly.shift(5,5); - * //[[55, 5, 105, 5, 5, 105]; - * ~~~ + * @see .checkHits */ - shift: function (x, y) { - var i = 0, p =this.points, - l = p.length; - for (; i < l; i+=2) { - p[i] += x; - p[i+1] += y; - } + _createCollisionHandler: function(component, collisionData) { + return function() { + var hitData = this.hit(component); + + if (collisionData.occurring === true) { + if (hitData !== null) { + // The collision is still in progress + return; + } + + collisionData.occurring = false; + this.trigger("HitOff", component); + } else if (hitData !== null) { + collisionData.occurring = true; + this.trigger("HitOn", hitData); + } + }; }, /**@ - * #.clone - * @comp Crafty.polygon - * @sign public void .clone() + * #.checkHits + * @comp Collision + * @kind Method * - * Returns a clone of the polygon. + * @sign public this .checkHits(String componentList) + * @param componentList - A comma seperated list of components to check for collisions with. + * @sign public this .checkHits(String component1[, .., String componentN]) + * @param component# - A component to check for collisions with. + * + * Performs collision checks against all entities that have at least one of + * the components specified when calling this method. If collisions occur, + * a "HitOn" event containing the collision information will be fired for the + * entity on which this method was invoked. See the documentation for `.hit()` + * for a description of collision data contained in the event. + * When a collision that was reported ends, a corresponding "HitOff" event + * will be fired. + * + * Calling this method more than once for the same component type will not + * cause redundant hit checks. + * + * If you want more fine-grained control consider using `.hit()` or even `Crafty.map.search()`. + * + * @note Hit checks are performed upon entering each new frame (using + * the *EnterFrame* event). It is entirely possible for object to move in + * said frame after the checks were performed (even if the more is the + * result of *EnterFrame*, as handlers run in no particular order). In such + * a case, the hit events will not fire until the next check is performed in + * the following frame. * * @example * ~~~ - * var poly = new Crafty.polygon([50, 0, 100, 100, 0, 100]); - * var shiftedpoly = poly.clone().shift(5,5); - * //[55, 5, 105, 5, 5, 105], but the original polygon is unchanged + * Crafty.e("2D, Collision") + * .checkHits('Solid') // check for collisions with entities that have the Solid component in each frame + * .bind("HitOn", function(hitData) { + * Crafty.log("Collision with Solid entity occurred for the first time."); + * }) + * .bind("HitOff", function(comp) { + * Crafty.log("Collision with Solid entity ended."); + * }); * ~~~ + * + * @see .hit + * @see Crafty.map#Crafty.map.search */ - clone: function() { - //Shallow clone, but points should be full of Number primitives that are copied - return new Crafty.polygon(this.points.slice(0)); - }, + checkHits: function () { + var components = arguments; + var i = 0; - rotate: function (e) { - var i = 0, p = this.points, - l = p.length, - x, y; - - for (; i < l; i+=2) { + if (components.length === 1) { + components = components[0].split(/\s*,\s*/); + } - x = e.o.x + (p[i] - e.o.x) * e.cos + (p[i+1] - e.o.y) * e.sin; - y = e.o.y - (p[i] - e.o.x) * e.sin + (p[i+1] - e.o.y) * e.cos; + for (; i < components.length; ++i) { + var component = components[i]; + var collisionData = this._collisionData[component]; - p[i] = x; - p[i+1] = y; - } - } -}; + if (collisionData !== undefined) { + // There is already a handler for collision with this component + continue; + } -/**@ - * #Crafty.circle - * @category 2D - * Circle object used for hitboxes and click maps. Must pass a `x`, a `y` and a `radius` value. - * - *@example - * ~~~ - * var centerX = 5, - * centerY = 10, - * radius = 25; - * - * new Crafty.circle(centerX, centerY, radius); - * ~~~ - * - * When creating a circle for an entity, each point should be offset or relative from the entities `x` and `y` - * (don't include the absolute values as it will automatically calculate this). - */ -Crafty.circle = function (x, y, radius) { - this.x = x; - this.y = y; - this.radius = radius; + this._collisionData[component] = collisionData = { occurring: false, handler: null }; + collisionData.handler = this._createCollisionHandler(component, collisionData); - // Creates an octagon that approximate the circle for backward compatibility. - this.points = []; - var theta; + this.bind("EnterFrame", collisionData.handler); + } - for (var i = 0; i < 16; i+=2) { - theta = i * Math.PI / 8; - this.points[i] = this.x + (Math.sin(theta) * radius); - this.points[i+1] = this.y + (Math.cos(theta) * radius); - } -}; + return this; + }, -Crafty.circle.prototype = { /**@ - * #.containsPoint - * @comp Crafty.circle - * @sign public Boolean .containsPoint(Number x, Number y) - * @param x - X position of the point - * @param y - Y position of the point + * #.ignoreHits + * @comp Collision + * @kind Method * - * Method is used to determine if a given point is contained by the circle. + * @sign public this .ignoreHits() + * + * @sign public this .ignoreHits(String componentList) + * @param componentList - A comma separated list of components to stop checking + * for collisions with. + * + * @sign public this .ignoreHits(String component1[, .., String componentN]) + * @param component# - A component to stop checking for collisions with. + * + * Stops checking for collisions with all, or certain, components. If called + * without arguments, this method will cause all collision checks on the + * entity to cease. To disable checks for collisions with specific + * components, specify the components as a comma separated string or as + * a set of arguments. + * + * Calling this method with component names for which there are no collision + * checks has no effect. * * @example * ~~~ - * var circle = new Crafty.circle(0, 0, 10); - * circle.containsPoint(0, 0); //TRUE - * circle.containsPoint(50, 50); //FALSE + * Crafty.e("2D, Collision") + * .checkHits('Solid') + * ... + * .ignoreHits('Solid'); // stop checking for collisions with entities that have the Solid component * ~~~ */ - containsPoint: function (x, y) { - var radius = this.radius, - sqrt = Math.sqrt, - deltaX = this.x - x, - deltaY = this.y - y; + ignoreHits: function () { + var components = arguments; + var i = 0; + var collisionData; - return (deltaX * deltaX + deltaY * deltaY) < (radius * radius); + if (components.length === 0) { + for (collisionData in this._collisionData) { + this.unbind("EnterFrame", collisionData.handler); + } + + this._collisionData = {}; + } + + if (components.length === 1) { + components = components[0].split(/\s*,\s*/); + } + + for (; i < components.length; ++i) { + var component = components[i]; + collisionData = this._collisionData[component]; + + if (collisionData === undefined) { + continue; + } + + this.unbind("EnterFrame", collisionData.handler); + delete this._collisionData[component]; + } + + return this; }, /**@ - * #.shift - * @comp Crafty.circle - * @sign public void .shift(Number x, Number y) - * @param x - Amount to shift the `x` axis - * @param y - Amount to shift the `y` axis + * #.resetHitChecks + * @comp Collision + * @kind Method + * + * @sign public this .resetHitChecks() + * @sign public this .resetHitChecks(String componentList) + * @param componentList - A comma seperated list of components to re-check + * for collisions with. + * @sign public this .resetHitChecks(String component1[, .., String componentN]) + * @param component# - A component to re-check for collisions with. * - * Shifts the circle by the specified amount. + * Causes collision events to be received for collisions that are already + * taking place (normally, an additional event would not fire before said + * collisions cease and happen another time). + * If called without arguments, this method will cause all collision checks on the + * entity to fire events once more. To re-check for collisions with specific + * components, specify the components as a comma separated string or as + * a set of arguments. + * + * Calling this method with component names for which there are no collision + * checks has no effect. * * @example * ~~~ - * var circle = new Crafty.circle(0, 0, 10); - * circle.shift(5,5); - * //{x: 5, y: 5, radius: 10}; + * // this example fires the HitOn event each frame the collision with the Solid entity is active, instead of just the first time the collision occurs. + * Crafty.e("2D, Collision") + * .checkHits('Solid') + * .bind("HitOn", function(hitData) { + * Crafty.log("Collision with Solid entity was reported in this frame again!"); + * this.resetHitChecks('Solid'); // fire the HitOn event in the next frame also, if the collision is still active. + * }) * ~~~ */ - shift: function (x, y) { - this.x += x; - this.y += y; + resetHitChecks: function() { + var components = arguments; + var i = 0; + var collisionData; - var i = 0, p = this.points, - l = p.length, - current; - for (; i < l; i+=2) { - p[i] += x; - p[i+1] += y; + if (components.length === 0) { + for (collisionData in this._collisionData) { + this._collisionData[collisionData].occurring = false; + } } - }, - rotate: function () { - // We are a circle, we don't have to rotate :) - } -}; + if (components.length === 1) { + components = components[0].split(/\s*,\s*/); + } + for (; i < components.length; ++i) { + var component = components[i]; + collisionData = this._collisionData[component]; -Crafty.matrix = function (m) { - this.mtx = m; - this.width = m[0].length; - this.height = m.length; -}; + if (collisionData === undefined) { + continue; + } -Crafty.matrix.prototype = { - x: function (other) { - if (this.width != other.height) { - return; + collisionData.occurring = false; } - var result = []; - for (var i = 0; i < this.height; i++) { - result[i] = []; - for (var j = 0; j < other.width; j++) { - var sum = 0; - for (var k = 0; k < this.width; k++) { - sum += this.mtx[i][k] * other.mtx[k][j]; - } - result[i][j] = sum; - } - } - return new Crafty.matrix(result); + return this; }, + _SAT: function (poly1, poly2) { + var i = 0, + points1 = poly1.points, points2 = poly2.points, + l = points1.length/2, + j, k = points2.length/2, + nx=0, ny=0, + length, + min1, min2, + max1, max2, + interval, + MTV = -Infinity, + MNx = null, + MNy = null, + dot, + np; - e: function (row, col) { - //test if out of bounds - if (row < 1 || row > this.mtx.length || col < 1 || col > this.mtx[0].length) return null; - return this.mtx[row - 1][col - 1]; - } -}; + //loop through the edges of Polygon 1 + for (; i < l; i++) { + np = (i === l - 1 ? 0 : i + 1); -},{"../core/core.js":7,"./spatial-grid.js":43}],40:[function(require,module,exports){ -var Crafty = require('../core/core.js'), - DEG_TO_RAD = Math.PI / 180; + //generate the normal for the current edge + nx = -(points1[2*i+1] - points1[2*np+1]); + ny = (points1[2*i] - points1[2*np]); -/**@ - * #Collision - * @category 2D - * @trigger HitOn - Triggered when collisions occur. Will not trigger again until collisions of this type cease, or an event is requested once more (using `resetHitChecks(component)`). - { hitData } - * @trigger HitOff - Triggered when collision with a specific component type ceases - String - componentName - * - * Component to detect collision between any two convex polygons. - * - * If collision checks are registered for multiple component and collisions with - * multiple types occur simultaniously, each collision will cause an individual - * event to fire. - * - * @note All data received from events is only valid for the duration of the event's callback. - * If you wish to preserve the data, make a copy of it. - * - * For a description of collision event data (hitData above), see the documentation for - * `.hit()`. - * - */ -Crafty.c("Collision", { - init: function () { - this.requires("2D"); - this._collisionData = {}; + //normalize the vector + length = Math.sqrt(nx * nx + ny * ny); + nx /= length; + ny /= length; - this.collision(); - }, + //default min max + min1 = min2 = Infinity; + max1 = max2 = -Infinity; - // Run by Crafty when the component is removed - remove: function() { - this._cbr = null; - this.unbind("Resize", this._resizeMap); - this.unbind("Resize", this._checkBounds); - }, + //project all vertices from poly1 onto axis + for (j = 0; j < l; ++j) { + dot = points1[2*j] * nx + points1[2*j+1] * ny; + if (dot > max1) max1 = dot; + if (dot < min1) min1 = dot; + } - /**@ - * #.collision - * @comp Collision - * - * @trigger NewHitbox - when a new hitbox is assigned - Crafty.polygon - * - * @sign public this .collision([Crafty.polygon polygon]) - * @param polygon - Optional Crafty.polygon object that will act as the hit area. - * - * @sign public this .collision([Array coordinatePairs]) - * @param coordinatePairs - Optional array of x, y coordinate pairs to generate a hit area polygon. - * - * @sign public this .collision([x1, y1,.., xN, yN]) - * @param point# - Optional list of x, y coordinate pairs to generate a hit area polygon. - * - * Constructor that takes a polygon, an array of points or a list of points to use as the hit area, - * with points being relative to the object's position in its unrotated state. - * - * The hit area must be a convex shape and not concave for collision detection to work properly. - * - * If no parameter is passed, the x, y, w, h properties of the entity will be used, and the hitbox will be resized when the entity is. - * - * If a hitbox is set that is outside of the bounds of the entity itself, there will be a small performance penalty as it is tracked separately. - * - * In order for your custom hitbox to have any effect, you have to add the `Collision` component to all other entities this entity needs to collide with using this custom hitbox. - * On the contrary the collisions will be resolved using the default hitbox. See `.hit()` - `MBR` represents default hitbox collision, `SAT` represents custom hitbox collision. - * - * @example - * ~~~ - * Crafty.e("2D, Collision").collision( - * new Crafty.polygon([50, 0, 100, 100, 0, 100]) - * ); - * - * Crafty.e("2D, Collision").collision([50, 0, 100, 100, 0, 100]); - * - * Crafty.e("2D, Collision").collision(50, 0, 100, 100, 0, 100); - * ~~~ - * - * @see Crafty.polygon - */ - collision: function (polygon) { - // Unbind anything bound to "Resize" - this.unbind("Resize", this._resizeMap); - this.unbind("Resize", this._checkBounds); + //project all vertices from poly2 onto axis + for (j = 0; j < k; ++j) { + dot = points2[2*j] * nx + points2[2*j+1] * ny; + if (dot > max2) max2 = dot; + if (dot < min2 ) min2 = dot; + } - if (!polygon) { - // If no polygon is specified, then a polygon is created that matches the bounds of the entity - // It will be adjusted on a "Resize" event - polygon = new Crafty.polygon([0, 0, this._w, 0, this._w, this._h, 0, this._h]); - this.bind("Resize", this._resizeMap); - this._cbr = null; - } else { - // Otherwise, we set the specified hitbox, converting from a list of arguments to a polygon if necessary - if (arguments.length > 1) { - //convert args to array to create polygon - var args = Array.prototype.slice.call(arguments, 0); - polygon = new Crafty.polygon(args); - // Otherwise, we set the specified hitbox, converting from an array of points to a polygon if necessary - } else if (polygon.constructor === Array) { - //Clone the array so we don't modify it for anything else that might be using it - polygon = new Crafty.polygon(polygon.slice()); - // Otherwise, we set the specified hitbox + //calculate the minimum translation vector should be negative + if (min1 < min2) { + interval = min2 - max1; + nx = -nx; + ny = -ny; } else { - //Clone the polygon so we don't modify it for anything else that might be using it - polygon = polygon.clone(); + interval = min1 - max2; } - // Check to see if the polygon sits outside the entity, and set _cbr appropriately - // On resize, the new bounds will be checked if necessary - this._findBounds(polygon.points); - } - // If the entity is currently rotated, the points in the hitbox must also be rotated - if (this.rotation) { - polygon.rotate({ - cos: Math.cos(-this.rotation * DEG_TO_RAD), - sin: Math.sin(-this.rotation * DEG_TO_RAD), - o: { - x: this._origin.x, - y: this._origin.y - } - }); - } + //exit early if positive + if (interval >= 0) { + return false; + } - // Finally, assign the hitbox, and attach it to the "Collision" entity - this.map = polygon; - this.attach(this.map); - this.map.shift(this._x, this._y); - this.trigger("NewHitbox", polygon); - return this; - }, + if (interval > MTV) { + MTV = interval; + MNx = nx; + MNy = ny; + } + } - // If the hitbox is set by hand, it might extend beyond the entity. - // In such a case, we need to track this separately. - // This function finds a (non-minimal) bounding circle around the hitbox. - // - // It uses a pretty naive algorithm to do so, for more complicated options see [wikipedia](http://en.wikipedia.org/wiki/Bounding_sphere). - _findBounds: function(points) { - var minX = Infinity, maxX = -Infinity, minY=Infinity, maxY=-Infinity; - var l = points.length; + //loop through the edges of Polygon 2 + for (i = 0; i < k; i++) { + np = (i === k - 1 ? 0 : i + 1); - // Calculate the MBR of the points by finding the min/max x and y - for (var i=0; i maxX) - maxX = points[i]; - if (points[i+1] < minY) - minY = points[i+1]; - if (points[i+1] > maxY) - maxY = points[i+1]; - } + //generate the normal for the current edge + nx = -(points2[2*i+1] - points2[2*np+1]); + ny = (points2[2*i] - points2[2*np]); - // This describes a circle centered on the MBR of the points, with a diameter equal to its diagonal - // It will be used to find a rough bounding box round the points, even if they've been rotated - var cbr = { - cx: (minX + maxX) / 2, - cy: (minY + maxY) / 2, - r: Math.sqrt((maxX - minX)*(maxX - minX) + (maxY - minY)*(maxY - minY)) / 2 - }; + //normalize the vector + length = Math.sqrt(nx * nx + ny * ny); + nx /= length; + ny /= length; - // We need to worry about resizing, but only if resizing could possibly change whether the hitbox is in or out of bounds - // Thus if the upper-left corner is out of bounds, then there's no need to recheck on resize - if (minX >= 0 && minY >= 0) { - this._checkBounds = function() { - if (this._cbr === null && this._w < maxX || this._h < maxY) { - this._cbr = cbr; - this._calculateMBR(); - } else if (this._cbr) { - this._cbr = null; - this._calculateMBR(); - } - }; - this.bind("Resize", this._checkBounds); - } + //default min max + min1 = min2 = Infinity; + max1 = max2 = -Infinity; - // If the hitbox is within the entity, _cbr is null - // Otherwise, set it, and immediately calculate the bounding box. - if (minX >= 0 && minY >= 0 && maxX <= this._w && maxY <= this._h) { - this._cbr = null; - return false; - } else { - this._cbr = cbr; - this._calculateMBR(); - return true; - } - }, + //project all vertices from poly1 onto axis + for (j = 0; j < l; ++j) { + dot = points1[2*j] * nx + points1[2*j+1] * ny; + if (dot > max1) max1 = dot; + if (dot < min1) min1 = dot; + } - // The default behavior is to match the hitbox to the entity. - // This function will change the hitbox when a "Resize" event triggers. - _resizeMap: function (e) { - var dx, dy, rot = this.rotation * DEG_TO_RAD, - points = this.map.points; + //project all vertices from poly2 onto axis + for (j = 0; j < k; ++j) { + dot = points2[2*j] * nx + points2[2*j+1] * ny; + if (dot > max2) max2 = dot; + if (dot < min2) min2 = dot; + } - // Depending on the change of axis, move the corners of the rectangle appropriately - if (e.axis === 'w') { - if (rot) { - dx = e.amount * Math.cos(rot); - dy = e.amount * Math.sin(rot); + //calculate the minimum translation vector should be negative + if (min1 < min2) { + interval = min2 - max1; + nx = -nx; + ny = -ny; } else { - dx = e.amount; - dy = 0; + interval = min1 - max2; } - // "top right" point shifts on change of w - points[2] += dx; - points[3] += dy; - } else { - if (rot) { - dy = e.amount * Math.cos(rot); - dx = -e.amount * Math.sin(rot); - } else { - dx = 0; - dy = e.amount; + //exit early if positive + if (interval >= 0) { + return false; } - // "bottom left" point shifts on change of h - points[6] += dx; - points[7] += dy; + if (interval > MTV) { + MTV = interval; + MNx = nx; + MNy = ny; + } } - // "bottom right" point shifts on either change - points[4] += dx; - points[5] += dy; - }, - - /**@ - * #.hit - * @comp Collision - * @sign public Boolean/Array hit(String component) - * @param component - Check collision with entities that have this component - * applied to them. - * @return `false` if there is no collision. If a collision is detected, - * returns an Array of collision data objects (see below). - * - * Tests for collisions with entities that have the specified component - * applied to them. - * If a collision is detected, data regarding the collision will be present in - * the array returned by this method. - * If no collisions occur, this method returns false. - * - * Following is a description of a collision data object that this method may - * return: The returned collision data will be an Array of Objects with the - * type of collision used, the object collided and if the type used was SAT (a polygon was used as the hitbox) then an amount of overlap. - * ~~~ - * [{ - * obj: [entity], - * type: ["MBR" or "SAT"], - * overlap: [number] - * }] - * ~~~ - * - * - **obj:** The entity with which the collision occured. - * - **type:** Collision detection method used. One of: - * - *MBR:* Standard axis aligned rectangle intersection (`.intersect` in the 2D component). - * - *SAT:* Collision between any two convex polygons. Used when both colliding entities have the `Collision` component applied to them. - * - **overlap:** If SAT collision was used, this will signify the overlap percentage between the colliding entities. - * - * Keep in mind that both entities need to have the `Collision` component, if you want to check for `SAT` (custom hitbox) collisions between them. - * - * If you want more fine-grained control consider using `Crafty.map.search()`. - * - * @see 2D - */ - hit: function (component) { - var area = this._cbr || this._mbr || this, - results = Crafty.map.search(area, false), - i = 0, - l = results.length, - dupes = {}, - id, obj, oarea, key, - overlap = Crafty.rectManager.overlap, - hasMap = ('map' in this && 'containsPoint' in this.map), - finalresult = []; - - if (!l) { - return false; - } + return { + overlap: MTV, + normal: { + x: MNx, + y: MNy + } + }; + } +}); - for (; i < l; ++i) { - obj = results[i]; - oarea = obj._cbr || obj._mbr || obj; //use the mbr +},{"../core/core.js":8}],45:[function(require,module,exports){ +var Crafty = require('../core/core.js'); - if (!obj) continue; - id = obj[0]; - //check if not added to hash and that actually intersects - if (!dupes[id] && this[0] !== id && obj.__c[component] && overlap(oarea, area)) - dupes[id] = obj; - } +/**@ + * #Crafty.math + * @category Utilities + * @kind CoreObj + * + * A set of utility functions for common (and not so common) operations. + */ +Crafty.math = { + /**@ + * #Crafty.math.abs + * @comp Crafty.math + * @kind Method + * + * @sign public this Crafty.math.abs(Number n) + * @param n - Some value. + * @return Absolute value. + * + * Returns the absolute value. + */ + abs: function (x) { + return x < 0 ? -x : x; + }, - for (key in dupes) { - obj = dupes[key]; + /**@ + * #Crafty.math.amountOf + * @comp Crafty.math + * @kind Method + * + * @sign public Number Crafty.math.amountOf(Number checkValue, Number minValue, Number maxValue) + * @param checkValue - Value that should checked with minimum and maximum. + * @param minValue - Bottom of the range + * @param maxValue - Top of the range + * @return The position of the checked value in a coordinate system normalized such that `minValue` is 0 and `maxValue` is 1. + * + * If checkValue is within the range, this will return a number between 0 and 1. + */ + amountOf: function (checkValue, minValue, maxValue) { + if (minValue < maxValue) + return (checkValue - minValue) / (maxValue - minValue); + else + return (checkValue - maxValue) / (minValue - maxValue); + }, - if (hasMap && 'map' in obj) { - var SAT = this._SAT(this.map, obj.map); - SAT.obj = obj; - SAT.type = "SAT"; - if (SAT) finalresult.push(SAT); - } else { - finalresult.push({ - obj: obj, - type: "MBR" - }); - } - } - if (!finalresult.length) { - return false; - } + /**@ + * #Crafty.math.clamp + * @comp Crafty.math + * @kind Method + * + * @sign public Number Crafty.math.clamp(Number value, Number min, Number max) + * @param value - A value. + * @param max - Maximum that value can be. + * @param min - Minimum that value can be. + * @return The value between minimum and maximum. + * + * Restricts a value to be within a specified range. + */ + clamp: function (value, min, max) { + if (value > max) + return max; + else if (value < min) + return min; + else + return value; + }, - return finalresult; + /**@ + * #Crafty.math.degToRad + * Converts angle from degree to radian. + * @comp Crafty.math + * @kind Method + * + * @sign public Number degToRad(angleInDeg) + * @param angleInDeg - The angle in degrees. + * @return The angle in radians. + */ + degToRad: function (angleInDeg) { + return angleInDeg * Math.PI / 180; }, /**@ - * #.onHit - * @comp Collision - * @sign public this .onHit(String component, Function callbackOn[, Function callbackOff]) - * @param component - Component to check collisions for. - * @param callbackOn - Callback method to execute upon collision with component. Will be passed the results of the collision check in the same format documented for hit(). - * @param callbackOff - Callback method executed once as soon as collision stops. - * - * Creates an EnterFrame event calling `.hit()` each frame. When a collision is detected the `callbackOn` will be invoked. - * Note that the `callbackOn` will be invoked every frame the collision is active, not just the first time the collision occurs. - * - * If you want more fine-grained control consider using `.checkHits()`, `.hit()` or even `Crafty.map.search()`. + * #Crafty.math.distance + * @comp Crafty.math + * @kind Method + * + * @sign public Number Crafty.math.distance(Number x1, Number y1, Number x2, Number y2) + * @param x1 - First x coordinate. + * @param y1 - First y coordinate. + * @param x2 - Second x coordinate. + * @param y2 - Second y coordinate. + * @return The distance between the two points. * - * @see .checkHits - * @see .hit + * Distance between two points. */ - onHit: function (component, callbackOn, callbackOff) { - var justHit = false; - this.bind("EnterFrame", function () { - var hitData = this.hit(component); - if (hitData) { - justHit = true; - callbackOn.call(this, hitData); - } else if (justHit) { - if (typeof callbackOff == 'function') { - callbackOff.call(this); - } - justHit = false; - } - }); - return this; + distance: function (x1, y1, x2, y2) { + var squaredDistance = Crafty.math.squaredDistance(x1, y1, x2, y2); + return Math.sqrt(parseFloat(squaredDistance)); }, - /** - * This is a helper method for creating collisions handlers set up by `checkHits`. Do not call this directly. - * - * @param {String} component - The name of the component for which this handler checks for collisions. - * @param {Object} collisionData - Collision data object used to track collisions with the specified component. + /**@ + * #Crafty.math.lerp + * @comp Crafty.math + * @kind Method + * + * @sign public Number Crafty.math.lerp(Number value1, Number value2, Number amount) + * @param value1 - One value. + * @param value2 - Another value. + * @param amount - Amount of value2 to value1. + * @return Linear interpolated value. * - * @see .checkHits + * Linear interpolation. Passing amount with a value of 0 will cause value1 to be returned, + * a value of 1 will cause value2 to be returned. */ - _createCollisionHandler: function(component, collisionData) { - return function() { - var hitData = this.hit(component); - - if (collisionData.occurring === true) { - if (hitData !== false) { - // The collision is still in progress - return; - } + lerp: function (value1, value2, amount) { + return value1 + (value2 - value1) * amount; + }, - collisionData.occurring = false; - this.trigger("HitOff", component); - } else if (hitData !== false) { - collisionData.occurring = true; - this.trigger("HitOn", hitData); - } - }; + /**@ + * #Crafty.math.negate + * @comp Crafty.math + * @kind Method + * + * @sign public Number Crafty.math.negate(Number percent) + * @param percent - The probability of returning `-1` + * @return 1 or -1. + * + * Returns `1` or `-1` randomly. + */ + negate: function (percent) { + if (Math.random() < percent) + return -1; + else + return 1; }, /**@ - * #.checkHits - * @comp Collision - * @sign public this .checkHits(String componentList) - * @param componentList - A comma seperated list of components to check for collisions with. - * @sign public this .checkHits(String component1[, .., String componentN]) - * @param component# - A component to check for collisions with. + * #Crafty.math.radToDeg + * @comp Crafty.math + * @kind Method + * + * @sign public Number Crafty.math.radToDeg(Number angle) + * @param angleInRad - The angle in radian. + * @return The angle in degree. * - * Performs collision checks against all entities that have at least one of - * the components specified when calling this method. If collisions occur, - * a "HitOn" event containing the collision information will be fired for the - * entity on which this method was invoked. See the documentation for `.hit()` - * for a description of collision data contained in the event. - * When a collision that was reported ends, a corresponding "HitOff" event - * will be fired. - * - * Calling this method more than once for the same component type will not - * cause redundant hit checks. - * - * If you want more fine-grained control consider using `.hit()` or even `Crafty.map.search()`. + * Converts angle from radian to degree. + */ + radToDeg: function (angleInRad) { + return angleInRad * 180 / Math.PI; + }, + + /**@ + * #Crafty.math.randomElementOfArray + * @comp Crafty.math + * @kind Method + * + * @sign public Object Crafty.math.randomElementOfArray(Array array) + * @param array - A specific array. + * @return A random element of a specific array. * - * @note Hit checks are performed upon entering each new frame (using - * the *EnterFrame* event). It is entirely possible for object to move in - * said frame after the checks were performed (even if the more is the - * result of *EnterFrame*, as handlers run in no particular order). In such - * a case, the hit events will not fire until the next check is performed in - * the following frame. + * Returns a random element of a specific array. + */ + randomElementOfArray: function (array) { + return array[Math.floor(array.length * Math.random())]; + }, + + /**@ + * #Crafty.math.randomInt + * @comp Crafty.math + * @kind Method + * + * @sign public Number Crafty.math.randomInt(Number start, Number end) + * @param start - Smallest int value that can be returned. + * @param end - Biggest int value that can be returned. + * @return A random int. * - * @example - * ~~~ - * Crafty.e("2D, Collision") - * .checkHits('Solid') // check for collisions with entities that have the Solid component in each frame - * .bind("HitOn", function(hitData) { - * Crafty.log("Collision with Solid entity occurred for the first time."); - * }) - * .bind("HitOff", function(comp) { - * Crafty.log("Collision with Solid entity ended."); - * }); - * ~~~ + * Returns a random int within a specific range. + */ + randomInt: function (start, end) { + return start + Math.floor((1 + end - start) * Math.random()); + }, + + /**@ + * #Crafty.math.randomNumber + * @comp Crafty.math + * @kind Method + * + * @sign public Number Crafty.math.randomNumber(Number start, Number end) + * @param start - Smallest number value that can be returned. + * @param end - Biggest number value that can be returned. + * @return A random number. * - * @see .hit + * Returns a random number in within a specific range. */ - checkHits: function () { - var components = arguments; - var i = 0; + randomNumber: function (start, end) { + return start + (end - start) * Math.random(); + }, - if (components.length === 1) { - components = components[0].split(/\s*,\s*/); - } + /**@ + * #Crafty.math.squaredDistance + * @comp Crafty.math + * @kind Method + * + * @sign public Number Crafty.math.squaredDistance(Number x1, Number y1, Number x2, Number y2) + * @param x1 - First x coordinate. + * @param y1 - First y coordinate. + * @param x2 - Second x coordinate. + * @param y2 - Second y coordinate. + * @return The squared distance between the two points. + * + * Squared distance between two points. + */ + squaredDistance: function (x1, y1, x2, y2) { + return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2); + }, - for (; i < components.length; ++i) { - var component = components[i]; - var collisionData = this._collisionData[component]; + /**@ + * #Crafty.math.withinRange + * @comp Crafty.math + * @kind Method + * + * @sign public Boolean Crafty.math.withinRange(Number value, Number min, Number max) + * @param value - The specific value. + * @param min - Minimum value. + * @param max - Maximum value. + * @return Returns true if value is within a specific range. + * + * Check if a value is within a specific range. + */ + withinRange: function (value, min, max) { + return (value >= min && value <= max); + } +}; - if (collisionData !== undefined) { - // There is already a handler for collision with this component - continue; - } +Crafty.math.Vector2D = (function () { + /**@ + * #Crafty.math.Vector2D + * @category 2D + * @kind Class + * + * @class This is a general purpose 2D vector class + * + * Vector2D uses the following form: + * + * + * @public + * @sign public {Vector2D} Vector2D(); + * @sign public {Vector2D} Vector2D(Vector2D); + * @sign public {Vector2D} Vector2D(Number, Number); + * @param {Vector2D|Number=0} x + * @param {Number=0} y + */ - this._collisionData[component] = collisionData = { occurring: false, handler: null }; - collisionData.handler = this._createCollisionHandler(component, collisionData); + function Vector2D(x, y) { + if (x instanceof Vector2D) { + this.x = x.x; + this.y = x.y; + } else if (arguments.length === 2) { + this.x = x; + this.y = y; + } else if (arguments.length > 0) + throw "Unexpected number of arguments for Vector2D()"; + } // class Vector2D - this.bind("EnterFrame", collisionData.handler); - } + Vector2D.prototype.x = 0; + Vector2D.prototype.y = 0; + /**@ + * #.add + * @comp Crafty.math.Vector2D + * @kind Method + * + * + * Adds the passed vector to this vector + * + * @public + * @sign public {Vector2D} add(Vector2D); + * @param {vector2D} vecRH + * @returns {Vector2D} this after adding + */ + Vector2D.prototype.add = function (vecRH) { + this.x += vecRH.x; + this.y += vecRH.y; return this; - }, + }; // add /**@ - * #.ignoreHits - * @comp Collision + * #.angleBetween + * @comp Crafty.math.Vector2D + * @kind Method + * * - * @sign public this .ignoreHits() + * Calculates the angle between the passed vector and this vector, using <0,0> as the point of reference. + * Angles returned have the range (−π, π]. * - * @sign public this .ignoreHits(String componentList) - * @param componentList - A comma separated list of components to stop checking - * for collisions with. + * @public + * @sign public {Number} angleBetween(Vector2D); + * @param {Vector2D} vecRH + * @returns {Number} the angle between the two vectors in radians + */ + Vector2D.prototype.angleBetween = function (vecRH) { + return Math.atan2(this.x * vecRH.y - this.y * vecRH.x, this.x * vecRH.x + this.y * vecRH.y); + }; // angleBetween + + /**@ + * #.angleTo + * @comp Crafty.math.Vector2D + * @kind Method + * * - * @sign public this .ignoreHits(String component1[, .., String componentN]) - * @param component# - A component to stop checking for collisions with. + * Calculates the angle to the passed vector from this vector, using this vector as the point of reference. * - * Stops checking for collisions with all, or certain, components. If called - * without arguments, this method will cause all collision checks on the - * entity to cease. To disable checks for collisions with specific - * components, specify the components as a comma separated string or as - * a set of arguments. + * @public + * @sign public {Number} angleTo(Vector2D); + * @param {Vector2D} vecRH + * @returns {Number} the angle to the passed vector in radians + */ + Vector2D.prototype.angleTo = function (vecRH) { + return Math.atan2(vecRH.y - this.y, vecRH.x - this.x); + }; + + /**@ + * #.clone + * @comp Crafty.math.Vector2D + * @kind Method + * * - * Calling this method with component names for which there are no collision - * checks has no effect. + * Creates and exact, numeric copy of this vector * - * @example - * ~~~ - * Crafty.e("2D, Collision") - * .checkHits('Solid') - * ... - * .ignoreHits('Solid'); // stop checking for collisions with entities that have the Solid component - * ~~~ + * @public + * @sign public {Vector2D} clone(); + * @returns {Vector2D} the new vector */ - ignoreHits: function () { - var components = arguments; - var i = 0; - var collisionData; - - if (components.length === 0) { - for (collisionData in this._collisionData) { - this.unbind("EnterFrame", collisionData.handler); - } - - this._collisionData = {}; - } + Vector2D.prototype.clone = function () { + return new Vector2D(this); + }; // clone - if (components.length === 1) { - components = components[0].split(/\s*,\s*/); - } - - for (; i < components.length; ++i) { - var component = components[i]; - collisionData = this._collisionData[component]; - - if (collisionData === undefined) { - continue; - } - - this.unbind("EnterFrame", collisionData.handler); - delete this._collisionData[component]; - } - - return this; - }, + /**@ + * #.distance + * @comp Crafty.math.Vector2D + * @kind Method + * + * + * Calculates the distance from this vector to the passed vector. + * + * @public + * @sign public {Number} distance(Vector2D); + * @param {Vector2D} vecRH + * @returns {Number} the distance between the two vectors + */ + Vector2D.prototype.distance = function (vecRH) { + return Math.sqrt((vecRH.x - this.x) * (vecRH.x - this.x) + (vecRH.y - this.y) * (vecRH.y - this.y)); + }; // distance /**@ - * #.resetHitChecks - * @comp Collision - * @sign public this .resetHitChecks() - * @sign public this .resetHitChecks(String componentList) - * @param componentList - A comma seperated list of components to re-check - * for collisions with. - * @sign public this .resetHitChecks(String component1[, .., String componentN]) - * @param component# - A component to re-check for collisions with. - * - * Causes collision events to be received for collisions that are already - * taking place (normally, an additional event would not fire before said - * collisions cease and happen another time). - * If called without arguments, this method will cause all collision checks on the - * entity to fire events once more. To re-check for collisions with specific - * components, specify the components as a comma separated string or as - * a set of arguments. + * #.distanceSq + * @comp Crafty.math.Vector2D + * @kind Method + * * - * Calling this method with component names for which there are no collision - * checks has no effect. + * Calculates the squared distance from this vector to the passed vector. + * This function avoids calculating the square root, thus being slightly faster than .distance( ). * - * @example - * ~~~ - * // this example fires the HitOn event each frame the collision with the Solid entity is active, instead of just the first time the collision occurs. - * Crafty.e("2D, Collision") - * .checkHits('Solid') - * .bind("HitOn", function(hitData) { - * Crafty.log("Collision with Solid entity was reported in this frame again!"); - * this.resetHitChecks('Solid'); // fire the HitOn event in the next frame also, if the collision is still active. - * }) - * ~~~ + * @public + * @sign public {Number} distanceSq(Vector2D); + * @param {Vector2D} vecRH + * @returns {Number} the squared distance between the two vectors + * @see .distance */ - resetHitChecks: function() { - var components = arguments; - var i = 0; - var collisionData; - - if (components.length === 0) { - for (collisionData in this._collisionData) { - this._collisionData[collisionData].occurring = false; - } - } - - if (components.length === 1) { - components = components[0].split(/\s*,\s*/); - } - - for (; i < components.length; ++i) { - var component = components[i]; - collisionData = this._collisionData[component]; - - if (collisionData === undefined) { - continue; - } - - collisionData.occurring = false; - } + Vector2D.prototype.distanceSq = function (vecRH) { + return (vecRH.x - this.x) * (vecRH.x - this.x) + (vecRH.y - this.y) * (vecRH.y - this.y); + }; // distanceSq + /**@ + * #.divide + * @comp Crafty.math.Vector2D + * @kind Method + * + * + * Divides this vector by the passed vector. + * + * @public + * @sign public {Vector2D} divide(Vector2D); + * @param {Vector2D} vecRH + * @returns {Vector2D} this vector after dividing + */ + Vector2D.prototype.divide = function (vecRH) { + this.x /= vecRH.x; + this.y /= vecRH.y; return this; - }, - - _SAT: function (poly1, poly2) { - var i = 0, - points1 = poly1.points, points2 = poly2.points, - l = points1.length/2, - j, k = points2.length/2, - nx=0, ny=0, - length, - min1, min2, - max1, max2, - interval, - MTV = -Infinity, - MNx = null, - MNy = null, - dot, - np; - - //loop through the edges of Polygon 1 - for (; i < l; i++) { - np = (i == l - 1 ? 0 : i + 1); - - //generate the normal for the current edge - nx = -(points1[2*i+1] - points1[2*np+1]); - ny = (points1[2*i] - points1[2*np]); - - //normalize the vector - length = Math.sqrt(nx * nx + ny * ny); - nx /= length; - ny /= length; - - //default min max - min1 = min2 = Infinity; - max1 = max2 = -Infinity; - - //project all vertices from poly1 onto axis - for (j = 0; j < l; ++j) { - dot = points1[2*j] * nx + points1[2*j+1] * ny; - if (dot > max1) max1 = dot; - if (dot < min1) min1 = dot; - } - - //project all vertices from poly2 onto axis - for (j = 0; j < k; ++j) { - dot = points2[2*j] * nx + points2[2*j+1] * ny; - if (dot > max2) max2 = dot; - if (dot < min2 ) min2 = dot; - } - - //calculate the minimum translation vector should be negative - if (min1 < min2) { - interval = min2 - max1; - nx = -nx; - ny = -ny; - } else { - interval = min1 - max2; - } - - //exit early if positive - if (interval >= 0) { - return false; - } + }; // divide - if (interval > MTV) { - MTV = interval; - MNx = nx; - MNy = ny; - } - } + /**@ + * #.dotProduct + * @comp Crafty.math.Vector2D + * @kind Method + * + * + * Calculates the dot product of this and the passed vectors + * + * @public + * @sign public {Number} dotProduct(Vector2D); + * @param {Vector2D} vecRH + * @returns {Number} the resultant dot product + */ + Vector2D.prototype.dotProduct = function (vecRH) { + return this.x * vecRH.x + this.y * vecRH.y; + }; // dotProduct - //loop through the edges of Polygon 2 - for (i = 0; i < k; i++) { - np = (i == k - 1 ? 0 : i + 1); + /**@ + * #.crossProduct + * @comp Crafty.math.Vector2D + * @kind Method + * + * + * Calculates the z component of the cross product of the two vectors augmented to 3D. + * + * @public + * @sign public {Number} crossProduct(Vector2D); + * @param {Vector2D} vecRH + * @returns {Number} the resultant cross product + */ + Vector2D.prototype.crossProduct = function (vecRH) { + return this.x * vecRH.y - this.y * vecRH.x; + }; // crossProduct - //generate the normal for the current edge - nx = -(points2[2*i+1] - points2[2*np+1]); - ny = (points2[2*i] - points2[2*np]); + /**@ + * #.equals + * @comp Crafty.math.Vector2D + * @kind Method + * + * + * Determines if this vector is numerically equivalent to the passed vector. + * + * @public + * @sign public {Boolean} equals(Vector2D); + * @param {Vector2D} vecRH + * @returns {Boolean} true if the vectors are equivalent + */ + Vector2D.prototype.equals = function (vecRH) { + return vecRH instanceof Vector2D && + this.x === vecRH.x && this.y === vecRH.y; + }; // equals - //normalize the vector - length = Math.sqrt(nx * nx + ny * ny); - nx /= length; - ny /= length; + /**@ + * #.perpendicular + * @comp Crafty.math.Vector2D + * @kind Method + * + * + * Calculates a new vector that is perpendicular to this vector. + * The perpendicular vector has the same magnitude as this vector and is obtained by a counter-clockwise rotation of 90° of this vector. + * + * @public + * @sign public {Vector2D} perpendicular([Vector2D]); + * @param {Vector2D} [result] - An optional parameter to save the result in + * @returns {Vector2D} the perpendicular vector + */ + Vector2D.prototype.perpendicular = function (result) { + result = result || new Vector2D(); + return result.setValues(-this.y, this.x); + }; // perpendicular - //default min max - min1 = min2 = Infinity; - max1 = max2 = -Infinity; + /**@ + * #.getNormal + * @comp Crafty.math.Vector2D + * @kind Method + * + * Calculates a new right-handed unit vector that is perpendicular to the line created by this and the passed vector. + * + * @public + * @sign public {Vector2D} getNormal(Vector2D[, Vector2D]); + * @param {Vector2D} vecRH + * @param {Vector2D} [result] - An optional parameter to save the result in + * @returns {Vector2D} the new normal vector + */ + Vector2D.prototype.getNormal = function (vecRH, result) { + result = result || new Vector2D(); + return result.setValues(vecRH.y - this.y, this.x - vecRH.x).normalize(); + }; // getNormal - //project all vertices from poly1 onto axis - for (j = 0; j < l; ++j) { - dot = points1[2*j] * nx + points1[2*j+1] * ny; - if (dot > max1) max1 = dot; - if (dot < min1) min1 = dot; - } + /**@ + * #.isZero + * @comp Crafty.math.Vector2D + * @kind Method + * + * + * Determines if this vector is equal to <0,0> + * + * @public + * @sign public {Boolean} isZero(); + * @returns {Boolean} true if this vector is equal to <0,0> + */ + Vector2D.prototype.isZero = function () { + return this.x === 0 && this.y === 0; + }; // isZero - //project all vertices from poly2 onto axis - for (j = 0; j < k; ++j) { - dot = points2[2*j] * nx + points2[2*j+1] * ny; - if (dot > max2) max2 = dot; - if (dot < min2) min2 = dot; - } + /**@ + * #.magnitude + * @comp Crafty.math.Vector2D + * @kind Method + * + * Calculates the magnitude of this vector. + * Note: Function objects in JavaScript already have a 'length' member, hence the use of magnitude instead. + * + * @public + * @sign public {Number} magnitude(); + * @returns {Number} the magnitude of this vector + */ + Vector2D.prototype.magnitude = function () { + return Math.sqrt(this.x * this.x + this.y * this.y); + }; // magnitude - //calculate the minimum translation vector should be negative - if (min1 < min2) { - interval = min2 - max1; - nx = -nx; - ny = -ny; - } else { - interval = min1 - max2; - } + /**@ + * #.magnitudeSq + * @comp Crafty.math.Vector2D + * @kind Method + * + * Calculates the square of the magnitude of this vector. + * This function avoids calculating the square root, thus being slightly faster than .magnitude( ). + * + * @public + * @sign public {Number} magnitudeSq(); + * @returns {Number} the square of the magnitude of this vector + * @see .magnitude + */ + Vector2D.prototype.magnitudeSq = function () { + return this.x * this.x + this.y * this.y; + }; // magnitudeSq - //exit early if positive - if (interval >= 0) { - return false; - } + /**@ + * #.multiply + * @comp Crafty.math.Vector2D + * @kind Method + * + * Multiplies this vector by the passed vector + * + * @public + * @sign public {Vector2D} multiply(Vector2D); + * @param {Vector2D} vecRH + * @returns {Vector2D} this vector after multiplying + */ + Vector2D.prototype.multiply = function (vecRH) { + this.x *= vecRH.x; + this.y *= vecRH.y; + return this; + }; // multiply - if (interval > MTV) { - MTV = interval; - MNx = nx; - MNy = ny; - } - } + /**@ + * #.negate + * @comp Crafty.math.Vector2D + * @kind Method + * + * Negates this vector (ie. <-x,-y>) + * + * @public + * @sign public {Vector2D} negate(); + * @returns {Vector2D} this vector after negation + */ + Vector2D.prototype.negate = function () { + this.x = -this.x; + this.y = -this.y; + return this; + }; // negate - return { - overlap: MTV, - normal: { - x: MNx, - y: MNy - } - }; - } -}); + /**@ + * #.normalize + * @comp Crafty.math.Vector2D + * @kind Method + * + * Normalizes this vector (scales the vector so that its new magnitude is 1) + * For vectors where magnitude is 0, <1,0> is returned. + * + * @public + * @sign public {Vector2D} normalize(); + * @returns {Vector2D} this vector after normalization + */ + Vector2D.prototype.normalize = function () { + var lng = Math.sqrt(this.x * this.x + this.y * this.y); -},{"../core/core.js":7}],41:[function(require,module,exports){ -var Crafty = require('../core/core.js'); + if (lng === 0) { + // default due East + this.x = 1; + this.y = 0; + } else { + this.x /= lng; + this.y /= lng; + } // else + return this; + }; // normalize -/**@ - * #Crafty.math - * @category Utilities - * - * A set of utility functions for common (and not so common) operations. - */ -Crafty.math = { /**@ - * #Crafty.math.abs - * @comp Crafty.math - * @sign public this Crafty.math.abs(Number n) - * @param n - Some value. - * @return Absolute value. + * #.scale + * @comp Crafty.math.Vector2D + * @kind Method + * + * Scales this vector by the passed amount(s) + * If scalarY is omitted, scalarX is used for both axes * - * Returns the absolute value. + * @public + * @sign public {Vector2D} scale(Number[, Number]); + * @param {Number} scalarX + * @param {Number} [scalarY] + * @returns {Vector2D} this after scaling */ - abs: function (x) { - return x < 0 ? -x : x; - }, + Vector2D.prototype.scale = function (scalarX, scalarY) { + if (scalarY === undefined) + scalarY = scalarX; + + this.x *= scalarX; + this.y *= scalarY; + + return this; + }; // scale /**@ - * #Crafty.math.amountOf - * @comp Crafty.math - * @sign public Number Crafty.math.amountOf(Number checkValue, Number minValue, Number maxValue) - * @param checkValue - Value that should checked with minimum and maximum. - * @param minValue - Bottom of the range - * @param maxValue - Top of the range - * @return The position of the checked value in a coordinate system normalized such that `minValue` is 0 and `maxValue` is 1. + * #.scaleToMagnitude + * @comp Crafty.math.Vector2D + * @kind Method + * + * Scales this vector such that its new magnitude is equal to the passed value. * - * If checkValue is within the range, this will return a number between 0 and 1. + * @public + * @sign public {Vector2D} scaleToMagnitude(Number); + * @param {Number} mag + * @returns {Vector2D} this vector after scaling */ - amountOf: function (checkValue, minValue, maxValue) { - if (minValue < maxValue) - return (checkValue - minValue) / (maxValue - minValue); - else - return (checkValue - maxValue) / (minValue - maxValue); - }, - + Vector2D.prototype.scaleToMagnitude = function (mag) { + var k = mag / this.magnitude(); + this.x *= k; + this.y *= k; + return this; + }; // scaleToMagnitude /**@ - * #Crafty.math.clamp - * @comp Crafty.math - * @sign public Number Crafty.math.clamp(Number value, Number min, Number max) - * @param value - A value. - * @param max - Maximum that value can be. - * @param min - Minimum that value can be. - * @return The value between minimum and maximum. + * #.setValues + * @comp Crafty.math.Vector2D + * @kind Method + * + * Sets the values of this vector using a passed vector or pair of numbers. * - * Restricts a value to be within a specified range. + * @public + * @sign public {Vector2D} setValues(Vector2D); + * @sign public {Vector2D} setValues(Number, Number); + * @param {Number|Vector2D} x + * @param {Number} y + * @returns {Vector2D} this vector after setting of values */ - clamp: function (value, min, max) { - if (value > max) - return max; - else if (value < min) - return min; - else - return value; - }, + Vector2D.prototype.setValues = function (x, y) { + if (x instanceof Vector2D) { + this.x = x.x; + this.y = x.y; + } else { + this.x = x; + this.y = y; + } // else + + return this; + }; // setValues /**@ - * #Crafty.math.degToRad - * Converts angle from degree to radian. - * @comp Crafty.math - * @sign public Number degToRad(angleInDeg) - * @param angleInDeg - The angle in degrees. - * @return The angle in radians. + * #.subtract + * @comp Crafty.math.Vector2D + * @kind Method + * + * Subtracts the passed vector from this vector. + * + * @public + * @sign public {Vector2D} subtract(Vector2D); + * @param {Vector2D} vecRH + * @returns {vector2D} this vector after subtracting */ - degToRad: function (angleInDeg) { - return angleInDeg * Math.PI / 180; - }, + Vector2D.prototype.subtract = function (vecRH) { + this.x -= vecRH.x; + this.y -= vecRH.y; + return this; + }; // subtract /**@ - * #Crafty.math.distance - * @comp Crafty.math - * @sign public Number Crafty.math.distance(Number x1, Number y1, Number x2, Number y2) - * @param x1 - First x coordinate. - * @param y1 - First y coordinate. - * @param x2 - Second x coordinate. - * @param y2 - Second y coordinate. - * @return The distance between the two points. + * #.toString + * @comp Crafty.math.Vector2D + * @kind Method + * + * Returns a string representation of this vector. * - * Distance between two points. + * @public + * @sign public {String} toString(); + * @returns {String} */ - distance: function (x1, y1, x2, y2) { - var squaredDistance = Crafty.math.squaredDistance(x1, y1, x2, y2); - return Math.sqrt(parseFloat(squaredDistance)); - }, + Vector2D.prototype.toString = function () { + return "Vector2D(" + this.x + ", " + this.y + ")"; + }; // toString /**@ - * #Crafty.math.lerp - * @comp Crafty.math - * @sign public Number Crafty.math.lerp(Number value1, Number value2, Number amount) - * @param value1 - One value. - * @param value2 - Another value. - * @param amount - Amount of value2 to value1. - * @return Linear interpolated value. + * #.translate + * @comp Crafty.math.Vector2D + * @kind Method + * + * Translates (moves) this vector by the passed amounts. + * If dy is omitted, dx is used for both axes. * - * Linear interpolation. Passing amount with a value of 0 will cause value1 to be returned, - * a value of 1 will cause value2 to be returned. + * @public + * @sign public {Vector2D} translate(Number[, Number]); + * @param {Number} dx + * @param {Number} [dy] + * @returns {Vector2D} this vector after translating */ - lerp: function (value1, value2, amount) { - return value1 + (value2 - value1) * amount; - }, + Vector2D.prototype.translate = function (dx, dy) { + if (dy === undefined) + dy = dx; + + this.x += dx; + this.y += dy; + + return this; + }; // translate /**@ - * #Crafty.math.negate - * @comp Crafty.math - * @sign public Number Crafty.math.negate(Number percent) - * @param percent - The probability of returning `-1` - * @return 1 or -1. + * #.tripleProduct + * @comp Crafty.math.Vector2D + * @kind Method + * + * Calculates the triple product of three vectors. + * triple vector product = b(a•c) - a(b•c) * - * Returns `1` or `-1` randomly. + * @public + * @static + * @sign public {Vector2D} tripleProduct(Vector2D, Vector2D, Vector2D, [Vector2D]); + * @param {Vector2D} a + * @param {Vector2D} b + * @param {Vector2D} c + * @param {Vector2D} [result] - An optional parameter to save the result in + * @return {Vector2D} the triple product as a new vector */ - negate: function (percent) { - if (Math.random() < percent) - return -1; - else - return 1; - }, + Vector2D.tripleProduct = function (a, b, c, result) { + result = result || new Crafty.math.Vector2D(); + var ac = a.dotProduct(c); + var bc = b.dotProduct(c); + return result.setValues(b.x * ac - a.x * bc, b.y * ac - a.y * bc); + }; + + return Vector2D; +})(); +Crafty.math.Matrix2D = (function () { /**@ - * #Crafty.math.radToDeg - * @comp Crafty.math - * @sign public Number Crafty.math.radToDeg(Number angle) - * @param angleInRad - The angle in radian. - * @return The angle in degree. + * #Crafty.math.Matrix2D + * @category 2D + * @kind Class + * + * @class This is a 2D Matrix2D class. It is 3x3 to allow for affine transformations in 2D space. + * The third row is always assumed to be [0, 0, 1]. * - * Converts angle from radian to degree. + * Matrix2D uses the following form, as per the whatwg.org specifications for canvas.transform(): + * [a, c, e] + * [b, d, f] + * [0, 0, 1] + * + * @public + * @sign public {Matrix2D} new Matrix2D(); + * @sign public {Matrix2D} new Matrix2D(Matrix2D); + * @sign public {Matrix2D} new Matrix2D(Number, Number, Number, Number, Number, Number); + * @param {Matrix2D|Number=1} a + * @param {Number=0} b + * @param {Number=0} c + * @param {Number=1} d + * @param {Number=0} e + * @param {Number=0} f */ - radToDeg: function (angleInRad) { - return angleInRad * 180 / Math.PI; - }, + function Matrix2D (a, b, c, d, e, f) { + if (a instanceof Matrix2D) { + this.a = a.a; + this.b = a.b; + this.c = a.c; + this.d = a.d; + this.e = a.e; + this.f = a.f; + } else if (arguments.length === 6) { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.e = e; + this.f = f; + } else if (arguments.length > 0) + throw "Unexpected number of arguments for Matrix2D()"; + } // class Matrix2D + + Matrix2D.prototype.a = 1; + Matrix2D.prototype.b = 0; + Matrix2D.prototype.c = 0; + Matrix2D.prototype.d = 1; + Matrix2D.prototype.e = 0; + Matrix2D.prototype.f = 0; /**@ - * #Crafty.math.randomElementOfArray - * @comp Crafty.math - * @sign public Object Crafty.math.randomElementOfArray(Array array) - * @param array - A specific array. - * @return A random element of a specific array. + * #.apply + * @comp Crafty.math.Matrix2D + * @kind Method + * + * Applies the matrix transformations to the passed object * - * Returns a random element of a specific array. + * @public + * @sign public {Vector2D} apply(Vector2D); + * @param {Vector2D} vecRH - vector to be transformed + * @returns {Vector2D} the passed vector object after transforming */ - randomElementOfArray: function (array) { - return array[Math.floor(array.length * Math.random())]; - }, + Matrix2D.prototype.apply = function (vecRH) { + // I'm not sure of the best way for this function to be implemented. Ideally + // support for other objects (rectangles, polygons, etc) should be easily + // addable in the future. Maybe a function (apply) is not the best way to do + // this...? + + var tmpX = vecRH.x; + vecRH.x = tmpX * this.a + vecRH.y * this.c + this.e; + vecRH.y = tmpX * this.b + vecRH.y * this.d + this.f; + // no need to homogenize since the third row is always [0, 0, 1] + + return vecRH; + }; // apply /**@ - * #Crafty.math.randomInt - * @comp Crafty.math - * @sign public Number Crafty.math.randomInt(Number start, Number end) - * @param start - Smallest int value that can be returned. - * @param end - Biggest int value that can be returned. - * @return A random int. + * #.clone + * @comp Crafty.math.Matrix2D + * @kind Method + * + * Creates an exact, numeric copy of the current matrix * - * Returns a random int within a specific range. + * @public + * @sign public {Matrix2D} clone(); + * @returns {Matrix2D} */ - randomInt: function (start, end) { - return start + Math.floor((1 + end - start) * Math.random()); - }, + Matrix2D.prototype.clone = function () { + return new Matrix2D(this); + }; // clone /**@ - * #Crafty.math.randomNumber - * @comp Crafty.math - * @sign public Number Crafty.math.randomInt(Number start, Number end) - * @param start - Smallest number value that can be returned. - * @param end - Biggest number value that can be returned. - * @return A random number. + * #.combine + * @comp Crafty.math.Matrix2D + * @kind Method * - * Returns a random number in within a specific range. + * Multiplies this matrix with another, overriding the values of this matrix. + * The passed matrix is assumed to be on the right-hand side. + * + * @public + * @sign public {Matrix2D} combine(Matrix2D); + * @param {Matrix2D} mtrxRH + * @returns {Matrix2D} this matrix after combination */ - randomNumber: function (start, end) { - return start + (end - start) * Math.random(); - }, + Matrix2D.prototype.combine = function (mtrxRH) { + var tmp = this.a; + this.a = tmp * mtrxRH.a + this.b * mtrxRH.c; + this.b = tmp * mtrxRH.b + this.b * mtrxRH.d; + tmp = this.c; + this.c = tmp * mtrxRH.a + this.d * mtrxRH.c; + this.d = tmp * mtrxRH.b + this.d * mtrxRH.d; + tmp = this.e; + this.e = tmp * mtrxRH.a + this.f * mtrxRH.c + mtrxRH.e; + this.f = tmp * mtrxRH.b + this.f * mtrxRH.d + mtrxRH.f; + return this; + }; // combine /**@ - * #Crafty.math.squaredDistance - * @comp Crafty.math - * @sign public Number Crafty.math.squaredDistance(Number x1, Number y1, Number x2, Number y2) - * @param x1 - First x coordinate. - * @param y1 - First y coordinate. - * @param x2 - Second x coordinate. - * @param y2 - Second y coordinate. - * @return The squared distance between the two points. + * #.equals + * @comp Crafty.math.Matrix2D + * @kind Method * - * Squared distance between two points. + * Checks for the numeric equality of this matrix versus another. + * + * @public + * @sign public {Boolean} equals(Matrix2D); + * @param {Matrix2D} mtrxRH + * @returns {Boolean} true if the two matrices are numerically equal */ - squaredDistance: function (x1, y1, x2, y2) { - return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2); - }, + Matrix2D.prototype.equals = function (mtrxRH) { + return mtrxRH instanceof Matrix2D && + this.a === mtrxRH.a && this.b === mtrxRH.b && this.c === mtrxRH.c && + this.d === mtrxRH.d && this.e === mtrxRH.e && this.f === mtrxRH.f; + }; // equals /**@ - * #Crafty.math.withinRange - * @comp Crafty.math - * @sign public Boolean Crafty.math.withinRange(Number value, Number min, Number max) - * @param value - The specific value. - * @param min - Minimum value. - * @param max - Maximum value. - * @return Returns true if value is within a specific range. + * #.determinant + * @comp Crafty.math.Matrix2D + * @kind Method * - * Check if a value is within a specific range. + * Calculates the determinant of this matrix + * + * @public + * @sign public {Number} determinant(); + * @returns {Number} det(this matrix) */ - withinRange: function (value, min, max) { - return (value >= min && value <= max); - } -}; + Matrix2D.prototype.determinant = function () { + return this.a * this.d - this.b * this.c; + }; // determinant -Crafty.math.Vector2D = (function () { /**@ - * #Crafty.math.Vector2D - * @category 2D - * @class This is a general purpose 2D vector class + * #.invert + * @comp Crafty.math.Matrix2D + * @kind Method * - * Vector2D uses the following form: - * + * Inverts this matrix if possible * * @public - * @sign public {Vector2D} Vector2D(); - * @sign public {Vector2D} Vector2D(Vector2D); - * @sign public {Vector2D} Vector2D(Number, Number); - * @param {Vector2D|Number=0} x - * @param {Number=0} y + * @sign public {Matrix2D} invert(); + * @returns {Matrix2D} this inverted matrix or the original matrix on failure + * @see .isInvertible */ + Matrix2D.prototype.invert = function () { + var det = this.determinant(); - function Vector2D(x, y) { - if (x instanceof Vector2D) { - this.x = x.x; - this.y = x.y; - } else if (arguments.length === 2) { - this.x = x; - this.y = y; - } else if (arguments.length > 0) - throw "Unexpected number of arguments for Vector2D()"; - } // class Vector2D + // matrix is invertible if its determinant is non-zero + if (det !== 0) { + var old = { + a: this.a, + b: this.b, + c: this.c, + d: this.d, + e: this.e, + f: this.f + }; + this.a = old.d / det; + this.b = -old.b / det; + this.c = -old.c / det; + this.d = old.a / det; + this.e = (old.c * old.f - old.e * old.d) / det; + this.f = (old.e * old.b - old.a * old.f) / det; + } // if - Vector2D.prototype.x = 0; - Vector2D.prototype.y = 0; + return this; + }; // invert /**@ - * #.add - * @comp Crafty.math.Vector2D - * - * Adds the passed vector to this vector + * #.isIdentity + * @comp Crafty.math.Matrix2D + * @kind Method + * + * Returns true if this matrix is the identity matrix * * @public - * @sign public {Vector2D} add(Vector2D); - * @param {vector2D} vecRH - * @returns {Vector2D} this after adding + * @sign public {Boolean} isIdentity(); + * @returns {Boolean} */ - Vector2D.prototype.add = function (vecRH) { - this.x += vecRH.x; - this.y += vecRH.y; - return this; - }; // add + Matrix2D.prototype.isIdentity = function () { + return this.a === 1 && this.b === 0 && this.c === 0 && this.d === 1 && this.e === 0 && this.f === 0; + }; // isIdentity /**@ - * #.angleBetween - * @comp Crafty.math.Vector2D - * - * Calculates the angle between the passed vector and this vector, using <0,0> as the point of reference. - * Angles returned have the range (−π, π]. + * #.isInvertible + * @comp Crafty.math.Matrix2D + * @kind Method + * + * Determines is this matrix is invertible. * * @public - * @sign public {Number} angleBetween(Vector2D); - * @param {Vector2D} vecRH - * @returns {Number} the angle between the two vectors in radians + * @sign public {Boolean} isInvertible(); + * @returns {Boolean} true if this matrix is invertible + * @see .invert */ - Vector2D.prototype.angleBetween = function (vecRH) { - return Math.atan2(this.x * vecRH.y - this.y * vecRH.x, this.x * vecRH.x + this.y * vecRH.y); - }; // angleBetween + Matrix2D.prototype.isInvertible = function () { + return this.determinant() !== 0; + }; // isInvertible /**@ - * #.angleTo - * @comp Crafty.math.Vector2D + * #.preRotate + * @comp Crafty.math.Matrix2D + * @kind Method + * + * Applies a counter-clockwise pre-rotation to this matrix * - * Calculates the angle to the passed vector from this vector, using this vector as the point of reference. + * @public + * @sign public {Matrix2D} preRotate(Number); + * @param {number} rads - angle to rotate in radians + * @returns {Matrix2D} this matrix after pre-rotation + */ + Matrix2D.prototype.preRotate = function (rads) { + var nCos = Math.cos(rads); + var nSin = Math.sin(rads); + + var tmp = this.a; + this.a = nCos * tmp - nSin * this.b; + this.b = nSin * tmp + nCos * this.b; + tmp = this.c; + this.c = nCos * tmp - nSin * this.d; + this.d = nSin * tmp + nCos * this.d; + + return this; + }; // preRotate + + /**@ + * #.preScale + * @comp Crafty.math.Matrix2D + * @kind Method + * + * Applies a pre-scaling to this matrix * * @public - * @sign public {Number} angleTo(Vector2D); - * @param {Vector2D} vecRH - * @returns {Number} the angle to the passed vector in radians + * @sign public {Matrix2D} preScale(Number[, Number]); + * @param {Number} scalarX + * @param {Number} [scalarY] scalarX is used if scalarY is undefined + * @returns {Matrix2D} this after pre-scaling */ - Vector2D.prototype.angleTo = function (vecRH) { - return Math.atan2(vecRH.y - this.y, vecRH.x - this.x); - }; + Matrix2D.prototype.preScale = function (scalarX, scalarY) { + if (scalarY === undefined) + scalarY = scalarX; + + this.a *= scalarX; + this.b *= scalarY; + this.c *= scalarX; + this.d *= scalarY; + + return this; + }; // preScale /**@ - * #.clone - * @comp Crafty.math.Vector2D - * - * Creates and exact, numeric copy of this vector + * #.preTranslate + * @comp Crafty.math.Matrix2D + * @kind Method + * + * Applies a pre-translation to this matrix * * @public - * @sign public {Vector2D} clone(); - * @returns {Vector2D} the new vector + * @sign public {Matrix2D} preTranslate(Vector2D); + * @sign public {Matrix2D} preTranslate(Number, Number); + * @param {Number|Vector2D} dx + * @param {Number} dy + * @returns {Matrix2D} this matrix after pre-translation */ - Vector2D.prototype.clone = function () { - return new Vector2D(this); - }; // clone + Matrix2D.prototype.preTranslate = function (dx, dy) { + if (typeof dx === "number") { + this.e += dx; + this.f += dy; + } else { + this.e += dx.x; + this.f += dx.y; + } // else - /**@ - * #.distance - * @comp Crafty.math.Vector2D - * - * Calculates the distance from this vector to the passed vector. - * - * @public - * @sign public {Number} distance(Vector2D); - * @param {Vector2D} vecRH - * @returns {Number} the distance between the two vectors - */ - Vector2D.prototype.distance = function (vecRH) { - return Math.sqrt((vecRH.x - this.x) * (vecRH.x - this.x) + (vecRH.y - this.y) * (vecRH.y - this.y)); - }; // distance + return this; + }; // preTranslate /**@ - * #.distanceSq - * @comp Crafty.math.Vector2D - * - * Calculates the squared distance from this vector to the passed vector. - * This function avoids calculating the square root, thus being slightly faster than .distance( ). + * #.rotate + * @comp Crafty.math.Matrix2D + * @kind Method + * + * Applies a counter-clockwise post-rotation to this matrix * * @public - * @sign public {Number} distanceSq(Vector2D); - * @param {Vector2D} vecRH - * @returns {Number} the squared distance between the two vectors - * @see .distance + * @sign public {Matrix2D} rotate(Number); + * @param {Number} rads - angle to rotate in radians + * @returns {Matrix2D} this matrix after rotation */ - Vector2D.prototype.distanceSq = function (vecRH) { - return (vecRH.x - this.x) * (vecRH.x - this.x) + (vecRH.y - this.y) * (vecRH.y - this.y); - }; // distanceSq + Matrix2D.prototype.rotate = function (rads) { + var nCos = Math.cos(rads); + var nSin = Math.sin(rads); + + var tmp = this.a; + this.a = nCos * tmp - nSin * this.b; + this.b = nSin * tmp + nCos * this.b; + tmp = this.c; + this.c = nCos * tmp - nSin * this.d; + this.d = nSin * tmp + nCos * this.d; + tmp = this.e; + this.e = nCos * tmp - nSin * this.f; + this.f = nSin * tmp + nCos * this.f; + + return this; + }; // rotate /**@ - * #.divide - * @comp Crafty.math.Vector2D - * - * Divides this vector by the passed vector. + * #.scale + * @comp Crafty.math.Matrix2D + * @kind Method + * + * Applies a post-scaling to this matrix * * @public - * @sign public {Vector2D} divide(Vector2D); - * @param {Vector2D} vecRH - * @returns {Vector2D} this vector after dividing + * @sign public {Matrix2D} scale(Number[, Number]); + * @param {Number} scalarX + * @param {Number} [scalarY] scalarX is used if scalarY is undefined + * @returns {Matrix2D} this after post-scaling */ - Vector2D.prototype.divide = function (vecRH) { - this.x /= vecRH.x; - this.y /= vecRH.y; + Matrix2D.prototype.scale = function (scalarX, scalarY) { + if (scalarY === undefined) + scalarY = scalarX; + + this.a *= scalarX; + this.b *= scalarY; + this.c *= scalarX; + this.d *= scalarY; + this.e *= scalarX; + this.f *= scalarY; + return this; - }; // divide + }; // scale /**@ - * #.dotProduct - * @comp Crafty.math.Vector2D - * - * Calculates the dot product of this and the passed vectors + * #.setValues + * @comp Crafty.math.Matrix2D + * @kind Method + * + * Sets the values of this matrix * * @public - * @sign public {Number} dotProduct(Vector2D); - * @param {Vector2D} vecRH - * @returns {Number} the resultant dot product + * @sign public {Matrix2D} setValues(Matrix2D); + * @sign public {Matrix2D} setValues(Number, Number, Number, Number, Number, Number); + * @param {Matrix2D|Number} a + * @param {Number} b + * @param {Number} c + * @param {Number} d + * @param {Number} e + * @param {Number} f + * @returns {Matrix2D} this matrix containing the new values */ - Vector2D.prototype.dotProduct = function (vecRH) { - return this.x * vecRH.x + this.y * vecRH.y; - }; // dotProduct + Matrix2D.prototype.setValues = function (a, b, c, d, e, f) { + if (a instanceof Matrix2D) { + this.a = a.a; + this.b = a.b; + this.c = a.c; + this.d = a.d; + this.e = a.e; + this.f = a.f; + } else { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.e = e; + this.f = f; + } // else + + return this; + }; // setValues /**@ - * #.crossProduct - * @comp Crafty.math.Vector2D - * - * Calculates the z component of the cross product of the two vectors augmented to 3D. + * #.toString + * @comp Crafty.math.Matrix2D + * @kind Method + * + * Returns the string representation of this matrix. * * @public - * @sign public {Number} crossProduct(Vector2D); - * @param {Vector2D} vecRH - * @returns {Number} the resultant cross product + * @sign public {String} toString(); + * @returns {String} */ - Vector2D.prototype.crossProduct = function (vecRH) { - return this.x * vecRH.y - this.y * vecRH.x; - }; // crossProduct + Matrix2D.prototype.toString = function () { + return "Matrix2D([" + this.a + ", " + this.c + ", " + this.e + + "] [" + this.b + ", " + this.d + ", " + this.f + "] [0, 0, 1])"; + }; // toString /**@ - * #.equals - * @comp Crafty.math.Vector2D - * - * Determines if this vector is numerically equivalent to the passed vector. + * #.translate + * @comp Crafty.math.Matrix2D + * @kind Method + * + * Applies a post-translation to this matrix * * @public - * @sign public {Boolean} equals(Vector2D); - * @param {Vector2D} vecRH - * @returns {Boolean} true if the vectors are equivalent + * @sign public {Matrix2D} translate(Vector2D); + * @sign public {Matrix2D} translate(Number, Number); + * @param {Number|Vector2D} dx + * @param {Number} dy + * @returns {Matrix2D} this matrix after post-translation */ - Vector2D.prototype.equals = function (vecRH) { - return vecRH instanceof Vector2D && - this.x == vecRH.x && this.y == vecRH.y; - }; // equals + Matrix2D.prototype.translate = function (dx, dy) { + if (typeof dx === "number") { + this.e += this.a * dx + this.c * dy; + this.f += this.b * dx + this.d * dy; + } else { + this.e += this.a * dx.x + this.c * dx.y; + this.f += this.b * dx.x + this.d * dx.y; + } // else + + return this; + }; // translate + + return Matrix2D; +})(); +},{"../core/core.js":8}],46:[function(require,module,exports){ +var Crafty = require('../core/core.js'); + + + +// This is used to define getters and setters for Motion properties +// For instance +// __motionProp(entity, "a", "x", true) +// will define a getter for `ax` which accesses an underlying private property `_ax` +// If the `setter` property is false, setting a value will be a null-op +var __motionProp = function(self, prefix, prop, setter) { + var publicProp = prefix + prop; + var privateProp = "_" + publicProp; + + var motionEvent = { key: "", oldValue: 0}; + // getters & setters for public property + if (setter) { + Crafty.defineField(self, publicProp, function() { return this[privateProp]; }, function(newValue) { + var oldValue = this[privateProp]; + if (newValue !== oldValue) { + this[privateProp] = newValue; + + motionEvent.key = publicProp; + motionEvent.oldValue = oldValue; + this.trigger("MotionChange", motionEvent); + } + }); + } else { + Crafty.defineField(self, publicProp, function() { return this[privateProp]; }, function(newValue) {}); + } + + // hide private property + Object.defineProperty(self, privateProp, { + value : 0, + writable : true, + enumerable : false, + configurable : false + }); +}; + +// This defines an alias for a pair of underlying properties which represent the components of a vector +// It takes an object with vector methods, and redefines its x/y properties as getters and setters to properties of self +// This allows you to use the vector's special methods to manipulate the entity's properties, +// while still allowing you to manipulate those properties directly if performance matters +var __motionVector = function(self, prefix, setter, vector) { + var publicX = prefix + "x", + publicY = prefix + "y", + privateX = "_" + publicX, + privateY = "_" + publicY; + + if (setter) { + Crafty.defineField(vector, "x", function() { return self[privateX]; }, function(v) { self[publicX] = v; }); + Crafty.defineField(vector, "y", function() { return self[privateY]; }, function(v) { self[publicY] = v; }); + } else { + Crafty.defineField(vector, "x", function() { return self[privateX]; }, function(v) {}); + Crafty.defineField(vector, "y", function() { return self[privateY]; }, function(v) {}); + } + if (Object.seal) { Object.seal(vector); } - /**@ - * #.perpendicular - * @comp Crafty.math.Vector2D - * - * Calculates a new vector that is perpendicular to this vector. - * The perpendicular vector has the same magnitude as this vector and is obtained by a counter-clockwise rotation of 90° of this vector. - * - * @public - * @sign public {Vector2D} perpendicular([Vector2D]); - * @param {Vector2D} [result] - An optional parameter to save the result in - * @returns {Vector2D} the perpendicular vector - */ - Vector2D.prototype.perpendicular = function (result) { - result = result || new Vector2D(); - return result.setValues(-this.y, this.x); - }; // perpendicular + return vector; +}; +/**@ + * #AngularMotion + * @category 2D + * @kind Component + * + * @trigger Rotated - When entity has rotated due to angular velocity/acceleration a Rotated event is triggered. - Number - Old rotation + * @trigger NewRotationDirection - When entity has changed rotational direction due to rotational velocity a NewRotationDirection event is triggered. The event is triggered once, if direction is different from last frame. - -1 | 0 | 1 - New direction + * @trigger MotionChange - When a motion property has changed a MotionChange event is triggered. - { key: String, oldValue: Number } - Motion property name and old value + * + * Component that allows rotating an entity by applying angular velocity and acceleration. + * All angular motion values are expressed in degrees per second (e.g. an entity with `vrotation` of 10 will rotate 10 degrees each second). + */ +Crafty.c("AngularMotion", { /**@ - * #.getNormal - * @comp Crafty.math.Vector2D + * #.vrotation + * @comp AngularMotion + * @kind Property + * + * A property for accessing/modifying the angular(rotational) velocity. + * The velocity remains constant over time, unless the acceleration increases the velocity. * - * Calculates a new right-handed unit vector that is perpendicular to the line created by this and the passed vector. + * @example + * ~~~ + * var ent = Crafty.e("2D, AngularMotion"); * - * @public - * @sign public {Vector2D} getNormal(Vector2D[, Vector2D]); - * @param {Vector2D} vecRH - * @param {Vector2D} [result] - An optional parameter to save the result in - * @returns {Vector2D} the new normal vector + * var vrotation = ent.vrotation; // retrieve the angular velocity + * ent.vrotation += 1; // increase the angular velocity + * ent.vrotation = 0; // reset the angular velocity + * ~~~ */ - Vector2D.prototype.getNormal = function (vecRH, result) { - result = result || new Vector2D(); - return result.setValues(vecRH.y - this.y, this.x - vecRH.x).normalize(); - }; // getNormal + _vrotation: 0, /**@ - * #.isZero - * @comp Crafty.math.Vector2D + * #.arotation + * @comp AngularMotion + * @kind Property + * + * A property for accessing/modifying the angular(rotational) acceleration. + * The acceleration increases the velocity over time, resulting in ever increasing speed. * - * Determines if this vector is equal to <0,0> + * @example + * ~~~ + * var ent = Crafty.e("2D, AngularMotion"); * - * @public - * @sign public {Boolean} isZero(); - * @returns {Boolean} true if this vector is equal to <0,0> + * var arotation = ent.arotation; // retrieve the angular acceleration + * ent.arotation += 1; // increase the angular acceleration + * ent.arotation = 0; // reset the angular acceleration + * ~~~ */ - Vector2D.prototype.isZero = function () { - return this.x === 0 && this.y === 0; - }; // isZero + _arotation: 0, /**@ - * #.magnitude - * @comp Crafty.math.Vector2D + * #.drotation + * @comp AngularMotion + * @kind Property + * + * A number that reflects the change in rotation (difference between the old & new rotation) that was applied in the last frame. * - * Calculates the magnitude of this vector. - * Note: Function objects in JavaScript already have a 'length' member, hence the use of magnitude instead. + * @example + * ~~~ + * var ent = Crafty.e("2D, AngularMotion"); * - * @public - * @sign public {Number} magnitude(); - * @returns {Number} the magnitude of this vector + * var drotation = ent.drotation; // the change of rotation in the last frame + * ~~~ */ - Vector2D.prototype.magnitude = function () { - return Math.sqrt(this.x * this.x + this.y * this.y); - }; // magnitude + _drotation: 0, - /**@ - * #.magnitudeSq - * @comp Crafty.math.Vector2D - * - * Calculates the square of the magnitude of this vector. - * This function avoids calculating the square root, thus being slightly faster than .magnitude( ). - * - * @public - * @sign public {Number} magnitudeSq(); - * @returns {Number} the square of the magnitude of this vector - * @see .magnitude - */ - Vector2D.prototype.magnitudeSq = function () { - return this.x * this.x + this.y * this.y; - }; // magnitudeSq + init: function () { + this.requires("2D"); - /**@ - * #.multiply - * @comp Crafty.math.Vector2D - * - * Multiplies this vector by the passed vector - * - * @public - * @sign public {Vector2D} multiply(Vector2D); - * @param {Vector2D} vecRH - * @returns {Vector2D} this vector after multiplying - */ - Vector2D.prototype.multiply = function (vecRH) { - this.x *= vecRH.x; - this.y *= vecRH.y; - return this; - }; // multiply + __motionProp(this, "v", "rotation", true); + __motionProp(this, "a", "rotation", true); + __motionProp(this, "d", "rotation", false); - /**@ - * #.negate - * @comp Crafty.math.Vector2D - * - * Negates this vector (ie. <-x,-y>) - * - * @public - * @sign public {Vector2D} negate(); - * @returns {Vector2D} this vector after negation - */ - Vector2D.prototype.negate = function () { - this.x = -this.x; - this.y = -this.y; - return this; - }; // negate + this.__oldRotationDirection = 0; + + this.bind("EnterFrame", this._angularMotionTick); + }, + remove: function(destroyed) { + this.unbind("EnterFrame", this._angularMotionTick); + }, /**@ - * #.normalize - * @comp Crafty.math.Vector2D - * - * Normalizes this vector (scales the vector so that its new magnitude is 1) - * For vectors where magnitude is 0, <1,0> is returned. - * - * @public - * @sign public {Vector2D} normalize(); - * @returns {Vector2D} this vector after normalization + * #.resetAngularMotion + * @comp AngularMotion + * @kind Method + * + * @sign public this .resetAngularMotion() + * + * Reset all motion (resets velocity, acceleration, motionDelta). */ - Vector2D.prototype.normalize = function () { - var lng = Math.sqrt(this.x * this.x + this.y * this.y); - - if (lng === 0) { - // default due East - this.x = 1; - this.y = 0; - } else { - this.x /= lng; - this.y /= lng; - } // else + resetAngularMotion: function() { + this._drotation = 0; + this.vrotation = 0; + this.arotation = 0; return this; - }; // normalize + }, - /**@ - * #.scale - * @comp Crafty.math.Vector2D - * - * Scales this vector by the passed amount(s) - * If scalarY is omitted, scalarX is used for both axes - * - * @public - * @sign public {Vector2D} scale(Number[, Number]); - * @param {Number} scalarX - * @param {Number} [scalarY] - * @returns {Vector2D} this after scaling + /* + * s += v * Δt + (0.5 * a) * Δt * Δt + * v += a * Δt */ - Vector2D.prototype.scale = function (scalarX, scalarY) { - if (scalarY === undefined) - scalarY = scalarX; + _angularMotionTick: function(frameData) { + var dt = frameData.dt / 1000; // Time in s + var oldR = this._rotation, + vr = this._vrotation, + ar = this._arotation; - this.x *= scalarX; - this.y *= scalarY; + // s += v * Δt + (0.5 * a) * Δt * Δt + var newR = oldR + vr * dt + 0.5 * ar * dt * dt; + // v += a * Δt + this.vrotation = vr + ar * dt; - return this; - }; // scale + // Check if direction of velocity has changed + var _vr = this._vrotation, dvr = _vr ? (_vr<0 ? -1:1):0; // Quick implementation of Math.sign + if (this.__oldRotationDirection !== dvr) { + this.__oldRotationDirection = dvr; + this.trigger('NewRotationDirection', dvr); + } - /**@ - * #.scaleToMagnitude - * @comp Crafty.math.Vector2D + // Check if velocity has changed + // Δs = s[t] - s[t-1] + this._drotation = newR - oldR; + if (this._drotation !== 0) { + this.rotation = newR; + this.trigger('Rotated', oldR); + } + } +}); + +/**@ + * #Motion + * @category 2D + * @kind Component + * + * @trigger Moved - When entity has moved due to velocity/acceleration on either x or y axis a Moved event is triggered. If the entity has moved on both axes for diagonal movement the event is triggered twice. - { axis: 'x' | 'y', oldValue: Number } - Old position + * @trigger NewDirection - When entity has changed direction due to velocity on either x or y axis a NewDirection event is triggered. The event is triggered once, if direction is different from last frame. - { x: -1 | 0 | 1, y: -1 | 0 | 1 } - New direction + * @trigger MotionChange - When a motion property has changed a MotionChange event is triggered. - { key: String, oldValue: Number } - Motion property name and old value + * + * Component that allows moving an entity by applying linear velocity and acceleration. + * All linear motion values are expressed in pixels per second (e.g. an entity with `vx` of 1 will move 1px on the x axis each second). + * + * @note Several methods return Vector2D objects that dynamically reflect the entity's underlying properties. If you want a static copy instead, use the vector's `clone()` method. + */ +Crafty.c("Motion", { + /**@ + * #.vx + * @comp Motion + * @kind Property + * + * A property for accessing/modifying the linear velocity in the x axis. + * The velocity remains constant over time, unless the acceleration changes the velocity. * - * Scales this vector such that its new magnitude is equal to the passed value. + * @example + * ~~~ + * var ent = Crafty.e("2D, Motion"); * - * @public - * @sign public {Vector2D} scaleToMagnitude(Number); - * @param {Number} mag - * @returns {Vector2D} this vector after scaling + * var vx = ent.vx; // retrieve the linear velocity in the x axis + * ent.vx += 1; // increase the linear velocity in the x axis + * ent.vx = 0; // reset the linear velocity in the x axis + * ~~~ */ - Vector2D.prototype.scaleToMagnitude = function (mag) { - var k = mag / this.magnitude(); - this.x *= k; - this.y *= k; - return this; - }; // scaleToMagnitude + _vx: 0, /**@ - * #.setValues - * @comp Crafty.math.Vector2D + * #.vy + * @comp Motion + * @kind Property + * + * A property for accessing/modifying the linear velocity in the y axis. + * The velocity remains constant over time, unless the acceleration changes the velocity. * - * Sets the values of this vector using a passed vector or pair of numbers. + * @example + * ~~~ + * var ent = Crafty.e("2D, Motion"); * - * @public - * @sign public {Vector2D} setValues(Vector2D); - * @sign public {Vector2D} setValues(Number, Number); - * @param {Number|Vector2D} x - * @param {Number} y - * @returns {Vector2D} this vector after setting of values + * var vy = ent.vy; // retrieve the linear velocity in the y axis + * ent.vy += 1; // increase the linear velocity in the y axis + * ent.vy = 0; // reset the linear velocity in the y axis + * ~~~ */ - Vector2D.prototype.setValues = function (x, y) { - if (x instanceof Vector2D) { - this.x = x.x; - this.y = x.y; - } else { - this.x = x; - this.y = y; - } // else - - return this; - }; // setValues + _vy: 0, /**@ - * #.subtract - * @comp Crafty.math.Vector2D + * #.ax + * @comp Motion + * @kind Property + * + * A property for accessing/modifying the linear acceleration in the x axis. + * The acceleration changes the velocity over time. * - * Subtracts the passed vector from this vector. + * @example + * ~~~ + * var ent = Crafty.e("2D, Motion"); * - * @public - * @sign public {Vector2D} subtract(Vector2D); - * @param {Vector2D} vecRH - * @returns {vector2D} this vector after subtracting + * var ax = ent.ax; // retrieve the linear acceleration in the x axis + * ent.ax += 1; // increase the linear acceleration in the x axis + * ent.ax = 0; // reset the linear acceleration in the x axis + * ~~~ */ - Vector2D.prototype.subtract = function (vecRH) { - this.x -= vecRH.x; - this.y -= vecRH.y; - return this; - }; // subtract + _ax: 0, /**@ - * #.toString - * @comp Crafty.math.Vector2D + * #.ay + * @comp Motion + * @kind Property + * + * A property for accessing/modifying the linear acceleration in the y axis. + * The acceleration changes the velocity over time. * - * Returns a string representation of this vector. + * @example + * ~~~ + * var ent = Crafty.e("2D, Motion"); * - * @public - * @sign public {String} toString(); - * @returns {String} + * var ay = ent.ay; // retrieve the linear acceleration in the y axis + * ent.ay += 1; // increase the linear acceleration in the y axis + * ent.ay = 0; // reset the linear acceleration in the y axis + * ~~~ */ - Vector2D.prototype.toString = function () { - return "Vector2D(" + this.x + ", " + this.y + ")"; - }; // toString + _ay: 0, /**@ - * #.translate - * @comp Crafty.math.Vector2D + * #.dx + * @comp Motion + * @kind Property + * + * A number that reflects the change in x (difference between the old & new x) that was applied in the last frame. * - * Translates (moves) this vector by the passed amounts. - * If dy is omitted, dx is used for both axes. + * @example + * ~~~ + * var ent = Crafty.e("2D, Motion"); * - * @public - * @sign public {Vector2D} translate(Number[, Number]); - * @param {Number} dx - * @param {Number} [dy] - * @returns {Vector2D} this vector after translating + * var dx = ent.dx; // the change of x in the last frame + * ~~~ */ - Vector2D.prototype.translate = function (dx, dy) { - if (dy === undefined) - dy = dx; - - this.x += dx; - this.y += dy; - - return this; - }; // translate + _dx: 0, /**@ - * #.tripleProduct - * @comp Crafty.math.Vector2D + * #.dy + * @comp Motion + * @kind Property + * + * A number that reflects the change in y (difference between the old & new y) that was applied in the last frame. * - * Calculates the triple product of three vectors. - * triple vector product = b(a•c) - a(b•c) + * @example + * ~~~ + * var ent = Crafty.e("2D, Motion"); * - * @public - * @static - * @sign public {Vector2D} tripleProduct(Vector2D, Vector2D, Vector2D, [Vector2D]); - * @param {Vector2D} a - * @param {Vector2D} b - * @param {Vector2D} c - * @param {Vector2D} [result] - An optional parameter to save the result in - * @return {Vector2D} the triple product as a new vector + * var dy = ent.dy; // the change of y in the last frame + * ~~~ */ - Vector2D.tripleProduct = function (a, b, c, result) { - result = result || new Crafty.math.Vector2D(); - var ac = a.dotProduct(c); - var bc = b.dotProduct(c); - return result.setValues(b.x * ac - a.x * bc, b.y * ac - a.y * bc); - }; + _dy: 0, - return Vector2D; -})(); + init: function () { + this.requires("2D"); -Crafty.math.Matrix2D = (function () { - /**@ - * #Crafty.math.Matrix2D - * @category 2D - * - * @class This is a 2D Matrix2D class. It is 3x3 to allow for affine transformations in 2D space. - * The third row is always assumed to be [0, 0, 1]. - * - * Matrix2D uses the following form, as per the whatwg.org specifications for canvas.transform(): - * [a, c, e] - * [b, d, f] - * [0, 0, 1] - * - * @public - * @sign public {Matrix2D} new Matrix2D(); - * @sign public {Matrix2D} new Matrix2D(Matrix2D); - * @sign public {Matrix2D} new Matrix2D(Number, Number, Number, Number, Number, Number); - * @param {Matrix2D|Number=1} a - * @param {Number=0} b - * @param {Number=0} c - * @param {Number=1} d - * @param {Number=0} e - * @param {Number=0} f - */ - Matrix2D = function (a, b, c, d, e, f) { - if (a instanceof Matrix2D) { - this.a = a.a; - this.b = a.b; - this.c = a.c; - this.d = a.d; - this.e = a.e; - this.f = a.f; - } else if (arguments.length === 6) { - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.e = e; - this.f = f; - } else if (arguments.length > 0) - throw "Unexpected number of arguments for Matrix2D()"; - }; // class Matrix2D + __motionProp(this, "v", "x", true); + __motionProp(this, "v", "y", true); + this._velocity = __motionVector(this, "v", true, new Crafty.math.Vector2D()); + __motionProp(this, "a", "x", true); + __motionProp(this, "a", "y", true); + this._acceleration = __motionVector(this, "a", true, new Crafty.math.Vector2D()); + __motionProp(this, "d", "x", false); + __motionProp(this, "d", "y", false); + this._motionDelta = __motionVector(this, "d", false, new Crafty.math.Vector2D()); - Matrix2D.prototype.a = 1; - Matrix2D.prototype.b = 0; - Matrix2D.prototype.c = 0; - Matrix2D.prototype.d = 1; - Matrix2D.prototype.e = 0; - Matrix2D.prototype.f = 0; + this.__movedEvent = {axis: '', oldValue: 0}; + this.__oldDirection = {x: 0, y: 0}; + + this.bind("EnterFrame", this._linearMotionTick); + }, + remove: function(destroyed) { + this.unbind("EnterFrame", this._linearMotionTick); + }, /**@ - * #.apply - * @comp Crafty.math.Matrix2D - * - * Applies the matrix transformations to the passed object - * - * @public - * @sign public {Vector2D} apply(Vector2D); - * @param {Vector2D} vecRH - vector to be transformed - * @returns {Vector2D} the passed vector object after transforming + * #.resetMotion + * @comp Motion + * @kind Method + * + * @sign public this .resetMotion() + * @return this + * + * Reset all linear motion (resets velocity, acceleration, motionDelta). */ - Matrix2D.prototype.apply = function (vecRH) { - // I'm not sure of the best way for this function to be implemented. Ideally - // support for other objects (rectangles, polygons, etc) should be easily - // addable in the future. Maybe a function (apply) is not the best way to do - // this...? - - var tmpX = vecRH.x; - vecRH.x = tmpX * this.a + vecRH.y * this.c + this.e; - vecRH.y = tmpX * this.b + vecRH.y * this.d + this.f; - // no need to homogenize since the third row is always [0, 0, 1] + resetMotion: function() { + this.vx = 0; this.vy = 0; + this.ax = 0; this.ay = 0; + this._dx = 0; this._dy = 0; - return vecRH; - }; // apply + return this; + }, /**@ - * #.clone - * @comp Crafty.math.Matrix2D + * #.motionDelta + * @comp Motion + * @kind Method + * + * @sign public Vector2D .motionDelta() + * @return A Vector2D with the properties {x, y} that reflect the change in x & y. + * + * Returns the difference between the old & new position that was applied in the last frame. * - * Creates an exact, numeric copy of the current matrix + * @example + * ~~~ + * var ent = Crafty.e("2D, Motion"); * - * @public - * @sign public {Matrix2D} clone(); - * @returns {Matrix2D} + * var deltaY = ent.motionDelta().y; // the change of y in the last frame + * ~~~ + * @see Crafty.math.Vector2D */ - Matrix2D.prototype.clone = function () { - return new Matrix2D(this); - }; // clone + motionDelta: function() { + return this._motionDelta; + }, /**@ - * #.combine - * @comp Crafty.math.Matrix2D + * #.velocity + * @comp Motion + * @kind Method + * + * Method for accessing/modifying the linear(x,y) velocity. + * The velocity remains constant over time, unless the acceleration increases the velocity. * - * Multiplies this matrix with another, overriding the values of this matrix. - * The passed matrix is assumed to be on the right-hand side. + * @sign public Vector2D .velocity() + * @return The velocity Vector2D with the properties {x, y} that reflect the velocities in the direction of the entity. * - * @public - * @sign public {Matrix2D} combine(Matrix2D); - * @param {Matrix2D} mtrxRH - * @returns {Matrix2D} this matrix after combination - */ - Matrix2D.prototype.combine = function (mtrxRH) { - var tmp = this.a; - this.a = tmp * mtrxRH.a + this.b * mtrxRH.c; - this.b = tmp * mtrxRH.b + this.b * mtrxRH.d; - tmp = this.c; - this.c = tmp * mtrxRH.a + this.d * mtrxRH.c; - this.d = tmp * mtrxRH.b + this.d * mtrxRH.d; - tmp = this.e; - this.e = tmp * mtrxRH.a + this.f * mtrxRH.c + mtrxRH.e; - this.f = tmp * mtrxRH.b + this.f * mtrxRH.d + mtrxRH.f; - return this; - }; // combine + * Returns the current velocity. You can access/modify the properties in order to retrieve/change the velocity. - /**@ - * #.equals - * @comp Crafty.math.Matrix2D - * - * Checks for the numeric equality of this matrix versus another. + * @example + * ~~~ + * var ent = Crafty.e("2D, Motion"); * - * @public - * @sign public {Boolean} equals(Matrix2D); - * @param {Matrix2D} mtrxRH - * @returns {Boolean} true if the two matrices are numerically equal + * var vel = ent.velocity(); //returns the velocity vector + * vel.x; // retrieve the velocity in the x direction + * vel.x = 0; // set the velocity in the x direction + * vel.x += 4 // add to the velocity in the x direction + * ~~~ + * @see Crafty.math.Vector2D */ - Matrix2D.prototype.equals = function (mtrxRH) { - return mtrxRH instanceof Matrix2D && - this.a == mtrxRH.a && this.b == mtrxRH.b && this.c == mtrxRH.c && - this.d == mtrxRH.d && this.e == mtrxRH.e && this.f == mtrxRH.f; - }; // equals + velocity: function() { + return this._velocity; + }, + /**@ - * #.determinant - * @comp Crafty.math.Matrix2D + * #.acceleration + * @comp Motion + * @kind Method + * + * Method for accessing/modifying the linear(x,y) acceleration. + * The acceleration increases the velocity over time, resulting in ever increasing speed. + * + * @sign public Vector2D .acceleration() + * @return The acceleration Vector2D with the properties {x, y} that reflects the acceleration in the direction of the entity. * - * Calculates the determinant of this matrix + * Returns the current acceleration. You can access/modify the properties in order to retrieve/change the acceleration. * - * @public - * @sign public {Number} determinant(); - * @returns {Number} det(this matrix) + * @example + * ~~~ + * var ent = Crafty.e("2D, Motion"); + * + * var acc = ent.acceleration(); //returns the acceleration object + * acc.x; // retrieve the acceleration in the x direction + * acc.x = 0; // set the acceleration in the x direction + * acc.x += 4 // add to the acceleration in the x direction + * ~~~ + * @see Crafty.math.Vector2D */ - Matrix2D.prototype.determinant = function () { - return this.a * this.d - this.b * this.c; - }; // determinant + acceleration: function() { + return this._acceleration; + }, /**@ - * #.invert - * @comp Crafty.math.Matrix2D + * #.ccdbr + * @comp Motion + * @kind Method + * + * @sign public Object .ccdbr([Object ccdbr]) + * @param ccdbr - an object to use as output + * @returns an object with `_x`, `_y`, `_w`, and `_h` properties; if an object is passed in, it will be reused rather than creating a new object. * - * Inverts this matrix if possible + * Return an object containing the entity's continuous collision detection bounding rectangle. + * The CCDBR encompasses the motion delta of the entity's bounding rectangle since last frame. + * The CCDBR is minimal if the entity moved on only one axis since last frame, however it encompasses a non-minimal region if it moved on both axis. + * For further details, refer to [FAQ#Tunneling](https://github.com/craftyjs/Crafty/wiki/Crafty-FAQ-%28draft%29#why-are-my-bullets-passing-through-other-entities-without-registering-hits). * - * @public - * @sign public {Matrix2D} invert(); - * @returns {Matrix2D} this inverted matrix or the original matrix on failure - * @see .isInvertible + * @note The keys have an underscore prefix. This is due to the x, y, w, h properties + * being setters and getters that wrap the underlying properties with an underscore (_x, _y, _w, _h). + * + * @see .motionDelta, Collision#.cbr */ - Matrix2D.prototype.invert = function () { - var det = this.determinant(); + ccdbr: function (ccdbr) { + var pos = this._cbr || this._mbr || this, + dx = this._dx, + dy = this._dy, + ccdX = 0, ccdY = 0, + ccdW = dx > 0 ? (ccdX = dx) : -dx, + ccdH = dy > 0 ? (ccdY = dy) : -dy; + + ccdbr = ccdbr || {}; + ccdbr._x = pos._x - ccdX; + ccdbr._y = pos._y - ccdY; + ccdbr._w = pos._w + ccdW; + ccdbr._h = pos._h + ccdH; + + return ccdbr; + }, - // matrix is invertible if its determinant is non-zero - if (det !== 0) { - var old = { - a: this.a, - b: this.b, - c: this.c, - d: this.d, - e: this.e, - f: this.f - }; - this.a = old.d / det; - this.b = -old.b / det; - this.c = -old.c / det; - this.d = old.a / det; - this.e = (old.c * old.f - old.e * old.d) / det; - this.f = (old.e * old.b - old.a * old.f) / det; - } // if + /* + * s += v * Δt + (0.5 * a) * Δt * Δt + * v += a * Δt + */ + _linearMotionTick: function(frameData) { + var dt = frameData.dt / 1000; // time in s + var oldX = this._x, vx = this._vx, ax = this._ax, + oldY = this._y, vy = this._vy, ay = this._ay; - return this; - }; // invert + // s += v * Δt + (0.5 * a) * Δt * Δt + var newX = oldX + vx * dt + 0.5 * ax * dt * dt; + var newY = oldY + vy * dt + 0.5 * ay * dt * dt; + // v += a * Δt + this.vx = vx + ax * dt; + this.vy = vy + ay * dt; + + // Check if direction of velocity has changed + var oldDirection = this.__oldDirection, + _vx = this._vx, dvx = _vx ? (_vx<0 ? -1:1):0, // A quick implementation of Math.sign + _vy = this._vy, dvy = _vy ? (_vy<0 ? -1:1):0; + if (oldDirection.x !== dvx || oldDirection.y !== dvy) { + oldDirection.x = dvx; + oldDirection.y = dvy; + this.trigger('NewDirection', oldDirection); + } + + // Check if velocity has changed + var movedEvent = this.__movedEvent; + // Δs = s[t] - s[t-1] + this._dx = newX - oldX; + this._dy = newY - oldY; + if (this._dx !== 0) { + this.x = newX; + movedEvent.axis = 'x'; + movedEvent.oldValue = oldX; + this.trigger('Moved', movedEvent); + } + if (this._dy !== 0) { + this.y = newY; + movedEvent.axis = 'y'; + movedEvent.oldValue = oldY; + this.trigger('Moved', movedEvent); + } + } +}); + +},{"../core/core.js":8}],47:[function(require,module,exports){ +var Crafty = require('../core/core.js'); +/**@ + * #Supportable + * @category 2D + * @kind Component + * + * @trigger LandedOnGround - When entity has landed. This event is triggered with the object the entity landed on. + * @trigger LiftedOffGround - When entity has lifted off. This event is triggered with the object the entity stood on before lift-off. + * @trigger CheckLanding - When entity is about to land. This event is triggered with the object the entity is about to land on. Third parties can respond to this event and prevent the entity from being able to land. + * + * Component that detects if the entity collides with the ground. This component is automatically added and managed by the Gravity component. + * The appropriate events are fired when the entity state changes (lands on ground / lifts off ground). The current ground entity can also be accessed with `.ground`. + */ +Crafty.c("Supportable", { /**@ - * #.isIdentity - * @comp Crafty.math.Matrix2D - * - * Returns true if this matrix is the identity matrix + * #.ground + * @comp Supportable + * @kind Property * - * @public - * @sign public {Boolean} isIdentity(); - * @returns {Boolean} + * Access the ground entity (which may be the actual ground entity if it exists, or `null` if it doesn't exist) and thus whether this entity is currently on the ground or not. + * The ground entity is also available through the events, when the ground entity changes. */ - Matrix2D.prototype.isIdentity = function () { - return this.a === 1 && this.b === 0 && this.c === 0 && this.d === 1 && this.e === 0 && this.f === 0; - }; // isIdentity + _ground: null, + _groundComp: null, + _preventGroundTunneling: false, /**@ - * #.isInvertible - * @comp Crafty.math.Matrix2D + * #.canLand + * @comp Supportable + * @kind Property + * + * The canLand boolean determines if the entity is allowed to land or not (e.g. perhaps the entity should not land if it's not falling). + * The Supportable component will trigger a "CheckLanding" event. + * Interested parties can listen to this event and prevent the entity from landing by setting `canLand` to false. + * + * @example + * ~~~ + * var player = Crafty.e("2D, Gravity"); + * player.bind("CheckLanding", function(ground) { + * if (player.y + player.h > ground.y + player.dy) { // forbid landing, if player's feet are not above ground + * player.canLand = false; + * } + * }); + * ~~~ + */ + canLand: true, + + init: function () { + this.requires("2D"); + this.__area = {_x: 0, _y: 0, _w: 0, _h: 0}; + this.defineField("ground", function() { return this._ground; }, function(newValue) {}); + }, + remove: function(destroyed) { + this.unbind("EnterFrame", this._detectGroundTick); + }, + + /*@ + * #.startGroundDetection + * @comp Supportable + * @kind Method + * + * @sign private this .startGroundDetection([comp]) + * @param comp - The name of a component that will be treated as ground * - * Determines is this matrix is invertible. + * This method is automatically called by the Gravity component and should not be called by the user. * - * @public - * @sign public {Boolean} isInvertible(); - * @returns {Boolean} true if this matrix is invertible - * @see .invert + * Enable ground detection for this entity no matter whether comp parameter is specified or not. + * If comp parameter is specified all entities with that component will stop this entity from falling. + * For a player entity in a platform game this would be a component that is added to all entities + * that the player should be able to walk on. + * + * @example + * ~~~ + * Crafty.e("2D, DOM, Color, Gravity") + * .color("red") + * .attr({ w: 100, h: 100 }) + * .gravity("platform"); + * ~~~ + * + * @see Gravity */ - Matrix2D.prototype.isInvertible = function () { - return this.determinant() !== 0; - }; // isInvertible + startGroundDetection: function(ground) { + if (ground) this._groundComp = ground; + this.uniqueBind("EnterFrame", this._detectGroundTick); - /**@ - * #.preRotate - * @comp Crafty.math.Matrix2D + return this; + }, + /*@ + * #.stopGroundDetection + * @comp Supportable + * @kind Method + * + * @sign private this .stopGroundDetection() * - * Applies a counter-clockwise pre-rotation to this matrix + * This method is automatically called by the Gravity component and should not be called by the user. * - * @public - * @sign public {Matrix2D} preRotate(Number); - * @param {number} rads - angle to rotate in radians - * @returns {Matrix2D} this matrix after pre-rotation + * Disable ground detection for this component. It can be reenabled by calling .startGroundDetection() */ - Matrix2D.prototype.preRotate = function (rads) { - var nCos = Math.cos(rads); - var nSin = Math.sin(rads); - - var tmp = this.a; - this.a = nCos * tmp - nSin * this.b; - this.b = nSin * tmp + nCos * this.b; - tmp = this.c; - this.c = nCos * tmp - nSin * this.d; - this.d = nSin * tmp + nCos * this.d; + stopGroundDetection: function() { + this.unbind("EnterFrame", this._detectGroundTick); return this; - }; // preRotate + }, /**@ - * #.preScale - * @comp Crafty.math.Matrix2D + * #.preventGroundTunneling + * @comp Supportable + * @kind Method + * + * @sign this .preventGroundTunneling([Boolean enable]) + * @param enable - Boolean indicating whether to enable continous collision detection or not; if omitted defaults to true * - * Applies a pre-scaling to this matrix + * Prevent entity from falling through thin ground entities at high speeds. This setting is disabled by default. + * This is performed by approximating continous collision detection, which may impact performance negatively. + * For further details, refer to [FAQ#Tunneling](https://github.com/craftyjs/Crafty/wiki/Crafty-FAQ-%28draft%29#why-are-my-bullets-passing-through-other-entities-without-registering-hits). * - * @public - * @sign public {Matrix2D} preScale(Number[, Number]); - * @param {Number} scalarX - * @param {Number} [scalarY] scalarX is used if scalarY is undefined - * @returns {Matrix2D} this after pre-scaling + * @see Motion#.ccdbr */ - Matrix2D.prototype.preScale = function (scalarX, scalarY) { - if (scalarY === undefined) - scalarY = scalarX; - - this.a *= scalarX; - this.b *= scalarY; - this.c *= scalarX; - this.d *= scalarY; + preventGroundTunneling: function(enable) { + if (typeof enable === 'undefined') + enable = true; + if (enable) + this.requires("Motion"); + this._preventGroundTunneling = enable; return this; - }; // preScale + }, - /**@ - * #.preTranslate - * @comp Crafty.math.Matrix2D - * - * Applies a pre-translation to this matrix - * - * @public - * @sign public {Matrix2D} preTranslate(Vector2D); - * @sign public {Matrix2D} preTranslate(Number, Number); - * @param {Number|Vector2D} dx - * @param {Number} dy - * @returns {Matrix2D} this matrix after pre-translation - */ - Matrix2D.prototype.preTranslate = function (dx, dy) { - if (typeof dx === "number") { - this.e += dx; - this.f += dy; + _detectGroundTick: function() { + var groundComp = this._groundComp, + ground = this._ground, + overlap = Crafty.rectManager.overlap, + area; + + if (!this._preventGroundTunneling) { + var pos = this._cbr || this._mbr || this; + area = this.__area; + area._x = pos._x; + area._y = pos._y; + area._w = pos._w; + area._h = pos._h; } else { - this.e += dx.x; - this.f += dx.y; - } // else + area = this.ccdbr(this.__area); + } + area._h++; // Increase by 1 to make sure map.search() finds the floor + // Decrease width by 1px from left and 1px from right, to fall more gracefully + // area._x++; area._w--; - return this; - }; // preTranslate - /**@ - * #.rotate - * @comp Crafty.math.Matrix2D - * - * Applies a counter-clockwise post-rotation to this matrix - * - * @public - * @sign public {Matrix2D} rotate(Number); - * @param {Number} rads - angle to rotate in radians - * @returns {Matrix2D} this matrix after rotation - */ - Matrix2D.prototype.rotate = function (rads) { - var nCos = Math.cos(rads); - var nSin = Math.sin(rads); + // check if we lift-off + if (ground) { + var garea = ground._cbr || ground._mbr || ground; + if (!(ground.__c[groundComp] && Crafty(ground[0]) === ground && overlap(garea, area))) { + this._ground = null; + this.trigger("LiftedOffGround", ground); // no collision with ground was detected for first time + ground = null; + } + } - var tmp = this.a; - this.a = nCos * tmp - nSin * this.b; - this.b = nSin * tmp + nCos * this.b; - tmp = this.c; - this.c = nCos * tmp - nSin * this.d; - this.d = nSin * tmp + nCos * this.d; - tmp = this.e; - this.e = nCos * tmp - nSin * this.f; - this.f = nSin * tmp + nCos * this.f; + // check if we land (also possible to land on other ground object in same frame after lift-off from current ground object) + if (!ground) { + var obj, oarea, + results = Crafty.map.search(area, false), + i = 0, + l = results.length; - return this; - }; // rotate + for (; i < l; ++i) { + obj = results[i]; + oarea = obj._cbr || obj._mbr || obj; + // check for an intersection with the player + if (obj !== this && obj.__c[groundComp] && overlap(oarea, area)) { + this.canLand = true; + this.trigger("CheckLanding", obj); // is entity allowed to land? + if (this.canLand) { + this._ground = ground = obj; - /**@ - * #.scale - * @comp Crafty.math.Matrix2D - * - * Applies a post-scaling to this matrix - * - * @public - * @sign public {Matrix2D} scale(Number[, Number]); - * @param {Number} scalarX - * @param {Number} [scalarY] scalarX is used if scalarY is undefined - * @returns {Matrix2D} this after post-scaling - */ - Matrix2D.prototype.scale = function (scalarX, scalarY) { - if (scalarY === undefined) - scalarY = scalarX; + // snap entity to ground object + this.y = ground._y - this._h; + if (this._x > ground._x + ground._w) + this.x = ground._x + ground._w - 1; + else if (this._x + this._w < ground._x) + this.x = ground._x - this._w + 1; - this.a *= scalarX; - this.b *= scalarY; - this.c *= scalarX; - this.d *= scalarY; - this.e *= scalarX; - this.f *= scalarY; + this.trigger("LandedOnGround", ground); // collision with ground was detected for first time + break; + } + } + } + } + } +}); - return this; - }; // scale +/**@ + * #GroundAttacher + * @category 2D + * @kind Component + * + * Attach the entity to the ground when it lands. Useful for platformers with moving platforms. + * Remove the component to disable the functionality. + * + * Additionally, this component provides the entity with `Supportable` methods & events. + * + * @example + * ~~~ + * Crafty.e("2D, Gravity, GroundAttacher") + * .gravity("Platform"); // entity will land on and move with entites that have the "Platform" component + * ~~~ + * + * @see Supportable, Gravity + */ +Crafty.c("GroundAttacher", { + _groundAttach: function(ground) { + ground.attach(this); + }, + _groundDetach: function(ground) { + ground.detach(this); + }, + + init: function () { + this.requires("Supportable"); + + this.bind("LandedOnGround", this._groundAttach); + this.bind("LiftedOffGround", this._groundDetach); + }, + remove: function(destroyed) { + this.unbind("LandedOnGround", this._groundAttach); + this.unbind("LiftedOffGround", this._groundDetach); + } +}); + + +/**@ + * #Gravity + * @category 2D + * @kind Component + * + * Adds gravitational pull to the entity. + * + * Additionally, this component provides the entity with `Supportable` and `Motion` methods & events. + * + * @see Supportable, Motion + */ +Crafty.c("Gravity", { + _gravityConst: 500, + _gravityActive: false, + + init: function () { + this.requires("2D, Supportable, Motion"); + + this.bind("LiftedOffGround", this._startGravity); // start gravity if we are off ground + this.bind("LandedOnGround", this._stopGravity); // stop gravity once landed + }, + remove: function(destroyed) { + this.unbind("LiftedOffGround", this._startGravity); + this.unbind("LandedOnGround", this._stopGravity); + }, + + _gravityCheckLanding: function(ground) { + if (this._dy < 0) + this.canLand = false; + }, /**@ - * #.setValues - * @comp Crafty.math.Matrix2D + * #.gravity + * @comp Gravity + * @kind Method + * + * @sign public this .gravity([comp]) + * @param comp - The name of a component that will stop this entity from falling * - * Sets the values of this matrix + * Enable gravity for this entity no matter whether comp parameter is specified or not. + * If comp parameter is specified all entities with that component will stop this entity from falling. + * For a player entity in a platform game this would be a component that is added to all entities + * that the player should be able to walk on. * - * @public - * @sign public {Matrix2D} setValues(Matrix2D); - * @sign public {Matrix2D} setValues(Number, Number, Number, Number, Number, Number); - * @param {Matrix2D|Number} a - * @param {Number} b - * @param {Number} c - * @param {Number} d - * @param {Number} e - * @param {Number} f - * @returns {Matrix2D} this matrix containing the new values + * @example + * ~~~ + * Crafty.e("2D, DOM, Color, Gravity") + * .color("red") + * .attr({ w: 100, h: 100 }) + * .gravity("platform"); + * ~~~ */ - Matrix2D.prototype.setValues = function (a, b, c, d, e, f) { - if (a instanceof Matrix2D) { - this.a = a.a; - this.b = a.b; - this.c = a.c; - this.d = a.d; - this.e = a.e; - this.f = a.f; - } else { - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.e = e; - this.f = f; - } // else + gravity: function (comp) { + this.uniqueBind("CheckLanding", this._gravityCheckLanding); + this.startGroundDetection(comp); + this._startGravity(); return this; - }; // setValues - + }, /**@ - * #.toString - * @comp Crafty.math.Matrix2D - * - * Returns the string representation of this matrix. - * - * @public - * @sign public {String} toString(); - * @returns {String} + * #.antigravity + * @comp Gravity + * @kind Method + * + * @sign public this .antigravity() + * Disable gravity for this component. It can be reenabled by calling .gravity() */ - Matrix2D.prototype.toString = function () { - return "Matrix2D([" + this.a + ", " + this.c + ", " + this.e + - "] [" + this.b + ", " + this.d + ", " + this.f + "] [0, 0, 1])"; - }; // toString + antigravity: function () { + this._stopGravity(); + this.stopGroundDetection(); + this.unbind("CheckLanding", this._gravityCheckLanding); + + return this; + }, /**@ - * #.translate - * @comp Crafty.math.Matrix2D + * #.gravityConst + * @comp Gravity + * @kind Method + * + * @sign public this .gravityConst(g) + * @param g - gravitational constant in pixels per second squared * - * Applies a post-translation to this matrix + * Set the gravitational constant to g for this entity. The default is 500. The greater g, the stronger the downwards acceleration. * - * @public - * @sign public {Matrix2D} translate(Vector2D); - * @sign public {Matrix2D} translate(Number, Number); - * @param {Number|Vector2D} dx - * @param {Number} dy - * @returns {Matrix2D} this matrix after post-translation + * @example + * ~~~ + * Crafty.e("2D, DOM, Color, Gravity") + * .color("red") + * .attr({ w: 100, h: 100 }) + * .gravityConst(750) + * .gravity("platform"); + * ~~~ */ - Matrix2D.prototype.translate = function (dx, dy) { - if (typeof dx === "number") { - this.e += this.a * dx + this.c * dy; - this.f += this.b * dx + this.d * dy; - } else { - this.e += this.a * dx.x + this.c * dx.y; - this.f += this.b * dx.x + this.d * dx.y; - } // else + gravityConst: function (g) { + if (this._gravityActive) { // gravity active, change acceleration + this.ay -= this._gravityConst; + this.ay += g; + } + this._gravityConst = g; return this; - }; // translate + }, - return Matrix2D; -})(); -},{"../core/core.js":7}],42:[function(require,module,exports){ + _startGravity: function() { + if (this._gravityActive) return; + this._gravityActive = true; + this.ay += this._gravityConst; + }, + _stopGravity: function() { + if (!this._gravityActive) return; + this._gravityActive = false; + this.ay = 0; + this.vy = 0; + } +}); + + +},{"../core/core.js":8}],48:[function(require,module,exports){ var Crafty = require('../core/core.js'); /**@ * #Crafty.rectManager * @category 2D + * @kind CoreObj * * Collection of methods for handling rectangles */ @@ -15468,6 +17976,8 @@ Crafty.extend({ /**@ * #Crafty.rectManager.overlap * @comp Crafty.rectManager + * @kind Method + * * @sign public Boolean Crafty.rectManager.overlap(Object rectA, Object rectA) * @param rectA - An object that must have the `_x, _y, _w, _h` values as properties * @param rectB - An object that must have the `_x, _y, _w, _h` values as properties @@ -15479,10 +17989,36 @@ Crafty.extend({ return (rectA._x < rectB._x + rectB._w && rectA._x + rectA._w > rectB._x && rectA._y < rectB._y + rectB._h && rectA._y + rectA._h > rectB._y); }, + + /**@ + * #Crafty.rectManager.integerBounds + * @comp Crafty.rectManager + * @kind Method + * + * @sign public Boolean Crafty.rectManager.integerBounds(Object rect) + * @param rect - An object that must have the `_x, _y, _w, _h` values as properties + * @return An enclosing rectangle with integer coordinates + * + * Calculate the smallest rectangle with integer coordinates that encloses the specified rectangle, + * modifying the passed object to have those bounds. + */ + integerBounds: function(rect){ + rect._w = rect._x + rect._w; + rect._h = rect._y + rect._h; + rect._x = (rect._x > 0) ? (rect._x|0) : (rect._x|0) - 1; + rect._y = (rect._y > 0) ? (rect._y|0) : (rect._y|0) - 1; + rect._w -= rect._x; + rect._h -= rect._y; + rect._w = (rect._w === (rect._w|0)) ? rect._w : (rect._w|0) + 1; + rect._h = (rect._h === (rect._h|0)) ? rect._h : (rect._h|0) + 1; + return rect; + }, /**@ * #Crafty.rectManager.mergeSet * @comp Crafty.rectManager + * @kind Method + * * @sign public Object Crafty.rectManager.mergeSet(Object set) * @param set - an array of rectangular regions * @@ -15516,6 +18052,8 @@ Crafty.extend({ /**@ * #Crafty.rectManager.boundingRect * @comp Crafty.rectManager + * @kind Method + * * @sign public Crafty.rectManager.boundingRect(set) * @param set - An array of rectangles * @@ -15524,8 +18062,7 @@ Crafty.extend({ */ boundingRect: function (set) { if (!set || !set.length) return; - var newset = [], - i = 1, + var i = 1, l = set.length, current, master = set[0], tmp; @@ -15597,10 +18134,7 @@ Crafty.extend({ }); -},{"../core/core.js":7}],43:[function(require,module,exports){ -var Crafty = require('../core/core.js'); - - +},{"../core/core.js":8}],49:[function(require,module,exports){ /** * Spatial HashMap for broad phase collision * @@ -15610,6 +18144,8 @@ var Crafty = require('../core/core.js'); /**@ * #Crafty.HashMap.constructor * @comp Crafty.HashMap + * @kind Class + * * @sign public void Crafty.HashMap([cellsize]) * @param cellsize - the cell size. If omitted, `cellsize` is 64. * @@ -15621,6 +18157,28 @@ var Crafty = require('../core/core.js'); HashMap = function (cell) { cellsize = cell || 64; this.map = {}; + + this.boundsDirty = false; + this.boundsHash = { + max: { + x: -Infinity, + y: -Infinity + }, + min: { + x: Infinity, + y: Infinity + } + }; + this.boundsCoords = { + max: { + x: -Infinity, + y: -Infinity + }, + min: { + x: Infinity, + y: Infinity + } + }; }, SPACE = " ", @@ -15630,6 +18188,8 @@ var Crafty = require('../core/core.js'); /**@ * #Crafty.map.insert * @comp Crafty.map + * @kind Method + * * @sign public Object Crafty.map.insert(Object obj) * @param obj - An entity to be inserted. * @returns An object representing this object's entry in the HashMap @@ -15660,20 +18220,42 @@ var Crafty = require('../core/core.js'); } } + //mark map boundaries as dirty + this.boundsDirty = true; + return entry; }, /**@ * #Crafty.map.search * @comp Crafty.map + * @kind Method + * * @sign public Object Crafty.map.search(Object rect[, Boolean filter]) * @param rect - the rectangular region to search for entities. - * @param filter - If false, only performs a broad-phase collision check. The default value is true. + * This object must contain the properties `_x`,`_y`,`_w`,`_h`. + * @param filter - If `false`, only performs a broad-phase collision check. The default value is `true`. + * @return an (possibly empty) array of entities that have been found in the given region + * + * Search for entities in the given region, using their broadphase bounding rectangles. * * - If `filter` is `false`, just search for all the entries in the give `rect` region by broad phase collision. Entity may be returned duplicated. * - If `filter` is `true`, filter the above results by checking that they actually overlap `rect`. * - * The easier usage is with `filter == true`. For performance reason, you may use `filter == false`, and filter the result yourself. See examples in drawing.js and collision.js + * The easier usage is with `filter == true`. For performance reason, you may use `filter == false`, and filter the result yourself. See examples in drawing.js and collision.js. + * + * @example + * ~~~ + * // search for entities located in the current visible region of the viewport + * var results = Crafty.map.search(Crafty.viewport.rect()); + * // iterate over all those entities + * var ent; + * for (var i = 0, l = results.length; i < l; ++i) { + * // do something with an entity + * ent = results[i]; + * Crafty.log('Found entity with id', ent.getId()); + * } + * ~~~ */ search: function (rect, filter) { @@ -15687,8 +18269,7 @@ var Crafty = require('../core/core.js'); for (i = keys.x1; i <= keys.x2; i++) { //insert into all y buckets for (j = keys.y1; j <= keys.y2; j++) { - cell = this.map[(i << 16) ^ j]; - if (cell) { + if ((cell = this.map[(i << 16) ^ j])) { for (k = 0; k < cell.length; k++) results.push(cell[k]); } @@ -15703,7 +18284,7 @@ var Crafty = require('../core/core.js'); obj = results[i]; if (!obj) continue; //skip if deleted id = obj[0]; //unique ID - obj = obj._mbr || obj; + obj = obj._cbr || obj._mbr || obj; //check if not added to hash and that actually intersects if (!found[id] && obj._x < rect._x + rect._w && obj._x + obj._w > rect._x && obj._y < rect._y + rect._h && obj._y + obj._h > rect._y) @@ -15722,27 +18303,24 @@ var Crafty = require('../core/core.js'); /**@ * #Crafty.map.remove * @comp Crafty.map - * @sign public void Crafty.map.remove([Object keys, ]Object obj) - * @param keys - key region. If omitted, it will be derived from obj by `Crafty.HashMap.key`. - * @param obj - An object to remove from the hashmap + * @kind Method + * + * @sign public void Crafty.map.remove(Entry entry) + * @param entry - An entry to remove from the hashmap * - * Remove an entity in a broad phase map. - * - The second form is only used in Crafty.HashMap to save time for computing keys again, where keys were computed previously from obj. End users should not call this form directly. + * Remove an entry from the broad phase map. * * @example * ~~~ * Crafty.map.remove(e); * ~~~ */ - remove: function (keys, obj) { + remove: function (entry) { + var keys = entry.keys; + var obj = entry.obj; var i = 0, j, hash; - if (arguments.length == 1) { - obj = keys; - keys = HashMap.key(obj, keyHolder); - } - //search in all x buckets for (i = keys.x1; i <= keys.x2; i++) { //insert into all y buckets @@ -15759,12 +18337,17 @@ var Crafty = require('../core/core.js'); } } } + + //mark map boundaries as dirty + this.boundsDirty = true; }, /**@ * #Crafty.map.refresh * @comp Crafty.map - * @sign public void Crafty.map.remove(Entry entry) + * @kind Method + * + * @sign public void Crafty.map.refresh(Entry entry) * @param entry - An entry to update * * Update an entry's keys, and its position in the broad phrase map. @@ -15805,18 +18388,24 @@ var Crafty = require('../core/core.js'); } } + //mark map boundaries as dirty + this.boundsDirty = true; + return entry; }, - - /**@ * #Crafty.map.boundaries * @comp Crafty.map + * @kind Method + * * @sign public Object Crafty.map.boundaries() * @returns An object with the following structure, which represents an MBR which contains all entities * + * Note that the returned object is a reference to the internally used object. + * Use `Crafty.clone` to get a copy instead. + * * ~~~ * { * min: { @@ -15830,29 +18419,56 @@ var Crafty = require('../core/core.js'); * } * ~~~ */ - boundaries: function () { - var k, ent, - hash = { - max: { - x: -Infinity, - y: -Infinity - }, - min: { - x: Infinity, - y: Infinity - } - }, - coords = { - max: { - x: -Infinity, - y: -Infinity - }, - min: { - x: Infinity, - y: Infinity - } - }; + boundaries: function() { + this._updateBoundaries(); + return this.boundsCoords; + }, + + /** + * #Crafty.map._keyBoundaries + * @comp Crafty.map + * @kind Method + * + * @sign private Object Crafty.map._keyBoundaries() + * @returns An object with the following structure, which represents an MBR which contains all hash keys + * + * Find boundaries of row/col cell grid keys instead of actual x/y pixel coordinates. + * + * ~~~ + * { + * min: { + * x: val_x, + * y: val_y + * }, + * max: { + * x: val_x, + * y: val_y + * } + * } + * ~~~ + */ + _keyBoundaries: function() { + this._updateBoundaries(); + return this.boundsHash; + }, + + _updateBoundaries: function() { + // update map boundaries if they were changed + if (!this.boundsDirty) return; + + var hash = this.boundsHash; + hash.max.x = -Infinity; + hash.max.y = -Infinity; + hash.min.x = Infinity; + hash.min.y = Infinity; + + var coords = this.boundsCoords; + coords.max.x = -Infinity; + coords.max.y = -Infinity; + coords.min.x = Infinity; + coords.min.y = Infinity; + var k, ent; //Using broad phase hash to speed up the computation of boundaries. for (var h in this.map) { if (!this.map[h].length) continue; @@ -15868,7 +18484,7 @@ var Crafty = require('../core/core.js'); for (k in this.map[h]) { ent = this.map[h][k]; //make sure that this is a Crafty entity - if (typeof ent == 'object' && 'requires' in ent) { + if (typeof ent === 'object' && 'requires' in ent) { coords.max.x = Math.max(coords.max.x, ent.x + ent.w); } } @@ -15877,7 +18493,7 @@ var Crafty = require('../core/core.js'); hash.min.x = i; for (k in this.map[h]) { ent = this.map[h][k]; - if (typeof ent == 'object' && 'requires' in ent) { + if (typeof ent === 'object' && 'requires' in ent) { coords.min.x = Math.min(coords.min.x, ent.x); } } @@ -15886,7 +18502,7 @@ var Crafty = require('../core/core.js'); hash.max.y = j; for (k in this.map[h]) { ent = this.map[h][k]; - if (typeof ent == 'object' && 'requires' in ent) { + if (typeof ent === 'object' && 'requires' in ent) { coords.max.y = Math.max(coords.max.y, ent.y + ent.h); } } @@ -15895,20 +18511,210 @@ var Crafty = require('../core/core.js'); hash.min.y = j; for (k in this.map[h]) { ent = this.map[h][k]; - if (typeof ent == 'object' && 'requires' in ent) { + if (typeof ent === 'object' && 'requires' in ent) { coords.min.y = Math.min(coords.min.y, ent.y); } } } } - return coords; + // mark map boundaries as clean + this.boundsDirty = false; + }, + + + /**@ + * #Crafty.map.traverseRay + * @comp Crafty.map + * @kind Method + * + * @sign public void Crafty.map.traverseRay(Object origin, Object direction, Function callback) + * @param origin - the point of origin from which the ray will be cast. The object must contain the properties `_x` and `_y`. + * @param direction - the direction the ray will be cast. It must be normalized. The object must contain the properties `x` and `y`. + * @param callback - a callback that will be called for each object that is encountered along the ray. + * This function is called with two arguments: The first one represents the object encountered; + * the second one represents the distance up to which all objects have been reported so far. + * The callback can return a truthy value in order to stop the traversal early. + * + * Traverse the spatial map in the direction of the supplied ray. + * + * Given the `origin` and `direction` the ray is cast and the `callback` is called + * for each object encountered in map cells traversed by the ray. + * + * The callback is called for each object that may be intersected by the ray. + * Whether an actual intersection occurs shall be determined by the callback's implementation. + * + * @example + * ~~~ + * Crafty.e("2D") + * .setName('First entity') + * .attr({x: 0, y: 0, w: 10, h: 10}); + * + * Crafty.e("2D") + * .setName('Second entity') + * .attr({x: 20, y: 20, w: 10, h: 10}); + * + * var origin = {_x: -25, _y: -25}; + * var direction = new Crafty.math.Vector2D(1, 1).normalize(); + * + * Crafty.map.traverseRay(origin, direction, function(ent, processedDistance) { + * Crafty.log('Encountered entity named', ent.getName()); // logs 'First entity' + * Crafty.log('All entities up to', processedDistance, 'px away have been reported thus far.'); + * Crafty.log('Stopping traversal after encountering the first entity.'); + * return true; + * }); + * ~~~ + */ + + // See [this tutorial](http://www.flipcode.com/archives/Raytracing_Topics_Techniques-Part_4_Spatial_Subdivisions.shtml) and linked materials + // Segment-segment intersection is described here: http://stackoverflow.com/a/565282/3041008 + // + // origin = {_x, _y} + // direction = {x, y}, must be normalized + // + // + // # Let + // edge = end - start + // edge x edge == 0 + // + // # Segment - segment intersection equation + // origin + d * direction = start + e * edge + // + // # Solving for d + // (origin + d * direction) x edge = (start + e * edge) x edge + // d = (start − origin) × edge / (direction × edge) + // + // (start.x - origin.x) * edge.y - (start.y - origin.y) * edge.x + // d = -------------------------------------------------------------- + // direction.x * edge.y - direction.y * edge.x + // + // + // # In case ray intersects vertical cell grid edge + // start = (x, 0) + // edge = (0, 1) + // + // start.x - origin.x + // d = ------------------- + // direction.x + // + // # In case ray intersects horizontal cell grid edge + // start = (0, y) + // edge = (1, 0) + // + // start.y - origin.y + // d = ------------------- + // direction.y + // + traverseRay: function(origin, direction, callback) { + var dirX = direction.x, + dirY = direction.y; + // copy input data + // TODO maybe allow HashMap.key search with point only + origin = { + _x: origin._x, + _y: origin._y, + _w: 0, + _h: 0 + }; + + + var keyBounds = this._keyBoundaries(); + var keys = HashMap.key(origin, keyHolder); + + // calculate col & row cell indices + var currentCol = keys.x1, + currentRow = keys.y1; + var minCol = keyBounds.min.x, + minRow = keyBounds.min.y, + maxCol = keyBounds.max.x, + maxRow = keyBounds.max.y; + // direction to traverse cells + var stepCol = dirX > 0 ? 1 : (dirX < 0 ? -1 : 0), + stepRow = dirY > 0 ? 1 : (dirY < 0 ? -1 : 0); + + + // first, next cell edge in absolute coordinates + var firstCellEdgeX = (dirX >= 0) ? (currentCol + 1) * cellsize : currentCol * cellsize, + firstCellEdgeY = (dirY >= 0) ? (currentRow + 1) * cellsize : currentRow * cellsize; + + // distance from origin to previous cell edge + var previousDistance = -Infinity; + + // distances to next horizontal and vertical cell edge + var deltaDistanceX = 0, // distance for the ray to be advanced to cross a whole cell horizontally + deltaDistanceY = 0, // distance for the ray to be advanced to cross a whole cell vertically + nextDistanceX = Infinity, // distance we can advance(increase magnitude) ray until we advance to next horizontal cell + nextDistanceY = Infinity; // distance we can advance(increase magnitude) ray until we advance to next vertical cell + + var norm; + if (dirX !== 0) { + norm = 1.0 / dirX; + nextDistanceX = (firstCellEdgeX - origin._x) * norm; + deltaDistanceX = (cellsize * stepCol) * norm; + } + if (dirY !== 0) { + norm = 1.0 / dirY; + nextDistanceY = (firstCellEdgeY - origin._y) * norm; + deltaDistanceY = (cellsize * stepRow) * norm; + } + + + // advance starting cell to be inside of map bounds + while ((stepCol === 1 && currentCol < minCol && minCol !== Infinity) || (stepCol === -1 && currentCol > maxCol && maxCol !== -Infinity) || + (stepRow === 1 && currentRow < minRow && minRow !== Infinity) || (stepRow === -1 && currentRow > maxRow && maxRow !== -Infinity)) { + + // advance to closest cell + if (nextDistanceX < nextDistanceY) { + previousDistance = nextDistanceX; + + currentCol += stepCol; + nextDistanceX += deltaDistanceX; + } else { + previousDistance = nextDistanceY; + + currentRow += stepRow; + nextDistanceY += deltaDistanceY; + } + } + + var cell; + // traverse over cells + // TODO: maybe change condition to `while (currentCol !== endX) || (currentRow !== endY)` + while ((minCol <= currentCol && currentCol <= maxCol) && + (minRow <= currentRow && currentRow <= maxRow)) { + + // process cell + if ((cell = this.map[(currentCol << 16) ^ currentRow])) { + // check each object inside this cell + for (var k = 0; k < cell.length; k++) { + // if supplied callback returns true, abort traversal + if (callback(cell[k], previousDistance)) + return; + } + } + + // advance to closest cell + if (nextDistanceX < nextDistanceY) { + previousDistance = nextDistanceX; + + currentCol += stepCol; + nextDistanceX += deltaDistanceX; + } else { + previousDistance = nextDistanceY; + + currentRow += stepRow; + nextDistanceY += deltaDistanceY; + } + } } + }; /**@ * #Crafty.HashMap * @category 2D + * @kind Class + * * Broad-phase collision detection engine. See background information at * * - [N Tutorial B - Broad-Phase Collision](http://www.metanetsoftware.com/technique/tutorialB.html) @@ -15919,6 +18725,8 @@ var Crafty = require('../core/core.js'); /**@ * #Crafty.HashMap.key * @comp Crafty.HashMap + * @kind Method + * * @sign public Object Crafty.HashMap.key(Object obj) * @param obj - an Object that has .mbr() or _x, _y, _w and _h. * @@ -15928,12 +18736,8 @@ var Crafty = require('../core/core.js'); * @see Crafty.HashMap.constructor */ HashMap.key = function (obj, keys) { - if (obj._mbr) { - obj = obj._mbr; - } - if (!keys) { - keys = {}; - } + obj = obj._cbr || obj._mbr || obj; + keys = keys || {}; keys.x1 = Math.floor(obj._x / cellsize); keys.y1 = Math.floor(obj._y / cellsize); @@ -15955,7 +18759,7 @@ var Crafty = require('../core/core.js'); Entry.prototype = { update: function (rect) { //check if buckets change - if (HashMap.hash(HashMap.key(rect, keyHolder)) != HashMap.hash(this.keys)) { + if (HashMap.hash(HashMap.key(rect, keyHolder)) !== HashMap.hash(this.keys)) { this.map.refresh(this); } } @@ -15963,4 +18767,4 @@ var Crafty = require('../core/core.js'); module.exports = HashMap; -},{"../core/core.js":7}]},{},[17]); +},{}]},{},[18]); diff --git a/package.json b/package.json index 90af42d6e..083fdc133 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "craftyjs", - "version": "0.7.1-rc2", + "version": "0.8.0-rc1", "author": { "name": "Louis Stowasser", "url": "http://craftyjs.com/" @@ -68,4 +68,4 @@ "brfs" ] } -} +} \ No newline at end of file diff --git a/src/core/version.js b/src/core/version.js index 272b7e2c6..4f8d12ebd 100644 --- a/src/core/version.js +++ b/src/core/version.js @@ -1 +1 @@ -module.exports = "0.7.1-rc2"; \ No newline at end of file +module.exports = "0.8.0-rc1"; \ No newline at end of file