UNPKG

phaser-ce

Version:

Phaser CE (Community Edition) is a fast, free and fun HTML5 Game Framework for Desktop and Mobile web browsers.

1,664 lines (1,468 loc) 3.32 MB
/** * @author Richard Davey <rich@photonstorm.com> * @copyright 2016 Photon Storm Ltd. * @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} * * @overview * * Phaser - http://phaser.io * * v2.9.2 "2017-11-09" - Built: Thu Nov 09 2017 18:05:28 * * By Richard Davey http://www.photonstorm.com @photonstorm * * Phaser is a fun, free and fast 2D game framework for making HTML5 games * for desktop and mobile web browsers, supporting Canvas and WebGL rendering. * * Phaser uses Pixi.js for rendering, created by Mat Groves http://matgroves.com @Doormat23 * Phaser uses p2.js for full-body physics, created by Stefan Hedman https://github.com/schteppe/p2.js @schteppe * Phaser contains a port of N+ Physics, converted by Richard Davey, original by http://www.metanetsoftware.com * * Many thanks to Adam Saltsman (@ADAMATOMIC) for releasing Flixel, from which both Phaser and my love of framework development originate. * * Follow development at http://phaser.io and on our forum * * "If you want your children to be intelligent, read them fairy tales." * "If you want them to be more intelligent, read them more fairy tales." * -- Albert Einstein */ /** * The MIT License (MIT) * * Copyright (c) 2015 p2.js authors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ !function(e){if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&false)define(e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.p2=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){ var Scalar = _dereq_('./Scalar'); module.exports = Line; /** * Container for line-related functions * @class Line */ function Line(){}; /** * Compute the intersection between two lines. * @static * @method lineInt * @param {Array} l1 Line vector 1 * @param {Array} l2 Line vector 2 * @param {Number} precision Precision to use when checking if the lines are parallel * @return {Array} The intersection point. */ Line.lineInt = function(l1,l2,precision){ precision = precision || 0; var i = [0,0]; // point var a1, b1, c1, a2, b2, c2, det; // scalars a1 = l1[1][1] - l1[0][1]; b1 = l1[0][0] - l1[1][0]; c1 = a1 * l1[0][0] + b1 * l1[0][1]; a2 = l2[1][1] - l2[0][1]; b2 = l2[0][0] - l2[1][0]; c2 = a2 * l2[0][0] + b2 * l2[0][1]; det = a1 * b2 - a2*b1; if (!Scalar.eq(det, 0, precision)) { // lines are not parallel i[0] = (b2 * c1 - b1 * c2) / det; i[1] = (a1 * c2 - a2 * c1) / det; } return i; }; /** * Checks if two line segments intersects. * @method segmentsIntersect * @param {Array} p1 The start vertex of the first line segment. * @param {Array} p2 The end vertex of the first line segment. * @param {Array} q1 The start vertex of the second line segment. * @param {Array} q2 The end vertex of the second line segment. * @return {Boolean} True if the two line segments intersect */ Line.segmentsIntersect = function(p1, p2, q1, q2){ var dx = p2[0] - p1[0]; var dy = p2[1] - p1[1]; var da = q2[0] - q1[0]; var db = q2[1] - q1[1]; // segments are parallel if(da*dy - db*dx == 0) return false; var s = (dx * (q1[1] - p1[1]) + dy * (p1[0] - q1[0])) / (da * dy - db * dx) var t = (da * (p1[1] - q1[1]) + db * (q1[0] - p1[0])) / (db * dx - da * dy) return (s>=0 && s<=1 && t>=0 && t<=1); }; },{"./Scalar":4}],2:[function(_dereq_,module,exports){ module.exports = Point; /** * Point related functions * @class Point */ function Point(){}; /** * Get the area of a triangle spanned by the three given points. Note that the area will be negative if the points are not given in counter-clockwise order. * @static * @method area * @param {Array} a * @param {Array} b * @param {Array} c * @return {Number} */ Point.area = function(a,b,c){ return (((b[0] - a[0])*(c[1] - a[1]))-((c[0] - a[0])*(b[1] - a[1]))); }; Point.left = function(a,b,c){ return Point.area(a,b,c) > 0; }; Point.leftOn = function(a,b,c) { return Point.area(a, b, c) >= 0; }; Point.right = function(a,b,c) { return Point.area(a, b, c) < 0; }; Point.rightOn = function(a,b,c) { return Point.area(a, b, c) <= 0; }; var tmpPoint1 = [], tmpPoint2 = []; /** * Check if three points are collinear * @method collinear * @param {Array} a * @param {Array} b * @param {Array} c * @param {Number} [thresholdAngle=0] Threshold angle to use when comparing the vectors. The function will return true if the angle between the resulting vectors is less than this value. Use zero for max precision. * @return {Boolean} */ Point.collinear = function(a,b,c,thresholdAngle) { if(!thresholdAngle) return Point.area(a, b, c) == 0; else { var ab = tmpPoint1, bc = tmpPoint2; ab[0] = b[0]-a[0]; ab[1] = b[1]-a[1]; bc[0] = c[0]-b[0]; bc[1] = c[1]-b[1]; var dot = ab[0]*bc[0] + ab[1]*bc[1], magA = Math.sqrt(ab[0]*ab[0] + ab[1]*ab[1]), magB = Math.sqrt(bc[0]*bc[0] + bc[1]*bc[1]), angle = Math.acos(dot/(magA*magB)); return angle < thresholdAngle; } }; Point.sqdist = function(a,b){ var dx = b[0] - a[0]; var dy = b[1] - a[1]; return dx * dx + dy * dy; }; },{}],3:[function(_dereq_,module,exports){ var Line = _dereq_("./Line") , Point = _dereq_("./Point") , Scalar = _dereq_("./Scalar") module.exports = Polygon; /** * Polygon class. * @class Polygon * @constructor */ function Polygon(){ /** * Vertices that this polygon consists of. An array of array of numbers, example: [[0,0],[1,0],..] * @property vertices * @type {Array} */ this.vertices = []; } /** * Get a vertex at position i. It does not matter if i is out of bounds, this function will just cycle. * @method at * @param {Number} i * @return {Array} */ Polygon.prototype.at = function(i){ var v = this.vertices, s = v.length; return v[i < 0 ? i % s + s : i % s]; }; /** * Get first vertex * @method first * @return {Array} */ Polygon.prototype.first = function(){ return this.vertices[0]; }; /** * Get last vertex * @method last * @return {Array} */ Polygon.prototype.last = function(){ return this.vertices[this.vertices.length-1]; }; /** * Clear the polygon data * @method clear * @return {Array} */ Polygon.prototype.clear = function(){ this.vertices.length = 0; }; /** * Append points "from" to "to"-1 from an other polygon "poly" onto this one. * @method append * @param {Polygon} poly The polygon to get points from. * @param {Number} from The vertex index in "poly". * @param {Number} to The end vertex index in "poly". Note that this vertex is NOT included when appending. * @return {Array} */ Polygon.prototype.append = function(poly,from,to){ if(typeof(from) == "undefined") throw new Error("From is not given!"); if(typeof(to) == "undefined") throw new Error("To is not given!"); if(to-1 < from) throw new Error("lol1"); if(to > poly.vertices.length) throw new Error("lol2"); if(from < 0) throw new Error("lol3"); for(var i=from; i<to; i++){ this.vertices.push(poly.vertices[i]); } }; /** * Make sure that the polygon vertices are ordered counter-clockwise. * @method makeCCW */ Polygon.prototype.makeCCW = function(){ var br = 0, v = this.vertices; // find bottom right point for (var i = 1; i < this.vertices.length; ++i) { if (v[i][1] < v[br][1] || (v[i][1] == v[br][1] && v[i][0] > v[br][0])) { br = i; } } // reverse poly if clockwise if (!Point.left(this.at(br - 1), this.at(br), this.at(br + 1))) { this.reverse(); } }; /** * Reverse the vertices in the polygon * @method reverse */ Polygon.prototype.reverse = function(){ var tmp = []; for(var i=0, N=this.vertices.length; i!==N; i++){ tmp.push(this.vertices.pop()); } this.vertices = tmp; }; /** * Check if a point in the polygon is a reflex point * @method isReflex * @param {Number} i * @return {Boolean} */ Polygon.prototype.isReflex = function(i){ return Point.right(this.at(i - 1), this.at(i), this.at(i + 1)); }; var tmpLine1=[], tmpLine2=[]; /** * Check if two vertices in the polygon can see each other * @method canSee * @param {Number} a Vertex index 1 * @param {Number} b Vertex index 2 * @return {Boolean} */ Polygon.prototype.canSee = function(a,b) { var p, dist, l1=tmpLine1, l2=tmpLine2; if (Point.leftOn(this.at(a + 1), this.at(a), this.at(b)) && Point.rightOn(this.at(a - 1), this.at(a), this.at(b))) { return false; } dist = Point.sqdist(this.at(a), this.at(b)); for (var i = 0; i !== this.vertices.length; ++i) { // for each edge if ((i + 1) % this.vertices.length === a || i === a) // ignore incident edges continue; if (Point.leftOn(this.at(a), this.at(b), this.at(i + 1)) && Point.rightOn(this.at(a), this.at(b), this.at(i))) { // if diag intersects an edge l1[0] = this.at(a); l1[1] = this.at(b); l2[0] = this.at(i); l2[1] = this.at(i + 1); p = Line.lineInt(l1,l2); if (Point.sqdist(this.at(a), p) < dist) { // if edge is blocking visibility to b return false; } } } return true; }; /** * Copy the polygon from vertex i to vertex j. * @method copy * @param {Number} i * @param {Number} j * @param {Polygon} [targetPoly] Optional target polygon to save in. * @return {Polygon} The resulting copy. */ Polygon.prototype.copy = function(i,j,targetPoly){ var p = targetPoly || new Polygon(); p.clear(); if (i < j) { // Insert all vertices from i to j for(var k=i; k<=j; k++) p.vertices.push(this.vertices[k]); } else { // Insert vertices 0 to j for(var k=0; k<=j; k++) p.vertices.push(this.vertices[k]); // Insert vertices i to end for(var k=i; k<this.vertices.length; k++) p.vertices.push(this.vertices[k]); } return p; }; /** * Decomposes the polygon into convex pieces. Returns a list of edges [[p1,p2],[p2,p3],...] that cuts the polygon. * Note that this algorithm has complexity O(N^4) and will be very slow for polygons with many vertices. * @method getCutEdges * @return {Array} */ Polygon.prototype.getCutEdges = function() { var min=[], tmp1=[], tmp2=[], tmpPoly = new Polygon(); var nDiags = Number.MAX_VALUE; for (var i = 0; i < this.vertices.length; ++i) { if (this.isReflex(i)) { for (var j = 0; j < this.vertices.length; ++j) { if (this.canSee(i, j)) { tmp1 = this.copy(i, j, tmpPoly).getCutEdges(); tmp2 = this.copy(j, i, tmpPoly).getCutEdges(); for(var k=0; k<tmp2.length; k++) tmp1.push(tmp2[k]); if (tmp1.length < nDiags) { min = tmp1; nDiags = tmp1.length; min.push([this.at(i), this.at(j)]); } } } } } return min; }; /** * Decomposes the polygon into one or more convex sub-Polygons. * @method decomp * @return {Array} An array or Polygon objects. */ Polygon.prototype.decomp = function(){ var edges = this.getCutEdges(); if(edges.length > 0) return this.slice(edges); else return [this]; }; /** * Slices the polygon given one or more cut edges. If given one, this function will return two polygons (false on failure). If many, an array of polygons. * @method slice * @param {Array} cutEdges A list of edges, as returned by .getCutEdges() * @return {Array} */ Polygon.prototype.slice = function(cutEdges){ if(cutEdges.length == 0) return [this]; if(cutEdges instanceof Array && cutEdges.length && cutEdges[0] instanceof Array && cutEdges[0].length==2 && cutEdges[0][0] instanceof Array){ var polys = [this]; for(var i=0; i<cutEdges.length; i++){ var cutEdge = cutEdges[i]; // Cut all polys for(var j=0; j<polys.length; j++){ var poly = polys[j]; var result = poly.slice(cutEdge); if(result){ // Found poly! Cut and quit polys.splice(j,1); polys.push(result[0],result[1]); break; } } } return polys; } else { // Was given one edge var cutEdge = cutEdges; var i = this.vertices.indexOf(cutEdge[0]); var j = this.vertices.indexOf(cutEdge[1]); if(i != -1 && j != -1){ return [this.copy(i,j), this.copy(j,i)]; } else { return false; } } }; /** * Checks that the line segments of this polygon do not intersect each other. * @method isSimple * @param {Array} path An array of vertices e.g. [[0,0],[0,1],...] * @return {Boolean} * @todo Should it check all segments with all others? */ Polygon.prototype.isSimple = function(){ var path = this.vertices; // Check for(var i=0; i<path.length-1; i++){ for(var j=0; j<i-1; j++){ if(Line.segmentsIntersect(path[i], path[i+1], path[j], path[j+1] )){ return false; } } } // Check the segment between the last and the first point to all others for(var i=1; i<path.length-2; i++){ if(Line.segmentsIntersect(path[0], path[path.length-1], path[i], path[i+1] )){ return false; } } return true; }; function getIntersectionPoint(p1, p2, q1, q2, delta){ delta = delta || 0; var a1 = p2[1] - p1[1]; var b1 = p1[0] - p2[0]; var c1 = (a1 * p1[0]) + (b1 * p1[1]); var a2 = q2[1] - q1[1]; var b2 = q1[0] - q2[0]; var c2 = (a2 * q1[0]) + (b2 * q1[1]); var det = (a1 * b2) - (a2 * b1); if(!Scalar.eq(det,0,delta)) return [((b2 * c1) - (b1 * c2)) / det, ((a1 * c2) - (a2 * c1)) / det] else return [0,0] } /** * Quickly decompose the Polygon into convex sub-polygons. * @method quickDecomp * @param {Array} result * @param {Array} [reflexVertices] * @param {Array} [steinerPoints] * @param {Number} [delta] * @param {Number} [maxlevel] * @param {Number} [level] * @return {Array} */ Polygon.prototype.quickDecomp = function(result,reflexVertices,steinerPoints,delta,maxlevel,level){ maxlevel = maxlevel || 100; level = level || 0; delta = delta || 25; result = typeof(result)!="undefined" ? result : []; reflexVertices = reflexVertices || []; steinerPoints = steinerPoints || []; var upperInt=[0,0], lowerInt=[0,0], p=[0,0]; // Points var upperDist=0, lowerDist=0, d=0, closestDist=0; // scalars var upperIndex=0, lowerIndex=0, closestIndex=0; // Integers var lowerPoly=new Polygon(), upperPoly=new Polygon(); // polygons var poly = this, v = this.vertices; if(v.length < 3) return result; level++; if(level > maxlevel){ console.warn("quickDecomp: max level ("+maxlevel+") reached."); return result; } for (var i = 0; i < this.vertices.length; ++i) { if (poly.isReflex(i)) { reflexVertices.push(poly.vertices[i]); upperDist = lowerDist = Number.MAX_VALUE; for (var j = 0; j < this.vertices.length; ++j) { if (Point.left(poly.at(i - 1), poly.at(i), poly.at(j)) && Point.rightOn(poly.at(i - 1), poly.at(i), poly.at(j - 1))) { // if line intersects with an edge p = getIntersectionPoint(poly.at(i - 1), poly.at(i), poly.at(j), poly.at(j - 1)); // find the point of intersection if (Point.right(poly.at(i + 1), poly.at(i), p)) { // make sure it's inside the poly d = Point.sqdist(poly.vertices[i], p); if (d < lowerDist) { // keep only the closest intersection lowerDist = d; lowerInt = p; lowerIndex = j; } } } if (Point.left(poly.at(i + 1), poly.at(i), poly.at(j + 1)) && Point.rightOn(poly.at(i + 1), poly.at(i), poly.at(j))) { p = getIntersectionPoint(poly.at(i + 1), poly.at(i), poly.at(j), poly.at(j + 1)); if (Point.left(poly.at(i - 1), poly.at(i), p)) { d = Point.sqdist(poly.vertices[i], p); if (d < upperDist) { upperDist = d; upperInt = p; upperIndex = j; } } } } // if there are no vertices to connect to, choose a point in the middle if (lowerIndex == (upperIndex + 1) % this.vertices.length) { //console.log("Case 1: Vertex("+i+"), lowerIndex("+lowerIndex+"), upperIndex("+upperIndex+"), poly.size("+this.vertices.length+")"); p[0] = (lowerInt[0] + upperInt[0]) / 2; p[1] = (lowerInt[1] + upperInt[1]) / 2; steinerPoints.push(p); if (i < upperIndex) { //lowerPoly.insert(lowerPoly.end(), poly.begin() + i, poly.begin() + upperIndex + 1); lowerPoly.append(poly, i, upperIndex+1); lowerPoly.vertices.push(p); upperPoly.vertices.push(p); if (lowerIndex != 0){ //upperPoly.insert(upperPoly.end(), poly.begin() + lowerIndex, poly.end()); upperPoly.append(poly,lowerIndex,poly.vertices.length); } //upperPoly.insert(upperPoly.end(), poly.begin(), poly.begin() + i + 1); upperPoly.append(poly,0,i+1); } else { if (i != 0){ //lowerPoly.insert(lowerPoly.end(), poly.begin() + i, poly.end()); lowerPoly.append(poly,i,poly.vertices.length); } //lowerPoly.insert(lowerPoly.end(), poly.begin(), poly.begin() + upperIndex + 1); lowerPoly.append(poly,0,upperIndex+1); lowerPoly.vertices.push(p); upperPoly.vertices.push(p); //upperPoly.insert(upperPoly.end(), poly.begin() + lowerIndex, poly.begin() + i + 1); upperPoly.append(poly,lowerIndex,i+1); } } else { // connect to the closest point within the triangle //console.log("Case 2: Vertex("+i+"), closestIndex("+closestIndex+"), poly.size("+this.vertices.length+")\n"); if (lowerIndex > upperIndex) { upperIndex += this.vertices.length; } closestDist = Number.MAX_VALUE; if(upperIndex < lowerIndex){ return result; } for (var j = lowerIndex; j <= upperIndex; ++j) { if (Point.leftOn(poly.at(i - 1), poly.at(i), poly.at(j)) && Point.rightOn(poly.at(i + 1), poly.at(i), poly.at(j))) { d = Point.sqdist(poly.at(i), poly.at(j)); if (d < closestDist) { closestDist = d; closestIndex = j % this.vertices.length; } } } if (i < closestIndex) { lowerPoly.append(poly,i,closestIndex+1); if (closestIndex != 0){ upperPoly.append(poly,closestIndex,v.length); } upperPoly.append(poly,0,i+1); } else { if (i != 0){ lowerPoly.append(poly,i,v.length); } lowerPoly.append(poly,0,closestIndex+1); upperPoly.append(poly,closestIndex,i+1); } } // solve smallest poly first if (lowerPoly.vertices.length < upperPoly.vertices.length) { lowerPoly.quickDecomp(result,reflexVertices,steinerPoints,delta,maxlevel,level); upperPoly.quickDecomp(result,reflexVertices,steinerPoints,delta,maxlevel,level); } else { upperPoly.quickDecomp(result,reflexVertices,steinerPoints,delta,maxlevel,level); lowerPoly.quickDecomp(result,reflexVertices,steinerPoints,delta,maxlevel,level); } return result; } } result.push(this); return result; }; /** * Remove collinear points in the polygon. * @method removeCollinearPoints * @param {Number} [precision] The threshold angle to use when determining whether two edges are collinear. Use zero for finest precision. * @return {Number} The number of points removed */ Polygon.prototype.removeCollinearPoints = function(precision){ var num = 0; for(var i=this.vertices.length-1; this.vertices.length>3 && i>=0; --i){ if(Point.collinear(this.at(i-1),this.at(i),this.at(i+1),precision)){ // Remove the middle point this.vertices.splice(i%this.vertices.length,1); i--; // Jump one point forward. Otherwise we may get a chain removal num++; } } return num; }; },{"./Line":1,"./Point":2,"./Scalar":4}],4:[function(_dereq_,module,exports){ module.exports = Scalar; /** * Scalar functions * @class Scalar */ function Scalar(){} /** * Check if two scalars are equal * @static * @method eq * @param {Number} a * @param {Number} b * @param {Number} [precision] * @return {Boolean} */ Scalar.eq = function(a,b,precision){ precision = precision || 0; return Math.abs(a-b) < precision; }; },{}],5:[function(_dereq_,module,exports){ module.exports = { Polygon : _dereq_("./Polygon"), Point : _dereq_("./Point"), }; },{"./Point":2,"./Polygon":3}],6:[function(_dereq_,module,exports){ module.exports={ "name": "p2", "version": "0.7.1", "description": "A JavaScript 2D physics engine.", "author": "Stefan Hedman <schteppe@gmail.com> (http://steffe.se)", "keywords": [ "p2.js", "p2", "physics", "engine", "2d" ], "main": "./src/p2.js", "engines": { "node": "*" }, "repository": { "type": "git", "url": "https://github.com/schteppe/p2.js.git" }, "bugs": { "url": "https://github.com/schteppe/p2.js/issues" }, "licenses": [ { "type": "MIT" } ], "devDependencies": { "grunt": "^0.4.5", "grunt-contrib-jshint": "^0.11.2", "grunt-contrib-nodeunit": "^0.4.1", "grunt-contrib-uglify": "~0.4.0", "grunt-contrib-watch": "~0.5.0", "grunt-browserify": "~2.0.1", "grunt-contrib-concat": "^0.4.0" }, "dependencies": { "poly-decomp": "0.1.1" } } },{}],7:[function(_dereq_,module,exports){ var vec2 = _dereq_('../math/vec2') , Utils = _dereq_('../utils/Utils'); module.exports = AABB; /** * Axis aligned bounding box class. * @class AABB * @constructor * @param {Object} [options] * @param {Array} [options.upperBound] * @param {Array} [options.lowerBound] */ function AABB(options){ /** * The lower bound of the bounding box. * @property lowerBound * @type {Array} */ this.lowerBound = vec2.create(); if(options && options.lowerBound){ vec2.copy(this.lowerBound, options.lowerBound); } /** * The upper bound of the bounding box. * @property upperBound * @type {Array} */ this.upperBound = vec2.create(); if(options && options.upperBound){ vec2.copy(this.upperBound, options.upperBound); } } var tmp = vec2.create(); /** * Set the AABB bounds from a set of points, transformed by the given position and angle. * @method setFromPoints * @param {Array} points An array of vec2's. * @param {Array} position * @param {number} angle * @param {number} skinSize Some margin to be added to the AABB. */ AABB.prototype.setFromPoints = function(points, position, angle, skinSize){ var l = this.lowerBound, u = this.upperBound; if(typeof(angle) !== "number"){ angle = 0; } // Set to the first point if(angle !== 0){ vec2.rotate(l, points[0], angle); } else { vec2.copy(l, points[0]); } vec2.copy(u, l); // Compute cosines and sines just once var cosAngle = Math.cos(angle), sinAngle = Math.sin(angle); for(var i = 1; i<points.length; i++){ var p = points[i]; if(angle !== 0){ var x = p[0], y = p[1]; tmp[0] = cosAngle * x -sinAngle * y; tmp[1] = sinAngle * x +cosAngle * y; p = tmp; } for(var j=0; j<2; j++){ if(p[j] > u[j]){ u[j] = p[j]; } if(p[j] < l[j]){ l[j] = p[j]; } } } // Add offset if(position){ vec2.add(this.lowerBound, this.lowerBound, position); vec2.add(this.upperBound, this.upperBound, position); } if(skinSize){ this.lowerBound[0] -= skinSize; this.lowerBound[1] -= skinSize; this.upperBound[0] += skinSize; this.upperBound[1] += skinSize; } }; /** * Copy bounds from an AABB to this AABB * @method copy * @param {AABB} aabb */ AABB.prototype.copy = function(aabb){ vec2.copy(this.lowerBound, aabb.lowerBound); vec2.copy(this.upperBound, aabb.upperBound); }; /** * Extend this AABB so that it covers the given AABB too. * @method extend * @param {AABB} aabb */ AABB.prototype.extend = function(aabb){ // Loop over x and y var i = 2; while(i--){ // Extend lower bound var l = aabb.lowerBound[i]; if(this.lowerBound[i] > l){ this.lowerBound[i] = l; } // Upper var u = aabb.upperBound[i]; if(this.upperBound[i] < u){ this.upperBound[i] = u; } } }; /** * Returns true if the given AABB overlaps this AABB. * @method overlaps * @param {AABB} aabb * @return {Boolean} */ AABB.prototype.overlaps = function(aabb){ var l1 = this.lowerBound, u1 = this.upperBound, l2 = aabb.lowerBound, u2 = aabb.upperBound; // l2 u2 // |---------| // |--------| // l1 u1 return ((l2[0] <= u1[0] && u1[0] <= u2[0]) || (l1[0] <= u2[0] && u2[0] <= u1[0])) && ((l2[1] <= u1[1] && u1[1] <= u2[1]) || (l1[1] <= u2[1] && u2[1] <= u1[1])); }; /** * @method containsPoint * @param {Array} point * @return {boolean} */ AABB.prototype.containsPoint = function(point){ var l = this.lowerBound, u = this.upperBound; return l[0] <= point[0] && point[0] <= u[0] && l[1] <= point[1] && point[1] <= u[1]; }; /** * Check if the AABB is hit by a ray. * @method overlapsRay * @param {Ray} ray * @return {number} -1 if no hit, a number between 0 and 1 if hit. */ AABB.prototype.overlapsRay = function(ray){ var t = 0; // ray.direction is unit direction vector of ray var dirFracX = 1 / ray.direction[0]; var dirFracY = 1 / ray.direction[1]; // this.lowerBound is the corner of AABB with minimal coordinates - left bottom, rt is maximal corner var t1 = (this.lowerBound[0] - ray.from[0]) * dirFracX; var t2 = (this.upperBound[0] - ray.from[0]) * dirFracX; var t3 = (this.lowerBound[1] - ray.from[1]) * dirFracY; var t4 = (this.upperBound[1] - ray.from[1]) * dirFracY; var tmin = Math.max(Math.max(Math.min(t1, t2), Math.min(t3, t4))); var tmax = Math.min(Math.min(Math.max(t1, t2), Math.max(t3, t4))); // if tmax < 0, ray (line) is intersecting AABB, but whole AABB is behing us if (tmax < 0){ //t = tmax; return -1; } // if tmin > tmax, ray doesn't intersect AABB if (tmin > tmax){ //t = tmax; return -1; } return tmin; }; },{"../math/vec2":30,"../utils/Utils":57}],8:[function(_dereq_,module,exports){ var vec2 = _dereq_('../math/vec2'); var Body = _dereq_('../objects/Body'); module.exports = Broadphase; /** * Base class for broadphase implementations. * @class Broadphase * @constructor */ function Broadphase(type){ this.type = type; /** * The resulting overlapping pairs. Will be filled with results during .getCollisionPairs(). * @property result * @type {Array} */ this.result = []; /** * The world to search for collision pairs in. To change it, use .setWorld() * @property world * @type {World} * @readOnly */ this.world = null; /** * The bounding volume type to use in the broadphase algorithms. Should be set to Broadphase.AABB or Broadphase.BOUNDING_CIRCLE. * @property {Number} boundingVolumeType */ this.boundingVolumeType = Broadphase.AABB; } /** * Axis aligned bounding box type. * @static * @property {Number} AABB */ Broadphase.AABB = 1; /** * Bounding circle type. * @static * @property {Number} BOUNDING_CIRCLE */ Broadphase.BOUNDING_CIRCLE = 2; /** * Set the world that we are searching for collision pairs in. * @method setWorld * @param {World} world */ Broadphase.prototype.setWorld = function(world){ this.world = world; }; /** * Get all potential intersecting body pairs. * @method getCollisionPairs * @param {World} world The world to search in. * @return {Array} An array of the bodies, ordered in pairs. Example: A result of [a,b,c,d] means that the potential pairs are: (a,b), (c,d). */ Broadphase.prototype.getCollisionPairs = function(world){}; var dist = vec2.create(); /** * Check whether the bounding radius of two bodies overlap. * @method boundingRadiusCheck * @param {Body} bodyA * @param {Body} bodyB * @return {Boolean} */ Broadphase.boundingRadiusCheck = function(bodyA, bodyB){ vec2.sub(dist, bodyA.position, bodyB.position); var d2 = vec2.squaredLength(dist), r = bodyA.boundingRadius + bodyB.boundingRadius; return d2 <= r*r; }; /** * Check whether the bounding radius of two bodies overlap. * @method boundingRadiusCheck * @param {Body} bodyA * @param {Body} bodyB * @return {Boolean} */ Broadphase.aabbCheck = function(bodyA, bodyB){ return bodyA.getAABB().overlaps(bodyB.getAABB()); }; /** * Check whether the bounding radius of two bodies overlap. * @method boundingRadiusCheck * @param {Body} bodyA * @param {Body} bodyB * @return {Boolean} */ Broadphase.prototype.boundingVolumeCheck = function(bodyA, bodyB){ var result; switch(this.boundingVolumeType){ case Broadphase.BOUNDING_CIRCLE: result = Broadphase.boundingRadiusCheck(bodyA,bodyB); break; case Broadphase.AABB: result = Broadphase.aabbCheck(bodyA,bodyB); break; default: throw new Error('Bounding volume type not recognized: '+this.boundingVolumeType); } return result; }; /** * Check whether two bodies are allowed to collide at all. * @method canCollide * @param {Body} bodyA * @param {Body} bodyB * @return {Boolean} */ Broadphase.canCollide = function(bodyA, bodyB){ var KINEMATIC = Body.KINEMATIC; var STATIC = Body.STATIC; // Cannot collide static bodies if(bodyA.type === STATIC && bodyB.type === STATIC){ return false; } // Cannot collide static vs kinematic bodies if( (bodyA.type === KINEMATIC && bodyB.type === STATIC) || (bodyA.type === STATIC && bodyB.type === KINEMATIC)){ return false; } // Cannot collide kinematic vs kinematic if(bodyA.type === KINEMATIC && bodyB.type === KINEMATIC){ return false; } // Cannot collide both sleeping bodies if(bodyA.sleepState === Body.SLEEPING && bodyB.sleepState === Body.SLEEPING){ return false; } // Cannot collide if one is static and the other is sleeping if( (bodyA.sleepState === Body.SLEEPING && bodyB.type === STATIC) || (bodyB.sleepState === Body.SLEEPING && bodyA.type === STATIC)){ return false; } return true; }; Broadphase.NAIVE = 1; Broadphase.SAP = 2; },{"../math/vec2":30,"../objects/Body":31}],9:[function(_dereq_,module,exports){ var Circle = _dereq_('../shapes/Circle'), Plane = _dereq_('../shapes/Plane'), Shape = _dereq_('../shapes/Shape'), Particle = _dereq_('../shapes/Particle'), Broadphase = _dereq_('../collision/Broadphase'), vec2 = _dereq_('../math/vec2'); module.exports = NaiveBroadphase; /** * Naive broadphase implementation. Does N^2 tests. * * @class NaiveBroadphase * @constructor * @extends Broadphase */ function NaiveBroadphase(){ Broadphase.call(this, Broadphase.NAIVE); } NaiveBroadphase.prototype = new Broadphase(); NaiveBroadphase.prototype.constructor = NaiveBroadphase; /** * Get the colliding pairs * @method getCollisionPairs * @param {World} world * @return {Array} */ NaiveBroadphase.prototype.getCollisionPairs = function(world){ var bodies = world.bodies, result = this.result; result.length = 0; for(var i=0, Ncolliding=bodies.length; i!==Ncolliding; i++){ var bi = bodies[i]; for(var j=0; j<i; j++){ var bj = bodies[j]; if(Broadphase.canCollide(bi,bj) && this.boundingVolumeCheck(bi,bj)){ result.push(bi,bj); } } } return result; }; /** * Returns all the bodies within an AABB. * @method aabbQuery * @param {World} world * @param {AABB} aabb * @param {array} result An array to store resulting bodies in. * @return {array} */ NaiveBroadphase.prototype.aabbQuery = function(world, aabb, result){ result = result || []; var bodies = world.bodies; for(var i = 0; i < bodies.length; i++){ var b = bodies[i]; if(b.aabbNeedsUpdate){ b.updateAABB(); } if(b.aabb.overlaps(aabb)){ result.push(b); } } return result; }; },{"../collision/Broadphase":8,"../math/vec2":30,"../shapes/Circle":39,"../shapes/Particle":43,"../shapes/Plane":44,"../shapes/Shape":45}],10:[function(_dereq_,module,exports){ var vec2 = _dereq_('../math/vec2') , sub = vec2.sub , add = vec2.add , dot = vec2.dot , Utils = _dereq_('../utils/Utils') , ContactEquationPool = _dereq_('../utils/ContactEquationPool') , FrictionEquationPool = _dereq_('../utils/FrictionEquationPool') , TupleDictionary = _dereq_('../utils/TupleDictionary') , Equation = _dereq_('../equations/Equation') , ContactEquation = _dereq_('../equations/ContactEquation') , FrictionEquation = _dereq_('../equations/FrictionEquation') , Circle = _dereq_('../shapes/Circle') , Convex = _dereq_('../shapes/Convex') , Shape = _dereq_('../shapes/Shape') , Body = _dereq_('../objects/Body') , Box = _dereq_('../shapes/Box'); module.exports = Narrowphase; // Temp things var yAxis = vec2.fromValues(0,1); var tmp1 = vec2.fromValues(0,0) , tmp2 = vec2.fromValues(0,0) , tmp3 = vec2.fromValues(0,0) , tmp4 = vec2.fromValues(0,0) , tmp5 = vec2.fromValues(0,0) , tmp6 = vec2.fromValues(0,0) , tmp7 = vec2.fromValues(0,0) , tmp8 = vec2.fromValues(0,0) , tmp9 = vec2.fromValues(0,0) , tmp10 = vec2.fromValues(0,0) , tmp11 = vec2.fromValues(0,0) , tmp12 = vec2.fromValues(0,0) , tmp13 = vec2.fromValues(0,0) , tmp14 = vec2.fromValues(0,0) , tmp15 = vec2.fromValues(0,0) , tmp16 = vec2.fromValues(0,0) , tmp17 = vec2.fromValues(0,0) , tmp18 = vec2.fromValues(0,0) , tmpArray = []; /** * Narrowphase. Creates contacts and friction given shapes and transforms. * @class Narrowphase * @constructor */ function Narrowphase(){ /** * @property contactEquations * @type {Array} */ this.contactEquations = []; /** * @property frictionEquations * @type {Array} */ this.frictionEquations = []; /** * Whether to make friction equations in the upcoming contacts. * @property enableFriction * @type {Boolean} */ this.enableFriction = true; /** * Whether to make equations enabled in upcoming contacts. * @property enabledEquations * @type {Boolean} */ this.enabledEquations = true; /** * The friction slip force to use when creating friction equations. * @property slipForce * @type {Number} */ this.slipForce = 10.0; /** * The friction value to use in the upcoming friction equations. * @property frictionCoefficient * @type {Number} */ this.frictionCoefficient = 0.3; /** * Will be the .relativeVelocity in each produced FrictionEquation. * @property {Number} surfaceVelocity */ this.surfaceVelocity = 0; /** * Keeps track of the allocated ContactEquations. * @property {ContactEquationPool} contactEquationPool * * @example * * // Allocate a few equations before starting the simulation. * // This way, no contact objects need to be created on the fly in the game loop. * world.narrowphase.contactEquationPool.resize(1024); * world.narrowphase.frictionEquationPool.resize(1024); */ this.contactEquationPool = new ContactEquationPool({ size: 32 }); /** * Keeps track of the allocated ContactEquations. * @property {FrictionEquationPool} frictionEquationPool */ this.frictionEquationPool = new FrictionEquationPool({ size: 64 }); /** * The restitution value to use in the next contact equations. * @property restitution * @type {Number} */ this.restitution = 0; /** * The stiffness value to use in the next contact equations. * @property {Number} stiffness */ this.stiffness = Equation.DEFAULT_STIFFNESS; /** * The stiffness value to use in the next contact equations. * @property {Number} stiffness */ this.relaxation = Equation.DEFAULT_RELAXATION; /** * The stiffness value to use in the next friction equations. * @property frictionStiffness * @type {Number} */ this.frictionStiffness = Equation.DEFAULT_STIFFNESS; /** * The relaxation value to use in the next friction equations. * @property frictionRelaxation * @type {Number} */ this.frictionRelaxation = Equation.DEFAULT_RELAXATION; /** * Enable reduction of friction equations. If disabled, a box on a plane will generate 2 contact equations and 2 friction equations. If enabled, there will be only one friction equation. Same kind of simplifications are made for all collision types. * @property enableFrictionReduction * @type {Boolean} * @deprecated This flag will be removed when the feature is stable enough. * @default true */ this.enableFrictionReduction = true; /** * Keeps track of the colliding bodies last step. * @private * @property collidingBodiesLastStep * @type {TupleDictionary} */ this.collidingBodiesLastStep = new TupleDictionary(); /** * Contact skin size value to use in the next contact equations. * @property {Number} contactSkinSize * @default 0.01 */ this.contactSkinSize = 0.01; } var bodiesOverlap_shapePositionA = vec2.create(); var bodiesOverlap_shapePositionB = vec2.create(); /** * @method bodiesOverlap * @param {Body} bodyA * @param {Body} bodyB * @return {Boolean} * @todo shape world transforms are wrong */ Narrowphase.prototype.bodiesOverlap = function(bodyA, bodyB){ var shapePositionA = bodiesOverlap_shapePositionA; var shapePositionB = bodiesOverlap_shapePositionB; // Loop over all shapes of bodyA for(var k=0, Nshapesi=bodyA.shapes.length; k!==Nshapesi; k++){ var shapeA = bodyA.shapes[k]; bodyA.toWorldFrame(shapePositionA, shapeA.position); // All shapes of body j for(var l=0, Nshapesj=bodyB.shapes.length; l!==Nshapesj; l++){ var shapeB = bodyB.shapes[l]; bodyB.toWorldFrame(shapePositionB, shapeB.position); if(this[shapeA.type | shapeB.type]( bodyA, shapeA, shapePositionA, shapeA.angle + bodyA.angle, bodyB, shapeB, shapePositionB, shapeB.angle + bodyB.angle, true )){ return true; } } } return false; }; /** * Check if the bodies were in contact since the last reset(). * @method collidedLastStep * @param {Body} bodyA * @param {Body} bodyB * @return {Boolean} */ Narrowphase.prototype.collidedLastStep = function(bodyA, bodyB){ var id1 = bodyA.id|0, id2 = bodyB.id|0; return !!this.collidingBodiesLastStep.get(id1, id2); }; /** * Throws away the old equations and gets ready to create new * @method reset */ Narrowphase.prototype.reset = function(){ this.collidingBodiesLastStep.reset(); var eqs = this.contactEquations; var l = eqs.length; while(l--){ var eq = eqs[l], id1 = eq.bodyA.id, id2 = eq.bodyB.id; this.collidingBodiesLastStep.set(id1, id2, true); } var ce = this.contactEquations, fe = this.frictionEquations; for(var i=0; i<ce.length; i++){ this.contactEquationPool.release(ce[i]); } for(var i=0; i<fe.length; i++){ this.frictionEquationPool.release(fe[i]); } // Reset this.contactEquations.length = this.frictionEquations.length = 0; }; /** * Creates a ContactEquation, either by reusing an existing object or creating a new one. * @method createContactEquation * @param {Body} bodyA * @param {Body} bodyB * @return {ContactEquation} */ Narrowphase.prototype.createContactEquation = function(bodyA, bodyB, shapeA, shapeB){ var c = this.contactEquationPool.get(); c.bodyA = bodyA; c.bodyB = bodyB; c.shapeA = shapeA; c.shapeB = shapeB; c.restitution = this.restitution; c.firstImpact = !this.collidedLastStep(bodyA,bodyB); c.stiffness = this.stiffness; c.relaxation = this.relaxation; c.needsUpdate = true; c.enabled = this.enabledEquations; c.offset = this.contactSkinSize; return c; }; /** * Creates a FrictionEquation, either by reusing an existing object or creating a new one. * @method createFrictionEquation * @param {Body} bodyA * @param {Body} bodyB * @return {FrictionEquation} */ Narrowphase.prototype.createFrictionEquation = function(bodyA, bodyB, shapeA, shapeB){ var c = this.frictionEquationPool.get(); c.bodyA = bodyA; c.bodyB = bodyB; c.shapeA = shapeA; c.shapeB = shapeB; c.setSlipForce(this.slipForce); c.frictionCoefficient = this.frictionCoefficient; c.relativeVelocity = this.surfaceVelocity; c.enabled = this.enabledEquations; c.needsUpdate = true; c.stiffness = this.frictionStiffness; c.relaxation = this.frictionRelaxation; c.contactEquations.length = 0; return c; }; /** * Creates a FrictionEquation given the data in the ContactEquation. Uses same offset vectors ri and rj, but the tangent vector will be constructed from the collision normal. * @method createFrictionFromContact * @param {ContactEquation} contactEquation * @return {FrictionEquation} */ Narrowphase.prototype.createFrictionFromContact = function(c){ var eq = this.createFrictionEquation(c.bodyA, c.bodyB, c.shapeA, c.shapeB); vec2.copy(eq.contactPointA, c.contactPointA); vec2.copy(eq.contactPointB, c.contactPointB); vec2.rotate90cw(eq.t, c.normalA); eq.contactEquations.push(c); return eq; }; // Take the average N latest contact point on the plane. Narrowphase.prototype.createFrictionFromAverage = function(numContacts){ var c = this.contactEquations[this.contactEquations.length - 1]; var eq = this.createFrictionEquation(c.bodyA, c.bodyB, c.shapeA, c.shapeB); var bodyA = c.bodyA; var bodyB = c.bodyB; vec2.set(eq.contactPointA, 0, 0); vec2.set(eq.contactPointB, 0, 0); vec2.set(eq.t, 0, 0); for(var i=0; i!==numContacts; i++){ c = this.contactEquations[this.contactEquations.length - 1 - i]; if(c.bodyA === bodyA){ vec2.add(eq.t, eq.t, c.normalA); vec2.add(eq.contactPointA, eq.contactPointA, c.contactPointA); vec2.add(eq.contactPointB, eq.contactPointB, c.contactPointB); } else { vec2.sub(eq.t, eq.t, c.normalA); vec2.add(eq.contactPointA, eq.contactPointA, c.contactPointB); vec2.add(eq.contactPointB, eq.contactPointB, c.contactPointA); } eq.contactEquations.push(c); } var invNumContacts = 1/numContacts; vec2.scale(eq.contactPointA, eq.contactPointA, invNumContacts); vec2.scale(eq.contactPointB, eq.contactPointB, invNumContacts); vec2.normalize(eq.t, eq.t); vec2.rotate90cw(eq.t, eq.t); return eq; }; /** * Convex/line narrowphase * @method convexLine * @param {Body} convexBody * @param {Convex} convexShape * @param {Array} convexOffset * @param {Number} convexAngle * @param {Body} lineBody * @param {Line} lineShape * @param {Array} lineOffset * @param {Number} lineAngle * @param {boolean} justTest * @todo Implement me! */ Narrowphase.prototype[Shape.LINE | Shape.CONVEX] = Narrowphase.prototype.convexLine = function( convexBody, convexShape, convexOffset, convexAngle, lineBody, lineShape, lineOffset, lineAngle, justTest ){ // TODO if(justTest){ return false; } else { return 0; } }; /** * Line/box narrowphase * @method lineBox * @param {Body} lineBody * @param {Line} lineShape * @param {Array} lineOffset * @param {Number} lineAngle * @param {Body} boxBody * @param {Box} boxShape * @param {Array} boxOffset * @param {Number} boxAngle * @param {Boolean} justTest * @todo Implement me! */ Narrowphase.prototype[Shape.LINE | Shape.BOX] = Narrowphase.prototype.lineBox = function( lineBody, lineShape, lineOffset, lineAngle, boxBody, boxShape, boxOffset, boxAngle, justTest ){ // TODO if(justTest){ return false; } else { return 0; } }; function setConvexToCapsuleShapeMiddle(convexShape, capsuleShape){ vec2.set(convexShape.vertices[0], -capsuleShape.length * 0.5, -capsuleShape.radius); vec2.set(convexShape.vertices[1], capsuleShape.length * 0.5, -capsuleShape.radius); vec2.set(convexShape.vertices[2], capsuleShape.length * 0.5, capsuleShape.radius); vec2.set(convexShape.vertices[3], -capsuleShape.length * 0.5, capsuleShape.radius); } var convexCapsule_tempRect = new Box({ width: 1, height: 1 }), convexCapsule_tempVec = vec2.create(); /** * Convex/capsule narrowphase * @method convexCapsule * @param {Body} convexBody * @param {Convex} convexShape * @param {Array} convexPosition * @param {Number} convexAngle * @param {Body} capsuleBody * @param {Capsule} capsuleShape * @param {Array} capsulePosition * @param {Number} capsuleAngle */ Narrowphase.prototype[Shape.CAPSULE | Shape.CONVEX] = Narrowphase.prototype[Shape.CAPSULE | Shape.BOX] = Narrowphase.prototype.convexCapsule = function( convexBody, convexShape, convexPosition, convexAngle, capsuleBody, capsuleShape, capsulePosition, capsuleAngle, justTest ){ // Check the circles // Add offsets! var circlePos = convexCapsule_tempVec; vec2.set(circlePos, capsuleShape.length/2,0); vec2.rotate(circlePos,circlePos,capsuleAngle); v