diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e72c33d..89dff73c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,50 @@ +## 0.9.0 (rc-1) +*(This set of changes is in the testing branch, but has yet to be released)* + +A large number of performance improvements lead to a larger than normal set of breaking changes in this release. There was some refactoring of control systems, and built-in support for freezing entities allows for some performance optimizations when spawning and destroying large numbers of identical entities. + +### New features +- Several internal changes to how input and control events are dispatched. (#1121, also for an overview https://github.com/craftyjs/Crafty/projects/1) + - Mouse scroll supported (#1115) +- Properties shorthand for systems (#1122) and entities (#1096), allowing simpler definition of getter/setter behavior +- Support for freezing/unfreezing entities (#1087), which essentially remove an entity from the gamestate while preserving its internal state. This allows useful optimizations such as reusing entities. +- Support setting the size of a custom stage element (#1154) +- Allow passing a list of components as an argument list when calling `requires` (#1150) +- Allow sprite animations to be specified with a list of named positions (1091, 1114) +- Allow controls to be destroyed, and fire events on creation/destruction #1133 +- Several improvements to input events (#1121) +- Add `ox` and `oy` properties that get and set the position of the entities origin on the stage +- Bind the callbacks in a components `event` prop before running `init`(#1131) +- Allow specifying multiway behavior when calling fourway (#1094) +- "SetStyle" event emitted when using the `.css` method on DOM entities. + +### Breaking changes + +- **Major**: Built-in components moved to use "UpdateFrame" rather than "EnterFrame". "EnterFrame" is still fired once per frame, before the "UpdateFrame" event, and so will occur before things like "Motion" have been processed. #1034 +- **Major**: The "Moved" event is removed completely (use "Move" instead) #1146 +- The 'Motion' component will fire the "Move" event once per tick, rather than separately for x/y motion #1146 +- The event handlers declared using the `events` property shorthand are now bound *before* `init()` is run, instead of after +- The map.search previously took a flag that controlled whether it would filter the results. For performance reasons, this behavior has now been split into two methods: `search` and `unfilteredSearch`. #1144 +- The results of the internal _SAT method now return nx and ny as top level properties `{overlap, nx, ny}` rather than as a nested normal object `{overlap, normal {nx, ny}}`. #1147 +- The 'Rotate' event now only contains the rotation amount in degrees. The `rotate` method signature now takes a set of parameters rather than the 'Rotate' event object. #1145 +- Internally, _cascade now handles linear motion of any attached entities, while _cascadeRotation handles their rotation. #1145 +- Changes to touch events: + - TouchStart and TouchEnd are no longer triggered when a finger enters or leaves entity. They are now triggered only once, when a finger is pressed or raised on the touch surface, no matter which entity was targeted. + - TouchOver and TouchOut replace those events, which are triggered when a finger enters or leaves the entity. +- Crafty.selected now defaults to true rather than false + +### Bug fixes and optimizations +- Fix a bug when when destroying pointer entities could result in double-decrementing the pointer entity counter for their layer #1143 +- Bugfix for weird behavior with cancelTween (1113) +- A large number of optimizations to simplify events and allocate fewer object. These effect: + - How the 'Motion' component fires events #1146 + - spatial map search and collision algorithms #1147 + - how the 'Rotate' event fires #1145 + - the "Particles" component; should now render more like other standard graphics components #1132, #1141 + - the `Crafty("component")` selector (1117) + - How Canvas partial redraws work (bnow rely on the spatial map grid struture as an optimization in the common case) (#1125) + - Misc allocation fixes (#1136, #1137, #1138) + ## 0.8.0 Several important new features: raycasting, control over webgl shaders, a new system for wiring up inputs to entities, and more versatile graphics layers. diff --git a/Gruntfile.js b/Gruntfile.js index c93a1279..27125221 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -363,5 +363,4 @@ module.exports = function (grunt) { // Make crafty.js ready for release - minified version grunt.registerTask('release', ['version', 'changelog', 'build:release', 'uglify', 'jsvalidate:release', 'api']); - }; diff --git a/dist/crafty-min.js b/dist/crafty-min.js index 5b51f3b1..8d1e55c2 100644 --- a/dist/crafty-min.js +++ b/dist/crafty-min.js @@ -1,13 +1,14 @@ /** - * craftyjs 0.8.0 + * craftyjs 0.9.0-rc2 * http://craftyjs.com/ * - * Copyright 2017, Louis Stowasser + * Copyright 2018, 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&&(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":9}],6:[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":9}],7:[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":9}],8:[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":9}],9:[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":18}],10:[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":9,_process:1}],11:[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)},imageWhitelist:["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.imageWhitelist.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":9}],12:[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":9}],13:[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":9}],14:[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":9}],15:[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":9}],16:[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}}},{}],17:[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":9}],18:[function(a,b,c){b.exports="0.8.0"},{}],19:[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"),a("./aliases").defineAliases(d),window&&(window.Crafty=d),b.exports=d},{"./aliases":2,"./controls/controls":4,"./controls/controls-system":3,"./controls/device":5,"./controls/inputs":6,"./controls/keycodes":7,"./core/animation":8,"./core/core":9,"./core/extensions":10,"./core/loader":11,"./core/model":12,"./core/scenes":13,"./core/storage":14,"./core/systems":15,"./core/time":16,"./core/tween":17,"./debug/debug-layer":20,"./debug/logging":21,"./graphics/canvas":23,"./graphics/canvas-layer":22,"./graphics/color":24,"./graphics/dom":27,"./graphics/dom-helper":25,"./graphics/dom-layer":26,"./graphics/drawing":28,"./graphics/gl-textures":29,"./graphics/html":30,"./graphics/image":31,"./graphics/layers":32,"./graphics/particles":33,"./graphics/renderable":34,"./graphics/sprite":36,"./graphics/sprite-animation":35,"./graphics/text":37,"./graphics/viewport":38,"./graphics/webgl":40,"./graphics/webgl-layer":39,"./isometric/diamond-iso":41,"./isometric/isometric":42,"./sound/sound":43,"./spatial/2d":44,"./spatial/collision":45,"./spatial/math":46,"./spatial/motion":47,"./spatial/platform":48,"./spatial/rect-manager":49,"./spatial/spatial-grid":50}],20:[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":9}],25:[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":9}],26:[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":9}],33:[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":9}],34:[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":9}],35:[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":9}],36:[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":9}],37:[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":9}],38:[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":9}],43:[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":9,"./spatial-grid.js":50}],45:[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":9}],46:[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":9}],47:[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":9}],48:[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":9}],50:[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);)y1)for(var c=1;c0&&(c/=g,d/=g)}a.x=c,a.y=d},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.timeDown=0?this.touchPoints[b]:null)){var c=this._touchPointsPool.pop()||{};this._setTouchPoint(c,a),this.touchPoints.push(c),this.triggerTouchEvent(c.eventName,c)}},_handleMove:function(a){var b=this._indexOfTouchPoint(a.identifier),c=b>=0?this.touchPoints[b]:null;c&&(this._setTouchPoint(c,a),this.triggerTouchEvent(c.eventName,c))},_handleEnd:function(a){var b=this._indexOfTouchPoint(a.identifier),c=b>=0?this.touchPoints[b]:null;c&&(this._setTouchPoint(c,a),this.triggerTouchEvent(c.eventName,c),this.touchPoints.splice(b,1),c.target=null,c.entity=null,c.originalEvent=null,this._touchPointsPool.push(c))}},d.c("TouchState",d.__touchStateTemplate),d.s("Touch",d.extend.call({triggerTouchEvent:function(a,b){d.trigger(a,b)}},d.__touchStateTemplate),{},!1)},{"../core/core.js":10}],9:[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,void 0!==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&&!1===this.complete;)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":10}],10:[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,p=a("./version"),q=function(a){return new q.fn.init(a)};h={},m=Array.prototype.slice,n=/\s*,\s*/,o=/\s+/;var r=function(){f=1,g=0,i={},l={},j={},k=[]};r(),q.fn=q.prototype={init:function(a){if("string"!=typeof a)return a||(a=0)in i||(i[a]=this),a in i?(this[0]=a,this.length=1,this.__c||(this.__c={}),this._callbacks||q._addCallbackMethods(this),i[a]||(i[a]=this),i[a]):(this.length=0,this);var b,c,d,e,f,g,h,j,k,m=0,p=!1,r=!1;if("*"===a){j=0;for(b in i)this[j]=+b,j++;return this.length=j,1===j?i[this[0]]:this}if(-1!==a.indexOf(",")?(r=!0,c=n):-1!==a.indexOf(" ")&&(p=!0,c=o),r){for(d=a.split(c),e={},j=0,k=d.length;j=0&&(this[m++]=j)}else{f=l[a];for(h in f)this[m++]=+h}return this.length=m,1===m?i[this[m-1]]:(q._addCallbackMethods(this),this)},setName:function(a){var b=String(a);return this._entityName=b,this.trigger("NewEntityName",b),this},getName:function(a){return this._entityName},addComponent:function(a){var b,c,d,e=0;for(b=1===arguments.length&&-1!==a.indexOf(",")?a.split(n):arguments;e1)for(b=arguments.length;d-1?(d=a.split("."),c=d.shift(),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 m.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&&q.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,q.trigger("PreRender"),q.trigger("RenderScene"),q.trigger("PostRender"),m=Date.now(),q.trigger("MeasureRenderTime",m-a)),h=m},FPS:function(a){if(void 0===a)return j;j=a,k=1e3/j,q.trigger("FPSChange",a)},simulateFrames:function(a,b){for(b=b||k;a-- >0;){var c={frame:g++,dt:b};q.trigger("EnterFrame",c),q.trigger("UpdateFrame",c),q.trigger("ExitFrame",c)}q.trigger("PreRender"),q.trigger("RenderScene"),q.trigger("PostRender")}}}(),e:function(){var a=d();return i[a]=null,i[a]=q(a),arguments.length>0&&i[a].addComponent.apply(i[a],arguments),i[a].setName("Entity #"+a),i[a].addComponent("obj"),q.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])&&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},entities:function(){return i},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 q}),b.exports=q},{"./version":19}],11:[function(a,b,c){(function(b){var c=a("../core/core.js"),d="undefined"!=typeof window&&window.document;!function(){var a=c.support={},e="undefined"!=typeof navigator&&navigator.userAgent.toLowerCase()||void 0!==b&&b.version,f=/(webkit)[ \/]([\w.]+)/.exec(e)||/(o)pera(?:.*version)?[ \/]([\w.]+)/.exec(e)||/(ms)ie ([\w.]+)/.exec(e)||/(moz)illa(?:.*? rv:([\w.]+))?/.exec(e)||/(v)\d+\.(\d+)/.exec(e)||[],g=/iPad|iPod|iPhone|Android|webOS|IEMobile/i.exec(e);if(g&&(c.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 d.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 d.createElement("canvas"),a.canvas){var h;try{var i=d.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&&(void 0!==d.createElement("div").style.Perspective||void 0!==d.createElement("div").style[a.prefix+"Perspective"]),a.deviceorientation="undefined"!=typeof window&&(void 0!==window.DeviceOrientationEvent||void 0!==window.OrientationEvent),a.devicemotion="undefined"!=typeof window&&void 0!==window.DeviceMotionEvent}()}).call(this,a("_process"))},{"../core/core.js":10,_process:1}],12:[function(a,b,c){var d=a("../core/core.js");b.exports={assets:{},__paths:{audio:"",images:""},paths:function(a){if(void 0===a)return this.__paths;a.audio&&(this.__paths.audio=a.audio),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)},imageWhitelist:["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===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-1===b.search("://")?"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))};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 t=[];for(var u in h)i=q(k,h[u]),r(i)||!s(h[u])||d.audio.sounds[l]||t.push(i);t.length>0&&(j=d.audio.add(l,t))}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)&&function(a){return-1!==d.imageWhitelist.indexOf(p(a))}(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),function(a,b){a.onload=f,"webkit"===d.support.prefix&&(a.src=""),a.src=b}(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-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":10}],13:[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":10}],14:[function(a,b,c){var d=a("../core/core.js");b.exports={_scenes:{},_current:null,scene:function(a,b,c){if(1===arguments.length||"function"!=typeof arguments[1])return void d.enterScene(a,arguments[1]);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,void 0!==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":10}],15:[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){if(!e)return void d.error("Local storage is not accessible. (Perhaps you are including crafty.js cross-domain?)");e.removeItem(a)},b.exports=f},{"../core/core.js":10}],16:[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){if(!b)return e._systems[a];"boolean"==typeof c&&(d=c,c=null),!1===d?(e._systems[a]=new e.CraftySystem(a,b,c),e.trigger("SystemLoaded",a)):e._registerLazySystem(a,b,c)},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++,"properties"in c){var g=c.properties;for(var h in g)Object.defineProperty(this,h,g[h])}if("events"in c){var i=c.events;for(var j in i){var k="function"==typeof i[j]?i[j]:c[i[j]];this.bind(j,k)}}"function"==typeof this.init&&this.init(b)}}(),e.CraftySystem.prototype={extend:function(a){for(var b in a)void 0===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":10}],17:[function(a,b,c){b.exports={delaySpeed:1,init:function(){this._delays=[],this._delaysPaused=!1,this.bind("UpdateFrame",function(a){if(!this._delaysPaused)for(var b=this._delays.length;--b>=0;){var c=this._delays[b];if(!1===c)this._delays.splice(b,1);else{for(c.accumulator+=a.dt*this.delaySpeed;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}}},{}],18:[function(a,b,c){var d=a("../core/core.js");b.exports={tweenSpeed:1,init:function(){this.tweenGroup={},this.tweenStart={},this.tweens=[],this.uniqueBind("UpdateFrame",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*this.tweenSpeed),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)void 0!==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){var b=!1;for(var c in a)b=!0,delete this.tweenGroup[c];b&&this.trigger("TweenEnd",a)}}},{"../core/core.js":10}],19:[function(a,b,c){b.exports="0.9.0-rc2"},{}],20:[function(a,b,c){b.exports=function(b){b&&(a=b);var c=a("./core/core");a("./core/extensions"),c.easing=a("./core/animation"),c.c("Model",a("./core/model")),c.extend(a("./core/scenes")),c.storage=a("./core/storage"),c.c("Delay",a("./core/time")),c.c("Tween",a("./core/tween"));var d=a("./spatial/spatial-grid");return c.HashMap=d,c.map=new d,a("./core/systems"),a("./spatial/2d"),a("./spatial/motion"),a("./spatial/platform"),a("./spatial/collision"),a("./spatial/rect-manager"),a("./spatial/math"),a("./controls/controls-system"),a("./controls/controls"),a("./controls/keyboard"),a("./controls/keycodes"),a("./controls/mouse"),a("./controls/touch"),a("./debug/logging"),c}},{"./controls/controls":4,"./controls/controls-system":3,"./controls/keyboard":5,"./controls/keycodes":6,"./controls/mouse":7,"./controls/touch":8,"./core/animation":9,"./core/core":10,"./core/extensions":11,"./core/model":13,"./core/scenes":14,"./core/storage":15,"./core/systems":16,"./core/time":17,"./core/tween":18,"./debug/logging":23,"./spatial/2d":54,"./spatial/collision":55,"./spatial/math":56,"./spatial/motion":57,"./spatial/platform":58,"./spatial/rect-manager":59,"./spatial/spatial-grid":60}],21:[function(a,b,c){var d=a("./crafty-common.js")();d.extend(a("./core/loader")),d.extend(a("./inputs/dom-events")),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("./inputs/util"),a("./inputs/device"),a("./inputs/keyboard"),a("./inputs/lifecycle"),a("./inputs/mouse"),a("./inputs/pointer"),a("./inputs/touch"),a("./sound/sound"),a("./debug/debug-layer"),a("./aliases").defineAliases(d),window&&(window.Crafty=d),b.exports=d},{"./aliases":2,"./core/loader":12,"./crafty-common.js":20,"./debug/debug-layer":22,"./graphics/canvas":25,"./graphics/canvas-layer":24,"./graphics/color":26,"./graphics/dom":29,"./graphics/dom-helper":27,"./graphics/dom-layer":28,"./graphics/drawing":30,"./graphics/gl-textures":31,"./graphics/html":32,"./graphics/image":33,"./graphics/layers":34,"./graphics/particles":35,"./graphics/renderable":36,"./graphics/sprite":38,"./graphics/sprite-animation":37,"./graphics/text":39,"./graphics/viewport":40,"./graphics/webgl":42,"./graphics/webgl-layer":41,"./inputs/device":43,"./inputs/dom-events":44,"./inputs/keyboard":45,"./inputs/lifecycle":46,"./inputs/mouse":47,"./inputs/pointer":48,"./inputs/touch":49,"./inputs/util":50,"./isometric/diamond-iso":51,"./isometric/isometric":52,"./sound/sound":53}],22:[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 void 0===a&&(a="red"),this._debug.fillStyle=a,this},debugStroke:function(a){return void 0===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("WiredMBR",{init:function(){this.requires("DebugRectangle").debugStroke("purple")},events:{PreRender:function(){this.debugRectangle(this._mbr||this)}}});var f={init:function(){this.requires("DebugRectangle").debugFill("pink")},events:{PreRender:function(){this.debugRectangle(this._mbr||this)}}};d.c("SolidMBR",f),d.c("VisibleMBR",f),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(void 0!==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._drawDirtyCells(),this._clean()}},_drawDirtyCells:function(a){var b,c,e=this._viewportRect(),f=this.__tempRect,g=this._dirtyRects,h=d.rectManager.integerBounds,i=this.context;for(a=h(a||e),this._createDirtyCells(a),this._createDirtyRects(),b=0,c=g.length;bg&&f._visible&&f._drawLayer===this&&(f.draw(i),f._changed=!1,g=f._globalZ);i.restore()},debug:function(){d.log(this._changedObjs)},_clean:function(){var a,b,c,d,e,f=this._changedObjs;for(d=0,e=f.length;d>16,b=c<0?~(a>>16):a>>16,g.push(b*e,c*e,e,e)},_resize:function(){var a=this._canvas;a.width=d.viewport.width,a.height=d.viewport.height},_setPixelart:function(a){var b=this.context;b.imageSmoothingEnabled=!a,b.mozImageSmoothingEnabled=!a,b.webkitImageSmoothingEnabled=!a,b.oImageSmoothingEnabled=!a,b.msImageSmoothingEnabled=!a}})},{"../core/core.js":10}],25:[function(a,b,c){var d=a("../core/core.js");d.c("Canvas",{init:function(){this.requires("Renderable"),this.currentRect={},this._customLayer||this._attachToLayer(d.s("DefaultCanvasLayer"))},remove:function(){this._detachFromLayer()},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){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=a||this._drawContext,h=this.__coord||[0,0,0,0],i=this.drawVars.co;i.x=h[0]+(b||0),i.y=h[1]+(c||0),i.w=d||h[2],i.h=e||h[3],(this._flipX||this._flipY||this._rotation)&&g.save(),0!==this._rotation&&(g.translate(this._origin.x+this._x,this._origin.y+this._y),f._x=-this._origin.x,f._y=-this._origin.y,g.rotate(this._rotation%360*(Math.PI/180))),(this._flipX||this._flipY)&&(g.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 j;return this._alpha<1&&(j=g.globalAlpha,g.globalAlpha=this._alpha),this.drawVars.ctx=g,this.trigger("Draw",this.drawVars),(0!==this._rotation||this._flipX||this._flipY)&&g.restore(),j&&(g.globalAlpha=j),this}}})},{"../core/core.js":10}],26:[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,e,f,g=b.length;if(7===g)d=b.substr(1,2),e=b.substr(3,2),f=b.substr(5,2);else{if(4!==g)return a(c);d=b.substr(1,1),d+=d,e=b.substr(2,1),e+=e,f=b.substr(3,1),f+=f}return c._red=parseInt(d,16),c._green=parseInt(e,16),c._blue=parseInt(f,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(void 0===k[a]){!1===j&&(window.document.body.appendChild(i),j=!0),i.style.color=a;f(window.getComputedStyle(i).color,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();"#"===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)}}()}),d.defaultShader("Color",new d.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(a,b){a.program.writeVector("aColor",b._red/255,b._green/255,b._blue/255,b._strength)})),d.c("Color",{_red:0,_green:0,_blue:0,_strength:1,_color:"",ready:!0,init:function(){this.__coord=this.__coord||[0,0,0,0],this.bind("Draw",this._drawColor),this._drawLayer&&this._setupColor(this._drawLayer),this.trigger("Invalidate")},events:{LayerAttached:"_setupColor"},remove:function(){this.unbind("Draw",this._drawColor),this.has("DOM")&&(this._element.style.backgroundColor="transparent"),this.trigger("Invalidate")},_setupColor:function(a){"WebGL"===a.type&&this._establishShader("Color",d.defaultShader("Color"))},_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.draw(a,this))},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":10}],27:[function(a,b,c){var d=a("../core/core.js"),e=window.document;d.extend({domHelper:{innerPosition:function(a,b){b=b||{};var c=a.getBoundingClientRect(),d=c.left+(window.pageXOffset?window.pageXOffset:e.body.scrollLeft),f=c.top+(window.pageYOffset?window.pageYOffset:e.body.scrollTop),g=parseInt(this.getStyle(a,"border-left-width")||0,10)||parseInt(this.getStyle(a,"borderLeftWidth")||0,10)||0,h=parseInt(this.getStyle(a,"border-top-width")||0,10)||parseInt(this.getStyle(a,"borderTopWidth")||0,10)||0;return b.x=d+g,b.y=f+h,b},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,f){f=f||{};var g,h=e.documentElement,i=e.body;return c?(g=c._viewportRect(),f.x=(a-d.stage.x+(h&&h.scrollLeft||i&&i.scrollLeft||0))/g._scale+g._x,f.y=(b-d.stage.y+(h&&h.scrollTop||i&&i.scrollTop||0))/g._scale+g._y):(g=d.viewport,f.x=(a-d.stage.x+(h&&h.scrollLeft||i&&i.scrollLeft||0))/g._scale-g._x,f.y=(b-d.stage.y+(h&&h.scrollTop||i&&i.scrollTop||0))/g._scale-g._y),f}}})},{"../core/core.js":10}],28:[function(a,b,c){var d=a("../core/core.js"),e=window.document;d._registerLayerTemplate("DOM",{type:"DOM",_changedObjs:[],_div:null,events:{LayerInit:"layerInit",LayerRemove:"layerRemove",RenderScene:"_render",PixelartSet:"_setPixelart"},layerInit: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"},layerRemove:function(){this._div.parentNode.removeChild(this._div)},_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])},_commonLayerProperties:{options:{xResponse:1,yResponse:1,scaleResponse:1,z:0},_pointerEntities:0,_dirtyViewport:!1,_cachedViewportRect:null,init:function(){this._cachedViewportRect={},this.trigger("LayerInit"),this.uniqueBind("InvalidateViewport",function(){this._dirtyViewport=!0}),this.trigger("PixelartSet",d._pixelartEnabled),d._addDrawLayerInstance(this)},remove:function(){this.trigger("LayerRemove"),d._removeDrawLayerInstance(this)},_sort:function(a,b){return a._globalZ-b._globalZ},_viewportRect:function(a){var b=this._cachedViewportRect;if(a)return b;var c=d.viewport,e=this.options,f=Math.pow(c._scale,e.scaleResponse);return b._scale=f,b._w=c._width/f,b._h=c._height/f,b._x=e.xResponse*-c._x-.5*(e.xResponse-1)*(1-1/f)*c._width,b._y=e.yResponse*-c._y-.5*(e.yResponse-1)*(1-1/f)*c._height,b},_viewTransformRect:function(a,b,c){var d=this._viewportRect(c),e=d._scale;return b=b||{},b._x=a._x*e+Math.round(-d._x*e),b._y=a._y*e+Math.round(-d._y*e),b._w=a._w*e,b._h=a._h*e,b}},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":10}],35:[function(a,b,c){var d=a("../core/core.js");d.c("Particles",{required:"Renderable",ready:!0,_particlesPaused:!1,init:function(){this._Particles=d.clone(this._Particles),this._Particles.init(),this._Particles.parentEntity=this},events:{UpdateFrame:function(){!this._particlesPaused&&this._Particles.active&&(this._Particles.update(),this.trigger("Invalidate"))},Draw:function(a){this._Particles.active&&"canvas"===a.type&&this._Particles.render(a)}},particles:function(a){return this._Particles.config(a),this._Particles.start(),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}},emissionRate:0,elapsedFrames:0,emitCounter:0,active:!0,particles:[],init:function(){for(var a in this.presets)this[a]=this.presets[a]},config:function(a){a=a||{};for(var b in a)this[b]=a[b];if(this.emissionRate=this.maxParticles/this.lifeSpan,this.particles.length!==this.maxParticles){this.particles.length=0;for(var c=0,d=this.maxParticles;c100?100:f<0?0:f,a.sizeSmall=~~(d/200*f),g=h=this.startColour[0]+this.startColourRandom[0]*this.RANDM1TO1(),a.colourR=g>255?255:g<0?0:~~g,g=i=this.startColour[1]+this.startColourRandom[1]*this.RANDM1TO1(),a.colourG=g>255?255:g<0?0:~~g,g=j=this.startColour[2]+this.startColourRandom[2]*this.RANDM1TO1(),a.colourB=g>255?255:g<0?0:~~g,g=k=this.startColour[3]+this.startColourRandom[3]*this.RANDM1TO1(),a.colourA=g>1?1:g<0?0:~~(100*g)/100,l=this.endColour[0]+this.endColourRandom[0]*this.RANDM1TO1(),m=this.endColour[1]+this.endColourRandom[1]*this.RANDM1TO1(),n=this.endColour[2]+this.endColourRandom[2]*this.RANDM1TO1(),o=this.endColour[3]+this.endColourRandom[3]*this.RANDM1TO1(),a.deltaColourR=(l-h)/e,a.deltaColourG=(m-i)/e,a.deltaColourB=(n-j)/e,a.deltaColourA=(o-k)/e},update:function(){var a=this.RANDM1TO1,b=this.gravity.x,c=this.gravity.y,d=this.jitter;this.elapsedFrames++,this.duration>=0&&this.duration0?1/this.emissionRate:1/0;this.emitCounter++;for(var f,g,h=this.particles,i=0,j=h.length;i0?(g.directionX+=b,g.directionY+=c,g.positionX+=g.directionX,g.positionY+=g.directionY,d&&(g.positionX+=d*a(),g.positionY+=d*a()),f=g.colourR+g.deltaColourR,g.colourR=f>255?255:f<0?0:~~f,f=g.colourG+g.deltaColourG,g.colourG=f>255?255:f<0?0:~~f,f=g.colourB+g.deltaColourB,g.colourB=f>255?255:f<0?0:~~f,f=g.colourA+g.deltaColourA,g.colourA=f>1?1:f<0?0:~~(100*f)/100,g.timeToLive--):this.emitCounter>e&&(this.initParticle(g),this.emitCounter-=e)},render:function(a){for(var b,c=a.ctx,d=this.particles,e=0,f=d.length;e>1;if(!(b.positionX<0||b.positionX+g>a.pos._w||b.positionY<0||b.positionY+g>a.pos._h)){var i=~~(a.pos._x+b.positionX),j=~~(a.pos._y+b.positionY),k=b.colourR,l=b.colourG,m=b.colourB,n=b.colourA,o="rgba("+k+","+l+","+m+","+n+")";if(this.fastMode)c.fillStyle=o;else{var p="rgba("+k+","+l+","+m+",0)",q=c.createRadialGradient(i+h,j+h,b.sizeSmall,i+h,j+h,h);q.addColorStop(0,o),q.addColorStop(.9,p),c.fillStyle=q}c.fillRect(i,j,g,g)}}},Particle:function(){this.positionX=0,this.positionY=0,this.directionX=0,this.directionY=0,this.size=0,this.sizeSmall=0,this.timeToLive=0,this.colourR=0,this.colourG=0,this.colourB=0,this.colourA=0,this.deltaColourR=0,this.deltaColourG=0,this.deltaColourB=0,this.deltaColourA=0,this.sharpness=0},RANDM1TO1:function(){return 2*Math.random()-1}},pauseParticles:function(){this._particlesPaused=!0},resumeParticles:function(){this._particlesPaused=!1}})},{"../core/core.js":10}],36:[function(a,b,c){a("../core/core.js").c("Renderable",{_changed:!1,_alpha:1,_visible:!0,_setterRenderable:function(a,b){this[a]!==b&&(this[a]=b,this.trigger("Invalidate"))},properties:{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}},init:function(){},_hideOnUnfreeze:!1,events:{Freeze:function(){this._hideOnUnfreeze=!this._visible,this._visible=!1,this.trigger("Invalidate")},Unfreeze:function(){this._visible=!this._hideOnUnfreeze,this.trigger("Invalidate")}},_invalidateRenderable:function(){!1===this._changed&&(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":10}],37:[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(void 0===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(void 0===c||null===c)throw"No reel is specified, and there is no currently active reel.";return this.pauseAnimation(),void 0===b&&(b="number"==typeof a?a:1),c.easing.reset(),this.loops(b),this._setFrame(0),this.bind("UpdateFrame",this._animationTick),this._isPlaying=!0,this.trigger("StartAnimation",c),this},resumeAnimation:function(){return!1===this._isPlaying&&null!==this._currentReel&&(this.bind("UpdateFrame",this._animationTick),this._isPlaying=!0,this._currentReel.easing.resume(),this.trigger("StartAnimation",this._currentReel)),this},pauseAnimation:function(){return!0===this._isPlaying&&(this.unbind("UpdateFrame",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},reelFrame:function(a){if(null===this._currentReel)throw"No active reel.";var b=this._currentReel.frames.indexOf(a);if(-1===b)throw"Frame name "+a+" is invalid.";return this.reelPosition(b),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),!0===b.easing.complete&&(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];"string"==typeof b?this.sprite(b):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":10}],38:[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=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],void 0!==c&&void 0!==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":10}],39:[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()}},SetStyle:function(a){switch(a){case"textAlign":this._textAlign=this._element.style.textAlign;break;case"color":this.textColor(this._element.style.color);break;case"fontType":this._textFont.type=this._element.style.fontType;break;case"fontWeight":this._textFont.weight=this._element.style.fontWeight;break;case"fontSize":this._textFont.size=this._element.style.fontSize;break;case"fontFamily":this._textFont.family=this._element.style.fontFamily;break;case"fontVariant":this._textFont.variant=this._element.style.fontVariant;break;case"lineHeight":this._textFont.lineHeight=this._element.style.lineHeight}}},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,b){return void 0===a||null===a?this._text:("function"==typeof a?(this._text=a.call(this,b),this._textGenerator=a):(this._text=a,this._textGenerator=null),this.has("Canvas")&&this._resizeForCanvas(),this.trigger("Invalidate"),this)},_dynamicTextOn:!1,_textUpdateEvent:null,_dynamicTextUpdate:function(a){this._textGenerator&&this.text(this._textGenerator,a)},dynamicTextGeneration:function(a,b){return this.unbind(this._textUpdateEvent,this._dynamicTextUpdate),a&&(this._textUpdateEvent=b||"UpdateFrame",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)this._textFont[c]="family"===c?"'"+a[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":10}],40:[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("UpdateFrame",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("UpdateFrame",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=void 0!==g?g:0,f=void 0!==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("UpdateFrame",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){if(!a)return void d.viewport.scale(1);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),d.uniqueBind("UpdateFrame",b)}}(),scale:function(){return function(a){this._scale=a||1,d.trigger("InvalidateViewport"),d.trigger("ViewportScale")}}(),mouselook:function(){function a(a){g||a.target||(d.trigger("StopCamera"),h.x=a.clientX,h.y=a.clientY,g=!0)}function b(a){if(g){i.x=a.clientX-h.x,i.y=a.clientY-h.y,h.x=a.clientX,h.y=a.clientY;var b=d.viewport;b.x+=i.x/b._scale,b.y+=i.y/b._scale,b._clamp()}}function c(a){g&&(g=!1)}var e,f=!1,g=!1,h={x:0,y:0},i={x:0,y:0};return function(g){e=d.s("Mouse"),g&&!f?(e.bind("MouseDown",a),e.bind("MouseMove",b),e.bind("MouseUp",c),f=g):!g&&f&&(e.unbind("MouseDown",a),e.unbind("MouseMove",b),e.unbind("MouseUp",c),f=g)}}(),_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){void 0===c&&void 0===b&&void 0!==a&&"number"!=typeof a&&(c=a,a=window.innerWidth,b=window.innerHeight),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,void 0===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||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(!1),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?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":10}],44:[function(a,b,c){b.exports={_events:{},addEvent:function(a,b,c,d){3===arguments.length&&(d=c,c=b,b=window.document);var e=a[0]||"",f=function(b){d.call(a,b)};this._events[e+b+c+d]||(this._events[e+b+c+d]=f,b.addEventListener(c,f,!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])}}},{}],45:[function(a,b,c){var d=a("../core/core.js");d.s("Keyboard",d.extend.call(d.extend.call(new d.__eventDispatcher,{_evt:{eventName:"",key:0,which:0,originalEvent:null},prepareEvent:function(a){var b=this._evt,c=a.type;return b.eventName="keydown"===c?"KeyDown":"keyup"===c?"KeyUp":c,b.which=null!==a.charCode?a.charCode:a.keyCode,b.key=a.keyCode||a.which,b.originalEvent=a,b},triggerKeyEvent:function(a,b){d.trigger(a,b)},dispatchEvent:function(a){var b=this.prepareEvent(a);this.triggerKey(b.eventName,b)}}),d.__keyboardStateTemplate),{},!1),d.c("Keyboard",{isDown:function(a){return d.s("Keyboard").isKeyDown(a)}})},{"../core/core.js":10}],46:[function(a,b,c){var d=a("../core/core.js"),e=window.document,f=void 0!==e.onwheel?"wheel":void 0!==e.onmousewheel?"mousewheel":"DOMMouseScroll";d._preBind("Load",function(){d.addEvent(this,e.body,"mouseup",d.detectBlur),d.addEvent(d.s("Keyboard"),window,"blur",d.s("Keyboard").resetKeyDown),d.addEvent(d.s("Mouse"),window,"mouseup",d.s("Mouse").resetButtonDown),d.addEvent(d.s("Touch"),window,"touchend",d.s("Touch").resetTouchPoints),d.addEvent(d.s("Touch"),window,"touchcancel",d.s("Touch").resetTouchPoints),d.addEvent(d.s("Keyboard"),"keydown",d.s("Keyboard").processEvent),d.addEvent(d.s("Keyboard"),"keyup",d.s("Keyboard").processEvent),d.addEvent(d.s("Mouse"),d.stage.elem,"mousedown",d.s("Mouse").processEvent),d.addEvent(d.s("Mouse"),d.stage.elem,"mouseup",d.s("Mouse").processEvent),d.addEvent(d.s("Mouse"),d.stage.elem,"mousemove",d.s("Mouse").processEvent),d.addEvent(d.s("Mouse"),d.stage.elem,"click",d.s("Mouse").processEvent),d.addEvent(d.s("Mouse"),d.stage.elem,"dblclick",d.s("Mouse").processEvent),d.addEvent(this,d.stage.elem,"touchstart",this._touchDispatch),d.addEvent(this,d.stage.elem,"touchmove",this._touchDispatch),d.addEvent(this,d.stage.elem,"touchend",this._touchDispatch),d.addEvent(this,d.stage.elem,"touchcancel",this._touchDispatch),d.addEvent(this,d.stage.elem,"touchleave",this._touchDispatch),d.addEvent(d.s("MouseWheel"),d.stage.elem,f,d.s("MouseWheel").processEvent)}),d.bind("Pause",function(){d.s("Keyboard").resetKeyDown(),d.s("Mouse").resetButtonDown()}),d._preBind("CraftyStop",function(){d.s("Keyboard").resetKeyDown(),d.s("Mouse").resetButtonDown()}),d._preBind("CraftyStop",function(){d.removeEvent(this,e.body,"mouseup",d.detectBlur),d.removeEvent(d.s("Keyboard"),window,"blur",d.s("Keyboard").resetKeyDown),d.removeEvent(d.s("Mouse"),window,"mouseup",d.s("Mouse").resetButtonDown),d.removeEvent(d.s("Touch"),window,"touchend",d.s("Touch").resetTouchPoints),d.removeEvent(d.s("Touch"),window,"touchcancel",d.s("Touch").resetTouchPoints),d.removeEvent(d.s("Keyboard"),"keydown",d.s("Keyboard").processEvent),d.removeEvent(d.s("Keyboard"),"keyup",d.s("Keyboard").processEvent),d.stage&&(d.removeEvent(d.s("Mouse"),d.stage.elem,"mousedown",d.s("Mouse").processEvent),d.removeEvent(d.s("Mouse"),d.stage.elem,"mouseup",d.s("Mouse").processEvent),d.removeEvent(d.s("Mouse"),d.stage.elem,"mousemove",d.s("Mouse").processEvent),d.removeEvent(d.s("Mouse"),d.stage.elem,"click",d.s("Mouse").processEvent),d.removeEvent(d.s("Mouse"),d.stage.elem,"dblclick",d.s("Mouse").processEvent),d.removeEvent(this,d.stage.elem,"touchstart",this._touchDispatch),d.removeEvent(this,d.stage.elem,"touchmove",this._touchDispatch),d.removeEvent(this,d.stage.elem,"touchend",this._touchDispatch),d.removeEvent(this,d.stage.elem,"touchcancel",this._touchDispatch),d.removeEvent(this,d.stage.elem,"touchleave",this._touchDispatch),d.removeEvent(d.s("MouseWheel"),d.stage.elem,f,d.s("MouseWheel").processEvent))})},{"../core/core.js":10}],47:[function(a,b,c){var d=a("../core/core.js");d.s("MouseWheel",d.extend.call(new d.__eventDispatcher,{_evt:{eventName:"",direction:0,target:null,clientX:0,clientY:0,realX:0,realY:0,originalEvent:null},_mouseSystem:null,prepareEvent:function(a){var b=this._mouseSystem;b||(this._mouseSystem=b=d.s("Mouse"));var c=this._evt;return c.eventName="MouseWheelScroll",c.direction=a.detail<0||a.wheelDelta>0||a.deltaY<0?1:-1,c.clientX=void 0!==a.clientX?a.clientX:b.lastMouseEvent.clientX,c.clientY=void 0!==a.clientY?a.clientY:b.lastMouseEvent.clientY,d.translatePointerEventCoordinates(a,c),c.target=b.mouseObjs?d.findPointerEventTargetByComponent("Mouse",a):null,c.originalEvent=a,c},dispatchEvent:function(a){var b=this.prepareEvent(a);d.trigger("MouseWheelScroll",b)}}),{},!1),d.s("Mouse",d.extend.call(d.extend.call(new d.__eventDispatcher,{normedEventNames:{mousedown:"MouseDown",mouseup:"MouseUp",dblclick:"DoubleClick",click:"Click",mousemove:"MouseMove"},_evt:{eventName:"",mouseButton:-1,target:null,clientX:0,clientY:0,realX:0,realY:0,originalEvent:null},mouseObjs:0,over:null,prepareEvent:function(a){var b=this._evt,c=a.type;return b.eventName=this.normedEventNames[c]||c,void 0===a.which?b.mouseButton=a.button<2?d.mouseButtons.LEFT:4===a.button?d.mouseButtons.MIDDLE:d.mouseButtons.RIGHT:b.mouseButton=a.which<2?d.mouseButtons.LEFT:2===a.which?d.mouseButtons.MIDDLE:d.mouseButtons.RIGHT,b.clientX=a.clientX,b.clientY=a.clientY,d.translatePointerEventCoordinates(a,b),b.target=this.mouseObjs?d.findPointerEventTargetByComponent("Mouse",a):null,b.originalEvent=a,b},triggerMouseEvent:function(a,b){this.trigger(a,b);var c=this.over,d=b.target;"MouseMove"===a&&c!==d&&(c&&(b.eventName="MouseOut",b.target=c,c.trigger("MouseOut",b),b.eventName="MouseMove",b.target=d),this.over=d,d&&(b.eventName="MouseOver",d.trigger("MouseOver",b),b.eventName="MouseMove")),d&&d.trigger(a,b)},dispatchEvent:function(a){var b=this.prepareEvent(a);this.triggerMouse(b.eventName,b)}}),d.__mouseStateTemplate),{},!1),d.c("Mouse",{required:"AreaMap",init:function(){d.s("Mouse").mouseObjs++},remove:function(){d.s("Mouse").mouseObjs--}}),d.c("MouseDrag",{_dragging:!1,required:"Mouse",events:{MouseDown:"_ondown"},init:function(){this._ondown=this._ondown.bind(this),this._ondrag=this._ondrag.bind(this),this._onup=this._onup.bind(this)},_ondown:function(a){a.mouseButton===d.mouseButtons.LEFT&&this.startDrag(a)},_ondrag:function(a){if(!this._dragging||0===a.realX||0===a.realY)return!1;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.s("Mouse").bind("MouseMove",this._ondrag),d.s("Mouse").bind("MouseUp",this._onup),this.trigger("StartDrag",a||d.s("Mouse").lastMouseEvent),this},stopDrag:function(a){if(this._dragging)return this._dragging=!1,d.s("Mouse").unbind("MouseMove",this._ondrag),d.s("Mouse").unbind("MouseUp",this._onup),this.trigger("StopDrag",a||d.s("Mouse").lastMouseEvent),this}})},{"../core/core.js":10}],48:[function(a,b,c){var d=a("../core/core.js");d.extend({findPointerEventTargetByComponent:function(a,b,c){var e=b.target||b.srcElement||d.stage.elem;c=void 0!==c?c:b.clientY,b=void 0!==b.clientX?b.clientX:b;var f,g,h,i,j,k=null,l=-1/0;if("CANVAS"!==e.nodeName){for(;"string"!=typeof e.id&&-1===e.id.indexOf("ent");)e=e.parentNode;var m=d(parseInt(e.id.replace("ent",""),10));j=d.domHelper.translate(b,c,m._drawLayer),m.__c[a]&&m.isAt(j.x,j.y)&&(k=m)}if(!k)for(var n in d._drawLayers){var o=d._drawLayers[n];if(!(o._pointerEntities<=0))for(j=d.domHelper.translate(b,c,o),g=d.map.unfilteredSearch({_x:j.x,_y:j.y,_w:1,_h:1}),i=0,h=g.length;il&&f.__c[a]&&f.isAt(j.x,j.y)&&(l=f._globalZ,k=f)}return k},translatePointerEventCoordinates:function(a,b){b=b||a;var c=d.domHelper.translate(a.clientX,a.clientY,void 0,this.__pointerPos);b.realX=c.x,b.realY=c.y},__pointerPos:{x:0,y:0}}),d.c("AreaMap",{init:function(){this.has("Renderable")&&this._drawLayer&&this._drawLayer._pointerEntities++},remove:function(a){!a&&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)}})},{"../core/core.js":10}],49:[function(a,b,c){var d=a("../core/core.js");d.extend({multitouch:function(a){return"boolean"!=typeof a?this._multitouch:(this._multitouch=a,this)},_multitouch:!1,_touchDispatch:function(){function a(a){var b,d;"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?d=a.touches[0]:a.changedTouches&&a.changedTouches.length&&(d=a.changedTouches[0]);var f=document.createEvent("MouseEvent");if(f.initMouseEvent(b,!0,!0,window,1,d.screenX,d.screenY,d.clientX,d.clientY,!1,!1,!1,!1,0,a.relatedTarget),d.target.dispatchEvent(f),"mousedown"===b)c=d.clientX,e=d.clientY;else if("mouseup"===b){var g=d.clientX-c,h=d.clientY-e;g*g+h*h<=256&&(b="click",f=document.createEvent("MouseEvent"),f.initMouseEvent(b,!0,!0,window,1,d.screenX,d.screenY,d.clientX,d.clientY,!1,!1,!1,!1,0,a.relatedTarget),d.target.dispatchEvent(f))}}var b,c=0,e=0;return function(c){d._multitouch?(b||(b=d.s("Touch")),b.processEvent(c)):a(c)}}()}),d.s("Touch",d.extend.call(d.extend.call(new d.__eventDispatcher,{normedEventNames:{touchstart:"TouchStart",touchmove:"TouchMove",touchend:"TouchEnd",touchcancel:"TouchCancel"},_evt:{eventName:"",identifier:-1,target:null,entity:null,realX:0,realY:0,originalEvent:null},touchObjs:0,overs:{},prepareEvent:function(a,b){var c=this._evt;return c.eventName=this.normedEventNames[b]||b,c.identifier=a.identifier,d.translatePointerEventCoordinates(a,c),c.target=this.touchObjs?d.findPointerEventTargetByComponent("Touch",a):null,c.entity=c.target,c},triggerTouchEvent:function(a,b){this.trigger(a,b);var c=b.identifier,d=b.target,e=this.overs[c];e&&("TouchMove"===a&&e!==d||"TouchEnd"===a||"TouchCancel"===a)&&(b.eventName="TouchOut",b.target=e,b.entity=e,e.trigger("TouchOut",b),b.eventName=a,b.target=d,b.entity=d,delete this.overs[c]),d&&d.trigger(a,b),d&&("TouchStart"===a||"TouchMove"===a&&e!==d)&&(b.eventName="TouchOver",d.trigger("TouchOver",b),b.eventName=a,this.overs[c]=d)},dispatchEvent:function(a){for(var b,c=a.changedTouches,d=0,e=c.length;d=112&&a.key<=135))return a.stopPropagation?a.stopPropagation():a.cancelBubble=!0,!(!a.target||"INPUT"!==a.target.nodeName&&"TEXTAREA"!==a.target.nodeName)||(a.preventDefault?a.preventDefault():a.returnValue=!1,!1)}},d.extend({selected:!0,detectBlur:function(a){var b=a.clientX>d.stage.x&&a.clientXd.stage.y&&a.clientY0&&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":10}],53:[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]),this.supported[c]=""!==a&&"no"!==a}},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._cascadeRotation(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+h*j,p=-e*j+h*i,q=d*i+h*j,r=-d*j+h*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;0!==b&&(this._rotation=a,this._calculateMBR(),this.trigger("Rotate",b))},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||b)&&this._setPosition(this._x+a,this._y+b),c&&(this.w+=c),d&&(this.h+=d),this},_cascade:function(a){if(a)for(var b,c=0,d=this._children,e=d.length,f=this._x-a._x,g=this._y-a._y,h=this._w-a._w,i=this._h-a._h;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":10}],55:[function(a,b,c){var d=a("../core/core.js"),e=Math.PI/180;d.extend({raycast:function(a,b){for(var c,e,f="obj",g=1/0,h=!0,i=2,j=arguments.length;ig)return!0;if(c.map&&c.__c[f]&&!o[c[0]]){o[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(this.rotation,this._origin.x,this._origin.y,Math.cos(-this.rotation*e),Math.sin(-this.rotation*e)),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},_collisionHitDupes:[],_collisionHitResults:[],hit:function(a,b){var c=this._cbr||this._mbr||this,e=this._collisionHitResults;e.length=0,e=d.map.unfilteredSearch(c,e);var f=e.length;if(!f)return null;var g,h,i=0,j=this._collisionHitDupes;for(b=b||[],j.length=0;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,nx:t,ny:u}}})},{"../core/core.js":10}],56:[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":10}],57:[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("UpdateFrame",this._angularMotionTick)},remove:function(a){this.unbind("UpdateFrame",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.__oldDirection={x:0,y:0},this.bind("UpdateFrame",this._linearMotionTick)},remove:function(a){this.unbind("UpdateFrame",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._vx,d=this._ax,e=this._vy,f=this._ay,g=c*b+.5*d*b*b,h=e*b+.5*f*b*b;this.vx=c+d*b,this.vy=e+f*b;var i=this.__oldDirection,j=this._vx,k=j?j<0?-1:1:0,l=this._vy,m=l?l<0?-1:1:0;i.x===k&&i.y===m||(i.x=k,i.y=m,this.trigger("NewDirection",i)),this._dx=g,this._dy=h,this._setPosition(this._x+g,this._y+h)}})},{"../core/core.js":10}],58:[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("UpdateFrame",this._detectGroundTick)},startGroundDetection:function(a){return a&&(this._groundComp=a),this.uniqueBind("UpdateFrame",this._detectGroundTick),this},stopGroundDetection:function(){return this.unbind("UpdateFrame",this._detectGroundTick),this},preventGroundTunneling:function(a){return void 0===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.unfilteredSearch(a),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._x=Math.floor(a._x),a._y=Math.floor(a._y),a._w=Math.ceil(a._w),a._h=Math.ceil(a._h),a},mergeSet:function(a){if(a.length<2)return a;for(var b=a.length-1;b--;)this.overlap(a[b],a[b+1])&&(this.merge(a[b],a[b+1],a[b]),a.splice(b+1,1));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":10}],60:[function(a,b,c){function d(a,b,c){this.keys=a,this.map=c,this.obj=b}var e,f={},g=function(a){e=a||64,this.map={},this.boundsDirty=!1,this.coordBoundsDirty=!1,this.boundsHash={maxX:-1/0,maxY:-1/0,minX:1/0,minY:1/0},this.boundsCoords={maxX:-1/0,maxY:-1/0,minX:1/0,minY:1/0}};g.key=function(a,b){return a=a._cbr||a._mbr||a,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},g.hash=function(a){return a.x1+" "+a.y1+" "+a.x2+" "+a.y2},g.cellsize=function(){return e},g.prototype={insert:function(a,b){var c,e,f,h=g.key(a,b&&b.keys);for(b=b||new d(h,a,this),c=h.x1;c<=h.x2;c++)for(e=h.y1;e<=h.y2;e++)f=c<<16^e,this.map[f]||(this.map[f]=[]),this.map[f].push(a);return this.boundsDirty=!0,b},_searchHolder:[],search:function(a,b){var c,d,e,h,i=g.key(a,f),j=this._searchHolder;b=b||[],j.length=0;var k;for(c=i.x1;c<=i.x2;c++)for(d=i.y1;d<=i.y2;d++)if(h=this.map[c<<16^d])for(e=0;ea._x&&k._ya._y&&b.push(h[e]));return b},unfilteredSearch:function(a,b){var c,d,e,h,i=g.key(a,f);for(b=b||[],c=i.x1;c<=i.x2;c++)for(d=i.y1;d<=i.y2;d++)if(h=this.map[c<<16^d])for(e=0;e>16,h=f<<16>>16;if(h<0&&(g=~g),g>=a.maxX){a.maxX=g;for(c in e)"object"==typeof(d=e[c])&&"requires"in d&&(b.maxX=Math.max(b.maxX,d.x+d.w))}if(g<=a.minX){a.minX=g;for(c in e)"object"==typeof(d=e[c])&&"requires"in d&&(b.minX=Math.min(b.minX,d.x))}if(h>=a.maxY){a.maxY=h;for(c in e)"object"==typeof(d=e[c])&&"requires"in d&&(b.maxY=Math.max(b.maxY,d.y+d.h))}if(h<=a.minY){a.minY=h;for(c in e)"object"==typeof(d=e[c])&&"requires"in d&&(b.minY=Math.min(b.minY,d.y))}}this.boundsDirty=!1,this.coordBoundsDirty=!1}},traverseRay:function(a,b,c){var d=b.x,h=b.y;a={_x:a._x,_y:a._y,_w:0,_h:0};var i,j=this._keyBoundaries(),k=g.key(a,f),l=k.x1,m=k.y1,n=j.minX,o=j.minY,p=j.maxX,q=j.maxY,r=d>0?1:d<0?-1:0,s=h>0?1:h<0?-1:0,t=d>=0?(l+1)*e:l*e,u=h>=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!==h&&(i=1/h,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; + x /= m; + y /= m; } } + + dpad.x = x; + dpad.y = y; }, updateTriggerInput: function (trigger) { @@ -572,7 +628,7 @@ Crafty.s("Controls", { if (winner) winner.active = true; } }); -},{"../core/core.js":9}],4:[function(require,module,exports){ +},{"../core/core.js":10}],4:[function(require,module,exports){ var Crafty = require('../core/core.js'); /**@ @@ -590,15 +646,10 @@ Crafty.c("Draggable", { _oldY: null, _dir: null, - init: function () { - this.requires("MouseDrag"); - this.bind("StartDrag", this._startDrag) - .bind("Dragging", this._drag); - }, - - remove: function() { - this.unbind("StartDrag", this._startDrag) - .unbind("Dragging", this._drag); + required: "MouseDrag", + events: { + "StartDrag": "_startDrag", + "Dragging": "_drag" }, /**@ @@ -771,7 +822,7 @@ Crafty.c("Controllable", { * @example * ~~~~ * // Create a trigger bound to the `b` key - * Crafty.s("Controls").defineTriggerInput("BlushTrigger", {keys:['b']}); + * Crafty.s("Controls").defineTriggerGroup("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") @@ -782,6 +833,7 @@ Crafty.c("Controllable", { */ linkInput: function(event, name, fn) { this._inputBindings[event][name] = fn; + return this; }, /**@ @@ -799,6 +851,7 @@ Crafty.c("Controllable", { */ unlinkInput: function(event, name) { delete this._inputBindings[event][name]; + return this; }, @@ -875,10 +928,12 @@ Crafty.c("Multiway", { remove: function() { if (!this.disableControls) this.vx = this.vy = 0; + this.unlinkInput("DirectionalInput", this._dpadName); + Crafty.s("Controls").destroyDpad(this._dpadName); }, events: { - "EnterFrame": function() { + "UpdateFrame": function() { if (!this.disableControls) { if (typeof this._speed.x !== 'undefined' && this._speed.x !== null){ this.vx = this._speed.x * this._direction.x; @@ -1019,10 +1074,9 @@ Crafty.c("Jumper", { this.requires("Supportable, Motion, Controllable"); }, - - remove: function() { this.unlinkInput("TriggerInputDown", this._jumpTriggerName); + Crafty.s("Controls").destroyTriggerGroup(this._jumpTriggerName); }, _keydown_jumper: function (e) { @@ -1135,6 +1189,7 @@ Crafty.c("Jumper", { * It is a well suited for games with a top-down (birds-eye) perspective. * * @see Multiway + * @see Motion */ Crafty.c("Fourway", { @@ -1147,14 +1202,24 @@ Crafty.c("Fourway", { * @comp Fourway * @kind Method * - * @sign public this .fourway([Number speed]) + * @sign public this .fourway([Number speed[, Object options]]) * @param speed - The speed of motion in pixels per second. + * @param options - A dictionary of options passed through to the underlying Multiway component * - * 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. + * Initialize the component with the given speed and options. See the Multiway component for available options. + * + * @example + * ~~~ + * Crafty.e("2D, Color, Fourway") + * .attr({x: 100, y: 100, w: 50, h:50}) + * .color("green") + * .fourway(100, {normalize:true}); + * ~~~ + * Create a green square controlled by the arrow keys and WASD, with diagonal movement normalized to the given speed. + * + * The speed is in units of pixels per second. */ - fourway: function (speed) { + fourway: function (speed, options) { this.multiway(speed || this._speed, { UP_ARROW: -90, DOWN_ARROW: 90, @@ -1166,7 +1231,7 @@ Crafty.c("Fourway", { A: 180, Z: -90, Q: 180 - }); + }, options); return this; } @@ -1227,2692 +1292,2144 @@ Crafty.c("Twoway", { } }); -},{"../core/core.js":9}],5:[function(require,module,exports){ +},{"../core/core.js":10}],5:[function(require,module,exports){ var Crafty = require('../core/core.js'); +/**@ + * #KeyboardState + * @category Input + * @kind Component + * + * Handles valid key related events and key states for the entity. + * @note This is an internally used component, automatically included in the `KeyboardSystem`. + * + * @trigger KeyDown - when a key is pressed - KeyboardEvent + * @trigger KeyUp - when a key is released - KeyboardEvent + * + * The standard Crafty `KeyboardEvent` object: + * ~~~ + * // event name of key event + * e.eventName + * + * // Normalized keyCode number according to `Crafty.keys` + * e.key + * + * // Original keyboard event, containing additional native properties + * e.originalEvent + * ~~~ + * + * In addition to binding to these events, the current state (pressed/released) of a key can also be queried using the `.isKeyDown` method. + * + * @see Keyboard, KeyboardSystem + * @see Crafty.keys + */ +Crafty.__keyboardStateTemplate = { + _keyDown: null, + + init: function() { + this._keyDown = {}; + // use custom trigger method if specified + this.triggerKeyEvent = this.triggerKeyEvent || this.trigger; + }, -Crafty.extend({ /**@ - * #Crafty.device - * @category Misc - * @kind Property + * #.isKeyDown + * @comp KeyboardState + * @kind Method * - * Methods relating to devices such as tablets or phones + * @sign public Boolean isKeyDown(String keyName) + * @param keyName - Name of the key to check. See `Crafty.keys`. + * @returns The pressed state of the key + * + * @sign public Boolean isKeyDown(Number keyCode) + * @param keyCode - Key code in `Crafty.keys`. + * @returns The pressed state of the key + * + * Determine if a certain key is currently down. + * + * @example + * ~~~ + * ent.bind('UpdateFrame', function() { + * if (Crafty.s('Keyboard').isKeyDown('SPACE')) + * this.y--; + * }); + * ~~~ + * + * @see .resetKeyDown + * @see Crafty.keys */ - device: { - _deviceOrientationCallback: false, - _deviceMotionCallback: false, + isKeyDown: function (key) { + if (typeof key === "string") { + key = Crafty.keys[key]; + } + return !!this._keyDown[key]; + }, - /** - * The HTML5 DeviceOrientation event returns three pieces of data: - * * alpha the direction the device is facing according to the compass - * * beta the angle in degrees the device is tilted front-to-back - * * gamma the angle in degrees the device is tilted left-to-right. - * * The angles values increase as you tilt the device to the right or towards you. - * - * Since Firefox uses the MozOrientationEvent which returns similar data but - * using different parameters and a different measurement system, we want to - * normalize that before we pass it to our _deviceOrientationCallback function. - * - * @param eventData HTML5 DeviceOrientation event - */ - _normalizeDeviceOrientation: function (eventData) { - var data; - if (window.DeviceOrientationEvent) { - data = { - // gamma is the left-to-right tilt in degrees, where right is positive - 'tiltLR': eventData.gamma, - // beta is the front-to-back tilt in degrees, where front is positive - 'tiltFB': eventData.beta, - // alpha is the compass direction the device is facing in degrees - 'dir': eventData.alpha, - // deviceorientation does not provide this data - 'motUD': null - }; - } else if (window.OrientationEvent) { - data = { - // x is the left-to-right tilt from -1 to +1, so we need to convert to degrees - 'tiltLR': eventData.x * 90, - // y is the front-to-back tilt from -1 to +1, so we need to convert to degrees - // We also need to invert the value so tilting the device towards us (forward) - // results in a positive value. - 'tiltFB': eventData.y * -90, - // MozOrientation does not provide this data - 'dir': null, - // z is the vertical acceleration of the device - 'motUD': eventData.z - }; + /**@ + * #.resetKeyDown + * @comp KeyboardState + * @kind Method + * + * @sign public this .resetKeyDown() + * + * Reset all currently pressed keys. Triggers appropriate "KeyUp" events. + * + * This method is called internally, but may be useful when running Crafty in headless mode. + * + * @see .isKeyDown + * @see Crafty.keys + */ + resetKeyDown: function () { + var evt = { key: -1, eventName: "KeyUp" }; + + // Tell all the keys they're no longer held down + var keyDown = this._keyDown; + for (var k in keyDown) { + if (keyDown[k] === true) { + evt.key = +k; // convert k propertyString to number! + this.triggerKey("KeyUp", evt); } + } - Crafty.device._deviceOrientationCallback(data); - }, + return this; + }, - /** - * @param eventData HTML5 DeviceMotion event - */ - _normalizeDeviceMotion: function (eventData) { - var acceleration = eventData.accelerationIncludingGravity, - facingUp = (acceleration.z > 0) ? +1 : -1; + /**@ + * #.triggerKey + * @comp KeyboardState + * @kind Method + * + * @sign public this triggerKey(String eventName, Object eventData) + * @param eventName - Name of the key event to trigger ("KeyDown" or "KeyUp") + * @param eventData - The key event to trigger + * + * Try to trigger a key event on this entity and persist the key state. + * This method prevents inconsistent key state. + * e.g. If this entity didn't receive a "KeyDown" previously, it won't fire a "KeyUp" event. + * + * This method is called internally, but may be useful when running Crafty in headless mode. + * + * @example + * ~~~ + * var wasTriggered = false; + * + * ent.requires('KeyboardState') + * .bind('KeyUp', function(evt) { + * wasTriggered = true; + * }) + * .triggerKey('KeyUp', { key: Crafty.keys.RIGHT_ARROW }); + * + * Crafty.log(wasTriggered); // prints false + * ~~~ + * + * @see Crafty.keys + */ + triggerKey: function (eventName, eventData) { + // trigger event only if valid state + var key = eventData.key; + if (eventName === "KeyDown") { + // ignore KeyDown due to inconsistent state caused by loosing focus + if (this._keyDown[key] !== true) { + this._keyDown[key] = true; + this.triggerKeyEvent(eventName, eventData); + } + } else if (eventName === "KeyUp") { + // ignore KeyUp due to inconsistent state caused by loosing focus + if (this._keyDown[key] === true) { + this._keyDown[key] = false; + this.triggerKeyEvent(eventName, eventData); + } + } else { + // trigger the event otherwise + this.triggerKeyEvent(eventName, eventData); + } - var data = { - // Grab the acceleration including gravity from the results - 'acceleration': acceleration, - 'rawAcceleration': "[" + Math.round(acceleration.x) + ", " + Math.round(acceleration.y) + ", " + Math.round(acceleration.z) + "]", - // Z is the acceleration in the Z axis, and if the device is facing up or down - 'facingUp': facingUp, - // Convert the value from acceleration to degrees acceleration.x|y is the - // acceleration according to gravity, we'll assume we're on Earth and divide - // by 9.81 (earth gravity) to get a percentage value, and then multiply that - // by 90 to convert to degrees. - 'tiltLR': Math.round(((acceleration.x) / 9.81) * -90), - 'tiltFB': Math.round(((acceleration.y + 9.81) / 9.81) * 90 * facingUp) - }; + return this; + } +}; +Crafty.c("KeyboardState", Crafty.__keyboardStateTemplate); + +// define a basic Keyboard system for headless mode +// will be substituted with proper one in browser mode +Crafty.s("Keyboard", Crafty.extend.call({ + // this method will be called by KeyboardState iff triggerKey event was valid + triggerKeyEvent: function (eventName, e) { + Crafty.trigger(eventName, e); + } +}, Crafty.__keyboardStateTemplate), {}, false); - Crafty.device._deviceMotionCallback(data); - }, +},{"../core/core.js":10}],6:[function(require,module,exports){ +var Crafty = require('../core/core.js'); - /**@ - * #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 - * - * Do something with normalized device orientation data: - * ~~~ - * { - * tiltLR : 'gamma -- the angle in degrees the device is tilted left-to-right.', - * tiltFB : 'beta -- the angle in degrees the device is tilted front-to-back', - * dir : 'alpha -- the direction the device is facing according to the compass', - * motUD : 'The angle's values increase as you tilt the device to the right or towards you.' - * } - * ~~~ - * - * @example - * ~~~ - * // Get DeviceOrientation event normalized data. - * Crafty.device.deviceOrientation(function(data){ - * Crafty.log('data.tiltLR : '+Math.round(data.tiltLR)+', data.tiltFB : '+Math.round(data.tiltFB)+', data.dir : '+Math.round(data.dir)+', data.motUD : '+data.motUD+''); - * }); - * ~~~ - * - * See browser support at http://caniuse.com/#search=device orientation. - */ - deviceOrientation: function (func) { - this._deviceOrientationCallback = func; - if (Crafty.support.deviceorientation) { - if (window.DeviceOrientationEvent) { - // Listen for the deviceorientation event and handle DeviceOrientationEvent object - Crafty.addEvent(this, window, 'deviceorientation', this._normalizeDeviceOrientation); - } else if (window.OrientationEvent) { - // Listen for the MozOrientation event and handle OrientationData object - Crafty.addEvent(this, window, 'MozOrientation', this._normalizeDeviceOrientation); - } - } - }, - - /**@ - * #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 - * - * Do something with normalized device motion data: - * ~~~ - * { - * acceleration : 'Grab the acceleration including gravity from the results', - * rawAcceleration : 'Display the raw acceleration data', - * facingUp : 'Z is the acceleration in the Z axis, and if the device is facing up or down', - * tiltLR : 'Convert the value from acceleration to degrees. acceleration.x is the acceleration according to gravity, we'll assume we're on Earth and divide by 9.81 (earth gravity) to get a percentage value, and then multiply that by 90 to convert to degrees.', - * tiltFB : 'Convert the value from acceleration to degrees.' - * } - * ~~~ - * - * @example - * ~~~ - * // Get DeviceMotion event normalized data. - * Crafty.device.deviceMotion(function(data){ - * Crafty.log('data.moAccel : '+data.rawAcceleration+', data.moCalcTiltLR : '+Math.round(data.tiltLR)+', data.moCalcTiltFB : '+Math.round(data.tiltFB)+''); - * }); - * ~~~ - * - * See browser support at http://caniuse.com/#search=motion. - */ - deviceMotion: function (func) { - this._deviceMotionCallback = func; - if (Crafty.support.devicemotion) { - if (window.DeviceMotionEvent) { - // Listen for the devicemotion event and handle DeviceMotionEvent object - Crafty.addEvent(this, window, 'devicemotion', this._normalizeDeviceMotion); - } - } - } - } -}); - -},{"../core/core.js":9}],6:[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: {}, - touchObjs: 0, - /**@ - * #Crafty.lastEvent + * #Crafty.keys * @category Input * @kind Property - * Check which mouse event occured most recently (useful for determining mouse position in every frame). + * + * Object of key names and the corresponding Unicode key code. * - * 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 + * 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 * ~~~ - * @see Mouse, Crafty.mouseButtons, Crafty.mouseDispatch */ + 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 + + }, - lastEvent: null, /**@ - * #Crafty.keydown + * #Crafty.mouseButtons * @category Input * @kind Property - * Check which keys (referred by `Crafty.keys` key codes) are currently down. + * + * 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: * - * @example * ~~~ - * // is "Shift" currently pressed? - * var shiftDown = !!Crafty.keydown[Crafty.keys.SHIFT]; + * LEFT: 0, + * MIDDLE: 1, + * RIGHT: 2 * ~~~ - * @see Keyboard, Crafty.keys, Crafty.keyboardDispatch */ - keydown: {}, + mouseButtons: { + LEFT: 0, + MIDDLE: 1, + RIGHT: 2 + } +}); +},{"../core/core.js":10}],7:[function(require,module,exports){ +var Crafty = require('../core/core.js'); + +/**@ + * #MouseState + * @category Input + * @kind Component + * + * Handles valid mouse related events and button states for the entity. + * @note This is an internally used component, automatically included in the `MouseSystem`. + * + * @trigger MouseDown - when a mouse button is pressed - MouseEvent + * @trigger MouseMove - when the mouse moves - MouseEvent + * @trigger MouseUp - when a mouse button is released - MouseEvent + * + * The standard Crafty `MouseEvent` object: + * ~~~ + * // event name of mouse event + * e.eventName + * + * // Normalized mouse button according to Crafty.mouseButtons: + * // Crafty.mouseButtons.LEFT, Crafty.mouseButtons.RIGHT or Crafty.mouseButtons.MIDDLE + * e.mouseButton + * + * // the closest (visible & Mouse-enhanced) entity to the source of the event (if available), otherwise null + * e.target + * + * // (x,y) coordinates of mouse event in world (default viewport) space + * e.realX + * e.realY + * + * // Original mouse event, containing additional native properties + * e.originalEvent + * ~~~ + * + * In addition to binding to these events, the current state (pressed/released) of a mouse button can also be queried using the `.isButtonDown` method. + * + * @see Mouse, MouseSystem + * @see Crafty.mouseButtons + */ +Crafty.__mouseStateTemplate = { + _buttonDown: null, /**@ - * #Crafty.selected - * @category Input + * #.lastMouseEvent + * @comp MouseState * @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`. + * Check which read-only mouse event occured most recently (useful for determining mouse position in every frame). * - * @see Crafty.stage#Crafty.stage.elem + * @see Mouse, MouseSystem, Crafty.mouseButtons */ - selected: false, - - detectBlur: function (e) { - var selected = ((e.clientX > Crafty.stage.x && e.clientX < Crafty.stage.x + Crafty.viewport.width) && - (e.clientY > Crafty.stage.y && e.clientY < Crafty.stage.y + Crafty.viewport.height)); - - if (!Crafty.selected && selected) { - Crafty.trigger("CraftyFocus"); - } - - if (Crafty.selected && !selected) { - Crafty.trigger("CraftyBlur"); - } + lastMouseEvent: null, - Crafty.selected = selected; + init: function() { + this._buttonDown = {}; + // use custom trigger method if specified + this.triggerMouseEvent = this.triggerMouseEvent || this.trigger; + this.lastMouseEvent = { + eventName: '', + mouseButton: -1, + target: null, + realX: 0, + realY: 0, + clientX: 0, // DEPRECATED: remove in upcoming release + clientY: 0, // DEPRECATED: remove in upcoming release + originalEvent: null + }; }, /**@ - * #Crafty.multitouch - * @category Input + * #.isButtonDown + * @comp MouseState * @kind Method - * @sign public this .multitouch(Boolean bool) - * @param bool - Turns multitouch on and off. The initial state is off (false). * - * @sign public Boolean .multitouch() - * @returns Whether multitouch is currently enabled; + * @sign public Boolean .isButtonDown(String mouseButtonName) + * @param mouseButtonName - Name of the button to check. See `Crafty.mouseButtons`. + * @returns The pressed state of the button * - * Enables/disables support for multitouch feature. - * - * If this is set to true, it is expected that your entities have the Touch component instead of Mouse component. - * If false (default), then only entities with the Mouse component will respond to touch. + * @sign public Boolean .isButtonDown(Number buttonId) + * @param buttonId - ButtonId in `Crafty.mouseButtons`. + * @returns The pressed state of the button + * + * Determine if a certain mouse button is currently down. * - * If no boolean is passed to the function call, it will just return whether multitouch is on or not. - * - * @note The Touch component (and thus the multitouch feature) is currently incompatible with the Draggable component. - * * @example * ~~~ - * Crafty.multitouch(true); - * - * var myEntity1 = Crafty.e('2D, Canvas, Color, Touch') - * .attr({x: 100, y: 100, w:200, h:200, z:1 }) - * .color('black') - * .bind('TouchStart',function(e){ alert('big black box was touched', e); }), - * myEntity2 = Crafty.e('2D, Canvas, Color, Touch') - * .attr({x: 40, y: 150, w:90, h:300, z:2 }) - * .color('green') - * .bind('TouchStart',function(e){ alert('big GREEN box was touched', e); }); - * - * Crafty.log("multitouch is "+Crafty.multitouch()); + * ent.bind('UpdateFrame', function() { + * if (Crafty.s('Mouse').isButtonDown('LEFT')) + * this.y--; + * }); * ~~~ - * @see Crafty.touchDispatch - * @see Touch + * + * @see .resetButtonDown + * @see Crafty.mouseButtons */ - multitouch: function (bool) { - if (typeof bool !== "boolean") return this._touchHandler.multitouch; - this._touchHandler.multitouch = bool; + isButtonDown: function (button) { + if (typeof button === "string") { + button = Crafty.mouseButtons[button]; + } + return !!this._buttonDown[button]; }, - 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] - }); + /**@ + * #.resetButtonDown + * @comp MouseState + * @kind Method + * + * @sign public this .resetButtonDown() + * + * Reset all currently pressed buttons. Triggers appropriate "MouseUp" events. + * + * This method is called internally, but may be useful when running Crafty in headless mode. + * + * @see .isButtonDown + * @see Crafty.mouseButtons + */ + resetButtonDown: function () { + var lastEvent = this.lastMouseEvent; + + // Tell all buttons they're no longer held down + var buttonsDown = this._buttonDown; + for (var button in buttonsDown) { + if (buttonsDown[button] === true) { + lastEvent.mouseButton = +button; // convert button propertyString to number! + lastEvent.eventName = "MouseUp"; + this.triggerMouse("MouseUp", lastEvent); } } - Crafty.keydown = {}; + return this; }, /**@ - * #Crafty.mouseDispatch - * @category Input - * @private + * #.triggerMouse + * @comp MouseState * @kind Method * - * Internal method which dispatches mouse events received by Crafty. + * @sign public this triggerMouse(String eventName, Object eventData) + * @param eventName - Name of the mouse event to trigger ("MouseDown", "MouseUp", "MouseMove", ...) + * @param eventData - The mouse event to trigger + * + * Try to trigger a mouse event on this entity and persist the button state. + * This method prevents inconsistent button state. + * e.g. If this entity didn't receive a "MouseDown" previously, it won't fire a "MouseUp" event. + * + * This method is called internally, but may be useful when running Crafty in headless mode. * - * 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). + * @example + * ~~~ + * var wasTriggered = false; * - * This method also updates `Crafty.lastEvent`. + * ent.requires('MouseState') + * .bind('MouseUp', function(evt) { + * wasTriggered = true; + * }) + * .triggerMouse('MouseUp', { mouseButton: Crafty.mouseButtons.LEFT }); + * + * Crafty.log(wasTriggered); // prints false + * ~~~ * - * @see Crafty.mouseButtons, Crafty.lastEvent, Mouse + * @see Crafty.mouseButtons */ - mouseButtonsDown: { }, - mouseDispatch: function (e) { - if (!Crafty.mouseObjs) return; - Crafty.lastEvent = e; - - var tar = e.target ? e.target : e.srcElement, - closest, - pos = Crafty.domHelper.translate(e.clientX, e.clientY), - 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); + triggerMouse: function (eventName, eventData) { + // copy newest event to lastEvent + var lastEvent = this.lastMouseEvent; + lastEvent.eventName = eventName; + lastEvent.mouseButton = eventData.mouseButton; + lastEvent.target = eventData.target; + lastEvent.realX = eventData.realX; + lastEvent.realY = eventData.realY; + lastEvent.clientX = eventData.clientX; // DEPRECATED: remove in upcoming release + lastEvent.clientY = eventData.clientY; // DEPRECATED: remove in upcoming release + lastEvent.originalEvent = eventData.originalEvent; + + // trigger event only if valid state + var mouseButton = eventData.mouseButton; + if (eventName === "MouseDown") { + // ignore MouseDown due to inconsistent state caused by loosing focus + if (this._buttonDown[mouseButton] !== true) { + this._buttonDown[mouseButton] = true; + this.triggerMouseEvent(eventName, eventData); + } + } else if (eventName === "MouseUp") { + // ignore MouseUp due to inconsistent state caused by loosing focus + if (this._buttonDown[mouseButton] === true) { + this._buttonDown[mouseButton] = false; + this.triggerMouseEvent(eventName, eventData); + } } else { - e.mouseButton = (e.which < 2) ? Crafty.mouseButtons.LEFT : ((e.which === 2) ? Crafty.mouseButtons.MIDDLE : Crafty.mouseButtons.RIGHT); + // trigger the event otherwise + this.triggerMouseEvent(eventName, eventData); } - // Set the mouse position based on standard viewport coordinates - Crafty.mousePos.x = pos.x; - Crafty.mousePos.y = pos.y; + return this; + } +}; +Crafty.c("MouseState", Crafty.__mouseStateTemplate); + +// define a basic Mouse system for headless mode +// will be substituted with proper one in browser mode +Crafty.s("Mouse", Crafty.extend.call({ + // this method will be called by MouseState iff triggerMouse event was valid + triggerMouseEvent: function (eventName, e) { + Crafty.trigger(eventName, e); + } +}, Crafty.__mouseStateTemplate), {}, false); - // Track button state - if (type === "mousedown") { - this.mouseButtonsDown[e.mouseButton] = true; - } - if (type === "mouseup") { - delete this.mouseButtonsDown[e.mouseButton]; - } +},{"../core/core.js":10}],8:[function(require,module,exports){ +var Crafty = require('../core/core.js'); - closest = Crafty.findPointerEventTargetByComponent("Mouse", e, tar); - //found closest object to mouse - if (closest) { - //click must mousedown and out on tile - if (type === "mousedown") { - closest.trigger("MouseDown", e); - } else if (type === "mouseup") { - closest.trigger("MouseUp", e); - } else if (type === "dblclick") { - closest.trigger("DoubleClick", e); - } 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) { - this.over.trigger("MouseOut", e); //if over wasn't null, send mouseout - this.over = null; - } - this.over = closest; - closest.trigger("MouseOver", e); - } - } else closest.trigger(type, e); //trigger whatever it is - } else { - if (type === "mousemove" && this.over) { - this.over.trigger("MouseOut", e); - this.over = null; - } - if (type === "mousedown") { - Crafty.viewport.mouselook('start', e); - } else if (type === "mousemove") { - Crafty.viewport.mouselook('drag', e); - } else if (type === "mouseup") { - Crafty.viewport.mouselook('stop'); - } +/**@ + * #TouchState + * @category Input + * @kind Component + * + * Handles valid touch related events and touch points for the entity. + * @note This is an internally used component, automatically included in the `TouchSystem`. + * + * @trigger TouchStart - when a finger is pressed - TouchPointEvent + * @trigger TouchMove - when a pressed finger is moved - TouchPointEvent + * @trigger TouchEnd - when a finger is raised - TouchPointEvent + * @trigger TouchCancel - when a touch event has been disrupted in some way - TouchPointEvent + * + * The standard Crafty `TouchPointEvent` object: + * ~~~ + * // event name of touch event + * e.eventName + * + * // identifier for this touch point, unique over the duration the finger is on the touch surface + * e.identifier + * + * // the closest (visible & Touch-enhanced) entity to the source of the event (if available), otherwise null + * e.target + * + * // (x,y) coordinates of the touch point in world (default viewport) space + * e.realX + * e.realY + * + * // Original touch event, containing additional native properties + * e.originalEvent + * ~~~ + * + * In addition to binding to these events, the current touch points can also be queried using the `.touchPoints` property. + * + * @see Touch, TouchSystem + */ +Crafty.__touchStateTemplate = { + /**@ + * #.touchPoints + * @comp TouchState + * @kind Property + * + * Holds data of all currently pressed touch points (useful for determining positions of fingers in every frame). + * Data of a touch point is lost when the respective finger is raised. + * + * @example + * ~~~ + * var touchPoint, touchPoints = Crafty.s('Touch').touchPoints; + * for (var i = 0, l = touchPoints.length; i < l; i++) { + * touchPoint = touchPoints[i]; + * Crafty.log(touchPoint.realX, touchPoint.realY); // logs coordinates in Crafty's world space + * } + * ~~~ + * + * @see .resetTouchPoints + */ + touchPoints: null, - // 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); - } - } + _touchPointsPool: null, + + init: function() { + this.touchPoints = []; + this._touchPointsPool = []; + // use custom trigger method if specified + this.triggerTouchEvent = this.triggerTouchEvent || this.trigger; + }, - if (type === "mousemove") { - this.lastEvent = e; + /**@ + * #.resetTouchPoints + * @comp TouchState + * @kind Method + * + * @sign public this .resetTouchPoints() + * + * Reset all current touch points. Triggers appropriate "TouchCancel" events. + * + * This method is called internally, but may be useful when running Crafty in headless mode. + * + * @see .touchPoints + */ + resetTouchPoints: function () { + // Tell all touch points they're no longer held down + var touchPoints = this.touchPoints, touchPoint, + i = touchPoints.length; + while (i--) { // iterate backwards to avoid conflicts with removal of array elements + touchPoint = touchPoints[i]; + touchPoint.eventName = "TouchCancel"; + this.triggerTouch("TouchCancel", touchPoint); } + return this; }, - /**@ - * #Crafty.touchDispatch - * @category Input + * #.triggerTouch + * @comp TouchState * @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). - * - * By default, touch events are treated as mouse events. To change this behaviour (and enable multitouch) - * you must use Crafty.multitouch. - * - * If using multitouch feature, this method sets the array Crafty.touchHandler.fingers, which holds data - * of the most recent touches that occured (useful for determining positions of fingers in every frame) - * as well as last entity touched by each finger. Data is lost as soon as the finger is raised. - * - * You can read about the MouseEvent, which is the parameter passed to the Mouse entity's callback. - * https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent + * @sign public this triggerTouch(String eventName, Object eventData) + * @param eventName - Name of the touch event to trigger ("TouchStart", "TouchMove", "TouchEnd", "TouchCancel", ...) + * @param eventData - The touch event to trigger * - * You can also read about the TouchEvent. - * https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent - * - * And about the touch point interface, which is the parameter passed to the Touch entity's callback. - * http://www.w3.org/TR/touch-events/#dfn-active-touch-point - * - * @see Crafty.multitouch - * @see Touch + * Try to trigger a touch event on this entity and persist the touch point. + * This method prevents inconsistent touch state. + * e.g. If this entity didn't receive a "TouchStart" of a identifier previously, it won't fire a "TouchEnd" event for that identifier. + * + * This method is called internally, but may be useful when running Crafty in headless mode. + * + * @example + * ~~~ + * var wasTriggered = false; + * + * ent.requires('TouchState') + * .bind('TouchEnd', function(evt) { + * wasTriggered = true; + * }) + * .triggerTouch('TouchEnd', { identifier: 0 }); + * + * Crafty.log(wasTriggered); // prints false + * ~~~ */ - touchDispatch: function (e) { - if (!Crafty.touchObjs && !Crafty.mouseObjs) return; + triggerTouch: function (eventName, eventData) { + switch (eventName) { + case "TouchStart": + this._handleStart(eventData); + break; + case "TouchMove": + this._handleMove(eventData); + break; + case "TouchCancel": + case "TouchEnd": + this._handleEnd(eventData); + break; + default: + this.triggerTouchEvent(eventName, eventData); // trigger the event otherwise + } + return this; + }, - if (this._touchHandler.multitouch) - switch (e.type) { - case "touchstart": - this._touchHandler.handleStart(e); - break; - case "touchmove": - this._touchHandler.handleMove(e); - break; - case "touchleave": // touchleave is treated as touchend - case "touchcancel": // touchcancel is treated as touchend, but triggers a TouchCancel event - case "touchend": - this._touchHandler.handleEnd(e); - break; + _indexOfTouchPoint: function (identifier) { + var touchPoints = this.touchPoints; + for (var i = 0, l = touchPoints.length; i < l; i++) { + if (touchPoints[i].identifier === identifier) { + return i; } - else - this._touchHandler.mimicMouse(e); + } + return -1; + }, - //Don't prevent default actions if target node is input or textarea. - if (e.target && e.target.nodeName !== 'INPUT' && e.target.nodeName !== 'TEXTAREA') - if (e.preventDefault) { - e.preventDefault(); - } else { - e.returnValue = false; - } + _setTouchPoint: function (touchPointDest, touchPointSrc) { + touchPointDest.eventName = touchPointSrc.eventName; + touchPointDest.identifier = touchPointSrc.identifier; + touchPointDest.target = touchPointSrc.target; + touchPointDest.entity = touchPointSrc.entity; // DEPRECATED: remove this in upcoming release + touchPointDest.realX = touchPointSrc.realX; + touchPointDest.realY = touchPointSrc.realY; + touchPointDest.originalEvent = touchPointSrc.originalEvent; }, - _touchHandler: { - fingers: [], // keeps track of touching fingers - multitouch: false, + _handleStart: function (touchPoint) { + var oldIndex = this._indexOfTouchPoint(touchPoint.identifier), + oldTouchPoint = oldIndex >= 0 ? this.touchPoints[oldIndex] : null; + if (!oldTouchPoint) { // ignore TouchStart due to inconsistent state caused by loosing focus + // allocate touch point + var newTouchPoint = this._touchPointsPool.pop() || {}; + this._setTouchPoint(newTouchPoint, touchPoint); + this.touchPoints.push(newTouchPoint); - handleStart: function (e) { - var touches = e.changedTouches; - for (var i = 0, l = touches.length; i < l; i++) { - var idx = false, - tar = e.target ? e.target : e.srcElement, - closest; - closest = this.findClosestTouchEntity(touches[i], tar); + this.triggerTouchEvent(newTouchPoint.eventName, newTouchPoint); + } + }, - if (closest) { - closest.trigger("TouchStart", touches[i]); - // In case the entity was already being pressed, get the finger index - idx = this.fingerDownIndexByEntity(closest); - } - var touch = this.setTouch(touches[i], closest); - if (idx !== false && idx >= 0) { - // Recycling finger... - this.fingers[idx] = touch; - } else { - this.fingers.push(touch); - } - } - }, + _handleMove: function (touchPoint) { + var oldIndex = this._indexOfTouchPoint(touchPoint.identifier), + oldTouchPoint = oldIndex >= 0 ? this.touchPoints[oldIndex] : null; + if (oldTouchPoint) { // ignore TouchMove due to inconsistent state caused by loosing focus + // update touch point + this._setTouchPoint(oldTouchPoint, touchPoint); - handleMove: function (e) { - var touches = e.changedTouches; - for (var i = 0, l = touches.length; i < l; i++) { - var idx = this.fingerDownIndexById(touches[i].identifier), - tar = e.target ? e.target : e.srcElement; - var closest = this.findClosestTouchEntity(touches[i], tar); - - if (idx >= 0) { - 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]); - finger.entity.trigger("TouchEnd"); - } - finger.entity = closest; - finger.realX = touches[i].realX; - finger.realY = touches[i].realY; - } - } - }, + this.triggerTouchEvent(oldTouchPoint.eventName, oldTouchPoint); + } + }, - handleEnd: function (e) { - var touches = e.changedTouches, - eventName = e.type === "touchcancel" ? "TouchCancel" : "TouchEnd"; - for (var i = 0, l = touches.length; i < l; i++) { - var idx = this.fingerDownIndexById(touches[i].identifier); + _handleEnd: function (touchPoint) { + var oldIndex = this._indexOfTouchPoint(touchPoint.identifier), + oldTouchPoint = oldIndex >= 0 ? this.touchPoints[oldIndex] : null; + if (oldTouchPoint) { // ignore TouchEnd due to inconsistent state caused by loosing focus + this._setTouchPoint(oldTouchPoint, touchPoint); + this.triggerTouchEvent(oldTouchPoint.eventName, oldTouchPoint); - if (idx >= 0) { - if (this.fingers[idx].entity) - this.fingers[idx].entity.trigger(eventName); - this.fingers.splice(idx, 1); - } - } - }, + // free touch point + this.touchPoints.splice(oldIndex, 1); + oldTouchPoint.target = null; // release reference for possible GC + oldTouchPoint.entity = null; // DEPRECATED: remove this in upcoming release + oldTouchPoint.originalEvent = null; // release reference for possible GC + this._touchPointsPool.push(oldTouchPoint); + } + } +}; +Crafty.c("TouchState", Crafty.__touchStateTemplate); + +// define a basic Touch system for headless mode +// will be substituted with proper one in browser mode +Crafty.s("Touch", Crafty.extend.call({ + // this method will be called by TouchState iff triggerTouch event was valid + triggerTouchEvent: function (eventName, e) { + Crafty.trigger(eventName, e); + } +}, Crafty.__touchStateTemplate), {}, false); - setTouch: function (touch, entity) { - return { identifier: touch.identifier, realX: touch.realX, realY: touch.realY, entity: entity }; - }, +},{"../core/core.js":10}],9:[function(require,module,exports){ +var Crafty = require('../core/core.js'); - findClosestTouchEntity: function (touchEvent, tar) { - return Crafty.findPointerEventTargetByComponent("Touch", touchEvent, tar); - }, - 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; - } - } - return -1; - }, +/**@ + * #Crafty.easing + * @category Animation + * @kind Class + * + * + * An object for tracking transitions. Typically used indirectly through "SpriteAnimation", "Tween", or viewport animations. + * + * If a method allows you to specify the type of easing, you can do so by providing a custom function or a string corresponding to the name of a built-in method. + * + * Built-in easing functions are "linear", "smoothStep", "smootherStep", "easeInQuad", "easeOutQuad", and "easeInOutQuad". + * + * A custom function will be passed a parameter `t` which will vary between 0 and 1, and should return the progress of the animation between 0 and 1. + * @example + * Here is how you might use easing functions with the "Tween" component. + * ~~~~ + * var e = Crafty.e("2D, Tween"); + * // Use built-in easing functions + * e.tween({x:100}, 1000, "smoothStep"); + * e.tween({y:100}, 1000, "easeInQuad"); + * // Define a custom easing function: 2t^2 - t + * e.tween({w:0}, 1000, function(t){return 2*t*t - t;}); + * ~~~ + * @see Tween, SpriteAnimation + */ +var easing = function(duration, easingFn) { + this.timePerFrame = 1000 / Crafty.timer.FPS(); + this.duration = duration; //default duration given in ms + if (typeof easingFn === "function"){ + this.easing_function = easingFn; + } else if (typeof easingFn === "string" && this.standardEasingFunctions[easingFn]){ + this.easing_function = this.standardEasingFunctions[easingFn]; + } else { + this.easing_function = this.standardEasingFunctions.linear; + } + this.reset(); +}; - fingerDownIndexByEntity: function (entityToFind) { - for (var i = 0, l = this.fingers.length; i < l; i++) { - var ent = this.fingers[i].entity; - if (ent === entityToFind) { - return i; - } - } - return -1; - }, +easing.prototype = { + duration: 0, + clock:0, + steps: null, + complete: false, + paused: false, - mimicMouse: function (e) { - var type, first, - lastEvent = Crafty.lastEvent; - if (e.type === "touchstart") type = "mousedown"; - else if (e.type === "touchmove") type = "mousemove"; - else if (e.type === "touchend") type = "mouseup"; - else if (e.type === "touchcancel") type = "mouseup"; - else if (e.type === "touchleave") type = "mouseup"; - if (e.touches && e.touches.length) { - first = e.touches[0]; - } else if (e.changedTouches && e.changedTouches.length) { - first = e.changedTouches[0]; - } - 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.target.dispatchEvent(simulatedEvent); - // trigger click when it should be triggered - 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.target.dispatchEvent(simulatedEvent); - } - }, - }, - - /**@ - * #Crafty.findPointerEventTargetByComponent - * @category Input - * @kind Method - * @private - * - * @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 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) 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. - * 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. - * - */ - findPointerEventTargetByComponent: function (comp, e, target) { - var tar = target ? target : Crafty.stage.elem, - 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) { - tar = tar.parentNode; - } - var ent = Crafty(parseInt(tar.id.replace('ent', ''), 10)); - 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) { - - // 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); + // init values + reset: function(){ + this.loops = 1; + this.clock = 0; + this.complete = false; + this.paused = 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; - } - } - } - } - - // 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); - } + repeat: function(loopCount){ + this.loops = loopCount; + }, - // Update the event coordinates and return the event target - e.realX = layerPos.x; - e.realY = layerPos.y; - - return closest; - }, + setProgress: function(progress, loopCount){ + this.clock = this.duration * progress; + if (typeof loopCount !== "undefined") + this.loops = loopCount; - /**@ - * #Crafty.mouseWheelDispatch - * @category Input - * @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) - * - * 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 [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) { - e.direction = (e.detail < 0 || e.wheelDelta > 0) ? 1 : -1; - Crafty.trigger("MouseWheelScroll", e); - }, + }, - /**@ - * #Crafty.keyboardDispatch - * @category Input - * @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.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.keydown, Keyboard - */ - keyboardDispatch: function (e) { - // Use a Crafty-standard event object to avoid cross-browser issues - var original = e, - evnt = {}, - props = "char charCode keyCode type shiftKey ctrlKey metaKey timestamp".split(" "); - for (var i = props.length; i;) { - var prop = props[--i]; - evnt[prop] = original[prop]; - } - evnt.which = original.charCode !== null ? original.charCode : original.keyCode; - evnt.key = original.keyCode || original.which; - evnt.originalEvent = original; - e = evnt; + pause: function(){ + this.paused = true; + }, - if (e.type === "keydown") { - if (Crafty.keydown[e.key] !== true) { - Crafty.keydown[e.key] = true; - Crafty.trigger("KeyDown", e); - } - } else if (e.type === "keyup") { - delete Crafty.keydown[e.key]; - Crafty.trigger("KeyUp", e); - } + resume: function(){ + this.paused = false; + this.complete = false; + }, - //prevent default actions for all keys except backspace and F1-F12 and except actions in INPUT and TEXTAREA. - //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 (original.stopPropagation) original.stopPropagation(); - else original.cancelBubble = true; + // Increment the clock by some amount dt + // Handles looping and sets a flag on completion + tick: function(dt){ + if (this.paused || this.complete) return; + this.clock += dt; + this.frames = Math.floor(this.clock/this.timePerFrame); + while (this.clock >= this.duration && this.complete === false){ + this.loops--; + if (this.loops > 0) + this.clock -= this.duration; + else + this.complete = true; + } + }, - //Don't prevent default actions if target node is input or textarea. - if (original.target && original.target.nodeName !== 'INPUT' && original.target.nodeName !== 'TEXTAREA') { - if (original.preventDefault) { - original.preventDefault(); - } else { - original.returnValue = false; - } - } - return false; - } - } -}); + // same as value for now; with other time value functions would be more useful + time: function(){ + return ( Math.min(this.clock/this.duration, 1) ); -//initialize the input events onload -Crafty._preBind("Load", function () { - Crafty.addEvent(this, "keydown", Crafty.keyboardDispatch); - Crafty.addEvent(this, "keyup", Crafty.keyboardDispatch); + }, - Crafty.addEvent(this, Crafty.stage.elem, "mousedown", Crafty.mouseDispatch); - Crafty.addEvent(this, Crafty.stage.elem, "mouseup", Crafty.mouseDispatch); - Crafty.addEvent(this, document.body, "mouseup", Crafty.detectBlur); - Crafty.addEvent(this, window, "blur", Crafty.resetKeyDown); - Crafty.addEvent(this, Crafty.stage.elem, "mousemove", Crafty.mouseDispatch); - Crafty.addEvent(this, Crafty.stage.elem, "click", Crafty.mouseDispatch); - Crafty.addEvent(this, Crafty.stage.elem, "dblclick", Crafty.mouseDispatch); - - Crafty.addEvent(this, Crafty.stage.elem, "touchstart", Crafty.touchDispatch); - Crafty.addEvent(this, Crafty.stage.elem, "touchmove", Crafty.touchDispatch); - Crafty.addEvent(this, Crafty.stage.elem, "touchend", Crafty.touchDispatch); - Crafty.addEvent(this, Crafty.stage.elem, "touchcancel", Crafty.touchDispatch); - Crafty.addEvent(this, Crafty.stage.elem, "touchleave", Crafty.touchDispatch); - - if (Crafty.support.prefix === "Moz") // mouse wheel event for firefox - Crafty.addEvent(this, Crafty.stage.elem, "DOMMouseScroll", Crafty.mouseWheelDispatch); - else // mouse wheel event for rest of browsers - Crafty.addEvent(this, Crafty.stage.elem, "mousewheel", Crafty.mouseWheelDispatch); -}); + // Value is where along the tweening curve we are + value: function(){ + return this.easing_function(this.time()); + }, -Crafty._preBind("CraftyStop", function () { - Crafty.removeEvent(this, "keydown", Crafty.keyboardDispatch); - Crafty.removeEvent(this, "keyup", Crafty.keyboardDispatch); + // Easing functions, formulas taken from https://gist.github.com/gre/1650294 + // and https://en.wikipedia.org/wiki/Smoothstep + standardEasingFunctions: { + // no easing, no acceleration + linear: function (t) { return t; }, + // smooth step; starts and ends with v=0 + smoothStep: function(t){ return (3-2*t)*t*t; }, + // smootherstep; starts and ends with v, a=0 + smootherStep: function(t){ return (6*t*t-15*t+10)*t*t*t; }, + // quadratic curve; starts with v=0 + easeInQuad: function (t) { return t*t; }, + // quadratic curve; ends with v=0 + easeOutQuad: function (t) { return t*(2-t); }, + // quadratic curve; starts and ends with v=0 + easeInOutQuad: function (t) { return t<0.5 ? 2*t*t : (4-2*t)*t-1; } + } +}; - if (Crafty.stage) { - Crafty.removeEvent(this, Crafty.stage.elem, "mousedown", Crafty.mouseDispatch); - Crafty.removeEvent(this, Crafty.stage.elem, "mouseup", Crafty.mouseDispatch); - Crafty.removeEvent(this, Crafty.stage.elem, "mousemove", Crafty.mouseDispatch); - Crafty.removeEvent(this, Crafty.stage.elem, "click", Crafty.mouseDispatch); - Crafty.removeEvent(this, Crafty.stage.elem, "dblclick", Crafty.mouseDispatch); - - Crafty.removeEvent(this, Crafty.stage.elem, "touchstart", Crafty.touchDispatch); - Crafty.removeEvent(this, Crafty.stage.elem, "touchmove", Crafty.touchDispatch); - Crafty.removeEvent(this, Crafty.stage.elem, "touchend", Crafty.touchDispatch); - Crafty.removeEvent(this, Crafty.stage.elem, "touchcancel", Crafty.touchDispatch); - Crafty.removeEvent(this, Crafty.stage.elem, "touchleave", Crafty.touchDispatch); - - if (Crafty.support.prefix === "Moz") // mouse wheel event for firefox - Crafty.removeEvent(this, Crafty.stage.elem, "DOMMouseScroll", Crafty.mouseWheelDispatch); - else // mouse wheel event for rest of browsers - Crafty.removeEvent(this, Crafty.stage.elem, "mousewheel", Crafty.mouseWheelDispatch); - } +module.exports = easing; +},{"../core/core.js":10}],10:[function(require,module,exports){ +var version = require('./version'); - Crafty.removeEvent(this, document.body, "mouseup", Crafty.detectBlur); - Crafty.removeEvent(this, window, "blur", Crafty.resetKeyDown); -}); /**@ - * #Mouse - * @category Input - * @kind Component + * #Crafty + * @category Core + * @kind CoreObject * - * Provides the entity with mouse related events. + * `Crafty` is both an object, and a function for selecting entities. + * Its many methods and properties are discussed individually. + * Below is the documentation for use as a selector. * - * If you do not add this component, mouse events will not be triggered on the entity. + * @sign public EntitySelection Crafty( String selector) + * @param selector - A string representing which entities to select * - * @trigger MouseOver - when the mouse enters - MouseEvent - * @trigger MouseOut - when the mouse leaves - MouseEvent - * @trigger MouseDown - when the mouse button is pressed on - MouseEvent - * @trigger MouseUp - when the mouse button is released on - MouseEvent - * @trigger Click - when the user clicks - MouseEvent - * @trigger DoubleClick - when the user double clicks - MouseEvent - * @trigger MouseMove - when the mouse is over and moves - MouseEvent + * @sign public Entity Crafty( Number selector ) + * @param selector - An entity's id * - * 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 + * Select a set of or single entities by components or an entity's ID. * - * //(x,y) coordinates of mouse event in world (default viewport) space - * e.realX - * e.realY + * Crafty uses syntax similar to jQuery by having a selector engine to select entities by their components. * - * // Normalized mouse button according to Crafty.mouseButtons: - * // Crafty.mouseButtons.LEFT, Crafty.mouseButtons.RIGHT or Crafty.mouseButtons.MIDDLE - * e.mouseButton - * ~~~ + * If there is more than one match, the return value is an Array-like object listing the ID numbers of each matching entity. If there is exactly one match, the entity itself is returned. If you're not sure how many matches to expect, check the number of matches via Crafty(...).length. Alternatively, use Crafty(...).each(...), which works in all cases. * - * @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 - * to make use of the Touch component instead, which can break compatibility with things which directly interact with the Mouse component. + * @note You can treat an entity as if it was a selection of length 1 -- it implements all the same methods. * * @example * ~~~ - * var myEntity = Crafty.e('2D, Canvas, Color, Mouse') - * .attr({x: 10, y: 10, w: 40, h: 40}) - * .color('red') - * .bind('Click', function(MouseEvent){ - * alert('clicked', MouseEvent); - * }); - * - * myEntity.bind('MouseUp', function(e) { - * if( e.mouseButton == Crafty.mouseButtons.RIGHT ) - * Crafty.log("Clicked right button"); - * }) + * Crafty("MyComponent") + * Crafty("Hello 2D Component") + * Crafty("Hello, 2D, Component") * ~~~ - * @see Crafty.mouseButtons - * @see Crafty.mouseDispatch - * @see Crafty.multitouch - * @see Crafty.touchDispatch - */ -Crafty.c("Mouse", { - init: function () { - Crafty.mouseObjs++; - this.requires("AreaMap") - .bind("Remove", function () { - Crafty.mouseObjs--; - }); - } -}); - -/**@ - * #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 - * @trigger TouchCancel - when a touch event has been disrupted in some way - TouchPoint - * @trigger TouchEnd - when the finger is raised over the entity, or when finger leaves entity. (Passes no data) - null * - * To be able to use multitouch, you must enable it with `Crafty.multitouch(true)`. - * - * If you don't need multitouch, you can probably use the Mouse component instead, since by default Crafty will trigger mouse events for touch input. - * - * You can read more about the TouchEvent. - * - [TouchEvent.touches and TouchEvent.changedTouches](https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent) - * - [TouchPoint](http://www.w3.org/TR/touch-events/#dfn-active-touch-point) is the parameter passed to the event callback in the related touch. - * + * The first selector will return all entities that have the component `MyComponent`. The second will return all entities that have `Hello` and `2D` and `Component` whereas the last will return all entities that have at least one of those components (or). * - * @example * ~~~ - * Crafty.multitouch(true); - * - * var myEntity = Crafty.e('2D, Canvas, Color, Touch') - * .attr({x: 10, y: 10, w: 40, h: 40}) - * .color('green') - * .bind('TouchStart', function(TouchPoint){ - * Crafty.log('myEntity has been touched', TouchPoint); - * }).bind('TouchMove', function(TouchPoint) { - * Crafty.log('Finger moved over myEntity at the { x: ' + TouchPoint.realX + ', y: ' + TouchPoint.realY + ' } coordinates.'); - * }).bind('TouchEnd', function() { - * Crafty.log('Touch over myEntity has finished.'); - * }); + * Crafty("*") * ~~~ - * @see Crafty.multitouch - * @see Crafty.touchDispatch + * Passing `*` will select all entities. + * + * ~~~ + * Crafty(1) + * ~~~ + * Passing an integer will select the entity with that `ID`. + * + * To work directly with an array of entities, use the `get()` method on a selection. + * To call a function in the context of each entity, use the `.each()` method. + * + * The event related methods such as `bind` and `trigger` will work on selections of entities. + * + * @see Crafty Core#.get + * @see Crafty Core#.each */ -Crafty.c("Touch", { - init: function () { - Crafty.touchObjs++; - this.requires("AreaMap") - .bind("Remove", function () { - Crafty.touchObjs--; - }); - } -}); + +var Crafty = function (selector) { + return new Crafty.fn.init(selector); +}; + // Internal variables +var GUID, frame, components, entities, handlers, onloads, compEntities, +slice, rlist, rspace; + + +components = {}; // Map of components and their functions +slice = Array.prototype.slice; +rlist = /\s*,\s*/; +rspace = /\s+/; + +var initState = function () { + GUID = 1; // GUID for entity IDs + frame = 0; + + entities = {}; // Map of entities and their data + compEntities= {}; // Map from componentName to (entityId -> entity) + handlers = {}; // Global event handlers + onloads = []; // Temporary storage of onload handlers +}; + +initState(); /**@ - * #AreaMap - * @category Input - * @kind Component - * - * Component used by Mouse and Touch. - * Can be added to other entities for use with the Crafty.findClosestEntityByComponent method. + * #Crafty Core + * @category Core + * @kind CoreObject * - * @see Button - * @see Crafty.polygon + * @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 + * @trigger Remove - when the entity is removed by calling .destroy() + * + * A set of methods added to every single entity. */ -Crafty.c("AreaMap", { - init: function () { - if (this.has("Renderable") && this._drawLayer) { - this._drawLayer._pointerEntities++; +Crafty.fn = Crafty.prototype = { + + init: function (selector) { + //select entities by component + if (typeof selector === "string") { + var elem = 0, //index elements + e, //entity forEach + and = false, //flags for multiple + or = false, + del, + comps, + filteredCompEnts, compEnts, compEnts2, compEnt, + i, l; + + if (selector === '*') { + i = 0; + for (e in entities) { + // entities is something like {2:entity2, 3:entity3, 11:entity11, ...} + // The for...in loop sets e to "2", "3", "11", ... i.e. all + // the entity ID numbers. e is a string, so +e converts to number type. + this[i] = +e; + i++; + } + this.length = i; + // if there's only one entity, return the actual entity + if (i === 1) { + return entities[this[0]]; + } + return this; + } + + //multiple components OR + if (selector.indexOf(',') !== -1) { + or = true; + del = rlist; + //deal with multiple components AND + } else if (selector.indexOf(' ') !== -1) { + and = true; + del = rspace; + } + + if (or) { + comps = selector.split(del); + + filteredCompEnts = {}; // populate object with union of keys from all sets + for (i = 0, l = comps.length; i < l; i++) { + compEnts = compEntities[comps[i]]; + for (compEnt in compEnts) { + filteredCompEnts[compEnt] = +compEnt; //convert to int + } + } + + for (compEnt in filteredCompEnts) { // add set elements to result + this[elem++] = filteredCompEnts[compEnt]; + } + } else if (and) { + comps = selector.split(del); + + filteredCompEnts = {}; // populate initial object with keys in common from first two sets + compEnts = compEntities[comps[0]]; + compEnts2 = compEntities[comps[1]]; + for (compEnt in compEnts) { + if (compEnts2[compEnt] !== undefined) + filteredCompEnts[compEnt] = +compEnt; //convert to int + } + + for (i = 2, l = comps.length; i < l; i++) { // strip initial object keys not in following sets + compEnts = compEntities[comps[i]]; + for (compEnt in filteredCompEnts) { + if (compEnts[compEnt] === undefined) + filteredCompEnts[compEnt] = -1; + } + } + + for (compEnt in filteredCompEnts) { // add valid elements to result + i = filteredCompEnts[compEnt]; + if (i >= 0) this[elem++] = i; + } + } else { // single component selector + compEnts = compEntities[selector]; + for (compEnt in compEnts) { + this[elem++] = +compEnt; //convert to int + } + } + + this.length = elem; //length is the last index (already incremented) + + // if there's only one entity, return the actual entity + if (elem === 1) { + return entities[this[elem - 1]]; + } + + } else { //Select a specific entity + + if (!selector) { //nothin passed creates God entity + selector = 0; + if (!(selector in entities)) entities[selector] = this; + } + + //if not exists, return undefined + if (!(selector in entities)) { + this.length = 0; + return this; + } + + this[0] = selector; + this.length = 1; + + //update from the cache + if (!this.__c) this.__c = {}; + if (!this._callbacks) Crafty._addCallbackMethods(this); + + //update to the cache if NULL + if (!entities[selector]) entities[selector] = this; + return entities[selector]; //return the cached selector } + + Crafty._addCallbackMethods(this); + return this; }, - remove: function () { - if (this.has("Renderable") && this._drawLayer) { - this._drawLayer._pointerEntities--; - } + /**@ + * #.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 + * ~~~ + * 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; }, - events: { - "LayerAttached": function (layer) { - layer._pointerEntities++; - }, - "LayerDetached": function (layer) { - layer._pointerEntities--; - } + /**@ + * #.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; }, /**@ - * #.areaMap - * @comp AreaMap + * #.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]) + * @param Component# - Component ID to add. * - * @trigger NewAreaMap - when a new areaMap is assigned - Crafty.polygon + * Adds a component to the selected entities or entity. * - * @sign public this .areaMap(Crafty.polygon polygon) - * @param polygon - Instance of Crafty.polygon used to check if the mouse coordinates are inside this region + * Components are used to extend the functionality of entities. + * This means it will copy properties and assign methods to + * augment the functionality of the entity. * - * @sign public this .areaMap(Array coordinatePairs) - * @param coordinatePairs - Array of `x`, `y` coordinate pairs to generate a polygon + * For adding multiple components, you can either pass a string with + * all the component names (separated by commas), or pass each component name as + * an argument. * - * @sign public this .areaMap(x1, y1,.., xN, yN) - * @param point# - List of `x`, `y` coordinate pairs to generate a polygon + * If the component has a function named `init` it will be called. * - * Assign a polygon to the entity so that pointer (mouse or touch) events will only be triggered if - * the coordinates are inside the given polygon. + * If the entity already has the component, the component is skipped (nothing happens). * * @example * ~~~ - * Crafty.e("2D, DOM, Color, Mouse") - * .color("red") - * .attr({ w: 100, h: 100 }) - * .bind('MouseOver', function() {Crafty.log("over")}) - * .areaMap(0, 0, 50, 0, 50, 50, 0, 50); - * - * Crafty.e("2D, Mouse") - * .areaMap([0, 0, 50, 0, 50, 50, 0, 50]); - * - * Crafty.e("2D, Mouse").areaMap( - * new Crafty.polygon([0, 0, 50, 0, 50, 50, 0, 50]) - * ); + * this.addComponent("2D, Canvas"); + * this.addComponent("2D", "Canvas"); * ~~~ - * - * @see Crafty.polygon */ - areaMap: function (poly) { - //create polygon - if (arguments.length > 1) { - //convert args to array to create polygon - var args = Array.prototype.slice.call(arguments, 0); - poly = new Crafty.polygon(args); - } else if (poly.constructor === Array) { - poly = new Crafty.polygon(poly.slice()); + addComponent: function (id) { + var comps, compName, + comp, c = 0; + + //add multiple arguments + if (arguments.length === 1 && id.indexOf(',') !== -1) { + comps = id.split(rlist); } else { - poly = poly.clone(); + comps = arguments; } - poly.shift(this._x, this._y); - this.mapArea = poly; - this.attach(this.mapArea); - this.trigger("NewAreaMap", poly); - return this; - } -}); - -/**@ - * #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", { - init: function () { - var req = (!Crafty.mobile || (Crafty.mobile && !Crafty.multitouch())) ? "Mouse" : "Touch"; - this.requires(req); - } -}); - -/**@ - * #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 - * @trigger StopDrag - is triggered when dragging ends - MouseEvent - * - * @see Mouse - */ -Crafty.c("MouseDrag", { - _dragging: false, - - init: function () { - this.requires("Mouse"); - this.bind("MouseDown", this._ondown); - }, - - remove: function () { - this.unbind("MouseDown", this._ondown); - }, + //extend the components + for (; c < comps.length; c++) { + compName = comps[c]; - // When dragging is enabled, this method is bound to the MouseDown crafty event - _ondown: function (e) { - if (e.mouseButton !== Crafty.mouseButtons.LEFT) return; - this.startDrag(e); - }, + // If component already exists, continue + if (this.__c[compName] === true) { + continue; + } + this.__c[compName] = true; + // update map from component to (entityId -> entity) + (compEntities[compName] = compEntities[compName] || {})[this[0]] = this; - // While a drag is occurring, this method is bound to the mousemove DOM event - _ondrag: function (e) { - // ignore invalid 0 position - strange problem on ipad - if (!this._dragging || e.realX === 0 || e.realY === 0) return false; - this.trigger("Dragging", e); - }, + comp = components[compName]; + // Copy all methods of the component + this.extend(comp); + // Add any required components + if (comp && "required" in comp) { + this.requires( comp.required ); + } + // Define properties + if (comp && "properties" in comp) { + var props = comp.properties; + for (var propertyName in props) { + Object.defineProperty(this, propertyName, props[propertyName]); + } + } + // Bind events + if (comp && "events" in comp){ + var auto = comp.events; + for (var eventName in auto){ + var fn = typeof auto[eventName] === "function" ? auto[eventName] : comp[auto[eventName]]; + this.bind(eventName, fn); + } + } + // Call constructor function + if (comp && "init" in comp) { + comp.init.call(this); + } + } - // While a drag is occurring, this method is bound to mouseup DOM event - _onup: function (e) { - if (e.mouseButton !== Crafty.mouseButtons.LEFT) return; - this.stopDrag(e); + this.trigger("NewComponent", comps); + return this; }, /**@ - * #.startDrag - * @comp MouseDrag + * #.toggleComponent + * @comp Crafty Core * @kind Method * - * @sign public this .startDrag(void) + * @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]) + * @param Component# - Component ID to add or remove. + * + * Add or Remove Components from an entity. * - * Make the entity produce drag events, essentially making the entity follow the mouse positions. + * @example + * ~~~ + * var e = Crafty.e("2D,DOM,Test"); + * e.toggleComponent("Test,Test2"); //Remove Test, add Test2 + * e.toggleComponent("Test,Test2"); //Add Test, remove Test2 + * ~~~ * - * @see .stopDrag + * ~~~ + * var e = Crafty.e("2D,DOM,Test"); + * e.toggleComponent("Test","Test2"); //Remove Test, add Test2 + * e.toggleComponent("Test","Test2"); //Add Test, remove Test2 + * e.toggleComponent("Test"); //Remove Test + * ~~~ */ - startDrag: function (e) { - if (this._dragging) return; - this._dragging = true; + toggleComponent: function (toggle) { + var i = 0, + l, comps; + if (arguments.length > 1) { + l = arguments.length; - Crafty.addEvent(this, Crafty.stage.elem, "mousemove", this._ondrag); - Crafty.addEvent(this, Crafty.stage.elem, "mouseup", this._onup); + for (; i < l; i++) { + if (this.has(arguments[i])) { + this.removeComponent(arguments[i]); + } else { + this.addComponent(arguments[i]); + } + } + //split components if contains comma + } else if (toggle.indexOf(',') !== -1) { + comps = toggle.split(rlist); + l = comps.length; + for (; i < l; i++) { + if (this.has(comps[i])) { + this.removeComponent(comps[i]); + } else { + this.addComponent(comps[i]); + } + } + + //single component passed + } else { + if (this.has(toggle)) { + this.removeComponent(toggle); + } else { + this.addComponent(toggle); + } + } - // if event undefined, use the last known position of the mouse - this.trigger("StartDrag", e || Crafty.lastEvent); return this; }, /**@ - * #.stopDrag - * @comp MouseDrag + * #.requires + * @comp Crafty Core * @kind Method * - * @sign public this .stopDrag(void) + * @sign public this .requires(String componentList) + * @param componentList - List of components that must be added + * + * @sign public this .addComponent(String component1, String component2[, .. , ComponentN]) + * @param Component# - A component to add * - * Stop the entity from producing drag events, essentially reproducing the drop. + * Makes sure the entity has the components listed. If the entity does not + * have the component, it will add it. * - * @see .startDrag + * (In the current version of Crafty, this function behaves exactly the same + * as `addComponent`. By convention, developers have used `requires` for + * component dependencies -- i.e. to indicate specifically that one component + * will only work properly if another component is present -- and used + * `addComponent` in all other situations.) + * + * @see .addComponent */ - stopDrag: function (e) { - if (!this._dragging) return; - this._dragging = false; - - Crafty.removeEvent(this, Crafty.stage.elem, "mousemove", this._ondrag); - Crafty.removeEvent(this, Crafty.stage.elem, "mouseup", this._onup); - - // if event undefined, use the last known position of the mouse - this.trigger("StopDrag", e || Crafty.lastEvent); - return this; - } -}); + requires: function () { + return this.addComponent.apply(this, arguments); + }, -/**@ - * #Keyboard - * @category Input - * @kind Component - * - * 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 - * - * In addition to binding to these events, the current state (pressed/released) of a key can also be queried using the `.isDown` method. - * - * @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 Crafty.keys - * @see Crafty.keydown - * @see Crafty.keyboardDispatch - */ -Crafty.c("Keyboard", { /**@ - * #.isDown - * @comp Keyboard + * #.removeComponent + * @comp Crafty Core * @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) - * @param keyCode - Key code in `Crafty.keys`. + * @sign public this .removeComponent(String Component[, soft]) + * @param component - Component to remove + * @param soft - Whether to soft remove it (defaults to `true`) * - * Determine if a certain key is currently down. + * Removes a component from an entity. A soft remove (the default) will only + * refrain `.has()` from returning true. Hard will remove all + * associated properties and methods. * * @example * ~~~ - * ent.requires('Keyboard') - * .bind('EnterFrame', function() { - * if (this.isDown('SPACE')) - * this.y--; - * }); + * var e = Crafty.e("2D,DOM,Test"); + * e.removeComponent("Test"); //Soft remove Test component + * e.removeComponent("Test", false); //Hard remove Test component * ~~~ - * - * @see Crafty.keys */ - isDown: function (key) { - if (typeof key === "string") { - key = Crafty.keys[key]; + removeComponent: function (id, soft) { + var comp = components[id]; + this.trigger("RemoveComponent", id); + if (comp && "events" in comp){ + var auto = comp.events; + for (var eventName in auto){ + var fn = typeof auto[eventName] === "function" ? auto[eventName] : comp[auto[eventName]]; + this.unbind(eventName, fn); + } + } + if (comp && "remove" in comp) { + comp.remove.call(this, false); + } + if (soft === false && comp) { + for (var prop in comp) { + delete this[prop]; + } + } + delete this.__c[id]; + // update map from component to (entityId -> entity) + if (compEntities[id]) { + delete compEntities[id][this[0]]; } - return !!Crafty.keydown[key]; - } -}); -},{"../core/core.js":9}],7:[function(require,module,exports){ -var Crafty = require('../core/core.js'); - -Crafty.extend({ + return this; + }, + /**@ - * #Crafty.keys - * @category Input - * @kind Property + * #.getId + * @comp Crafty Core + * @kind Method * - * Object of key names and the corresponding Unicode key code. + * @sign public Number .getId(void) + * @returns the ID of this entity. + * + * For better performance, simply use the this[0] property. * + * @example + * Finding out the `ID` of an entity can be done by returning the property `0`. * ~~~ - * 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 + * var ent = Crafty.e("2D"); + * ent[0]; //ID + * ent.getId(); //also ID * ~~~ */ - 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 - + getId: function () { + return this[0]; }, /**@ - * #Crafty.mouseButtons - * @category Input - * @kind Property + * #.has + * @comp Crafty Core + * @kind Method * - * 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: + * @sign public Boolean .has(String component) + * @param component - The name of the component to check + * @returns `true` or `false` depending on if the + * entity has the given component. * - * ~~~ - * LEFT: 0, - * MIDDLE: 1, - * RIGHT: 2 - * ~~~ + * For better performance, simply use the `.__c` object + * which will be `true` if the entity has the component or + * will not exist (or be `false`). */ - mouseButtons: { - LEFT: 0, - MIDDLE: 1, - RIGHT: 2 - } -}); -},{"../core/core.js":9}],8:[function(require,module,exports){ -var Crafty = require('../core/core.js'); - + has: function (id) { + return !!this.__c[id]; + }, -/**@ - * #Crafty.easing - * @category Animation - * @kind Class - * - * - * An object for tracking transitions. Typically used indirectly through "SpriteAnimation", "Tween", or viewport animations. - * - * If a method allows you to specify the type of easing, you can do so by providing a custom function or a string corresponding to the name of a built-in method. - * - * Built-in easing functions are "linear", "smoothStep", "smootherStep", "easeInQuad", "easeOutQuad", and "easeInOutQuad". - * - * A custom function will be passed a parameter `t` which will vary between 0 and 1, and should return the progress of the animation between 0 and 1. - * @example - * Here is how you might use easing functions with the "Tween" component. - * ~~~~ - * var e = Crafty.e("2D, Tween"); - * // Use built-in easing functions - * e.tween({x:100}, 1000, "smoothStep"); - * e.tween({y:100}, 1000, "easeInQuad"); - * // Define a custom easing function: 2t^2 - t - * e.tween({w:0}, 1000, function(t){return 2*t*t - t;}); - * ~~~ - * @see Tween, SpriteAnimation - */ -var easing = function(duration, easingFn) { - this.timePerFrame = 1000 / Crafty.timer.FPS(); - this.duration = duration; //default duration given in ms - if (typeof easingFn === "function"){ - this.easing_function = easingFn; - } else if (typeof easingFn === "string" && this.standardEasingFunctions[easingFn]){ - this.easing_function = this.standardEasingFunctions[easingFn]; - } else { - this.easing_function = this.standardEasingFunctions.linear; - } - this.reset(); -}; + /**@ + * #.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]]) + * @param property - Property of the entity to modify + * @param value - Value to set the property to + * @param silent - If you would like to supress events + * @param recursive - If you would like merge recursively + * + * Use this method to set any property of the entity. + * + * @sign public this .attr(Object map[, Boolean silent[, Boolean recursive]]) + * @param map - Object where each key is the property to modify and the value as the property value + * @param silent - If you would like to supress events + * @param recursive - If you would like merge recursively + * + * Use this method to set multiple properties of the entity. + * + * Setter options: + * - `silent`: If you want to prevent it from firing events. + * - `recursive`: If you pass in an object you could overwrite sibling keys, this recursively merges instead of just merging it. This is `false` by default, unless you are using dot notation `name.first`. + * + * @sign public Any .attr(String property) + * @param property - Property of the entity to modify + * @returns Value - the value of the property + * + * Use this method to get any property of the entity. You can also retrieve the property using `this.property`. + * + * + * @example + * ~~~ + * this.attr({key: "value", prop: 5}); + * this.attr("key"); // returns "value" + * this.attr("prop"); // returns 5 + * this.key; // "value" + * this.prop; // 5 + * + * this.attr("key", "newvalue"); + * this.attr("key"); // returns "newvalue" + * this.key; // "newvalue" + * + * this.attr("parent.child", "newvalue"); + * this.parent; // {child: "newvalue"}; + * this.attr('parent.child'); // "newvalue" + * ~~~ + */ + attr: function (key, value, silent, recursive) { + if (arguments.length === 1 && typeof arguments[0] === 'string') { + return this._attr_get(key); + } else { + return this._attr_set(key, value, silent, recursive); + } + }, + /** + * Internal getter method for data on the entity. Called by `.attr`. + * + * example + * ~~~ + * person._attr_get('name'); // Foxxy + * person._attr_get('contact'); // {email: 'fox_at_example.com'} + * person._attr_get('contact.email'); // fox_at_example.com + * ~~~ + */ + _attr_get: function(key, context) { + var first, keys, subkey; + if (typeof context === "undefined" || context === null) { + context = this; + } + if (key.indexOf('.') > -1) { + keys = key.split('.'); + first = keys.shift(); + subkey = keys.join('.'); + return this._attr_get(keys.join('.'), context[first]); + } else { + return context[key]; + } + }, -easing.prototype = { - duration: 0, - clock:0, - steps: null, - complete: false, - paused: false, + /** + * Internal setter method for attributes on the component. Called by `.attr`. + * + * Options: + * + * `silent`: If you want to prevent it from firing events. + * + * `recursive`: If you pass in an object you could overwrite + * sibling keys, this recursively merges instead of just + * merging it. This is `false` by default, unless you are + * using dot notation `name.first`. + * + * example + * ~~~ + * person._attr_set('name', 'Foxxy', true); + * person._attr_set('name', 'Foxxy'); + * person._attr_set({name: 'Foxxy'}, true); + * person._attr_set({name: 'Foxxy'}); + * person._attr_set('name.first', 'Foxxy'); + * ~~~ + */ + _attr_set: function() { + var data, silent, recursive; + if (typeof arguments[0] === 'string') { + data = this._set_create_object(arguments[0], arguments[1]); + silent = !!arguments[2]; + recursive = arguments[3] || arguments[0].indexOf('.') > -1; + } else { + data = arguments[0]; + silent = !!arguments[1]; + recursive = !!arguments[2]; + } - // init values - reset: function(){ - this.loops = 1; - this.clock = 0; - this.complete = false; - this.paused = false; - }, + if (!silent) { + this.trigger('Change', data); + } - repeat: function(loopCount){ - this.loops = loopCount; - }, + if (recursive) { + this._recursive_extend(data, this); + } else { + this.extend.call(this, data); + } + return this; + }, - setProgress: function(progress, loopCount){ - this.clock = this.duration * progress; - if (typeof loopCount !== "undefined") - this.loops = loopCount; + /** + * If you are setting a key of 'foo.bar' or 'bar', this creates + * the appropriate object for you to recursively merge with the + * current attributes. + */ + _set_create_object: function(key, value) { + var data = {}, keys, first, subkey; + if (key.indexOf('.') > -1) { + keys = key.split('.'); + first = keys.shift(); + subkey = keys.join('.'); + data[first] = this._set_create_object(subkey, value); + } else { + data[key] = value; + } + return data; + }, - }, + /** + * Recursively puts `new_data` into `original_data`. + */ + _recursive_extend: function(new_data, original_data) { + var key; + for (key in new_data) { + if (new_data[key].constructor === Object) { + original_data[key] = this._recursive_extend(new_data[key], original_data[key]); + } else { + original_data[key] = new_data[key]; + } + } + return original_data; + }, - pause: function(){ - this.paused = true; - }, + /**@ + * #.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()`. + * @see .get + */ + toArray: function () { + return slice.call(this, 0); + }, - resume: function(){ - this.paused = false; - this.complete = false; - }, + /**@ + * #.timeout + * @comp Crafty Core + * @kind Method - // Increment the clock by some amount dt - // Handles looping and sets a flag on completion - tick: function(dt){ - if (this.paused || this.complete) return; - this.clock += dt; - this.frames = Math.floor(this.clock/this.timePerFrame); - while (this.clock >= this.duration && this.complete === false){ - this.loops--; - if (this.loops > 0) - this.clock -= this.duration; - else - this.complete = true; - } - }, - - // same as value for now; with other time value functions would be more useful - time: function(){ - return ( Math.min(this.clock/this.duration, 1) ); - - }, - - // Value is where along the tweening curve we are - value: function(){ - return this.easing_function(this.time()); - }, - - // Easing functions, formulas taken from https://gist.github.com/gre/1650294 - // and https://en.wikipedia.org/wiki/Smoothstep - standardEasingFunctions: { - // no easing, no acceleration - linear: function (t) { return t; }, - // smooth step; starts and ends with v=0 - smoothStep: function(t){ return (3-2*t)*t*t; }, - // smootherstep; starts and ends with v, a=0 - smootherStep: function(t){ return (6*t*t-15*t+10)*t*t*t; }, - // quadratic curve; starts with v=0 - easeInQuad: function (t) { return t*t; }, - // quadratic curve; ends with v=0 - easeOutQuad: function (t) { return t*(2-t); }, - // quadratic curve; starts and ends with v=0 - easeInOutQuad: function (t) { return t<0.5 ? 2*t*t : (4-2*t)*t-1; } - } -}; - -module.exports = easing; -},{"../core/core.js":9}],9:[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. - * Below is the documentation for use as a selector. - * - * @sign public EntitySelection Crafty( String selector) - * @param selector - A string representing which entities to select - * - * @sign public Entity Crafty( Number selector ) - * @param selector - An entity's id - * - * Select a set of or single entities by components or an entity's ID. - * - * Crafty uses syntax similar to jQuery by having a selector engine to select entities by their components. - * - * If there is more than one match, the return value is an Array-like object listing the ID numbers of each matching entity. If there is exactly one match, the entity itself is returned. If you're not sure how many matches to expect, check the number of matches via Crafty(...).length. Alternatively, use Crafty(...).each(...), which works in all cases. - * - * @note You can treat an entity as if it was a selection of length 1 -- it implements all the same methods. - * - * @example - * ~~~ - * Crafty("MyComponent") - * Crafty("Hello 2D Component") - * Crafty("Hello, 2D, Component") - * ~~~ - * - * The first selector will return all entities that have the component `MyComponent`. The second will return all entities that have `Hello` and `2D` and `Component` whereas the last will return all entities that have at least one of those components (or). - * - * ~~~ - * Crafty("*") - * ~~~ - * Passing `*` will select all entities. - * - * ~~~ - * Crafty(1) - * ~~~ - * Passing an integer will select the entity with that `ID`. - * - * To work directly with an array of entities, use the `get()` method on a selection. - * To call a function in the context of each entity, use the `.each()` method. - * - * The event related methods such as `bind` and `trigger` will work on selections of entities. - * - * @see Crafty Core#.get - * @see Crafty Core#.each - */ - -var Crafty = function (selector) { - return new Crafty.fn.init(selector); -}; - // Internal variables -var GUID, frame, components, entities, handlers, onloads, -slice, rlist, rspace; - - -components = {}; // Map of components and their functions -slice = Array.prototype.slice; -rlist = /\s*,\s*/; -rspace = /\s+/; - -var initState = function () { - GUID = 1; // GUID for entity IDs - frame = 0; - - entities = {}; // Map of entities and their data - handlers = {}; // Global event handlers - onloads = []; // Temporary storage of onload handlers -}; - -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 - * @trigger Remove - when the entity is removed by calling .destroy() - * - * A set of methods added to every single entity. - */ -Crafty.fn = Crafty.prototype = { - - init: function (selector) { - //select entities by component - if (typeof selector === "string") { - var elem = 0, //index elements - e, //entity forEach - current, - and = false, //flags for multiple - or = false, - del, - comps, - score, - i, l; - - if (selector === '*') { - i = 0; - for (e in entities) { - // entities is something like {2:entity2, 3:entity3, 11:entity11, ...} - // The for...in loop sets e to "2", "3", "11", ... i.e. all - // the entity ID numbers. e is a string, so +e converts to number type. - this[i] = +e; - i++; - } - this.length = i; - // if there's only one entity, return the actual entity - if (i === 1) { - return entities[this[0]]; - } - return this; - } - - //multiple components OR - if (selector.indexOf(',') !== -1) { - or = true; - del = rlist; - //deal with multiple components AND - } else if (selector.indexOf(' ') !== -1) { - and = true; - del = rspace; - } - - //loop over entities - for (e in entities) { - if (!entities.hasOwnProperty(e)) continue; //skip - current = entities[e]; - - if (and || or) { //multiple components - comps = selector.split(del); - i = 0; - l = comps.length; - score = 0; - - for (; i < l; i++) //loop over components - if (current.__c[comps[i]]) score++; //if component exists add to score - - //if anded comps and has all OR ored comps and at least 1 - if (and && score === l || or && score > 0) this[elem++] = +e; - - } else if (current.__c[selector]) this[elem++] = +e; //convert to int - } - - //extend all common components - if (elem > 0 && !and && !or) this.extend(components[selector]); - if (comps && and) - for (i = 0; i < l; i++) this.extend(components[comps[i]]); - - this.length = elem; //length is the last index (already incremented) - - // if there's only one entity, return the actual entity - if (elem === 1) { - return entities[this[elem - 1]]; - } - - } else { //Select a specific entity - - if (!selector) { //nothin passed creates God entity - selector = 0; - if (!(selector in entities)) entities[selector] = this; - } - - //if not exists, return undefined - if (!(selector in entities)) { - this.length = 0; - return this; - } - - this[0] = selector; - this.length = 1; - - //update from the cache - if (!this.__c) this.__c = {}; - if (!this._callbacks) Crafty._addCallbackMethods(this); - - //update to the cache if NULL - if (!entities[selector]) entities[selector] = this; - return entities[selector]; //return the cached selector - } - - Crafty._addCallbackMethods(this); - return this; - }, + * @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 + * + * The delay method will execute a function after a given amount of time in milliseconds. + * + * Essentially a wrapper for `setTimeout`. + * + * @example + * Destroy itself after 100 milliseconds + * ~~~ + * this.timeout(function() { + this.destroy(); + * }, 100); + * ~~~ + */ + timeout: function (callback, duration) { + this.each(function () { + var self = this; + setTimeout(function () { + callback.call(self); + }, duration); + }); + return this; + }, /**@ - * #.setName + * #.bind * @comp Crafty Core * @kind Method * - * @sign public this .setName(String name) - * @param name - A human readable name for debugging purposes. + * @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 * - * Set a human readable name for debugging purposes. + * Attach the current entity (or entities) to listen for an event. + * + * Callback will be invoked when an event with the event name passed + * is triggered. Depending on the event, some data may be passed + * via an argument to the callback function. + * + * The first argument is the event name (can be anything) whilst the + * second argument is the callback. If the event has data, the + * callback should have an argument. + * + * Events are arbitrary and provide communication between components. + * You can trigger or bind an event even if it doesn't exist yet. + * + * Unlike DOM events, Crafty events are executed synchronously. * * @example * ~~~ - * var ent = Crafty.e().setName("Player"); + * this.attr("triggers", 0); //set a trigger count + * this.bind("myevent", function() { + * this.triggers++; //whenever myevent is triggered, increment + * }); + * this.bind("UpdateFrame", function() { + * this.trigger("myevent"); //trigger myevent on every frame + * }); * ~~~ * - * @see Crafty Core#.getName + * @see .trigger, .unbind */ - setName: function (name) { - var entityName = String(name); - this._entityName = entityName; - this.trigger("NewEntityName", entityName); + bind: function (event, callback) { + // To learn how the event system functions, see the comments for Crafty._callbackMethods + //optimization for 1 entity + if (this.length === 1) { + this._bindCallback(event, callback); + } else { + for (var i = 0; i < this.length; i++) { + var e = entities[this[i]]; + if (e) { + e._bindCallback(event, callback); + } + } + } return this; }, /**@ - * #.getName + * #.uniqueBind * @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. + * @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 + * @returns ID of the current callback used to unbind * - * @example - * ~~~ - * var ent = Crafty.e().setName("Player"); - * var name = ent.getName(); - * ~~~ + * Works like Crafty.bind, but prevents a callback from being bound multiple times. * - * @see Crafty Core#.setName + * @see .bind */ - getName: function (name) { - return this._entityName; + uniqueBind: function (event, callback) { + this.unbind(event, callback); + this.bind(event, callback); + }, /**@ - * #.addComponent + * #.one * @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]) - * @param Component# - Component ID to add. - * - * Adds a component to the selected entities or entity. - * - * Components are used to extend the functionality of entities. - * This means it will copy properties and assign methods to - * augment the functionality of the entity. - * - * For adding multiple components, you can either pass a string with - * all the component names (separated by commas), or pass each component name as - * an argument. - * - * If the component has a function named `init` it will be called. + * @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 + * @returns ID of the current callback used to unbind * - * If the entity already has the component, the component is skipped (nothing happens). + * Works like Crafty.bind, but will be unbound once the event triggers. * - * @example - * ~~~ - * this.addComponent("2D, Canvas"); - * this.addComponent("2D", "Canvas"); - * ~~~ + * @see .bind */ - addComponent: function (id) { - var comps, - comp, c = 0; - - //add multiple arguments - if (arguments.length === 1 && id.indexOf(',') !== -1) { - comps = id.split(rlist); - } else { - comps = arguments; - } - - //extend the components - for (; c < comps.length; c++) { - // If component already exists, continue - if (this.__c[comps[c]] === true) { - continue; - } - this.__c[comps[c]] = true; - comp = components[comps[c]]; - // Copy all methods of the component - this.extend(comp); - // Add any required components - if (comp && "required" in comp) { - this.requires( comp.required ); - } - // Call constructor function - if (comp && "init" in comp) { - comp.init.call(this); - } - // Bind events - if (comp && "events" in comp){ - var auto = comp.events; - for (var eventName in auto){ - var fn = typeof auto[eventName] === "function" ? auto[eventName] : comp[auto[eventName]]; - this.bind(eventName, fn); - } - } - } + one: function (event, callback) { + var self = this; + var oneHandler = function (data) { + callback.call(self, data); + self.unbind(event, oneHandler); + }; + return self.bind(event, oneHandler); - this.trigger("NewComponent", comps); - return this; }, /**@ - * #.toggleComponent + * #.unbind * @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]) - * @param Component# - Component ID to add or remove. - * - * Add or Remove Components from an entity. + * @sign public this .unbind(String eventName[, Function callback]) + * @param eventName - Name of the event to unbind + * @param callback - Function to unbind * - * @example - * ~~~ - * var e = Crafty.e("2D,DOM,Test"); - * e.toggleComponent("Test,Test2"); //Remove Test, add Test2 - * e.toggleComponent("Test,Test2"); //Add Test, remove Test2 - * ~~~ + * Removes binding with an event from current entity. * - * ~~~ - * var e = Crafty.e("2D,DOM,Test"); - * e.toggleComponent("Test","Test2"); //Remove Test, add Test2 - * e.toggleComponent("Test","Test2"); //Add Test, remove Test2 - * e.toggleComponent("Test"); //Remove Test - * ~~~ + * Passing an event name will remove all events bound to + * that event. Passing a reference to the callback will + * unbind only that callback. + * @see .bind, .trigger */ - toggleComponent: function (toggle) { - var i = 0, - l, comps; - if (arguments.length > 1) { - l = arguments.length; - - for (; i < l; i++) { - if (this.has(arguments[i])) { - this.removeComponent(arguments[i]); - } else { - this.addComponent(arguments[i]); - } - } - //split components if contains comma - } else if (toggle.indexOf(',') !== -1) { - comps = toggle.split(rlist); - l = comps.length; - for (; i < l; i++) { - if (this.has(comps[i])) { - this.removeComponent(comps[i]); - } else { - this.addComponent(comps[i]); - } - } - - //single component passed - } else { - if (this.has(toggle)) { - this.removeComponent(toggle); - } else { - this.addComponent(toggle); + unbind: function (event, callback) { + // To learn how the event system functions, see the comments for Crafty._callbackMethods + var i, e; + for (i = 0; i < this.length; i++) { + e = entities[this[i]]; + if (e) { + e._unbindCallbacks(event, callback); } } - return this; }, /**@ - * #.requires + * #.trigger * @comp Crafty Core * @kind Method * - * @sign public this .requires(String componentList) - * @param componentList - List of components that must be added + * @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 * - * Makes sure the entity has the components listed. If the entity does not - * have the component, it will add it. + * Trigger an event with arbitrary data. Will invoke all callbacks with + * the context (value of `this`) of the current entity object. * - * (In the current version of Crafty, this function behaves exactly the same - * as `addComponent`. By convention, developers have used `requires` for - * component dependencies -- i.e. to indicate specifically that one component - * will only work properly if another component is present -- and used - * `addComponent` in all other situations.) + * *Note: This will only execute callbacks within the current entity, no other entity.* * - * @see .addComponent + * 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 executed synchronously. */ - requires: function (list) { - return this.addComponent(list); + trigger: function (event, data) { + // To learn how the event system functions, see the comments for Crafty._callbackMethods + if (this.length === 1) { + //find the handlers assigned to the entity + this._runCallbacks(event, data); + } else { + for (var i = 0; i < this.length; i++) { + var e = entities[this[i]]; + if (e) { + e._runCallbacks(event, data); + } + } + } + return this; }, /**@ - * #.removeComponent + * #.each * @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`) + * @sign public this .each(Function method) + * @param method - Method to call on each iteration * - * Removes a component from an entity. A soft remove (the default) will only - * refrain `.has()` from returning true. Hard will remove all - * associated properties and methods. + * Iterates over found entities, calling a function for every entity. + * + * The function will be called for every entity and will pass the index + * in the iteration as an argument. The context (value of `this`) of the + * function will be the current entity in the iteration. * * @example + * Destroy every second 2D entity * ~~~ - * var e = Crafty.e("2D,DOM,Test"); - * e.removeComponent("Test"); //Soft remove Test component - * e.removeComponent("Test", false); //Hard remove Test component + * Crafty("2D").each(function(i) { + * if(i % 2 === 0) { + * this.destroy(); + * } + * }); * ~~~ */ - removeComponent: function (id, soft) { - var comp = components[id]; - this.trigger("RemoveComponent", id); - if (comp && "events" in comp){ - var auto = comp.events; - for (var eventName in auto){ - var fn = typeof auto[eventName] === "function" ? auto[eventName] : comp[auto[eventName]]; - this.unbind(eventName, fn); - } - } - if (comp && "remove" in comp) { - comp.remove.call(this, false); - } - if (soft === false && comp) { - for (var prop in comp) { - delete this[prop]; - } + each: function (func) { + var i = 0, + l = this.length; + for (; i < l; i++) { + //skip if not exists + if (!entities[this[i]]) continue; + func.call(entities[this[i]], i); } - delete this.__c[id]; - - return this; }, /**@ - * #.getId + * #.get * @comp Crafty Core * @kind Method * - * @sign public Number .getId(void) - * @returns the ID of this entity. + * @sign public Array .get() + * @returns An array of entities corresponding to the active selector + * + * @sign public Entity .get(Number index) + * @returns an entity belonging to the current selection + * @param index - The index of the entity to return. If negative, counts back from the end of the array. * - * For better performance, simply use the this[0] property. * * @example - * Finding out the `ID` of an entity can be done by returning the property `0`. + * Get an array containing every "2D" entity * ~~~ - * var ent = Crafty.e("2D"); - * ent[0]; //ID - * ent.getId(); //also ID + * var arr = Crafty("2D").get() + * ~~~ + * Get the first entity matching the selector * ~~~ + * // equivalent to Crafty("2D").get()[0], but doesn't create a new array + * var e = Crafty("2D").get(0) + * ~~~ + * Get the last "2D" entity matching the selector + * ~~~ + * var e = Crafty("2D").get(-1) + * ~~~ + * */ - getId: function () { - return this[0]; + get: function(index) { + var l = this.length; + if (typeof index !== "undefined") { + if (index >= l || index+l < 0) + return undefined; + if (index>=0) + return entities[this[index]]; + else + return entities[this[index+l]]; + } else { + var i=0, result = []; + for (; i < l; i++) { + //skip if not exists + if (!entities[this[i]]) continue; + result.push( entities[this[i]] ); + } + return result; + } }, /**@ - * #.has + * #.clone * @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 - * entity has the given component. + * @sign public Entity .clone(void) + * @returns Cloned entity of the current entity * - * For better performance, simply use the `.__c` object - * which will be `true` if the entity has the component or - * will not exist (or be `false`). + * Method will create another entity with the exact same + * properties, components and methods as the current entity. */ - has: function (id) { - return !!this.__c[id]; + clone: function () { + var comps = this.__c, + comp, + prop, + clone = Crafty.e(); + + for (comp in comps) { + clone.addComponent(comp); + } + for (prop in this) { + if (prop !== "0" && prop !== "_global" && prop !== "_changed" && typeof this[prop] !== "function" && typeof this[prop] !== "object") { + clone[prop] = this[prop]; + } + } + + return clone; }, + /**@ - * #.attr + * #.setter * @comp Crafty Core * @kind Method * - * @trigger Change - when properties change - {key: value} - * - * @sign public this .attr(String property, Any value[, Boolean silent[, Boolean recursive]]) - * @param property - Property of the entity to modify - * @param value - Value to set the property to - * @param silent - If you would like to supress events - * @param recursive - If you would like merge recursively - * - * Use this method to set any property of the entity. - * - * @sign public this .attr(Object map[, Boolean silent[, Boolean recursive]]) - * @param map - Object where each key is the property to modify and the value as the property value - * @param silent - If you would like to supress events - * @param recursive - If you would like merge recursively - * - * Use this method to set multiple properties of the entity. - * - * Setter options: - * - `silent`: If you want to prevent it from firing events. - * - `recursive`: If you pass in an object you could overwrite sibling keys, this recursively merges instead of just merging it. This is `false` by default, unless you are using dot notation `name.first`. + * @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 * - * @sign public Any .attr(String property) - * @param property - Property of the entity to modify - * @returns Value - the value of the property + * Will watch a property waiting for modification and will then invoke the + * given callback when attempting to modify. * - * Use this method to get any property of the entity. You can also retrieve the property using `this.property`. + * This feature is deprecated; use .defineField() instead. + * @see .defineField + */ + setter: function (prop, callback) { + return this.defineField(prop, function(){}, callback); + }, + + /**@ + * #.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 + * @param setCallback - Method to execute if the property is mutated + * + * Assigns getters and setters to the property. + * A getter will watch a property waiting for access and will then invoke the + * given getCallback when attempting to retrieve. + * A setter will watch a property waiting for mutation and will then invoke the + * given setCallback when attempting to modify. * * @example * ~~~ - * this.attr({key: "value", prop: 5}); - * this.attr("key"); // returns "value" - * this.attr("prop"); // returns 5 - * this.key; // "value" - * this.prop; // 5 - * - * this.attr("key", "newvalue"); - * this.attr("key"); // returns "newvalue" - * this.key; // "newvalue" + * var ent = Crafty.e("2D"); + * ent.defineField("customData", function() { + * return this._customData; + * }, function(newValue) { + * this._customData = newValue; + * }); * - * this.attr("parent.child", "newvalue"); - * this.parent; // {child: "newvalue"}; - * this.attr('parent.child'); // "newvalue" + * ent.customData = "2" // set customData to 2 + * Crafty.log(ent.customData) // prints 2 * ~~~ */ - attr: function (key, value, silent, recursive) { - if (arguments.length === 1 && typeof arguments[0] === 'string') { - return this._attr_get(key); - } else { - return this._attr_set(key, value, silent, recursive); - } + defineField: function (prop, getCallback, setCallback) { + Crafty.defineField(this, prop, getCallback, setCallback); + return this; }, - /** - * Internal getter method for data on the entity. Called by `.attr`. - * - * example - * ~~~ - * person._attr_get('name'); // Foxxy - * person._attr_get('contact'); // {email: 'fox_at_example.com'} - * person._attr_get('contact.email'); // fox_at_example.com - * ~~~ + /**@ + * #.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 */ - _attr_get: function(key, context) { - var first, keys, subkey; - if (typeof context === "undefined" || context === null) { - context = this; - } - if (key.indexOf('.') > -1) { - keys = key.split('.'); - first = keys.shift(); - subkey = keys.join('.'); - return this._attr_get(keys.join('.'), context[first]); - } else { - return context[key]; - } + destroy: function () { + //remove all event handlers, delete from entities + this.each(function () { + var comp; + this.trigger("Remove"); + for (var compName in this.__c) { + comp = components[compName]; + if (comp && "remove" in comp) + comp.remove.call(this, true); + + // update map from component to (entityId -> entity) + delete compEntities[compName][this[0]]; + } + this._unbindAll(); + delete entities[this[0]]; + }); }, - /** - * Internal setter method for attributes on the component. Called by `.attr`. - * - * Options: - * - * `silent`: If you want to prevent it from firing events. - * - * `recursive`: If you pass in an object you could overwrite - * sibling keys, this recursively merges instead of just - * merging it. This is `false` by default, unless you are - * using dot notation `name.first`. - * - * example - * ~~~ - * person._attr_set('name', 'Foxxy', true); - * person._attr_set('name', 'Foxxy'); - * person._attr_set({name: 'Foxxy'}, true); - * person._attr_set({name: 'Foxxy'}); - * person._attr_set('name.first', 'Foxxy'); - * ~~~ + /**@ + * #.freeze + * @comp Crafty Core + * @kind Method + * + * @sign public this .freeze() + * + * @triggers Freeze - Directly before the entity is frozen + * + * Freezes the entity. A frozen entity will not receive events or be displayed by graphics systems. + * It is also removed from the spatial map, which means it will not be found by collisions, + * raycasting, or similar functions. + * + * This method may be called upon a collection of entities. + * + * @note Because the entity no longer listens to events, modifying its properties can result in an inconsistent state. + * + * If custom components need to handle frozen entities, they can listen to the "Freeze" event, which will be triggered before the event system is disabled. + * + * @example + * + * ``` + * // Freeze all entities with the Dead component + * Crafty("Dead").freeze(); + * ``` + * + * @see .unfreeze */ - _attr_set: function() { - var data, silent, recursive; - if (typeof arguments[0] === 'string') { - data = this._set_create_object(arguments[0], arguments[1]); - silent = !!arguments[2]; - recursive = arguments[3] || arguments[0].indexOf('.') > -1; - } else { - data = arguments[0]; - silent = !!arguments[1]; - recursive = !!arguments[2]; - } - - if (!silent) { - this.trigger('Change', data); - } - - if (recursive) { - this._recursive_extend(data, this); + freeze: function () { + if (this.length === 1 && !this.__frozen) { + this.trigger("Freeze", this); + this._freezeCallbacks(); + this.__frozen = true; } else { - this.extend.call(this, data); + for (var i = 0; i < this.length; i++) { + var e = entities[this[i]]; + if (e && !e.__frozen) { + e.trigger("Freeze", e); + e._freezeCallbacks(); + // Set a frozen flag. (This is distinct from the __callbackFrozen flag) + e.__frozen = true; + } + } } return this; }, - /** - * If you are setting a key of 'foo.bar' or 'bar', this creates - * the appropriate object for you to recursively merge with the - * current attributes. - */ - _set_create_object: function(key, value) { - var data = {}, keys, first, subkey; - if (key.indexOf('.') > -1) { - keys = key.split('.'); - first = keys.shift(); - subkey = keys.join('.'); - data[first] = this._set_create_object(subkey, value); - } else { - data[key] = value; - } - return data; - }, - - /** - * Recursively puts `new_data` into `original_data`. - */ - _recursive_extend: function(new_data, original_data) { - var key; - for (key in new_data) { - if (new_data[key].constructor === Object) { - original_data[key] = this._recursive_extend(new_data[key], original_data[key]); - } else { - original_data[key] = new_data[key]; - } - } - return original_data; - }, - - /**@ - * #.toArray + /**# + * #.unfreeze * @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()`. - * @see .get - */ - toArray: function () { - return slice.call(this, 0); - }, - - /**@ - * #.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 - * - * The delay method will execute a function after a given amount of time in milliseconds. - * - * Essentially a wrapper for `setTimeout`. - * - * @example - * Destroy itself after 100 milliseconds - * ~~~ - * this.timeout(function() { - this.destroy(); - * }, 100); - * ~~~ - */ - timeout: function (callback, duration) { - this.each(function () { - var self = this; - setTimeout(function () { - callback.call(self); - }, duration); - }); - return this; - }, - - /**@ - * #.bind - * @comp Crafty Core - * @kind Method + * @sign public this .unfreeze() + * + * @triggers Unfreeze - While the entity is being unfrozen + * + * Unfreezes the entity, allowing it to receive events, inserting it back into the spatial map, + * and restoring it to its previous visibility. + * + * This method may be called upon a collection of entities. + * + * If a custom component needs to know when an entity is unfrozen, they can listen to the "Unfreeze"" event. * - * @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 - * - * Attach the current entity (or entities) to listen for an event. - * - * Callback will be invoked when an event with the event name passed - * is triggered. Depending on the event, some data may be passed - * via an argument to the callback function. - * - * The first argument is the event name (can be anything) whilst the - * second argument is the callback. If the event has data, the - * callback should have an argument. - * - * Events are arbitrary and provide communication between components. - * You can trigger or bind an event even if it doesn't exist yet. - * - * Unlike DOM events, Crafty events are executed synchronously. - * * @example - * ~~~ - * this.attr("triggers", 0); //set a trigger count - * this.bind("myevent", function() { - * this.triggers++; //whenever myevent is triggered, increment - * }); - * this.bind("EnterFrame", function() { - * this.trigger("myevent"); //trigger myevent on every frame - * }); - * ~~~ - * - * @see .trigger, .unbind + * ``` + * // Bring the dead back to life! + * Crafty("Dead").unfreeze().addComponent("Undead"); + * ``` */ - bind: function (event, callback) { - // To learn how the event system functions, see the comments for Crafty._callbackMethods - //optimization for 1 entity - if (this.length === 1) { - this._bindCallback(event, callback); + unfreeze: function () { + if (this.length === 1 && this.__frozen) { + this.__frozen = false; + this._unfreezeCallbacks(); + this.trigger("Unfreeze", this); } else { for (var i = 0; i < this.length; i++) { var e = entities[this[i]]; - if (e) { - e._bindCallback(event, callback); + if (e && e.__frozen) { + e.__frozen = false; + e._unfreezeCallbacks(); + e.trigger("Unfreeze", e); } } } return this; - }, + } +}; - /**@ - * #.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 - * @returns ID of the current callback used to unbind - * - * Works like Crafty.bind, but prevents a callback from being bound multiple times. - * - * @see .bind - */ - uniqueBind: function (event, callback) { - this.unbind(event, callback); - this.bind(event, callback); +//give the init instances the Crafty prototype +Crafty.fn.init.prototype = Crafty.fn; - }, - /**@ - * #.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 - * @returns ID of the current callback used to unbind - * - * Works like Crafty.bind, but will be unbound once the event triggers. - * - * @see .bind - */ - one: function (event, callback) { - var self = this; - var oneHandler = function (data) { - callback.call(self, data); - self.unbind(event, oneHandler); - }; - return self.bind(event, oneHandler); +/**@ + * #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. + * + * Used to extend the Crafty namespace by passing in an object of properties and methods to add. + * + * @example + * ~~~ * + * Crafty.extend({ + * isArray: function(arg){ + * return Object.prototype.toString.call(arg) === '[object Array]' + * } + * }); + * + * Crafty.isArray([4, 5, 6]); // returns true + * Crafty.isArray('hi'); // returns false + * ~~~ + */ +Crafty.extend = Crafty.fn.extend = function (obj) { + var target = this, + key; - }, + //don't bother with nulls + if (!obj) return target; - /**@ - * #.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 - * - * Removes binding with an event from current entity. - * - * Passing an event name will remove all events bound to - * that event. Passing a reference to the callback will - * unbind only that callback. - * @see .bind, .trigger - */ - unbind: function (event, callback) { - // To learn how the event system functions, see the comments for Crafty._callbackMethods - var i, e; - for (i = 0; i < this.length; i++) { - e = entities[this[i]]; - if (e) { - e._unbindCallbacks(event, callback); - } - } - return this; - }, + for (key in obj) { + if (target === obj[key]) continue; //handle circular reference + target[key] = obj[key]; + } - /**@ - * #.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 - * - * Trigger an event with arbitrary data. Will invoke all callbacks with - * the context (value of `this`) of the current entity object. - * - * *Note: This will only execute callbacks within the current entity, no other entity.* - * - * 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 executed synchronously. - */ - trigger: function (event, data) { - // To learn how the event system functions, see the comments for Crafty._callbackMethods - if (this.length === 1) { - //find the handlers assigned to the entity - this._runCallbacks(event, data); - } else { - for (var i = 0; i < this.length; i++) { - var e = entities[this[i]]; - if (e) { - e._runCallbacks(event, data); - } - } - } - return this; - }, - - /**@ - * #.each - * @comp Crafty Core - * @kind Method - * - * @sign public this .each(Function method) - * @param method - Method to call on each iteration - * - * Iterates over found entities, calling a function for every entity. - * - * The function will be called for every entity and will pass the index - * in the iteration as an argument. The context (value of `this`) of the - * function will be the current entity in the iteration. - * - * @example - * Destroy every second 2D entity - * ~~~ - * Crafty("2D").each(function(i) { - * if(i % 2 === 0) { - * this.destroy(); - * } - * }); - * ~~~ - */ - each: function (func) { - var i = 0, - l = this.length; - for (; i < l; i++) { - //skip if not exists - if (!entities[this[i]]) continue; - func.call(entities[this[i]], i); - } - return this; - }, - - /**@ - * #.get - * @comp Crafty Core - * @kind Method - * - * @sign public Array .get() - * @returns An array of entities corresponding to the active selector - * - * @sign public Entity .get(Number index) - * @returns an entity belonging to the current selection - * @param index - The index of the entity to return. If negative, counts back from the end of the array. - * - * - * @example - * Get an array containing every "2D" entity - * ~~~ - * var arr = Crafty("2D").get() - * ~~~ - * Get the first entity matching the selector - * ~~~ - * // equivalent to Crafty("2D").get()[0], but doesn't create a new array - * var e = Crafty("2D").get(0) - * ~~~ - * Get the last "2D" entity matching the selector - * ~~~ - * var e = Crafty("2D").get(-1) - * ~~~ - * - */ - get: function(index) { - var l = this.length; - if (typeof index !== "undefined") { - if (index >= l || index+l < 0) - return undefined; - if (index>=0) - return entities[this[index]]; - else - return entities[this[index+l]]; - } else { - var i=0, result = []; - for (; i < l; i++) { - //skip if not exists - if (!entities[this[i]]) continue; - result.push( entities[this[i]] ); - } - return result; - } - }, - - /**@ - * #.clone - * @comp Crafty Core - * @kind Method - * - * @sign public Entity .clone(void) - * @returns Cloned entity of the current entity - * - * Method will create another entity with the exact same - * properties, components and methods as the current entity. - */ - clone: function () { - var comps = this.__c, - comp, - prop, - clone = Crafty.e(); - - for (comp in comps) { - clone.addComponent(comp); - } - for (prop in this) { - if (prop !== "0" && prop !== "_global" && prop !== "_changed" && typeof this[prop] !== "function" && typeof this[prop] !== "object") { - clone[prop] = this[prop]; - } - } - - return clone; - }, - - - /**@ - * #.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 - * - * Will watch a property waiting for modification and will then invoke the - * given callback when attempting to modify. - * - * This feature is deprecated; use .defineField() instead. - * @see .defineField - */ - setter: function (prop, callback) { - return this.defineField(prop, function(){}, callback); - }, - - /**@ - * #.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 - * @param setCallback - Method to execute if the property is mutated - * - * Assigns getters and setters to the property. - * A getter will watch a property waiting for access and will then invoke the - * given getCallback when attempting to retrieve. - * A setter will watch a property waiting for mutation and will then invoke the - * given setCallback when attempting to modify. - * - * @example - * ~~~ - * var ent = Crafty.e("2D"); - * ent.defineField("customData", function() { - * return this._customData; - * }, function(newValue) { - * this._customData = newValue; - * }); - * - * ent.customData = "2" // set customData to 2 - * Crafty.log(ent.customData) // prints 2 - * ~~~ - */ - defineField: function (prop, getCallback, setCallback) { - Crafty.defineField(this, prop, getCallback, setCallback); - return this; - }, - - /**@ - * #.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 - */ - destroy: function () { - //remove all event handlers, delete from entities - this.each(function () { - var comp; - this.trigger("Remove"); - for (var compName in this.__c) { - comp = components[compName]; - if (comp && "remove" in comp) - comp.remove.call(this, true); - } - this._unbindAll(); - delete entities[this[0]]; - }); - } -}; - -//give the init instances the Crafty prototype -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. - * - * Used to extend the Crafty namespace by passing in an object of properties and methods to add. - * - * @example - * ~~~ * - * Crafty.extend({ - * isArray: function(arg){ - * return Object.prototype.toString.call(arg) === '[object Array]' - * } - * }); - * - * Crafty.isArray([4, 5, 6]); // returns true - * Crafty.isArray('hi'); // returns false - * ~~~ - */ -Crafty.extend = Crafty.fn.extend = function (obj) { - var target = this, - key; - - //don't bother with nulls - if (!obj) return target; - - for (key in obj) { - if (target === obj[key]) continue; //handle circular reference - target[key] = obj[key]; - } - - return target; -}; + return target; +}; @@ -3967,7 +3484,7 @@ Crafty._callbackMethods = { // Process for running all callbacks for the given event _runCallbacks: function(event, data) { - if (!this._callbacks[event]) { + if (!this._callbacks[event] || this.__callbacksFrozen) { return; } var callbacks = this._callbacks[event]; @@ -4015,6 +3532,7 @@ Crafty._callbackMethods = { // Completely all callbacks for every event, such as on object destruction _unbindAll: function() { if (!this._callbacks) return; + this.__callbacksFrozen = false; for (var event in this._callbacks) { if (this._callbacks[event]) { // Remove the normal way, in case we've got a nested loop @@ -4023,6 +3541,30 @@ Crafty._callbackMethods = { delete handlers[event][this[0]]; } } + }, + + _freezeCallbacks: function() { + if (!this._callbacks) return; + for (var event in this._callbacks) { + if (this._callbacks[event]) { + // Remove the callbacks from the global list of handlers + delete handlers[event][this[0]]; + } + } + // Mark this callback list as frozen + this.__callbacksFrozen = true; + }, + + _unfreezeCallbacks: function() { + if (!this._callbacks) return; + this.__callbacksFrozen = false; + for (var event in this._callbacks) { + if (this._callbacks[event]) { + // Add the callbacks back to the global list of handlers + handlers[event][this[0]] = this._callbacks[event]; + } + } + } }; @@ -4043,7 +3585,7 @@ Crafty.extend({ * @category Core * @kind Method * - * @trigger Load - Just after the viewport is initialised. Before the EnterFrame loops is started + * @trigger Load - Just after the viewport is initialised. Before the UpdateFrame 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]) * @param Number width - Width of the stage @@ -4052,7 +3594,7 @@ Crafty.extend({ * * Sets the element to use as the stage, creating it if necessary. By default a div with id 'cr-stage' is used, but if the 'stage_elem' argument is provided that will be used instead. (see `Crafty.viewport.init`) * - * Starts the `EnterFrame` interval. This will call the `EnterFrame` event for every frame. + * Starts the `UpdateFrame` interval. This will call the `UpdateFrame` event for every frame. * * Can pass width and height values for the stage otherwise will default to window size. * @@ -4121,7 +3663,7 @@ Crafty.extend({ * @sign public this Crafty.stop([bool clearState]) * @param clearState - if true the stage and all game state is cleared. * - * Stops the EnterFrame interval and removes the stage element. + * Stops the `UpdateFrame` interval and removes the stage element. * * To restart, use `Crafty.init()`. * @see Crafty.init @@ -4165,7 +3707,7 @@ Crafty.extend({ * @trigger Unpause - when the game is unpaused * @sign public this Crafty.pause(void) * - * Pauses the game by stopping the EnterFrame event from firing. If the game is already paused it is unpaused. + * Pauses the game by stopping the `UpdateFrame` event from firing. If the game is already paused it is unpaused. * You can pass a boolean parameter if you want to pause or unpause no matter what the current state is. * Modern browsers pauses the game when the page is not visible to the user. If you want the Pause event * to be triggered when that happens you can enable autoPause in `Crafty.settings`. @@ -4185,7 +3727,6 @@ Crafty.extend({ setTimeout(function () { Crafty.timer.stop(); }, 0); - Crafty.keydown = {}; } else { this.trigger('Unpause'); this._paused = false; @@ -4249,7 +3790,7 @@ Crafty.extend({ init: function () { // When first called, set the gametime one frame before now! if (typeof gameTime === "undefined") - gameTime = (new Date().getTime()) - milliSecPerFrame; + gameTime = Date.now() - milliSecPerFrame; var onFrame = (typeof window !== "undefined") && ( window.requestAnimationFrame || @@ -4348,7 +3889,8 @@ Crafty.extend({ * @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 EnterFrame - Triggered before 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 UpdateFrame - 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 } * @trigger PreRender - Triggered every time immediately before a scene should be rendered * @trigger RenderScene - Triggered every time a scene should be rendered @@ -4358,7 +3900,7 @@ Crafty.extend({ * @trigger MeasureRenderTime - Triggered after each render. Passes the time it took to render the scene - Number * * Advances the game by performing a step. A step consists of one/multiple frames followed by a render. The amount of frames depends on the timer's steptype. - * Specifically it triggers `EnterFrame` & `ExitFrame` events for each frame and `PreRender`, `RenderScene` & `PostRender` events for each render. + * Specifically it triggers `EnterFrame`, `UpdateFrame` & `ExitFrame` events for each frame and `PreRender`, `RenderScene` & `PostRender` events for each render. * * @see Crafty.timer.steptype * @see Crafty.timer.FPS @@ -4366,7 +3908,7 @@ Crafty.extend({ step: function () { var drawTimeStart, dt, lastFrameTime, loops = 0; - var currentTime = new Date().getTime(); + var currentTime = Date.now(); if (endTime > 0) Crafty.trigger("MeasureWaitTime", currentTime - endTime); @@ -4411,15 +3953,18 @@ 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 + + // Event that happens before "UpdateFrame", + // e.g. for setting-up movement in response to user input for the next "UpdateFrame" event Crafty.trigger("EnterFrame", frameData); - // Event that happens after "EnterFrame", e.g. for resolivng collisions applied through movement during "EnterFrame" events + // Everything that changes over time hooks into this event + Crafty.trigger("UpdateFrame", frameData); + // Event that happens after "UpdateFrame", + // e.g. for resolivng collisions applied through movement during "UpdateFrame" events Crafty.trigger("ExitFrame", frameData); gameTime += dt; - currentTime = new Date().getTime(); + currentTime = Date.now(); Crafty.trigger("MeasureFrameTime", currentTime - lastFrameTime); } @@ -4429,7 +3974,7 @@ Crafty.extend({ Crafty.trigger("PreRender"); // Pre-render setup opportunity Crafty.trigger("RenderScene"); Crafty.trigger("PostRender"); // Post-render cleanup opportunity - currentTime = new Date().getTime(); + currentTime = Date.now(); Crafty.trigger("MeasureRenderTime", currentTime - drawTimeStart); } @@ -4478,8 +4023,8 @@ Crafty.extend({ frame: frame++, dt: timestep }; - Crafty.trigger("EnterFrameInput", frameData); Crafty.trigger("EnterFrame", frameData); + Crafty.trigger("UpdateFrame", frameData); Crafty.trigger("ExitFrame", frameData); } Crafty.trigger("PreRender"); @@ -4556,6 +4101,7 @@ Crafty.extend({ * - `init`: A function to be called when the component is added to an entity * - `remove`: A function which will be called just before a component is removed, or before an entity is destroyed. It is passed a single boolean parameter that is `true` if the entity is being destroyed. * - `events`: An object whose properties represent functions bound to events equivalent to the property names. (See the example below.) The binding occurs directly after the call to `init`, and will be removed directly before `remove` is called. + * - `properties`: A dictionary of properties which will be defined using Object.defineProperty. Typically used to add setters and getters. * * In addition to these hardcoded special methods, there are some conventions for writing components. * @@ -4568,19 +4114,19 @@ Crafty.extend({ * Crafty.c("Annoying", { * _message: "HiHi", * init: function() { - * this.bind("EnterFrame", function() { alert(this.message); }); + * this.bind("UpdateFrame", function() { alert(this.message); }); * }, * annoying: function(message) { this.message = message; } * }); * * Crafty.e("Annoying").annoying("I'm an orange..."); * ~~~ - * To attach to the "EnterFrame" event using the `events` property instead: + * To attach to the "UpdateFrame" event using the `events` property instead: * ~~~ * Crafty.c("Annoying", { * _message: "HiHi", * events: { - * "EnterFrame": function(){alert(this.message);} + * "UpdateFrame": function(){alert(this.message);} * } * annoying: function(message) { this.message = message; } * }); @@ -4750,6 +4296,10 @@ Crafty.extend({ return frame; }, + entities: function () { + return entities; + }, + components: function () { return components; }, @@ -4952,7 +4502,7 @@ if (typeof define === 'function') { // AMD } module.exports = Crafty; -},{"./version":18}],10:[function(require,module,exports){ +},{"./version":19}],11:[function(require,module,exports){ (function (process){ var Crafty = require('../core/core.js'); var document = (typeof window !== "undefined") && window.document; @@ -5108,130 +4658,10 @@ var document = (typeof window !== "undefined") && window.document; support.devicemotion = (typeof window !== "undefined") && (typeof window.DeviceMotionEvent !== "undefined"); })(); - -module.exports = { - _events: {}, - - /**@ - * #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 - * @param event - Event name to bind to - * @param callback - Method to execute when triggered - * - * Adds DOM level 3 events to elements. The arguments it accepts are the call - * context (the value of `this`), the DOM element to attach the event to, - * the event name (without `on` (`click` rather than `onclick`)) and - * finally the callback method. - * - * If no element is passed, the default element will be `window.document`. - * - * Callbacks are passed with event data. - * - * @note This is related to DOM events only, not Crafty's own event system. - * Of course, you can trigger Crafty events in the callback function! - * - * @example - * Normally you'd use Crafty's built-in mouse component, but for the sake of an example let's pretend that doesn't exist. - * The following code will add a stage-wide MouseDown event listener to the player, and log both which button was pressed - * and the (x,y) coordinates in viewport/world/game space. - * ~~~ - * var player = Crafty.e("2D"); - * player.onMouseDown = function(e) { - * Crafty.log(e.mouseButton, e.realX, e.realY); - * }; - * Crafty.addEvent(player, Crafty.stage.elem, "mousedown", player.onMouseDown); - * ~~~ - * @see Crafty.removeEvent - */ - addEvent: function (ctx, obj, type, callback) { - if (arguments.length === 3) { - callback = type; - type = obj; - obj = window.document; - } - - //save anonymous function to be able to remove - var afn = function (e) { - callback.call(ctx, e); - }, - id = ctx[0] || ""; - - if (!this._events[id + obj + type + callback]) - this._events[id + obj + type + callback] = afn; - else { - return; - } - - obj.addEventListener(type, afn, false); - - }, - - /**@ - * #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 - * @param event - Name of the event - * @param callback - Method executed when triggered - * - * Removes events attached by `Crafty.addEvent()`. All parameters must - * be the same that were used to attach the event including a reference - * to the callback method. - * - * @see Crafty.addEvent - */ - removeEvent: function (ctx, obj, type, callback) { - if (arguments.length === 3) { - callback = type; - type = obj; - obj = window.document; - } - - //retrieve anonymous function - var id = ctx[0] || "", - afn = this._events[id + obj + type + callback]; - - if (afn) { - obj.removeEventListener(type, afn, false); - delete this._events[id + obj + type + callback]; - } - }, - - /**@ - * #Crafty.background - * @category Graphics, Stage - * @kind Method - * - * @sign public void Crafty.background(String style) - * @param style - Modify the background with a color or image - * - * This method is a shortcut for adding a background - * style to the stage element, i.e. - * `Crafty.stage.elem.style.background = ...` - * - * For example, if you want the background to be white, - * with an image in the center, you might use: - * ~~~ - * Crafty.background('#FFFFFF url(landscape.png) no-repeat center center'); - * ~~~ - * - */ - background: function (style) { - Crafty.stage.elem.style.background = style; - } -}; - -}).call(this,require('_process')) -},{"../core/core.js":9,"_process":1}],11:[function(require,module,exports){ -var Crafty = require('../core/core.js'); + +}).call(this,require('_process')) +},{"../core/core.js":10,"_process":1}],12:[function(require,module,exports){ +var Crafty = require('../core/core.js'); module.exports = { /**@ @@ -5692,7 +5122,7 @@ module.exports = { } }; -},{"../core/core.js":9}],12:[function(require,module,exports){ +},{"../core/core.js":10}],13:[function(require,module,exports){ var Crafty = require('../core/core.js'); @@ -5800,7 +5230,7 @@ module.exports = { }; -},{"../core/core.js":9}],13:[function(require,module,exports){ +},{"../core/core.js":10}],14:[function(require,module,exports){ var Crafty = require('../core/core.js'); @@ -5847,7 +5277,7 @@ module.exports = { * Crafty.e("2D, DOM, Text") * .attr({ w: 100, h: 20, x: 150, y: 120 }) * .text("Loading") - * .css({ "border": "1px solid red"}) + * .textAlign("center") * .textColor("#FFFFFF"); * }); * @@ -5976,7 +5406,7 @@ module.exports = { } }; -},{"../core/core.js":9}],14:[function(require,module,exports){ +},{"../core/core.js":10}],15:[function(require,module,exports){ var Crafty = require('../core/core.js'); try { @@ -6100,7 +5530,7 @@ store.remove = function(key) { module.exports = store; -},{"../core/core.js":9}],15:[function(require,module,exports){ +},{"../core/core.js":10}],16:[function(require,module,exports){ var Crafty = require('../core/core.js'); @@ -6131,10 +5561,14 @@ Crafty._systems = {}; * * 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. + * 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, + * a `properties` dictionary which will be used to define properties with Object.defineProperty, + * 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, options, lazy) { if (obj) { @@ -6206,9 +5640,13 @@ Crafty.CraftySystem = (function() { // 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); + + // Define properties + if ("properties" in template) { + var props = template.properties; + for (var propertyName in props) { + Object.defineProperty(this, propertyName, props[propertyName]); + } } // If an events object is provided, bind the listed event handlers if ("events" in template) { @@ -6218,6 +5656,10 @@ Crafty.CraftySystem = (function() { this.bind(eventName, fn); } } + // Run any instantiation code + if (typeof this.init === "function") { + this.init(name); + } }; })(); @@ -6274,7 +5716,7 @@ Crafty.CraftySystem.prototype = { } }; -},{"../core/core.js":9}],16:[function(require,module,exports){ +},{"../core/core.js":10}],17:[function(require,module,exports){ /**@ * #Delay * @category Utilities @@ -6285,10 +5727,20 @@ Crafty.CraftySystem.prototype = { * This syncs with Crafty's internal clock, and so should generally be preferred to using methods such as `setTimeout`. */ module.exports = { + /**@ + * #.delaySpeed + * @comp Delay + * + * The rate of the delay. This property defaults to 1. + * When setting delaySpeed to 0.5, delays will take twice as long, + * setting it to 2.0 will make them twice as short + */ + delaySpeed: 1, + init: function () { this._delays = []; this._delaysPaused = false; - this.bind("EnterFrame", function (frameData) { + this.bind("UpdateFrame", function (frameData) { if (this._delaysPaused) return; var index = this._delays.length; while (--index >= 0) { @@ -6297,7 +5749,7 @@ module.exports = { // remove canceled item from array this._delays.splice(index, 1); } else { - item.accumulator+=frameData.dt; + item.accumulator += frameData.dt * this.delaySpeed; // The while loop handles the (pathological) case where dt>delay while(item.accumulator >= item.delay && item.repeat >= 0){ item.accumulator -= item.delay; @@ -6459,7 +5911,7 @@ module.exports = { } }; -},{}],17:[function(require,module,exports){ +},{}],18:[function(require,module,exports){ var Crafty = require('../core/core.js'); @@ -6468,17 +5920,27 @@ var Crafty = require('../core/core.js'); * @category Animation * @kind Component * - * @trigger TweenEnd - when a tween finishes - String - property + * @trigger TweenEnd - when a tween finishes - Object - an object containing the properties that finished tweening * * Component to animate the change in 2D properties over time. */ module.exports = { + /**@ + * #.tweenSpeed + * @comp Tween + * + * The rate of the tween. This property defaults to 1. + * When setting tweenSpeed to 0.5, tweens will take twice as long, + * setting it to 2.0 will make them twice as short + */ + tweenSpeed: 1, + init: function(){ this.tweenGroup = {}; this.tweenStart = {}; this.tweens = []; - this.uniqueBind("EnterFrame", this._tweenTick); + this.uniqueBind("UpdateFrame", this._tweenTick); }, @@ -6486,7 +5948,7 @@ module.exports = { var tween, v, i; for ( i = this.tweens.length-1; i>=0; i--){ tween = this.tweens[i]; - tween.easing.tick(frameData.dt); + tween.easing.tick(frameData.dt * this.tweenSpeed); v = tween.easing.value(); this._doTween(tween.props, v); if (tween.easing.complete) { @@ -6618,40 +6080,69 @@ module.exports = { * Stops tweening the specified group of properties, and fires the "TweenEnd" event. */ _endTween: function(properties){ + var notEmpty = false; for (var propname in properties){ + notEmpty = true; delete this.tweenGroup[propname]; } - this.trigger("TweenEnd", properties); + if (notEmpty) this.trigger("TweenEnd", properties); } }; -},{"../core/core.js":9}],18:[function(require,module,exports){ -module.exports = "0.8.0"; -},{}],19:[function(require,module,exports){ -var Crafty = require('./core/core'); +},{"../core/core.js":10}],19:[function(require,module,exports){ +module.exports = "0.9.0-rc2"; +},{}],20:[function(require,module,exports){ +// Define common features available in both browser and node +module.exports = function(requireNew) { + if (requireNew) { + require = requireNew; // jshint ignore:line + } + + var Crafty = require('./core/core'); + require('./core/extensions'); + + Crafty.easing = require('./core/animation'); + Crafty.c('Model', require('./core/model')); + Crafty.extend(require('./core/scenes')); + Crafty.storage = require('./core/storage'); + Crafty.c('Delay', require('./core/time')); + Crafty.c('Tween', require('./core/tween')); + + var HashMap = require('./spatial/spatial-grid'); + Crafty.HashMap = HashMap; + Crafty.map = new HashMap(); + + require('./core/systems'); + + require('./spatial/2d'); + require('./spatial/motion'); + require('./spatial/platform'); + require('./spatial/collision'); + require('./spatial/rect-manager'); + require('./spatial/math'); + + require('./controls/controls-system'); + require('./controls/controls'); + require('./controls/keyboard'); + require('./controls/keycodes'); + require('./controls/mouse'); + require('./controls/touch'); + + require('./debug/logging'); + + return Crafty; +}; +},{"./controls/controls":4,"./controls/controls-system":3,"./controls/keyboard":5,"./controls/keycodes":6,"./controls/mouse":7,"./controls/touch":8,"./core/animation":9,"./core/core":10,"./core/extensions":11,"./core/model":13,"./core/scenes":14,"./core/storage":15,"./core/systems":16,"./core/time":17,"./core/tween":18,"./debug/logging":23,"./spatial/2d":54,"./spatial/collision":55,"./spatial/math":56,"./spatial/motion":57,"./spatial/platform":58,"./spatial/rect-manager":59,"./spatial/spatial-grid":60}],21:[function(require,module,exports){ +// Define common features +var Crafty = require('./crafty-common.js')(); + +// Define features only available in browser environment -Crafty.easing = require('./core/animation'); -Crafty.extend(require('./core/extensions')); Crafty.extend(require('./core/loader')); -Crafty.c('Model', require('./core/model')); -Crafty.extend(require('./core/scenes')); -Crafty.storage = require('./core/storage'); -Crafty.c('Delay', require('./core/time')); -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'); +Crafty.extend(require('./inputs/dom-events')); // Needs to be required before any specific layers are require('./graphics/layers'); - require('./graphics/canvas'); require('./graphics/canvas-layer'); require('./graphics/webgl'); @@ -6675,25 +6166,27 @@ require('./graphics/viewport'); require('./isometric/diamond-iso'); require('./isometric/isometric'); -require('./controls/inputs'); -require('./controls/controls-system'); -require('./controls/controls'); -require('./controls/device'); -require('./controls/keycodes'); +// Needs to be required before any specific inputs are +require('./inputs/util'); +require('./inputs/device'); +require('./inputs/keyboard'); +require('./inputs/lifecycle'); +require('./inputs/mouse'); +require('./inputs/pointer'); +require('./inputs/touch'); require('./sound/sound'); require('./debug/debug-layer'); -require('./debug/logging'); // Define some aliases for renamed properties require('./aliases').defineAliases(Crafty); -if(window) window.Crafty = Crafty; +if (window) window.Crafty = Crafty; module.exports = Crafty; -},{"./aliases":2,"./controls/controls":4,"./controls/controls-system":3,"./controls/device":5,"./controls/inputs":6,"./controls/keycodes":7,"./core/animation":8,"./core/core":9,"./core/extensions":10,"./core/loader":11,"./core/model":12,"./core/scenes":13,"./core/storage":14,"./core/systems":15,"./core/time":16,"./core/tween":17,"./debug/debug-layer":20,"./debug/logging":21,"./graphics/canvas":23,"./graphics/canvas-layer":22,"./graphics/color":24,"./graphics/dom":27,"./graphics/dom-helper":25,"./graphics/dom-layer":26,"./graphics/drawing":28,"./graphics/gl-textures":29,"./graphics/html":30,"./graphics/image":31,"./graphics/layers":32,"./graphics/particles":33,"./graphics/renderable":34,"./graphics/sprite":36,"./graphics/sprite-animation":35,"./graphics/text":37,"./graphics/viewport":38,"./graphics/webgl":40,"./graphics/webgl-layer":39,"./isometric/diamond-iso":41,"./isometric/isometric":42,"./sound/sound":43,"./spatial/2d":44,"./spatial/collision":45,"./spatial/math":46,"./spatial/motion":47,"./spatial/platform":48,"./spatial/rect-manager":49,"./spatial/spatial-grid":50}],20:[function(require,module,exports){ +},{"./aliases":2,"./core/loader":12,"./crafty-common.js":20,"./debug/debug-layer":22,"./graphics/canvas":25,"./graphics/canvas-layer":24,"./graphics/color":26,"./graphics/dom":29,"./graphics/dom-helper":27,"./graphics/dom-layer":28,"./graphics/drawing":30,"./graphics/gl-textures":31,"./graphics/html":32,"./graphics/image":33,"./graphics/layers":34,"./graphics/particles":35,"./graphics/renderable":36,"./graphics/sprite":38,"./graphics/sprite-animation":37,"./graphics/text":39,"./graphics/viewport":40,"./graphics/webgl":42,"./graphics/webgl-layer":41,"./inputs/device":43,"./inputs/dom-events":44,"./inputs/keyboard":45,"./inputs/lifecycle":46,"./inputs/mouse":47,"./inputs/pointer":48,"./inputs/touch":49,"./inputs/util":50,"./isometric/diamond-iso":51,"./isometric/isometric":52,"./sound/sound":53}],22:[function(require,module,exports){ var Crafty = require('../core/core.js'), document = window.document; @@ -6709,7 +6202,10 @@ var Crafty = require('../core/core.js'), * * Crafty.debugCanvas.init() will be automatically called if it is not called already to initialize the canvas element. * - * To visualise an object's MBR, use "VisibleMBR". To visualise a "Collision" object's hitbox, use "WiredHitBox" or "SolidHitBox". + * To visualise an object's MBR, use "WiredMBR" or "SolidMBR". + * To visualise a "Collision" object's hitbox, use "WiredHitBox" or "SolidHitBox". + * To visualize an entity's click area, use "WiredAreaMap" or "SolidAreaMap". + * * @see DebugPolygon, DebugRectangle */ Crafty.c("DebugCanvas", { @@ -6875,35 +6371,59 @@ Crafty.c("DebugRectangle", { }); - /**@ - * #VisibleMBR + * #WiredMBR * @category Debug * @kind Component * - * Adding this component to an entity will cause it's MBR to be drawn to the debug canvas. + * Adding this component to an entity will cause its MBR to be drawn to the debug canvas as an outline. * * The methods of DebugCanvas can be used to control this component's appearance. + * * @see 2D, DebugRectangle, DebugCanvas */ -Crafty.c("VisibleMBR", { +Crafty.c("WiredMBR", { init: function () { this.requires("DebugRectangle") - .debugFill("purple") - .bind("EnterFrame", this._assignRect); + .debugStroke("purple"); }, - // Internal method for updating the MBR drawn. - _assignRect: function () { - if (this._mbr) - this.debugRectangle(this._mbr); - else - this.debugRectangle(this); - + events: { + "PreRender": function () { + // Internal method for updating the MBR drawn. + this.debugRectangle(this._mbr || this); + } } +}); -}); +/**@ + * #SolidMBR + * @category Debug + * @kind Component + * + * Adding this component to an entity will cause its MBR to be drawn to the debug canvas. + * + * The methods of DebugCanvas can be used to control this component's appearance. + * + * @see 2D, DebugRectangle, DebugCanvas + */ +var solidMBR = { + init: function () { + this.requires("DebugRectangle") + .debugFill("pink"); + }, + + events: { + "PreRender": function () { + // Internal method for updating the MBR drawn. + this.debugRectangle(this._mbr || this); + } + } +}; +Crafty.c("SolidMBR", solidMBR); +// DEPRECATED: remove this in an upcoming release +Crafty.c("VisibleMBR", solidMBR); /**@ @@ -6915,8 +6435,6 @@ Crafty.c("VisibleMBR", { * * The methods of DebugCanvas can be used to control this component's appearance -- by default it is neither filled nor outlined * - * For debugging hitboxes, use WiredHitBox or SolidHitBox. For debugging MBR, use VisibleMBR - * * @see DebugCanvas */ Crafty.c("DebugPolygon", { @@ -7125,12 +6643,17 @@ Crafty.DebugCanvas = { 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)); + // If the current entity has no layer, switch back to the viewport's transform + if (lastLayer !== current._drawLayer){ + if (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)); + } else { + view = Crafty.viewport; + 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); } @@ -7138,7 +6661,7 @@ Crafty.DebugCanvas = { }; -},{"../core/core.js":9}],21:[function(require,module,exports){ +},{"../core/core.js":10}],23:[function(require,module,exports){ var Crafty = require('../core/core.js'); @@ -7179,10 +6702,9 @@ Crafty.extend({ } } }); -},{"../core/core.js":9}],22:[function(require,module,exports){ +},{"../core/core.js":10}],24:[function(require,module,exports){ var Crafty = require('../core/core.js'); - /**@ * #CanvasLayer * @category Graphics @@ -7195,22 +6717,16 @@ var Crafty = require('../core/core.js'); Crafty._registerLayerTemplate("Canvas", { type: "Canvas", - options: { - xResponse: 1, - yResponse: 1, - scaleResponse: 1, - z: 0 - }, - - _dirtyRects: [], - _changedObjs: [], layerCount: 0, - _dirtyViewport: false, + _changedObjs: null, + + _dirtyRects: null, + _dirtyCells: null, + _viewKeys: null, + __tempRect: null, + __tempSearchRect: null, + __tempScreenRect: null, - // Sort function for rendering in the correct order - _sort: function(a, b) { - return a._globalZ - b._globalZ; - }, /**@ * #.dirty @@ -7282,9 +6798,21 @@ Crafty._registerLayerTemplate("Canvas", { */ _canvas: null, + events: { + // Respond to init & remove events + "LayerInit": "layerInit", + "LayerRemove": "layerRemove", + // Bind scene rendering (see drawing.js) + "RenderScene": "_render", + // Listen for pixelart changes + "PixelartSet": "_setPixelart", + // Handle viewport modifications + "ViewportResize": "_resize" + }, + // When the system is first created, create the necessary canvas element and initial state // Bind to the necessary events - init: function () { + layerInit: function () { //check if canvas is supported if (!Crafty.support.canvas) { Crafty.trigger("NoCanvas"); @@ -7293,8 +6821,13 @@ Crafty._registerLayerTemplate("Canvas", { } // set referenced objects to initial values -- necessary to avoid shared state between systems - this._dirtyRects = []; this._changedObjs = []; + this._dirtyRects = []; + this._dirtyCells = {}; + this._viewKeys = { x1: 0, y1: 0, x2: 0, y2: 0 }; + this.__tempRect = { _x: 0, _y: 0, _w: 0, _h: 0 }; + this.__tempSearchRect = { _x: 0, _y: 0, _w: 0, _h: 0 }; + this.__tempScreenRect = { _x: 0, _y: 0, _w: 0, _h: 0 }; //create an empty canvas element var c; @@ -7314,28 +6847,11 @@ Crafty._registerLayerTemplate("Canvas", { var zoom = Crafty.viewport._scale; if (zoom !== 1) this.context.scale(zoom, zoom); - - // Set pixelart to current status, and listen for changes - this._setPixelart(Crafty._pixelartEnabled); - this.uniqueBind("PixelartSet", this._setPixelart); - - //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); }, // When the system is destroyed, remove related resources - remove: function() { - + layerRemove: function() { this._canvas.parentNode.removeChild(this._canvas); - Crafty._removeDrawLayerInstance(this); }, _render: function() { @@ -7345,7 +6861,7 @@ Crafty._registerLayerTemplate("Canvas", { 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) { @@ -7356,119 +6872,77 @@ Crafty._registerLayerTemplate("Canvas", { ctx.setTransform(scale, 0, 0, scale, Math.round(dx), Math.round(dy) ); } - //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! + // TODO: check if these conditions really make that much sense! + // if the amount of changed objects is over 60% of the total objects, do the naive method redrawing if (l / this.layerCount > 0.6 || dirtyViewport) { this._drawAll(); + // otherwise draw dirty cell grid regions } else { - this._drawDirty(); + this._drawDirtyCells(); } + //Clean up lists etc this._clean(); }, /**@ - * #._drawDirty + * #._drawDirtyCells * @comp CanvasLayer * @kind Method * @private - * - * @sign public ._drawDirty() + * + * @sign public ._drawDirtyCells([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. * * - 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. + * - This method is invoked if the number of rects is under 60% of the total number of objects + * and the total number of objects is greater than 16. + * - Clear the dirty spatial grid cells, and redraw entities overlapping the dirty spatial grid cells. * * @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); + _drawDirtyCells: function (view) { + var viewportRect = this._viewportRect(), // this updates the viewportRect for later cached use + rect = this.__tempRect, + dirtyRects = this._dirtyRects, + integerBounds = Crafty.rectManager.integerBounds, + ctx = this.context; + var i, l; + // Canvas works better with integral coordinates where possible + view = integerBounds(view || viewportRect); - l = dirty.length; + // Calculate dirty spatial map cells from all changed objects + // Don't include cells outside rect to be drawn (e.g. viewport) + this._createDirtyCells(view); + // Afterwards, calculate dirty rectangles from dirty spatial map cells + this._createDirtyRects(); // 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 = rectManager.integerBounds(rect); - - // If a dirty rect doesn't overlap with the viewport, skip to the next one - if (!overlap(rect, view)) continue; - - //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._drawLayer !== this) ) - continue; - dupes[obj[0]] = true; - objs.push(obj); - } - - // 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(); + for (i = 0, l = dirtyRects.length; i < l; i += 4) { //loop over every dirty rect + rect._x = dirtyRects[i + 0]; + rect._y = dirtyRects[i + 1]; + rect._w = dirtyRects[i + 2]; + rect._h = dirtyRects[i + 3]; + // Draw the rectangle + this._drawRect(rect); } // 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); + var frame = Crafty.frame(), + r = (6 * frame + 0) % 255, + g = (6 * frame + 85) % 255, + b = (6 * frame + 170) % 255; + ctx.strokeStyle = "rgb(" + r + ", " + g + ", " + b + ")"; + for (i = 0, l = dirtyRects.length; i < l; i += 4) { + ctx.strokeRect(dirtyRects[i + 0], dirtyRects[i + 1], dirtyRects[i + 2], dirtyRects[i + 3]); } } - }, /**@ @@ -7477,32 +6951,91 @@ Crafty._registerLayerTemplate("Canvas", { * @kind Method * @private * - * @sign public CanvasLayer.drawAll([Object rect]) + * @sign public ._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 + * - If rect is provided, redraw within the rect. + * + * - Triggered by the "RenderScene" event + * - This method is invoked if the number of rects is over 60% of the total number of objects. + * - Clear the whole viewport, and redraw entities overlapping it by default. + * + * @see Canvas#.draw */ - _drawAll: function (rect) { - rect = rect || this._viewportRect(); - rect = Crafty.rectManager.integerBounds(rect); - var q = Crafty.map.search(rect), - i = 0, - l = q.length, + _drawAll: function (view) { + var viewportRect = this._viewportRect(); // this updates the viewportRect for later cached use + + // Draw the whole layer rectangle + this._drawRect(view || viewportRect); + }, + + _drawRect: function(rect) { + var i, l, q, obj, previousGlobalZ, + integerBounds = Crafty.rectManager.integerBounds, ctx = this.context, - current; + searchRect = this.__tempSearchRect, + screenRect = this.__tempScreenRect; - ctx.clearRect(rect._x, rect._y, rect._w, rect._h); + // Compute the final screen coordinates for the rectangle + screenRect = this._viewTransformRect(rect, screenRect, true); // use cached viewportRect + // Find the smallest rectangle with integer coordinates that encloses screenRect + screenRect = integerBounds(screenRect); + + // Find the smallest rectangle with integer coordinates that encloses rect + rect = integerBounds(rect); - //sort the objects by the global Z + // Search for ents under dirty rect. + // + // Don't need to search for full entity dimensions! + // If coordinates are integers, the search area with default _w and _h is (1 px - Number.Epsilon px) too big. + // Thus trim the search area accordingly. + // Otherwise unecessary neighboring grid cells would be searched for entities, + // these additional entities would later be removed because of failing condition rectManager.overlap + // This is a performance optimization for dirty cell drawing, to only search one grid cell for each rect, + // else three additional cells would be searched unnecessarily. + searchRect._x = rect._x; + searchRect._y = rect._y; + searchRect._w = rect._w - 1; + searchRect._h = rect._h - 1; + q = Crafty.map.search(searchRect); + // Sort objects by z level, duplicate objs will be ordered next to each other due to same _globalZ 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; + + + // save context before drawing, saves e.g. infinite clip region + ctx.save(); + + // Clip and clear works best with default identity transform, + // but do the actual clipping after restoring viewport transform, + // as the clipping region would be disgarded otherwise + ctx.save(); + ctx.setTransform(1, 0, 0, 1, 0, 0); + // Clip drawing region to dirty rectangle + ctx.beginPath(); + ctx.rect(screenRect._x, screenRect._y, screenRect._w, screenRect._h); + // Clear the rect from the main canvas + ctx.clearRect(screenRect._x, screenRect._y, screenRect._w, screenRect._h); + ctx.restore(); + ctx.clip(); + + // Then draw each visible canvas object from this layer in that order, avoiding duplicates + // No need to check for overlap with drawing area, as it's a single grid cell or the entire viewport + // -> in both cases all entities returned from collision search overlap that drawing area + previousGlobalZ = -Infinity; + for (i = 0, l = q.length; i < l; ++i) { + obj = q[i]; + + if (obj._globalZ > previousGlobalZ && obj._visible && obj._drawLayer === this) { + obj.draw(ctx); + obj._changed = false; + + previousGlobalZ = obj._globalZ; } } + + // restore context after drawing, restores e.g. clip regions + ctx.restore(); }, debug: function() { @@ -7511,53 +7044,93 @@ Crafty._registerLayerTemplate("Canvas", { /** 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) { - - 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); + var dirtyKeys, staleKeys, obj, i, l; + + var changed = this._changedObjs; + for (i = 0, l = changed.length; i < l; i++) { + obj = changed[i]; + + // we need to keep track of all stale states, because drawing method can change dynamically + // track stale grid cell keys for dirty grid cell drawing + dirtyKeys = obj._entry.keys; // cached computation of Crafty.HashMap.key(obj) + staleKeys = obj.staleKeys; + if (staleKeys === undefined) obj.staleKeys = staleKeys = { x1: 0, y1: 0, x2: 0, y2: 0 }; + staleKeys.x1 = dirtyKeys.x1; + staleKeys.y1 = dirtyKeys.y1; + staleKeys.x2 = dirtyKeys.x2; + staleKeys.y2 = dirtyKeys.y2; + + obj._changed = false; + } + changed.length = 0; + + this._dirtyCells = {}; + this._dirtyRects.length = 0; + + this._dirtyViewport = false; + }, + + // Takes the current and previous position of changed objects and + // adds the dirty spatial map cells they are contained in to a set. + // + // If a dirty cell doesn't overlap with the area to be drawn (e.g. viewport), + // don't include it + // + _createDirtyCells: function (view) { + var changed = this._changedObjs, + dirtyCells = this._dirtyCells; + var viewKeys = Crafty.HashMap.key(view, this._viewKeys); + + var i, l, j, k, obj, keys; + for (i = 0, l = changed.length; i < l; i++) { + obj = changed[i]; + + // if object was previously drawn it's old position needs to be redrawn (cleared) + if ((keys = obj.staleKeys)) { // cached computation of stale keys + for (j = keys.x1; j <= keys.x2; j++) { + for (k = keys.y1; k <= keys.y2; k++) { + // if stale cell is inside area to be drawn + if (viewKeys.x1 <= j && j <= viewKeys.x2 && + viewKeys.y1 <= k && k <= viewKeys.y2) { + + // combine two 16 bit unsigned numbers into a unique 32 bit unsigned number + dirtyCells[(j << 16) ^ k] = true; + } + } + } + } + + keys = obj._entry.keys; // cached computation of Crafty.HashMap.key(obj) + for (j = keys.x1; j <= keys.x2; j++) { + for (k = keys.y1; k <= keys.y2; k++) { + // if dirty cell is inside area to be drawn + if (viewKeys.x1 <= j && j <= viewKeys.x2 && + viewKeys.y1 <= k && k <= viewKeys.y2) { + + // combine two 16 bit unsigned numbers into a unique 32 bit unsigned number + dirtyCells[(j << 16) ^ k] = true; + } + } } } + }, - // 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); + // Takes all dirty spatial map cells and + // pushes the corresponding dirty rectangles onto the stack. + // + _createDirtyRects: function() { + var cellsize = Crafty.HashMap.cellsize(), + dirtyCells = this._dirtyCells, + dirtyRects = this._dirtyRects; + var hash, j, k; + for (var strHash in dirtyCells) { + hash = +strHash; + // deconstruct a 32 bit unsigned number into a unique pair of 16 bit unsigned numbers + k = (hash << 16) >> 16; + j = (k < 0) ? ~(hash >> 16) : hash >> 16; + dirtyRects.push(j * cellsize, k * cellsize, cellsize, cellsize); + } }, @@ -7580,7 +7153,7 @@ Crafty._registerLayerTemplate("Canvas", { }); -},{"../core/core.js":9}],23:[function(require,module,exports){ +},{"../core/core.js":10}],25:[function(require,module,exports){ var Crafty = require('../core/core.js'); @@ -7627,7 +7200,7 @@ Crafty.c("Canvas", { * @comp Canvas * @kind Method * - * @sign public this .draw([[Context ctx, ]Number x, Number y, Number w, Number h]) + * @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 * @param y - Y offset for drawing a segment @@ -7653,13 +7226,6 @@ Crafty.c("Canvas", { draw: function (ctx, x, y, w, h) { if (!this.ready) return; - if (arguments.length === 4) { - h = w; - w = y; - y = x; - x = ctx; - ctx = this._drawContext; - } var pos = this.drawVars.pos; pos._x = (this._x + (x || 0)); @@ -7722,7 +7288,7 @@ Crafty.c("Canvas", { } }); -},{"../core/core.js":9}],24:[function(require,module,exports){ +},{"../core/core.js":10}],26:[function(require,module,exports){ var Crafty = require('../core/core.js'), document = window.document; @@ -7907,6 +7473,9 @@ Crafty.c("Color", { ready: true, init: function () { + // Necessary for some rendering layers + this.__coord = this.__coord || [0, 0, 0, 0]; + this.bind("Draw", this._drawColor); if (this._drawLayer) { this._setupColor(this._drawLayer); @@ -8007,7 +7576,7 @@ Crafty.c("Color", { }); -},{"../core/core.js":9}],25:[function(require,module,exports){ +},{"../core/core.js":10}],27:[function(require,module,exports){ var Crafty = require('../core/core.js'), document = window.document; @@ -8023,29 +7592,26 @@ Crafty.extend({ /**@ * #Crafty.domHelper.innerPosition * @comp Crafty.domHelper - * @sign public Object Crafty.domHelper.innerPosition(HTMLElement obj) + * @sign public Object Crafty.domHelper.innerPosition(HTMLElement obj[, Object out]) * @param obj - HTML element to calculate the position + * @param out - optional object to save result in * @returns Object with `x` key being the `x` position, `y` being the `y` position * * Find a DOM elements position including * padding and border. */ - innerPosition: function (obj) { + innerPosition: function (obj, out) { + out = out || {}; var rect = obj.getBoundingClientRect(), x = rect.left + (window.pageXOffset ? window.pageXOffset : document.body.scrollLeft), y = rect.top + (window.pageYOffset ? window.pageYOffset : document.body.scrollTop), - //border left borderX = parseInt(this.getStyle(obj, 'border-left-width') || 0, 10) || parseInt(this.getStyle(obj, 'borderLeftWidth') || 0, 10) || 0, borderY = parseInt(this.getStyle(obj, 'border-top-width') || 0, 10) || parseInt(this.getStyle(obj, 'borderTopWidth') || 0, 10) || 0; - x += borderX; - y += borderY; - - return { - x: x, - y: y - }; + out.x = x + borderX; + out.y = y + borderY; + return out; }, /**@ @@ -8094,10 +7660,11 @@ Crafty.extend({ * @comp Crafty.domHelper * @kind Method * - * @sign public Object Crafty.domHelper.translate(Number clientX, Number clientY[, DrawLayer layer]) + * @sign public Object Crafty.domHelper.translate(Number clientX, Number clientY[, DrawLayer layer[, Object out]]) * @param clientX - clientX position in the browser screen * @param clientY - clientY position in the browser screen * @param layer - a Crafty draw layer + * @param out - an optional object to save result in * @return Object `{x: ..., y: ...}` with Crafty coordinates. * * The parameters clientX and clientY are pixel coordinates within the visible @@ -8107,7 +7674,8 @@ Crafty.extend({ * * If a draw layer is specified, the returned object will take into account any special scaling rules for that object. */ - translate: function (clientX, clientY, layer) { + translate: function (clientX, clientY, layer, out) { + out = out || {}; var doc = document.documentElement; var body = document.body; var view; @@ -8116,21 +7684,18 @@ Crafty.extend({ // 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 - }; + out.x = (clientX - Crafty.stage.x + (doc && doc.scrollLeft || body && body.scrollLeft || 0)) / view._scale + view._x; + out.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 - }; + out.x = (clientX - Crafty.stage.x + (doc && doc.scrollLeft || body && body.scrollLeft || 0)) / view._scale - view._x; + out.y = (clientY - Crafty.stage.y + (doc && doc.scrollTop || body && body.scrollTop || 0)) / view._scale - view._y; } + return out; } } }); -},{"../core/core.js":9}],26:[function(require,module,exports){ +},{"../core/core.js":10}],28:[function(require,module,exports){ var Crafty = require('../core/core.js'), document = window.document; @@ -8144,15 +7709,8 @@ var Crafty = require('../core/core.js'), */ Crafty._registerLayerTemplate("DOM", { type: "DOM", - options: { - xResponse: 1, - yResponse: 1, - scaleResponse: 1, - z: 0 - }, _changedObjs: [], - _dirtyViewport: false, /**@ * #._div @@ -8164,7 +7722,20 @@ Crafty._registerLayerTemplate("DOM", { */ _div: null, - init: function () { + events: { + // Respond to init & remove events + "LayerInit": "layerInit", + "LayerRemove": "layerRemove", + // Bind scene rendering (see drawing.js) + "RenderScene": "_render", + // Listen for pixelart changes + "PixelartSet": "_setPixelart" + // Layers should generally listen for resize events, + // but the DOM layers automatically inherit the stage's dimensions + //"ViewportResize": "_resize" + }, + + layerInit: function () { // Avoid shared state between systems this._changedObjs = []; @@ -8175,26 +7746,11 @@ Crafty._registerLayerTemplate("DOM", { div.style.position = "absolute"; div.style.zIndex = this.options.z; div.style.transformStyle = "preserve-3d"; // Seems necessary for Firefox to preserve zIndexes? - - // Bind scene rendering (see drawing.js) - this.uniqueBind("RenderScene", this._render); - - // Layers should generally listen for resize events, but the DOM layers automatically inherit the stage's dimensions - - // 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); }, // Cleanup the DOM when the layer is destroyed - remove: function() { + layerRemove: function() { this._div.parentNode.removeChild(this._div); - Crafty._removeDrawLayerInstance(this); }, // Handle whether images should be smoothed or not @@ -8333,7 +7889,7 @@ Crafty._registerLayerTemplate("DOM", { } }); -},{"../core/core.js":9}],27:[function(require,module,exports){ +},{"../core/core.js":10}],29:[function(require,module,exports){ var Crafty = require('../core/core.js'), document = window.document; @@ -8552,10 +8108,18 @@ Crafty.c("DOM", { return this; }, + _setCssProperty: function(style, key, val) { + key = Crafty.domHelper.camelize(key); + if (typeof val === "number") val += 'px'; + style[key] = val; + this.trigger("SetStyle", key); + }, + /**@ * #.css * @comp DOM * @kind Method + * @trigger SetStyle - for each style that is set - string - propertyName * * @sign public css(String property, String value) * @param property - CSS property to modify @@ -8575,13 +8139,15 @@ Crafty.c("DOM", { * To return a value, pass the property. * * Note: For entities with "Text" component, some css properties are controlled by separate functions - * `.textFont()` and `.textColor()`, and ignore `.css()` settings. See Text component for details. + * `.textFont()`, `.textAlign()` and `.textColor()`. When possible, prefer text-specific methods, since + * they will work for non-DOM text. + * See the Text component for details. * * @example * ~~~ - * this.css({'border-radius': '5px', 'text-decoration': 'line-through'}); - * this.css("borderRadius", "10px"); - * this.css("border-radius"); //returns 10px + * this.css({'border': '1px solid black', 'text-decoration': 'line-through'}); + * this.css("textDecoration", "line-through"); + * this.css("text-Decoration"); //returns line-through * ~~~ */ css: function (obj, value) { @@ -8595,30 +8161,50 @@ Crafty.c("DOM", { for (key in obj) { if (!obj.hasOwnProperty(key)) continue; val = obj[key]; - if (typeof val === "number") val += 'px'; - - style[Crafty.domHelper.camelize(key)] = val; + this._setCssProperty(style, key, val); } } else { //if a value is passed, set the property if (value) { - if (typeof value === "number") value += 'px'; - style[Crafty.domHelper.camelize(obj)] = value; + this._setCssProperty(style, obj, value); } else { //otherwise return the computed property return Crafty.domHelper.getStyle(elem, obj); } } this.trigger("Invalidate"); + return this; } }); -},{"../core/core.js":9}],28:[function(require,module,exports){ +},{"../core/core.js":10}],30:[function(require,module,exports){ var Crafty = require('../core/core.js'); Crafty.extend({ + /**@ + * #Crafty.background + * @category Graphics, Stage + * @kind Method + * + * @sign public void Crafty.background(String style) + * @param style - Modify the background with a color or image + * + * This method is a shortcut for adding a background + * style to the stage element, i.e. + * `Crafty.stage.elem.style.background = ...` + * + * For example, if you want the background to be white, + * with an image in the center, you might use: + * ~~~ + * Crafty.background('#FFFFFF url(landscape.png) no-repeat center center'); + * ~~~ + */ + background: function (style) { + Crafty.stage.elem.style.background = style; + }, + /**@ * #Crafty.pixelart * @category Graphics @@ -8663,7 +8249,7 @@ Crafty.extend({ } }); -},{"../core/core.js":9}],29:[function(require,module,exports){ +},{"../core/core.js":10}],31:[function(require,module,exports){ var Crafty = require('../core/core.js'); // An object for wrangling textures @@ -8851,7 +8437,7 @@ TextureWrapper.prototype = { gl.uniform2f(gl.getUniformLocation(shader, dimension_name), this.width, this.height); } }; -},{"../core/core.js":9}],30:[function(require,module,exports){ +},{"../core/core.js":10}],32:[function(require,module,exports){ var Crafty = require('../core/core.js'); @@ -8943,7 +8529,7 @@ Crafty.c("HTML", { return this; } }); -},{"../core/core.js":9}],31:[function(require,module,exports){ +},{"../core/core.js":10}],33:[function(require,module,exports){ var Crafty = require('../core/core.js'); @@ -8978,6 +8564,14 @@ Crafty.defaultShader("Image", new Crafty.WebGLShader( * @kind Component * * Draw an image with or without repeating (tiling). + * + * If the entity's width and height are smaller than the width and height of the image source, the image will appear cropped. + * If the entity's dimensions are larger than the dimensions of the image source, the exact appearance of the remaining space will depend on what renderer (WebGL, DOM, or Canvas) is used. + * However, if tiling is enabled, the remaining space will be filled by a repeating pattern of the image. + * + * @note Image scaling is not supported by this component. Use a spritesheet, defined by `Crafty.sprite`, consisting of a single `Sprite` instead. + * + * @see Sprite, Crafty.sprite */ Crafty.c("Image", { _repeat: "repeat", @@ -9095,7 +8689,7 @@ Crafty.c("Image", { } }); -},{"../core/core.js":9}],32:[function(require,module,exports){ +},{"../core/core.js":10}],34:[function(require,module,exports){ var Crafty = require('../core/core.js'); Crafty.extend({ @@ -9117,25 +8711,74 @@ Crafty.extend({ _registerLayerTemplate: function (type, layerTemplate) { this._drawLayerTemplates[type] = layerTemplate; var common = this._commonLayerProperties; + + // add common properties, don't overwrite existing ones 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); + // Layer options + options: { + xResponse: 1, + yResponse: 1, + scaleResponse: 1, + z: 0 + }, + + // A tracker for whether any elements in this layer need to listen to mouse/touch events + _pointerEntities: 0, + + // Track dirty viewport state - render code should uncheck flag once finished handling it + _dirtyViewport: false, + + // A cached version of the viewport rect + _cachedViewportRect: null, + + + init: function() { + this._cachedViewportRect = {}; + + // Trigger layer-specific init code + this.trigger("LayerInit"); + + // Handle viewport invalidation + this.uniqueBind("InvalidateViewport", function () { this._dirtyViewport = true; }); + // Set pixelart to current status + this.trigger("PixelartSet", Crafty._pixelartEnabled); + + Crafty._addDrawLayerInstance(this); + }, + + remove: function() { + // Trigger layer-specific remove code + this.trigger("LayerRemove"); + + Crafty._removeDrawLayerInstance(this); + }, + + // Sort function for rendering in the correct order + // Sort by globalZ + _sort: function(a, b) { + return a._globalZ - b._globalZ; + }, + + // Based on the camera options, find the Crafty coordinates + // corresponding to the layer's position in the viewport + _viewportRect: function (useCached) { + var rect = this._cachedViewportRect; + if (useCached) return rect; + + // total transform is viewport transform combined with this layer's transform var viewport = Crafty.viewport; + var options = this.options; + + var scale = Math.pow(viewport._scale, options.scaleResponse); 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, @@ -9145,10 +8788,23 @@ Crafty.extend({ 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 + + // transform a given rect to view space, depending on this layers' total transform + _viewTransformRect: function(rect, outRect, useCached) { + var view = this._viewportRect(useCached), + scale = view._scale; + + outRect = outRect || {}; + outRect._x = rect._x * scale + Math.round(-view._x * scale); + outRect._y = rect._y * scale + Math.round(-view._y * scale); + outRect._w = rect._w * scale; + outRect._h = rect._h * scale; + + return outRect; + } }, /**@ @@ -9226,29 +8882,66 @@ Crafty.extend({ }); } }); -},{"../core/core.js":9}],33:[function(require,module,exports){ -var Crafty = require('../core/core.js'), - document = window.document; +},{"../core/core.js":10}],35:[function(require,module,exports){ +var Crafty = require('../core/core.js'); + + +////////////////////////////////////////////////////////////////////////////////////////////////////// +// Particles are based on Parcycle by Mr. Speaker, licensed under the MIT, Ported by Leo Koppelkamm // +////////////////////////////////////////////////////////////////////////////////////////////////////// /**@ * #Particles * @category Graphics * @kind Component - * + * + * @trigger ParticleStart - when the particle animation has started * @trigger ParticleEnd - when the particle animation has finished * - * Based on Parcycle by Mr. Speaker, licensed under the MIT, Ported by Leo Koppelkamm + * Create particle effects. + * + * Particles won't be drawn outside the entity's bounds. Make sure to adjust the entity's dimensions accordingly. * - * @note This requires the canvas element, and won't do anything if the browser doesn't support it! + * @note Particles effects currently work exclusively with the Canvas render backend. + * Particles won't be drawn if the browser doesn't support this! * - * For implementation details, check out the source code. + * @see .particles */ Crafty.c("Particles", { + required: "Renderable", + ready: true, + + _particlesPaused: false, + init: function () { - //We need to clone it + // We need to clone particle handler object to avoid shared object trap this._Particles = Crafty.clone(this._Particles); + // Add default options + this._Particles.init(); + this._Particles.parentEntity = this; - this._particlesPaused = false; + }, + + events: { + "UpdateFrame": function () { + // don't update if paused or no particle fx active + if (this._particlesPaused || !this._Particles.active) return; + + // This updates all particle colors & positions + this._Particles.update(); + // Request redraw from render backend, as appearance has changed + this.trigger("Invalidate"); + }, + + "Draw": function (e) { + // don't render if no particle fx active, but do redraw paused particles + if (!this._Particles.active) return; + + if (e.type === "canvas") { + // This renders the updated particles + this._Particles.render(e); + } + } }, /**@ @@ -9256,9 +8949,17 @@ Crafty.c("Particles", { * @comp Particles * @kind Method * - * @sign public this .particles(Object options) + * @sign public this .particles([Object options]) * @param options - Map of options that specify the behavior and look of the particles. * + * Create a new particle animation. + * + * If the `options` object is missing a property, the default will be used. + * Default options are listed in the example below. + * + * Invoking this method without an `options` object will restart the particle animation + * with the previously used options, or the default options otherwise. + * * @example * ~~~ * var options = { @@ -9293,81 +8994,24 @@ Crafty.c("Particles", { * originOffset: {x: 0, y: 0} * }; * - * Crafty.e("2D,Canvas,Particles").particles(options); + * Crafty.e("2D, Canvas, Particles") + * .attr({ w: 200, h: 200 }) + * // debug entity's bounds while developing + * // make sure particles fit into entity's bounds + * .addComponent('WiredMBR') + * // init particle animation + * .particles(options); * ~~~ */ particles: function (options) { + // Overwrite default options + this._Particles.config(options); + // Start animation + this._Particles.start(); - if (!Crafty.support.canvas || Crafty.deactivateParticles) return this; - - //If we drew on the main canvas, we'd have to redraw - //potentially huge sections of the screen every frame - //So we create a separate canvas, where we only have to redraw - //the changed particles. - var c, ctx, relativeX, relativeY, bounding; - - 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"; - - Crafty.stage.elem.appendChild(c); - - ctx = c.getContext('2d'); - - this._Particles.init(options); - - // Clean up the DOM when this component is removed - this.bind('Remove', function () { - Crafty.stage.elem.removeChild(c); - }).bind("RemoveComponent", function (id) { - if (id === "particles") - Crafty.stage.elem.removeChild(c); - }); - - relativeX = this.x + Crafty.viewport.x; - relativeY = this.y + Crafty.viewport.y; - this._Particles.position = this._Particles.vectorHelpers.create(relativeX, relativeY); - - var oldViewport = { - x: Crafty.viewport.x, - y: Crafty.viewport.y - }; - - this.bind('EnterFrame', function () { - if (this._particlesPaused) return; - relativeX = this.x + Crafty.viewport.x; - relativeY = this.y + Crafty.viewport.y; - this._Particles.viewportDelta = { - x: Crafty.viewport.x - oldViewport.x, - y: Crafty.viewport.y - oldViewport.y - }; - - oldViewport = { - x: Crafty.viewport.x, - y: Crafty.viewport.y - }; - - this._Particles.position = this._Particles.vectorHelpers.create(relativeX, relativeY); - - //Selective clearing - 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 { - ctx.clearRect(0, 0, Crafty.viewport.width, Crafty.viewport.height); - } - - //This updates all particle colors & positions - this._Particles.update(); - - //This renders the updated particles - this._Particles.render(ctx); - }); return this; }, + _Particles: { presets: { maxParticles: 150, @@ -9401,221 +9045,235 @@ Crafty.c("Particles", { // sensible values are 0-3 jitter: 0, // offset of particles from origin - originOffset: {x: 0, y: 0}, - - //Don't modify the following - particles: [], - active: true, - particleCount: 0, - elapsedFrames: 0, - emissionRate: 0, - emitCounter: 0, - particleIndex: 0 + originOffset: {x: 0, y: 0} }, + emissionRate: 0, + elapsedFrames: 0, + emitCounter: 0, + active: true, + particles: [], - init: function (options) { - this.position = this.vectorHelpers.create(0, 0); - if (typeof options === 'undefined') options = {}; - - //Create current config by merging given options and presets. + init: function () { + // Create initial config by adding presets. for (var key in this.presets) { - if (typeof options[key] !== 'undefined') this[key] = options[key]; - else this[key] = this.presets[key]; + this[key] = this.presets[key]; } + }, + config: function (options) { + options = options || {}; + + // Create current config by merging in given options. + for (var key in options) { + this[key] = options[key]; + } this.emissionRate = this.maxParticles / this.lifeSpan; - this.positionRandom = this.vectorHelpers.create(this.spread, this.spread); - }, - addParticle: function () { - if (this.particleCount === this.maxParticles) { - return false; + // Create a new pool of particles, if it doesn't match existing one + if (this.particles.length !== this.maxParticles) { + this.particles.length = 0; + for (var i = 0, l = this.maxParticles; i < l; ++i) { + this.particles.push(new this.Particle()); + } } + }, - // Take the next particle out of the particle pool we have created and initialize it - var particle = new this.particle(this.vectorHelpers); - this.initParticle(particle); - this.particles[this.particleCount] = particle; - // Increment the particle count - this.particleCount++; + start: function () { + // (re)set active state + this.active = true; + this.elapsedFrames = 0; + this.emitCounter = 0; - return true; + // reset particle pool state between multiple animations, make sure timeToLive === 0 + var particles = this.particles; + for (var i = 0, l = particles.length; i < l; ++i) { + particles[i].timeToLive = 0; + } + + this.parentEntity.trigger("ParticleStart"); }, - RANDM1TO1: function () { - return Math.random() * 2 - 1; + + stop: function () { + // set disabled state + this.active = false; + + this.parentEntity.trigger("ParticleEnd"); }, + initParticle: function (particle) { - particle.position.x = Crafty.viewport._scale * (this.position.x + this.originOffset.x + this.positionRandom.x * this.RANDM1TO1()); - particle.position.y = Crafty.viewport._scale * (this.position.y + this.originOffset.y + this.positionRandom.y * this.RANDM1TO1()); + var angle, speed, size, timeToLive, sharpness, c, + startR, startG, startB, startA, + endR, endG, endB, endA; - var newAngle = (this.angle + this.angleRandom * this.RANDM1TO1()) * (Math.PI / 180); // convert to radians - var vector = this.vectorHelpers.create(Math.sin(newAngle), -Math.cos(newAngle)); // Could move to lookup for speed - var vectorSpeed = this.speed + this.speedRandom * this.RANDM1TO1(); - particle.direction = this.vectorHelpers.multiply(vector, vectorSpeed); + particle.timeToLive = timeToLive = this.lifeSpan + this.lifeSpanRandom * this.RANDM1TO1(); - particle.size = Crafty.viewport._scale * (this.size + this.sizeRandom * this.RANDM1TO1()); - particle.size = particle.size < 0 ? 0 : ~~particle.size; - particle.timeToLive = this.lifeSpan + this.lifeSpanRandom * this.RANDM1TO1(); + // TODO default to entity origin instead, deprecate originOffset + // TODO subtract size/2 from position + particle.positionX = this.originOffset.x + this.spread * this.RANDM1TO1(); + particle.positionY = this.originOffset.y + this.spread * this.RANDM1TO1(); - particle.sharpness = this.sharpness + this.sharpnessRandom * this.RANDM1TO1(); - particle.sharpness = particle.sharpness > 100 ? 100 : particle.sharpness < 0 ? 0 : particle.sharpness; - // internal circle gradient size - affects the sharpness of the radial gradient - particle.sizeSmall = ~~ ((particle.size / 200) * particle.sharpness); //(size/2/100) - var start = [ - 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() - ]; + angle = (this.angle + this.angleRandom * this.RANDM1TO1()) * (Math.PI / 180); // convert to radians + speed = this.speed + this.speedRandom * this.RANDM1TO1(); + // Could move to lookup for speed + particle.directionX = Math.sin(angle) * speed; + particle.directionY = -Math.cos(angle) * speed; - var end = [ - 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() - ]; + size = this.size + this.sizeRandom * this.RANDM1TO1(); + particle.size = size = size < 0 ? 0 : ~~size; - particle.colour = start; - particle.deltaColour[0] = (end[0] - start[0]) / particle.timeToLive; - particle.deltaColour[1] = (end[1] - start[1]) / particle.timeToLive; - particle.deltaColour[2] = (end[2] - start[2]) / particle.timeToLive; - particle.deltaColour[3] = (end[3] - start[3]) / particle.timeToLive; + sharpness = this.sharpness + this.sharpnessRandom * this.RANDM1TO1(); + particle.sharpness = sharpness = sharpness > 100 ? 100 : sharpness < 0 ? 0 : sharpness; + + // internal circle gradient size - affects the sharpness of the radial gradient + particle.sizeSmall = ~~ ((size / 200) * sharpness); //(size/2/100) + + c = startR = this.startColour[0] + this.startColourRandom[0] * this.RANDM1TO1(); + particle.colourR = c > 255 ? 255 : c < 0 ? 0 : ~~c; + c = startG = this.startColour[1] + this.startColourRandom[1] * this.RANDM1TO1(); + particle.colourG = c > 255 ? 255 : c < 0 ? 0 : ~~c; + c = startB = this.startColour[2] + this.startColourRandom[2] * this.RANDM1TO1(); + particle.colourB = c > 255 ? 255 : c < 0 ? 0 : ~~c; + c = startA = this.startColour[3] + this.startColourRandom[3] * this.RANDM1TO1(); + particle.colourA = c > 1 ? 1 : c < 0 ? 0 : (~~(c * 100)) / 100; + + endR = this.endColour[0] + this.endColourRandom[0] * this.RANDM1TO1(); + endG = this.endColour[1] + this.endColourRandom[1] * this.RANDM1TO1(); + endB = this.endColour[2] + this.endColourRandom[2] * this.RANDM1TO1(); + endA = this.endColour[3] + this.endColourRandom[3] * this.RANDM1TO1(); + + particle.deltaColourR = (endR - startR) / timeToLive; + particle.deltaColourG = (endG - startG) / timeToLive; + particle.deltaColourB = (endB - startB) / timeToLive; + particle.deltaColourA = (endA - startA) / timeToLive; }, + update: function () { - if (this.active && this.emissionRate > 0) { - var rate = 1 / this.emissionRate; - this.emitCounter++; - while (this.particleCount < this.maxParticles && this.emitCounter > rate) { - this.addParticle(); - this.emitCounter -= rate; - } - this.elapsedFrames++; - if (this.duration !== -1 && this.duration < this.elapsedFrames) { - this.stop(); - } + var RANDM1TO1 = this.RANDM1TO1; + var gravityX = this.gravity.x, + gravityY = this.gravity.y; + var jitter = this.jitter; + + // stop if duration elapsed + this.elapsedFrames++; + if (this.duration >= 0 && this.duration < this.elapsedFrames) { + this.stop(); } - this.particleIndex = 0; - this.register = []; - var draw; - while (this.particleIndex < this.particleCount) { + // update emission logic + var rate = this.emissionRate > 0 ? 1 / this.emissionRate : Infinity; + this.emitCounter++; - var currentParticle = this.particles[this.particleIndex]; + // update all particles + var c, particle, particles = this.particles; + for (var i = 0, l = particles.length; i < l; ++i) { + particle = particles[i]; // If the current particle is alive then update it - if (currentParticle.timeToLive > 0) { - - // Calculate the new direction based on gravity - currentParticle.direction = this.vectorHelpers.add(currentParticle.direction, this.gravity); - currentParticle.position = this.vectorHelpers.add(currentParticle.position, currentParticle.direction); - currentParticle.position = this.vectorHelpers.add(currentParticle.position, this.viewportDelta); - if (this.jitter) { - currentParticle.position.x += this.jitter * this.RANDM1TO1(); - currentParticle.position.y += this.jitter * this.RANDM1TO1(); + if (particle.timeToLive > 0) { + + // Calculate the new position based on gravity + particle.directionX += gravityX; + particle.directionY += gravityY; + particle.positionX += particle.directionX; + particle.positionY += particle.directionY; + if (jitter) { + particle.positionX += jitter * RANDM1TO1(); + particle.positionY += jitter * RANDM1TO1(); } - currentParticle.timeToLive--; // Update colours - var r = currentParticle.colour[0] += currentParticle.deltaColour[0]; - var g = currentParticle.colour[1] += currentParticle.deltaColour[1]; - var b = currentParticle.colour[2] += currentParticle.deltaColour[2]; - var a = currentParticle.colour[3] += currentParticle.deltaColour[3]; - - // Calculate the rgba string to draw. - draw = []; - draw.push("rgba(" + (r > 255 ? 255 : r < 0 ? 0 : ~~r)); - draw.push(g > 255 ? 255 : g < 0 ? 0 : ~~g); - draw.push(b > 255 ? 255 : b < 0 ? 0 : ~~b); - draw.push((a > 1 ? 1 : a < 0 ? 0 : a.toFixed(2)) + ")"); - currentParticle.drawColour = draw.join(","); - - if (!this.fastMode) { - draw[3] = "0)"; - currentParticle.drawColourEnd = draw.join(","); - } - - this.particleIndex++; - } else { - // Replace particle with the last active - if (this.particleIndex !== this.particleCount - 1) { - this.particles[this.particleIndex] = this.particles[this.particleCount - 1]; - } - this.particleCount--; + c = particle.colourR + particle.deltaColourR; + particle.colourR = c > 255 ? 255 : c < 0 ? 0 : ~~c; + c = particle.colourG + particle.deltaColourG; + particle.colourG = c > 255 ? 255 : c < 0 ? 0 : ~~c; + c = particle.colourB + particle.deltaColourB; + particle.colourB = c > 255 ? 255 : c < 0 ? 0 : ~~c; + c = particle.colourA + particle.deltaColourA; + particle.colourA = c > 1 ? 1 : c < 0 ? 0 : (~~(c * 100)) / 100; + + // Decrease particle's lifespan + particle.timeToLive--; + + // Else reinitialize particle if within emission rate + } else if (this.emitCounter > rate) { + this.initParticle(particle); + this.emitCounter -= rate; } - var rect = {}; - rect._x = ~~currentParticle.position.x; - rect._y = ~~currentParticle.position.y; - rect._w = currentParticle.size; - rect._h = currentParticle.size; - - this.register.push(rect); } }, - stop: function () { - this.active = false; - this.elapsedFrames = 0; - this.emitCounter = 0; - this.parentEntity.trigger("ParticleEnd"); - }, + render: function (e) { + var context = e.ctx; + var delim = ","; - render: function (context) { + var particle, particles = this.particles; + for (var i = 0, l = particles.length; i < l; i++) { + particle = particles[i]; - for (var i = 0, j = this.particleCount; i < j; i++) { - var particle = this.particles[i]; var size = particle.size; var halfSize = size >> 1; - if (particle.position.x + size < 0 || particle.position.y + size < 0 || particle.position.x - size > Crafty.viewport.width || particle.position.y - size > Crafty.viewport.height) { + if (particle.positionX < 0 || particle.positionX + size > e.pos._w || + particle.positionY < 0 || particle.positionY + size > e.pos._h) { //Particle is outside continue; } - var x = ~~particle.position.x; - var y = ~~particle.position.y; + var x = ~~(e.pos._x + particle.positionX); + var y = ~~(e.pos._y + particle.positionY); + + var r = particle.colourR, + g = particle.colourG, + b = particle.colourB, + a = particle.colourA; + // Calculate the rgba string to draw. + var drawColour = "rgba(" + r + delim + g + delim + b + delim + a + ")"; if (this.fastMode) { - context.fillStyle = particle.drawColour; + context.fillStyle = drawColour; } else { + var drawColourEnd = "rgba(" + r + delim + g + delim + b + delim + "0)"; + var radgrad = context.createRadialGradient(x + halfSize, y + halfSize, particle.sizeSmall, x + halfSize, y + halfSize, halfSize); - radgrad.addColorStop(0, particle.drawColour); + radgrad.addColorStop(0, drawColour); //0.9 to avoid visible boxing - radgrad.addColorStop(0.9, particle.drawColourEnd); + radgrad.addColorStop(0.9, drawColourEnd); context.fillStyle = radgrad; } context.fillRect(x, y, size, size); } }, - particle: function (vectorHelpers) { - this.position = vectorHelpers.create(0, 0); - this.direction = vectorHelpers.create(0, 0); + + Particle: function () { + this.positionX = 0; + this.positionY = 0; + + this.directionX = 0; + this.directionY = 0; + this.size = 0; this.sizeSmall = 0; + this.timeToLive = 0; - this.colour = []; - this.drawColour = ""; - this.deltaColour = []; + + this.colourR = 0; + this.colourG = 0; + this.colourB = 0; + this.colourA = 0; + + this.deltaColourR = 0; + this.deltaColourG = 0; + this.deltaColourB = 0; + this.deltaColourA = 0; + this.sharpness = 0; }, - vectorHelpers: { - create: function (x, y) { - return { - "x": x, - "y": y - }; - }, - multiply: function (vector, scaleFactor) { - vector.x *= scaleFactor; - vector.y *= scaleFactor; - return vector; - }, - add: function (vector1, vector2) { - vector1.x += vector2.x; - vector1.y += vector2.y; - return vector1; - } + + RANDM1TO1: function () { + return Math.random() * 2 - 1; } }, + /**@ * #.pauseParticles * @comp Particles @@ -9638,6 +9296,7 @@ Crafty.c("Particles", { pauseParticles: function() { this._particlesPaused = true; }, + /**@ * #.resumeParticles * @comp Particles @@ -9665,7 +9324,7 @@ Crafty.c("Particles", { } }); -},{"../core/core.js":9}],34:[function(require,module,exports){ +},{"../core/core.js":10}],36:[function(require,module,exports){ var Crafty = require('../core/core.js'); @@ -9716,7 +9375,7 @@ Crafty.c("Renderable", { }, // Setup all the properties that we need to define - _graphics_property_definitions: { + properties: { alpha: { set: function (v) { this._setterRenderable('_alpha', v); @@ -9742,15 +9401,21 @@ Crafty.c("Renderable", { _visible: {enumerable:false} }, - _defineRenderableProperites: function () { - for (var prop in this._graphics_property_definitions){ - Object.defineProperty(this, prop, this._graphics_property_definitions[prop]); - } + init: function () { }, - init: function () { - // create setters and getters that associate properties such as alpha/_alpha - this._defineRenderableProperites(); + // Need to store visibility before being frozen + _hideOnUnfreeze: false, + events: { + "Freeze":function(){ + this._hideOnUnfreeze = !this._visible; + this._visible = false; + this.trigger("Invalidate"); + }, + "Unfreeze":function(){ + this._visible = !this._hideOnUnfreeze; + this.trigger("Invalidate"); + } }, // Renderable assumes that a draw layer has 3 important methods: attach, detach, and dirty @@ -9837,7 +9502,7 @@ Crafty.c("Renderable", { return this; } }); -},{"../core/core.js":9}],35:[function(require,module,exports){ +},{"../core/core.js":10}],37:[function(require,module,exports){ var Crafty = require('../core/core.js'); @@ -9845,7 +9510,7 @@ var Crafty = require('../core/core.js'); * #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 } @@ -9910,7 +9575,7 @@ Crafty.c("SpriteAnimation", { * #.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]) @@ -9930,6 +9595,12 @@ Crafty.c("SpriteAnimation", { * @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, Duration duration, Array frames) + * Defines a reel by an explicit list of frames with sprite names + * @param reelId - ID of the animation reel being created + * @param duration - The length of the animation in milliseconds. + * @param frames - An array of strings containing the sprite names of successive frames: ['spriteName1','spriteName2',...]. + * * @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 @@ -9949,7 +9620,11 @@ Crafty.c("SpriteAnimation", { * ~~~ * // Define a sprite-map component * Crafty.sprite(16, "images/sprite.png", { - * PlayerSprite: [0,0] + * PlayerSprite: [0,0], + * PlayerIdle1: [0,1], + * PlayerLeftFootForward: [1,1], + * PlayerIdle2: [2,1], + * PlayerRightFootForward: [3,1] * }); * * // Define an animation on the second row of the sprite map (fromY = 1) @@ -9959,17 +9634,23 @@ Crafty.c("SpriteAnimation", { * * // 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]]); + * + * // This is the same animation definition, but uses sprite names instead of numbers + * Crafty.e("2D, DOM, SpriteAnimation, PlayerSprite") + * .reel('PlayerRunning', 1000, ['PlayerIdle1', 'PlayerLeftFootForward', 'PlayerIdle2', 'PlayerRightFootForward']); * ~~~ */ reel: function (reelId, duration, fromX, fromY, frameCount, rowLength) { // @sign public this .reel() - if (arguments.length === 0) + 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") + 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; @@ -10036,7 +9717,7 @@ Crafty.c("SpriteAnimation", { * #.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. @@ -10067,22 +9748,21 @@ Crafty.c("SpriteAnimation", { */ animate: function(reelId, loopCount) { // switch to the specified reel if necessary - if (typeof reelId === "string") - this.reel(reelId); + if (typeof reelId === "string") this.reel(reelId); var currentReel = this._currentReel; - if (typeof currentReel === "undefined" || currentReel === null) + 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; + if (typeof loopCount === "undefined") { + if (typeof reelId === "number") loopCount = reelId; + else loopCount = 1; + } // set the animation to the beginning currentReel.easing.reset(); @@ -10094,7 +9774,7 @@ Crafty.c("SpriteAnimation", { this._setFrame(0); // Start the anim - this.bind("EnterFrame", this._animationTick); + this.bind("UpdateFrame", this._animationTick); this._isPlaying = true; this.trigger("StartAnimation", currentReel); @@ -10105,7 +9785,7 @@ Crafty.c("SpriteAnimation", { * #.resumeAnimation * @comp SpriteAnimation * @kind Method - * + * * @sign public this .resumeAnimation() * * This will resume animation of the current reel from its current state. @@ -10113,7 +9793,7 @@ Crafty.c("SpriteAnimation", { */ resumeAnimation: function() { if (this._isPlaying === false && this._currentReel !== null) { - this.bind("EnterFrame", this._animationTick); + this.bind("UpdateFrame", this._animationTick); this._isPlaying = true; this._currentReel.easing.resume(); this.trigger("StartAnimation", this._currentReel); @@ -10126,14 +9806,14 @@ Crafty.c("SpriteAnimation", { * #.pauseAnimation * @comp SpriteAnimation * @kind Method - * + * * @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.unbind("UpdateFrame", this._animationTick); this._isPlaying = false; this._reels[this._currentReelId].easing.pause(); } @@ -10145,17 +9825,17 @@ Crafty.c("SpriteAnimation", { * #.resetAnimation * @comp SpriteAnimation * @kind Method - * + * * @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(){ + resetAnimation: function() { var currentReel = this._currentReel; - if (currentReel === null) - throw("No active reel to reset."); + if (currentReel === null) throw("No active reel to reset."); + this.reelPosition(0); currentReel.easing.repeat(currentReel.defaultLoops); @@ -10167,7 +9847,7 @@ Crafty.c("SpriteAnimation", { * #.loops * @comp SpriteAnimation * @kind Method - * + * * @sign public this .loops(Number loopCount) * @param loopCount - The number of times to play the animation * @@ -10178,16 +9858,16 @@ Crafty.c("SpriteAnimation", { * @returns The number of loops left. Returns 0 if no reel is active. */ loops: function(loopCount) { - if (arguments.length === 0){ + 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; + if (this._currentReel !== null) { + if (loopCount < 0) loopCount = Infinity; + this._currentReel.easing.repeat(loopCount); this._currentReel.defaultLoops = loopCount; } @@ -10198,7 +9878,7 @@ Crafty.c("SpriteAnimation", { /**@ * #.reelPosition * @kind Method - * + * * @comp SpriteAnimation * * @sign public this .reelPosition(Integer position) @@ -10217,16 +9897,18 @@ Crafty.c("SpriteAnimation", { * */ reelPosition: function(position) { - if (this._currentReel === null) - throw("No active reel."); + if (this._currentReel === null) throw("No active reel."); - if (arguments.length === 0) + if (arguments.length === 0) { return this._currentReel.currentFrame; + } var progress, l = this._currentReel.frames.length; - if (position === "end") + + if (position === "end") { position = l - 1; + } if (position < 1 && position > 0) { progress = position; @@ -10247,8 +9929,34 @@ Crafty.c("SpriteAnimation", { return this; }, + /**@ + * #.reelFrame + * @kind Method + * + * @comp SpriteAnimation + * + * @sign public this .reelFrame(String frameName) + * Sets the position of the current reel by frame name. + * @param frameName - Name in the sprite map. + * + * Jumps to specifed frame if the reel was created with sprite names. + * + */ + reelFrame: function (frameName) { + if (this._currentReel === null) throw("No active reel."); + + var index = this._currentReel.frames.indexOf(frameName); + + if (index === -1) { + throw("Frame name " + frameName + " is invalid."); + } + + this.reelPosition(index); + + return this; + }, - // Bound to "EnterFrame". Progresses the animation by dt, changing the frame if necessary. + // Bound to "UpdateFrame". 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]; @@ -10264,10 +9972,6 @@ Crafty.c("SpriteAnimation", { } }, - - - - // Set the current frame and update the displayed sprite // The actual progress for the animation must be set seperately. _setFrame: function(frameNumber) { @@ -10282,9 +9986,11 @@ Crafty.c("SpriteAnimation", { // 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 + var frame = currentReel.frames[currentReel.currentFrame]; + // .sprite will trigger redraw + if(typeof frame === "string") this.sprite(frame); + else this.sprite(frame[0], frame[1]); }, @@ -10298,7 +10004,7 @@ Crafty.c("SpriteAnimation", { * #.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 @@ -10322,7 +10028,7 @@ Crafty.c("SpriteAnimation", { * #.getReel * @comp SpriteAnimation * @kind Method - * + * * @sign public Reel .getReel() * @returns The current reel, or null if there is no active reel * @@ -10332,7 +10038,7 @@ Crafty.c("SpriteAnimation", { * */ getReel: function (reelId) { - if (arguments.length === 0){ + if (arguments.length === 0) { if (!this._currentReelId) return null; reelId = this._currentReelId; } @@ -10340,8 +10046,7 @@ Crafty.c("SpriteAnimation", { return this._reels[reelId]; } }); - -},{"../core/core.js":9}],36:[function(require,module,exports){ +},{"../core/core.js":10}],38:[function(require,module,exports){ var Crafty = require('../core/core.js'); // Define some variables required for webgl @@ -10714,7 +10419,7 @@ Crafty.c("Sprite", { } }); -},{"../core/core.js":9}],37:[function(require,module,exports){ +},{"../core/core.js":10}],39:[function(require,module,exports){ var Crafty = require('../core/core.js'); @@ -10737,9 +10442,10 @@ var Crafty = require('../core/core.js'); * rotate together. * * @note For DOM (but not canvas) text entities, various font settings (such as - * text-decoration) can be set using `.css()` (see DOM component). But - * you cannot use `.css()` to set the properties which are controlled by `.textFont()`, - * `.textColor()`, or `.textAlign()` -- the settings will be ignored. + * text-decoration) can be set using `.css()` (see DOM component). If you + * use `.css()` to set the *individual* properties which are controlled by `.textFont()`, + * `.textColor()`, or `.textAlign()`, the text component will set these properties internally as well. + * However, if you use `.css()` to set shorthand properties such as `font`, these will be ignored by the text component. * * @note If you use canvas text with glyphs that are taller than standard letters, portions of the glyphs might be cut off. */ @@ -10791,6 +10497,41 @@ Crafty.c("Text", { context.restore(); } + }, + + // type, weight, size, family, lineHeight, and variant. + // For a few hardcoded css properties, set the internal definitions + "SetStyle": function(propertyName) { + // could check for DOM component, but this event should only be fired by such an entity! + // Rather than triggering Invalidate on each of these, we rely on css() triggering that event + switch(propertyName) { + case "textAlign": + this._textAlign = this._element.style.textAlign; + break; + case "color": + // Need to set individual color components, so use method + this.textColor(this._element.style.color); + break; + case "fontType": + this._textFont.type = this._element.style.fontType; + break; + case "fontWeight": + this._textFont.weight = this._element.style.fontWeight; + break; + case "fontSize": + this._textFont.size = this._element.style.fontSize; + break; + case "fontFamily": + this._textFont.family = this._element.style.fontFamily; + break; + case "fontVariant": + this._textFont.variant = this._element.style.fontVariant; + break; + case "lineHeight": + this._textFont.lineHeight = this._element.style.lineHeight; + break; + } + } }, @@ -10833,9 +10574,11 @@ Crafty.c("Text", { * @sign public this .text(String text) * @param text - String of text that will be inserted into the DOM or Canvas element. * - * @sign public this .text(Function textGenerator) + * @sign public this .text(Function textGenerator[, Any eventData]) * @param textGenerator - A function that returns a string. - * It will be immediately invoked in the context of the entity, with the result used as the text to display. + * It will be immediately invoked with the optional eventData in the context of the entity, + * with the result used as the text to display. + * @param [eventData] - Optional parameter to invoke the function with. * * This method will update the text inside the entity. * @@ -10859,10 +10602,10 @@ Crafty.c("Text", { * ~~~ */ _textGenerator: null, - text: function (text) { + text: function (text, eventData) { if (!(typeof text !== "undefined" && text !== null)) return this._text; if (typeof (text) === "function"){ - this._text = text.call(this); + this._text = text.call(this, eventData); this._textGenerator = text; } else { this._text = text; @@ -10883,12 +10626,14 @@ Crafty.c("Text", { * * @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.) + * @param textUpdateEvent - The name of the event which will trigger text to be updated. Defaults to "UpdateFrame". (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". + * If textUpdateEvent is provided, text generation will be bound to that event instead of "UpdateFrame". + * + * The text generating function is invoked with the event object parameter, which the event was triggered with. * * @note Dynamic text generation could cause performance issues when the entity is attached to a Canvas layer. * @@ -10902,14 +10647,14 @@ Crafty.c("Text", { */ _dynamicTextOn: false, _textUpdateEvent: null, - _dynamicTextUpdate: function(){ + _dynamicTextUpdate: function(eventData) { if (!this._textGenerator) return; - this.text(this._textGenerator); + this.text(this._textGenerator, eventData); }, dynamicTextGeneration: function(dynamicTextOn, textUpdateEvent) { this.unbind(this._textUpdateEvent, this._dynamicTextUpdate); if (dynamicTextOn) { - this._textUpdateEvent = textUpdateEvent || "EnterFrame"; + this._textUpdateEvent = textUpdateEvent || "UpdateFrame"; this.bind(this._textUpdateEvent, this._dynamicTextUpdate); } return this; @@ -11080,7 +10825,7 @@ Crafty.c("Text", { }); -},{"../core/core.js":9}],38:[function(require,module,exports){ +},{"../core/core.js":10}],40:[function(require,module,exports){ var Crafty = require('../core/core.js'), document = window.document; @@ -11291,7 +11036,7 @@ Crafty.extend({ pan: (function () { var targetX, targetY, startingX, startingY, easing; - function enterFrame(e) { + function updateFrame(e) { easing.tick(e.dt); var v = easing.value(); Crafty.viewport.x = (1-v) * startingX + v * targetX; @@ -11305,7 +11050,7 @@ Crafty.extend({ } function stopPan(){ - Crafty.unbind("EnterFrame", enterFrame); + Crafty.unbind("UpdateFrame", updateFrame); } Crafty._preBind("StopCamera", stopPan); @@ -11327,7 +11072,7 @@ Crafty.extend({ easing = new Crafty.easing(time, easingFn); // bind to event, using uniqueBind prevents multiple copies from being bound - Crafty.uniqueBind("EnterFrame", enterFrame); + Crafty.uniqueBind("UpdateFrame", updateFrame); }; })(), @@ -11443,13 +11188,13 @@ Crafty.extend({ function stopZoom(){ - Crafty.unbind("EnterFrame", enterFrame); + Crafty.unbind("UpdateFrame", updateFrame); } Crafty._preBind("StopCamera", stopZoom); var startingZoom, finalZoom, finalAmount, startingX, finalX, startingY, finalY, easing; - function enterFrame(e){ + function updateFrame(e){ var amount, v; easing.tick(e.dt); @@ -11510,7 +11255,7 @@ Crafty.extend({ easing = new Crafty.easing(time, easingFn); - Crafty.uniqueBind("EnterFrame", enterFrame); + Crafty.uniqueBind("UpdateFrame", updateFrame); }; @@ -11563,54 +11308,65 @@ Crafty.extend({ * If the user starts a drag, "StopCamera" will be triggered, which will cancel any existing camera animations. */ mouselook: (function () { + var mouseSystem; + var active = false, dragging = false, - lastMouse = {}; + lastMouse = {x: 0, y: 0}, + diff = {x: 0, y: 0}; - return function (op, arg) { - if (typeof op === 'boolean') { - active = op; - if (active) { - Crafty.mouseObjs++; - } else { - Crafty.mouseObjs = Math.max(0, Crafty.mouseObjs - 1); - } - return; - } - if (!active) return; - switch (op) { - case 'move': - case 'drag': - if (!dragging) return; - var diff = { - x: arg.clientX - lastMouse.x, - y: arg.clientY - lastMouse.y - }; + function startFn (e) { + if (dragging || e.target) return; + + Crafty.trigger("StopCamera"); + // DEPRECATED: switch computation to use e.realX, e.realY + lastMouse.x = e.clientX; + lastMouse.y = e.clientY; + dragging = true; + } + function moveFn (e) { + if (!dragging) return; - lastMouse.x = arg.clientX; - lastMouse.y = arg.clientY; + diff.x = e.clientX - lastMouse.x; + diff.y = e.clientY - lastMouse.y; - Crafty.viewport.x += diff.x; - Crafty.viewport.y += diff.y; - Crafty.viewport._clamp(); - break; - case 'start': - Crafty.trigger("StopCamera"); - lastMouse.x = arg.clientX; - lastMouse.y = arg.clientY; - dragging = true; - break; - case 'stop': - dragging = false; - break; + lastMouse.x = e.clientX; + lastMouse.y = e.clientY; + + var viewport = Crafty.viewport; + viewport.x += diff.x / viewport._scale; + viewport.y += diff.y / viewport._scale; + viewport._clamp(); + } + function stopFn (e) { + if (!dragging) return; + + dragging = false; + } + + return function (op) { + // TODO: lock pointer events on controls system in future + mouseSystem = Crafty.s('Mouse'); + + if (op && !active) { + mouseSystem.bind("MouseDown", startFn); + mouseSystem.bind("MouseMove", moveFn); + mouseSystem.bind("MouseUp", stopFn); + active = op; + } else if (!op && active) { + mouseSystem.unbind("MouseDown", startFn); + mouseSystem.unbind("MouseMove", moveFn); + mouseSystem.unbind("MouseUp", stopFn); + active = op; } }; })(), + _clamp: function () { // 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.clone(Crafty.map.boundaries()); + var bound = Crafty.clone(this.bounds) || Crafty.map.boundaries(); bound.max.x *= this._scale; bound.min.x *= this._scale; bound.max.y *= this._scale; @@ -11640,8 +11396,8 @@ Crafty.extend({ * @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]) + * @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). @@ -11655,6 +11411,14 @@ Crafty.extend({ * @see Crafty.device, Crafty.domHelper, Crafty.stage, Crafty.viewport.reload */ init: function (w, h, stage_elem) { + // Handle specifying stage_elem without w & h + if (typeof(stage_elem) === 'undefined' && typeof(h) === 'undefined' && + typeof(w) !=='undefined' && typeof(w) !== 'number') { + stage_elem = w; + w = window.innerWidth; + h = window.innerHeight; + } + // Define default graphics layers with default z-layers Crafty.createLayer("DefaultCanvasLayer", "Canvas", {z: 20}); Crafty.createLayer("DefaultDOMLayer", "DOM", {z: 30}); @@ -11738,964 +11502,2436 @@ Crafty.extend({ }); Crafty.settings.modify("stageSelectable", false); - //make the stage have no context menu - Crafty.settings.register("stageContextMenu", function (v) { - Crafty.stage.elem.oncontextmenu = v ? function () { - return true; - } : function () { - return false; - }; - }); - Crafty.settings.modify("stageContextMenu", false); + //make the stage have no context menu + Crafty.settings.register("stageContextMenu", function (v) { + Crafty.stage.elem.oncontextmenu = v ? function () { + return true; + } : function () { + return false; + }; + }); + Crafty.settings.modify("stageContextMenu", false); + + Crafty.settings.register("autoPause", function () {}); + Crafty.settings.modify("autoPause", false); + + //add to the body and give it an ID if not exists + if (!crstage) { + document.body.appendChild(Crafty.stage.elem); + Crafty.stage.elem.id = stage_elem; + } + + var elem = Crafty.stage.elem.style, + offset; + + //css style + elem.width = this.width + "px"; + elem.height = this.height + "px"; + elem.overflow = "hidden"; + + + // resize events + Crafty.bind("ViewportResize", function(){Crafty.trigger("InvalidateViewport");}); + + if (Crafty.mobile) { + + // remove default gray highlighting after touch + if (typeof elem.webkitTapHighlightColor !== undefined) { + elem.webkitTapHighlightColor = "rgba(0,0,0,0)"; + } + + var meta = document.createElement("meta"), + head = document.getElementsByTagName("head")[0]; + + //hide the address bar + meta = document.createElement("meta"); + meta.setAttribute("name", "apple-mobile-web-app-capable"); + meta.setAttribute("content", "yes"); + head.appendChild(meta); + + Crafty.addEvent(this, Crafty.stage.elem, "touchmove", function (e) { + e.preventDefault(); + }); + + + } + + elem.position = "relative"; + //find out the offset position of the stage + offset = Crafty.domHelper.innerPosition(Crafty.stage.elem); + Crafty.stage.x = offset.x; + Crafty.stage.y = offset.y; + + Crafty.uniqueBind("ViewportResize", this._resize); + }, + + _resize: function(){ + Crafty.stage.elem.style.width = Crafty.viewport.width + "px"; + Crafty.stage.elem.style.height = Crafty.viewport.height + "px"; + }, + + // Create setters/getters for x, y, width, height + _defineViewportProperties: function(){ + Object.defineProperty(this, 'x', { + set: function (v) { + this.scroll('_x', v); + }, + get: function () { + return this._x; + }, + configurable : true + }); + Object.defineProperty(this, 'y', { + set: function (v) { + this.scroll('_y', v); + }, + get: function () { + return this._y; + }, + configurable : true + }); + Object.defineProperty(this, 'width', { + set: function (v) { + this._width = v; + Crafty.trigger("ViewportResize"); + }, + get: function () { + return this._width; + }, + configurable : true + }); + Object.defineProperty(this, 'height', { + set: function (v) { + this._height = v; + Crafty.trigger("ViewportResize"); + }, + get: function () { + return this._height; + }, + configurable : true + }); + }, + + /**@ + * #Crafty.viewport.reload + * @comp Crafty.stage + * @kind Method + * + * @sign public Crafty.viewport.reload() + * + * Recalculate and reload stage width, height and position. + * Useful when browser return wrong results on init (like safari on Ipad2). + * You should also call this method if you insert custom DOM elements that affect Crafty's stage offset. + * + */ + reload: function () { + var w = window.innerWidth, + h= window.innerHeight, + offset; + + + if (Crafty.stage.fullscreen) { + this._width = w; + this._height = h; + Crafty.trigger("ViewportResize"); + } + + offset = Crafty.domHelper.innerPosition(Crafty.stage.elem); + Crafty.stage.x = offset.x; + Crafty.stage.y = offset.y; + }, + + /**@ + * #Crafty.viewport.reset + * @comp Crafty.stage + * @kind Method + * + * @trigger StopCamera - called to cancel camera animations + * + * @sign public Crafty.viewport.reset() + * + * Resets the viewport to starting values, and cancels any existing camera animations. + * Called when scene() is run. + */ + reset: function () { + Crafty.viewport.mouselook(false); + Crafty.trigger("StopCamera"); + // Reset viewport position and scale + Crafty.viewport.scroll("_x", 0); + Crafty.viewport.scroll("_y", 0); + Crafty.viewport.scale(1); + }, + + /**@ + * #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} + * + * Test if a rectangle is completely in viewport + */ + 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":10}],41:[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 +function RenderProgramWrapper(layer, shader){ + this.shader = shader; + 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 = layer.context.createBuffer(); +} + +RenderProgramWrapper.prototype = { + // 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 < attributes.length; i++) { + var a = attributes[i]; + this._attribute_table[a.name] = a; + + a.bytes = a.bytes || Float32Array.BYTES_PER_ELEMENT; + a.type = a.type || this.context.FLOAT; + a.offset = offset; + a.location = this.context.getAttribLocation(this.shader, a.name); + + this.context.enableVertexAttribArray(a.location); + + offset += a.width; + } + + // Stride is the full width including the last set + this.stride = offset; + + // Create attribute array of correct size to hold max elements + this._attributeArray = new Float32Array(this.array_size * 4 * this.stride); + this._attributeBuffer = this.context.createBuffer(); + this._registryHoles = []; + this._registrySize = 0; + }, + + // increase the size of the typed arrays + // does so by creating a new array of that size and copying the existing one into it + 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 newIndexArray = new Uint16Array(6 * newsize); + + newAttributeArray.set(this._attributeArray); + newIndexArray.set(this._indexArray); + + this._attributeArray = newAttributeArray; + this._indexArray = newIndexArray; + this.array_size = newsize; + }, + + // Add an entity that needs to be rendered by this program + // Needs to be assigned an index in the buffer + registerEntity: function(e) { + if (this._registryHoles.length === 0) { + 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); + } + e._glBufferIndex = this._registrySize; + this._registrySize++; + } else { + e._glBufferIndex = this._registryHoles.pop(); + } + }, + + // remove an entity; allow its buffer index to be reused + unregisterEntity: function(e) { + if (typeof e._glBufferIndex === "number") + this._registryHoles.push(e._glBufferIndex); + e._glBufferIndex = null; + }, + + resetRegistry: function() { + this._maxElement = 0; + this._registryHoles.length = 0; + }, + + setCurrentEntity: function(ent) { + // offset is 4 * buffer index, because each entity has 4 vertices + this.ent_offset = ent._glBufferIndex * 4; + this.ent = ent; + }, + + // Called before a batch of entities is prepped for rendering + 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; i < attributes.length; i++) { + a = attributes[i]; + gl.vertexAttribPointer(a.location, a.width, a.type, false, this.stride * a.bytes, a.offset * a.bytes); + } + + // For now, special case the need for texture objects + var t = this.texture_obj; + if (t && t.unit === null) { + this.layer.texture_manager.bindTexture(t); + } + + this.index_pointer = 0; + }, + + // Sets a texture + setTexture: function(texture_obj) { + // Only needs to be done once + if (this.texture_obj !== undefined) + return; + // Set the texture buffer to use + texture_obj.setToProgram(this.shader, "uSampler", "uTextureDimensions"); + this.texture_obj = texture_obj; + }, + + // adds a set of 6 indices to the index array + // Corresponds to 2 triangles that make up a rectangle + addIndices: function(offset) { + var index = this._indexArray, l = this.index_pointer; + index[0 + l] = 0 + offset; + index[1 + l] = 1 + offset; + index[2 + l] = 2 + offset; + index[3 + l] = 1 + offset; + index[4 + l] = 2 + offset; + index[5 + l] = 3 + offset; + this.index_pointer += 6; + }, + + + // Writes data from the attribute and index arrays to the appropriate buffers, and then calls drawElements. + renderBatch: function() { + var gl = this.context; + gl.bindBuffer(gl.ARRAY_BUFFER, this._attributeBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this._attributeArray, gl.STATIC_DRAW); + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this._indexArray, gl.STATIC_DRAW); + gl.drawElements(gl.TRIANGLES, this.index_pointer, gl.UNSIGNED_SHORT, 0); + }, + + setViewportUniforms: function(viewport, cameraOptions) { + var gl = this.context; + gl.useProgram(this.shader); + gl.uniform4f(this.shader.viewport, -viewport._x, -viewport._y, viewport._w , viewport._h ); + }, + + // Fill in the attribute with the given arguments, cycling through the data if necessary + // If the arguments provided match the width of the attribute, that means it'll fill the same values for each of the four vertices. + // TODO determine if this abstraction is a performance hit! + writeVector: function(name, x, y) { + var a = this._attribute_table[name]; + var stride = this.stride, offset = a.offset + this.ent_offset * stride, w = a.width; + var l = (arguments.length - 1); + var data = this._attributeArray; + + for (var r = 0; r < 4; r++) + for (var c = 0; c < w; c++) { + data[offset + stride * r + c] = arguments[(w * r + c) % l + 1]; + } + } +}; + +/**@ + * #WebGLLayer + * @category Graphics + * @kind System + * + * A collection of methods to handle webgl contexts. + */ +Crafty._registerLayerTemplate("WebGL", { + type: "WebGL", + + /**@ + * #.context + * @comp WebGLLayer + * @kind Property + * + * This will return the context of the webgl canvas element. + */ + context: null, + + // Create a vertex or fragment shader, given the source and type + _compileShader: function(src, type) { + var gl = this.context; + var shader = gl.createShader(type); + gl.shaderSource(shader, src); + gl.compileShader(shader); + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { + throw (gl.getShaderInfoLog(shader)); + } + return shader; + }, + + // Create and return a complete, linked shader program, given the source for the fragment and vertex shaders. + // Will compile the two shaders and then link them together + _makeProgram: function(shader) { + var gl = this.context; + var fragmentShader = this._compileShader(shader.fragmentCode, gl.FRAGMENT_SHADER); + var vertexShader = this._compileShader(shader.vertexCode, gl.VERTEX_SHADER); + + var shaderProgram = gl.createProgram(); + gl.attachShader(shaderProgram, vertexShader); + gl.attachShader(shaderProgram, fragmentShader); + gl.linkProgram(shaderProgram); + + if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { + throw ("Could not initialise shaders"); + } + + shaderProgram.viewport = gl.getUniformLocation(shaderProgram, "uViewport"); + return shaderProgram; + }, + + // Will create and return a RenderProgramWrapper for a shader program. + // name is a unique id, attributes an array of attribute names with their metadata. + // Each attribute needs at least a `name` and `width` property: + // ~~~ + // [ + // {name:"aPosition", width: 2}, + // {name:"aOrientation", width: 3}, + // {name:"aLayer", width:2}, + // {name:"aColor", width: 4} + // ] + // ~~~ + // The "aPositon", "aOrientation", and "aLayer" attributes should be the same for any webgl entity, + // since they support the basic 2D properties + getProgramWrapper: function(name, shader) { + if (this.programs[name] === undefined) { + var compiledShader = this._makeProgram(shader); + var program = new RenderProgramWrapper(this, compiledShader); + program.name = name; + program.initAttributes(shader.attributeList); + program.draw = shader.drawCallback; + program.setViewportUniforms(this._viewportRect(), this.options); + this.programs[name] = program; + } + return this.programs[name]; + }, + + // Make a texture out of the given image element + // The url is just used as a unique ID + makeTexture: function(url, image, repeating) { + return this.texture_manager.makeTexture(url, image, repeating); + }, + + events: { + // Respond to init & remove events + "LayerInit": "layerInit", + "LayerRemove": "layerRemove", + // Bind scene rendering (see drawing.js) + "RenderScene": "_render", + // Listen for pixelart changes + "PixelartSet": "_setPixelart", + // Handle viewport modifications + "ViewportResize": "_resize" + }, + + layerInit: function() { + + //check if we support webgl is supported + if (!Crafty.support.webgl) { + Crafty.trigger("NoWebGL"); + Crafty.stop(); + return; + } + + // Avoid shared state between systems + this.programs = {}; + + //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; + + Crafty.stage.elem.appendChild(c); + + // Try to get a webgl context + var gl; + try { + gl = c.getContext("webgl", { premultipliedalpha: true }) || c.getContext("experimental-webgl", { premultipliedalpha: true }); + gl.viewportWidth = c.width; + gl.viewportHeight = c.height; + } catch (e) { + Crafty.trigger("NoWebGL"); + Crafty.stop(); + return; + } + + // assign to this renderer + this.context = gl; + this._canvas = c; + + gl.clearColor(0.0, 0.0, 0.0, 0.0); + + // These commands allow partial transparency, but require drawing in z-order + gl.disable(gl.DEPTH_TEST); + // This particular blend function requires the shader programs to output pre-multiplied alpha + // This is necessary to match the blending of canvas/dom entities against the background color + gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); + gl.enable(gl.BLEND); + + this.texture_manager = new Crafty.TextureManager(gl, this); + + this._dirtyViewport = true; + }, + + // Cleanup the DOM when the system is destroyed + layerRemove: function() { + this._canvas.parentNode.removeChild(this._canvas); + }, + + // Called when the viewport resizes + _resize: function() { + var c = this._canvas; + c.width = Crafty.viewport.width; + c.height = Crafty.viewport.height; + + var gl = this.context; + gl.viewportWidth = c.width; + gl.viewportHeight = c.height; + }, + + // TODO consider shifting to texturemanager + _setPixelart: function(enabled) { + var gl = this.context; + if (enabled) { + this.texture_filter = gl.NEAREST; + } else { + this.texture_filter = gl.LINEAR; + } + }, + + // Hold an array ref to avoid garbage + visible_gl: [], + + // Render any entities associated with this context; called in response to a draw event + _render: function(rect) { + rect = rect || this._viewportRect(); + var gl = this.context; + + // Set viewport and clear it + gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + + //Set the viewport uniform variables used by each registered program + var programs = this.programs; + if (this._dirtyViewport) { + var view = this._viewportRect(); + for (var comp in programs) { + programs[comp].setViewportUniforms(view, this.options); + } + this._dirtyViewport = false; + } + + // Search for any entities in the given area (viewport unless otherwise specified) + var q = Crafty.map.search(rect), + i = 0, + l = q.length, + current; + //From all potential candidates, build a list of visible entities, then sort by zorder + var visible_gl = this.visible_gl; + visible_gl.length = 0; + for (i = 0; i < l; i++) { + current = q[i]; + if (current._visible && current.program && (current._drawLayer === this)) { + visible_gl.push(current); + } + } + visible_gl.sort(this._sort); + l = visible_gl.length; + + + // Now iterate through the z-sorted entities to be rendered + // Each entity writes it's data into a typed array + // The entities are rendered in batches, where the entire array is copied to a buffer in one operation + // A batch is rendered whenever the next element needs to use a different type of program + // Therefore, you get better performance by grouping programs by z-order if possible. + // (Each sprite sheet will use a different program, but multiple sprites on the same sheet can be rendered in one batch) + var shaderProgram = null; + for (i = 0; i < l; i++) { + current = visible_gl[i]; + if (shaderProgram !== current.program) { + if (shaderProgram !== null) { + shaderProgram.renderBatch(); + } + + shaderProgram = current.program; + shaderProgram.index_pointer = 0; + shaderProgram.switchTo(); + } + current.draw(); + current._changed = false; + } + + if (shaderProgram !== null) { + shaderProgram.renderBatch(); + } + + }, + + /**@ + * #.dirty + * @comp WebGLLayer + * @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 dirty(ent) { + // WebGL doens't need to do any special tracking of changed objects + }, + + /**@ + * #.attach + * @comp WebGLLayer + * @kind Method + * @private + * + * @sign public .attach(ent) + * @param ent - The entity to add + * + * Add an entity to the layer + */ + attach: function attach(ent) { + // WebGL entities really need to be added to a specific program, which is handled in the LayerAttached event by components + ent._drawContext = this.context; + }, + + /**@ + * #.detach + * @comp WebGLLayer + * @kind Method + * @private + * + * @sign public .detach(ent) + * @param ent - The entity to remove + * + * Removes an entity from the layer + */ + detach: function detach(ent) { + // This could, like attach, be handled by components + // We instead handle it in a central place for now + if (ent.program) { + ent.program.unregisterEntity(ent); + } + } + +}); + + +},{"../core/core.js":10}],42:[function(require,module,exports){ +var Crafty = require('../core/core.js'); + +/**@ + * #WebGL + * @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 webgl canvas element. Its canvas element (and hence any WebGL entity) is always rendered below any DOM entities. + * + * Sprite, Image, SpriteAnimation, and Color all support WebGL rendering. Text entities will need to use DOM or Canvas for now. + * + * If a webgl context does not yet exist, a WebGL entity will automatically create one. + * + * @note For better performance, minimize the number of spritesheets used, and try to arrange it so that entities with different spritesheets are on different z-levels. This is because entities are rendered in z order, and only entities sharing the same texture can be efficiently batched. + * + * Create a webgl entity like this + * ~~~ + * var myEntity = Crafty.e("2D, WebGL, Color") + * .color(1, 1, 0, 0.5) + * .attr({x: 13, y: 37, w: 42, h: 42}); + *~~~ + */ + +Crafty.extend({ + /**@ + * #Crafty.WebGLShader + * @category Graphics + * @kind Method + * + * @sign public Crafty.WebGLShader Crafty.WebGLShader(String vertexShaderCode, String fragmentShaderCode, Array attributeList, Function drawCallback(e, entity)) + * @param vertexShaderCode - GLSL code for the vertex shader + * @param fragmentShaderCode - GLSL code for the fragment shader + * @param attributeList - List of variable names with their vertex length + * @param drawCallback - Function that pushes all attribute values to WebGL. + * + * Assigns or fetches a default shader for a component. + * + * This allows the default shader for a component to be overridden, and therefor allows + * developers to override the default shader behaviour with more complex shaders. + * + * @example + * Let's say we want to extend sprite to draw the images in grayscale when we + * set a `grayscale: true` attribute. + * ~~~ + * var recoloredSprite = new Crafty.WebGLShader( + * // The vertex shader + * "attribute vec2 aPosition;\n" + + * "attribute vec3 aOrientation;\n" + + * "attribute vec2 aLayer;\n" + + * "attribute vec2 aTextureCoord;\n" + + * "attribute vec2 aGrayscale;\n" + // Addition of our grayscale + * "varying mediump vec3 vTextureCoord;\n" + + * "varying mediump vec2 vGrayscale;\n" + // passing attribute to fragment shader + * "uniform vec4 uViewport;\n" + + * "uniform mediump vec2 uTextureDimensions;\n" + + * "mat4 viewportScale = mat4(2.0 / uViewport.z, 0, 0, 0, 0, -2.0 / uViewport.w, 0,0, 0, 0,1,0, -1,+1,0,1);\n" + + * "vec4 viewportTranslation = vec4(uViewport.xy, 0, 0);\n" + + * "void 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" + + * " 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" + + * " vGrayscale = aGrayscale;\n" + // Assigning the grayscale for fragment shader + * "}", + * // The fragment shader + * "precision mediump float;\n" + + * "varying mediump vec3 vTextureCoord;\n" + + * "varying mediump vec2 vGrayscale;\n" + + * "uniform sampler2D uSampler;\n " + + * "uniform mediump vec2 uTextureDimensions;\n" + + * "void main() {\n" + + * " highp vec2 coord = vTextureCoord.xy / uTextureDimensions;\n" + + * " mediump vec4 base_color = texture2D(uSampler, coord);\n" + + * " if (vGrayscale.x == 1.0) {\n" + + * " mediump float lightness = (0.2126*base_color.r + 0.7152*base_color.g + 0.0722*base_color.b);\n" + + * " lightness *= base_color.a * vTextureCoord.z; // Premultiply alpha\n" + + * " gl_FragColor = vec4(lightness, lightness, lightness, base_color.a*vTextureCoord.z);\n" + + * " } else {\n" + + * " gl_FragColor = vec4(base_color.rgb*base_color.a*vTextureCoord.z, base_color.a*vTextureCoord.z);\n" + + * " }\n" + + * "}", + * [ + * { name: "aPosition", width: 2 }, + * { name: "aOrientation", width: 3 }, + * { name: "aLayer", width: 2 }, + * { name: "aTextureCoord", width: 2 }, + * { name: "aGrayscale", 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 + * ); + * // Write our grayscale attribute + * e.program.writeVector("aGrayscale", + * entity.grayscale ? 1.0 : 0.0, + * 0.0 + * ); + * } + * ); + * ~~~ + * + * It seems like a lot of work, but most of the above code is the default Crafty shader code. + * When you get the hang of it, it is really easy to extend for your own effects. And remember + * you only need to write it once, and suddenly all sprite entities have extra effects available. + * + * @see Crafty.defaultShader + * @see Sprite + * @see Image + * @see Color + * @see WebGL + */ + WebGLShader: function(vertexCode, fragmentCode, attributeList, drawCallback){ + this.vertexCode = vertexCode; + this.fragmentCode = fragmentCode; + this.attributeList = attributeList; + this.drawCallback = drawCallback; + }, + /**@ + * #Crafty.defaultShader + * @category Graphics + * @kind Method + * + * @sign public Crafty.WebGLShader Crafty.defaultShader(String component[, Crafty.WebGLShader shader]) + * @param component - Name of the component to assign a default shader to + * @param shader - New default shader to assign to a component + * + * Assigns or fetches a default shader for a component. + * + * This allows the default shader for a component to be overridden, and therefor allows + * developers to override the default shader behaviour with more complex shaders. + * + * @example + * Let's say we want to set the grayscale enabled shader from the example of the WebGLShader + * as default for sprites: + * ~~~ + * Crafty.defaultShader("Sprite", recoloredSprite); + * ~~~ + * + * @see Crafty.WebGLShader + * @see Sprite + * @see Image + * @see Color + * @see WebGL + */ + defaultShader: function(component, shader) { + this._defaultShaders = (this._defaultShaders || {}); + if (arguments.length === 1 ){ + return this._defaultShaders[component]; + } + this._defaultShaders[component] = shader; + }, + +}); + +Crafty.c("WebGL", { + /**@ + * #.context + * @comp WebGL + * @kind Property + * + * The webgl context this entity will be rendered to. + */ + init: function () { + this.requires("Renderable"); + // Attach to webgl layer + if (!this._customLayer){ + this._attachToLayer( Crafty.s("DefaultWebGLLayer") ); + } + }, + + remove: function(){ + this._detachFromLayer(); + }, + + // Cache the various objects and arrays used in draw + drawVars: { + type: "webgl", + pos: {}, + ctx: null, + coord: [0, 0, 0, 0], + co: { + x: 0, + y: 0, + w: 0, + h: 0 + } + }, + + /**@ + * #.draw + * @comp WebGL + * @kind Method + * @private + * + * @sign public this .draw() + * + * An internal method to draw the entity on the webgl canvas element. Rather then rendering directly, it writes relevent information into a buffer to allow batch rendering. + */ + draw: function () { - Crafty.settings.register("autoPause", function () {}); - Crafty.settings.modify("autoPause", false); + if (!this.ready) return; - //add to the body and give it an ID if not exists - if (!crstage) { - document.body.appendChild(Crafty.stage.elem); - Crafty.stage.elem.id = stage_elem; - } + var pos = this.drawVars.pos; + pos._x = this._x; + pos._y = this._y; + pos._w = this._w; + pos._h = this._h; - var elem = Crafty.stage.elem.style, - offset; + var coord = this.__coord || [0, 0, 0, 0]; + var co = this.drawVars.co; + co.x = coord[0]; + co.y = coord[1]; + co.w = coord[2]; + co.h = coord[3]; - //css style - elem.width = this.width + "px"; - elem.height = this.height + "px"; - elem.overflow = "hidden"; + // Handle flipX, flipY + // (Just swap the positions of e.g. x and x+w) + if (this._flipX ) { + co.x = co.x + co.w; + co.w = - co.w; + } + if (this._flipY ) { + co.y = co.y + co.h; + co.h = - co.h; + } + //Draw entity + var gl = this._drawContext; + this.drawVars.gl = gl; + var prog = this.drawVars.program = this.program; - // resize events - Crafty.bind("ViewportResize", function(){Crafty.trigger("InvalidateViewport");}); + // The program might need to refer to the current element's index + prog.setCurrentEntity(this); - if (Crafty.mobile) { + // Write position; x, y, w, h + prog.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 + ); - // remove default gray highlighting after touch - if (typeof elem.webkitTapHighlightColor !== undefined) { - elem.webkitTapHighlightColor = "rgba(0,0,0,0)"; - } + // Write orientation + prog.writeVector("aOrientation", + this._origin.x + this._x, + this._origin.y + this._y, + this._rotation * Math.PI / 180 + ); - var meta = document.createElement("meta"), - head = document.getElementsByTagName("head")[0]; + // Write z, alpha + prog.writeVector("aLayer", + this._globalZ, + this._alpha + ); - //hide the address bar - meta = document.createElement("meta"); - meta.setAttribute("name", "apple-mobile-web-app-capable"); - meta.setAttribute("content", "yes"); - head.appendChild(meta); + // This should only need to handle *specific* attributes! + this.trigger("Draw", this.drawVars); - Crafty.addEvent(this, Crafty.stage.elem, "touchmove", function (e) { - e.preventDefault(); - }); + // Register the vertex groups to be drawn, referring to this entities position in the big buffer + prog.addIndices(prog.ent_offset); + return this; + }, - } - - elem.position = "relative"; - //find out the offset position of the stage - offset = Crafty.domHelper.innerPosition(Crafty.stage.elem); - Crafty.stage.x = offset.x; - Crafty.stage.y = offset.y; + // v_src is optional, there's a default vertex shader that works for regular rectangular entities + _establishShader: function(compName, shader){ + this.program = this._drawLayer.getProgramWrapper(compName, shader); - Crafty.uniqueBind("ViewportResize", this._resize); - }, + // Needs to know where in the big array we are! + this.program.registerEntity(this); + // Shader program means ready + this.ready = true; + } +}); - _resize: function(){ - Crafty.stage.elem.style.width = Crafty.viewport.width + "px"; - Crafty.stage.elem.style.height = Crafty.viewport.height + "px"; - }, +},{"../core/core.js":10}],43:[function(require,module,exports){ +var Crafty = require('../core/core.js'); - // Create setters/getters for x, y, width, height - _defineViewportProperties: function(){ - Object.defineProperty(this, 'x', { - set: function (v) { - this.scroll('_x', v); - }, - get: function () { - return this._x; - }, - configurable : true - }); - Object.defineProperty(this, 'y', { - set: function (v) { - this.scroll('_y', v); - }, - get: function () { - return this._y; - }, - configurable : true - }); - Object.defineProperty(this, 'width', { - set: function (v) { - this._width = v; - Crafty.trigger("ViewportResize"); - }, - get: function () { - return this._width; - }, - configurable : true - }); - Object.defineProperty(this, 'height', { - set: function (v) { - this._height = v; - Crafty.trigger("ViewportResize"); - }, - get: function () { - return this._height; - }, - configurable : true - }); - }, - /**@ - * #Crafty.viewport.reload - * @comp Crafty.stage - * @kind Method - * - * @sign public Crafty.viewport.reload() +Crafty.extend({ + /**@ + * #Crafty.device + * @category Misc + * @kind Property + * + * Methods relating to devices such as tablets or phones + */ + device: { + _deviceOrientationCallback: false, + _deviceMotionCallback: false, + + /** + * The HTML5 DeviceOrientation event returns three pieces of data: + * * alpha the direction the device is facing according to the compass + * * beta the angle in degrees the device is tilted front-to-back + * * gamma the angle in degrees the device is tilted left-to-right. + * * The angles values increase as you tilt the device to the right or towards you. * - * Recalculate and reload stage width, height and position. - * Useful when browser return wrong results on init (like safari on Ipad2). - * You should also call this method if you insert custom DOM elements that affect Crafty's stage offset. + * Since Firefox uses the MozOrientationEvent which returns similar data but + * using different parameters and a different measurement system, we want to + * normalize that before we pass it to our _deviceOrientationCallback function. * + * @param eventData HTML5 DeviceOrientation event */ - reload: function () { - var w = window.innerWidth, - h= window.innerHeight, - offset; + _normalizeDeviceOrientation: function (eventData) { + var data; + if (window.DeviceOrientationEvent) { + data = { + // gamma is the left-to-right tilt in degrees, where right is positive + 'tiltLR': eventData.gamma, + // beta is the front-to-back tilt in degrees, where front is positive + 'tiltFB': eventData.beta, + // alpha is the compass direction the device is facing in degrees + 'dir': eventData.alpha, + // deviceorientation does not provide this data + 'motUD': null + }; + } else if (window.OrientationEvent) { + data = { + // x is the left-to-right tilt from -1 to +1, so we need to convert to degrees + 'tiltLR': eventData.x * 90, + // y is the front-to-back tilt from -1 to +1, so we need to convert to degrees + // We also need to invert the value so tilting the device towards us (forward) + // results in a positive value. + 'tiltFB': eventData.y * -90, + // MozOrientation does not provide this data + 'dir': null, + // z is the vertical acceleration of the device + 'motUD': eventData.z + }; + } + Crafty.device._deviceOrientationCallback(data); + }, - if (Crafty.stage.fullscreen) { - this._width = w; - this._height = h; - Crafty.trigger("ViewportResize"); - } + /** + * @param eventData HTML5 DeviceMotion event + */ + _normalizeDeviceMotion: function (eventData) { + var acceleration = eventData.accelerationIncludingGravity, + facingUp = (acceleration.z > 0) ? +1 : -1; - offset = Crafty.domHelper.innerPosition(Crafty.stage.elem); - Crafty.stage.x = offset.x; - Crafty.stage.y = offset.y; + var data = { + // Grab the acceleration including gravity from the results + 'acceleration': acceleration, + 'rawAcceleration': "[" + Math.round(acceleration.x) + ", " + Math.round(acceleration.y) + ", " + Math.round(acceleration.z) + "]", + // Z is the acceleration in the Z axis, and if the device is facing up or down + 'facingUp': facingUp, + // Convert the value from acceleration to degrees acceleration.x|y is the + // acceleration according to gravity, we'll assume we're on Earth and divide + // by 9.81 (earth gravity) to get a percentage value, and then multiply that + // by 90 to convert to degrees. + 'tiltLR': Math.round(((acceleration.x) / 9.81) * -90), + 'tiltFB': Math.round(((acceleration.y + 9.81) / 9.81) * 90 * facingUp) + }; + + Crafty.device._deviceMotionCallback(data); }, /**@ - * #Crafty.viewport.reset - * @comp Crafty.stage + * #Crafty.device.deviceOrientation + * @comp Crafty.device * @kind Method * - * @trigger StopCamera - called to cancel camera animations + * @sign public Crafty.device.deviceOrientation(Function callback) + * @param callback - Callback method executed once as soon as device orientation is change * - * @sign public Crafty.viewport.reset() + * Do something with normalized device orientation data: + * ~~~ + * { + * tiltLR : 'gamma -- the angle in degrees the device is tilted left-to-right.', + * tiltFB : 'beta -- the angle in degrees the device is tilted front-to-back', + * dir : 'alpha -- the direction the device is facing according to the compass', + * motUD : 'The angle's values increase as you tilt the device to the right or towards you.' + * } + * ~~~ * - * Resets the viewport to starting values, and cancels any existing camera animations. - * Called when scene() is run. + * @example + * ~~~ + * // Get DeviceOrientation event normalized data. + * Crafty.device.deviceOrientation(function(data){ + * Crafty.log('data.tiltLR : '+Math.round(data.tiltLR)+', data.tiltFB : '+Math.round(data.tiltFB)+', data.dir : '+Math.round(data.dir)+', data.motUD : '+data.motUD+''); + * }); + * ~~~ + * + * See browser support at http://caniuse.com/#search=device orientation. */ - reset: function () { - Crafty.viewport.mouselook("stop"); - Crafty.trigger("StopCamera"); - // Reset viewport position and scale - Crafty.viewport.scroll("_x", 0); - Crafty.viewport.scroll("_y", 0); - Crafty.viewport.scale(1); + deviceOrientation: function (func) { + this._deviceOrientationCallback = func; + if (Crafty.support.deviceorientation) { + if (window.DeviceOrientationEvent) { + // Listen for the deviceorientation event and handle DeviceOrientationEvent object + Crafty.addEvent(this, window, 'deviceorientation', this._normalizeDeviceOrientation); + } else if (window.OrientationEvent) { + // Listen for the MozOrientation event and handle OrientationData object + Crafty.addEvent(this, window, 'MozOrientation', this._normalizeDeviceOrientation); + } + } }, /**@ - * #Crafty.viewport.onScreen - * @comp Crafty.viewport + * #Crafty.device.deviceMotion + * @comp Crafty.device * @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} + * @sign public Crafty.device.deviceMotion(Function callback) + * @param callback - Callback method executed once as soon as device motion is change * - * Test if a rectangle is completely in viewport + * Do something with normalized device motion data: + * ~~~ + * { + * acceleration : 'Grab the acceleration including gravity from the results', + * rawAcceleration : 'Display the raw acceleration data', + * facingUp : 'Z is the acceleration in the Z axis, and if the device is facing up or down', + * tiltLR : 'Convert the value from acceleration to degrees. acceleration.x is the acceleration according to gravity, we'll assume we're on Earth and divide by 9.81 (earth gravity) to get a percentage value, and then multiply that by 90 to convert to degrees.', + * tiltFB : 'Convert the value from acceleration to degrees.' + * } + * ~~~ + * + * @example + * ~~~ + * // Get DeviceMotion event normalized data. + * Crafty.device.deviceMotion(function(data){ + * Crafty.log('data.moAccel : '+data.rawAcceleration+', data.moCalcTiltLR : '+Math.round(data.tiltLR)+', data.moCalcTiltFB : '+Math.round(data.tiltFB)+''); + * }); + * ~~~ + * + * See browser support at http://caniuse.com/#search=motion. */ - 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; + deviceMotion: function (func) { + this._deviceMotionCallback = func; + if (Crafty.support.devicemotion) { + if (window.DeviceMotionEvent) { + // Listen for the devicemotion event and handle DeviceMotionEvent object + Crafty.addEvent(this, window, 'devicemotion', this._normalizeDeviceMotion); + } + } } } }); -},{"../core/core.js":9}],39:[function(require,module,exports){ -var Crafty = require('../core/core.js'), - document = window.document; +},{"../core/core.js":10}],44:[function(require,module,exports){ +module.exports = { + _events: {}, -// Object for abstracting out all the gl calls to handle rendering entities with a particular program -function RenderProgramWrapper(layer, shader){ - this.shader = shader; - this.layer = layer; - this.context = layer.context; - this.draw = function() { }; + /**@ + * #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 + * @param event - Event name to bind to + * @param callback - Method to execute when triggered + * + * Adds DOM level 3 events to elements. The arguments it accepts are the call + * context (the value of `this`), the DOM element to attach the event to, + * the event name (without `on` (`click` rather than `onclick`)) and + * finally the callback method. + * + * If no element is passed, the default element will be `window.document`. + * + * Callbacks are passed with event data. + * + * @note This is related to DOM events only, not Crafty's own event system. + * Of course, you can trigger Crafty events in the callback function! + * + * @example + * Normally you'd use Crafty's built-in mouse component, but for the sake of an example let's pretend that doesn't exist. + * The following code will add a stage-wide MouseDown event listener to the player, and log both which button was pressed + * and the (x,y) coordinates in viewport/world/game space. + * ~~~ + * var player = Crafty.e("2D"); + * player.onMouseDown = function(e) { + * Crafty.log(e.mouseButton, e.realX, e.realY); + * }; + * Crafty.addEvent(player, Crafty.stage.elem, "mousedown", player.onMouseDown); + * ~~~ + * @see Crafty.removeEvent + */ + addEvent: function (ctx, obj, type, callback) { + if (arguments.length === 3) { + callback = type; + type = obj; + obj = window.document; + } - this.array_size = 16; - this.max_size = 1024; - this._indexArray = new Uint16Array(6 * this.array_size); - this._indexBuffer = layer.context.createBuffer(); -} + //save anonymous function to be able to remove + var id = ctx[0] || "", + afn = function (e) { + callback.call(ctx, e); + }; -RenderProgramWrapper.prototype = { - // 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 < attributes.length; i++) { - var a = attributes[i]; - this._attribute_table[a.name] = a; + if (!this._events[id + obj + type + callback]) + this._events[id + obj + type + callback] = afn; + else { + return; + } - a.bytes = a.bytes || Float32Array.BYTES_PER_ELEMENT; - a.type = a.type || this.context.FLOAT; - a.offset = offset; - a.location = this.context.getAttribLocation(this.shader, a.name); + obj.addEventListener(type, afn, false); - this.context.enableVertexAttribArray(a.location); + }, - offset += a.width; + /**@ + * #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 + * @param event - Name of the event + * @param callback - Method executed when triggered + * + * Removes events attached by `Crafty.addEvent()`. All parameters must + * be the same that were used to attach the event including a reference + * to the callback method. + * + * @see Crafty.addEvent + */ + removeEvent: function (ctx, obj, type, callback) { + if (arguments.length === 3) { + callback = type; + type = obj; + obj = window.document; } - // Stride is the full width including the last set - this.stride = offset; + //retrieve anonymous function + var id = ctx[0] || "", + afn = this._events[id + obj + type + callback]; - // Create attribute array of correct size to hold max elements - this._attributeArray = new Float32Array(this.array_size * 4 * this.stride); - this._attributeBuffer = this.context.createBuffer(); - this._registryHoles = []; - this._registrySize = 0; + if (afn) { + obj.removeEventListener(type, afn, false); + delete this._events[id + obj + type + callback]; + } + } +}; + +},{}],45:[function(require,module,exports){ +var Crafty = require('../core/core.js'); + +/**@ + * #KeyboardSystem + * @category Input + * @kind System + * + * Provides access to key events. + * @note Events and methods are inherited from the `KeyboardState` component. + * + * The event callbacks are triggered with a native [`KeyboardEvent`](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent) + * received by `window.document`, which is wrapped in a standard Crafty event object (as described in `KeyboardState`). + * + * These key events are triggered globally, thus on the global Crafty instance, every entity and system. + * + * @example + * Move viewport by arrow keys. + * ~~~ + * 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 KeyboardState, Keyboard + */ +Crafty.s("Keyboard", Crafty.extend.call(Crafty.extend.call(new Crafty.__eventDispatcher(), { + _evt: { // evt object to reuse + eventName:'', + key: 0, + which: 0, + originalEvent: null + }, + + prepareEvent: function (e) { + var evt = this._evt; + + // Normalize event name + var type = e.type; + evt.eventName = type === "keydown" ? "KeyDown" : + type === "keyup" ? "KeyUp" : type; + + // Normalize key to avoid cross-browser issues + evt.which = e.charCode !== null ? e.charCode : e.keyCode; + evt.key = e.keyCode || e.which; + + // wrap original event into standard Crafty event object + // as original key event's properties are read-only + evt.originalEvent = e; + + return evt; }, - // increase the size of the typed arrays - // does so by creating a new array of that size and copying the existing one into it - growArrays: function(size) { - if (this.array_size >= this.max_size) return; + // this method will be called by KeyboardState iff triggerKey event was valid + triggerKeyEvent: function (eventName, e) { + Crafty.trigger(eventName, e); + }, + + dispatchEvent: function (e) { + var evt = this.prepareEvent(e); + this.triggerKey(evt.eventName, evt); + } +}), Crafty.__keyboardStateTemplate), {}, false); + + +/**@ + * #Keyboard + * @category Input + * @kind Component + * + * Provides the entity with keyboard events. + * Keyboard events get dispatched to all entities that have the Keyboard component. + * @note If you do not add this component, key events will not be triggered on the entity. + * + * Triggers all events described in the `KeyboardState` component, these are: + * @trigger KeyDown - when a key is pressed - KeyboardEvent + * @trigger KeyUp - when a key is released - KeyboardEvent + * + * @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 -= 1; + * } else if (e.key == Crafty.keys.RIGHT_ARROW) { + * this.x += 1; + * } else if (e.key == Crafty.keys.UP_ARROW) { + * this.y -= 1; + * } else if (e.key == Crafty.keys.DOWN_ARROW) { + * this.y += 1; + * } + * }); + * ~~~ + * + * @see KeyboardState, KeyboardSystem + */ +Crafty.c("Keyboard", { + // DEPRECATED: remove in an upcoming release + isDown: function(key) { + return Crafty.s('Keyboard').isKeyDown(key); + } +}); + +},{"../core/core.js":10}],46:[function(require,module,exports){ +var Crafty = require('../core/core.js'), + document = window.document; + +// figure out which eventName to listen to for mousewheel events +var mouseWheelEvent = typeof document.onwheel !== 'undefined' ? 'wheel' : // modern browsers + typeof document.onmousewheel !== 'undefined' ? 'mousewheel' : // old Webkit and IE + 'DOMMouseScroll'; // old Firefox - var newsize = Math.min(size, this.max_size); +//initialize the input events onload +Crafty._preBind("Load", function () { + Crafty.addEvent(this, document.body, "mouseup", Crafty.detectBlur); + Crafty.addEvent(Crafty.s('Keyboard'), window, "blur", Crafty.s('Keyboard').resetKeyDown); + Crafty.addEvent(Crafty.s('Mouse'), window, "mouseup", Crafty.s('Mouse').resetButtonDown); + Crafty.addEvent(Crafty.s('Touch'), window, "touchend", Crafty.s('Touch').resetTouchPoints); + Crafty.addEvent(Crafty.s('Touch'), window, "touchcancel", Crafty.s('Touch').resetTouchPoints); + + Crafty.addEvent(Crafty.s('Keyboard'), "keydown", Crafty.s('Keyboard').processEvent); + Crafty.addEvent(Crafty.s('Keyboard'), "keyup", Crafty.s('Keyboard').processEvent); + + Crafty.addEvent(Crafty.s('Mouse'), Crafty.stage.elem, "mousedown", Crafty.s('Mouse').processEvent); + Crafty.addEvent(Crafty.s('Mouse'), Crafty.stage.elem, "mouseup", Crafty.s('Mouse').processEvent); + Crafty.addEvent(Crafty.s('Mouse'), Crafty.stage.elem, "mousemove", Crafty.s('Mouse').processEvent); + Crafty.addEvent(Crafty.s('Mouse'), Crafty.stage.elem, "click", Crafty.s('Mouse').processEvent); + Crafty.addEvent(Crafty.s('Mouse'), Crafty.stage.elem, "dblclick", Crafty.s('Mouse').processEvent); + + Crafty.addEvent(this, Crafty.stage.elem, "touchstart", this._touchDispatch); + Crafty.addEvent(this, Crafty.stage.elem, "touchmove", this._touchDispatch); + Crafty.addEvent(this, Crafty.stage.elem, "touchend", this._touchDispatch); + Crafty.addEvent(this, Crafty.stage.elem, "touchcancel", this._touchDispatch); + Crafty.addEvent(this, Crafty.stage.elem, "touchleave", this._touchDispatch); + + Crafty.addEvent(Crafty.s('MouseWheel'), Crafty.stage.elem, mouseWheelEvent, Crafty.s('MouseWheel').processEvent); +}); - var newAttributeArray = new Float32Array(newsize * 4 * this.stride); - var newIndexArray = new Uint16Array(6 * newsize); +Crafty.bind("Pause", function () { + // Reset pressed keys and buttons + Crafty.s('Keyboard').resetKeyDown(); + Crafty.s('Mouse').resetButtonDown(); +}); - newAttributeArray.set(this._attributeArray); - newIndexArray.set(this._indexArray); +Crafty._preBind("CraftyStop", function () { + // Reset pressed keys and buttons + Crafty.s('Keyboard').resetKeyDown(); + Crafty.s('Mouse').resetButtonDown(); +}); - this._attributeArray = newAttributeArray; - this._indexArray = newIndexArray; - this.array_size = newsize; - }, +Crafty._preBind("CraftyStop", function () { + Crafty.removeEvent(this, document.body, "mouseup", Crafty.detectBlur); + Crafty.removeEvent(Crafty.s('Keyboard'), window, "blur", Crafty.s('Keyboard').resetKeyDown); + Crafty.removeEvent(Crafty.s('Mouse'), window, "mouseup", Crafty.s('Mouse').resetButtonDown); + Crafty.removeEvent(Crafty.s('Touch'), window, "touchend", Crafty.s('Touch').resetTouchPoints); + Crafty.removeEvent(Crafty.s('Touch'), window, "touchcancel", Crafty.s('Touch').resetTouchPoints); - // Add an entity that needs to be rendered by this program - // Needs to be assigned an index in the buffer - registerEntity: function(e) { - if (this._registryHoles.length === 0) { - 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); - } - e._glBufferIndex = this._registrySize; - this._registrySize++; - } else { - e._glBufferIndex = this._registryHoles.pop(); - } - }, + Crafty.removeEvent(Crafty.s('Keyboard'), "keydown", Crafty.s('Keyboard').processEvent); + Crafty.removeEvent(Crafty.s('Keyboard'), "keyup", Crafty.s('Keyboard').processEvent); - // remove an entity; allow its buffer index to be reused - unregisterEntity: function(e) { - if (typeof e._glBufferIndex === "number") - this._registryHoles.push(e._glBufferIndex); - e._glBufferIndex = null; - }, + if (Crafty.stage) { + Crafty.removeEvent(Crafty.s('Mouse'), Crafty.stage.elem, "mousedown", Crafty.s('Mouse').processEvent); + Crafty.removeEvent(Crafty.s('Mouse'), Crafty.stage.elem, "mouseup", Crafty.s('Mouse').processEvent); + Crafty.removeEvent(Crafty.s('Mouse'), Crafty.stage.elem, "mousemove", Crafty.s('Mouse').processEvent); + Crafty.removeEvent(Crafty.s('Mouse'), Crafty.stage.elem, "click", Crafty.s('Mouse').processEvent); + Crafty.removeEvent(Crafty.s('Mouse'), Crafty.stage.elem, "dblclick", Crafty.s('Mouse').processEvent); + + Crafty.removeEvent(this, Crafty.stage.elem, "touchstart", this._touchDispatch); + Crafty.removeEvent(this, Crafty.stage.elem, "touchmove", this._touchDispatch); + Crafty.removeEvent(this, Crafty.stage.elem, "touchend", this._touchDispatch); + Crafty.removeEvent(this, Crafty.stage.elem, "touchcancel", this._touchDispatch); + Crafty.removeEvent(this, Crafty.stage.elem, "touchleave", this._touchDispatch); + + Crafty.removeEvent(Crafty.s('MouseWheel'), Crafty.stage.elem, mouseWheelEvent, Crafty.s('MouseWheel').processEvent); + } +}); - resetRegistry: function() { - this._maxElement = 0; - this._registryHoles.length = 0; - }, +},{"../core/core.js":10}],47:[function(require,module,exports){ +var Crafty = require('../core/core.js'); - setCurrentEntity: function(ent) { - // offset is 4 * buffer index, because each entity has 4 vertices - this.ent_offset = ent._glBufferIndex * 4; - this.ent = ent; +/**@ + * #MouseWheel + * @category Input + * @kind System + * + * System which dispatches mouse wheel events received by Crafty. + * @trigger MouseWheelScroll - when mouse is scrolled - MouseWheelEvent + * + * The event callback is triggered with a native [`wheel` event](https://developer.mozilla.org/en-US/docs/Web/Events/wheel) (all newer browsers), + * a native [`mousewheel` event](https://developer.mozilla.org/en-US/docs/Web/Events/mousewheel) (old IE and WebKit browsers) or + * a native [`DOMMouseScroll` event](https://developer.mozilla.org/en-US/docs/Web/Events/DOMMouseScroll) (old Firefox browsers) + * received by Crafty's stage (`Crafty.stage.elem`), which is wrapped in a standard Crafty event object (see below). + * + * These MouseWheel events are triggered on the global Crafty object and thus on every entity and system. + * + * The standard `MouseWheelEvent` object: + * ~~~ + * // event name of mouse wheel event - "MouseWheelScroll" + * e.eventName + * + * // the direction the wheel was scrolled, +1 if wheel was scrolled up, -1 if wheel was scrolled down + * e.direction + * + * // the closest (visible & Mouse-enhanced) entity to the source of the event (if available), otherwise null + * e.target + * + * // (x,y) coordinates of mouse event in world (default viewport) space + * e.realX + * e.realY + * + * // Original mouse wheel event, containing additional native properties + * e.originalEvent + * ~~~ + * + * @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)); + * }); + * ~~~ + * + * For more details see [mdn article on wheel events](https://developer.mozilla.org/en-US/docs/Web/Events/wheel#Listening_to_this_event_across_browser). + * @note 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 [details](http://stackoverflow.com/questions/5527601/normalizing-mousewheel-speed-across-browsers)). + * + * @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) { + * zoomTowards(1 + evt.direction/10, evt.realX, evt.realY, 5); + * }); + * ~~~ + */ + +Crafty.s("MouseWheel", Crafty.extend.call(new Crafty.__eventDispatcher(), { + _evt: { // evt object to reuse + eventName:'', + direction: 0, + target: null, + clientX: 0, // DEPRECATED: remove in upcoming release + clientY: 0, // DEPRECATED: remove in upcoming release + realX: 0, + realY: 0, + originalEvent: null }, + _mouseSystem: null, - // Called before a batch of entities is prepped for rendering - 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; i < attributes.length; i++) { - a = attributes[i]; - gl.vertexAttribPointer(a.location, a.width, a.type, false, this.stride * a.bytes, a.offset * a.bytes); - } + prepareEvent: function (e) { + var mouseSystem = this._mouseSystem; + if (!mouseSystem) this._mouseSystem = mouseSystem = Crafty.s('Mouse'); - // For now, special case the need for texture objects - var t = this.texture_obj; - if (t && t.unit === null) { - this.layer.texture_manager.bindTexture(t); - } + var evt = this._evt; - this.index_pointer = 0; - }, + // normalize eventName + evt.eventName = "MouseWheelScroll"; - // Sets a texture - setTexture: function(texture_obj) { - // Only needs to be done once - if (this.texture_obj !== undefined) - return; - // Set the texture buffer to use - texture_obj.setToProgram(this.shader, "uSampler", "uTextureDimensions"); - this.texture_obj = texture_obj; - }, + // normalize direction + evt.direction = (e.detail < 0 || e.wheelDelta > 0 || e.deltaY < 0) ? 1 : -1; - // adds a set of 6 indices to the index array - // Corresponds to 2 triangles that make up a rectangle - addIndices: function(offset) { - var index = this._indexArray, l = this.index_pointer; - index[0 + l] = 0 + offset; - index[1 + l] = 1 + offset; - index[2 + l] = 2 + offset; - index[3 + l] = 1 + offset; - index[4 + l] = 2 + offset; - index[5 + l] = 3 + offset; - this.index_pointer += 6; - }, + // copy screen coordinates + // only browsers supporting `wheel` event contain mouse coordinates + // DEPRECATED: remove in upcoming release + evt.clientX = e.clientX !== undefined ? e.clientX : mouseSystem.lastMouseEvent.clientX; + evt.clientY = e.clientY !== undefined ? e.clientY : mouseSystem.lastMouseEvent.clientY; + // augment mouse event with real coordinates + Crafty.translatePointerEventCoordinates(e, evt); - // Writes data from the attribute and index arrays to the appropriate buffers, and then calls drawElements. - renderBatch: function() { - var gl = this.context; - gl.bindBuffer(gl.ARRAY_BUFFER, this._attributeBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this._attributeArray, gl.STATIC_DRAW); - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._indexBuffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this._indexArray, gl.STATIC_DRAW); - gl.drawElements(gl.TRIANGLES, this.index_pointer, gl.UNSIGNED_SHORT, 0); - }, + // augment mouse event with target entity + evt.target = mouseSystem.mouseObjs ? Crafty.findPointerEventTargetByComponent("Mouse", e) : null; - setViewportUniforms: function(viewport, cameraOptions) { - var gl = this.context; - gl.useProgram(this.shader); - gl.uniform4f(this.shader.viewport, -viewport._x, -viewport._y, viewport._w , viewport._h ); - }, + // wrap original event into standard Crafty event object + evt.originalEvent = e; - // Fill in the attribute with the given arguments, cycling through the data if necessary - // If the arguments provided match the width of the attribute, that means it'll fill the same values for each of the four vertices. - // TODO determine if this abstraction is a performance hit! - writeVector: function(name, x, y) { - var a = this._attribute_table[name]; - var stride = this.stride, offset = a.offset + this.ent_offset * stride, w = a.width; - var l = (arguments.length - 1); - var data = this._attributeArray; + return evt; + }, - for (var r = 0; r < 4; r++) - for (var c = 0; c < w; c++) { - data[offset + stride * r + c] = arguments[(w * r + c) % l + 1]; - } + dispatchEvent: function (e) { + var evt = this.prepareEvent(e); + // trigger event + Crafty.trigger("MouseWheelScroll", evt); } -}; +}), {}, false); + /**@ - * #WebGLLayer - * @category Graphics + * #MouseSystem + * @category Input * @kind System * - * A collection of methods to handle webgl contexts. + * Provides access to mouse events. + * @note Additional events and methods are inherited from the `MouseState` component. + * + * @trigger MouseOver - when the mouse enters an entity - MouseEvent + * @trigger MouseOut - when the mouse leaves an entity - MouseEvent + * @trigger Click - when the user clicks - MouseEvent + * @trigger DoubleClick - when the user double clicks - MouseEvent + * + * The event callbacks are triggered with a native [`MouseEvent`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent) + * received by Crafty's stage (`Crafty.stage.elem`), which is wrapped in a standard Crafty event object (as described in `MouseState`). + * + * These mouse events are triggered on the MouseSystem itself. + * Additionally, they are dispatched to the closest (visible & `Mouse`-enhanced) entity to the source of the event (if available). + * + * @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 + * to make use of the Touch component instead, which can break compatibility with things which directly interact with the Mouse component. + * + * @example + * Log the current position of the mouse. + * ~~~ + * Crafty.s('Mouse').bind('MouseMove', function(e) { + * Crafty.log('Mouse pos: <' + e.realX.toFixed(2) + ', ' + e.realY.toFixed(2) + '>'); + * }); + * ~~~ + * + * @see MouseState, Mouse + * @see Crafty.multitouch */ -Crafty._registerLayerTemplate("WebGL", { - type: "WebGL", - // Layer options - options: { - xResponse: 1, - yResponse: 1, - scaleResponse: 1, - z: 0 - }, - /**@ - * #.context - * @comp WebGLLayer - * @kind Property - * - * This will return the context of the webgl canvas element. - */ - context: null, - changed_objects: [], +Crafty.s("Mouse", Crafty.extend.call(Crafty.extend.call(new Crafty.__eventDispatcher(), { + normedEventNames: { + "mousedown": "MouseDown", + "mouseup": "MouseUp", + "dblclick": "DoubleClick", + "click": "Click", + "mousemove": "MouseMove" + }, + + _evt: { // evt object to reuse + eventName:'', + mouseButton: -1, + target: null, + clientX: 0, // DEPRECATED: remove in upcoming release + clientY: 0, // DEPRECATED: remove in upcoming release + realX: 0, + realY: 0, + originalEvent: null + }, + + // Indicates how many entities have the Mouse component, for performance optimization + // Mouse events are still routed to Crafty.s('Mouse') even if there are no entities with Mouse component + mouseObjs: 0, - // Create a vertex or fragment shader, given the source and type - _compileShader: function(src, type) { - var gl = this.context; - var shader = gl.createShader(type); - gl.shaderSource(shader, src); - gl.compileShader(shader); - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { - throw (gl.getShaderInfoLog(shader)); - } - return shader; - }, + // current entity that is moused over + over: null, - // Create and return a complete, linked shader program, given the source for the fragment and vertex shaders. - // Will compile the two shaders and then link them together - _makeProgram: function(shader) { - var gl = this.context; - var fragmentShader = this._compileShader(shader.fragmentCode, gl.FRAGMENT_SHADER); - var vertexShader = this._compileShader(shader.vertexCode, gl.VERTEX_SHADER); + prepareEvent: function (e) { + var evt = this._evt; - var shaderProgram = gl.createProgram(); - gl.attachShader(shaderProgram, vertexShader); - gl.attachShader(shaderProgram, fragmentShader); - gl.linkProgram(shaderProgram); + // Normalize event name + var type = e.type; + evt.eventName = this.normedEventNames[type] || type; - if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { - throw ("Could not initialise shaders"); + // Normalize button according to http://unixpapa.com/js/mouse.html + if (typeof e.which === 'undefined') { + evt.mouseButton = (e.button < 2) ? Crafty.mouseButtons.LEFT : ((e.button === 4) ? Crafty.mouseButtons.MIDDLE : Crafty.mouseButtons.RIGHT); + } else { + evt.mouseButton = (e.which < 2) ? Crafty.mouseButtons.LEFT : ((e.which === 2) ? Crafty.mouseButtons.MIDDLE : Crafty.mouseButtons.RIGHT); } - shaderProgram.viewport = gl.getUniformLocation(shaderProgram, "uViewport"); - return shaderProgram; - }, + // copy screen coordinates + // DEPRECATED: remove in upcoming release + evt.clientX = e.clientX; + evt.clientY = e.clientY; - // Will create and return a RenderProgramWrapper for a shader program. - // name is a unique id, attributes an array of attribute names with their metadata. - // Each attribute needs at least a `name` and `width` property: - // ~~~ - // [ - // {name:"aPosition", width: 2}, - // {name:"aOrientation", width: 3}, - // {name:"aLayer", width:2}, - // {name:"aColor", width: 4} - // ] - // ~~~ - // The "aPositon", "aOrientation", and "aLayer" attributes should be the same for any webgl entity, - // since they support the basic 2D properties - getProgramWrapper: function(name, shader) { - if (this.programs[name] === undefined) { - var compiledShader = this._makeProgram(shader); - var program = new RenderProgramWrapper(this, compiledShader); - program.name = name; - program.initAttributes(shader.attributeList); - program.draw = shader.drawCallback; - program.setViewportUniforms(this._viewportRect(), this.options); - this.programs[name] = program; - } - return this.programs[name]; - }, + // augment mouse event with real coordinates + Crafty.translatePointerEventCoordinates(e, evt); - // Make a texture out of the given image element - // The url is just used as a unique ID - makeTexture: function(url, image, repeating) { - return this.texture_manager.makeTexture(url, image, repeating); - }, + // augment mouse event with target entity + evt.target = this.mouseObjs ? Crafty.findPointerEventTargetByComponent("Mouse", e) : null; - init: function() { + // wrap original event into standard Crafty event object + evt.originalEvent = e; - //check if we support webgl is supported - if (!Crafty.support.webgl) { - Crafty.trigger("NoWebGL"); - Crafty.stop(); - return; - } + return evt; + }, - // Avoid shared state between systems - this.changed_objects = []; - this.programs = {}; + // this method will be called by MouseState iff triggerMouse event was valid + triggerMouseEvent: function (eventName, e) { + // trigger event on MouseSystem itself + this.trigger(eventName, e); - //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; + // special case: MouseOver & MouseOut + var over = this.over, closest = e.target; + if (eventName === "MouseMove" && over !== closest) { // MouseOver target changed + // if old MouseOver target wasn't null, send MouseOut + if (over) { + e.eventName = "MouseOut"; + e.target = over; + over.trigger("MouseOut", e); + e.eventName = "MouseMove"; + e.target = closest; + } - Crafty.stage.elem.appendChild(c); + // save new over entity + this.over = closest; - // Try to get a webgl context - var gl; - try { - gl = c.getContext("webgl", { premultipliedalpha: true }) || c.getContext("experimental-webgl", { premultipliedalpha: true }); - gl.viewportWidth = c.width; - gl.viewportHeight = c.height; - } catch (e) { - Crafty.trigger("NoWebGL"); - Crafty.stop(); - return; + // if new MouseOver target isn't null, send MouseOver + if (closest) { + e.eventName = "MouseOver"; + closest.trigger("MouseOver", e); + e.eventName = "MouseMove"; + } } - // assign to this renderer - this.context = gl; - this._canvas = c; + // TODO: move routing of events in future to controls system, make it similar to KeyboardSystem + // try to find closest element that will also receive mouse event, whatever the event is + if (closest) { + closest.trigger(eventName, e); + } + }, - gl.clearColor(0.0, 0.0, 0.0, 0.0); + dispatchEvent: function (e) { + var evt = this.prepareEvent(e); + this.triggerMouse(evt.eventName, evt); + } +}), Crafty.__mouseStateTemplate), {}, false); - // These commands allow partial transparency, but require drawing in z-order - gl.disable(gl.DEPTH_TEST); - // This particular blend function requires the shader programs to output pre-multiplied alpha - // This is necessary to match the blending of canvas/dom entities against the background color - gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); - gl.enable(gl.BLEND); - //Bind rendering of canvas context (see drawing.js) - this.uniqueBind("RenderScene", this.render); - this.uniqueBind("ViewportResize", this._resize); - this.uniqueBind("InvalidateViewport", function() { this.dirtyViewport = true; }); - this.uniqueBind("PixelartSet", this._setPixelart); - this._setPixelart(Crafty._pixelartEnabled); - this.dirtyViewport = true; +/**@ + * #Mouse + * @category Input + * @kind Component + * + * Provides the entity with mouse events. + * Mouse events get dispatched to the closest (visible & `Mouse`-enhanced) entity to the source of the event (if available). + * @note If you do not add this component, mouse events will not be triggered on the entity. + * + * Triggers all events described in `MouseSystem` and `MouseState`, these are: + * @trigger MouseOver - when the mouse enters the entity - MouseEvent + * @trigger MouseMove - when the mouse is over the entity and moves - MouseEvent + * @trigger MouseOut - when the mouse leaves the entity - MouseEvent + * @trigger MouseDown - when a mouse button is pressed on the entity - MouseEvent + * @trigger MouseUp - when a mouse button is released on the entity - MouseEvent + * @trigger Click - when the user clicks on the entity - MouseEvent + * @trigger DoubleClick - when the user double clicks on the entity - MouseEvent + * + * @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 + * to make use of the Touch component instead, which can break compatibility with things which directly interact with the Mouse component. + * + * @example + * ~~~ + * var myEntity = Crafty.e('2D, Canvas, Color, Mouse') + * .attr({x: 10, y: 10, w: 40, h: 40}) + * .color('red') + * .bind('Click', function(MouseEvent){ + * alert('clicked', MouseEvent); + * }); + * + * myEntity.bind('MouseUp', function(e) { + * if( e.mouseButton == Crafty.mouseButtons.RIGHT ) + * Crafty.log("Clicked right button"); + * }) + * ~~~ + * + * @see MouseState, MouseSystem + * @see Crafty.multitouch + */ +Crafty.c("Mouse", { + required: "AreaMap", - this.texture_manager = new Crafty.TextureManager(gl, this); - Crafty._addDrawLayerInstance(this); + init: function () { + Crafty.s("Mouse").mouseObjs++; }, - - // Cleanup the DOM when the system is destroyed remove: function() { - this._canvas.parentNode.removeChild(this._canvas); - Crafty._removeDrawLayerInstance(this); + Crafty.s("Mouse").mouseObjs--; + } +}); + +/**@ + * #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 + * @trigger StopDrag - is triggered when dragging ends - MouseEvent + * + * @see Mouse + */ +Crafty.c("MouseDrag", { + _dragging: false, + + required: "Mouse", + events: { + "MouseDown": "_ondown" }, - // Called when the viewport resizes - _resize: function() { - var c = this._canvas; - c.width = Crafty.viewport.width; - c.height = Crafty.viewport.height; + init: function () { + // TODO: remove this and instead lock on pointer control events in future + // bind the this object for listeners called with the MouseSystem as the this object + this._ondown = this._ondown.bind(this); + this._ondrag = this._ondrag.bind(this); + this._onup = this._onup.bind(this); + }, - var gl = this.context; - gl.viewportWidth = c.width; - gl.viewportHeight = c.height; + // When dragging is enabled, this method is bound to the MouseDown crafty event + _ondown: function (e) { + if (e.mouseButton !== Crafty.mouseButtons.LEFT) return; + this.startDrag(e); }, - // TODO consider shifting to texturemanager - _setPixelart: function(enabled) { - var gl = this.context; - if (enabled) { - this.texture_filter = gl.NEAREST; - } else { - this.texture_filter = gl.LINEAR; - } + // While a drag is occurring, this method is bound to the mousemove DOM event + _ondrag: function (e) { + // ignore invalid 0 position - strange problem on ipad + if (!this._dragging || e.realX === 0 || e.realY === 0) return false; + this.trigger("Dragging", e); }, - // convenicne to sort array by global Z - zsort: function(a, b) { - return a._globalZ - b._globalZ; + // While a drag is occurring, this method is bound to mouseup DOM event + _onup: function (e) { + if (e.mouseButton !== Crafty.mouseButtons.LEFT) return; + this.stopDrag(e); }, - // Hold an array ref to avoid garbage - visible_gl: [], + /**@ + * #.startDrag + * @comp MouseDrag + * @kind Method + * + * @sign public this .startDrag(void) + * + * Make the entity produce drag events, essentially making the entity follow the mouse positions. + * + * @see .stopDrag + */ + startDrag: function (e) { + if (this._dragging) return; + this._dragging = true; - // Render any entities associated with this context; called in response to a draw event - render: function(rect) { - rect = rect || this._viewportRect(); - var gl = this.context; + // TODO: remove this and instead lock on pointer control events in future + Crafty.s("Mouse").bind("MouseMove", this._ondrag); + Crafty.s("Mouse").bind("MouseUp", this._onup); - // Set viewport and clear it - gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + // if event undefined, use the last known position of the mouse + this.trigger("StartDrag", e || Crafty.s("Mouse").lastMouseEvent); + return this; + }, - //Set the viewport uniform variables used by each registered program - var programs = this.programs; - if (this.dirtyViewport) { - var view = this._viewportRect(); - for (var comp in programs) { - programs[comp].setViewportUniforms(view, this.options); - } - this.dirtyViewport = false; - } + /**@ + * #.stopDrag + * @comp MouseDrag + * @kind Method + * + * @sign public this .stopDrag(void) + * + * Stop the entity from producing drag events, essentially reproducing the drop. + * + * @see .startDrag + */ + stopDrag: function (e) { + if (!this._dragging) return; + this._dragging = false; - // Search for any entities in the given area (viewport unless otherwise specified) - var q = Crafty.map.search(rect), - i = 0, - l = q.length, - current; - //From all potential candidates, build a list of visible entities, then sort by zorder - var visible_gl = this.visible_gl; - visible_gl.length = 0; - for (i = 0; i < l; i++) { - current = q[i]; - if (current._visible && current.program && (current._drawLayer === this)) { - visible_gl.push(current); - } - } - visible_gl.sort(this.zsort); - l = visible_gl.length; + // TODO: remove this and instead lock on pointer control events in future + Crafty.s("Mouse").unbind("MouseMove", this._ondrag); + Crafty.s("Mouse").unbind("MouseUp", this._onup); + // if event undefined, use the last known position of the mouse + this.trigger("StopDrag", e || Crafty.s("Mouse").lastMouseEvent); + return this; + } +}); - // Now iterate through the z-sorted entities to be rendered - // Each entity writes it's data into a typed array - // The entities are rendered in batches, where the entire array is copied to a buffer in one operation - // A batch is rendered whenever the next element needs to use a different type of program - // Therefore, you get better performance by grouping programs by z-order if possible. - // (Each sprite sheet will use a different program, but multiple sprites on the same sheet can be rendered in one batch) - var shaderProgram = null; - for (i = 0; i < l; i++) { - current = visible_gl[i]; - if (shaderProgram !== current.program) { - if (shaderProgram !== null) { - shaderProgram.renderBatch(); - } +},{"../core/core.js":10}],48:[function(require,module,exports){ +var Crafty = require('../core/core.js'); - shaderProgram = current.program; - shaderProgram.index_pointer = 0; - shaderProgram.switchTo(); +Crafty.extend({ + /**@ + * #Crafty.findPointerEventTargetByComponent + * @category Input + * @kind Method + * + * @sign public Object .findPointerEventTargetByComponent(String comp, Number clientX, Number clientY) + * Finds closest entity with certain component at a given position. + * @param comp - Component name + * @param clientX - x coordinate in client space, usually taken from a pointer event + * @param clientY - y coordinate in client space, usually taken from a pointer event + * @returns The found entity, or null if no entity was found. + * + * @sign public Object .findPointerEventTargetByComponent(String comp, Event e) + * Finds closest entity with certain component at a given event. + * @param comp - Component name + * @param e - The pointer event, containing the target and the required properties `clientX` & `clientY`, which will be used as the query point + * @returns The found entity, or null if no entity was found. + * + * 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). + * For having a detection area specified for the enity, add the AreaMap component to the entity expected to be found. + * + */ + findPointerEventTargetByComponent: function (comp, x, y) { + var tar = x.target || x.srcElement || Crafty.stage.elem; + y = typeof y !== 'undefined' ? y : x.clientY; + x = typeof x.clientX !== 'undefined' ? x.clientX : x; + + var closest = null, current, q, l, i, pos, maxz = -Infinity; + + //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) { + tar = tar.parentNode; + } + var ent = Crafty(parseInt(tar.id.replace('ent', ''), 10)); + pos = Crafty.domHelper.translate(x, y, ent._drawLayer); + if (ent.__c[comp] && ent.isAt(pos.x, pos.y)) { + closest = ent; } - current.draw(); - current._changed = false; } - if (shaderProgram !== null) { - shaderProgram.renderBatch(); + //else we search for an entity with component + if (!closest) { + + // 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.unfilteredSearch({ + _x: pos.x, + _y: pos.y, + _w: 1, + _h: 1 + }); + + 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; + } + } + } } + return closest; }, /**@ - * #.dirty - * @comp WebGLLayer + * #Crafty.translatePointerEventCoordinates + * @category Input * @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 dirty(ent) { - // WebGL doens't need to do any special tracking of changed objects - }, - - /**@ - * #.attach - * @comp WebGLLayer - * @kind Method - * @private - * - * @sign public .attach(ent) - * @param ent - The entity to add + * @sign public Object .translatePointerEventCoordinates(PointerEvent e[, PointerEvent out]) + * @param e - Any pointer event with `clientX` and `clientY` properties, usually a `MouseEvent` or `Touch` object + * @param out - Optional pointer event to augment with coordinates instead + * @returns The pointer event, augmented with additional `realX` and `realY` properties * - * Add an entity to the layer - */ - attach: function attach(ent) { - // WebGL entities really need to be added to a specific program, which is handled in the LayerAttached event by components - ent._drawContext = this.context; - }, - - /**@ - * #.detach - * @comp WebGLLayer - * @kind Method - * @private - * - * @sign public .detach(ent) - * @param ent - The entity to remove + * Updates the passed event object to have two additional properties, `realX` and `realY`, + * which correspond to the point in actual world space the event happened. * - * Removes an entity from the layer + * This method is used internally by the .mouseDispatch and .touchDispatch methods, + * but may be used for custom events. + * + * @see Crafty.domHelper#Crafty.domHelper.translate */ - detach: function detach(ent) { - // This could, like attach, be handled by components - // We instead handle it in a central place for now - if (ent.program) { - ent.program.unregisterEntity(ent); - } - } - -}); + translatePointerEventCoordinates: function (e, out) { + out = out || e; + // Find the Crafty position in the default coordinate set, + // disregard the fact that the pointer event was related to a specific layer. + var pos = Crafty.domHelper.translate(e.clientX, e.clientY, undefined, this.__pointerPos); -},{"../core/core.js":9}],40:[function(require,module,exports){ -var Crafty = require('../core/core.js'); + // Set the mouse position based on standard viewport coordinates + out.realX = pos.x; + out.realY = pos.y; + }, + __pointerPos: {x: 0, y: 0} // object to reuse +}); /**@ - * #WebGL - * @category Graphics + * #AreaMap + * @category Input * @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 webgl canvas element. Its canvas element (and hence any WebGL entity) is always rendered below any DOM entities. * - * Sprite, Image, SpriteAnimation, and Color all support WebGL rendering. Text entities will need to use DOM or Canvas for now. - * - * If a webgl context does not yet exist, a WebGL entity will automatically create one. - * - * @note For better performance, minimize the number of spritesheets used, and try to arrange it so that entities with different spritesheets are on different z-levels. This is because entities are rendered in z order, and only entities sharing the same texture can be efficiently batched. + * Component used by Mouse and Touch. + * Can be added to other entities for use with the Crafty.findPointerEventTargetByComponent method. * - * Create a webgl entity like this - * ~~~ - * var myEntity = Crafty.e("2D, WebGL, Color") - * .color(1, 1, 0, 0.5) - * .attr({x: 13, y: 37, w: 42, h: 42}); - *~~~ + * @see Button + * @see Crafty.polygon + * @see Crafty.findPointerEventTargetByComponent */ +Crafty.c("AreaMap", { + init: function () { + if (this.has("Renderable") && this._drawLayer) { + this._drawLayer._pointerEntities++; + } + }, + + remove: function (isDestruction) { + if (!isDestruction && this.has("Renderable") && this._drawLayer) { + this._drawLayer._pointerEntities--; + } + }, + + events: { + "LayerAttached": function (layer) { + layer._pointerEntities++; + }, + "LayerDetached": function (layer) { + layer._pointerEntities--; + } + }, -Crafty.extend({ /**@ - * #Crafty.WebGLShader - * @category Graphics + * #.areaMap + * @comp AreaMap * @kind Method - * - * @sign public Crafty.WebGLShader Crafty.WebGLShader(String vertexShaderCode, String fragmentShaderCode, Array attributeList, Function drawCallback(e, entity)) - * @param vertexShaderCode - GLSL code for the vertex shader - * @param fragmentShaderCode - GLSL code for the fragment shader - * @param attributeList - List of variable names with their vertex length - * @param drawCallback - Function that pushes all attribute values to WebGL. * - * Assigns or fetches a default shader for a component. + * @trigger NewAreaMap - when a new areaMap is assigned - Crafty.polygon * - * This allows the default shader for a component to be overridden, and therefor allows - * developers to override the default shader behaviour with more complex shaders. + * @sign public this .areaMap(Crafty.polygon polygon) + * @param polygon - Instance of Crafty.polygon used to check if the mouse coordinates are inside this region + * + * @sign public this .areaMap(Array coordinatePairs) + * @param coordinatePairs - Array of `x`, `y` coordinate pairs to generate a polygon + * + * @sign public this .areaMap(x1, y1,.., xN, yN) + * @param point# - List of `x`, `y` coordinate pairs to generate a polygon + * + * Assign a polygon to the entity so that pointer (mouse or touch) events will only be triggered if + * the coordinates are inside the given polygon. * * @example - * Let's say we want to extend sprite to draw the images in grayscale when we - * set a `grayscale: true` attribute. * ~~~ - * var recoloredSprite = new Crafty.WebGLShader( - * // The vertex shader - * "attribute vec2 aPosition;\n" + - * "attribute vec3 aOrientation;\n" + - * "attribute vec2 aLayer;\n" + - * "attribute vec2 aTextureCoord;\n" + - * "attribute vec2 aGrayscale;\n" + // Addition of our grayscale - * "varying mediump vec3 vTextureCoord;\n" + - * "varying mediump vec2 vGrayscale;\n" + // passing attribute to fragment shader - * "uniform vec4 uViewport;\n" + - * "uniform mediump vec2 uTextureDimensions;\n" + - * "mat4 viewportScale = mat4(2.0 / uViewport.z, 0, 0, 0, 0, -2.0 / uViewport.w, 0,0, 0, 0,1,0, -1,+1,0,1);\n" + - * "vec4 viewportTranslation = vec4(uViewport.xy, 0, 0);\n" + - * "void 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" + - * " 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" + - * " vGrayscale = aGrayscale;\n" + // Assigning the grayscale for fragment shader - * "}", - * // The fragment shader - * "precision mediump float;\n" + - * "varying mediump vec3 vTextureCoord;\n" + - * "varying mediump vec2 vGrayscale;\n" + - * "uniform sampler2D uSampler;\n " + - * "uniform mediump vec2 uTextureDimensions;\n" + - * "void main() {\n" + - * " highp vec2 coord = vTextureCoord.xy / uTextureDimensions;\n" + - * " mediump vec4 base_color = texture2D(uSampler, coord);\n" + - * " if (vGrayscale.x == 1.0) {\n" + - * " mediump float lightness = (0.2126*base_color.r + 0.7152*base_color.g + 0.0722*base_color.b);\n" + - * " lightness *= base_color.a * vTextureCoord.z; // Premultiply alpha\n" + - * " gl_FragColor = vec4(lightness, lightness, lightness, base_color.a*vTextureCoord.z);\n" + - * " } else {\n" + - * " gl_FragColor = vec4(base_color.rgb*base_color.a*vTextureCoord.z, base_color.a*vTextureCoord.z);\n" + - * " }\n" + - * "}", - * [ - * { name: "aPosition", width: 2 }, - * { name: "aOrientation", width: 3 }, - * { name: "aLayer", width: 2 }, - * { name: "aTextureCoord", width: 2 }, - * { name: "aGrayscale", 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 - * ); - * // Write our grayscale attribute - * e.program.writeVector("aGrayscale", - * entity.grayscale ? 1.0 : 0.0, - * 0.0 - * ); - * } + * Crafty.e("2D, DOM, Color, Mouse") + * .color("red") + * .attr({ w: 100, h: 100 }) + * .bind('MouseOver', function() {Crafty.log("over")}) + * .areaMap(0, 0, 50, 0, 50, 50, 0, 50); + * + * Crafty.e("2D, Mouse") + * .areaMap([0, 0, 50, 0, 50, 50, 0, 50]); + * + * Crafty.e("2D, Mouse").areaMap( + * new Crafty.polygon([0, 0, 50, 0, 50, 50, 0, 50]) * ); * ~~~ * - * It seems like a lot of work, but most of the above code is the default Crafty shader code. - * When you get the hang of it, it is really easy to extend for your own effects. And remember - * you only need to write it once, and suddenly all sprite entities have extra effects available. - * - * @see Crafty.defaultShader - * @see Sprite - * @see Image - * @see Color - * @see WebGL + * @see Crafty.polygon */ - WebGLShader: function(vertexCode, fragmentCode, attributeList, drawCallback){ - this.vertexCode = vertexCode; - this.fragmentCode = fragmentCode; - this.attributeList = attributeList; - this.drawCallback = drawCallback; - }, + areaMap: function (poly) { + //create polygon + if (arguments.length > 1) { + //convert args to array to create polygon + var args = Array.prototype.slice.call(arguments, 0); + poly = new Crafty.polygon(args); + } else if (poly.constructor === Array) { + poly = new Crafty.polygon(poly.slice()); + } else { + poly = poly.clone(); + } + + poly.shift(this._x, this._y); + this.mapArea = poly; + this.attach(this.mapArea); + this.trigger("NewAreaMap", poly); + return this; + } +}); + +/**@ + * #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", { + init: function () { + var req = (!Crafty.mobile || (Crafty.mobile && !Crafty.multitouch())) ? "Mouse" : "Touch"; + this.requires(req); + } +}); + +},{"../core/core.js":10}],49:[function(require,module,exports){ +var Crafty = require('../core/core.js'); + +Crafty.extend({ /**@ - * #Crafty.defaultShader - * @category Graphics + * #Crafty.multitouch + * @category Input * @kind Method - * - * @sign public Crafty.WebGLShader Crafty.defaultShader(String component[, Crafty.WebGLShader shader]) - * @param component - Name of the component to assign a default shader to - * @param shader - New default shader to assign to a component * - * Assigns or fetches a default shader for a component. + * Enables/disables support for multitouch feature. + * @sign public this .multitouch(Boolean bool) + * @param bool - Turns multitouch on and off. The initial state is off (false). * - * This allows the default shader for a component to be overridden, and therefor allows - * developers to override the default shader behaviour with more complex shaders. + * Query whether multitouch is on or off. + * @sign public Boolean .multitouch() + * @returns Whether multitouch is currently enabled + * + * By default, touch events are treated as mouse events and are handled by the `MouseSystem`. + * To change this behaviour and handle events by the `TouchSystem`, enable multitouch. + * + * If this is set to true, it is expected that your entities have the `Touch` component instead of the `Mouse` component. + * If false (default), then only entities with the Mouse component will respond to touch. + * It's recommended to add the `Button` component instead, which requires the proper component depending on this feature. + * + * @note The multitouch feature is currently incompatible with the `Draggable` component and `Crafty.viewport.mouselook`. * * @example - * Let's say we want to set the grayscale enabled shader from the example of the WebGLShader - * as default for sprites: - * ~~~ - * Crafty.defaultShader("Sprite", recoloredSprite); * ~~~ + * Crafty.multitouch(true); + * Crafty.log("multitouch is " + Crafty.multitouch()); * - * @see Crafty.WebGLShader - * @see Sprite - * @see Image - * @see Color - * @see WebGL + * Crafty.e('2D, Canvas, Color, Button') + * .attr({ x: 100, y: 100, w:200, h:200, z:1 }) + * .color('black') + * .bind('TouchStart', function(e) { this.color('green'); }); + * ~~~ + * @see TouchSystem + * @see MouseSystem + * @see Button + * @see Touch + * @see Mouse */ - defaultShader: function(component, shader) { - this._defaultShaders = (this._defaultShaders || {}); - if (arguments.length === 1 ){ - return this._defaultShaders[component]; - } - this._defaultShaders[component] = shader; + multitouch: function (bool) { + if (typeof bool !== "boolean") return this._multitouch; + this._multitouch = bool; + return this; }, + _multitouch: false, -}); + _touchDispatch: (function () { + var touchSystem; -Crafty.c("WebGL", { - /**@ - * #.context - * @comp WebGL - * @kind Property - * - * The webgl context this entity will be rendered to. - */ - init: function () { - this.requires("Renderable"); - // Attach to webgl layer - if (!this._customLayer){ - this._attachToLayer( Crafty.s("DefaultWebGLLayer") ); - } - }, - - remove: function(){ - this._detachFromLayer(); - }, + var startX = 0, // keeps track of start touch location + startY = 0; // keeps track of start touch location - // Cache the various objects and arrays used in draw - drawVars: { - type: "webgl", - pos: {}, - ctx: null, - coord: [0, 0, 0, 0], - co: { - x: 0, - y: 0, - w: 0, - h: 0 + function mimicMouse (e) { + var type, first; + if (e.type === "touchstart") type = "mousedown"; + else if (e.type === "touchmove") type = "mousemove"; + else if (e.type === "touchend") type = "mouseup"; + else if (e.type === "touchcancel") type = "mouseup"; + else if (e.type === "touchleave") type = "mouseup"; + if (e.touches && e.touches.length) { + first = e.touches[0]; + } else if (e.changedTouches && e.changedTouches.length) { + first = e.changedTouches[0]; + } + 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.target.dispatchEvent(simulatedEvent); + + // trigger click when it should be triggered + if (type === 'mousedown') { + startX = first.clientX; + startY = first.clientY; + } else if (type === 'mouseup') { + var diffX = first.clientX - startX, + diffY = first.clientY - startY; + + // make sure that distance between touchstart and touchend smaller than some threshold, + // e.g. <= 16 px + if (diffX * diffX + diffY * diffY <= 256) { + 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.target.dispatchEvent(simulatedEvent); + } + } } + + return function(e) { + // either dispatch real touch events + if (Crafty._multitouch) { + if (!touchSystem) touchSystem = Crafty.s('Touch'); + touchSystem.processEvent(e); + // or mimic mouse events + } else { + mimicMouse(e); + } + }; + })() +}); + +/**@ + * #TouchSystem + * @category Input + * @kind System + * + * Provides access to touch point events. + * @note Additional events and methods are inherited from the `TouchState` component. + * + * @trigger TouchOver - when a finger enters an entity - TouchPointEvent + * @trigger TouchOut - when a finger leaves an entity - TouchPointEvent + * + * The event callbacks are triggered with the native [`TouchEvent`](https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent) + * received by Crafty's stage (`Crafty.stage.elem`), which is wrapped in a standard Crafty event object (see `TouchState`). + * Note that for each changed [`Touch` point](https://developer.mozilla.org/en-US/docs/Web/API/Touch)) a separate + * Crafty TouchPointEvent event is triggered. + * + * These touch point events are triggered on the TouchSystem itself. + * Additionally, they are dispatched to the closest (visible & `Touch`-enhanced) entity to the source of the event (if available). + * + * @note By default, touch events are treated as mouse events and are not triggered by this system. + * To change this behaviour (and enable multitouch) use `Crafty.multitouch`. + * + * @see TouchState, Touch + * @see Crafty.multitouch + */ +Crafty.s("Touch", Crafty.extend.call(Crafty.extend.call(new Crafty.__eventDispatcher(), { + normedEventNames: { + "touchstart": "TouchStart", + "touchmove": "TouchMove", + "touchend": "TouchEnd", + "touchcancel": "TouchCancel" // touchcancel is treated as touchend, but triggers a TouchCancel event + }, + + _evt: { // evt object to reuse + eventName:'', + identifier: -1, + target: null, + entity: null, // DEPRECATED: remove this in upcoming release + realX: 0, + realY: 0, + originalEvent: null + }, + + // Indicates how many entities have the Touch component, for performance optimization + // Touch events are still routed to Crafty.s('Touch') even if there are no entities with Touch component + touchObjs: 0, + + // current entites that are pointed at + overs: {}, + + prepareEvent: function (e, type) { + var evt = this._evt; + + // Normalize event name + evt.eventName = this.normedEventNames[type] || type; + + // copy identifier + evt.identifier = e.identifier; + + // augment touch event with real coordinates + Crafty.translatePointerEventCoordinates(e, evt); + + // augment touch event with target entity + evt.target = this.touchObjs ? Crafty.findPointerEventTargetByComponent("Touch", e) : null; + // DEPRECATED: remove this in upcoming release + evt.entity = evt.target; + + return evt; }, - /**@ - * #.draw - * @comp WebGL - * @kind Method - * @private - * - * @sign public this .draw([[Context ctx, ]Number x, Number y, Number w, Number h]) - * @param ctx - Optionally supply a different r 2D context if drawing on another canvas is required - * @param x - X offset for drawing a segment - * @param y - Y offset for drawing a segment - * @param w - Width of the segment to draw - * @param h - Height of the segment to draw - * - * An internal method to draw the entity on the webgl canvas element. Rather then rendering directly, it writes relevent information into a buffer to allow batch rendering. - */ - draw: function (ctx, x, y, w, h) { + // this method will be called by TouchState iff triggerTouch event was valid + triggerTouchEvent: function(eventName, e) { + // trigger event on TouchSystem itself + this.trigger(eventName, e); - if (!this.ready) return; + var identifier = e.identifier, + closest = e.target, + over = this.overs[identifier]; + + if (over) { // if old TouchOver target wasn't null, send TouchOut + if ((eventName === "TouchMove" && over !== closest) || // if TouchOver target changed + eventName === "TouchEnd" || eventName === "TouchCancel") { // or TouchEnd occurred - if (arguments.length === 4) { - h = w; - w = y; - y = x; - x = ctx; - ctx = this._drawLayer.context; + e.eventName = "TouchOut"; + e.target = over; + e.entity = over; // DEPRECATED: remove this in upcoming release + over.trigger("TouchOut", e); + e.eventName = eventName; + e.target = closest; + e.entity = closest; // DEPRECATED: remove this in upcoming release + + // delete old over entity + delete this.overs[identifier]; + } } - var pos = this.drawVars.pos; - pos._x = (this._x + (x || 0)); - pos._y = (this._y + (y || 0)); - pos._w = (w || this._w); - pos._h = (h || this._h); + // TODO: move routing of events in future to controls system, make it similar to KeyboardSystem + // try to find closest element that will also receive touch event, whatever the event is + if (closest) { + closest.trigger(eventName, e); + } - 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); - co.w = w || coord[2]; - co.h = h || coord[3]; + if (closest) { // if new TouchOver target isn't null, send TouchOver + if (eventName === "TouchStart" || // if TouchStart occurred + (eventName === "TouchMove" && over !== closest)) { // or TouchOver target changed - // Handle flipX, flipY - // (Just swap the positions of e.g. x and x+w) - if (this._flipX ) { - co.x = co.x + co.w; - co.w = - co.w; + e.eventName = "TouchOver"; + closest.trigger("TouchOver", e); + e.eventName = eventName; + + // save new over entity + this.overs[identifier] = closest; + } } - if (this._flipY ) { - co.y = co.y + co.h; - co.h = - co.h; + }, + + dispatchEvent: function (e) { + var evt, touches = e.changedTouches; + for (var i = 0, l = touches.length; i < l; i++) { + evt = this.prepareEvent(touches[i], e.type); + // wrap original event into standard Crafty event object + evt.originalEvent = e; + this.triggerTouch(evt.eventName, evt); } + } +}), Crafty.__touchStateTemplate), {}, false); - //Draw entity - var gl = this._drawContext; - this.drawVars.gl = gl; - var prog = this.drawVars.program = this.program; +/**@ + * #Touch + * @category Input + * @kind Component + * + * Provides the entity with touch point events. + * Touch point events get dispatched to the closest (visible & `Touch`-enhanced) entity to the source of the event (if available). + * @note If you do not add this component, touch events will not be triggered on the entity. + * + * Triggers all events described in `TouchSystem` and `TouchState`, these are: + * @trigger TouchOver - when a finger enters the entity - TouchPointEvent + * @trigger TouchMove - when a finger is over the entity and moves - TouchPointEvent + * @trigger TouchOut - when a finger leaves the entity - TouchPointEvent + * @trigger TouchStart - when a finger is pressed on the entity - TouchPointEvent + * @trigger TouchEnd - when a finger is raised over the entity - TouchPointEvent + * @trigger TouchCancel - when a touch event has been disrupted in some way whilst over the entity - TouchPointEvent + * + * @note By default, touch events are treated as mouse events and are not triggered by this component. + * To change this behaviour (and enable multitouch) use `Crafty.multitouch`. + * + * @example + * ~~~ + * Crafty.multitouch(true); + * + * Crafty.e('2D, Canvas, Color, Touch') + * .attr({x: 10, y: 10, w: 40, h: 40}) + * .color('green') + * .bind('TouchOver', function(TouchPoint){ + * Crafty.log('A finger is over the entity', TouchPoint.identifier); + * }) + * .bind('TouchMove', function(TouchPoint) { + * Crafty.log('A finger moves over the entity at { x: ' + TouchPoint.realX + ', y: ' + TouchPoint.realY + ' } coordinates.'); + * }) + * .bind('TouchOut', function(TouchPoint){ + * Crafty.log('A finger is no longer over the entity', TouchPoint.identifier); + * }); + * ~~~ + * + * @example + * ~~~ + * Crafty.multitouch(true); + * + * var myEntity1 = Crafty.e('2D, Canvas, Color, Touch') + * .attr({x: 100, y: 100, w:200, h:200, z:1 }) + * .color('black') + * .bind('TouchStart',function(e){ alert('big black box was touched', e); }), + * myEntity2 = Crafty.e('2D, Canvas, Color, Touch') + * .attr({x: 40, y: 150, w:90, h:300, z:2 }) + * .color('green') + * .bind('TouchStart',function(e){ alert('big green box was touched', e); }); + * ~~~ + * + * @see TouchState, TouchSystem + * @see Crafty.multitouch + */ +Crafty.c("Touch", { + required: "AreaMap", + init: function () { + Crafty.s('Touch').touchObjs++; + }, + remove: function () { + Crafty.s('Touch').touchObjs--; + } +}); - // The program might need to refer to the current element's index - prog.setCurrentEntity(this); +},{"../core/core.js":10}],50:[function(require,module,exports){ +var Crafty = require('../core/core.js'); - // Write position; x, y, w, h - prog.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 - ); +// common base functionality for all EventDispatchers +Crafty.__eventDispatcher = function EventDispatcher() {}; +Crafty.__eventDispatcher.prototype = { + // this method should be setup as the entry callback for DOM events + processEvent: function (e) { + this.dispatchEvent(e); + return this.preventBubbling(e); + }, - // Write orientation - prog.writeVector("aOrientation", - this._origin.x + this._x, - this._origin.y + this._y, - this._rotation * Math.PI / 180 - ); + // main method that handles logic of incoming DOM events + // to be implemented by instances + dispatchEvent: function (e) { + // normalize the event and prepare it for dispatching to Crafty, a system or entities + // set e.eventName to proper event to be triggered - // Write z, alpha - prog.writeVector("aLayer", - this._globalZ, - this._alpha - ); + // dispatch the element to Crafty, the proper system or entities + // find the entity to dispatch to (e.g. mouse events) or dispatch it globally (e.g. key events) + }, - // This should only need to handle *specific* attributes! - this.trigger("Draw", this.drawVars); + // prevents interaction with page (e.g. scrolling of page), if DOM events target Crafty's stage + // automatically called for all incoming DOM events + preventBubbling: function (e) { + // only prevent something if DOM event targets Crafty's stage + // prevent bubbling up for all events except key events backspace and F1-F12. + // prevent default actions for all events except key events backspace and F1-F12 and except actions on INPUT and TEXTAREA. + // 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 (e.stopPropagation) e.stopPropagation(); + else e.cancelBubble = true; - // Register the vertex groups to be drawn, referring to this entities position in the big buffer - prog.addIndices(prog.ent_offset); + // Don't prevent default actions if target node is input or textarea. + if (!e.target || (e.target.nodeName !== 'INPUT' && e.target.nodeName !== 'TEXTAREA')) { + if (e.preventDefault) { + e.preventDefault(); + } else { + e.returnValue = false; + } + return false; + } + return true; + } + } +}; - return this; - }, - // v_src is optional, there's a default vertex shader that works for regular rectangular entities - _establishShader: function(compName, shader){ - this.program = this._drawLayer.getProgramWrapper(compName, shader); +Crafty.extend({ + /**@ + * #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`. + * + * Defaults to true. + * + * @see Crafty.stage#Crafty.stage.elem + */ + selected: true, + + detectBlur: function (e) { + var selected = ((e.clientX > Crafty.stage.x && e.clientX < Crafty.stage.x + Crafty.viewport.width) && + (e.clientY > Crafty.stage.y && e.clientY < Crafty.stage.y + Crafty.viewport.height)); + + if (!Crafty.selected && selected) { + Crafty.trigger("CraftyFocus"); + } + + if (Crafty.selected && !selected) { + Crafty.trigger("CraftyBlur"); + } - // Needs to know where in the big array we are! - this.program.registerEntity(this); - // Shader program means ready - this.ready = true; + Crafty.selected = selected; } }); -},{"../core/core.js":9}],41:[function(require,module,exports){ +},{"../core/core.js":10}],51:[function(require,module,exports){ var Crafty = require('../core/core.js'); @@ -12909,7 +14145,7 @@ Crafty.extend({ }); -},{"../core/core.js":9}],42:[function(require,module,exports){ +},{"../core/core.js":10}],52:[function(require,module,exports){ var Crafty = require('../core/core.js'); @@ -13112,7 +14348,7 @@ Crafty.extend({ } }); -},{"../core/core.js":9}],43:[function(require,module,exports){ +},{"../core/core.js":10}],53:[function(require,module,exports){ var Crafty = require('../core/core.js'), document = window.document; @@ -13700,21 +14936,9 @@ Crafty.extend({ } }); -},{"../core/core.js":9}],44:[function(require,module,exports){ -var Crafty = require('../core/core.js'), - HashMap = require('./spatial-grid.js'); - - +},{"../core/core.js":10}],54:[function(require,module,exports){ +var Crafty = require('../core/core.js'); -/**@ - * #Crafty.map - * @category 2D - * @kind CoreObject - * - * Functions related with querying entities. - * @see Crafty.HashMap - */ -Crafty.map = new HashMap(); var M = Math, //Mc = M.cos, //Ms = M.sin, @@ -13729,8 +14953,9 @@ var M = Math, * Component for any entity that has a position on the stage. * @trigger Move - when the entity has moved - { _x:Number, _y:Number, _w:Number, _h:Number } - Old position * @trigger Invalidate - when the entity needs to be redrawn - * @trigger Rotate - when the entity is rotated - { cos:Number, sin:Number, deg:Number, rad:Number, o: {x:Number, y:Number}} + * @trigger Rotate - when the entity is rotated - { rotation:Number} - Rotation in degrees * @trigger Reorder - when the entity's z index has changed + * @trigger Resize - when the entity's dimensions have changed - { axis: 'w' | 'h', amount: Number } */ Crafty.c("2D", { /**@ @@ -13841,8 +15066,8 @@ Crafty.c("2D", { _children: null, _parent: null, - // Setup all the properties that we need to define - _2D_property_definitions: { + // Setup all the properties that we need to define + properties: { x: { set: function (v) { this._setter2d('_x', v); @@ -13913,12 +15138,48 @@ Crafty.c("2D", { configurable: true, enumerable: true }, - _rotation: {enumerable:false} - }, + _rotation: {enumerable:false}, + + /**@ + * #.ox + * @comp 2D + * @kind Property + * + * The `x` position on the stage of the origin. When modified, will set the underlying `x` value of the entity. + * + * @see .origin + */ + ox: { + set: function (v) { + var x = v - this._origin.x; + this._setter2d('_x', x); + }, + get: function () { + return this._x + this._origin.x; + }, + configurable: true, + enumerable: true + }, - _define2DProperties: function () { - for (var prop in this._2D_property_definitions){ - Object.defineProperty(this, prop, this._2D_property_definitions[prop]); + /**@ + * #.oy + * @comp 2D + * @kind Property + * + * The `y` position on the stage of the origin. When modified, will set the underlying `y` value of the entity. + * + * @see .origin + */ + oy: { + set: function (v) { + var y = v - this._origin.y; + this._setter2d('_y', y); + }, + get: function () { + return this._y + this._origin.y; + }, + configurable: true, + enumerable: true } }, @@ -13937,13 +15198,9 @@ Crafty.c("2D", { this._children = []; - - // create setters and getters that associate properties such as x/_x - this._define2DProperties(); - - //insert self into the HashMap this._entry = Crafty.map.insert(this); + //when object changes, update HashMap this.bind("Move", function (e) { @@ -13962,7 +15219,7 @@ Crafty.c("2D", { this._entry.update(old); // Rotate children (if any) by the same amount if (this._children.length > 0) { - this._cascade(e); + this._cascadeRotation(e); } }); @@ -13993,6 +15250,14 @@ Crafty.c("2D", { }); }, + events: { + "Freeze":function(){ + Crafty.map.remove(this._entry); + }, + "Unfreeze":function(){ + this._entry = Crafty.map.insert(this, this._entry); + } + }, /**@ * #.offsetBoundary @@ -14104,30 +15369,9 @@ Crafty.c("2D", { else this._rotation = v; - //Calculate the new MBR - var //rad = theta * DEG_TO_RAD, - o = { - x: this._origin.x + this._x, - y: this._origin.y + this._y - }; - this._calculateMBR(); - - //trigger "Rotate" event - var drad = difference * DEG_TO_RAD, - // ct = Math.cos(rad), - // st = Math.sin(rad), - cos = Math.cos(drad), - sin = Math.sin(drad); - - this.trigger("Rotate", { - cos: (-1e-10 < cos && cos < 1e-10) ? 0 : cos, // Special case 90 degree rotations to prevent rounding problems - sin: (-1e-10 < sin && sin < 1e-10) ? 0 : sin, // Special case 90 degree rotations to prevent rounding problems - deg: difference, - rad: drad, - o: o - }); + this.trigger("Rotate", difference); }, /**@ @@ -14352,8 +15596,7 @@ Crafty.c("2D", { * for an opposite direction. */ shift: function (x, y, w, h) { - if (x) this.x += x; - if (y) this.y += y; + if (x || y) this._setPosition(this._x + x, this._y + y); if (w) this.w += w; if (h) this.h += h; @@ -14369,7 +15612,7 @@ Crafty.c("2D", { * @sign public void ._cascade(e) * @param e - An object describing the motion * - * Move or rotate the entity's children according to a certain motion. + * Move the entity's children according to a certain motion. * This method is part of a function bound to "Move": It is used * internally for ensuring that when a parent moves, the child also * moves in the same way. @@ -14380,23 +15623,55 @@ Crafty.c("2D", { children = this._children, l = children.length, obj; - //rotation - if (("cos" in e) || ("sin" in e)) { - for (; i < l; ++i) { - obj = children[i]; - if ('rotate' in obj) obj.rotate(e); - } - } else { - //use current position - var dx = this._x - e._x, - dy = this._y - e._y, - dw = this._w - e._w, - dh = this._h - e._h; - for (; i < l; ++i) { - obj = children[i]; - obj.shift(dx, dy, dw, dh); - } + //use current position + var dx = this._x - e._x, + dy = this._y - e._y, + dw = this._w - e._w, + dh = this._h - e._h; + + for (; i < l; ++i) { + obj = children[i]; + if (obj.__frozen) continue; + obj.shift(dx, dy, dw, dh); + } + + }, + + /**@ + * #._cascadeRotation + * @comp 2D + * @kind Method + * @private + * + * @sign public void ._cascade(deg) + * @param deg - The amount of rotation in degrees + * + * Move the entity's children the specified amount + * This method is part of a function bound to "Move": It is used + * internally for ensuring that when a parent moves, the child also + * moves in the same way. + */ + _cascadeRotation: function(deg) { + if (!deg) return; + var i = 0, + children = this._children, + l = children.length, + obj; + // precalculate rotation info + var drad = deg * DEG_TO_RAD; + var cos = Math.cos(drad); + var sin = Math.sin(drad); + // Avoid some rounding problems + cos = (-1e-10 < cos && cos < 1e-10) ? 0 : cos; + sin = (-1e-10 < sin && sin < 1e-10) ? 0 : sin; + var ox = this._origin.x + this._x; + var oy = this._origin.y + this._y; + + for (; i < l; ++i) { + obj = children[i]; + if (obj.__frozen) continue; + if ('rotate' in obj) obj.rotate(deg, ox, oy, cos, sin); } }, @@ -14483,7 +15758,12 @@ Crafty.c("2D", { * @param offset - Alignment identifier, which is a combination of center, top, bottom, middle, left and right * * Set the origin point of an entity for it to rotate around. + * + * The properties `ox` and `oy` map to the coordinates of the origin on the stage; setting them moves the entity. + * In contrast, this method sets the origin relative to the entity itself. * + * @triggers OriginChanged -- after the new origin is assigned + * * @example * ~~~ * this.origin("top left") @@ -14506,7 +15786,7 @@ Crafty.c("2D", { * .attr({x: 25, y: 25, rotation: 180}); * ~~~ * - * @see .rotation + * @see .rotation, .ox, .oy */ origin: function (x, y) { //text based origin @@ -14528,22 +15808,46 @@ Crafty.c("2D", { this._origin.x = x; this._origin.y = y; - + this.trigger("OriginChanged"); return this; }, /** * Method for rotation rather than through a setter + * + * Pass in degree amount, origin coordinate and precalculated cos/sin */ - rotate: function (e) { + rotate: function (deg, ox, oy, cos, sin) { var x2, y2; - x2 = (this._x + this._origin.x - e.o.x) * e.cos + (this._y + this._origin.y - e.o.y) * e.sin + (e.o.x - this._origin.x); - y2 = (this._y + this._origin.y - e.o.y) * e.cos - (this._x + this._origin.x - e.o.x) * e.sin + (e.o.y - this._origin.y); - this._setter2d('_rotation', this._rotation - e.deg); + x2 = (this._x + this._origin.x - ox) * cos + (this._y + this._origin.y - oy) * sin + (ox - this._origin.x); + y2 = (this._y + this._origin.y - oy) * cos - (this._x + this._origin.x - ox) * sin + (oy - this._origin.y); + this._setter2d('_rotation', this._rotation - deg); this._setter2d('_x', x2 ); this._setter2d('_y', y2 ); }, + // A separate setter for the common case of moving an entity along both axes + _setPosition: function(x, y) { + if (x === this._x && y === this._y) return; + var old = Crafty.rectManager._pool.copy(this); + var mbr = this._mbr; + if (mbr) { + mbr._x -= this._x - x; + mbr._y -= this._y - y; + // cbr is a non-minimal bounding rectangle that contains both hitbox and mbr + // It will exist only when the collision hitbox sits outside the entity + if (this._cbr){ + this._cbr._x -= this._x - x; + this._cbr._y -= this._y - y; + } + } + this._x = x; + this._y = y; + this.trigger("Move", old); + this.trigger("Invalidate"); + Crafty.rectManager._pool.recycle(old); + }, + // This is a setter method for all 2D properties including // x, y, w, h, and rotation. _setter2d: function (name, value) { @@ -14564,7 +15868,7 @@ Crafty.c("2D", { mbr = this._mbr; if (mbr) { mbr[name] -= this[name] - value; - // cbr is a non-minmal bounding rectangle that contains both hitbox and mbr + // cbr is a non-minimal bounding rectangle that contains both hitbox and mbr // It will exist only when the collision hitbox sits outside the entity if (this._cbr){ this._cbr[name] -= this[name] - value; @@ -14728,15 +16032,15 @@ Crafty.polygon.prototype = { return new Crafty.polygon(this.points.slice(0)); }, - rotate: function (e) { + rotate: function (deg, ox, oy, cos, sin) { var i = 0, p = this.points, l = p.length, x, y; for (; i < l; i+=2) { - 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; + x = ox + (p[i] - ox) * cos + (p[i+1] - oy) * sin; + y = oy - (p[i] - ox) * sin + (p[i+1] - oy) * cos; p[i] = x; p[i+1] = y; @@ -14999,7 +16303,7 @@ Crafty.matrix.prototype = { } }; -},{"../core/core.js":9,"./spatial-grid.js":50}],45:[function(require,module,exports){ +},{"../core/core.js":10}],55:[function(require,module,exports){ var Crafty = require('../core/core.js'), DEG_TO_RAD = Math.PI / 180, EPSILON = 1e-6; @@ -15281,14 +16585,12 @@ Crafty.c("Collision", { // 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 - } - }); + polygon.rotate( + this.rotation, + this._origin.x, + this._origin.y, + Math.cos(-this.rotation * DEG_TO_RAD), + Math.sin(-this.rotation * DEG_TO_RAD)); } // Finally, assign the hitbox, and attach it to the "Collision" entity @@ -15429,18 +16731,23 @@ Crafty.c("Collision", { * @comp Collision * @kind Method * - * @sign public Array .hit(String component) + * @sign public Array .hit(String component[, Array results]) * @param component - Check collision with entities that have this component * applied to them. + * @param results - If a results array is supplied, any collisions will be appended to it * @return `null` if there is no collision. If a collision is detected, * returns an Array of collision data objects (see below). + * If the results parameter was passed, it will be used as the return value. * - * 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`. + * 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`. * + * When testing for collisions, if both entities have the `Collision` component, then + * the collision test will use the Separating Axis Theorem (SAT), and provide more detailed + * information about the collision. Otherwise, it will be a simple test of whether the + * minimal bounding rectangles (MBR) overlap. + * * 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. @@ -15448,17 +16755,27 @@ Crafty.c("Collision", { * [{ * obj: [entity], * type: ["MBR" or "SAT"], - * overlap: [number] + * overlap: [number], + * nx: [number], + * ny: [number] * }] * ~~~ * + * All collision results will have these properties: * - **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. + * + * If the collision result type is **SAT** then there will be three additional properties, which + * represent the minimum translation vector (MTV) -- the direction and distance of the minimal translation + * that will result in non-overlapping entities. + * - **overlap:** The magnitude of the translation vector. + * - **nx:** The x component of the MTV. + * - **ny:** The y component of the MTV. * - * Keep in mind that both entities need to have the `Collision` component, if you want to check for `SAT` (custom hitbox) collisions between them. + * These additional properties (returned only when both entities have the "Collision" component) + * are useful when providing more natural collision resolution. * * If you want more fine-grained control consider using `Crafty.map.search()`. * @@ -15475,8 +16792,8 @@ Crafty.c("Collision", { * 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; + * this.x -= hitData.overlap * hitData.nx; + * this.y -= hitData.overlap * hitData.ny; * } else { // MBR, simple collision resolution * // move player to position before he moved (on respective axis) * this[evt.axis] = evt.oldValue; @@ -15487,54 +16804,54 @@ Crafty.c("Collision", { * * @see Crafty.map#Crafty.map.search */ - 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 = []; - + _collisionHitDupes: [], + _collisionHitResults: [], + hit: function (component, results) { + var area = this._cbr || this._mbr || this; + var searchResults = this._collisionHitResults; + searchResults.length = 0; + searchResults = Crafty.map.unfilteredSearch(area, searchResults); + var l = searchResults.length; if (!l) { return null; } + var i = 0, + dupes = this._collisionHitDupes, + id, obj; + + results = results || []; + dupes.length = 0; for (; i < l; ++i) { - obj = results[i]; - oarea = obj._cbr || obj._mbr || obj; //use the mbr + obj = searchResults[i]; 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)) + if (!dupes[id] && this[0] !== id && obj.__c[component]){ dupes[id] = obj; - } - - 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 (obj.map) { + var SAT = this._SAT(this.map, obj.map); + if (SAT) { + results.push(SAT); + SAT.obj = obj; + SAT.type = "SAT"; + } + } else if (Crafty.rectManager.overlap(area, obj._cbr || obj._mbr || obj)){ + results.push({ + obj: obj, + type: "MBR" + }); + } } } - if (!finalresult.length) { + if (!results.length) { return null; } - return finalresult; + return results; }, /**@ @@ -15547,9 +16864,9 @@ Crafty.c("Collision", { * @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. + * @param callbackOff - Callback method executed once as soon as collision stops. No arguments are passed. * - * Creates an EnterFrame event calling `.hit()` each frame. When a collision is detected the `callbackOn` will be invoked. + * Creates an `UpdateFrame` 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. @@ -15577,7 +16894,7 @@ Crafty.c("Collision", { */ onHit: function (component, callbackOn, callbackOff) { var justHit = false; - this.bind("EnterFrame", function () { + this.bind("UpdateFrame", function () { var hitData = this.hit(component); if (hitData) { callbackOn.call(this, hitData, !justHit); @@ -15642,10 +16959,10 @@ Crafty.c("Collision", { * * 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 + * @note Hit checks are performed on each new frame (using + * the *UpdateFrame* 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 + * result of *UpdateFrame*, 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. * @@ -15684,7 +17001,7 @@ Crafty.c("Collision", { this._collisionData[component] = collisionData = { occurring: false, handler: null }; collisionData.handler = this._createCollisionHandler(component, collisionData); - this.bind("EnterFrame", collisionData.handler); + this.bind("UpdateFrame", collisionData.handler); } return this; @@ -15728,7 +17045,7 @@ Crafty.c("Collision", { if (components.length === 0) { for (collisionData in this._collisionData) { - this.unbind("EnterFrame", collisionData.handler); + this.unbind("UpdateFrame", collisionData.handler); } this._collisionData = {}; @@ -15746,7 +17063,7 @@ Crafty.c("Collision", { continue; } - this.unbind("EnterFrame", collisionData.handler); + this.unbind("UpdateFrame", collisionData.handler); delete this._collisionData[component]; } @@ -15938,15 +17255,13 @@ Crafty.c("Collision", { return { overlap: MTV, - normal: { - x: MNx, - y: MNy - } + nx: MNx, + ny: MNy }; } }); -},{"../core/core.js":9}],46:[function(require,module,exports){ +},{"../core/core.js":10}],56:[function(require,module,exports){ var Crafty = require('../core/core.js'); @@ -16188,18 +17503,30 @@ Crafty.math.Vector2D = (function () { * #Crafty.math.Vector2D * @category 2D * @kind Class + * @public * * @class This is a general purpose 2D vector class * - * Vector2D uses the following form: - * + * Vector2D has the following constructors: * - * @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 {Vector2D} new Vector2D(); + * @returns {Vector2D} A new vector with x and y equal to 0 + * + * @sign public {Vector2D} new Vector2D(Number x, Number y); + * @param {Number} x - The initial x value + * @param {Number} y - The initial y value + * @returns {Vector2D} - A new vector with the given x and y values + * + * @sign public {Vector2D} new Vector2D(Vector2D vector); + * @param {Vector2D} vector - A vector to copy + * @returns {Vector2D} A new vector with the copied x and y values + * + * @example + * ``` + * var v1 = new Crafty.math.Vector2D(3, 5); + * var v2 = new Crafty.math.Vector2D(v1); + * ``` + * */ function Vector2D(x, y) { @@ -16220,14 +17547,13 @@ Crafty.math.Vector2D = (function () { * #.add * @comp Crafty.math.Vector2D * @kind Method - * + * @public * * Adds the passed vector to this vector * - * @public - * @sign public {Vector2D} add(Vector2D); - * @param {vector2D} vecRH - * @returns {Vector2D} this after adding + * @sign public {Vector2D} add(Vector2D vecRH); + * @param {Vector2D} vecRH - The vector to add + * @returns {Vector2D} The resulting modified vector */ Vector2D.prototype.add = function (vecRH) { this.x += vecRH.x; @@ -16239,31 +17565,29 @@ Crafty.math.Vector2D = (function () { * #.angleBetween * @comp Crafty.math.Vector2D * @kind Method - * + * @public * * Calculates the angle between the passed vector and this vector, using <0,0> as the point of reference. * Angles returned have the range (−π, π]. * - * @public - * @sign public {Number} angleBetween(Vector2D); - * @param {Vector2D} vecRH + * @sign public {Number} angleBetween(Vector2D vecRH); + * @param {Vector2D} vecRH - The vector to compare * @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 - * * * Calculates the angle to the passed vector from this vector, using this vector as the point of reference. * * @public - * @sign public {Number} angleTo(Vector2D); - * @param {Vector2D} vecRH + * @sign public {Number} angleTo(Vector2D vecRH); + * @param {Vector2D} vecRH - The vector to compare * @returns {Number} the angle to the passed vector in radians */ Vector2D.prototype.angleTo = function (vecRH) { @@ -16295,8 +17619,8 @@ Crafty.math.Vector2D = (function () { * Calculates the distance from this vector to the passed vector. * * @public - * @sign public {Number} distance(Vector2D); - * @param {Vector2D} vecRH + * @sign public {Number} distance(Vector2D vecRH); + * @param {Vector2D} vecRH - The passed vector * @returns {Number} the distance between the two vectors */ Vector2D.prototype.distance = function (vecRH) { @@ -16313,9 +17637,10 @@ Crafty.math.Vector2D = (function () { * This function avoids calculating the square root, thus being slightly faster than .distance( ). * * @public - * @sign public {Number} distanceSq(Vector2D); - * @param {Vector2D} vecRH + * @sign public {Number} distanceSq(Vector2D vecRH); + * @param {Vector2D} vecRH - The passed vector * @returns {Number} the squared distance between the two vectors + * * @see .distance */ Vector2D.prototype.distanceSq = function (vecRH) { @@ -16331,8 +17656,8 @@ Crafty.math.Vector2D = (function () { * Divides this vector by the passed vector. * * @public - * @sign public {Vector2D} divide(Vector2D); - * @param {Vector2D} vecRH + * @sign public {Vector2D} divide(Vector2D vecRH); + * @param {Vector2D} vecRH - The passed vector * @returns {Vector2D} this vector after dividing */ Vector2D.prototype.divide = function (vecRH) { @@ -16350,8 +17675,8 @@ Crafty.math.Vector2D = (function () { * Calculates the dot product of this and the passed vectors * * @public - * @sign public {Number} dotProduct(Vector2D); - * @param {Vector2D} vecRH + * @sign public {Number} dotProduct(Vector2D vecRH); + * @param {Vector2D} vecRH - The passed vector * @returns {Number} the resultant dot product */ Vector2D.prototype.dotProduct = function (vecRH) { @@ -16367,8 +17692,8 @@ Crafty.math.Vector2D = (function () { * Calculates the z component of the cross product of the two vectors augmented to 3D. * * @public - * @sign public {Number} crossProduct(Vector2D); - * @param {Vector2D} vecRH + * @sign public {Number} crossProduct(Vector2D vecRH); + * @param {Vector2D} vecRH - The passed vector * @returns {Number} the resultant cross product */ Vector2D.prototype.crossProduct = function (vecRH) { @@ -16384,8 +17709,8 @@ Crafty.math.Vector2D = (function () { * Determines if this vector is numerically equivalent to the passed vector. * * @public - * @sign public {Boolean} equals(Vector2D); - * @param {Vector2D} vecRH + * @sign public {Boolean} equals(Vector2D vecRH); + * @param {Vector2D} vecRH - The passed vector * @returns {Boolean} true if the vectors are equivalent */ Vector2D.prototype.equals = function (vecRH) { @@ -16403,7 +17728,7 @@ Crafty.math.Vector2D = (function () { * 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]); + * @sign public {Vector2D} perpendicular([Vector2D result]); * @param {Vector2D} [result] - An optional parameter to save the result in * @returns {Vector2D} the perpendicular vector */ @@ -16420,8 +17745,8 @@ Crafty.math.Vector2D = (function () { * 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 + * @sign public {Vector2D} getNormal(Vector2D vecRH[, Vector2D result]); + * @param {Vector2D} vecRH - The passed vector * @param {Vector2D} [result] - An optional parameter to save the result in * @returns {Vector2D} the new normal vector */ @@ -16484,11 +17809,11 @@ Crafty.math.Vector2D = (function () { * @comp Crafty.math.Vector2D * @kind Method * - * Multiplies this vector by the passed vector + * Multiplies this vector by the passed vector, using component-wise multiplciation * * @public - * @sign public {Vector2D} multiply(Vector2D); - * @param {Vector2D} vecRH + * @sign public {Vector2D} multiply(Vector2D vecRH); + * @param {Vector2D} vecRH - The passed vector * @returns {Vector2D} this vector after multiplying */ Vector2D.prototype.multiply = function (vecRH) { @@ -16547,12 +17872,15 @@ Crafty.math.Vector2D = (function () { * @kind Method * * 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] + * @sign public {Vector2D} scale(Number scale); + * @param {Number} scale - The amount to scale by + * @returns {Vector2D} this after scaling + * + * @sign public {Vector2D} scale(Number scalarX, Number scalarY); + * @param {Number} scalarX - The amount to scale x by + * @param {Number} [scalarY] - The amount to scale y by * @returns {Vector2D} this after scaling */ Vector2D.prototype.scale = function (scalarX, scalarY) { @@ -16573,8 +17901,8 @@ Crafty.math.Vector2D = (function () { * Scales this vector such that its new magnitude is equal to the passed value. * * @public - * @sign public {Vector2D} scaleToMagnitude(Number); - * @param {Number} mag + * @sign public {Vector2D} scaleToMagnitude(Number mag); + * @param {Number} mag - The desired magnitude * @returns {Vector2D} this vector after scaling */ Vector2D.prototype.scaleToMagnitude = function (mag) { @@ -16592,11 +17920,14 @@ Crafty.math.Vector2D = (function () { * Sets the values of this vector using a passed vector or pair of numbers. * * @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 + * @sign public {Vector2D} setValues(Vector2D vector); + * @param {Vector2D} vector - a vector to copy + * @returns {Vector2D} this vector after copying the values + * + * @sign public {Vector2D} setValues(Number x, Number y); + * @param {Number} x - The x value to set + * @param {Number} y - The y value to set + * @returns {Vector2D} this vector after setting the values */ Vector2D.prototype.setValues = function (x, y) { if (x instanceof Vector2D) { @@ -16618,8 +17949,8 @@ Crafty.math.Vector2D = (function () { * Subtracts the passed vector from this vector. * * @public - * @sign public {Vector2D} subtract(Vector2D); - * @param {Vector2D} vecRH + * @sign public {Vector2D} subtract(Vector2D vecRH); + * @param {Vector2D} vecRH - the passed vector to subtract * @returns {vector2D} this vector after subtracting */ Vector2D.prototype.subtract = function (vecRH) { @@ -16637,7 +17968,7 @@ Crafty.math.Vector2D = (function () { * * @public * @sign public {String} toString(); - * @returns {String} + * @returns {String} A representation like "Vector2D(4, 7)" */ Vector2D.prototype.toString = function () { return "Vector2D(" + this.x + ", " + this.y + ")"; @@ -16652,9 +17983,9 @@ Crafty.math.Vector2D = (function () { * If dy is omitted, dx is used for both axes. * * @public - * @sign public {Vector2D} translate(Number[, Number]); - * @param {Number} dx - * @param {Number} [dy] + * @sign public {Vector2D} translate(Number dx[, Number dy]); + * @param {Number} dx - The amount to shift by + * @param {Number} [dy] - The amount to shift along the y axis * @returns {Vector2D} this vector after translating */ Vector2D.prototype.translate = function (dx, dy) { @@ -16677,10 +18008,10 @@ Crafty.math.Vector2D = (function () { * * @public * @static - * @sign public {Vector2D} tripleProduct(Vector2D, Vector2D, Vector2D, [Vector2D]); - * @param {Vector2D} a - * @param {Vector2D} b - * @param {Vector2D} c + * @sign public {Vector2D} tripleProduct(Vector2D a, Vector2D b, Vector2D c, [Vector2D result]); + * @param {Vector2D} a - The first vector + * @param {Vector2D} b - The second vector + * @param {Vector2D} c - The third vector * @param {Vector2D} [result] - An optional parameter to save the result in * @return {Vector2D} the triple product as a new vector */ @@ -16710,14 +18041,29 @@ Crafty.math.Matrix2D = (function () { * * @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 + * @returns {Matrix2D} A new identity matrix + * + * @sign public {Matrix2D} new Matrix2D(Matrix2D matrix); + * @param {Matrix2D} matrix - a matrix to copy + * @returns {Matrix2D} A new instance whose entries are copied from the passed matrix + * + * @sign public {Matrix2D} new Matrix2D(Number a, Number b, Number c, Number d, Number e, Number f); + * @param {Number=1} a - (m11) Horizontal scale + * @param {Number=0} b - (m12) Horizontal skew + * @param {Number=0} c - (m21) Vertical skew + * @param {Number=1} d - (m22) Vertical scale + * @param {Number=0} e - (dx) Horizontal translation + * @param {Number=0} f - (dy) Vertical translation + * @returns {Matrix2D} A new instance whose entries are set from the passed arguments + * + * @example + * ``` + * // Create the following translation matrix: + * // [1, 0, 5] + * // [0, 1, 7] + * // [0, 0, 1] + * var m = new Crafty.math.Matrix2D(1, 0, 0, 1, 5, 7); + * ``` */ function Matrix2D (a, b, c, d, e, f) { if (a instanceof Matrix2D) { @@ -16753,7 +18099,7 @@ Crafty.math.Matrix2D = (function () { * Applies the matrix transformations to the passed object * * @public - * @sign public {Vector2D} apply(Vector2D); + * @sign public {Vector2D} apply(Vector2D vecRH); * @param {Vector2D} vecRH - vector to be transformed * @returns {Vector2D} the passed vector object after transforming */ @@ -16780,7 +18126,7 @@ Crafty.math.Matrix2D = (function () { * * @public * @sign public {Matrix2D} clone(); - * @returns {Matrix2D} + * @returns {Matrix2D} The cloned matrix */ Matrix2D.prototype.clone = function () { return new Matrix2D(this); @@ -16795,8 +18141,8 @@ Crafty.math.Matrix2D = (function () { * The passed matrix is assumed to be on the right-hand side. * * @public - * @sign public {Matrix2D} combine(Matrix2D); - * @param {Matrix2D} mtrxRH + * @sign public {Matrix2D} combine(Matrix2D mtrxRH); + * @param {Matrix2D} mtrxRH - The passed matrix * @returns {Matrix2D} this matrix after combination */ Matrix2D.prototype.combine = function (mtrxRH) { @@ -16817,11 +18163,11 @@ Crafty.math.Matrix2D = (function () { * @comp Crafty.math.Matrix2D * @kind Method * - * Checks for the numeric equality of this matrix versus another. + * Checks for the numeric element-wise equality of this matrix versus another. * * @public - * @sign public {Boolean} equals(Matrix2D); - * @param {Matrix2D} mtrxRH + * @sign public {Boolean} equals(Matrix2D mtrxRH); + * @param {Matrix2D} mtrxRH - The matrix to check equality with * @returns {Boolean} true if the two matrices are numerically equal */ Matrix2D.prototype.equals = function (mtrxRH) { @@ -16890,7 +18236,7 @@ Crafty.math.Matrix2D = (function () { * * @public * @sign public {Boolean} isIdentity(); - * @returns {Boolean} + * @returns {Boolean} true if this matrix is an identity matrix */ Matrix2D.prototype.isIdentity = function () { return this.a === 1 && this.b === 0 && this.c === 0 && this.d === 1 && this.e === 0 && this.f === 0; @@ -16920,7 +18266,7 @@ Crafty.math.Matrix2D = (function () { * Applies a counter-clockwise pre-rotation to this matrix * * @public - * @sign public {Matrix2D} preRotate(Number); + * @sign public {Matrix2D} preRotate(Number rads); * @param {number} rads - angle to rotate in radians * @returns {Matrix2D} this matrix after pre-rotation */ @@ -16943,12 +18289,14 @@ Crafty.math.Matrix2D = (function () { * @comp Crafty.math.Matrix2D * @kind Method * - * Applies a pre-scaling to this matrix + * Applies a pre-scaling to this matrix, applied to the a, b, c, and d elements. + * + * If two arguments are supplied, a and c are multiplied by scalarX, b, and d by scalarY. * * @public - * @sign public {Matrix2D} preScale(Number[, Number]); - * @param {Number} scalarX - * @param {Number} [scalarY] scalarX is used if scalarY is undefined + * @sign public {Matrix2D} preScale(Number scalarX[, Number scalarY]); + * @param {Number} scalarX - The amount to scale + * @param {Number} [scalarY] - scalarX is used if scalarY is undefined * @returns {Matrix2D} this after pre-scaling */ Matrix2D.prototype.preScale = function (scalarX, scalarY) { @@ -16971,10 +18319,13 @@ Crafty.math.Matrix2D = (function () { * 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 + * @sign public {Matrix2D} preTranslate(Number dx, Number dy); + * @param {Number} dx - The amount to shift the e component + * @param {Number} dy - The amount to shift the f component + * @returns {Matrix2D} this matrix after pre-translation + * + * @sign public {Matrix2D} preTranslate(Vector2D vector); + * @param {Vector2D} vector - The vector to shift (e, f) by. * @returns {Matrix2D} this matrix after pre-translation */ Matrix2D.prototype.preTranslate = function (dx, dy) { @@ -16997,7 +18348,7 @@ Crafty.math.Matrix2D = (function () { * Applies a counter-clockwise post-rotation to this matrix * * @public - * @sign public {Matrix2D} rotate(Number); + * @sign public {Matrix2D} rotate(Number rads); * @param {Number} rads - angle to rotate in radians * @returns {Matrix2D} this matrix after rotation */ @@ -17023,11 +18374,13 @@ Crafty.math.Matrix2D = (function () { * @comp Crafty.math.Matrix2D * @kind Method * - * Applies a post-scaling to this matrix + * Applies a post-scaling to this matrix, modifying components a-f. + * + * If two arguments are passed, scalarX is used for components a, c, and e; scalarY for b, d, and f. * * @public - * @sign public {Matrix2D} scale(Number[, Number]); - * @param {Number} scalarX + * @sign public {Matrix2D} scale(Number scalarX[, Number scalarY]); + * @param {Number} scalarX The amount to scale by along the x axis * @param {Number} [scalarY] scalarX is used if scalarY is undefined * @returns {Matrix2D} this after post-scaling */ @@ -17050,17 +18403,21 @@ Crafty.math.Matrix2D = (function () { * @comp Crafty.math.Matrix2D * @kind Method * - * Sets the values of this matrix + * Sets the values of this matrix. * * @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 + * @sign public {Matrix2D} setValues(Matrix2D matrix); + * @param {Matrix2D} matrix - A matrix to copy the values from + * @returns {Matrix2D} This matrix after copying the values + * + * @sign public {Matrix2D} setValues(Number a, Number b, Number c, Number d, Number e, Number f); + * When used as a translation matrix, the 6 elements have particular meanings. + * @param {Number} a - (m11) Horizontal scale + * @param {Number} b - (m12) Horizontal skew + * @param {Number} c - (m21) Vertical skew + * @param {Number} d - (m22) Vertical scale + * @param {Number} e - (dx) Horizontal translation + * @param {Number} f - (dy) Vertical translation * @returns {Matrix2D} this matrix containing the new values */ Matrix2D.prototype.setValues = function (a, b, c, d, e, f) { @@ -17092,7 +18449,7 @@ Crafty.math.Matrix2D = (function () { * * @public * @sign public {String} toString(); - * @returns {String} + * @returns {String} A string representation like "Matrix2D([a, c, e], [b, d, f], [0, 0, 1])" */ Matrix2D.prototype.toString = function () { return "Matrix2D([" + this.a + ", " + this.c + ", " + this.e + @@ -17107,10 +18464,13 @@ Crafty.math.Matrix2D = (function () { * Applies a post-translation to this matrix * * @public - * @sign public {Matrix2D} translate(Vector2D); - * @sign public {Matrix2D} translate(Number, Number); - * @param {Number|Vector2D} dx - * @param {Number} dy + * @sign public {Matrix2D} translate(Vector2D vector); + * @param {Vector2D} vector - the vector to translate by + * @returns {Matrix2D} this matrix after post-translation + * + * @sign public {Matrix2D} translate(Number dx, Number dy); + * @param {Number} dx - The shift along the x-axis + * @param {Number} dy - The shift along the y-axis * @returns {Matrix2D} this matrix after post-translation */ Matrix2D.prototype.translate = function (dx, dy) { @@ -17127,7 +18487,7 @@ Crafty.math.Matrix2D = (function () { return Matrix2D; })(); -},{"../core/core.js":9}],47:[function(require,module,exports){ +},{"../core/core.js":10}],57:[function(require,module,exports){ var Crafty = require('../core/core.js'); @@ -17265,10 +18625,10 @@ Crafty.c("AngularMotion", { this.__oldRotationDirection = 0; - this.bind("EnterFrame", this._angularMotionTick); + this.bind("UpdateFrame", this._angularMotionTick); }, remove: function(destroyed) { - this.unbind("EnterFrame", this._angularMotionTick); + this.unbind("UpdateFrame", this._angularMotionTick); }, /**@ @@ -17325,7 +18685,6 @@ Crafty.c("AngularMotion", { * @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 * @@ -17456,13 +18815,12 @@ Crafty.c("Motion", { __motionProp(this, "d", "y", false); this._motionDelta = __motionVector(this, "d", false, new Crafty.math.Vector2D()); - this.__movedEvent = {axis: '', oldValue: 0}; this.__oldDirection = {x: 0, y: 0}; - this.bind("EnterFrame", this._linearMotionTick); + this.bind("UpdateFrame", this._linearMotionTick); }, remove: function(destroyed) { - this.unbind("EnterFrame", this._linearMotionTick); + this.unbind("UpdateFrame", this._linearMotionTick); }, /**@ @@ -17604,12 +18962,12 @@ Crafty.c("Motion", { */ _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; + var vx = this._vx, ax = this._ax, + vy = this._vy, ay = this._ay; // 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; + var dx = vx * dt + 0.5 * ax * dt * dt; + var dy = vy * dt + 0.5 * ay * dt * dt; // v += a * Δt this.vx = vx + ax * dt; this.vy = vy + ay * dt; @@ -17624,27 +18982,15 @@ Crafty.c("Motion", { 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); - } + this._dx = dx; + this._dy = dy; + + // Set the position using the optimized _setPosition method + this._setPosition(this._x + dx, this._y + dy); } }); -},{"../core/core.js":9}],48:[function(require,module,exports){ +},{"../core/core.js":10}],58:[function(require,module,exports){ var Crafty = require('../core/core.js'); /**@ @@ -17699,7 +19045,7 @@ Crafty.c("Supportable", { this.defineField("ground", function() { return this._ground; }, function(newValue) {}); }, remove: function(destroyed) { - this.unbind("EnterFrame", this._detectGroundTick); + this.unbind("UpdateFrame", this._detectGroundTick); }, /*@ @@ -17729,7 +19075,7 @@ Crafty.c("Supportable", { */ startGroundDetection: function(ground) { if (ground) this._groundComp = ground; - this.uniqueBind("EnterFrame", this._detectGroundTick); + this.uniqueBind("UpdateFrame", this._detectGroundTick); return this; }, @@ -17745,7 +19091,7 @@ Crafty.c("Supportable", { * Disable ground detection for this component. It can be reenabled by calling .startGroundDetection() */ stopGroundDetection: function() { - this.unbind("EnterFrame", this._detectGroundTick); + this.unbind("UpdateFrame", this._detectGroundTick); return this; }, @@ -17808,7 +19154,7 @@ Crafty.c("Supportable", { // 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), + results = Crafty.map.unfilteredSearch(area), i = 0, l = results.length; @@ -17886,6 +19232,9 @@ Crafty.c("GroundAttacher", { * * Additionally, this component provides the entity with `Supportable` and `Motion` methods & events. * + * Simulates jumping and falling when used with the `Twoway` component and is thus well suited for side-scrolling platformer type games. + * This component should not be used alongside `Fourway` or `Multiway`. + * * @see Supportable, Motion */ Crafty.c("Gravity", { @@ -17995,7 +19344,7 @@ Crafty.c("Gravity", { }); -},{"../core/core.js":9}],49:[function(require,module,exports){ +},{"../core/core.js":10}],59:[function(require,module,exports){ var Crafty = require('../core/core.js'); @@ -18053,15 +19402,13 @@ Crafty.extend({ * 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; + integerBounds: function(rect) { + // Truncate to next, lower integer, but don't modify if already integer + rect._x = Math.floor(rect._x); + rect._y = Math.floor(rect._y); + // Ceil to next, higher integer, but don't modify if already integer + rect._w = Math.ceil(rect._w); + rect._h = Math.ceil(rect._h); return rect; }, @@ -18081,19 +19428,14 @@ Crafty.extend({ * and so might be consecutive. */ mergeSet: function (set) { - var i = 0; - while (i < set.length - 1) { + if (set.length < 2) return set; + + var i = set.length - 1; + while (i--) { // If current and next overlap, merge them together into the first, removing the second - // Then skip the index backwards to compare the previous pair. - // Otherwise skip forward if (this.overlap(set[i], set[i + 1])) { this.merge(set[i], set[i + 1], set[i]); set.splice(i + 1, 1); - if (i > 0) { - i--; - } - } else { - i++; } } @@ -18185,637 +19527,686 @@ Crafty.extend({ }); -},{"../core/core.js":9}],50:[function(require,module,exports){ +},{"../core/core.js":10}],60:[function(require,module,exports){ /** * Spatial HashMap for broad phase collision * * @author Louis Stowasser */ - /**@ - * #Crafty.HashMap.constructor - * @comp Crafty.HashMap - * @kind Class - * - * @sign public void Crafty.HashMap([cellsize]) - * @param cellsize - the cell size. If omitted, `cellsize` is 64. - * - * Set `cellsize`. - * And create `this.map`. - */ - var cellsize, - HashMap = function (cell) { - cellsize = cell || 64; - this.map = {}; +/**@ + * #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) + * - [Broad-Phase Collision Detection with CUDA](http://http.developer.nvidia.com/GPUGems3/gpugems3_ch32.html) + * @see Crafty.map + */ +var cellsize, // TODO: make cellsize an instance property, so multiple maps with different cellsizes can be created + SPACE = " ", + keyHolder = {}; // temp object for reuse - 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 - } - }; - }, +/**@ + * #Crafty.HashMap.constructor + * @comp Crafty.HashMap + * @kind Class + * + * @sign public void Crafty.HashMap([cellsize]) + * @param cellsize - the cell size. If omitted, `cellsize` is 64. + * + * Set `cellsize`. + * And create `this.map`. + */ +var HashMap = function (cell) { + cellsize = cell || 64; + this.map = {}; + + this.boundsDirty = false; + this.coordBoundsDirty = false; + this.boundsHash = { + maxX: -Infinity, + maxY: -Infinity, + minX: Infinity, + minY: Infinity + }; + this.boundsCoords = { + maxX: -Infinity, + maxY: -Infinity, + minX: Infinity, + minY: Infinity + }; +}; - SPACE = " ", - keyHolder = {}; +/**@ + * #Crafty.HashMap.key + * @comp Crafty.HashMap + * @kind Method + * + * @sign public Object Crafty.HashMap.key(Object obj[, Object keys]) + * @param obj - an Object that has .cbr(), .mbr() or _x, _y, _w and _h. + * @param [keys] - optional object to reuse for the saving result in + * @returns an object describing the region in grid cells the given object is located in - { x1: start col index, y1: start row index, x2: end col index, y2: end row index} + * + * Get the rectangular region (in terms of the grid, with grid size `cellsize`), where the object may fall in. This region is determined by the object's bounding box. + * The `cellsize` is 64 by default. + * + * @see Crafty.HashMap.constructor + */ +HashMap.key = function (obj, keys) { + obj = obj._cbr || obj._mbr || obj; + keys = keys || {}; + + keys.x1 = Math.floor(obj._x / cellsize); + keys.y1 = Math.floor(obj._y / cellsize); + keys.x2 = Math.floor((obj._w + obj._x) / cellsize); + keys.y2 = Math.floor((obj._h + obj._y) / cellsize); + return keys; +}; - HashMap.prototype = { - /**@ - * #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 - * - * `obj` is inserted in '.map' of the corresponding broad phase cells. An object of the following fields is returned. - * ~~~ - * { - * keys: the object that keep track of cells - * obj: The inserted object - * map: the HashMap object - * } - * ~~~ - */ - insert: function (obj) { - var keys = HashMap.key(obj), - entry = new Entry(keys, obj, this), - i = 0, - j, - hash; - - //insert into all x buckets - for (i = keys.x1; i <= keys.x2; i++) { - //insert into all y buckets - for (j = keys.y1; j <= keys.y2; j++) { - hash = (i << 16) ^ j; - if (!this.map[hash]) this.map[hash] = []; - this.map[hash].push(obj); - } +HashMap.hash = function (keys) { + return keys.x1 + SPACE + keys.y1 + SPACE + keys.x2 + SPACE + keys.y2; +}; + +// TODO make this an instance property getter +HashMap.cellsize = function() { + return cellsize; +}; + +/**@ + * #Crafty.map + * @category 2D + * @kind CoreObject + * + * Functions related with querying entities. + * @see Crafty.HashMap + */ +HashMap.prototype = { + /**@ + * #Crafty.map.insert + * @comp Crafty.map + * @kind Method + * + * @sign public Object Crafty.map.insert(Object obj[, Object entry]) + * @param obj - An entity to be inserted. + * @param entry - An existing entry object to reuse. (Optional) + * @returns An object representing this object's entry in the HashMap + * + * `obj` is inserted in '.map' of the corresponding broad phase cells. An object of the following fields is returned. + * ~~~ + * { + * keys: the object that keep track of cells + * obj: The inserted object + * map: the HashMap object + * } + * ~~~ + */ + insert: function (obj, entry) { + var i, j, hash; + var keys = HashMap.key(obj, entry && entry.keys); + entry = entry || new Entry(keys, obj, this); + + //insert into all x buckets + for (i = keys.x1; i <= keys.x2; i++) { + //insert into all y buckets + for (j = keys.y1; j <= keys.y2; j++) { + hash = (i << 16) ^ j; + if (!this.map[hash]) this.map[hash] = []; + this.map[hash].push(obj); } + } - //mark map boundaries as dirty - this.boundsDirty = true; + //mark map boundaries as dirty + this.boundsDirty = true; - return entry; - }, + 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. - * 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. - * - * @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()); - * } - * ~~~ - */ + /**@ + * #Crafty.map.search + * @comp Crafty.map + * @kind Method + * + * @sign public Array Crafty.map.search(Object rect[, Array results]) + * @param rect - the rectangular region to search for entities. + * This object must contain the properties `_x`,`_y`,`_w`,`_h`. + * @param results - If passed, entities found will be appended to this array. + * @return a (possibly empty) array of entities that have been found in the given region + * + * Do a search for entities in the given region. Returned entities are guaranteed + * to overlap with the given region. + * + * 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()); + * } + * ~~~ + */ + _searchHolder: [], + search: function (rect, results) { + var keys = HashMap.key(rect, keyHolder), + i, j, k, cell, + previouslyChecked = this._searchHolder; + results = results || []; + previouslyChecked.length = 0; + + var obj; + //search in all x buckets + for (i = keys.x1; i <= keys.x2; i++) { + //insert into all y buckets + for (j = keys.y1; j <= keys.y2; j++) { + if ((cell = this.map[(i << 16) ^ j])) { + for (k = 0; k < cell.length; k++) { + if (previouslyChecked[cell[k][0]]) continue; + obj = previouslyChecked[cell[k][0]] = cell[k]; + obj = obj._cbr || obj._mbr || obj; + if (obj._x < rect._x + rect._w && obj._x + obj._w > rect._x && + obj._y < rect._y + rect._h && obj._y + obj._h > rect._y) { + results.push(cell[k]); + } + } + } + } + } - search: function (rect, filter) { - var keys = HashMap.key(rect, keyHolder), - i, j, k, l, cell, - results = []; + return results; + }, - if (filter === undefined) filter = true; //default filter to true + /**@ + * #Crafty.map.unfilteredSearch + * @comp Crafty.map + * @kind Method + * + * @sign public Array Crafty.map.search(Object rect[, Array results]) + * @param rect - the rectangular region to search for entities. + * This object must contain the properties `_x`,`_y`,`_w`,`_h`. + * @param results - If passed, entities found will be appended to this array. + * @return a (possibly empty) array of entities that have been found in the given region + * + * Do a search for entities in the given region. Returned entities are **not** guaranteed + * to overlap with the given region, and the results may contain duplicates. + * + * This method is intended to be used as the first step of a more complex search. + * More common use cases should use Crafty.map.search, which filters the results. + * + * @see Crafty.map.search + */ + unfilteredSearch: function(rect, results) { + var keys = HashMap.key(rect, keyHolder), + i, j, k, cell; + results = results || []; - //search in all x buckets - for (i = keys.x1; i <= keys.x2; i++) { - //insert into all y buckets - for (j = keys.y1; j <= keys.y2; j++) { - if ((cell = this.map[(i << 16) ^ j])) { - for (k = 0; k < cell.length; k++) - results.push(cell[k]); + //search in all x buckets + for (i = keys.x1; i <= keys.x2; i++) { + //insert into all y buckets + for (j = keys.y1; j <= keys.y2; j++) { + if ((cell = this.map[(i << 16) ^ j])) { + for (k = 0; k < cell.length; k++) { + results.push(cell[k]); } } } + } + + return results; + }, - if (filter) { - var obj, id, finalresult = [], - found = {}; - //add unique elements to lookup table with the entity ID as unique key - for (i = 0, l = results.length; i < l; i++) { - obj = results[i]; - if (!obj) continue; //skip if deleted - id = obj[0]; //unique ID - 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) - found[id] = results[i]; + /**@ + * #Crafty.map.remove + * @comp Crafty.map + * @kind Method + * + * @sign public void Crafty.map.remove(Entry entry) + * @param entry - An entry to remove from the hashmap + * + * Remove an entry from the broad phase map. + * + * @example + * ~~~ + * Crafty.map.remove(e); + * ~~~ + */ + remove: function (entry) { + var keys = entry.keys; + var obj = entry.obj; + var i = 0, + j, hash; + + //search in all x buckets + for (i = keys.x1; i <= keys.x2; i++) { + //insert into all y buckets + for (j = keys.y1; j <= keys.y2; j++) { + hash = (i << 16) ^ j; + + if (this.map[hash]) { + var cell = this.map[hash], + m, n = cell.length; + //loop over objs in cell and delete + for (m = 0; m < n; m++) + if (cell[m] && cell[m][0] === obj[0]) + cell.splice(m, 1); } + } + } - //loop over lookup table and copy to final array - for (obj in found) finalresult.push(found[obj]); + //mark map boundaries as dirty + this.boundsDirty = true; + }, - return finalresult; - } else { - return results; - } - }, + /**@ + * #Crafty.map.refresh + * @comp Crafty.map + * @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. + * + * @example + * ~~~ + * Crafty.map.refresh(e); + * ~~~ + */ + refresh: function (entry) { + var keys = entry.keys; + var obj = entry.obj; + var cell, i, j, m, n; - /**@ - * #Crafty.map.remove - * @comp Crafty.map - * @kind Method - * - * @sign public void Crafty.map.remove(Entry entry) - * @param entry - An entry to remove from the hashmap - * - * Remove an entry from the broad phase map. - * - * @example - * ~~~ - * Crafty.map.remove(e); - * ~~~ - */ - remove: function (entry) { - var keys = entry.keys; - var obj = entry.obj; - var i = 0, - j, hash; - - //search in all x buckets - for (i = keys.x1; i <= keys.x2; i++) { - //insert into all y buckets - for (j = keys.y1; j <= keys.y2; j++) { - hash = (i << 16) ^ j; - - if (this.map[hash]) { - var cell = this.map[hash], - m, n = cell.length; - //loop over objs in cell and delete - for (m = 0; m < n; m++) - if (cell[m] && cell[m][0] === obj[0]) - cell.splice(m, 1); - } + //First delete current object from appropriate cells + for (i = keys.x1; i <= keys.x2; i++) { + for (j = keys.y1; j <= keys.y2; j++) { + cell = this.map[(i << 16) ^ j]; + if (cell) { + n = cell.length; + //loop over objs in cell and delete + for (m = 0; m < n; m++) + if (cell[m] && cell[m][0] === obj[0]) + cell.splice(m, 1); } } + } - //mark map boundaries as dirty - this.boundsDirty = true; - }, + //update keys + HashMap.key(obj, keys); - /**@ - * #Crafty.map.refresh - * @comp Crafty.map - * @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. - * - * @example - * ~~~ - * Crafty.map.refresh(e); - * ~~~ - */ - refresh: function (entry) { - var keys = entry.keys; - var obj = entry.obj; - var cell, i, j, m, n; - - //First delete current object from appropriate cells - for (i = keys.x1; i <= keys.x2; i++) { - for (j = keys.y1; j <= keys.y2; j++) { - cell = this.map[(i << 16) ^ j]; - if (cell) { - n = cell.length; - //loop over objs in cell and delete - for (m = 0; m < n; m++) - if (cell[m] && cell[m][0] === obj[0]) - cell.splice(m, 1); - } - } + //insert into all rows and columns + for (i = keys.x1; i <= keys.x2; i++) { + for (j = keys.y1; j <= keys.y2; j++) { + cell = this.map[(i << 16) ^ j]; + if (!cell) cell = this.map[(i << 16) ^ j] = []; + cell.push(obj); } + } - //update keys - HashMap.key(obj, keys); + //mark map boundaries as dirty + this.boundsDirty = true; - //insert into all rows and columns - for (i = keys.x1; i <= keys.x2; i++) { - for (j = keys.y1; j <= keys.y2; j++) { - cell = this.map[(i << 16) ^ j]; - if (!cell) cell = this.map[(i << 16) ^ j] = []; - cell.push(obj); - } + 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 + * + * Return a copy of the minimum bounding rectangle encompassing all entities. + * + * ~~~ + * { + * min: { + * x: val_x, + * y: val_y + * }, + * max: { + * x: val_x, + * y: val_y + * } + * } + * ~~~ + */ + boundaries: function() { + // TODO: flatten output, likewise do it for Crafty.viewport.bounds + // TODO: accept optional parameter to save result in + this._updateBoundaries(); + var bounds = this.boundsCoords; + return { + min: { + x: bounds.minX, + y: bounds.minY + }, + max: { + x: bounds.maxX, + y: bounds.maxY } + }; + }, - //mark map boundaries as dirty - this.boundsDirty = true; + /** + * #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. + * + * ~~~ + * { + * minX: val_x, + * minY: val_y, + * maxX: val_x, + * maxY: val_y + * } + * ~~~ + */ + _keyBoundaries: function() { + this._updateBoundaries(); + return this.boundsHash; + }, - return entry; - }, + _updateBoundaries: function() { + // update map boundaries if they were changed + if (!this.boundsDirty && !this.coordBoundsDirty) return; - /**@ - * #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: { - * x: val_x, - * y: val_y - * }, - * max: { - * x: val_x, - * y: val_y - * } - * } - * ~~~ - */ - boundaries: function() { - this._updateBoundaries(); - return this.boundsCoords; - }, + var hash = this.boundsHash; + // Optimization: if no entities have moved cells, + // we don't need to recalculate the hash boundaries + if (this.boundsDirty) { + hash.maxX = -Infinity; + hash.maxY = -Infinity; + hash.minX = Infinity; + hash.minY = Infinity; + } - /** - * #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; - }, + var coords = this.boundsCoords; + coords.maxX = -Infinity; + coords.maxY = -Infinity; + coords.minX = Infinity; + coords.minY = Infinity; - _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; - - //broad phase coordinate - var i = h >> 16, - j = (h << 16) >> 16; - if (j < 0) { - i = i ^ -1; - } - if (i >= hash.max.x) { - hash.max.x = i; - 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) { - coords.max.x = Math.max(coords.max.x, ent.x + ent.w); - } + var k, ent, cell; + //Using broad phase hash to speed up the computation of boundaries. + for (var h in this.map) { + cell = this.map[h]; + if (!cell.length) continue; + + //broad phase coordinate + var i = h >> 16, + j = (h << 16) >> 16; + if (j < 0) { + i = ~i; // i ^ -1 + } + + if (i >= hash.maxX) { + hash.maxX = i; + for (k in cell) { + ent = cell[k]; + //TODO: remove these checks introduced by 25e7c88f61f64525adc32f7fd776099413cb1567? + //make sure that this is a Crafty entity + if (typeof ent === 'object' && 'requires' in ent) { + coords.maxX = Math.max(coords.maxX, ent.x + ent.w); } } - if (i <= hash.min.x) { - hash.min.x = i; - for (k in this.map[h]) { - ent = this.map[h][k]; - if (typeof ent === 'object' && 'requires' in ent) { - coords.min.x = Math.min(coords.min.x, ent.x); - } + } + if (i <= hash.minX) { + hash.minX = i; + for (k in cell) { + ent = cell[k]; + if (typeof ent === 'object' && 'requires' in ent) { + coords.minX = Math.min(coords.minX, ent.x); } } - if (j >= hash.max.y) { - hash.max.y = j; - for (k in this.map[h]) { - ent = this.map[h][k]; - if (typeof ent === 'object' && 'requires' in ent) { - coords.max.y = Math.max(coords.max.y, ent.y + ent.h); - } + } + if (j >= hash.maxY) { + hash.maxY = j; + for (k in cell) { + ent = cell[k]; + if (typeof ent === 'object' && 'requires' in ent) { + coords.maxY = Math.max(coords.maxY, ent.y + ent.h); } } - if (j <= hash.min.y) { - hash.min.y = j; - for (k in this.map[h]) { - ent = this.map[h][k]; - if (typeof ent === 'object' && 'requires' in ent) { - coords.min.y = Math.min(coords.min.y, ent.y); - } + } + if (j <= hash.minY) { + hash.minY = j; + for (k in cell) { + ent = cell[k]; + if (typeof ent === 'object' && 'requires' in ent) { + coords.minY = Math.min(coords.minY, ent.y); } } } + } - // mark map boundaries as clean - this.boundsDirty = false; - }, - + // mark map boundaries as clean + this.boundsDirty = false; + this.coordBoundsDirty = 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; - * }); - * ~~~ - */ + /**@ + * #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 - }; + // 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); + 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); + // calculate col & row cell indices + var currentCol = keys.x1, + currentRow = keys.y1; + var minCol = keyBounds.minX, + minRow = keyBounds.minY, + maxCol = keyBounds.maxX, + maxRow = keyBounds.maxY; + // 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; + // 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; + // 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 + // 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; - } + 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 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; + // advance to closest cell + if (nextDistanceX < nextDistanceY) { + previousDistance = nextDistanceX; - currentCol += stepCol; - nextDistanceX += deltaDistanceX; - } else { - previousDistance = nextDistanceY; + currentCol += stepCol; + nextDistanceX += deltaDistanceX; + } else { + previousDistance = nextDistanceY; - currentRow += stepRow; - nextDistanceY += deltaDistanceY; - } + 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; + var cell; + // traverse over cells + // TODO: maybe change condition to `while (currentCol !== endX) || (currentRow !== endY)` + while ((minCol <= currentCol && currentCol <= maxCol) && + (minRow <= currentRow && currentRow <= maxRow)) { - currentRow += stepRow; - nextDistanceY += deltaDistanceY; + // 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; } } - } - - }; - /**@ - * #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) - * - [Broad-Phase Collision Detection with CUDA](http://http.developer.nvidia.com/GPUGems3/gpugems3_ch32.html) - * @see Crafty.map - */ + // advance to closest cell + if (nextDistanceX < nextDistanceY) { + previousDistance = nextDistanceX; - /**@ - * #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. - * - * Get the rectangular region (in terms of the grid, with grid size `cellsize`), where the object may fall in. This region is determined by the object's bounding box. - * The `cellsize` is 64 by default. - * - * @see Crafty.HashMap.constructor - */ - HashMap.key = function (obj, keys) { - obj = obj._cbr || obj._mbr || obj; - keys = keys || {}; + currentCol += stepCol; + nextDistanceX += deltaDistanceX; + } else { + previousDistance = nextDistanceY; - keys.x1 = Math.floor(obj._x / cellsize); - keys.y1 = Math.floor(obj._y / cellsize); - keys.x2 = Math.floor((obj._w + obj._x) / cellsize); - keys.y2 = Math.floor((obj._h + obj._y) / cellsize); - return keys; - }; + currentRow += stepRow; + nextDistanceY += deltaDistanceY; + } + } + } - HashMap.hash = function (keys) { - return keys.x1 + SPACE + keys.y1 + SPACE + keys.x2 + SPACE + keys.y2; - }; +}; - function Entry(keys, obj, map) { - this.keys = keys; - this.map = map; - this.obj = obj; - } +function Entry(keys, obj, map) { + this.keys = keys; + this.map = map; + this.obj = obj; +} - Entry.prototype = { - update: function (rect) { - //check if buckets change - if (HashMap.hash(HashMap.key(rect, keyHolder)) !== HashMap.hash(this.keys)) { - this.map.refresh(this); - } +Entry.prototype = { + update: function (rect) { + //check if buckets change + if (HashMap.hash(HashMap.key(rect, keyHolder)) !== HashMap.hash(this.keys)) { + this.map.refresh(this); + } else { + //mark map coordinate boundaries as dirty + this.map.coordBoundsDirty = true; } - }; + + } +}; - module.exports = HashMap; +module.exports = HashMap; -},{}]},{},[19]); +},{}]},{},[21]); diff --git a/package.json b/package.json index 52c8d82e..d1b7a346 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "craftyjs", - "version": "0.9.0-rc1", + "version": "0.9.0-rc2", "description": "Crafty is a modern component and event based framework for developing games in JavaScript that targets DOM, Canvas and WebGL.", "keywords": [ "framework", diff --git a/src/core/version.js b/src/core/version.js index 69409851..1a6074af 100644 --- a/src/core/version.js +++ b/src/core/version.js @@ -1 +1 @@ -module.exports = "0.9.0-rc1"; \ No newline at end of file +module.exports = "0.9.0-rc2"; \ No newline at end of file