├── .gitattributes ├── .gitignore ├── README.md ├── flow-field.jpg ├── img ├── beetle.png ├── grid_bg.png ├── target.png └── tilesprite.png ├── index.html └── scripts ├── Main.js ├── components ├── BoundingCircle.js ├── VectorFieldState.js ├── behaviors │ └── Flock.js └── input │ ├── KeyboardController.js │ └── MouseController.js ├── engine ├── CollisionGrid.js ├── ComponentType.js ├── DebugDraw.js ├── FlowGrid.js ├── FlowGridNode.js ├── Kai.js └── TileMap.js ├── entities ├── Block.js └── Thing.js ├── lib ├── Signal.js ├── dat.gui.min.js ├── linkedlist.js ├── pixi.js └── require.js └── math ├── Point3.js └── Vec2.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build and Release Folders 2 | bin/ 3 | bin-debug/ 4 | bin-release/ 5 | 6 | # Other files and folders 7 | .settings/ 8 | 9 | flowfield.* 10 | 11 | *.psd -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![screenshot](flow-field.jpg) 2 | 3 | [Demo.](http://vonwolfehaus.github.io/flow-field/) 4 | 5 | Based on [this TutsPlus tutorial](http://gamedev.tutsplus.com/tutorials/implementation/goal-based-vector-field-pathfinding/). 6 | 7 | ## Overview 8 | 9 | Sometimes called flow fields, vector fields, wavefront expansion, brushfire, and so on. The idea is to use Dijkstra's algorithm to fill out a grid, starting from a single cell, with the distance from current cell to that original cell. On each cell we calculate a vector that points in the direction of the goal. As entities roll over a cell, we simply apply that vector to the entity's velocity. 10 | 11 | This makes pathfinding very large numbers of objects very efficient. However, there are some problems such as local optima. I hoped to solve local optima with flocking--if one entity gets stuck, it will come out of it by following its neighbors out (who did not get stuck). 12 | 13 | It didn't work as well as I hoped. I didn't spend enough time placing proper weights on the flocking rules, but just using collision resolution resulted in smoother pathing. Anyway, that's what experiments are for. 14 | 15 | # Details 16 | 17 | After playing [Planetary Annihilation](http://www.uberent.com/pa/) and witnessing how well [their flow fields](http://youtu.be/5Qyl7h7D1Q8?t=24m30s) worked for pathfinding tons of units at once while maintaining excellent performance, I was interested in trying it myself–unit movement in RTS games is a huge problem field with a lot of solutions. So I found [this article](http://gamedev.tutsplus.com/tutorials/implementation/goal-based-vector-field-pathfinding/) on the subject and proceeded to hack it out in JavaScript. 18 | 19 | ## Anatomy of a flow field 20 | 21 | The idea is to use Dijkstra’s algorithm to fill out a grid, starting from a single cell (the goal), with the distance from current cell to that goal cell. On each cell we calculate a vector (direction) derived from the distance value. As entities roll over a cell, we simply apply that vector to the entity’s velocity. That’s all there is to it! 22 | 23 | How it actually plays out is fascinating. By simply subtracting the weight of neighboring cells with each other (`left - right`, `top - bottom`), you get an angle that will point around obstacles if they exist nearby, as well as point directly at the goal itself if it’s in plain sight (see this post’s header image for a visual). So calculation is extremely fast, and it works really well! 24 | 25 | This makes pathfinding very large numbers of objects very efficient. Another huge bonus is that it makes pathfinding around dynamic objects efficient and easy. With traditional pathfinding like A*, you’d have to recalculate the path for dynamic terrain, but you’d also have to do special modifications to the steering behaviors to avoid dynamic objects. With a flow field, you simply mark the cells occupied by these dynamic obstacles with negative values–a much cheaper operation, certainly. 26 | 27 | However, there are some problems too. I hoped to solve the local optima problem in particular with flocking–if one entity gets stuck, it will come out of it by following its neighbors out (who did not get stuck since they were not occupying the same cell due to collision detection). That was the theory anyway… 28 | 29 | #### Performance concerns 30 | 31 | The trade-off is that the vector grid takes up a ton of memory. This is ok for desktop and laptop computers, but not for anything smaller. In addition, the sheer number of calculations makes this technique somewhat troublesome. So, severe optimizations have to be made and while they’re not hard to do, it does take a bit of grunt work to pull off. 32 | 33 | For example, to help with memory you can re-use grid cells and only create them when absolutely necessary at run-time. If a path is no longer used, toss those cells into the “free” list to reuse for a different query. You can also use one data structure per cell, but keeping an array of vectors inside of it so that multiple fields can exist in the same space simultaneously without creating multiple instances of the grid cell class. 34 | 35 | For keeping the number of calculations low, you can further partition the grid so that you only calculate the cells that the entities will actually run into (as opposed to the whole map). But figuring out which cells to use like this requires a different pathfinding algorithm to be used, and ran on a different, lower-fidelity grid. Additionally, you can compact large fields that carry the same value into a single cell reference–as you can see in the image at top, there are a lot of duplicate vectors everywhere, screaming to be optimized. 36 | 37 | ## Flocking 38 | 39 | As you can see for yourself in the demo, flocking doesn’t make it any better. This is sad news, since flocking is a very powerful tool for keeping unit movement realistic and even emergent. I think it can be made to work much better if the algorithms are tweaked a bit (and weighted), but I’m sad that what I _did_ have wasn’t good enough to improve the solution–it actually made it a little worse. 40 | 41 | However, I believe the problem is not in the steering behavior itself as much as it is with my particular implementation. The flocking algorithm is fragile, yes, but I think the fact that I use radial collision for obstacles kind of cheats the system a bit–when one hits, it forces the entity into a certain direction, thus “solving” the indirection problem that local optima creates. This means that collision detection is all that is necessary, so the flocking only threw it off the flow. That, and I have no angular velocity limit which is a key part of steering behaviors, further screwing things up. This is called “lazy science”. 42 | 43 | ## Entity Component System 44 | 45 | This was a good time to try the component-based approach to game engine architecture. Popular engines like [Unity3D](http://unity3d.com/) make excellent use of it to great effect–Unity3D is arguably the most flexible engine on the market today, and an absolute pleasure to work with. _(My implementation is not complete, and quite shallow since I’m merely playing with the idea–I still loop through all entities directly, instead of organizing and looping through component lists–a “system”–like a proper component engine would do.)_ Components are a particularly good choice for HTML5 games, since it works to JavaScript’s strengths, such as its object-based foundation and its completely dynamic nature. 46 | 47 | #### Overview 48 | 49 | In entity component systems, composition is used instead of inheritance. This is all kinds of excellent. There is no monolithic base object (or any monolithic classes at all!), nor awkward hierarchies that don’t satisfy every design requirement. I highly recommend researching this architecture if you’re not already familiar with it. 50 | 51 | There is some weirdness when it comes to the details of how each aspect works with the others, and everyone does it differently apparently. Eventually I decided on a system where each component has a reference to its parent object, and it’s instantiated by passing in that parent as well as (optionally) any settings unique to that component. 52 | 53 | Communication between components is done directly by accessing another component through its parent. In order to avoid errors, the component will attach any prerequisite components to its parent if it doesn’t already exist (on a per-component basis, in its init function). This results in fast execution since there’s no unnecessary layers of abstraction that the CPU would have to work through otherwise. 54 | 55 | #### Further details 56 | 57 | Often times in games you have entities that require special behaviors for special situations. To accommodate this, the entity can listen to a component’s [Signals](https://github.com/millermedeiros/js-signals), if any exist. For example, a Health component will dispatch a signal when its amount goes below 0, or another when it reaches its max amount again. The same goes for collider components: a signal is dispatched when a collision occurs (by the collision broadphase system), passing in the object it collided with and the manifold of the collision as parameters of the callback. The entity can listen for these signals, or its other components can as well. 58 | 59 | After I decided on this, the whole concept fell into place and made everything much more manageable, flexible, and even fun to build with. I will definitely going balls to the wall with component systems in future projects! 60 | 61 | ## Conclusion 62 | 63 | **Ultimately**, I think going with steering behaviors and doing a single A* calculation for a leader of the pack gets you better movement behavior for a lot less memory and CPU time. **Conversely**, this method can be augmented with more interesting features such as _potential fields_ which would make the flow field much more useful… but again, only with what seems like an unnecessary amount of work, and still at a loss of performance if not refactored to hell and back. There are alternative techniques that I think are more efficient but likely just as effective (if not more so), such as _nav mesh_. 64 | 65 | Keep in mind that I did not dig into this experiment too deep, as I was merely curious about the broader implications and other implementation details (_eg_ the component system). Honestly, I think my code is a poor indicator of how a production-quality system would perform, both in terms of CPU as well as the AI’s aesthetic aspect. 66 | 67 | With that said, the code is simple enough, so please have a look and play around with it yourself. If you found a superior solution, feel free to submit a pull request–I’d really appreciate it! I want this solution to work and interested in learning more about it, but I need to move on to other projects. Thanks! 68 | -------------------------------------------------------------------------------- /flow-field.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vonWolfehaus/flow-field/e3d367b45c5ffef3a3d78b600f5275bf7e7acff5/flow-field.jpg -------------------------------------------------------------------------------- /img/beetle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vonWolfehaus/flow-field/e3d367b45c5ffef3a3d78b600f5275bf7e7acff5/img/beetle.png -------------------------------------------------------------------------------- /img/grid_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vonWolfehaus/flow-field/e3d367b45c5ffef3a3d78b600f5275bf7e7acff5/img/grid_bg.png -------------------------------------------------------------------------------- /img/target.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vonWolfehaus/flow-field/e3d367b45c5ffef3a3d78b600f5275bf7e7acff5/img/target.png -------------------------------------------------------------------------------- /img/tilesprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vonWolfehaus/flow-field/e3d367b45c5ffef3a3d78b600f5275bf7e7acff5/img/tilesprite.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | Shift+click to draw/erase obstacles
48 | Source code on GitHub 49 |
50 | 51 | 52 | 53 | 63 | 64 | -------------------------------------------------------------------------------- /scripts/Main.js: -------------------------------------------------------------------------------- 1 | define(['engine/Kai', 'engine/ComponentType', 'engine/CollisionGrid', 'engine/FlowGrid', 'entities/Thing', 'engine/TileMap'], function(Kai, ComponentType, CollisionGrid, FlowGrid, Thing, TileMap) { 2 | 3 | return function Main() { 4 | 5 | var gui = new dat.GUI(); 6 | var grid = new CollisionGrid(200); 7 | var map = new TileMap(50, document.getElementById('tilesprite')); 8 | var flow = new FlowGrid(50, window.innerWidth, window.innerHeight); 9 | var allTheThings = []; 10 | var target = null; 11 | var paint = 0; 12 | // var timer = 120; // DEBUG 13 | 14 | var dt, last, now; 15 | 16 | function update() { 17 | var i, tile, pos = Kai.mouse.position; 18 | 19 | now = window.performance.now(); 20 | dt = now - last; 21 | last = now; 22 | Kai.elapsed = dt * 0.0001; 23 | Kai.debugCtx.clearRect(0, 0, window.innerWidth, window.innerHeight); 24 | 25 | if (Kai.keys.shift && Kai.mouse.down) { 26 | tile = map.getTile(pos.x, pos.y); 27 | 28 | if (paint !== tile) { 29 | newTile = map.setTile(pos.x, pos.y, paint); 30 | flow.setBlockAt(pos.x, pos.y); 31 | } 32 | } 33 | 34 | for (i = 0; i < allTheThings.length; i++) { 35 | allTheThings[i].update(); 36 | } 37 | 38 | if (Kai.guiOptions.collision) grid.update(); 39 | 40 | Kai.renderer.render(Kai.stage); 41 | 42 | if (Kai.guiOptions.drawVectors) flow.draw(Kai.debugCtx); 43 | // grid.draw(Kai.debugCtx); 44 | 45 | // if (timer--) { 46 | requestAnimFrame(update); 47 | // } 48 | } 49 | 50 | function onKeyDown(key) { 51 | if (key === 32) { 52 | // flow.build(); 53 | // flow.log(); 54 | // grid.log(); 55 | // allTheThings[0].flocker.log(); 56 | } 57 | } 58 | 59 | function onMouseDown(pos) { 60 | var i, on, 61 | x = ~~(pos.x/flow.cellPixelSize) * flow.cellPixelSize, 62 | y = ~~(pos.y/flow.cellPixelSize) * flow.cellPixelSize; 63 | 64 | if (Kai.mouse.shift) { 65 | paint = !!map.getTile(pos.x, pos.y) ? 0 : 1; 66 | 67 | } else { 68 | on = flow.setGoal(pos.x, pos.y); 69 | if (on) { 70 | flow.build(); 71 | 72 | target.position.x = x; 73 | target.position.y = y; 74 | target.visible = true; 75 | 76 | for (i = 0; i < allTheThings.length; i++) { 77 | allTheThings[i].vecFieldState.reachedGoal = false; 78 | } 79 | } 80 | } 81 | } 82 | 83 | 84 | init(); 85 | function init() { 86 | var debugCanvas = document.getElementById('debug'); 87 | debugCanvas.width = window.innerWidth; 88 | debugCanvas.height = window.innerHeight; 89 | Kai.debugCtx = debugCanvas.getContext('2d'); 90 | 91 | Kai.renderer = PIXI.autoDetectRenderer(window.innerWidth, window.innerHeight, null, true); 92 | document.body.appendChild(Kai.renderer.view); 93 | // document.body.insertBefore(Kai.renderer.view, debugCanvas); 94 | 95 | Kai.stage = new PIXI.Stage(); 96 | 97 | var texture = PIXI.Texture.fromImage('img/target.png'); 98 | target = new PIXI.Sprite(texture); 99 | target.visible = false; 100 | Kai.stage.addChild(target); 101 | 102 | var i, x = 0, y = 0, 103 | amount = 20, size = 50, 104 | g = ~~(amount / 4); 105 | 106 | for (i = 0; i < amount; i++) { 107 | allTheThings.push(new Thing(x*size+100, y*size+50)); 108 | if (++x === g) { 109 | x = 0; 110 | y++; 111 | } 112 | } 113 | 114 | var datgui = document.getElementsByClassName('dg')[0]; 115 | datgui.addEventListener('mousedown', function(evt) { 116 | evt.stopPropagation(); // don't let clicks on the gui trigger anything else, it's annoying 117 | }, false); 118 | 119 | gui.width = 200; 120 | gui.add(Kai.guiOptions, 'drawVectors'); 121 | gui.add(Kai.guiOptions, 'flocking'); 122 | gui.add(Kai.guiOptions, 'collision'); 123 | gui.add(Kai.guiOptions, 'clearObstacles'); 124 | 125 | Kai.mouse.onDown.add(onMouseDown); 126 | Kai.keys.onDown.add(onKeyDown); 127 | 128 | console.log('[Main] Running'); 129 | 130 | last = window.performance.now(); 131 | update(); 132 | } 133 | 134 | } // class 135 | }); -------------------------------------------------------------------------------- /scripts/components/BoundingCircle.js: -------------------------------------------------------------------------------- 1 | define(['engine/Kai'], function(Kai) { 2 | 3 | return function BoundingCircle(entity, settings) { 4 | // public shared components 5 | this.position = null; 6 | this.velocity = null; 7 | 8 | // public members unique to this component 9 | this.uniqueId = Date.now() + '' + Math.floor(Math.random()*1000); 10 | 11 | // components with the same collisionId will not be checked against each other 12 | // this is for composite entities and flocks 13 | this.collisionId = this.uniqueId; 14 | this.center = new Vec2(); 15 | this.radius = -1; 16 | this.solid = true; 17 | this.restitution = 0.4; 18 | 19 | this.mass = 1; 20 | this.invmass = 0; 21 | this.bounce = 0; 22 | 23 | this.collisionSignal = new Signal(); 24 | 25 | // internal 26 | var _self = this, 27 | _tau = Math.PI * 2; 28 | 29 | this.setMass = function(newMass) { 30 | this.mass = newMass; 31 | if (newMass <= 0) { 32 | this.invmass = 0; 33 | } else { 34 | this.invmass = 1/newMass; 35 | } 36 | }; 37 | 38 | // nothing uses center so there's no need for this 39 | /*this.update = function() { 40 | this.center.x = this.position.x + this.radius; 41 | this.center.y = this.position.y + this.radius; 42 | };*/ 43 | 44 | this.draw = function(ctx) { 45 | ctx.lineWidth = 1; 46 | ctx.strokeStyle = 'rgb(200, 10, 10)'; 47 | ctx.beginPath(); 48 | ctx.arc(this.center.x, this.center.y, this.radius, 0, _tau, true); 49 | ctx.stroke(); 50 | }; 51 | 52 | init(); 53 | function init() { 54 | var p, defaults = { 55 | mass: 50, 56 | radius: 25 57 | }; 58 | 59 | for (p in defaults) { 60 | if (!!settings && settings.hasOwnProperty(p)) { 61 | _self[p] = settings[p]; 62 | } else { 63 | _self[p] = defaults[p]; 64 | } 65 | } 66 | 67 | _self.position = entity.position; 68 | _self.velocity = entity.velocity; 69 | _self.setMass(_self.mass); 70 | } 71 | 72 | } // class 73 | 74 | }); 75 | -------------------------------------------------------------------------------- /scripts/components/VectorFieldState.js: -------------------------------------------------------------------------------- 1 | define(['engine/Kai', 'components/behaviors/Flock'], function(Kai, Flock) { 2 | 3 | return function VectorFieldState(entity) { 4 | 5 | // public members unique to this component 6 | this.uniqueId = Date.now() + '' + Math.floor(Math.random()*1000); 7 | 8 | this.fieldID = -1; // flow field index (as it sits in the array) 9 | this.reachedGoal = true; 10 | 11 | // shared references 12 | var _position = null, 13 | _vecField = Kai.flow; 14 | 15 | // internal settings 16 | var _self = this, 17 | // always best to use a timer to spread out the processing for expensive ops, especially when the user won't notice it 18 | _pollTime = 12, _timer = Math.ceil(Math.random() * _pollTime); 19 | 20 | 21 | this.update = function() { 22 | var node; 23 | _timer--; 24 | if (_timer < 0 && !this.reachedGoal) { 25 | _timer = _pollTime; 26 | 27 | if (_position.distanceTo(Kai.flow.goalPixels) < 100) { 28 | // console.log('reached goal'); 29 | this.reachedGoal = true; 30 | } else { 31 | node = entity.flock.nearby.first; 32 | while (node) { 33 | if (!node.obj.vecFieldState) { 34 | node = node.next; 35 | continue; 36 | } 37 | 38 | if (node.obj.vecFieldState.reachedGoal) { 39 | // console.log('neighbor reached goal'); 40 | this.reachedGoal = true; 41 | break; 42 | } 43 | node = node.next; 44 | } 45 | } 46 | } 47 | 48 | return _vecField.getVectorAt(_position, this.fieldID); 49 | }; 50 | 51 | this.destroy = function() { 52 | _vecField = null; 53 | _position = null; 54 | entity = null; 55 | }; 56 | 57 | init(); 58 | function init() { 59 | Kai.addComponent(entity, ComponentType.FLOCK, Flock); 60 | 61 | _position = entity.position; 62 | } 63 | } // class 64 | 65 | }); 66 | -------------------------------------------------------------------------------- /scripts/components/behaviors/Flock.js: -------------------------------------------------------------------------------- 1 | define(['engine/Kai'], function(Kai) { 2 | 3 | // http://files.arcticpaint.com/flock/ 4 | // fuck, can't do that rotation shit, that's not how I store it 5 | // try this instead: http://processingjs.org/learning/topic/flocking/ 6 | // http://lucasdup.in/js/bg.js 7 | return function Flock(entity, settings) { 8 | 9 | // public members unique to this component 10 | this.uniqueId = Date.now() + '' + Math.floor(Math.random()*1000); 11 | this.maxForce = 0; 12 | this.maxSpeed = 0; 13 | this.flockRadius = 0; 14 | this.flockId = 0; 15 | this.nearby = new LinkedList(); 16 | 17 | // shared references 18 | var _position = entity.position, 19 | _velocity = entity.velocity, 20 | _collisionGrid = Kai.grid; 21 | 22 | // internal settings 23 | var _self = this, _cachedLifetime = 4, // number of ticks until we refresh nearby 24 | _stepToCache = Math.ceil(Math.random() * _cachedLifetime), 25 | _avoidRadius2 = 0; 26 | 27 | // scratch objects 28 | var _separation = new Vec2(), 29 | _alignment = new Vec2(), 30 | _cohesion = new Vec2(), 31 | _accel = new Vec2(), 32 | _steer = new Vec2(), _scratch = new Vec2(); 33 | 34 | this.update = function() { 35 | var dx, dy, dist, node, obj, l, steer; 36 | 37 | _accel.x = _accel.y = 0; 38 | _separation.x = _separation.y = 0; 39 | _alignment.x = _alignment.y = 0; 40 | _cohesion.x = _cohesion.y = 0; 41 | 42 | if (--_stepToCache === 0) { 43 | _stepToCache = _cachedLifetime; 44 | _collisionGrid.getNeighbors(entity, this.flockRadius, this.nearby); 45 | } 46 | 47 | l = this.nearby.length; 48 | if (l > 0) { 49 | node = this.nearby.first; 50 | 51 | while (node) { 52 | obj = node.obj; 53 | if (!obj.flock || obj.flock.flockId !== this.flockId) { 54 | node = node.next; 55 | continue; 56 | } 57 | 58 | Vec2.draw(Kai.debugCtx, _position, obj.position, 'rgba(255, 255, 255, 0.6)'); // DEBUG 59 | 60 | dx = _position.x - obj.position.x; // this should be the center position 61 | dy = _position.y - obj.position.y; 62 | dist = Math.sqrt((dx * dx) + (dy * dy)); 63 | 64 | if (dist < this.separationRadius) { 65 | // rule 1 66 | _scratch.x = dx; 67 | _scratch.y = dy; 68 | _scratch.divideScalar(dist); 69 | _separation.add(_scratch); 70 | } else { 71 | // rule 2 72 | _alignment.add(obj.velocity); 73 | 74 | // rule 3 75 | _cohesion.add(obj.position); 76 | } 77 | 78 | node = node.next; 79 | } 80 | 81 | // i made a mistake somewhere so that this is needed, 82 | // otherwise they seek the top left corner. halp. 83 | l--; 84 | 85 | _separation.divideScalar(l); 86 | _separation.normalize(); 87 | 88 | _alignment.divideScalar(l); 89 | _alignment.normalize(); 90 | 91 | _cohesion.divideScalar(l); 92 | this.seek(_cohesion); 93 | } 94 | 95 | _accel.add(_separation); 96 | _accel.add(_alignment); 97 | 98 | return _accel; 99 | }; 100 | 101 | this.seek = function(target, slowdown) { 102 | var d; 103 | slowdown = !!slowdown; 104 | 105 | // DebugDraw.circle(target.x, target.y, 5); // DEBUG 106 | 107 | _scratch.x = target.x - _position.x; 108 | _scratch.y = target.y - _position.y; 109 | d = _scratch.getLength(); 110 | 111 | if (d > 0) { 112 | _scratch.normalize(); 113 | 114 | if (slowdown && d < this.slowingRadius) { 115 | _scratch.multiplyScalar(this.maxSpeed * (d / 100)); // arbitrary dampening 116 | } else { 117 | _scratch.multiplyScalar(this.maxSpeed); 118 | } 119 | 120 | _steer.x = _scratch.x - _velocity.x; 121 | _steer.y = _scratch.y - _velocity.y; 122 | _steer.truncate(this.maxForce); 123 | 124 | } else { 125 | return _accel; 126 | } 127 | 128 | return _accel.add(_steer); 129 | }; 130 | 131 | this.flee = function(target, slowdown) { 132 | var d; 133 | slowdown = !!slowdown; 134 | 135 | // DebugDraw.circle(target.x, target.y, 5); // DEBUG 136 | 137 | _scratch.x = _position.x - target.x; 138 | _scratch.y = _position.y - target.y; 139 | 140 | _steer.x = _scratch.x - _velocity.x; 141 | _steer.y = _scratch.y - _velocity.y; 142 | _steer.truncate(this.maxForce); 143 | 144 | return _accel.add(_steer); 145 | }; 146 | 147 | this.log = function() { 148 | console.log(_cohesion); 149 | }; 150 | 151 | 152 | init(); 153 | function init() { 154 | var p, defaults = { 155 | flockRadius: 100, 156 | flockId: 1, 157 | separationRadius: 50, 158 | maxForce: 1, 159 | maxSpeed: 10, 160 | slowingRadius: 100 161 | }; 162 | 163 | for (p in defaults) { 164 | if (!!settings && settings.hasOwnProperty(p)) { 165 | _self[p] = settings[p]; 166 | } else { 167 | _self[p] = defaults[p]; 168 | } 169 | } 170 | 171 | _avoidRadius2 = _self.flockRadius * _self.flockRadius; 172 | 173 | } 174 | 175 | } // class 176 | 177 | }); 178 | -------------------------------------------------------------------------------- /scripts/components/input/KeyboardController.js: -------------------------------------------------------------------------------- 1 | define(function() { 2 | 3 | return function KeyboardController() { 4 | 5 | this.key = -1; 6 | 7 | this.onDown = new Signal(); 8 | this.onUp = new Signal(); 9 | 10 | this.down = false; 11 | this.shift = false; 12 | this.ctrl = false; 13 | 14 | var _self = this, 15 | _downPrev = false; 16 | 17 | 18 | function onDown(evt) { 19 | _self.down = true; 20 | 21 | _self.shift = !!evt.shiftKey; 22 | _self.ctrl = !!evt.ctrlKey; 23 | _self.key = evt.keyCode; 24 | 25 | _self.onDown.dispatch(_self.key); 26 | } 27 | 28 | function onUp(evt) { 29 | _self.down = false; 30 | _self.key = -1; 31 | _self.shift = false; 32 | _self.ctrl = false; 33 | 34 | _self.onUp.dispatch(_self.key); 35 | } 36 | 37 | init(); 38 | function init() { 39 | document.addEventListener('keydown', onDown, false); 40 | document.addEventListener('keyup', onUp, false); 41 | } 42 | 43 | } // class 44 | 45 | }); -------------------------------------------------------------------------------- /scripts/components/input/MouseController.js: -------------------------------------------------------------------------------- 1 | define(function() { 2 | 3 | return function MouseController() { 4 | 5 | this.position = new Vec2(); 6 | 7 | this.onDown = new Signal(); 8 | this.onUp = new Signal(); 9 | 10 | this.down = false; 11 | this.shift = false; 12 | this.ctrl = false; 13 | 14 | var _self = this, 15 | _downPrev = false; 16 | 17 | function onDown(evt) { 18 | _self.position.x = evt.pageX; 19 | _self.position.y = evt.pageY; 20 | _self.down = true; 21 | 22 | _self.shift = !!evt.shiftKey; 23 | _self.ctrl = !!evt.ctrlKey; 24 | 25 | _self.onDown.dispatch(_self.position); 26 | } 27 | 28 | function onUp(evt) { 29 | _self.position.x = evt.pageX; 30 | _self.position.y = evt.pageY; 31 | _self.down = false; 32 | _self.onUp.dispatch(_self.position); 33 | } 34 | 35 | function onMove(evt) { 36 | _self.position.x = evt.pageX; 37 | _self.position.y = evt.pageY; 38 | } 39 | 40 | function onOut(evt) { 41 | _self.down = false; 42 | } 43 | 44 | init(); 45 | function init() { 46 | document.addEventListener('mousedown', onDown, false); 47 | document.addEventListener('mouseup', onUp, false); 48 | document.addEventListener('mouseout', onOut, false); 49 | document.addEventListener('mousemove', onMove, false); 50 | } 51 | 52 | } // class 53 | 54 | }); -------------------------------------------------------------------------------- /scripts/engine/CollisionGrid.js: -------------------------------------------------------------------------------- 1 | define(['engine/Kai', 'components/BoundingCircle'], function(Kai, BoundingCircle) { 2 | 3 | /** 4 | * 5 | */ 6 | return function CollisionGrid(cellSize) { 7 | 8 | this.cellPixelSize = cellSize; 9 | 10 | this.widthInCells = Math.floor(Kai.width / cellSize) + 1; 11 | this.heightInCells = Math.floor(Kai.height / cellSize) + 1; 12 | 13 | this.numCells = this.widthInCells * this.heightInCells; 14 | 15 | var _self = this, _nearbyList = new LinkedList(), 16 | _cells = [], _lengths = [], 17 | _itemList = new LinkedList(), // ALL THE THINGS 18 | _sizeMulti = 1 / this.cellPixelSize; 19 | 20 | // scratch objects 21 | var _normal = new Vec2(), 22 | _rv = new Vec2(), 23 | _impulse = new Vec2(), 24 | _mtd = new Vec2(), 25 | _difference = new Vec2(); 26 | 27 | // this is as naive a broadphase as you can get, so plenty of room to optimize! 28 | this.update = function() { 29 | var i, cell, cellPos, cellNode, m, node, item, other; 30 | var x, y, minX, minY, maxX, maxY, gridRadius; 31 | 32 | for (i = 0; i < this.numCells; i++) { 33 | _cells[i].clear(); 34 | } 35 | 36 | node = _itemList.first; 37 | while (node) { 38 | item = node.obj; 39 | if (!item.collider.solid) { 40 | node = node.next; 41 | continue; 42 | } 43 | 44 | gridRadius = Math.ceil(item.collider.radius * _sizeMulti); 45 | itemX = ~~(item.position.x * _sizeMulti); 46 | itemY = ~~(item.position.y * _sizeMulti); 47 | 48 | // in our case it will grab a 3x3 section of the grid, which is unnecessary (should only get 2x2 based on quadrant) but it works 49 | minX = itemX - gridRadius; 50 | if (minX < 0) minX = 0; 51 | minY = itemY - gridRadius; 52 | if (minY < 0) minY = 0; 53 | maxX = itemX + gridRadius; 54 | if (maxX > this.widthInCells) maxX = this.widthInCells; 55 | maxY = itemY + gridRadius; 56 | if (maxY > this.heightInCells) maxY = this.heightInCells; 57 | 58 | for (x = minX; x <= maxX; x++) { 59 | for (y = minY; y <= maxY; y++) { 60 | cellPos = (x * this.heightInCells) + y; 61 | cell = _cells[cellPos]; 62 | if (!cell) continue; 63 | 64 | cellNode = cell.first; 65 | while (cellNode) { 66 | other = cellNode.obj; 67 | if (!other.collider.solid || other.collider.collisionId === item.collider.collisionId) { 68 | cellNode = cellNode.next; 69 | continue; 70 | } 71 | 72 | m = this.collideBalls(item.collider, other.collider); // separates 73 | if (m) { 74 | this.resolveCollision(item.collider, other.collider, m); // reacts 75 | // item.collider.collisionSignal.dispatch(other, m); 76 | // other.collider.collisionSignal.dispatch(item, m); 77 | } 78 | 79 | cellNode = cellNode.next; 80 | } 81 | 82 | _cells[cellPos].add(item); 83 | } 84 | } 85 | 86 | node = node.next; 87 | } 88 | }; 89 | 90 | this.draw = function(ctx) { 91 | var i, j; 92 | 93 | ctx.lineWidth = 1; 94 | ctx.strokeStyle = 'rgba(0, 120, 120, 0.5)'; 95 | 96 | for (i = 0; i < this.widthInCells; i++) { 97 | for (j = 0; j < this.heightInCells; j++) { 98 | ctx.strokeRect(i*this.cellPixelSize, j*this.cellPixelSize, this.cellPixelSize, this.cellPixelSize); 99 | } 100 | } 101 | }; 102 | 103 | this.add = function(obj) { 104 | if (!obj.collider) { 105 | throw new Error('Any object added to the collision grid must have a collider component'); 106 | } 107 | if (_itemList.has(obj)) return; 108 | _itemList.add(obj); 109 | }; 110 | 111 | this.remove = function(obj) { 112 | _itemList.remove(obj); 113 | }; 114 | 115 | /** 116 | * Tests if there's any overlap between two given circles, and returns 117 | * the resulting Minimum Translation Distance if so. 118 | * 119 | * @source https://github.com/vonWolfehaus/von-physics 120 | */ 121 | this.collideBalls = function(a, b) { 122 | var dx = a.position.x - b.position.x; 123 | var dy = a.position.y - b.position.y; 124 | var dist = (dx * dx) + (dy * dy); 125 | var radii = a.radius + b.radius; 126 | 127 | if (dist < radii * radii) { 128 | dist = Math.sqrt(dist); 129 | 130 | DebugDraw.circle(a.position.x, a.position.y, 25, 'rgba(0, 0, 0, 0.2)'); // DEBUG 131 | 132 | _difference.reset(dx, dy); 133 | if (dist == 0) { 134 | dist = a.radius + b.radius - 1; 135 | _difference.reset(radii, radii); 136 | } 137 | var j = (radii - dist) / dist; 138 | _mtd.reset(_difference.x * j, _difference.y * j); 139 | 140 | // separate them! 141 | var cim = a.invmass + b.invmass; 142 | a.position.x += _mtd.x * (a.invmass / cim); 143 | a.position.y += _mtd.y * (a.invmass / cim); 144 | 145 | b.position.x -= _mtd.x * (b.invmass / cim); 146 | b.position.y -= _mtd.y * (b.invmass / cim); 147 | 148 | return _mtd; 149 | } 150 | return null; 151 | }; 152 | 153 | /** 154 | * Using the Minimum Translation Distance provided, will calculate the impulse to apply to 155 | * the circles to make them react "properly". 156 | * 157 | * @source https://github.com/vonWolfehaus/von-physics 158 | */ 159 | this.resolveCollision = function(a, b, mtd) { 160 | // impact speed 161 | _rv.reset(a.velocity.x - b.velocity.x, a.velocity.y - b.velocity.y); 162 | 163 | _normal.copy(mtd).normalize(); 164 | 165 | var velAlongNormal = _rv.dotProduct(_normal); 166 | if (velAlongNormal > 0) { 167 | // the 2 balls are intersecting, but they're moving away from each other already 168 | return; 169 | } 170 | 171 | var e = Math.min(a.restitution, b.restitution); 172 | 173 | // calculate impulse scalar 174 | var i = -(1 + e) * velAlongNormal; 175 | i /= a.invmass + b.invmass; 176 | 177 | _impulse.reset(_normal.x * i, _normal.y * i); 178 | 179 | a.velocity.x += (a.invmass * _impulse.x); 180 | a.velocity.y += (a.invmass * _impulse.y); 181 | 182 | b.velocity.x -= (b.invmass * _impulse.x); 183 | b.velocity.y -= (b.invmass * _impulse.y); 184 | }; 185 | 186 | this.getNeighbors = function(entity, pixelRadius, list) { 187 | var x, y, dx, dy, cell, node, other, cellPos, minX, minY, maxX, maxY, 188 | influence = pixelRadius * pixelRadius, 189 | gridRadius = Math.ceil(pixelRadius * _sizeMulti), 190 | pos = entity.position, 191 | itemX = ~~(pos.x * _sizeMulti), 192 | itemY = ~~(pos.y * _sizeMulti); 193 | 194 | // return _itemList; 195 | 196 | if (!list) { 197 | list = _nearbyList; 198 | } 199 | list.clear(); 200 | 201 | // enforce grid boundaries: 202 | minX = itemX - gridRadius; 203 | if (minX < 0) minX = 0; 204 | minY = itemY - gridRadius; 205 | if (minY < 0) minY = 0; 206 | maxX = itemX + gridRadius; 207 | if (maxX > this.widthInCells) maxX = this.widthInCells; 208 | maxY = itemY + gridRadius; 209 | if (maxY > this.heightInCells) maxY = this.heightInCells; 210 | 211 | // console.log('gridRadius: '+gridRadius+': '+minX+'-'+maxX+', '+minY+'-'+maxY); 212 | 213 | for (x = minX; x <= maxX; x++) { 214 | for (y = minY; y <= maxY; y++) { 215 | cellPos = (x * this.heightInCells) + y; 216 | cell = _cells[cellPos]; 217 | if (!cell) continue; 218 | 219 | node = cell.first; 220 | while (node) { 221 | other = node.obj; 222 | if (other.uniqueId === entity.uniqueId) { 223 | node = node.next; 224 | continue; 225 | } 226 | 227 | dx = pos.x - other.position.x; 228 | dy = pos.y - other.position.y; 229 | 230 | if ((dx * dx) + (dy * dy) <= influence) { 231 | list.add(other); 232 | } 233 | 234 | node = node.next; 235 | } 236 | } 237 | } 238 | 239 | return list; 240 | }; 241 | 242 | // does NOT clear the list for you; this is so we can build up a single list for multiple areas 243 | this.getNearby = function(pos, pixelRadius, list) { 244 | var x, y, dx, dy, cell, node, other, cellPos, minX, minY, maxX, maxY, 245 | influence = pixelRadius * pixelRadius, 246 | gridRadius = Math.ceil(pixelRadius * _sizeMulti), 247 | itemX = ~~(pos.x * _sizeMulti), 248 | itemY = ~~(pos.y * _sizeMulti); 249 | 250 | if (!list) { 251 | _nearbyList.clear(); 252 | list = _nearbyList; 253 | } 254 | 255 | // enforce grid boundaries: 256 | minX = itemX - gridRadius; 257 | if (minX < 0) minX = 0; 258 | minY = itemY - gridRadius; 259 | if (minY < 0) minY = 0; 260 | maxX = itemX + gridRadius; 261 | if (maxX > this.widthInCells) maxX = this.widthInCells; 262 | maxY = itemY + gridRadius; 263 | if (maxY > this.heightInCells) maxY = this.heightInCells; 264 | 265 | // console.log('gridRadius: '+gridRadius+': '+minX+'-'+maxX+', '+minY+'-'+maxY); 266 | 267 | for (x = minX; x <= maxX; x++) { 268 | for (y = minY; y <= maxY; y++) { 269 | cellPos = (x * this.heightInCells) + y; 270 | cell = _cells[cellPos]; 271 | if (!cell) continue; 272 | 273 | node = cell.first; 274 | while (node) { 275 | other = node.obj; 276 | dx = pos.x - other.position.x; 277 | dy = pos.y - other.position.y; 278 | 279 | if ((dx * dx) + (dy * dy) <= influence) { 280 | list.add(other); 281 | } 282 | 283 | node = node.next; 284 | } 285 | } 286 | } 287 | 288 | return list; 289 | }; 290 | 291 | this.log = function() { 292 | console.log('Cells: '+_cells.length); 293 | }; 294 | 295 | 296 | init(); 297 | function init() { 298 | var i, j; 299 | // console.log(_cells); 300 | for (i = 0; i < _self.numCells; i++) { 301 | _cells[i] = new LinkedList(); 302 | } 303 | // console.log(_cells); 304 | 305 | Kai.grid = _self; 306 | console.log('[CollisionGrid] '+_self.widthInCells+'x'+_self.heightInCells+': '+_self.numCells+' cells'); 307 | } 308 | 309 | } // class 310 | }); -------------------------------------------------------------------------------- /scripts/engine/ComponentType.js: -------------------------------------------------------------------------------- 1 | // instead of typing all this shit out by hand, have a script that build the list of all js files in 2 | // the components folder and just load that in (they'll have to be sorted by dependencies) 3 | define(['components/behaviors/Flock', 'components/VectorFieldState', 'components/BoundingCircle'], 4 | function(Flock, VectorFieldState, BoundingCircle) { 5 | 6 | var c = { 7 | VECTOR_FIELD: { accessor: 'vecFieldState', index: 0, proto: VectorFieldState }, 8 | FLOCK: { accessor: 'flock', index: 1, proto: Flock }, 9 | RADIAL_COLLIDER: { accessor: 'collider', index: 1, proto: BoundingCircle } 10 | }; 11 | 12 | window.ComponentType = c; 13 | 14 | return c; 15 | 16 | }); 17 | -------------------------------------------------------------------------------- /scripts/engine/DebugDraw.js: -------------------------------------------------------------------------------- 1 | var DebugDraw = {}; 2 | (function() { 3 | 4 | var ctx = document.getElementById('debug').getContext('2d'); 5 | 6 | DebugDraw.circle = function(x, y, radius, color) { 7 | color = color || 'rgba(10, 200, 30)'; 8 | ctx.beginPath(); 9 | ctx.arc(x, y, radius, 0, Math.PI*2); 10 | ctx.lineWidth = 1; 11 | ctx.strokeStyle = color; 12 | ctx.stroke(); 13 | }; 14 | 15 | DebugDraw.rectangle = function(x, y, sizeX, sizeY, color) { 16 | color = color || 'rgba(10, 200, 30)'; 17 | ctx.beginPath(); 18 | ctx.lineWidth = 1; 19 | ctx.strokeStyle = color; 20 | ctx.strokeRect(x - (sizeX*0.5), y - (sizeY*0.5), sizeX, sizeY); 21 | }; 22 | console.log('[DebugDraw] '); 23 | }()); -------------------------------------------------------------------------------- /scripts/engine/FlowGrid.js: -------------------------------------------------------------------------------- 1 | define(['engine/Kai', 'engine/FlowGridNode'], function(Kai, FlowGridNode) { 2 | 3 | /** 4 | * This is a flow grid (or vector grid) which is a combination of a grid that's generated using the 5 | * wavefront algorithm, which is then used to build a grid of vectors that literally point to a goal. 6 | * This provides directions for any entity to the goal point quickly. It is best used in situations where 7 | * a LOT of entities share a goal, and even better when those entities use steering behaviors, making for a 8 | * very fluid, natural motion path. 9 | * 10 | * Flexibility can be added by temporarily "disrupting" the grid with other fields emitted by dynamics obstacles. 11 | * They would change the vector grid under them (not the grid) and have it return to normal as they move away. 12 | * 13 | * Optimization is needed. There should be sectors of the grid (or just one larger grid holding multiple 14 | * FlowGrid instances) that only get rebuilt when needed. This might require another pathfinder like A* in 15 | * order to determine which sectors need updating, to prevent the wave from propagating outside the needed 16 | * bounds. 17 | * 18 | * A good place to improve on this is potential fields: http://aigamedev.com/open/tutorials/potential-fields/ 19 | * 20 | * @author Corey Birnbaum 21 | * @source http://gamedev.tutsplus.com/tutorials/implementation/goal-based-vector-field-pathfinding/ 22 | */ 23 | return function FlowGrid(cellSize, width, height) { 24 | 25 | this.cellPixelSize = cellSize; 26 | 27 | this.widthInCells = Math.floor(width / cellSize) + 1; 28 | this.heightInCells = Math.floor(height / cellSize) + 1; 29 | 30 | this.numCells = this.widthInCells * this.heightInCells; 31 | 32 | this.grid = []; 33 | 34 | this.goal = new Vec2(); 35 | this.goalPixels = new Vec2(); 36 | 37 | var _self = this, _openList = new LinkedList(), _tau = Math.PI*2, 38 | _sizeMulti = 1 / this.cellPixelSize; 39 | 40 | 41 | /** 42 | * Coordinates are in world space (pixels). 43 | */ 44 | this.setGoal = function(endPixelX, endPixelY) { 45 | var endX = ~~(endPixelX * _sizeMulti); 46 | var endY = ~~(endPixelY * _sizeMulti); 47 | 48 | if (endX < 0 || endY < 0 || endX >= this.widthInCells || endY >= this.heightInCells) { 49 | throw new Error('[FlowGrid.build] Out of bounds'); 50 | } 51 | 52 | if (this.goal.x === endX && this.goal.y === endY) return false; 53 | 54 | this.goal.x = endX; 55 | this.goal.y = endY; 56 | this.goalPixels.reset(endPixelX, endPixelY); 57 | 58 | return true; 59 | }; 60 | 61 | /** 62 | * Runs a breadth-first search on the heatmap, stores how many steps it took to get to each tile 63 | * along the way. Then calculates the movement vectors. 64 | */ 65 | this.build = function() { 66 | var i, j, current, node, neighbor, 67 | v, a, b; 68 | 69 | for (i = 0; i < this.widthInCells; i++) { 70 | for (j = 0; j < this.heightInCells; j++) { 71 | this.grid[i][j].open = true; 72 | } 73 | } 74 | 75 | _openList.clear(); 76 | 77 | node = this.grid[this.goal.x][this.goal.y]; 78 | node.cost = 0; 79 | 80 | _openList.add(node); 81 | 82 | // front the wave. set fire to the brush. etc. 83 | while (_openList.length) { 84 | node = _openList.shift(); 85 | node.open = false; 86 | 87 | current = this.grid[node.gridX][node.gridY]; 88 | 89 | // left 90 | neighbor = node.gridX-1 >= 0 ? this.grid[node.gridX-1][node.gridY] : null; 91 | if (neighbor && neighbor.open && neighbor.passable) { 92 | neighbor.cost = current.cost + 1; 93 | neighbor.open = false; // we must set false now, in case a different neighbor gets this as neighbor 94 | _openList.add(neighbor); 95 | } 96 | // right 97 | neighbor = this.grid[node.gridX+1] ? this.grid[node.gridX+1][node.gridY] : null; 98 | if (neighbor && neighbor.open && neighbor.passable) { 99 | neighbor.cost = current.cost + 1; 100 | neighbor.open = false; 101 | _openList.add(neighbor); 102 | } 103 | // up 104 | neighbor = this.grid[node.gridX][node.gridY-1] || null; 105 | if (neighbor && neighbor.open && neighbor.passable) { 106 | neighbor.cost = current.cost + 1; 107 | neighbor.open = false; 108 | _openList.add(neighbor); 109 | } 110 | // down 111 | neighbor = this.grid[node.gridX][node.gridY+1] || null; 112 | if (neighbor && neighbor.open && neighbor.passable) { 113 | neighbor.cost = current.cost + 1; 114 | neighbor.open = false; 115 | _openList.add(neighbor); 116 | } 117 | // i++; // DEBUG 118 | } 119 | 120 | // recalculate the vector field 121 | for (i = 0; i < this.widthInCells; i++) { 122 | for (j = 0; j < this.heightInCells; j++) { 123 | v = this.grid[i][j]; 124 | 125 | a = i-1 >= 0 && this.grid[i-1][j].passable ? this.grid[i-1][j].cost : v.cost; 126 | b = i+1 < this.widthInCells && this.grid[i+1][j].passable ? this.grid[i+1][j].cost : v.cost; 127 | v.x = a - b; 128 | 129 | a = j-1 >= 0 && this.grid[i][j-1].passable ? this.grid[i][j-1].cost : v.cost; 130 | b = j+1 < this.heightInCells && this.grid[i][j+1].passable ? this.grid[i][j+1].cost : v.cost; 131 | v.y = a - b; 132 | } 133 | } 134 | // TODO: normalize values 135 | 136 | // console.log('[FlowGrid.regenHeatmap] Completed in '+i+' iterations:'); 137 | // console.log(this.toString()); 138 | }; 139 | 140 | this.draw = function(ctx) { 141 | var i, j, v, vx, vy; 142 | ctx.lineWidth = 1; 143 | for (i = 0; i < this.widthInCells; i++) { 144 | for (j = 0; j < this.heightInCells; j++) { 145 | ctx.strokeStyle = 'rgba(0, 0, 0, 0.4)'; 146 | ctx.fillStyle = 'rgba(0, 0, 0, 0.3)'; 147 | v = this.grid[i][j]; 148 | if (!v.passable) continue; 149 | 150 | vx = (i*this.cellPixelSize)+(this.cellPixelSize*0.5); 151 | vy = (j*this.cellPixelSize)+(this.cellPixelSize*0.5); 152 | 153 | ctx.beginPath(); 154 | ctx.arc(vx, vy, 3, 0, _tau, false); 155 | ctx.fill(); 156 | ctx.moveTo(vx, vy); 157 | ctx.lineTo(vx+(v.x*11), vy+(v.y*11)); 158 | ctx.stroke(); 159 | 160 | // ctx.strokeStyle = 'rgba(120, 0, 0, 0.5)'; 161 | // ctx.strokeRect(i*this.cellPixelSize, j*this.cellPixelSize, this.cellPixelSize, this.cellPixelSize); 162 | } 163 | } 164 | }; 165 | 166 | /** 167 | * Given the pixel coordinates, return the Vec2 associated with that position. 168 | */ 169 | this.getVectorAt = function(pos) { 170 | var x = ~~(pos.x * _sizeMulti); 171 | var y = ~~(pos.y * _sizeMulti); 172 | if (this.grid[x] && this.grid[x][y]) { 173 | return this.grid[x][y]; 174 | } 175 | return null; 176 | }; 177 | 178 | /** 179 | * Flips the flow switch at the provided pixel coordinates, so it will either become passable, or not. 180 | */ 181 | this.setBlockAt = function(x, y) { 182 | x = ~~(x * _sizeMulti); 183 | y = ~~(y * _sizeMulti); 184 | this.grid[x][y].passable = !this.grid[x][y].passable; 185 | this.grid[x][y].cost = -1; 186 | 187 | this.build(); 188 | 189 | return !this.grid[x][y].passable; 190 | }; 191 | 192 | this.clear = function() { 193 | var i, j, v; 194 | for (i = 0; i < this.widthInCells; i++) { 195 | for (j = 0; j < this.heightInCells; j++) { 196 | v = this.grid[i][j]; 197 | v.passable = true; 198 | v.cost = -1; 199 | } 200 | } 201 | this.build(); 202 | }; 203 | 204 | this.toString = function() { 205 | var str = '', x = 0, y = 0, 206 | i, v; 207 | 208 | for (i = 0; i < this.numCells; i++) { 209 | v = this.grid[x][y].cost; 210 | 211 | if (v > 99) str += v + ','; 212 | else if (v > 9 && v < 100) str += ' ' + v + ','; 213 | else str += ' ' + v + ','; 214 | 215 | if (++x === this.widthInCells) { 216 | x = 0; 217 | y++; 218 | str += '\n'; 219 | } 220 | } 221 | str = str.substring(0, str.length-2); // get rid of the trailing comma because i'm ocd or something 222 | return str; 223 | }; 224 | 225 | this.log = function() { 226 | console.log(this.toString()); 227 | }; 228 | 229 | init(); 230 | function init() { 231 | var i, j; 232 | 233 | for (i = 0; i < _self.widthInCells; i++) { 234 | _self.grid[i] = []; 235 | for (j = 0; j < _self.heightInCells; j++) { 236 | _self.grid[i][j] = new FlowGridNode(i, j); 237 | } 238 | } 239 | 240 | Kai.flow = _self; 241 | 242 | console.log('[FlowGrid] '+_self.widthInCells+'x'+_self.heightInCells); 243 | } 244 | 245 | } // class 246 | }); -------------------------------------------------------------------------------- /scripts/engine/FlowGridNode.js: -------------------------------------------------------------------------------- 1 | define(function() { 2 | 3 | return function FlowGridNode(gx, gy) { 4 | // velocity 5 | this.x = 0; 6 | this.y = 0; 7 | 8 | this.gridX = gx; 9 | this.gridY = gy; 10 | 11 | // heat value 12 | this.cost = 0; 13 | this.open = true; 14 | this.passable = true; 15 | 16 | this.uniqueId = Date.now() + '' + Math.floor(Math.random()*1000); 17 | 18 | } // class 19 | }); -------------------------------------------------------------------------------- /scripts/engine/Kai.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Global state resources. No idea why I called it 'Kai'. 3 | */ 4 | define(['components/input/MouseController', 'components/input/KeyboardController'], function(MouseController, KeyboardController) { 5 | var g = { 6 | stage: null, 7 | renderer: null, 8 | grid: null, 9 | flow: null, 10 | map: null, 11 | 12 | debugCtx: null, 13 | components: null, 14 | guiOptions: { 15 | drawVectors: true, 16 | flocking: true, 17 | collision: true, 18 | clearObstacles: function() { 19 | window.Kai.map.clear(); 20 | window.Kai.flow.clear(); 21 | } 22 | }, 23 | 24 | elapsed: 0, 25 | 26 | // sim world dimensions 27 | width: window.innerWidth, 28 | height: window.innerHeight, 29 | 30 | mouse: new MouseController(), 31 | keys: new KeyboardController(), 32 | 33 | addComponent: function(entity, componentData, options) { 34 | var prop, idx, ComponentObject; 35 | options = options || null; 36 | 37 | prop = componentData.accessor; 38 | idx = componentData.index; 39 | ComponentObject = componentData.proto; 40 | 41 | if (!entity[prop]) { 42 | // console.log('[Kai] Adding '+prop); 43 | entity[prop] = new ComponentObject(entity, options); 44 | // engine.systemList[idx].add(entity[prop]); 45 | } /*else console.log('[Kai] '+prop+' already exists on entity');*/ 46 | 47 | return entity[prop]; 48 | }, 49 | 50 | removeComponent: function(entity, componentData) { 51 | 52 | } 53 | }; 54 | window.Kai = g; 55 | return g; 56 | }); -------------------------------------------------------------------------------- /scripts/engine/TileMap.js: -------------------------------------------------------------------------------- 1 | define(['engine/Kai', 'entities/Block'], function(Kai, Block) { 2 | 3 | // Simple array map. Collision is done by adding/removing collider components to the system at grid positions that have 4 | // less than 3 neighbors. 5 | 6 | return function TileMap(tileSize, tilesprite) { 7 | 8 | this.widthInTiles = Math.floor(Kai.width / tileSize) + 1; 9 | this.heightInTiles = Math.floor(Kai.height / tileSize) + 1; 10 | 11 | this.numTiles = this.widthInTiles * this.heightInTiles; 12 | this.grid = []; 13 | 14 | // internal 15 | var _self = this, 16 | _blockCache = new LinkedList(), 17 | _blockLookup = {}, 18 | _ctx = null, 19 | _sizeMulti = 1 / tileSize; 20 | 21 | 22 | 23 | /** 24 | * Add or remove a tile at the given pixel coordinates. 25 | * @returns [boolean] If a tile was changed. 26 | */ 27 | this.setTile = function(x, y, forceValue) { 28 | var idx, tile, block, px, py; 29 | 30 | forceValue = forceValue || null; 31 | x = ~~(x * _sizeMulti); 32 | y = ~~(y * _sizeMulti); 33 | idx = (x * this.heightInTiles) + y; 34 | if (idx < 0 || idx >= this.numTiles) return false; 35 | 36 | px = x * tileSize; 37 | py = y * tileSize; 38 | tile = this.grid[idx]; 39 | if (forceValue && tile === forceValue) return false; 40 | // console.log('[TileMap.setTile] '+x+', '+y+'; '+tile); 41 | 42 | if (tile > 0) { 43 | this.grid[idx] = 0; 44 | _ctx.clearRect(px, py, tileSize, tileSize); 45 | 46 | // kill the block 47 | block = _blockLookup[px+'-'+py]; 48 | _blockCache.add(block); 49 | block.disable(); 50 | delete _blockLookup[px+'-'+py]; 51 | 52 | } else { 53 | this.grid[idx] = 1; 54 | _ctx.drawImage(tilesprite, px, py); 55 | 56 | // add a block to the grid 57 | if (!!_blockCache.length) { 58 | block = _blockCache.pop(); 59 | block.position.x = px+25; 60 | block.position.y = py+25; 61 | } else { 62 | block = new Block(px+25, py+25); 63 | block.collider.setMass(0); 64 | } 65 | 66 | block.enable(); 67 | _blockLookup[px+'-'+py] = block; 68 | } 69 | 70 | // console.log(this.toString()); 71 | return true; 72 | }; 73 | 74 | this.getTile = function(x, y) { 75 | var idx, tile; 76 | 77 | x = ~~(x * _sizeMulti); 78 | y = ~~(y * _sizeMulti); 79 | idx = (x * this.heightInTiles) + y; 80 | if (idx < 0 || idx >= this.numTiles) return null; 81 | 82 | return this.grid[idx]; 83 | }; 84 | 85 | this.clear = function() { 86 | for (var id in _blockLookup) { 87 | var str = id.split('-'), 88 | x = ~~(parseInt(str[0], 10) * _sizeMulti); 89 | y = ~~(parseInt(str[1], 10) * _sizeMulti); 90 | idx = (x * this.heightInTiles) + y; 91 | 92 | this.grid[idx] = 0; 93 | block = _blockLookup[id]; 94 | _ctx.clearRect(block.position.x-25, block.position.y-25, tileSize, tileSize); 95 | 96 | _blockCache.add(block); 97 | block.disable(); 98 | delete _blockLookup[id]; 99 | } 100 | }; 101 | 102 | this.toString = function() { 103 | var str = '', x = 0, y = 0, 104 | i, v; 105 | 106 | for (i = 0; i < this.numTiles; i++) { 107 | v = this.grid[~~((x * this.heightInTiles) + y)]; 108 | 109 | if (v > 9 && v < 100) str += v + ','; 110 | else str += ' ' + v + ','; 111 | 112 | if (++x === this.widthInTiles) { 113 | x = 0; 114 | y++; 115 | str += '\n'; 116 | } 117 | } 118 | str = str.substring(0, str.length-2); // get rid of the trailing comma because i'm ocd or something 119 | return str; 120 | }; 121 | 122 | init(); 123 | function init() { 124 | var canvas = document.getElementById('tilemap'); 125 | canvas.width = window.innerWidth; 126 | canvas.height = window.innerHeight; 127 | _ctx = canvas.getContext('2d'); 128 | 129 | for (var i = 0; i < _self.numTiles; i++) { 130 | _self.grid[i] = 0; 131 | } 132 | 133 | Kai.map = _self; 134 | 135 | console.log('[TileMap] '+_self.widthInTiles+'x'+_self.heightInTiles); 136 | // console.log(_self.toString()); 137 | } 138 | 139 | } // class 140 | 141 | }); -------------------------------------------------------------------------------- /scripts/entities/Block.js: -------------------------------------------------------------------------------- 1 | define(['engine/Kai', 'components/BoundingCircle'], 2 | function(Kai, BoundingCircle) { 3 | 4 | return function Block(posx, posy) { 5 | if (typeof posx === 'undefined') posx = 0; 6 | if (typeof posy === 'undefined') posy = 0; 7 | 8 | // @private 9 | var _self = this; 10 | 11 | this.uniqueId = Date.now() + '' + Math.floor(Math.random()*1000); 12 | 13 | // base components 14 | this.position = new Vec2(posx, posy); 15 | this.velocity = new Vec2(); 16 | this.collider = new BoundingCircle(this, {radius: 25}); 17 | 18 | this.enable = function() { 19 | // Kai.grid.add(this); 20 | this.collider.solid = true; 21 | }; 22 | 23 | this.disable = function() { 24 | // Kai.grid.remove(this); 25 | this.collider.solid = false; 26 | }; 27 | 28 | init(); 29 | function init() { 30 | Kai.grid.add(_self); 31 | } 32 | 33 | } // class 34 | 35 | }); -------------------------------------------------------------------------------- /scripts/entities/Thing.js: -------------------------------------------------------------------------------- 1 | define(['engine/Kai', 'components/BoundingCircle', 'components/VectorFieldState', 'components/behaviors/Flock'], 2 | function(Kai, BoundingCircle, FlowState, Flock) { 3 | 4 | return function Thing(posx, posy) { 5 | if (typeof posx === 'undefined') posx = 0; 6 | if (typeof posy === 'undefined') posy = 0; 7 | 8 | // @private 9 | var _self = this, _nearby = null, /*_tau = Math.PI * 2,*/ 10 | _speed = 3, 11 | _angularSpeed = 2, 12 | _accel = new Vec2(); 13 | 14 | this.uniqueId = Date.now() + '' + Math.floor(Math.random()*1000); 15 | 16 | // base components 17 | this.position = new Vec2(posx, posy); 18 | this.velocity = new Vec2(Math.random()*_speed-_speed*0.5, Math.random()*_speed-_speed*0.5); 19 | // this.rotation = new Vec2(Math.random()*_tau, Math.random()*_tau); 20 | this.sprite = null; 21 | 22 | // special components 23 | this.flock = new Flock(this, {maxSpeed: _speed}); 24 | this.vecFieldState = new FlowState(this); 25 | this.collider = new BoundingCircle(this, {radius: 25}); 26 | 27 | // internal 28 | 29 | // we should have a component system that updates each one for us, and then use signals for 30 | // actual gameplay logic. but until then... 31 | this.update = function() { 32 | var fieldForce = this.vecFieldState.update(); 33 | 34 | if (this.vecFieldState.reachedGoal) { 35 | this.velocity.multiplyScalar(0.9); 36 | _accel.x = _accel.y = 0; 37 | } else { 38 | // VECTOR FIELD 39 | if (fieldForce) _accel.copy(fieldForce).normalize(); 40 | 41 | // FLOCK 42 | if (Kai.guiOptions.flocking) { 43 | var flockForce = this.flock.update()/*.multiplyScalar(1.6)*/; 44 | _accel.add(flockForce); 45 | } 46 | } 47 | 48 | // _accel.truncate(_speed); 49 | this.velocity.add(_accel); 50 | // this.velocity.normalize().multiplyScalar(_speed); 51 | this.velocity.truncate(_speed); 52 | 53 | this.position.add(this.velocity); 54 | this.sprite.rotation = Math.atan2(this.velocity.y, this.velocity.x); 55 | 56 | // GRID TEST 57 | /*_nearby = Kai.grid.getNearby(this.position, 100); 58 | var node = _nearby.first; 59 | while (node) { 60 | var obj = node.obj; 61 | Vec2.draw(Kai.debugCtx, this.position, obj.position); 62 | node = node.next; 63 | }*/ 64 | 65 | // screen wrap 66 | // if (this.position.x > window.innerWidth) this.position.x -= window.innerWidth; 67 | // if (this.position.x < 0) this.position.x += window.innerWidth; 68 | // if (this.position.y > window.innerHeight) this.position.y -= window.innerHeight; 69 | // if (this.position.y < 0) this.position.y += window.innerHeight; 70 | // bounds 71 | if (this.position.x > window.innerWidth) this.position.x = window.innerWidth; 72 | if (this.position.x < 0) this.position.x = 0; 73 | if (this.position.y > window.innerHeight) this.position.y = window.innerHeight; 74 | if (this.position.y < 0) this.position.y = 0; 75 | }; 76 | 77 | 78 | init(); 79 | function init() { 80 | var texture = PIXI.Texture.fromImage('img/beetle.png'); 81 | _self.sprite = new PIXI.Sprite(texture); 82 | // center the _self sprite's anchor point (as opposed to pivot which is relative position to parent) 83 | _self.sprite.anchor.x = 0.5; 84 | _self.sprite.anchor.y = 0.5; 85 | 86 | Kai.stage.addChild(_self.sprite); 87 | 88 | Kai.grid.add(_self); 89 | 90 | // DEBUG 91 | _self.sprite.mousedown = function(data) { 92 | // TODO: check if this guy is in the grid and if not, what the fuck 93 | // console.log(_self.collider); 94 | // DebugDraw.circle(_self.position.x, _self.position.y, 25); 95 | }; 96 | 97 | // link components 98 | _self.sprite.position = _self.position; 99 | } 100 | 101 | } // class 102 | 103 | }); -------------------------------------------------------------------------------- /scripts/lib/Signal.js: -------------------------------------------------------------------------------- 1 | var Signal = (function () { 2 | function Signal() { 3 | this._bindings = []; 4 | this._prevParams = null; 5 | this.memorize = false; 6 | this._shouldPropagate = true; 7 | this.active = true; 8 | } 9 | Signal.prototype.validateListener = function (listener, fnName) { 10 | if (typeof listener !== 'function') { 11 | throw new Error('listener is a required param of {fn}() and should be a Function.'.replace('{fn}', fnName)); 12 | } 13 | }; 14 | 15 | Signal.prototype._registerListener = function (listener, isOnce, listenerContext, priority) { 16 | var prevIndex = this._indexOfListener(listener, listenerContext); 17 | var binding; 18 | 19 | if (prevIndex !== -1) { 20 | binding = this._bindings[prevIndex]; 21 | 22 | if (binding.isOnce() !== isOnce) { 23 | throw new Error('You cannot add' + (isOnce ? '' : 'Once') + '() then add' + (!isOnce ? '' : 'Once') + '() the same listener without removing the relationship first.'); 24 | } 25 | } else { 26 | binding = new SignalBinding(this, listener, isOnce, listenerContext, priority); 27 | 28 | this._addBinding(binding); 29 | } 30 | 31 | if (this.memorize && this._prevParams) { 32 | binding.execute(this._prevParams); 33 | } 34 | 35 | return binding; 36 | }; 37 | 38 | Signal.prototype._addBinding = function (binding) { 39 | var n = this._bindings.length; 40 | 41 | do { 42 | --n; 43 | } while(this._bindings[n] && binding.priority <= this._bindings[n].priority); 44 | 45 | this._bindings.splice(n + 1, 0, binding); 46 | }; 47 | 48 | Signal.prototype._indexOfListener = function (listener, context) { 49 | var n = this._bindings.length; 50 | var cur; 51 | 52 | while (n--) { 53 | cur = this._bindings[n]; 54 | 55 | if (cur.getListener() === listener && cur.context === context) { 56 | return n; 57 | } 58 | } 59 | 60 | return -1; 61 | }; 62 | 63 | Signal.prototype.has = function (listener, context) { 64 | if (typeof context === "undefined") { context = null; } 65 | return this._indexOfListener(listener, context) !== -1; 66 | }; 67 | 68 | Signal.prototype.add = function (listener, listenerContext, priority) { 69 | if (typeof listenerContext === "undefined") { listenerContext = null; } 70 | if (typeof priority === "undefined") { priority = 0; } 71 | this.validateListener(listener, 'add'); 72 | 73 | return this._registerListener(listener, false, listenerContext, priority); 74 | }; 75 | 76 | Signal.prototype.addOnce = function (listener, listenerContext, priority) { 77 | if (typeof listenerContext === "undefined") { listenerContext = null; } 78 | if (typeof priority === "undefined") { priority = 0; } 79 | this.validateListener(listener, 'addOnce'); 80 | 81 | return this._registerListener(listener, true, listenerContext, priority); 82 | }; 83 | 84 | Signal.prototype.remove = function (listener, context) { 85 | if (typeof context === "undefined") { context = null; } 86 | this.validateListener(listener, 'remove'); 87 | 88 | var i = this._indexOfListener(listener, context); 89 | 90 | if (i !== -1) { 91 | this._bindings[i]._destroy(); 92 | this._bindings.splice(i, 1); 93 | } 94 | 95 | return listener; 96 | }; 97 | 98 | Signal.prototype.removeAll = function () { 99 | var n = this._bindings.length; 100 | 101 | while (n--) { 102 | this._bindings[n]._destroy(); 103 | } 104 | 105 | this._bindings.length = 0; 106 | }; 107 | 108 | Signal.prototype.getNumListeners = function () { 109 | return this._bindings.length; 110 | }; 111 | 112 | Signal.prototype.halt = function () { 113 | this._shouldPropagate = false; 114 | }; 115 | 116 | Signal.prototype.dispatch = function () { 117 | var paramsArr = []; 118 | for (var _i = 0; _i < (arguments.length - 0); _i++) { 119 | paramsArr[_i] = arguments[_i + 0]; 120 | } 121 | if (!this.active) { 122 | return; 123 | } 124 | 125 | var n = this._bindings.length; 126 | var bindings; 127 | 128 | if (this.memorize) { 129 | this._prevParams = paramsArr; 130 | } 131 | 132 | if (!n) { 133 | return; 134 | } 135 | 136 | bindings = this._bindings.slice(0); 137 | 138 | this._shouldPropagate = true; 139 | 140 | do { 141 | n--; 142 | } while(bindings[n] && this._shouldPropagate && bindings[n].execute(paramsArr) !== false); 143 | }; 144 | 145 | Signal.prototype.forget = function () { 146 | this._prevParams = null; 147 | }; 148 | 149 | Signal.prototype.dispose = function () { 150 | this.removeAll(); 151 | 152 | delete this._bindings; 153 | delete this._prevParams; 154 | }; 155 | 156 | Signal.prototype.toString = function () { 157 | return '[Signal active:' + this.active + ' numListeners:' + this.getNumListeners() + ']'; 158 | }; 159 | Signal.VERSION = '1.0.0'; 160 | return Signal; 161 | })(); 162 | var SignalBinding = (function () { 163 | function SignalBinding(signal, listener, isOnce, listenerContext, priority) { 164 | if (typeof priority === "undefined") { priority = 0; } 165 | this.active = true; 166 | this.params = null; 167 | this._listener = listener; 168 | this._isOnce = isOnce; 169 | this.context = listenerContext; 170 | this._signal = signal; 171 | this.priority = priority || 0; 172 | } 173 | SignalBinding.prototype.execute = function (paramsArr) { 174 | var handlerReturn; 175 | var params; 176 | 177 | if (this.active && !!this._listener) { 178 | params = this.params ? this.params.concat(paramsArr) : paramsArr; 179 | 180 | handlerReturn = this._listener.apply(this.context, params); 181 | 182 | if (this._isOnce) { 183 | this.detach(); 184 | } 185 | } 186 | 187 | return handlerReturn; 188 | }; 189 | 190 | SignalBinding.prototype.detach = function () { 191 | return this.isBound() ? this._signal.remove(this._listener, this.context) : null; 192 | }; 193 | 194 | SignalBinding.prototype.isBound = function () { 195 | return (!!this._signal && !!this._listener); 196 | }; 197 | 198 | SignalBinding.prototype.isOnce = function () { 199 | return this._isOnce; 200 | }; 201 | 202 | SignalBinding.prototype.getListener = function () { 203 | return this._listener; 204 | }; 205 | 206 | SignalBinding.prototype.getSignal = function () { 207 | return this._signal; 208 | }; 209 | 210 | SignalBinding.prototype._destroy = function () { 211 | delete this._signal; 212 | delete this._listener; 213 | delete this.context; 214 | }; 215 | 216 | SignalBinding.prototype.toString = function () { 217 | return '[SignalBinding isOnce:' + this._isOnce + ', isBound:' + this.isBound() + ', active:' + this.active + ']'; 218 | }; 219 | return SignalBinding; 220 | })(); 221 | -------------------------------------------------------------------------------- /scripts/lib/dat.gui.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * dat-gui JavaScript Controller Library 3 | * http://code.google.com/p/dat-gui 4 | * 5 | * Copyright 2011 Data Arts Team, Google Creative Lab 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | */ 13 | var dat=dat||{};dat.gui=dat.gui||{};dat.utils=dat.utils||{};dat.controllers=dat.controllers||{};dat.dom=dat.dom||{};dat.color=dat.color||{};dat.utils.css=function(){return{load:function(e,a){var a=a||document,c=a.createElement("link");c.type="text/css";c.rel="stylesheet";c.href=e;a.getElementsByTagName("head")[0].appendChild(c)},inject:function(e,a){var a=a||document,c=document.createElement("style");c.type="text/css";c.innerHTML=e;a.getElementsByTagName("head")[0].appendChild(c)}}}(); 14 | dat.utils.common=function(){var e=Array.prototype.forEach,a=Array.prototype.slice;return{BREAK:{},extend:function(c){this.each(a.call(arguments,1),function(a){for(var f in a)this.isUndefined(a[f])||(c[f]=a[f])},this);return c},defaults:function(c){this.each(a.call(arguments,1),function(a){for(var f in a)this.isUndefined(c[f])&&(c[f]=a[f])},this);return c},compose:function(){var c=a.call(arguments);return function(){for(var d=a.call(arguments),f=c.length-1;f>=0;f--)d=[c[f].apply(this,d)];return d[0]}}, 15 | each:function(a,d,f){if(e&&a.forEach===e)a.forEach(d,f);else if(a.length===a.length+0)for(var b=0,n=a.length;b-1?d.length-d.indexOf(".")-1:0};c.superclass=e;a.extend(c.prototype,e.prototype,{setValue:function(a){if(this.__min!==void 0&&athis.__max)a=this.__max;this.__step!==void 0&&a%this.__step!=0&&(a=Math.round(a/this.__step)*this.__step);return c.superclass.prototype.setValue.call(this,a)},min:function(a){this.__min=a;return this},max:function(a){this.__max=a;return this},step:function(a){this.__step=a;return this}});return c}(dat.controllers.Controller,dat.utils.common); 29 | dat.controllers.NumberControllerBox=function(e,a,c){var d=function(f,b,e){function h(){var a=parseFloat(l.__input.value);c.isNaN(a)||l.setValue(a)}function j(a){var b=o-a.clientY;l.setValue(l.getValue()+b*l.__impliedStep);o=a.clientY}function m(){a.unbind(window,"mousemove",j);a.unbind(window,"mouseup",m)}this.__truncationSuspended=false;d.superclass.call(this,f,b,e);var l=this,o;this.__input=document.createElement("input");this.__input.setAttribute("type","text");a.bind(this.__input,"change",h); 30 | a.bind(this.__input,"blur",function(){h();l.__onFinishChange&&l.__onFinishChange.call(l,l.getValue())});a.bind(this.__input,"mousedown",function(b){a.bind(window,"mousemove",j);a.bind(window,"mouseup",m);o=b.clientY});a.bind(this.__input,"keydown",function(a){if(a.keyCode===13)l.__truncationSuspended=true,this.blur(),l.__truncationSuspended=false});this.updateDisplay();this.domElement.appendChild(this.__input)};d.superclass=e;c.extend(d.prototype,e.prototype,{updateDisplay:function(){var a=this.__input, 31 | b;if(this.__truncationSuspended)b=this.getValue();else{b=this.getValue();var c=Math.pow(10,this.__precision);b=Math.round(b*c)/c}a.value=b;return d.superclass.prototype.updateDisplay.call(this)}});return d}(dat.controllers.NumberController,dat.dom.dom,dat.utils.common); 32 | dat.controllers.NumberControllerSlider=function(e,a,c,d,f){var b=function(d,c,f,e,l){function o(b){b.preventDefault();var d=a.getOffset(g.__background),c=a.getWidth(g.__background);g.setValue(g.__min+(g.__max-g.__min)*((b.clientX-d.left)/(d.left+c-d.left)));return false}function y(){a.unbind(window,"mousemove",o);a.unbind(window,"mouseup",y);g.__onFinishChange&&g.__onFinishChange.call(g,g.getValue())}b.superclass.call(this,d,c,{min:f,max:e,step:l});var g=this;this.__background=document.createElement("div"); 33 | this.__foreground=document.createElement("div");a.bind(this.__background,"mousedown",function(b){a.bind(window,"mousemove",o);a.bind(window,"mouseup",y);o(b)});a.addClass(this.__background,"slider");a.addClass(this.__foreground,"slider-fg");this.updateDisplay();this.__background.appendChild(this.__foreground);this.domElement.appendChild(this.__background)};b.superclass=e;b.useDefaultStyles=function(){c.inject(f)};d.extend(b.prototype,e.prototype,{updateDisplay:function(){this.__foreground.style.width= 34 | (this.getValue()-this.__min)/(this.__max-this.__min)*100+"%";return b.superclass.prototype.updateDisplay.call(this)}});return b}(dat.controllers.NumberController,dat.dom.dom,dat.utils.css,dat.utils.common,".slider {\n box-shadow: inset 0 2px 4px rgba(0,0,0,0.15);\n height: 1em;\n border-radius: 1em;\n background-color: #eee;\n padding: 0 0.5em;\n overflow: hidden;\n}\n\n.slider-fg {\n padding: 1px 0 2px 0;\n background-color: #aaa;\n height: 1em;\n margin-left: -0.5em;\n padding-right: 0.5em;\n border-radius: 1em 0 0 1em;\n}\n\n.slider-fg:after {\n display: inline-block;\n border-radius: 1em;\n background-color: #fff;\n border: 1px solid #aaa;\n content: '';\n float: right;\n margin-right: -1em;\n margin-top: -1px;\n height: 0.9em;\n width: 0.9em;\n}"); 35 | dat.controllers.FunctionController=function(e,a,c){var d=function(c,b,e){d.superclass.call(this,c,b);var h=this;this.__button=document.createElement("div");this.__button.innerHTML=e===void 0?"Fire":e;a.bind(this.__button,"click",function(a){a.preventDefault();h.fire();return false});a.addClass(this.__button,"button");this.domElement.appendChild(this.__button)};d.superclass=e;c.extend(d.prototype,e.prototype,{fire:function(){this.__onChange&&this.__onChange.call(this);this.__onFinishChange&&this.__onFinishChange.call(this, 36 | this.getValue());this.getValue().call(this.object)}});return d}(dat.controllers.Controller,dat.dom.dom,dat.utils.common); 37 | dat.controllers.BooleanController=function(e,a,c){var d=function(c,b){d.superclass.call(this,c,b);var e=this;this.__prev=this.getValue();this.__checkbox=document.createElement("input");this.__checkbox.setAttribute("type","checkbox");a.bind(this.__checkbox,"change",function(){e.setValue(!e.__prev)},false);this.domElement.appendChild(this.__checkbox);this.updateDisplay()};d.superclass=e;c.extend(d.prototype,e.prototype,{setValue:function(a){a=d.superclass.prototype.setValue.call(this,a);this.__onFinishChange&& 38 | this.__onFinishChange.call(this,this.getValue());this.__prev=this.getValue();return a},updateDisplay:function(){this.getValue()===true?(this.__checkbox.setAttribute("checked","checked"),this.__checkbox.checked=true):this.__checkbox.checked=false;return d.superclass.prototype.updateDisplay.call(this)}});return d}(dat.controllers.Controller,dat.dom.dom,dat.utils.common); 39 | dat.color.toString=function(e){return function(a){if(a.a==1||e.isUndefined(a.a)){for(a=a.hex.toString(16);a.length<6;)a="0"+a;return"#"+a}else return"rgba("+Math.round(a.r)+","+Math.round(a.g)+","+Math.round(a.b)+","+a.a+")"}}(dat.utils.common); 40 | dat.color.interpret=function(e,a){var c,d,f=[{litmus:a.isString,conversions:{THREE_CHAR_HEX:{read:function(a){a=a.match(/^#([A-F0-9])([A-F0-9])([A-F0-9])$/i);return a===null?false:{space:"HEX",hex:parseInt("0x"+a[1].toString()+a[1].toString()+a[2].toString()+a[2].toString()+a[3].toString()+a[3].toString())}},write:e},SIX_CHAR_HEX:{read:function(a){a=a.match(/^#([A-F0-9]{6})$/i);return a===null?false:{space:"HEX",hex:parseInt("0x"+a[1].toString())}},write:e},CSS_RGB:{read:function(a){a=a.match(/^rgb\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\)/); 41 | return a===null?false:{space:"RGB",r:parseFloat(a[1]),g:parseFloat(a[2]),b:parseFloat(a[3])}},write:e},CSS_RGBA:{read:function(a){a=a.match(/^rgba\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\,\s*(.+)\s*\)/);return a===null?false:{space:"RGB",r:parseFloat(a[1]),g:parseFloat(a[2]),b:parseFloat(a[3]),a:parseFloat(a[4])}},write:e}}},{litmus:a.isNumber,conversions:{HEX:{read:function(a){return{space:"HEX",hex:a,conversionName:"HEX"}},write:function(a){return a.hex}}}},{litmus:a.isArray,conversions:{RGB_ARRAY:{read:function(a){return a.length!= 42 | 3?false:{space:"RGB",r:a[0],g:a[1],b:a[2]}},write:function(a){return[a.r,a.g,a.b]}},RGBA_ARRAY:{read:function(a){return a.length!=4?false:{space:"RGB",r:a[0],g:a[1],b:a[2],a:a[3]}},write:function(a){return[a.r,a.g,a.b,a.a]}}}},{litmus:a.isObject,conversions:{RGBA_OBJ:{read:function(b){return a.isNumber(b.r)&&a.isNumber(b.g)&&a.isNumber(b.b)&&a.isNumber(b.a)?{space:"RGB",r:b.r,g:b.g,b:b.b,a:b.a}:false},write:function(a){return{r:a.r,g:a.g,b:a.b,a:a.a}}},RGB_OBJ:{read:function(b){return a.isNumber(b.r)&& 43 | a.isNumber(b.g)&&a.isNumber(b.b)?{space:"RGB",r:b.r,g:b.g,b:b.b}:false},write:function(a){return{r:a.r,g:a.g,b:a.b}}},HSVA_OBJ:{read:function(b){return a.isNumber(b.h)&&a.isNumber(b.s)&&a.isNumber(b.v)&&a.isNumber(b.a)?{space:"HSV",h:b.h,s:b.s,v:b.v,a:b.a}:false},write:function(a){return{h:a.h,s:a.s,v:a.v,a:a.a}}},HSV_OBJ:{read:function(b){return a.isNumber(b.h)&&a.isNumber(b.s)&&a.isNumber(b.v)?{space:"HSV",h:b.h,s:b.s,v:b.v}:false},write:function(a){return{h:a.h,s:a.s,v:a.v}}}}}];return function(){d= 44 | false;var b=arguments.length>1?a.toArray(arguments):arguments[0];a.each(f,function(e){if(e.litmus(b))return a.each(e.conversions,function(e,f){c=e.read(b);if(d===false&&c!==false)return d=c,c.conversionName=f,c.conversion=e,a.BREAK}),a.BREAK});return d}}(dat.color.toString,dat.utils.common); 45 | dat.GUI=dat.gui.GUI=function(e,a,c,d,f,b,n,h,j,m,l,o,y,g,i){function q(a,b,r,c){if(b[r]===void 0)throw Error("Object "+b+' has no property "'+r+'"');c.color?b=new l(b,r):(b=[b,r].concat(c.factoryArgs),b=d.apply(a,b));if(c.before instanceof f)c.before=c.before.__li;t(a,b);g.addClass(b.domElement,"c");r=document.createElement("span");g.addClass(r,"property-name");r.innerHTML=b.property;var e=document.createElement("div");e.appendChild(r);e.appendChild(b.domElement);c=s(a,e,c.before);g.addClass(c,k.CLASS_CONTROLLER_ROW); 46 | g.addClass(c,typeof b.getValue());p(a,c,b);a.__controllers.push(b);return b}function s(a,b,d){var c=document.createElement("li");b&&c.appendChild(b);d?a.__ul.insertBefore(c,params.before):a.__ul.appendChild(c);a.onResize();return c}function p(a,d,c){c.__li=d;c.__gui=a;i.extend(c,{options:function(b){if(arguments.length>1)return c.remove(),q(a,c.object,c.property,{before:c.__li.nextElementSibling,factoryArgs:[i.toArray(arguments)]});if(i.isArray(b)||i.isObject(b))return c.remove(),q(a,c.object,c.property, 47 | {before:c.__li.nextElementSibling,factoryArgs:[b]})},name:function(a){c.__li.firstElementChild.firstElementChild.innerHTML=a;return c},listen:function(){c.__gui.listen(c);return c},remove:function(){c.__gui.remove(c);return c}});if(c instanceof j){var e=new h(c.object,c.property,{min:c.__min,max:c.__max,step:c.__step});i.each(["updateDisplay","onChange","onFinishChange"],function(a){var b=c[a],H=e[a];c[a]=e[a]=function(){var a=Array.prototype.slice.call(arguments);b.apply(c,a);return H.apply(e,a)}}); 48 | g.addClass(d,"has-slider");c.domElement.insertBefore(e.domElement,c.domElement.firstElementChild)}else if(c instanceof h){var f=function(b){return i.isNumber(c.__min)&&i.isNumber(c.__max)?(c.remove(),q(a,c.object,c.property,{before:c.__li.nextElementSibling,factoryArgs:[c.__min,c.__max,c.__step]})):b};c.min=i.compose(f,c.min);c.max=i.compose(f,c.max)}else if(c instanceof b)g.bind(d,"click",function(){g.fakeEvent(c.__checkbox,"click")}),g.bind(c.__checkbox,"click",function(a){a.stopPropagation()}); 49 | else if(c instanceof n)g.bind(d,"click",function(){g.fakeEvent(c.__button,"click")}),g.bind(d,"mouseover",function(){g.addClass(c.__button,"hover")}),g.bind(d,"mouseout",function(){g.removeClass(c.__button,"hover")});else if(c instanceof l)g.addClass(d,"color"),c.updateDisplay=i.compose(function(a){d.style.borderLeftColor=c.__color.toString();return a},c.updateDisplay),c.updateDisplay();c.setValue=i.compose(function(b){a.getRoot().__preset_select&&c.isModified()&&B(a.getRoot(),true);return b},c.setValue)} 50 | function t(a,b){var c=a.getRoot(),d=c.__rememberedObjects.indexOf(b.object);if(d!=-1){var e=c.__rememberedObjectIndecesToControllers[d];e===void 0&&(e={},c.__rememberedObjectIndecesToControllers[d]=e);e[b.property]=b;if(c.load&&c.load.remembered){c=c.load.remembered;if(c[a.preset])c=c[a.preset];else if(c[w])c=c[w];else return;if(c[d]&&c[d][b.property]!==void 0)d=c[d][b.property],b.initialValue=d,b.setValue(d)}}}function I(a){var b=a.__save_row=document.createElement("li");g.addClass(a.domElement, 51 | "has-save");a.__ul.insertBefore(b,a.__ul.firstChild);g.addClass(b,"save-row");var c=document.createElement("span");c.innerHTML=" ";g.addClass(c,"button gears");var d=document.createElement("span");d.innerHTML="Save";g.addClass(d,"button");g.addClass(d,"save");var e=document.createElement("span");e.innerHTML="New";g.addClass(e,"button");g.addClass(e,"save-as");var f=document.createElement("span");f.innerHTML="Revert";g.addClass(f,"button");g.addClass(f,"revert");var m=a.__preset_select=document.createElement("select"); 52 | a.load&&a.load.remembered?i.each(a.load.remembered,function(b,c){C(a,c,c==a.preset)}):C(a,w,false);g.bind(m,"change",function(){for(var b=0;b0){a.preset=this.preset;if(!a.remembered)a.remembered={};a.remembered[this.preset]=z(this)}a.folders={};i.each(this.__folders,function(b, 69 | c){a.folders[c]=b.getSaveObject()});return a},save:function(){if(!this.load.remembered)this.load.remembered={};this.load.remembered[this.preset]=z(this);B(this,false)},saveAs:function(a){if(!this.load.remembered)this.load.remembered={},this.load.remembered[w]=z(this,true);this.load.remembered[a]=z(this);this.preset=a;C(this,a,true)},revert:function(a){i.each(this.__controllers,function(b){this.getRoot().load.remembered?t(a||this.getRoot(),b):b.setValue(b.initialValue)},this);i.each(this.__folders, 70 | function(a){a.revert(a)});a||B(this.getRoot(),false)},listen:function(a){var b=this.__listening.length==0;this.__listening.push(a);b&&E(this.__listening)}});return k}(dat.utils.css,'
\n\n Here\'s the new load parameter for your GUI\'s constructor:\n\n \n\n
\n\n Automatically save\n values to localStorage on exit.\n\n
The values saved to localStorage will\n override those passed to dat.GUI\'s constructor. This makes it\n easier to work incrementally, but localStorage is fragile,\n and your friends may not see the same values you do.\n \n
\n \n
\n\n
', 71 | ".dg ul{list-style:none;margin:0;padding:0;width:100%;clear:both}.dg.ac{position:fixed;top:0;left:0;right:0;height:0;z-index:0}.dg:not(.ac) .main{overflow:hidden}.dg.main{-webkit-transition:opacity 0.1s linear;-o-transition:opacity 0.1s linear;-moz-transition:opacity 0.1s linear;transition:opacity 0.1s linear}.dg.main.taller-than-window{overflow-y:auto}.dg.main.taller-than-window .close-button{opacity:1;margin-top:-1px;border-top:1px solid #2c2c2c}.dg.main ul.closed .close-button{opacity:1 !important}.dg.main:hover .close-button,.dg.main .close-button.drag{opacity:1}.dg.main .close-button{-webkit-transition:opacity 0.1s linear;-o-transition:opacity 0.1s linear;-moz-transition:opacity 0.1s linear;transition:opacity 0.1s linear;border:0;position:absolute;line-height:19px;height:20px;cursor:pointer;text-align:center;background-color:#000}.dg.main .close-button:hover{background-color:#111}.dg.a{float:right;margin-right:15px;overflow-x:hidden}.dg.a.has-save ul{margin-top:27px}.dg.a.has-save ul.closed{margin-top:0}.dg.a .save-row{position:fixed;top:0;z-index:1002}.dg li{-webkit-transition:height 0.1s ease-out;-o-transition:height 0.1s ease-out;-moz-transition:height 0.1s ease-out;transition:height 0.1s ease-out}.dg li:not(.folder){cursor:auto;height:27px;line-height:27px;overflow:hidden;padding:0 4px 0 5px}.dg li.folder{padding:0;border-left:4px solid rgba(0,0,0,0)}.dg li.title{cursor:pointer;margin-left:-4px}.dg .closed li:not(.title),.dg .closed ul li,.dg .closed ul li > *{height:0;overflow:hidden;border:0}.dg .cr{clear:both;padding-left:3px;height:27px}.dg .property-name{cursor:default;float:left;clear:left;width:40%;overflow:hidden;text-overflow:ellipsis}.dg .c{float:left;width:60%}.dg .c input[type=text]{border:0;margin-top:4px;padding:3px;width:100%;float:right}.dg .has-slider input[type=text]{width:30%;margin-left:0}.dg .slider{float:left;width:66%;margin-left:-5px;margin-right:0;height:19px;margin-top:4px}.dg .slider-fg{height:100%}.dg .c input[type=checkbox]{margin-top:9px}.dg .c select{margin-top:5px}.dg .cr.function,.dg .cr.function .property-name,.dg .cr.function *,.dg .cr.boolean,.dg .cr.boolean *{cursor:pointer}.dg .selector{display:none;position:absolute;margin-left:-9px;margin-top:23px;z-index:10}.dg .c:hover .selector,.dg .selector.drag{display:block}.dg li.save-row{padding:0}.dg li.save-row .button{display:inline-block;padding:0px 6px}.dg.dialogue{background-color:#222;width:460px;padding:15px;font-size:13px;line-height:15px}#dg-new-constructor{padding:10px;color:#222;font-family:Monaco, monospace;font-size:10px;border:0;resize:none;box-shadow:inset 1px 1px 1px #888;word-wrap:break-word;margin:12px 0;display:block;width:440px;overflow-y:scroll;height:100px;position:relative}#dg-local-explain{display:none;font-size:11px;line-height:17px;border-radius:3px;background-color:#333;padding:8px;margin-top:10px}#dg-local-explain code{font-size:10px}#dat-gui-save-locally{display:none}.dg{color:#eee;font:11px 'Lucida Grande', sans-serif;text-shadow:0 -1px 0 #111}.dg.main::-webkit-scrollbar{width:5px;background:#1a1a1a}.dg.main::-webkit-scrollbar-corner{height:0;display:none}.dg.main::-webkit-scrollbar-thumb{border-radius:5px;background:#676767}.dg li:not(.folder){background:#1a1a1a;border-bottom:1px solid #2c2c2c}.dg li.save-row{line-height:25px;background:#dad5cb;border:0}.dg li.save-row select{margin-left:5px;width:108px}.dg li.save-row .button{margin-left:5px;margin-top:1px;border-radius:2px;font-size:9px;line-height:7px;padding:4px 4px 5px 4px;background:#c5bdad;color:#fff;text-shadow:0 1px 0 #b0a58f;box-shadow:0 -1px 0 #b0a58f;cursor:pointer}.dg li.save-row .button.gears{background:#c5bdad url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAANCAYAAAB/9ZQ7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQJJREFUeNpiYKAU/P//PwGIC/ApCABiBSAW+I8AClAcgKxQ4T9hoMAEUrxx2QSGN6+egDX+/vWT4e7N82AMYoPAx/evwWoYoSYbACX2s7KxCxzcsezDh3evFoDEBYTEEqycggWAzA9AuUSQQgeYPa9fPv6/YWm/Acx5IPb7ty/fw+QZblw67vDs8R0YHyQhgObx+yAJkBqmG5dPPDh1aPOGR/eugW0G4vlIoTIfyFcA+QekhhHJhPdQxbiAIguMBTQZrPD7108M6roWYDFQiIAAv6Aow/1bFwXgis+f2LUAynwoIaNcz8XNx3Dl7MEJUDGQpx9gtQ8YCueB+D26OECAAQDadt7e46D42QAAAABJRU5ErkJggg==) 2px 1px no-repeat;height:7px;width:8px}.dg li.save-row .button:hover{background-color:#bab19e;box-shadow:0 -1px 0 #b0a58f}.dg li.folder{border-bottom:0}.dg li.title{padding-left:16px;background:#000 url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlI+hKgFxoCgAOw==) 6px 10px no-repeat;cursor:pointer;border-bottom:1px solid rgba(255,255,255,0.2)}.dg .closed li.title{background-image:url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlGIWqMCbWAEAOw==)}.dg .cr.boolean{border-left:3px solid #806787}.dg .cr.function{border-left:3px solid #e61d5f}.dg .cr.number{border-left:3px solid #2fa1d6}.dg .cr.number input[type=text]{color:#2fa1d6}.dg .cr.string{border-left:3px solid #1ed36f}.dg .cr.string input[type=text]{color:#1ed36f}.dg .cr.function:hover,.dg .cr.boolean:hover{background:#111}.dg .c input[type=text]{background:#303030;outline:none}.dg .c input[type=text]:hover{background:#3c3c3c}.dg .c input[type=text]:focus{background:#494949;color:#fff}.dg .c .slider{background:#303030;cursor:ew-resize}.dg .c .slider-fg{background:#2fa1d6}.dg .c .slider:hover{background:#3c3c3c}.dg .c .slider:hover .slider-fg{background:#44abda}\n", 72 | dat.controllers.factory=function(e,a,c,d,f,b,n){return function(h,j,m,l){var o=h[j];if(n.isArray(m)||n.isObject(m))return new e(h,j,m);if(n.isNumber(o))return n.isNumber(m)&&n.isNumber(l)?new c(h,j,m,l):new a(h,j,{min:m,max:l});if(n.isString(o))return new d(h,j);if(n.isFunction(o))return new f(h,j,"");if(n.isBoolean(o))return new b(h,j)}}(dat.controllers.OptionController,dat.controllers.NumberControllerBox,dat.controllers.NumberControllerSlider,dat.controllers.StringController=function(e,a,c){var d= 73 | function(c,b){function e(){h.setValue(h.__input.value)}d.superclass.call(this,c,b);var h=this;this.__input=document.createElement("input");this.__input.setAttribute("type","text");a.bind(this.__input,"keyup",e);a.bind(this.__input,"change",e);a.bind(this.__input,"blur",function(){h.__onFinishChange&&h.__onFinishChange.call(h,h.getValue())});a.bind(this.__input,"keydown",function(a){a.keyCode===13&&this.blur()});this.updateDisplay();this.domElement.appendChild(this.__input)};d.superclass=e;c.extend(d.prototype, 74 | e.prototype,{updateDisplay:function(){if(!a.isActive(this.__input))this.__input.value=this.getValue();return d.superclass.prototype.updateDisplay.call(this)}});return d}(dat.controllers.Controller,dat.dom.dom,dat.utils.common),dat.controllers.FunctionController,dat.controllers.BooleanController,dat.utils.common),dat.controllers.Controller,dat.controllers.BooleanController,dat.controllers.FunctionController,dat.controllers.NumberControllerBox,dat.controllers.NumberControllerSlider,dat.controllers.OptionController, 75 | dat.controllers.ColorController=function(e,a,c,d,f){function b(a,b,c,d){a.style.background="";f.each(j,function(e){a.style.cssText+="background: "+e+"linear-gradient("+b+", "+c+" 0%, "+d+" 100%); "})}function n(a){a.style.background="";a.style.cssText+="background: -moz-linear-gradient(top, #ff0000 0%, #ff00ff 17%, #0000ff 34%, #00ffff 50%, #00ff00 67%, #ffff00 84%, #ff0000 100%);";a.style.cssText+="background: -webkit-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);"; 76 | a.style.cssText+="background: -o-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);";a.style.cssText+="background: -ms-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);";a.style.cssText+="background: linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);"}var h=function(e,l){function o(b){q(b);a.bind(window,"mousemove",q);a.bind(window, 77 | "mouseup",j)}function j(){a.unbind(window,"mousemove",q);a.unbind(window,"mouseup",j)}function g(){var a=d(this.value);a!==false?(p.__color.__state=a,p.setValue(p.__color.toOriginal())):this.value=p.__color.toString()}function i(){a.unbind(window,"mousemove",s);a.unbind(window,"mouseup",i)}function q(b){b.preventDefault();var c=a.getWidth(p.__saturation_field),d=a.getOffset(p.__saturation_field),e=(b.clientX-d.left+document.body.scrollLeft)/c,b=1-(b.clientY-d.top+document.body.scrollTop)/c;b>1?b= 78 | 1:b<0&&(b=0);e>1?e=1:e<0&&(e=0);p.__color.v=b;p.__color.s=e;p.setValue(p.__color.toOriginal());return false}function s(b){b.preventDefault();var c=a.getHeight(p.__hue_field),d=a.getOffset(p.__hue_field),b=1-(b.clientY-d.top+document.body.scrollTop)/c;b>1?b=1:b<0&&(b=0);p.__color.h=b*360;p.setValue(p.__color.toOriginal());return false}h.superclass.call(this,e,l);this.__color=new c(this.getValue());this.__temp=new c(0);var p=this;this.domElement=document.createElement("div");a.makeSelectable(this.domElement, 79 | false);this.__selector=document.createElement("div");this.__selector.className="selector";this.__saturation_field=document.createElement("div");this.__saturation_field.className="saturation-field";this.__field_knob=document.createElement("div");this.__field_knob.className="field-knob";this.__field_knob_border="2px solid ";this.__hue_knob=document.createElement("div");this.__hue_knob.className="hue-knob";this.__hue_field=document.createElement("div");this.__hue_field.className="hue-field";this.__input= 80 | document.createElement("input");this.__input.type="text";this.__input_textShadow="0 1px 1px ";a.bind(this.__input,"keydown",function(a){a.keyCode===13&&g.call(this)});a.bind(this.__input,"blur",g);a.bind(this.__selector,"mousedown",function(){a.addClass(this,"drag").bind(window,"mouseup",function(){a.removeClass(p.__selector,"drag")})});var t=document.createElement("div");f.extend(this.__selector.style,{width:"122px",height:"102px",padding:"3px",backgroundColor:"#222",boxShadow:"0px 1px 3px rgba(0,0,0,0.3)"}); 81 | f.extend(this.__field_knob.style,{position:"absolute",width:"12px",height:"12px",border:this.__field_knob_border+(this.__color.v<0.5?"#fff":"#000"),boxShadow:"0px 1px 3px rgba(0,0,0,0.5)",borderRadius:"12px",zIndex:1});f.extend(this.__hue_knob.style,{position:"absolute",width:"15px",height:"2px",borderRight:"4px solid #fff",zIndex:1});f.extend(this.__saturation_field.style,{width:"100px",height:"100px",border:"1px solid #555",marginRight:"3px",display:"inline-block",cursor:"pointer"});f.extend(t.style, 82 | {width:"100%",height:"100%",background:"none"});b(t,"top","rgba(0,0,0,0)","#000");f.extend(this.__hue_field.style,{width:"15px",height:"100px",display:"inline-block",border:"1px solid #555",cursor:"ns-resize"});n(this.__hue_field);f.extend(this.__input.style,{outline:"none",textAlign:"center",color:"#fff",border:0,fontWeight:"bold",textShadow:this.__input_textShadow+"rgba(0,0,0,0.7)"});a.bind(this.__saturation_field,"mousedown",o);a.bind(this.__field_knob,"mousedown",o);a.bind(this.__hue_field,"mousedown", 83 | function(b){s(b);a.bind(window,"mousemove",s);a.bind(window,"mouseup",i)});this.__saturation_field.appendChild(t);this.__selector.appendChild(this.__field_knob);this.__selector.appendChild(this.__saturation_field);this.__selector.appendChild(this.__hue_field);this.__hue_field.appendChild(this.__hue_knob);this.domElement.appendChild(this.__input);this.domElement.appendChild(this.__selector);this.updateDisplay()};h.superclass=e;f.extend(h.prototype,e.prototype,{updateDisplay:function(){var a=d(this.getValue()); 84 | if(a!==false){var e=false;f.each(c.COMPONENTS,function(b){if(!f.isUndefined(a[b])&&!f.isUndefined(this.__color.__state[b])&&a[b]!==this.__color.__state[b])return e=true,{}},this);e&&f.extend(this.__color.__state,a)}f.extend(this.__temp.__state,this.__color.__state);this.__temp.a=1;var h=this.__color.v<0.5||this.__color.s>0.5?255:0,j=255-h;f.extend(this.__field_knob.style,{marginLeft:100*this.__color.s-7+"px",marginTop:100*(1-this.__color.v)-7+"px",backgroundColor:this.__temp.toString(),border:this.__field_knob_border+ 85 | "rgb("+h+","+h+","+h+")"});this.__hue_knob.style.marginTop=(1-this.__color.h/360)*100+"px";this.__temp.s=1;this.__temp.v=1;b(this.__saturation_field,"left","#fff",this.__temp.toString());f.extend(this.__input.style,{backgroundColor:this.__input.value=this.__color.toString(),color:"rgb("+h+","+h+","+h+")",textShadow:this.__input_textShadow+"rgba("+j+","+j+","+j+",.7)"})}});var j=["-moz-","-o-","-webkit-","-ms-",""];return h}(dat.controllers.Controller,dat.dom.dom,dat.color.Color=function(e,a,c,d){function f(a, 86 | b,c){Object.defineProperty(a,b,{get:function(){if(this.__state.space==="RGB")return this.__state[b];n(this,b,c);return this.__state[b]},set:function(a){if(this.__state.space!=="RGB")n(this,b,c),this.__state.space="RGB";this.__state[b]=a}})}function b(a,b){Object.defineProperty(a,b,{get:function(){if(this.__state.space==="HSV")return this.__state[b];h(this);return this.__state[b]},set:function(a){if(this.__state.space!=="HSV")h(this),this.__state.space="HSV";this.__state[b]=a}})}function n(b,c,e){if(b.__state.space=== 87 | "HEX")b.__state[c]=a.component_from_hex(b.__state.hex,e);else if(b.__state.space==="HSV")d.extend(b.__state,a.hsv_to_rgb(b.__state.h,b.__state.s,b.__state.v));else throw"Corrupted color state";}function h(b){var c=a.rgb_to_hsv(b.r,b.g,b.b);d.extend(b.__state,{s:c.s,v:c.v});if(d.isNaN(c.h)){if(d.isUndefined(b.__state.h))b.__state.h=0}else b.__state.h=c.h}var j=function(){this.__state=e.apply(this,arguments);if(this.__state===false)throw"Failed to interpret color arguments";this.__state.a=this.__state.a|| 88 | 1};j.COMPONENTS="r,g,b,h,s,v,hex,a".split(",");d.extend(j.prototype,{toString:function(){return c(this)},toOriginal:function(){return this.__state.conversion.write(this)}});f(j.prototype,"r",2);f(j.prototype,"g",1);f(j.prototype,"b",0);b(j.prototype,"h");b(j.prototype,"s");b(j.prototype,"v");Object.defineProperty(j.prototype,"a",{get:function(){return this.__state.a},set:function(a){this.__state.a=a}});Object.defineProperty(j.prototype,"hex",{get:function(){if(!this.__state.space!=="HEX")this.__state.hex= 89 | a.rgb_to_hex(this.r,this.g,this.b);return this.__state.hex},set:function(a){this.__state.space="HEX";this.__state.hex=a}});return j}(dat.color.interpret,dat.color.math=function(){var e;return{hsv_to_rgb:function(a,c,d){var e=a/60-Math.floor(a/60),b=d*(1-c),n=d*(1-e*c),c=d*(1-(1-e)*c),a=[[d,c,b],[n,d,b],[b,d,c],[b,n,d],[c,b,d],[d,b,n]][Math.floor(a/60)%6];return{r:a[0]*255,g:a[1]*255,b:a[2]*255}},rgb_to_hsv:function(a,c,d){var e=Math.min(a,c,d),b=Math.max(a,c,d),e=b-e;if(b==0)return{h:NaN,s:0,v:0}; 90 | a=a==b?(c-d)/e:c==b?2+(d-a)/e:4+(a-c)/e;a/=6;a<0&&(a+=1);return{h:a*360,s:e/b,v:b/255}},rgb_to_hex:function(a,c,d){a=this.hex_with_component(0,2,a);a=this.hex_with_component(a,1,c);return a=this.hex_with_component(a,0,d)},component_from_hex:function(a,c){return a>>c*8&255},hex_with_component:function(a,c,d){return d<<(e=c*8)|a&~(255< 22 | * In order to keep a track of node links, an object must be able to identify itself with a uniqueId function. 23 | *

24 | * To add an item use: 25 | *


 26 |  *   list.add(newItem);
 27 |  * 
28 | *

29 | * You can iterate using the first and next members, such as: 30 | *


 31 |  *   var node = list.first;
 32 |  *   while (node)
 33 |  *   {
 34 |  *       node.object().DOSOMETHING();
 35 |  *       node = node.next();
 36 |  *   }
 37 |  * 
38 | */ 39 | LinkedList = function() { 40 | this.first = null; 41 | this.last = null; 42 | this.length = 0; 43 | this.objToNodeMap = {}; // a quick lookup list to map linked list nodes to objects 44 | this.uniqueId = Date.now() + '' + Math.floor(Math.random()*1000); 45 | 46 | /** 47 | * Get the LinkedListNode for this object. 48 | * @param obj The object to get the node for 49 | */ 50 | this.getNode = function (obj) { 51 | // objects added to a list must implement a getUniqueId which returns a unique object identifier string 52 | return this.objToNodeMap[obj.uniqueId]; 53 | }; 54 | 55 | /** 56 | * Adds a specific node to the list -- typically only used internally unless you're doing something funky 57 | * Use add() to add an object to the list, not this. 58 | */ 59 | this.addNode = function (obj) { 60 | var node = new LinkedListNode(); 61 | node.obj = obj; 62 | node.prev = null; 63 | node.next = null; 64 | node.free = false; 65 | this.objToNodeMap[obj.uniqueId] = node; 66 | return node; 67 | }; 68 | 69 | /** 70 | * Add an item to the list 71 | * @param obj The object to add 72 | */ 73 | this.add = function (obj) { 74 | var node = this.objToNodeMap[obj.uniqueId]; 75 | 76 | if (!node) { 77 | node = this.addNode(obj); 78 | } else { 79 | if (node.free === false) return; 80 | 81 | // reusing a node, so we clean it up 82 | // this caching of node/object pairs is the reason an object can only exist 83 | // once in a list -- which also makes things faster (not always creating new node 84 | // object every time objects are moving on and off the list 85 | node.obj = obj; 86 | node.free = false; 87 | node.next = null; 88 | node.prev = null; 89 | } 90 | 91 | // append this obj to the end of the list 92 | if (!this.first) { // is this the first? 93 | this.first = node; 94 | this.last = node; 95 | node.next = null; // clear just in case 96 | node.prev = null; 97 | } else { 98 | if (this.last == null) { 99 | throw new Error("Hmm, no last in the list -- that shouldn't happen here"); 100 | } 101 | 102 | // add this entry to the end of the list 103 | this.last.next = node; // current end of list points to the new end 104 | node.prev = this.last; 105 | this.last = node; // new object to add becomes last in the list 106 | node.next = null; // just in case this was previously set 107 | } 108 | this.length++; 109 | 110 | if (this.showDebug) this.dump('after add'); 111 | }; 112 | 113 | this.has = function (obj) { 114 | return !!this.objToNodeMap[obj.uniqueId]; 115 | }; 116 | 117 | /** 118 | * Moves this item upwards in the list 119 | * @param obj 120 | */ 121 | this.moveUp = function (obj) { 122 | this.dump('before move up'); 123 | var c = this.getNode(obj); 124 | if (!c) throw "Oops, trying to move an object that isn't in the list"; 125 | if (c.prev == null) return; // already first, ignore 126 | 127 | // This operation makes C swap places with B: 128 | // A <-> B <-> C <-> D 129 | // A <-> C <-> B <-> D 130 | 131 | var b = c.prev; 132 | var a = b.prev; 133 | 134 | // fix last 135 | if (c == this.last) this.last = b; 136 | 137 | var oldCNext = c.next; 138 | 139 | if (a) a.next = c; 140 | c.next = b; 141 | c.prev = b.prev; 142 | 143 | b.next = oldCNext; 144 | b.prev = c; 145 | 146 | // check to see if we are now first 147 | if (this.first == b) this.first = c; 148 | }; 149 | 150 | /** 151 | * Moves this item downwards in the list 152 | * @param obj 153 | */ 154 | this.moveDown = function (obj) { 155 | var b = this.getNode(obj); 156 | if (!b) throw "Oops, trying to move an object that isn't in the list"; 157 | if (b.next == null) return; // already last, ignore 158 | 159 | // This operation makes B swap places with C: 160 | // A <-> B <-> C <-> D 161 | // A <-> C <-> B <-> D 162 | 163 | var c = b.next; 164 | this.moveUp(c.obj); 165 | 166 | // check to see if we are now last 167 | if (this.last == c) this.last = b; 168 | }; 169 | 170 | /** 171 | * Take everything off the list and put it in an array, sort it, then put it back. 172 | */ 173 | this.sort = function (compare) { 174 | var sortArray = []; 175 | var i, l, node = this.first; 176 | 177 | while (node) { 178 | sortArray.push(node.object()); 179 | node = node.next(); 180 | } 181 | 182 | this.clear(); 183 | 184 | sortArray.sort(compare); 185 | 186 | l = sortArray.length; 187 | for (i = 0; i < l; i++) { 188 | this.add(sortArray[i]); 189 | } 190 | }; 191 | 192 | /** 193 | * Removes an item from the list 194 | * @param obj The object to remove 195 | * @returns boolean true if the item was removed, false if the item was not on the list 196 | */ 197 | this.remove = function (obj) { 198 | var node = this.getNode(obj); 199 | if (node == null || node.free == true){ 200 | return false; // ignore this error (trying to remove something not there) 201 | } 202 | 203 | // pull this object out and tie up the ends 204 | if (node.prev != null) 205 | node.prev.next = node.next; 206 | if (node.next != null) 207 | node.next.prev = node.prev; 208 | 209 | // fix first and last 210 | if (node.prev == null) // if this was first on the list 211 | this.first = node.next; // make the next on the list first (can be null) 212 | if (node.next == null) // if this was the last 213 | this.last = node.prev; // then this node's previous becomes last 214 | 215 | node.free = true; 216 | node.prev = null; 217 | node.next = null; 218 | 219 | this.length--; 220 | 221 | return true; 222 | }; 223 | 224 | // remove the head and return it's object 225 | this.shift = function() { 226 | var node = this.first; 227 | if (this.length === 0) return null; 228 | // if (node == null || node.free == true) return null; 229 | 230 | // pull this object out and tie up the ends 231 | if (node.prev) { 232 | node.prev.next = node.next; 233 | } 234 | if (node.next) { 235 | node.next.prev = node.prev; 236 | } 237 | 238 | // make the next on the list first (can be null) 239 | this.first = node.next; 240 | if (!node.next) this.last = null; // make sure we clear this 241 | 242 | node.free = true; 243 | node.prev = null; 244 | node.next = null; 245 | 246 | this.length--; 247 | return node.obj; 248 | }; 249 | 250 | // remove the tail and return it's object 251 | this.pop = function() { 252 | var node = this.last; 253 | if (this.length === 0) return null; 254 | 255 | // pull this object out and tie up the ends 256 | if (node.prev) { 257 | node.prev.next = node.next; 258 | } 259 | if (node.next) { 260 | node.next.prev = node.prev; 261 | } 262 | 263 | // this node's previous becomes last 264 | this.last = node.prev; 265 | if (!node.prev) this.first = null; // make sure we clear this 266 | 267 | node.free = true; 268 | node.prev = null; 269 | node.next = null; 270 | 271 | this.length--; 272 | return node.obj; 273 | }; 274 | 275 | /** 276 | * Clears the list out 277 | */ 278 | this.clear = function() { 279 | var next = this.first; 280 | 281 | while (next) { 282 | next.free = true; 283 | next = next.next; 284 | } 285 | 286 | this.first = null; 287 | this.length = 0; 288 | }; 289 | 290 | this.destroy = function() { 291 | var next = this.first; 292 | 293 | while (next) { 294 | next.obj = null; 295 | next = next.next; 296 | } 297 | this.first = null; 298 | 299 | this.objToNodeMap = null; 300 | }; 301 | 302 | /** 303 | * Outputs the contents of the current list for debugging. 304 | */ 305 | this.dump = function(msg) { 306 | console.log('====================' + msg + '====================='); 307 | var a = this.first; 308 | while (a != null) { 309 | console.log("{" + a.obj.toString() + "} previous=" + ( a.prev ? a.prev.obj : "NULL")); 310 | a = a.next(); 311 | } 312 | console.log("==================================="); 313 | console.log("Last: {" + (this.last ? this.last.obj : 'NULL') + "} " + 314 | "First: {" + (this.first ? this.first.obj : 'NULL') + "}"); 315 | }; 316 | }; 317 | -------------------------------------------------------------------------------- /scripts/lib/pixi.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Pixi.JS - v1.2.0 4 | * Copyright (c) 2012, Mat Groves 5 | * http://goodboydigital.com/ 6 | * 7 | * Compiled: 2013-06-19 8 | * 9 | * Pixi.JS is licensed under the MIT License. 10 | * http://www.opensource.org/licenses/mit-license.php 11 | */ 12 | !function(){function c(a){return[(255&a>>16)/255,(255&a>>8)/255,(255&a)/255]}function d(){return f.Matrix="undefined"!=typeof Float32Array?Float32Array:Array,f.Matrix}var e=this,f=f||{};f.Point=function(a,b){this.x=a||0,this.y=b||0},f.Point.prototype.clone=function(){return new f.Point(this.x,this.y)},f.Point.constructor=f.Point,f.Rectangle=function(a,b,c,d){this.x=a||0,this.y=b||0,this.width=c||0,this.height=d||0},f.Rectangle.prototype.clone=function(){return new f.Rectangle(this.x,this.y,this.width,this.height)},f.Rectangle.constructor=f.Rectangle,f.Polygon=function(a){this.points=a},f.Polygon.clone=function(){for(var a=[],b=0;b=0&&b<=this.children.length))throw new Error(a+" The index "+b+" supplied is out of bounds "+this.children.length);void 0!=a.parent&&a.parent.removeChild(a),b==this.children.length?this.children.push(a):this.children.splice(b,0,a),a.parent=this,a.childIndex=b;for(var c=this.children.length,d=b;c>d;d++)this.children[d].childIndex=d;this.stage&&this.stage.__addChild(a),this.__renderGroup&&(a.__renderGroup&&a.__renderGroup.removeDisplayObjectAndChildren(a),this.__renderGroup.addDisplayObjectAndChildren(a))},f.DisplayObjectContainer.prototype.swapChildren=function(a,b){var c=this.children.indexOf(a),d=this.children.indexOf(b);if(-1===c||-1===d)throw new Error(a+" Both the supplied DisplayObjects must be a child of the caller "+this);this.stage&&(this.stage.__removeChild(a),this.stage.__removeChild(b),this.stage.__addChild(a),this.stage.__addChild(b)),a.childIndex=d,b.childIndex=c,this.children[c]=b,this.children[d]=a},f.DisplayObjectContainer.prototype.getChildAt=function(a){if(a>=0&&ac;c++)this.children[c].childIndex-=1},f.DisplayObjectContainer.prototype.updateTransform=function(){if(this.visible){f.DisplayObject.prototype.updateTransform.call(this);for(var a=0,b=this.children.length;b>a;a++)this.children[a].updateTransform()}},f.blendModes={},f.blendModes.NORMAL=0,f.blendModes.SCREEN=1,f.Sprite=function(a){f.DisplayObjectContainer.call(this),this.anchor=new f.Point,this.texture=a,this.blendMode=f.blendModes.NORMAL,this._width=0,this._height=0,a.baseTexture.hasLoaded?this.updateFrame=!0:(this.onTextureUpdateBind=this.onTextureUpdate.bind(this),this.texture.addEventListener("update",this.onTextureUpdateBind)),this.renderable=!0},f.Sprite.constructor=f.Sprite,f.Sprite.prototype=Object.create(f.DisplayObjectContainer.prototype),Object.defineProperty(f.Sprite.prototype,"width",{get:function(){return this.scale.x*this.texture.frame.width},set:function(a){this.scale.x=a/this.texture.frame.width,this._width=a}}),Object.defineProperty(f.Sprite.prototype,"height",{get:function(){return this.scale.y*this.texture.frame.height},set:function(a){this.scale.y=a/this.texture.frame.height,this._height=a}}),f.Sprite.prototype.setTexture=function(a){this.texture.baseTexture!=a.baseTexture&&(this.textureChange=!0),this.texture=a,this.updateFrame=!0},f.Sprite.prototype.onTextureUpdate=function(){this._width&&(this.scale.x=this._width/this.texture.frame.width),this._height&&(this.scale.y=this._height/this.texture.frame.height),this.updateFrame=!0},f.Sprite.fromFrame=function(a){var b=f.TextureCache[a];if(!b)throw new Error("The frameId '"+a+"' does not exist in the texture cache"+this);return new f.Sprite(b)},f.Sprite.fromImage=function(a){var b=f.Texture.fromImage(a);return new f.Sprite(b)},f.MovieClip=function(a){f.Sprite.call(this,a[0]),this.textures=a,this.currentFrame=0,this.animationSpeed=1,this.loop=!0,this.onComplete=null,this.playing},f.MovieClip.constructor=f.MovieClip,f.MovieClip.prototype=Object.create(f.Sprite.prototype),f.MovieClip.prototype.stop=function(){this.playing=!1},f.MovieClip.prototype.play=function(){this.playing=!0},f.MovieClip.prototype.gotoAndStop=function(a){this.playing=!1,this.currentFrame=a;var b=0|this.currentFrame+.5;this.setTexture(this.textures[b%this.textures.length])},f.MovieClip.prototype.gotoAndPlay=function(a){this.currentFrame=a,this.playing=!0},f.MovieClip.prototype.updateTransform=function(){if(f.Sprite.prototype.updateTransform.call(this),this.playing){this.currentFrame+=this.animationSpeed;var a=0|this.currentFrame+.5;this.loop||a=this.textures.length&&(this.gotoAndStop(this.textures.length-1),this.onComplete&&this.onComplete())}},f.Text=function(a,b){this.canvas=document.createElement("canvas"),this.context=this.canvas.getContext("2d"),f.Sprite.call(this,f.Texture.fromCanvas(this.canvas)),this.setText(a),this.setStyle(b),this.updateText(),this.dirty=!1},f.Text.constructor=f.Text,f.Text.prototype=Object.create(f.Sprite.prototype),f.Text.prototype.setStyle=function(a){a=a||{},a.font=a.font||"bold 20pt Arial",a.fill=a.fill||"black",a.align=a.align||"left",a.stroke=a.stroke||"black",a.strokeThickness=a.strokeThickness||0,a.wordWrap=a.wordWrap||!1,a.wordWrapWidth=a.wordWrapWidth||100,this.style=a,this.dirty=!0},f.Sprite.prototype.setText=function(a){this.text=a.toString()||" ",this.dirty=!0},f.Text.prototype.updateText=function(){this.context.font=this.style.font;var a=this.text;this.style.wordWrap&&(a=this.wordWrap(this.text));for(var b=a.split(/(?:\r\n|\r|\n)/),c=[],d=0,e=0;ee?f:arguments.callee(a,b,f,d,e):arguments.callee(a,b,c,f,e)},c=function(a,c,d){if(a.measureText(c).width<=d||c.length<1)return c;var e=b(a,c,0,c.length,d);return c.substring(0,e)+"\n"+arguments.callee(a,c.substring(e),d)},d="",e=a.split("\n"),f=0;f=2?parseInt(b[b.length-2],10):f.BitmapText.fonts[this.fontName].size,this.dirty=!0},f.BitmapText.prototype.updateText=function(){for(var a=f.BitmapText.fonts[this.fontName],b=new f.Point,c=null,d=[],e=0,g=[],h=0,i=this.fontSize/a.size,j=0;j=j;j++){var n=0;"right"==this.style.align?n=e-g[j]:"center"==this.style.align&&(n=(e-g[j])/2),m.push(n)}for(j=0;j0;)this.removeChild(this.getChildAt(0));this.updateText(),this.dirty=!1}f.DisplayObjectContainer.prototype.updateTransform.call(this)},f.BitmapText.fonts={},f.InteractionManager=function(a){this.stage=a,this.tempPoint=new f.Point,this.mouseoverEnabled=!0,this.mouse=new f.InteractionData,this.touchs={},this.pool=[],this.interactiveItems=[],this.last=0},f.InteractionManager.constructor=f.InteractionManager,f.InteractionManager.prototype.collectInteractiveSprite=function(a,b){for(var c=a.children,d=c.length,e=d-1;e>=0;e--){var f=c[e];f.visible&&(f.interactive?(b.interactiveChildren=!0,this.interactiveItems.push(f),f.children.length>0&&this.collectInteractiveSprite(f,f)):(f.__iParent=null,f.children.length>0&&this.collectInteractiveSprite(f,b)))}},f.InteractionManager.prototype.setTarget=function(a){window.navigator.msPointerEnabled&&(a.view.style["-ms-content-zooming"]="none",a.view.style["-ms-touch-action"]="none"),this.target=a,a.view.addEventListener("mousemove",this.onMouseMove.bind(this),!0),a.view.addEventListener("mousedown",this.onMouseDown.bind(this),!0),document.body.addEventListener("mouseup",this.onMouseUp.bind(this),!0),a.view.addEventListener("mouseout",this.onMouseUp.bind(this),!0),a.view.addEventListener("touchstart",this.onTouchStart.bind(this),!0),a.view.addEventListener("touchend",this.onTouchEnd.bind(this),!0),a.view.addEventListener("touchmove",this.onTouchMove.bind(this),!0)},f.InteractionManager.prototype.update=function(){if(this.target){var a=Date.now(),b=a-this.last;if(b=30*b/1e3,!(1>b)){if(this.last=a,this.dirty){this.dirty=!1,this.interactiveItems.length;for(var c=0;cc;c++){var e=this.interactiveItems[c];e.visible&&(e.mouseover||e.mouseout||e.buttonMode)&&(e.__hit=this.hitTest(e,this.mouse),e.__hit?(e.buttonMode&&(this.target.view.style.cursor="pointer"),e.__isOver||(e.mouseover&&e.mouseover(this.mouse),e.__isOver=!0)):e.__isOver&&(e.mouseout&&e.mouseout(this.mouse),e.__isOver=!1))}}}},f.InteractionManager.prototype.onMouseMove=function(a){var b=this.target.view.getBoundingClientRect();this.mouse.global.x=(a.clientX-b.left)*(this.target.width/b.width),this.mouse.global.y=(a.clientY-b.top)*(this.target.height/b.height);var c=this.interactiveItems.length;this.mouse.global;for(var d=0;c>d;d++){var e=this.interactiveItems[d];e.mousemove&&e.mousemove(this.mouse)}},f.InteractionManager.prototype.onMouseDown=function(a){a.preventDefault();var b=this.interactiveItems.length;this.mouse.global,this.stage;for(var c=0;b>c;c++){var d=this.interactiveItems[c];if((d.mousedown||d.click)&&(d.__mouseIsDown=!0,d.__hit=this.hitTest(d,this.mouse),d.__hit&&(d.mousedown&&d.mousedown(this.mouse),d.__isDown=!0,!d.interactiveChildren)))break}},f.InteractionManager.prototype.onMouseUp=function(){this.mouse.global;for(var a=this.interactiveItems.length,b=!1,c=0;a>c;c++){var d=this.interactiveItems[c];(d.mouseup||d.mouseupoutside||d.click)&&(d.__hit=this.hitTest(d,this.mouse),d.__hit&&!b?(d.mouseup&&d.mouseup(this.mouse),d.__isDown&&d.click&&d.click(this.mouse),d.interactiveChildren||(b=!0)):d.__isDown&&d.mouseupoutside&&d.mouseupoutside(this.mouse),d.__isDown=!1)}},f.InteractionManager.prototype.hitTest=function(a,b){var c=b.global;if(!a.visible)return!1;var d=a instanceof f.Sprite,e=a.worldTransform,g=e[0],h=e[1],i=e[2],j=e[3],k=e[4],l=e[5],m=1/(g*k+h*-j),n=k*m*c.x+-h*m*c.y+(l*h-i*k)*m,o=g*m*c.y+-j*m*c.x+(-l*g+i*j)*m;if(a.hitArea){var p=a.hitArea;if(a.hitArea instanceof f.Polygon){for(var q=!1,r=0,s=a.hitArea.points.length-1;ro!=w>o&&(v-t)*(o-u)/(w-u)+t>n;x&&(q=!q)}if(q)return d&&(b.target=a),!0}else{var y=p.x;if(n>y&&nz&&oy&&y+A>n&&(z=-B*a.anchor.y,o>z&&z+B>o))return b.target=a,!0}for(var C=a.children.length,r=0;C>r;r++){var D=a.children[r],E=this.hitTest(D,b);if(E)return!0}return!1},f.InteractionManager.prototype.onTouchMove=function(a){for(var b=this.target.view.getBoundingClientRect(),c=a.changedTouches,d=0;dd;d++){var h=this.interactiveItems[d];h.touchmove&&h.touchmove(f)}},f.InteractionManager.prototype.onTouchStart=function(a){a.preventDefault();for(var b=this.target.view.getBoundingClientRect(),c=a.changedTouches,d=0;di;i++){var j=this.interactiveItems[i];if((j.touchstart||j.tap)&&(j.__hit=this.hitTest(j,g),j.__hit&&(j.touchstart&&j.touchstart(g),j.__isDown=!0,j.__touchData=g,!j.interactiveChildren)))break}}},f.InteractionManager.prototype.onTouchEnd=function(a){for(var b=this.target.view.getBoundingClientRect(),c=a.changedTouches,d=0;di;i++){var j=this.interactiveItems[i],k=j.__touchData;j.__hit=this.hitTest(j,f),k==f&&((j.touchend||j.tap)&&(j.__hit&&!g?(j.touchend&&j.touchend(f),j.__isDown&&j.tap&&j.tap(f),j.interactiveChildren||(g=!0)):j.__isDown&&j.touchendoutside&&j.touchendoutside(f),j.__isDown=!1),j.__touchData=null)}this.pool.push(f),this.touchs[e.identifier]=null}},f.InteractionData=function(){this.global=new f.Point,this.local=new f.Point,this.target},f.InteractionData.prototype.getLocalPosition=function(a){var b=a.worldTransform,c=this.global,d=b[0],e=b[1],g=b[2],h=b[3],i=b[4],j=b[5],k=1/(d*i+e*-h);return new f.Point(i*k*c.x+-e*k*c.y+(j*e-g*i)*k,d*k*c.y+-h*k*c.x+(-j*d+g*h)*k)},f.InteractionData.constructor=f.InteractionData,f.Stage=function(a,b){f.DisplayObjectContainer.call(this),this.worldTransform=f.mat3.create(),this.__childrenAdded=[],this.__childrenRemoved=[],this.childIndex=0,this.stage=this,this.stage.hitArea=new f.Rectangle(0,0,1e5,1e5),this.interactive=!!b,this.interactionManager=new f.InteractionManager(this),this.setBackgroundColor(a),this.worldVisible=!0,this.stage.dirty=!0},f.Stage.constructor=f.Stage,f.Stage.prototype=Object.create(f.DisplayObjectContainer.prototype),f.Stage.prototype.updateTransform=function(){this.worldAlpha=1;for(var a=0,b=this.children.length;b>a;a++)this.children[a].updateTransform();this.dirty&&(this.dirty=!1,this.interactionManager.dirty=!0),this.interactive&&this.interactionManager.update()},f.Stage.prototype.setBackgroundColor=function(a){this.backgroundColor=a||0,this.backgroundColorSplit=c(this.backgroundColor);var b=this.backgroundColor.toString(16);b="000000".substr(0,6-b.length)+b,this.backgroundColorString="#"+b},f.Stage.prototype.getMousePosition=function(){return this.interactionManager.mouse.global},f.Stage.prototype.__addChild=function(a){if(a.interactive&&(this.dirty=!0),a.stage=this,a.children)for(var b=0;bb;b++)this.__removeChild(a.children[b])};for(var h=0,i=["ms","moz","webkit","o"],j=0;j0){for(var c=0;cc;c++){var d=6*c,e=4*c;this.indices[d+0]=e+0,this.indices[d+1]=e+1,this.indices[d+2]=e+2,this.indices[d+3]=e+0,this.indices[d+4]=e+2,this.indices[d+5]=e+3}a.bindBuffer(a.ELEMENT_ARRAY_BUFFER,this.indexBuffer),a.bufferData(a.ELEMENT_ARRAY_BUFFER,this.indices,a.STATIC_DRAW) 13 | },f.WebGLBatch.prototype.refresh=function(){this.gl,this.dynamicSize0;)n=n.children[n.children.length-1],n.renderable&&(m=n);if(m instanceof f.Sprite){l=m.batch;var k=l.head;if(k==m)g=0;else for(g=1;k.__next!=m;)g++,k=k.__next}else l=m;if(j==l)return j instanceof f.WebGLBatch?j.render(d,g+1):j instanceof f.TilingSprite?j.visible&&this.renderTilingSprite(j,b):j instanceof f.Strip?j.visible&&this.renderStrip(j,b):j instanceof f.CustomRenderable&&j.visible&&j.renderWebGL(this,b),void 0;e=this.batchs.indexOf(j),h=this.batchs.indexOf(l),j instanceof f.WebGLBatch?j.render(d):j instanceof f.TilingSprite?j.visible&&this.renderTilingSprite(j,b):j instanceof f.Strip?j.visible&&this.renderStrip(j,b):j instanceof f.CustomRenderable&&j.visible&&j.renderWebGL(this,b);for(var o=e+1;h>o;o++)renderable=this.batchs[o],renderable instanceof f.WebGLBatch?this.batchs[o].render():renderable instanceof f.TilingSprite?renderable.visible&&this.renderTilingSprite(renderable,b):renderable instanceof f.Strip?renderable.visible&&this.renderStrip(renderable,b):renderable instanceof f.CustomRenderable&&renderable.visible&&renderable.renderWebGL(this,b);l instanceof f.WebGLBatch?l.render(0,g+1):l instanceof f.TilingSprite?l.visible&&this.renderTilingSprite(l):l instanceof f.Strip?l.visible&&this.renderStrip(l):l instanceof f.CustomRenderable&&l.visible&&l.renderWebGL(this,b)},f.WebGLRenderGroup.prototype.checkVisibility=function(a,b){for(var c=a.children,d=0;d0&&this.checkVisibility(e,e.worldVisible)}},f.WebGLRenderGroup.prototype.updateTexture=function(a){if(1==a.batch.length)return a.batch.texture=a.texture.baseTexture,void 0;if(a.batch.texture!=a.texture.baseTexture)if(a.batch.head==a){var b=a.batch,c=this.batchs.indexOf(b),d=this.batchs[c-1];if(b.remove(a),d)if(d.texture==a.texture.baseTexture&&d.blendMode==a.blendMode)d.insertAfter(a,d.tail);else{var e=f.WebGLRenderer.getBatch();e.init(a),this.batchs.splice(c-1,0,e)}else{var e=f.WebGLRenderer.getBatch();e.init(a),this.batchs.splice(0,0,e)}}else if(a.batch.tail==a){var b=a.batch,c=this.batchs.indexOf(b),g=this.batchs[c+1];if(b.remove(a),g){if(g.texture==a.texture.baseTexture&&g.blendMode==a.blendMode)return g.insertBefore(a,g.head),void 0;var e=f.WebGLRenderer.getBatch();e.init(a),this.batchs.splice(c+1,0,e)}else{var e=f.WebGLRenderer.getBatch();e.init(a),this.batchs.push(e)}}else{var b=a.batch,h=b.split(a);h.remove(a);var e=f.WebGLRenderer.getBatch(),c=this.batchs.indexOf(b);e.init(a),this.batchs.splice(c+1,0,e,h)}},f.WebGLRenderGroup.prototype.addDisplayObject=function(a){if(a.__renderGroup&&a.__renderGroup.removeDisplayObjectAndChildren(a),a.__renderGroup=this,a.renderable){var b=this.getPreviousRenderable(a),c=this.getNextRenderable(a);if(a instanceof f.Sprite){var d,e;if(b instanceof f.Sprite){if(d=b.batch,d&&d.texture==a.texture.baseTexture&&d.blendMode==a.blendMode)return d.insertAfter(a,b),void 0}else d=b;if(c)if(c instanceof f.Sprite){if(e=c.batch){if(e.texture==a.texture.baseTexture&&e.blendMode==a.blendMode)return e.insertBefore(a,c),void 0;if(e==d){var g=d.split(c),h=f.WebGLRenderer.getBatch(),i=this.batchs.indexOf(d);return h.init(a),this.batchs.splice(i+1,0,h,g),void 0}}}else e=c;var h=f.WebGLRenderer.getBatch();if(h.init(a),d){var i=this.batchs.indexOf(d);this.batchs.splice(i+1,0,h)}else this.batchs.push(h)}else a instanceof f.TilingSprite?(this.initTilingSprite(a),this.batchs.push(a)):a instanceof f.Strip&&(this.initStrip(a),this.batchs.push(a));this.batchUpdate=!0}},f.WebGLRenderGroup.prototype.addDisplayObjectAndChildren=function(a){this.addDisplayObject(a);for(var b=a.children,c=0;c0&&(f.Texture.frameUpdates=[])},f.CanvasRenderer.prototype.resize=function(a,b){this.width=a,this.height=b,this.view.width=a,this.view.height=b},f.CanvasRenderer.prototype.renderDisplayObject=function(a){var b=a.worldTransform,c=this.context;if(a.visible){if(a instanceof f.Sprite){var d=a.texture.frame;d&&(c.globalAlpha=a.worldAlpha,c.setTransform(b[0],b[3],b[1],b[4],b[2],b[5]),c.drawImage(a.texture.baseTexture.source,d.x,d.y,d.width,d.height,a.anchor.x*-d.width,a.anchor.y*-d.height,d.width,d.height))}else a instanceof f.Strip?(c.setTransform(b[0],b[3],b[1],b[4],b[2],b[5]),this.renderStrip(a)):a instanceof f.TilingSprite?(c.setTransform(b[0],b[3],b[1],b[4],b[2],b[5]),this.renderTilingSprite(a)):a instanceof f.CustomRenderable&&a.renderCanvas(this);if(a.children)for(var e=0;ee;e++){var f=2*e,g=c[f],h=c[f+2],i=c[f+4],j=c[f+1],k=c[f+3],l=c[f+5];b.moveTo(g,j),b.lineTo(h,k),b.lineTo(i,l)}b.fillStyle="#FF0000",b.fill(),b.closePath()},f.CanvasRenderer.prototype.renderTilingSprite=function(a){var b=this.context;a.__tilePattern||(a.__tilePattern=b.createPattern(a.texture.baseTexture.source,"repeat")),b.beginPath();var c=a.tilePosition,d=a.tileScale;b.scale(d.x,d.y),b.translate(c.x,c.y),b.fillStyle=a.__tilePattern,b.fillRect(-c.x,-c.y,a.width/d.x,a.height/d.y),b.scale(1/d.x,1/d.y),b.translate(-c.x,-c.y),b.closePath()},f.CanvasRenderer.prototype.renderStrip=function(a){var b=this.context,c=a.verticies,d=a.uvs,e=c.length/2;this.count++;for(var f=1;e-2>f;f++){var g=2*f,h=c[g],i=c[g+2],j=c[g+4],k=c[g+1],l=c[g+3],m=c[g+5],n=d[g]*a.texture.width,o=d[g+2]*a.texture.width,p=d[g+4]*a.texture.width,q=d[g+1]*a.texture.height,r=d[g+3]*a.texture.height,s=d[g+5]*a.texture.height;b.save(),b.beginPath(),b.moveTo(h,k),b.lineTo(i,l),b.lineTo(j,m),b.closePath(),b.clip();var t=n*r+q*p+o*s-r*p-q*o-n*s,u=h*r+q*j+i*s-r*j-q*i-h*s,v=n*i+h*p+o*j-i*p-h*o-n*j,w=n*r*j+q*i*p+h*o*s-h*r*p-q*o*j-n*i*s,x=k*r+q*m+l*s-r*m-q*l-k*s,y=n*l+k*p+o*m-l*p-k*o-n*m,z=n*r*m+q*l*p+k*o*s-k*r*p-q*o*m-n*l*s;b.transform(u/t,x/t,v/t,y/t,w/t,z/t),b.drawImage(a.texture.baseTexture.source,0,0),b.restore()}},f.Strip=function(a,b,c){f.DisplayObjectContainer.call(this),this.texture=a,this.blendMode=f.blendModes.NORMAL;try{this.uvs=new Float32Array([0,1,1,1,1,0,0,1]),this.verticies=new Float32Array([0,0,0,0,0,0,0,0,0]),this.colors=new Float32Array([1,1,1,1]),this.indices=new Uint16Array([0,1,2,3])}catch(d){this.uvs=[0,1,1,1,1,0,0,1],this.verticies=[0,0,0,0,0,0,0,0,0],this.colors=[1,1,1,1],this.indices=[0,1,2,3]}this.width=b,this.height=c,a.baseTexture.hasLoaded?(this.width=this.texture.frame.width,this.height=this.texture.frame.height,this.updateFrame=!0):(this.onTextureUpdateBind=this.onTextureUpdate.bind(this),this.texture.addEventListener("update",this.onTextureUpdateBind)),this.renderable=!0},f.Strip.constructor=f.Strip,f.Strip.prototype=Object.create(f.DisplayObjectContainer.prototype),f.Strip.prototype.setTexture=function(a){this.texture=a,this.width=a.frame.width,this.height=a.frame.height,this.updateFrame=!0},f.Strip.prototype.onTextureUpdate=function(){this.updateFrame=!0},f.Rope=function(a,b){f.Strip.call(this,a),this.points=b;try{this.verticies=new Float32Array(4*b.length),this.uvs=new Float32Array(4*b.length),this.colors=new Float32Array(2*b.length),this.indices=new Uint16Array(2*b.length)}catch(c){this.verticies=verticies,this.uvs=uvs,this.colors=colors,this.indices=indices}this.refresh()},f.Rope.constructor=f.Rope,f.Rope.prototype=Object.create(f.Strip.prototype),f.Rope.prototype.refresh=function(){var a=this.points;if(!(a.length<1)){var b=this.uvs,c=this.indices,d=this.colors,e=a[0],f=a[0];this.count-=.2,b[0]=0,b[1]=1,b[2]=0,b[3]=1,d[0]=1,d[1]=1,c[0]=0,c[1]=1;for(var g=a.length,h=1;g>h;h++){var f=a[h],i=4*h,j=h/(g-1);h%2?(b[i]=j,b[i+1]=0,b[i+2]=j,b[i+3]=1):(b[i]=j,b[i+1]=0,b[i+2]=j,b[i+3]=1),i=2*h,d[i]=1,d[i+1]=1,i=2*h,c[i]=i,c[i+1]=i+1,e=f}}},f.Rope.prototype.updateTransform=function(){var a=this.points;if(!(a.length<1)){var b,c=this.verticies,d=a[0],e={x:0,y:0},g=a[0];this.count-=.2,c[0]=g.x+e.x,c[1]=g.y+e.y,c[2]=g.x-e.x,c[3]=g.y-e.y;for(var h=a.length,i=1;h>i;i++){var g=a[i],j=4*i;b=i1&&(k=1);var l=Math.sqrt(e.x*e.x+e.y*e.y),m=this.texture.height/2;e.x/=l,e.y/=l,e.x*=m,e.y*=m,c[j]=g.x+e.x,c[j+1]=g.y+e.y,c[j+2]=g.x-e.x,c[j+3]=g.y-e.y,d=g}f.DisplayObjectContainer.prototype.updateTransform.call(this)}},f.Rope.prototype.setTexture=function(a){this.texture=a,this.updateFrame=!0},f.TilingSprite=function(a,b,c){f.DisplayObjectContainer.call(this),this.texture=a,this.width=b,this.height=c,this.renderable=!0,this.tileScale=new f.Point(1,1),this.tilePosition=new f.Point(0,0),this.blendMode=f.blendModes.NORMAL},f.TilingSprite.constructor=f.TilingSprite,f.TilingSprite.prototype=Object.create(f.DisplayObjectContainer.prototype),f.TilingSprite.prototype.setTexture=function(a){this.texture=a,this.updateFrame=!0},f.TilingSprite.prototype.onTextureUpdate=function(){this.updateFrame=!0},f.Spine=function(a){if(f.DisplayObjectContainer.call(this),this.spineData=f.AnimCache[a],!this.spineData)throw new Error("Spine data must be preloaded using PIXI.SpineLoader or PIXI.AssetLoader: "+a);this.count=0,this.sprites=[],this.skeleton=new l.Skeleton(this.spineData),this.skeleton.updateWorldTransform(),this.stateData=new l.AnimationStateData(this.spineData),this.state=new l.AnimationState(this.stateData);for(var b=0;b>1),d+=-(b.attachment.height*(b.bone.worldScaleY+b.attachment.scaleY-1)>>1),this.sprites[a].position.x=c,this.sprites[a].position.y=d,this.sprites[a].rotation=-(b.bone.worldRotation+b.attachment.rotation)*(Math.PI/180)}f.DisplayObjectContainer.prototype.updateTransform.call(this)};var l={};l.BoneData=function(a,b){this.name=a,this.parent=b},l.BoneData.prototype={length:0,x:0,y:0,rotation:0,scaleX:1,scaleY:1},l.SlotData=function(a,b){this.name=a,this.boneData=b},l.SlotData.prototype={r:1,g:1,b:1,a:1,attachmentName:null},l.Bone=function(a,b){this.data=a,this.parent=b,this.setToSetupPose()},l.Bone.yDown=!1,l.Bone.prototype={x:0,y:0,rotation:0,scaleX:1,scaleY:1,m00:0,m01:0,worldX:0,m10:0,m11:0,worldY:0,worldRotation:0,worldScaleX:1,worldScaleY:1,updateWorldTransform:function(a,b){var c=this.parent;null!=c?(this.worldX=this.x*c.m00+this.y*c.m01+c.worldX,this.worldY=this.x*c.m10+this.y*c.m11+c.worldY,this.worldScaleX=c.worldScaleX*this.scaleX,this.worldScaleY=c.worldScaleY*this.scaleY,this.worldRotation=c.worldRotation+this.rotation):(this.worldX=this.x,this.worldY=this.y,this.worldScaleX=this.scaleX,this.worldScaleY=this.scaleY,this.worldRotation=this.rotation);var d=this.worldRotation*Math.PI/180,e=Math.cos(d),f=Math.sin(d);this.m00=e*this.worldScaleX,this.m10=f*this.worldScaleX,this.m01=-f*this.worldScaleY,this.m11=e*this.worldScaleY,a&&(this.m00=-this.m00,this.m01=-this.m01),b&&(this.m10=-this.m10,this.m11=-this.m11),l.Bone.yDown&&(this.m10=-this.m10,this.m11=-this.m11)},setToSetupPose:function(){var a=this.data;this.x=a.x,this.y=a.y,this.rotation=a.rotation,this.scaleX=a.scaleX,this.scaleY=a.scaleY}},l.Slot=function(a,b,c){this.data=a,this.skeleton=b,this.bone=c,this.setToSetupPose()},l.Slot.prototype={r:1,g:1,b:1,a:1,_attachmentTime:0,attachment:null,setAttachment:function(a){this.attachment=a,this._attachmentTime=this.skeleton.time},setAttachmentTime:function(a){this._attachmentTime=this.skeleton.time-a},getAttachmentTime:function(){return this.skeleton.time-this._attachmentTime},setToSetupPose:function(){var a=this.data;this.r=a.r,this.g=a.g,this.b=a.b,this.a=a.a;for(var b=this.skeleton.data.slots,c=0,d=b.length;d>c;c++)if(b[c]==a){this.setAttachment(a.attachmentName?this.skeleton.getAttachmentBySlotIndex(c,a.attachmentName):null);break}}},l.Skin=function(a){this.name=a,this.attachments={}},l.Skin.prototype={addAttachment:function(a,b,c){this.attachments[a+":"+b]=c},getAttachment:function(a,b){return this.attachments[a+":"+b]},_attachAll:function(a,b){for(var c in b.attachments){var d=c.indexOf(":"),e=parseInt(c.substring(0,d)),f=c.substring(d+1),g=a.slots[e];if(g.attachment&&g.attachment.name==f){var h=this.getAttachment(e,f);h&&g.setAttachment(h)}}}},l.Animation=function(a,b,c){this.name=a,this.timelines=b,this.duration=c},l.Animation.prototype={apply:function(a,b,c){c&&0!=this.duration&&(b%=this.duration);for(var d=this.timelines,e=0,f=d.length;f>e;e++)d[e].apply(a,b,1)},mix:function(a,b,c,d){c&&0!=this.duration&&(b%=this.duration);for(var e=this.timelines,f=0,g=e.length;g>f;f++)e[f].apply(a,b,d)}},l.binarySearch=function(a,b,c){var d=0,e=Math.floor(a.length/c)-2;if(0==e)return c;for(var f=e>>>1;;){if(a[(f+1)*c]<=b?d=f+1:e=f,d==e)return(d+1)*c;f=d+e>>>1}},l.linearSearch=function(a,b,c){for(var d=0,e=a.length-c;e>=d;d+=c)if(a[d]>b)return d;return-1},l.Curves=function(a){this.curves=[],this.curves.length=6*(a-1)},l.Curves.prototype={setLinear:function(a){this.curves[6*a]=0},setStepped:function(a){this.curves[6*a]=-1},setCurve:function(a,b,c,d,e){var f=.1,g=f*f,h=g*f,i=3*f,j=3*g,k=6*g,l=6*h,m=2*-b+d,n=2*-c+e,o=3*(b-d)+1,p=3*(c-e)+1,q=6*a,r=this.curves;r[q]=b*i+m*j+o*h,r[q+1]=c*i+n*j+p*h,r[q+2]=m*k+o*l,r[q+3]=n*k+p*l,r[q+4]=o*l,r[q+5]=p*l},getCurvePercent:function(a,b){b=0>b?0:b>1?1:b;var c=6*a,d=this.curves,e=d[c];if(!e)return b;if(-1==e)return 0;for(var f=d[c+1],g=d[c+2],h=d[c+3],i=d[c+4],j=d[c+5],k=e,l=f,m=8;;){if(k>=b){var n=k-e,o=l-f;return o+(l-o)*(b-n)/(k-n)}if(0==m)break;m--,e+=g,f+=h,g+=i,h+=j,k+=e,l+=f}return l+(1-l)*(b-k)/(1-k)}},l.RotateTimeline=function(a){this.curves=new l.Curves(a),this.frames=[],this.frames.length=2*a},l.RotateTimeline.prototype={boneIndex:0,getFrameCount:function(){return this.frames.length/2},setFrame:function(a,b,c){a*=2,this.frames[a]=b,this.frames[a+1]=c},apply:function(a,b,c){var d=this.frames;if(!(b=d[d.length-2]){for(var f=e.data.rotation+d[d.length-1]-e.rotation;f>180;)f-=360;for(;-180>f;)f+=360;return e.rotation+=f*c,void 0}var g=l.binarySearch(d,b,2),h=d[g-1],i=d[g],j=1-(b-i)/(d[g-2]-i);j=this.curves.getCurvePercent(g/2-1,j);for(var f=d[g+1]-h;f>180;)f-=360;for(;-180>f;)f+=360;for(f=e.data.rotation+(h+f*j)-e.rotation;f>180;)f-=360;for(;-180>f;)f+=360;e.rotation+=f*c}}},l.TranslateTimeline=function(a){this.curves=new l.Curves(a),this.frames=[],this.frames.length=3*a},l.TranslateTimeline.prototype={boneIndex:0,getFrameCount:function(){return this.frames.length/3},setFrame:function(a,b,c,d){a*=3,this.frames[a]=b,this.frames[a+1]=c,this.frames[a+2]=d},apply:function(a,b,c){var d=this.frames;if(!(b=d[d.length-3])return e.x+=(e.data.x+d[d.length-2]-e.x)*c,e.y+=(e.data.y+d[d.length-1]-e.y)*c,void 0;var f=l.binarySearch(d,b,3),g=d[f-2],h=d[f-1],i=d[f],j=1-(b-i)/(d[f+-3]-i);j=this.curves.getCurvePercent(f/3-1,j),e.x+=(e.data.x+g+(d[f+1]-g)*j-e.x)*c,e.y+=(e.data.y+h+(d[f+2]-h)*j-e.y)*c}}},l.ScaleTimeline=function(a){this.curves=new l.Curves(a),this.frames=[],this.frames.length=3*a},l.ScaleTimeline.prototype={boneIndex:0,getFrameCount:function(){return this.frames.length/3},setFrame:function(a,b,c,d){a*=3,this.frames[a]=b,this.frames[a+1]=c,this.frames[a+2]=d},apply:function(a,b,c){var d=this.frames;if(!(b=d[d.length-3])return e.scaleX+=(e.data.scaleX-1+d[d.length-2]-e.scaleX)*c,e.scaleY+=(e.data.scaleY-1+d[d.length-1]-e.scaleY)*c,void 0;var f=l.binarySearch(d,b,3),g=d[f-2],h=d[f-1],i=d[f],j=1-(b-i)/(d[f+-3]-i);j=this.curves.getCurvePercent(f/3-1,j),e.scaleX+=(e.data.scaleX-1+g+(d[f+1]-g)*j-e.scaleX)*c,e.scaleY+=(e.data.scaleY-1+h+(d[f+2]-h)*j-e.scaleY)*c}}},l.ColorTimeline=function(a){this.curves=new l.Curves(a),this.frames=[],this.frames.length=5*a},l.ColorTimeline.prototype={slotIndex:0,getFrameCount:function(){return this.frames.length/2},setFrame:function(c,d){c*=5,this.frames[c]=d,this.frames[c+1]=r,this.frames[c+2]=g,this.frames[c+3]=b,this.frames[c+4]=a},apply:function(a,b,c){var d=this.frames;if(!(b=d[d.length-5]){var f=d.length-1;return e.r=d[f-3],e.g=d[f-2],e.b=d[f-1],e.a=d[f],void 0}var g=l.binarySearch(d,b,5),h=d[g-4],i=d[g-3],j=d[g-2],k=d[g-1],m=d[g],n=1-(b-m)/(d[g-5]-m);n=this.curves.getCurvePercent(g/5-1,n);var o=h+(d[g+1]-h)*n,p=i+(d[g+2]-i)*n,q=j+(d[g+3]-j)*n,r=k+(d[g+4]-k)*n;1>c?(e.r+=(o-e.r)*c,e.g+=(p-e.g)*c,e.b+=(q-e.b)*c,e.a+=(r-e.a)*c):(e.r=o,e.g=p,e.b=q,e.a=r)}}},l.AttachmentTimeline=function(a){this.curves=new l.Curves(a),this.frames=[],this.frames.length=a,this.attachmentNames=[],this.attachmentNames.length=a},l.AttachmentTimeline.prototype={slotIndex:0,getFrameCount:function(){return this.frames.length/2},setFrame:function(a,b,c){this.frames[a]=b,this.attachmentNames[a]=c},apply:function(a,b){var c=this.frames;if(!(b=c[c.length-1]?c.length-1:l.binarySearch(c,b,1)-1;var e=this.attachmentNames[d];a.slots[this.slotIndex].setAttachment(e?a.getAttachmentBySlotIndex(this.slotIndex,e):null)}}},l.SkeletonData=function(){this.bones=[],this.slots=[],this.skins=[],this.animations=[]},l.SkeletonData.prototype={defaultSkin:null,findBone:function(a){for(var b=this.bones,c=0,d=b.length;d>c;c++)if(b[c].name==a)return b[c];return null},findBoneIndex:function(a){for(var b=this.bones,c=0,d=b.length;d>c;c++)if(b[c].name==a)return c;return-1},findSlot:function(a){for(var b=this.slots,c=0,d=b.length;d>c;c++)if(b[c].name==a)return slot[c];return null},findSlotIndex:function(a){for(var b=this.slots,c=0,d=b.length;d>c;c++)if(b[c].name==a)return c;return-1},findSkin:function(a){for(var b=this.skins,c=0,d=b.length;d>c;c++)if(b[c].name==a)return b[c];return null},findAnimation:function(a){for(var b=this.animations,c=0,d=b.length;d>c;c++)if(b[c].name==a)return b[c];return null}},l.Skeleton=function(a){this.data=a,this.bones=[];for(var b=0,c=a.bones.length;c>b;b++){var d=a.bones[b],e=d.parent?this.bones[a.bones.indexOf(d.parent)]:null;this.bones.push(new l.Bone(d,e))}this.slots=[],this.drawOrder=[];for(var b=0,c=a.slots.length;c>b;b++){var f=a.slots[b],g=this.bones[a.bones.indexOf(f.boneData)],h=new l.Slot(f,this,g);this.slots.push(h),this.drawOrder.push(h)}},l.Skeleton.prototype={x:0,y:0,skin:null,r:1,g:1,b:1,a:1,time:0,flipX:!1,flipY:!1,updateWorldTransform:function(){for(var a=this.flipX,b=this.flipY,c=this.bones,d=0,e=c.length;e>d;d++)c[d].updateWorldTransform(a,b)},setToSetupPose:function(){this.setBonesToSetupPose(),this.setSlotsToSetupPose()},setBonesToSetupPose:function(){for(var a=this.bones,b=0,c=a.length;c>b;b++)a[b].setToSetupPose()},setSlotsToSetupPose:function(){for(var a=this.slots,b=0,c=a.length;c>b;b++)a[b].setToSetupPose(b)},getRootBone:function(){return 0==this.bones.length?null:this.bones[0]},findBone:function(a){for(var b=this.bones,c=0,d=b.length;d>c;c++)if(b[c].data.name==a)return b[c];return null},findBoneIndex:function(a){for(var b=this.bones,c=0,d=b.length;d>c;c++)if(b[c].data.name==a)return c;return-1},findSlot:function(a){for(var b=this.slots,c=0,d=b.length;d>c;c++)if(b[c].data.name==a)return b[c];return null},findSlotIndex:function(a){for(var b=this.slots,c=0,d=b.length;d>c;c++)if(b[c].data.name==a)return c;return-1},setSkinByName:function(a){var b=this.data.findSkin(a);if(!b)throw"Skin not found: "+a;this.setSkin(b)},setSkin:function(a){this.skin&&a&&a._attachAll(this,this.skin),this.skin=a},getAttachmentBySlotName:function(a,b){return this.getAttachmentBySlotIndex(this.data.findSlotIndex(a),b)},getAttachmentBySlotIndex:function(a,b){if(this.skin){var c=this.skin.getAttachment(a,b);if(c)return c}return this.data.defaultSkin?this.data.defaultSkin.getAttachment(a,b):null},setAttachment:function(a,b){for(var c=this.slots,d=0,e=c.size;e>d;d++){var f=c[d];if(f.data.name==a){var g=null;if(b&&(g=this.getAttachment(d,b),null==g))throw"Attachment not found: "+b+", for slot: "+a;return f.setAttachment(g),void 0}}throw"Slot not found: "+a},update:function(a){time+=a}},l.AttachmentType={region:0},l.RegionAttachment=function(){this.offset=[],this.offset.length=8,this.uvs=[],this.uvs.length=8},l.RegionAttachment.prototype={x:0,y:0,rotation:0,scaleX:1,scaleY:1,width:0,height:0,rendererObject:null,regionOffsetX:0,regionOffsetY:0,regionWidth:0,regionHeight:0,regionOriginalWidth:0,regionOriginalHeight:0,setUVs:function(a,b,c,d,e){var f=this.uvs;e?(f[2]=a,f[3]=d,f[4]=a,f[5]=b,f[6]=c,f[7]=b,f[0]=c,f[1]=d):(f[0]=a,f[1]=d,f[2]=a,f[3]=b,f[4]=c,f[5]=b,f[6]=c,f[7]=d)},updateOffset:function(){var a=this.width/this.regionOriginalWidth*this.scaleX,b=this.height/this.regionOriginalHeight*this.scaleY,c=-this.width/2*this.scaleX+this.regionOffsetX*a,d=-this.height/2*this.scaleY+this.regionOffsetY*b,e=c+this.regionWidth*a,f=d+this.regionHeight*b,g=this.rotation*Math.PI/180,h=Math.cos(g),i=Math.sin(g),j=c*h+this.x,k=c*i,l=d*h+this.y,m=d*i,n=e*h+this.x,o=e*i,p=f*h+this.y,q=f*i,r=this.offset; 14 | r[0]=j-m,r[1]=l+k,r[2]=j-q,r[3]=p+k,r[4]=n-q,r[5]=p+o,r[6]=n-m,r[7]=l+o},computeVertices:function(a,b,c,d){a+=c.worldX,b+=c.worldY;var e=c.m00,f=c.m01,g=c.m10,h=c.m11,i=this.offset;d[0]=i[0]*e+i[1]*f+a,d[1]=i[0]*g+i[1]*h+b,d[2]=i[2]*e+i[3]*f+a,d[3]=i[2]*g+i[3]*h+b,d[4]=i[4]*e+i[5]*f+a,d[5]=i[4]*g+i[5]*h+b,d[6]=i[6]*e+i[7]*f+a,d[7]=i[6]*g+i[7]*h+b}},l.AnimationStateData=function(a){this.skeletonData=a,this.animationToMixTime={}},l.AnimationStateData.prototype={setMixByName:function(a,b,c){var d=this.skeletonData.findAnimation(a);if(!d)throw"Animation not found: "+a;var e=this.skeletonData.findAnimation(b);if(!e)throw"Animation not found: "+b;this.setMix(d,e,c)},setMix:function(a,b,c){this.animationToMixTime[a.name+":"+b.name]=c},getMix:function(a,b){var c=this.animationToMixTime[a.name+":"+b.name];return c?c:0}},l.AnimationState=function(a){this.data=a,this.queue=[]},l.AnimationState.prototype={current:null,previous:null,currentTime:0,previousTime:0,currentLoop:!1,previousLoop:!1,mixTime:0,mixDuration:0,update:function(a){if(this.currentTime+=a,this.previousTime+=a,this.mixTime+=a,this.queue.length>0){var b=this.queue[0];this.currentTime>=b.delay&&(this._setAnimation(b.animation,b.loop),this.queue.shift())}},apply:function(a){if(this.current)if(this.previous){this.previous.apply(a,this.previousTime,this.previousLoop);var b=this.mixTime/this.mixDuration;b>=1&&(b=1,this.previous=null),this.current.mix(a,this.currentTime,this.currentLoop,b)}else this.current.apply(a,this.currentTime,this.currentLoop)},clearAnimation:function(){this.previous=null,this.current=null,this.queue.length=0},_setAnimation:function(a,b){this.previous=null,a&&this.current&&(this.mixDuration=this.data.getMix(this.current,a),this.mixDuration>0&&(this.mixTime=0,this.previous=this.current,this.previousTime=this.currentTime,this.previousLoop=this.currentLoop)),this.current=a,this.currentLoop=b,this.currentTime=0},setAnimationByName:function(a,b){var c=this.data.skeletonData.findAnimation(a);if(!c)throw"Animation not found: "+a;this.setAnimation(c,b)},setAnimation:function(a,b){this.queue.length=0,this._setAnimation(a,b)},addAnimationByName:function(a,b,c){var d=this.data.skeletonData.findAnimation(a);if(!d)throw"Animation not found: "+a;this.addAnimation(d,b,c)},addAnimation:function(a,b,c){var d={};if(d.animation=a,d.loop=b,!c||0>=c){var e=0==this.queue.length?this.current:this.queue[this.queue.length-1].animation;c=null!=e?e.duration-this.data.getMix(e,a)+(c||0):0}d.delay=c,this.queue.push(d)},isComplete:function(){return!this.current||this.currentTime>=this.current.duration}},l.SkeletonJson=function(a){this.attachmentLoader=a},l.SkeletonJson.prototype={scale:1,readSkeletonData:function(a){for(var b=new l.SkeletonData,c=a.bones,d=0,e=c.length;e>d;d++){var f=c[d],g=null;if(f.parent&&(g=b.findBone(f.parent),!g))throw"Parent bone not found: "+f.parent;var h=new l.BoneData(f.name,g);h.length=(f.length||0)*this.scale,h.x=(f.x||0)*this.scale,h.y=(f.y||0)*this.scale,h.rotation=f.rotation||0,h.scaleX=f.scaleX||1,h.scaleY=f.scaleY||1,b.bones.push(h)}for(var i=a.slots,d=0,e=i.length;e>d;d++){var j=i[d],h=b.findBone(j.bone);if(!h)throw"Slot bone not found: "+j.bone;var k=new l.SlotData(j.name,h),m=j.color;m&&(k.r=l.SkeletonJson.toColor(m,0),k.g=l.SkeletonJson.toColor(m,1),k.b=l.SkeletonJson.toColor(m,2),k.a=l.SkeletonJson.toColor(m,3)),k.attachmentName=j.attachment,b.slots.push(k)}var n=a.skins;for(var o in n)if(n.hasOwnProperty(o)){var p=n[o],q=new l.Skin(o);for(var r in p)if(p.hasOwnProperty(r)){var s=b.findSlotIndex(r),t=p[r];for(var u in t)if(t.hasOwnProperty(u)){var v=this.readAttachment(q,u,t[u]);null!=v&&q.addAttachment(s,u,v)}}b.skins.push(q),"default"==q.name&&(b.defaultSkin=q)}var w=a.animations;for(var x in w)w.hasOwnProperty(x)&&this.readAnimation(x,w[x],b);return b},readAttachment:function(a,b,c){b=c.name||b;var d=l.AttachmentType[c.type||"region"],e=new l.RegionAttachment;return e.name=b,d==l.AttachmentType.region&&(e.x=(c.x||0)*this.scale,e.y=(c.y||0)*this.scale,e.scaleX=c.scaleX||1,e.scaleY=c.scaleY||1,e.rotation=c.rotation||0,e.width=(c.width||32)*this.scale,e.height=(c.height||32)*this.scale,e.updateOffset()),e},readAnimation:function(a,b,c){var d=[],e=0,f=b.bones;for(var g in f)if(f.hasOwnProperty(g)){var h=c.findBoneIndex(g);if(-1==h)throw"Bone not found: "+g;var i=f[g];for(var j in i)if(i.hasOwnProperty(j)){var k=i[j];if("rotate"==j){var m=new l.RotateTimeline(k.length);m.boneIndex=h;for(var n=0,o=0,p=k.length;p>o;o++){var q=k[o];m.setFrame(n,q.time,q.angle),l.SkeletonJson.readCurve(m,n,q),n++}d.push(m),e=Math.max(e,m.frames[2*m.getFrameCount()-2])}else{if("translate"!=j&&"scale"!=j)throw"Invalid timeline type for a bone: "+j+" ("+g+")";var m,r=1;"scale"==j?m=new l.ScaleTimeline(k.length):(m=new l.TranslateTimeline(k.length),r=this.scale),m.boneIndex=h;for(var n=0,o=0,p=k.length;p>o;o++){var q=k[o],s=(q.x||0)*r,t=(q.y||0)*r;m.setFrame(n,q.time,s,t),l.SkeletonJson.readCurve(m,n,q),n++}d.push(m),e=Math.max(e,m.frames[3*m.getFrameCount()-3])}}}var u=b.slots;for(var v in u)if(u.hasOwnProperty(v)){var w=u[v],x=c.findSlotIndex(v);for(var j in w)if(w.hasOwnProperty(j)){var k=w[j];if("color"==j){var m=new l.ColorTimeline(k.length);m.slotIndex=x;for(var n=0,o=0,p=k.length;p>o;o++){var q=k[o],y=q.color,z=l.SkeletonJson.toColor(y,0),A=l.SkeletonJson.toColor(y,1),B=l.SkeletonJson.toColor(y,2),C=l.SkeletonJson.toColor(y,3);m.setFrame(n,q.time,z,A,B,C),l.SkeletonJson.readCurve(m,n,q),n++}d.push(m),e=Math.max(e,m.frames[5*m.getFrameCount()-5])}else{if("attachment"!=j)throw"Invalid timeline type for a slot: "+j+" ("+v+")";var m=new l.AttachmentTimeline(k.length);m.slotIndex=x;for(var n=0,o=0,p=k.length;p>o;o++){var q=k[o];m.setFrame(n++,q.time,q.name)}d.push(m),e=Math.max(e,m.frames[Math.floor(m.getFrameCount())-1])}}}c.animations.push(new l.Animation(a,d,e))}},l.SkeletonJson.readCurve=function(a,b,c){var d=c.curve;d&&("stepped"==d?a.curves.setStepped(b):d instanceof Array&&a.curves.setCurve(b,d[0],d[1],d[2],d[3]))},l.SkeletonJson.toColor=function(a,b){if(8!=a.length)throw"Color hexidecimal length must be 8, recieved: "+a;return parseInt(a.substring(2*b,2),16)/255},l.Atlas=function(a,b){this.textureLoader=b,this.pages=[],this.regions=[];var c=new l.AtlasReader(a),d=[];d.length=4;for(var e=null;;){var f=c.readLine();if(null==f)break;if(f=c.trim(f),0==f.length)e=null;else if(e){var g=new l.AtlasRegion;g.name=f,g.page=e,g.rotate="true"==c.readValue(),c.readTuple(d);var h=parseInt(d[0]),i=parseInt(d[1]);c.readTuple(d);var j=parseInt(d[0]),k=parseInt(d[1]);g.u=h/e.width,g.v=i/e.height,g.rotate?(g.u2=(h+k)/e.width,g.v2=(i+j)/e.height):(g.u2=(h+j)/e.width,g.v2=(i+k)/e.height),g.x=h,g.y=i,g.width=Math.abs(j),g.height=Math.abs(k),4==c.readTuple(d)&&(g.splits=[parseInt(d[0]),parseInt(d[1]),parseInt(d[2]),parseInt(d[3])],4==c.readTuple(d)&&(g.pads=[parseInt(d[0]),parseInt(d[1]),parseInt(d[2]),parseInt(d[3])],c.readTuple(d))),g.originalWidth=parseInt(d[0]),g.originalHeight=parseInt(d[1]),c.readTuple(d),g.offsetX=parseInt(d[0]),g.offsetY=parseInt(d[1]),g.index=parseInt(c.readValue()),this.regions.push(g)}else{e=new l.AtlasPage,e.name=f,e.format=l.Atlas.Format[c.readValue()],c.readTuple(d),e.minFilter=l.Atlas.TextureFilter[d[0]],e.magFilter=l.Atlas.TextureFilter[d[1]];var m=c.readValue();e.uWrap=l.Atlas.TextureWrap.clampToEdge,e.vWrap=l.Atlas.TextureWrap.clampToEdge,"x"==m?e.uWrap=l.Atlas.TextureWrap.repeat:"y"==m?e.vWrap=l.Atlas.TextureWrap.repeat:"xy"==m&&(e.uWrap=e.vWrap=l.Atlas.TextureWrap.repeat),b.load(e,f),this.pages.push(e)}}},l.Atlas.prototype={findRegion:function(a){for(var b=this.regions,c=0,d=b.length;d>c;c++)if(b[c].name==a)return b[c];return null},dispose:function(){for(var a=this.pages,b=0,c=a.length;c>b;b++)this.textureLoader.unload(a[b].rendererObject)},updateUVs:function(a){for(var b=this.regions,c=0,d=b.length;d>c;c++){var e=b[c];e.page==a&&(e.u=e.x/a.width,e.v=e.y/a.height,e.rotate?(e.u2=(e.x+e.height)/a.width,e.v2=(e.y+e.width)/a.height):(e.u2=(e.x+e.width)/a.width,e.v2=(e.y+e.height)/a.height))}}},l.Atlas.Format={alpha:0,intensity:1,luminanceAlpha:2,rgb565:3,rgba4444:4,rgb888:5,rgba8888:6},l.Atlas.TextureFilter={nearest:0,linear:1,mipMap:2,mipMapNearestNearest:3,mipMapLinearNearest:4,mipMapNearestLinear:5,mipMapLinearLinear:6},l.Atlas.TextureWrap={mirroredRepeat:0,clampToEdge:1,repeat:2},l.AtlasPage=function(){},l.AtlasPage.prototype={name:null,format:null,minFilter:null,magFilter:null,uWrap:null,vWrap:null,rendererObject:null,width:0,height:0},l.AtlasRegion=function(){},l.AtlasRegion.prototype={page:null,name:null,x:0,y:0,width:0,height:0,u:0,v:0,u2:0,v2:0,offsetX:0,offsetY:0,originalWidth:0,originalHeight:0,index:0,rotate:!1,splits:null,pads:null},l.AtlasReader=function(a){this.lines=a.split(/\r\n|\r|\n/)},l.AtlasReader.prototype={index:0,trim:function(a){return a.replace(/^\s+|\s+$/g,"")},readLine:function(){return this.index>=this.lines.length?null:this.lines[this.index++]},readValue:function(){var a=this.readLine(),b=a.indexOf(":");if(-1==b)throw"Invalid line: "+a;return this.trim(a.substring(b+1))},readTuple:function(a){var b=this.readLine(),c=b.indexOf(":");if(-1==c)throw"Invalid line: "+b;for(var d=0,e=c+1;3>d;d++){var f=b.indexOf(",",e);if(-1==f){if(0==d)throw"Invalid line: "+b;break}a[d]=this.trim(b.substr(e,f-e)),e=f+1}return a[d]=this.trim(b.substring(e)),d+1}},l.AtlasAttachmentLoader=function(a){this.atlas=a},l.AtlasAttachmentLoader.prototype={newAttachment:function(a,b,c){switch(b){case l.AttachmentType.region:var d=this.atlas.findRegion(c);if(!d)throw"Region not found in atlas: "+c+" ("+b+")";var e=new l.RegionAttachment(c);return e.rendererObject=d,e.setUVs(d.u,d.v,d.u2,d.v2,d.rotate),e.regionOffsetX=d.offsetX,e.regionOffsetY=d.offsetY,e.regionWidth=d.width,e.regionHeight=d.height,e.regionOriginalWidth=d.originalWidth,e.regionOriginalHeight=d.originalHeight,e}throw"Unknown attachment type: "+b}},f.AnimCache={},l.Bone.yDown=!0,f.CustomRenderable=function(){f.DisplayObject.call(this)},f.CustomRenderable.constructor=f.CustomRenderable,f.CustomRenderable.prototype=Object.create(f.DisplayObject.prototype),f.CustomRenderable.prototype.renderCanvas=function(){},f.CustomRenderable.prototype.initWebGL=function(){},f.CustomRenderable.prototype.renderWebGL=function(){},f.BaseTextureCache={},f.texturesToUpdate=[],f.texturesToDestroy=[],f.BaseTexture=function(a){if(f.EventTarget.call(this),this.width=100,this.height=100,this.source=a,a){if(this.source instanceof Image)if(this.source.complete)this.hasLoaded=!0,this.width=this.source.width,this.height=this.source.height,f.texturesToUpdate.push(this);else{var b=this;this.source.onload=function(){b.hasLoaded=!0,b.width=b.source.width,b.height=b.source.height,f.texturesToUpdate.push(b),b.dispatchEvent({type:"loaded",content:b})}}else this.hasLoaded=!0,this.width=this.source.width,this.height=this.source.height,f.texturesToUpdate.push(this);this._powerOf2=!1}},f.BaseTexture.constructor=f.BaseTexture,f.BaseTexture.prototype.destroy=function(){this.source instanceof Image&&(this.source.src=null),this.source=null,f.texturesToDestroy.push(this)},f.BaseTexture.fromImage=function(a,b){var c=f.BaseTextureCache[a];if(!c){var d=new Image;b&&(d.crossOrigin=""),d.src=a,c=new f.BaseTexture(d),f.BaseTextureCache[a]=c}return c},f.TextureCache={},f.FrameCache={},f.Texture=function(a,b){if(f.EventTarget.call(this),b||(this.noFrame=!0,b=new f.Rectangle(0,0,1,1)),this.trim=new f.Point,a instanceof f.Texture&&(a=a.baseTexture),this.baseTexture=a,this.frame=b,this.scope=this,a.hasLoaded)this.noFrame&&(b=new f.Rectangle(0,0,a.width,a.height)),this.setFrame(b);else{var c=this;a.addEventListener("loaded",function(){c.onBaseTextureLoaded()})}},f.Texture.constructor=f.Texture,f.Texture.prototype.onBaseTextureLoaded=function(){var a=this.baseTexture;a.removeEventListener("loaded",this.onLoaded),this.noFrame&&(this.frame=new f.Rectangle(0,0,a.width,a.height)),this.noFrame=!1,this.width=this.frame.width,this.height=this.frame.height,this.scope.dispatchEvent({type:"update",content:this})},f.Texture.prototype.destroy=function(a){a&&this.baseTexture.destroy()},f.Texture.prototype.setFrame=function(a){if(this.frame=a,this.width=a.width,this.height=a.height,a.x+a.width>this.baseTexture.width||a.y+a.height>this.baseTexture.height)throw new Error("Texture Error: frame does not fit inside the base Texture dimensions "+this);this.updateFrame=!0,f.Texture.frameUpdates.push(this)},f.Texture.fromImage=function(a,b){var c=f.TextureCache[a];return c||(c=new f.Texture(f.BaseTexture.fromImage(a,b)),f.TextureCache[a]=c),c},f.Texture.fromFrame=function(a){var b=f.TextureCache[a];if(!b)throw new Error("The frameId '"+a+"' does not exist in the texture cache "+this);return b},f.Texture.fromCanvas=function(a){var b=new f.BaseTexture(a);return new f.Texture(b)},f.Texture.addTextureToCache=function(a,b){f.TextureCache[b]=a},f.Texture.removeTextureFromCache=function(a){var b=f.TextureCache[a];return f.TextureCache[a]=null,b},f.Texture.frameUpdates=[],f.RenderTexture=function(a,b){f.EventTarget.call(this),this.width=a||100,this.height=b||100,this.indetityMatrix=f.mat3.create(),this.frame=new f.Rectangle(0,0,this.width,this.height),f.gl?this.initWebGL():this.initCanvas()},f.RenderTexture.constructor=f.RenderTexture,f.RenderTexture.prototype=Object.create(f.Texture.prototype),f.RenderTexture.prototype.initWebGL=function(){var a=f.gl;this.glFramebuffer=a.createFramebuffer(),a.bindFramebuffer(a.FRAMEBUFFER,this.glFramebuffer),this.glFramebuffer.width=this.width,this.glFramebuffer.height=this.height,this.baseTexture=new f.BaseTexture,this.baseTexture.width=this.width,this.baseTexture.height=this.height,this.baseTexture._glTexture=a.createTexture(),a.bindTexture(a.TEXTURE_2D,this.baseTexture._glTexture),a.texImage2D(a.TEXTURE_2D,0,a.RGBA,this.width,this.height,0,a.RGBA,a.UNSIGNED_BYTE,null),a.texParameteri(a.TEXTURE_2D,a.TEXTURE_MAG_FILTER,a.LINEAR),a.texParameteri(a.TEXTURE_2D,a.TEXTURE_MIN_FILTER,a.LINEAR),a.texParameteri(a.TEXTURE_2D,a.TEXTURE_WRAP_S,a.CLAMP_TO_EDGE),a.texParameteri(a.TEXTURE_2D,a.TEXTURE_WRAP_T,a.CLAMP_TO_EDGE),this.baseTexture.isRender=!0,a.bindFramebuffer(a.FRAMEBUFFER,this.glFramebuffer),a.framebufferTexture2D(a.FRAMEBUFFER,a.COLOR_ATTACHMENT0,a.TEXTURE_2D,this.baseTexture._glTexture,0),this.projectionMatrix=f.mat4.create(),this.projectionMatrix[5]=2/this.height,this.projectionMatrix[13]=-1,this.projectionMatrix[0]=2/this.width,this.projectionMatrix[12]=-1,this.render=this.renderWebGL},f.RenderTexture.prototype.initCanvas=function(){this.renderer=new f.CanvasRenderer(this.width,this.height,null,0),this.baseTexture=new f.BaseTexture(this.renderer.view),this.frame=new f.Rectangle(0,0,this.width,this.height),this.render=this.renderCanvas},f.RenderTexture.prototype.renderWebGL=function(a,b){var c=f.gl;c.colorMask(!0,!0,!0,!0),c.viewport(0,0,this.width,this.height),c.bindFramebuffer(c.FRAMEBUFFER,this.glFramebuffer),b&&(c.clearColor(0,0,0,0),c.clear(c.COLOR_BUFFER_BIT));var d=a.children;a.worldTransform=f.mat3.create();for(var e=0,g=d.length;g>e;e++)d[e].updateTransform();var h=a.__renderGroup;h?a==h.root?h.render(this.projectionMatrix):h.renderSpecific(a,this.projectionMatrix):(this.renderGroup||(this.renderGroup=new f.WebGLRenderGroup(c)),this.renderGroup.setRenderable(a),this.renderGroup.render(this.projectionMatrix))},f.RenderTexture.prototype.renderCanvas=function(a,b){var c=a.children;a.worldTransform=f.mat3.create();for(var d=0,e=c.length;e>d;d++)c[d].updateTransform();b&&this.renderer.context.clearRect(0,0,this.width,this.height),this.renderer.renderDisplayObject(a),f.texturesToUpdate.push(this.baseTexture)},f.AssetLoader=function(a){f.EventTarget.call(this),this.assetURLs=a,this.crossorigin=!1,this.loadersByType={jpg:f.ImageLoader,jpeg:f.ImageLoader,png:f.ImageLoader,gif:f.ImageLoader,json:f.JsonLoader,anim:f.SpineLoader,xml:f.BitmapFontLoader,fnt:f.BitmapFontLoader}},f.AssetLoader.constructor=f.AssetLoader,f.AssetLoader.prototype.load=function(){var a=this;this.loadCount=this.assetURLs.length;for(var b=0;bthis.depCount&&!this.defined){if(H(m)){if(this.events.error&&this.map.isDefine||j.onError!==aa)try{e=i.execCb(c,m,b,e)}catch(d){a=d}else e=i.execCb(c,m,b,e);this.map.isDefine&&((b=this.module)&&void 0!==b.exports&&b.exports!== 19 | this.exports?e=b.exports:void 0===e&&this.usingExports&&(e=this.exports));if(a)return a.requireMap=this.map,a.requireModules=this.map.isDefine?[this.map.id]:null,a.requireType=this.map.isDefine?"define":"require",v(this.error=a)}else e=m;this.exports=e;if(this.map.isDefine&&!this.ignore&&(r[c]=e,j.onResourceLoad))j.onResourceLoad(i,this.map,this.depMaps);x(c);this.defined=!0}this.defining=!1;this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete= 20 | !0)}}else this.fetch()}},callPlugin:function(){var a=this.map,b=a.id,d=n(a.prefix);this.depMaps.push(d);t(d,"defined",u(this,function(e){var m,d;d=this.map.name;var g=this.map.parentMap?this.map.parentMap.name:null,h=i.makeRequire(a.parentMap,{enableBuildCallback:!0});if(this.map.unnormalized){if(e.normalize&&(d=e.normalize(d,function(a){return c(a,g,!0)})||""),e=n(a.prefix+"!"+d,this.map.parentMap),t(e,"defined",u(this,function(a){this.init([],function(){return a},null,{enabled:!0,ignore:!0})})), 21 | d=l(p,e.id)){this.depMaps.push(e);if(this.events.error)d.on("error",u(this,function(a){this.emit("error",a)}));d.enable()}}else m=u(this,function(a){this.init([],function(){return a},null,{enabled:!0})}),m.error=u(this,function(a){this.inited=!0;this.error=a;a.requireModules=[b];F(p,function(a){0===a.map.id.indexOf(b+"_unnormalized")&&x(a.map.id)});v(a)}),m.fromText=u(this,function(e,c){var d=a.name,g=n(d),B=O;c&&(e=c);B&&(O=!1);q(g);s(k.config,b)&&(k.config[d]=k.config[b]);try{j.exec(e)}catch(ca){return v(A("fromtexteval", 22 | "fromText eval for "+b+" failed: "+ca,ca,[b]))}B&&(O=!0);this.depMaps.push(g);i.completeLoad(d);h([d],m)}),e.load(a.name,h,m,k)}));i.enable(d,this);this.pluginMaps[d.id]=d},enable:function(){T[this.map.id]=this;this.enabling=this.enabled=!0;y(this.depMaps,u(this,function(a,b){var c,e;if("string"===typeof a){a=n(a,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap);this.depMaps[b]=a;if(c=l(N,a.id)){this.depExports[b]=c(this);return}this.depCount+=1;t(a,"defined",u(this,function(a){this.defineDep(b, 23 | a);this.check()}));this.errback&&t(a,"error",u(this,this.errback))}c=a.id;e=p[c];!s(N,c)&&(e&&!e.enabled)&&i.enable(a,this)}));F(this.pluginMaps,u(this,function(a){var b=l(p,a.id);b&&!b.enabled&&i.enable(a,this)}));this.enabling=!1;this.check()},on:function(a,b){var c=this.events[a];c||(c=this.events[a]=[]);c.push(b)},emit:function(a,b){y(this.events[a],function(a){a(b)});"error"===a&&delete this.events[a]}};i={config:k,contextName:b,registry:p,defined:r,urlFetched:S,defQueue:G,Module:X,makeModuleMap:n, 24 | nextTick:j.nextTick,onError:v,configure:function(a){a.baseUrl&&"/"!==a.baseUrl.charAt(a.baseUrl.length-1)&&(a.baseUrl+="/");var b=k.pkgs,c=k.shim,e={paths:!0,config:!0,map:!0};F(a,function(a,b){e[b]?"map"===b?(k.map||(k.map={}),Q(k[b],a,!0,!0)):Q(k[b],a,!0):k[b]=a});a.shim&&(F(a.shim,function(a,b){I(a)&&(a={deps:a});if((a.exports||a.init)&&!a.exportsFn)a.exportsFn=i.makeShimExports(a);c[b]=a}),k.shim=c);a.packages&&(y(a.packages,function(a){a="string"===typeof a?{name:a}:a;b[a.name]={name:a.name, 25 | location:a.location||a.name,main:(a.main||"main").replace(ja,"").replace(ea,"")}}),k.pkgs=b);F(p,function(a,b){!a.inited&&!a.map.unnormalized&&(a.map=n(b))});if(a.deps||a.callback)i.require(a.deps||[],a.callback)},makeShimExports:function(a){return function(){var b;a.init&&(b=a.init.apply(Z,arguments));return b||a.exports&&ba(a.exports)}},makeRequire:function(a,f){function d(e,c,h){var g,k;f.enableBuildCallback&&(c&&H(c))&&(c.__requireJsBuild=!0);if("string"===typeof e){if(H(c))return v(A("requireargs", 26 | "Invalid require call"),h);if(a&&s(N,e))return N[e](p[a.id]);if(j.get)return j.get(i,e,a,d);g=n(e,a,!1,!0);g=g.id;return!s(r,g)?v(A("notloaded",'Module name "'+g+'" has not been loaded yet for context: '+b+(a?"":". Use require([])"))):r[g]}K();i.nextTick(function(){K();k=q(n(null,a));k.skipMap=f.skipMap;k.init(e,c,h,{enabled:!0});C()});return d}f=f||{};Q(d,{isBrowser:z,toUrl:function(b){var d,f=b.lastIndexOf("."),g=b.split("/")[0];if(-1!==f&&(!("."===g||".."===g)||1h.attachEvent.toString().indexOf("[native code"))&&!W?(O=!0,h.attachEvent("onreadystatechange",b.onScriptLoad)):(h.addEventListener("load",b.onScriptLoad,!1),h.addEventListener("error", 34 | b.onScriptError,!1)),h.src=d,K=h,C?x.insertBefore(h,C):x.appendChild(h),K=null,h;if(da)try{importScripts(d),b.completeLoad(c)}catch(l){b.onError(A("importscripts","importScripts failed for "+c+" at "+d,l,[c]))}};z&&M(document.getElementsByTagName("script"),function(b){x||(x=b.parentNode);if(J=b.getAttribute("data-main"))return q=J,t.baseUrl||(D=q.split("/"),q=D.pop(),fa=D.length?D.join("/")+"/":"./",t.baseUrl=fa),q=q.replace(ea,""),j.jsExtRegExp.test(q)&&(q=J),t.deps=t.deps?t.deps.concat(q):[q],!0}); 35 | define=function(b,c,d){var h,j;"string"!==typeof b&&(d=c,c=b,b=null);I(c)||(d=c,c=null);!c&&H(d)&&(c=[],d.length&&(d.toString().replace(la,"").replace(ma,function(b,d){c.push(d)}),c=(1===d.length?["require"]:["require","exports","module"]).concat(c)));if(O){if(!(h=K))P&&"interactive"===P.readyState||M(document.getElementsByTagName("script"),function(b){if("interactive"===b.readyState)return P=b}),h=P;h&&(b||(b=h.getAttribute("data-requiremodule")),j=E[h.getAttribute("data-requirecontext")])}(j?j.defQueue: 36 | R).push([b,c,d])};define.amd={jQuery:!0};j.exec=function(b){return eval(b)};j(t)}})(this); 37 | -------------------------------------------------------------------------------- /scripts/math/Point3.js: -------------------------------------------------------------------------------- 1 | function Point3(x, y, z) { 2 | if (typeof x === 'undefined') x = 0; 3 | if (typeof y === 'undefined') y = 0; 4 | if (typeof z === 'undefined') z = 0; 5 | this.x = x; 6 | this.y = y; 7 | this.z = z; 8 | } -------------------------------------------------------------------------------- /scripts/math/Vec2.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Optimized 2D general-purpose vector class with fairly complete functionality. 3 | */ 4 | function Vec2(x, y) { 5 | if (typeof x === 'undefined') x = 0; 6 | if (typeof y === 'undefined') y = 0; 7 | this.x = x; 8 | this.y = y; 9 | // something extra for arbitrary data 10 | // this.w = null; 11 | } 12 | 13 | /** 14 | * Sets the length which will change x and y, but not the angle. 15 | */ 16 | Vec2.prototype.setLength = function(value) { 17 | var oldLength = this.getLength(); 18 | if (oldLength !== 0 && value !== oldLength) { 19 | this.multiplyScalar(value / oldLength); 20 | } 21 | return this; 22 | }; 23 | 24 | Vec2.prototype.getLength = function() { 25 | return Math.sqrt((this.x * this.x) + (this.y * this.y)); 26 | }; 27 | 28 | Vec2.prototype.getLengthSq = function() { 29 | return (this.x * this.x) + (this.y * this.y); 30 | }; 31 | 32 | Vec2.prototype.setAngle = function(value) { 33 | var len = this.getAngle(); 34 | this.x = Math.cos(value) * len; 35 | this.y = Math.sin(value) * len; 36 | return this; 37 | }; 38 | 39 | Vec2.prototype.getAngle = function() { 40 | return Math.atan2(this.y, this.x); 41 | }; 42 | 43 | Vec2.prototype.rotateBy = function(theta) { 44 | var x = this.x, y = this.y; 45 | var cos = Math.cos(theta), sin = Math.sin(theta); 46 | this.x = x * cos - y * sin; 47 | this.y = x * sin + y * cos; 48 | return this; 49 | }; 50 | 51 | Vec2.prototype.add = function(v) { 52 | this.x += v.x; 53 | this.y += v.y; 54 | return this; 55 | }; 56 | 57 | Vec2.prototype.addScalar = function(s) { 58 | this.x += s; 59 | this.y += s; 60 | return this; 61 | }; 62 | 63 | Vec2.prototype.subtract = function(v) { 64 | this.x -= v.x; 65 | this.y -= v.y; 66 | return this; 67 | }; 68 | 69 | Vec2.prototype.subtractScalar = function(s) { 70 | this.x -= s; 71 | this.y -= s; 72 | return this; 73 | }; 74 | 75 | Vec2.prototype.multiply = function(v) { 76 | this.x *= v.x; 77 | this.y *= v.y; 78 | return this; 79 | }; 80 | 81 | Vec2.prototype.multiplyScalar = function(s) { 82 | this.x *= s; 83 | this.y *= s; 84 | return this; 85 | }; 86 | 87 | Vec2.prototype.divide = function(v) { 88 | if (v.x === 0 || v.y === 0) return this; 89 | this.x /= v.x; 90 | this.y /= v.y; 91 | return this; 92 | }; 93 | 94 | Vec2.prototype.divideScalar = function(s) { 95 | if (s === 0) return this; 96 | this.x /= s; 97 | this.y /= s; 98 | return this; 99 | }; 100 | 101 | /** 102 | * Calculate the perpendicular vector (normal). 103 | */ 104 | Vec2.prototype.perp = function() { 105 | this.y = -this.y; 106 | return this; 107 | }; 108 | 109 | Vec2.prototype.negate = function() { 110 | this.x = -this.x; 111 | this.y = -this.y; 112 | return this; 113 | }; 114 | 115 | /** 116 | * This function assumes min < max, if this assumption isn't true it will not operate correctly. 117 | */ 118 | Vec2.prototype.clamp = function(min, max) { 119 | if ( this.x < min.x ) { 120 | this.x = min.x; 121 | } else if ( this.x > max.x ) { 122 | this.x = max.x; 123 | } 124 | if ( this.y < min.y ) { 125 | this.y = min.y; 126 | } else if ( this.y > max.y ) { 127 | this.y = max.y; 128 | } 129 | return this; 130 | }; 131 | 132 | /** 133 | * Calculate a vector dot product. 134 | * @param {Vector2D} v A vector 135 | * @return {Number} The dot product 136 | */ 137 | Vec2.prototype.dotProduct = function(v) { 138 | return (this.x * v.x + this.y * v.y); 139 | }; 140 | 141 | /** 142 | * Calculate the cross product of this and another vector. 143 | * @param {Vector2D} v A vector 144 | * @return {Number} The cross product 145 | */ 146 | Vec2.prototype.crossProd = function(v) { 147 | return this.x * v.y - this.y * v.x; 148 | }; 149 | 150 | Vec2.prototype.truncate = function(max) { 151 | var i, l = this.getLength(); 152 | if (l === 0 || l < max) return this; 153 | this.x /= l; 154 | this.y /= l; 155 | this.multiplyScalar(max); 156 | return this; 157 | }; 158 | 159 | Vec2.prototype.angleTo = function(v) { 160 | var dx = this.x - v.x, 161 | dy = this.y - v.y; 162 | return Math.atan2(dy, dx); 163 | }; 164 | 165 | Vec2.prototype.distanceTo = function(v) { 166 | var dx = this.x - v.x, 167 | dy = this.y - v.y; 168 | return Math.sqrt((dx * dx) + (dy * dy)); 169 | }; 170 | 171 | Vec2.prototype.distanceToSquared = function(v) { 172 | var dx = this.x - v.x, 173 | dy = this.y - v.y; 174 | return dx * dx + dy * dy; 175 | }; 176 | 177 | Vec2.prototype.lerp = function(v, alpha) { 178 | this.x += (v.x - this.x) * alpha; 179 | this.y += (v.y - this.y) * alpha; 180 | return this; 181 | }; 182 | 183 | /** 184 | * Normalize the vector 185 | * @return {Vector2D} 186 | */ 187 | Vec2.prototype.normalize = function() { 188 | var length = this.getLength(); 189 | if (length === 0) return this; 190 | this.x /= length; 191 | this.y /= length; 192 | return this; 193 | }; 194 | 195 | Vec2.prototype.reset = function(x, y) { 196 | x = x ? x : 0; 197 | y = y ? y : 0; 198 | this.x = x; 199 | this.y = y; 200 | return this; 201 | }; 202 | 203 | Vec2.prototype.equals = function(v) { 204 | if (this.x === v.x && this.y === v.y) return true; 205 | return false; 206 | }; 207 | 208 | /** 209 | * Copy from given Vector. 210 | */ 211 | Vec2.prototype.copy = function(v) { 212 | this.x = v.x; 213 | this.y = v.y; 214 | return this; 215 | }; 216 | 217 | /** 218 | * Return a new Vector object using this as a start. 219 | */ 220 | Vec2.prototype.clone = function() { 221 | return new Vec2(this.x, this.y); 222 | }; 223 | 224 | /** 225 | * Visualize this vector. 226 | * @param {type} context HTML canvas 2D context to draw to. 227 | * @param {type} [startX] X offset to draw from. 228 | * @param {type} [startY] Y offset to draw from. 229 | * @param {type} [drawingColor] CSS-compatible color to use. 230 | */ 231 | Vec2.prototype.draw = function(ctx, startX, startY, drawingColor) { 232 | startX = !!startX ? startX : 0; 233 | startY = !!startY ? startY : 0; 234 | drawingColor = !!drawingColor ? drawingColor : 'rgb(0, 250, 0)'; 235 | 236 | ctx.strokeStyle = drawingColor; 237 | ctx.beginPath(); 238 | ctx.moveTo(startX, startY); 239 | ctx.lineTo(this.x, this.y); 240 | ctx.stroke(); 241 | }; 242 | 243 | Vec2.draw = function(ctx, v1, v2, drawingColor, camOffsetX, camOffsetY) { 244 | camOffsetX = camOffsetX || 0; 245 | camOffsetY = camOffsetY || 0; 246 | ctx.strokeStyle = !!drawingColor ? drawingColor : 'rgb(250, 10, 10)'; 247 | ctx.beginPath(); 248 | ctx.moveTo(v1.x + camOffsetX, v1.y + camOffsetY); 249 | ctx.lineTo(v2.x + camOffsetX, v2.y + camOffsetY); 250 | ctx.stroke(); 251 | }; --------------------------------------------------------------------------------