Created
February 9, 2018 16:43
-
-
Save bcuz/8427a35f6ae16240e0e5ab7e57e67c7b to your computer and use it in GitHub Desktop.
This file has been truncated, but you can view the full file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @author Richard Davey <[email protected]> | |
* @copyright 2016 Photon Storm Ltd. | |
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} | |
* | |
* @overview | |
* | |
* Phaser - http://phaser.io | |
* | |
* v2.6.2 "Kore Springs" - Built: Fri Aug 26 2016 01:02:57 | |
* | |
* 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.0", | |
"description": "A JavaScript 2D physics engine.", | |
"author": "Stefan Hedman <[email protected]> (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.0" | |
} | |
} | |
},{}],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); | |
vec2.add(circlePos,circlePos,capsulePosition); | |
var result1 = this.circleConvex(capsuleBody,capsuleShape,circlePos,capsuleAngle, convexBody,convexShape,convexPosition,convexAngle, justTest, capsuleShape.radius); | |
vec2.set(circlePos,-capsuleShape.length/2, 0); | |
vec2.rotate(circlePos,circlePos,capsuleAngle); | |
vec2.add(circlePos,circlePos,capsulePosition); | |
var result2 = this.circleConvex(capsuleBody,capsuleShape,circlePos,capsuleAngle, convexBody,convexShape,convexPosition,convexAngle, justTest, capsuleShape.radius); | |
if(justTest && (result1 || result2)){ | |
return true; | |
} | |
// Check center rect | |
var r = convexCapsule_tempRect; | |
setConvexToCapsuleShapeMiddle(r,capsuleShape); | |
var result = this.convexConvex(convexBody,convexShape,convexPosition,convexAngle, capsuleBody,r,capsulePosition,capsuleAngle, justTest); | |
return result + result1 + result2; | |
}; | |
/** | |
* Capsule/line narrowphase | |
* @method lineCapsule | |
* @param {Body} lineBody | |
* @param {Line} lineShape | |
* @param {Array} linePosition | |
* @param {Number} lineAngle | |
* @param {Body} capsuleBody | |
* @param {Capsule} capsuleShape | |
* @param {Array} capsulePosition | |
* @param {Number} capsuleAngle | |
* @todo Implement me! | |
*/ | |
Narrowphase.prototype[Shape.CAPSULE | Shape.LINE] = | |
Narrowphase.prototype.lineCapsule = function( | |
lineBody, | |
lineShape, | |
linePosition, | |
lineAngle, | |
capsuleBody, | |
capsuleShape, | |
capsulePosition, | |
capsuleAngle, | |
justTest | |
){ | |
// TODO | |
if(justTest){ | |
return false; | |
} else { | |
return 0; | |
} | |
}; | |
var capsuleCapsule_tempVec1 = vec2.create(); | |
var capsuleCapsule_tempVec2 = vec2.create(); | |
var capsuleCapsule_tempRect1 = new Box({ width: 1, height: 1 }); | |
/** | |
* Capsule/capsule narrowphase | |
* @method capsuleCapsule | |
* @param {Body} bi | |
* @param {Capsule} si | |
* @param {Array} xi | |
* @param {Number} ai | |
* @param {Body} bj | |
* @param {Capsule} sj | |
* @param {Array} xj | |
* @param {Number} aj | |
*/ | |
Narrowphase.prototype[Shape.CAPSULE | Shape.CAPSULE] = | |
Narrowphase.prototype.capsuleCapsule = function(bi,si,xi,ai, bj,sj,xj,aj, justTest){ | |
var enableFrictionBefore; | |
// Check the circles | |
// Add offsets! | |
var circlePosi = capsuleCapsule_tempVec1, | |
circlePosj = capsuleCapsule_tempVec2; | |
var numContacts = 0; | |
// Need 4 circle checks, between all | |
for(var i=0; i<2; i++){ | |
vec2.set(circlePosi,(i===0?-1:1)*si.length/2,0); | |
vec2.rotate(circlePosi,circlePosi,ai); | |
vec2.add(circlePosi,circlePosi,xi); | |
for(var j=0; j<2; j++){ | |
vec2.set(circlePosj,(j===0?-1:1)*sj.length/2, 0); | |
vec2.rotate(circlePosj,circlePosj,aj); | |
vec2.add(circlePosj,circlePosj,xj); | |
// Temporarily turn off friction | |
if(this.enableFrictionReduction){ | |
enableFrictionBefore = this.enableFriction; | |
this.enableFriction = false; | |
} | |
var result = this.circleCircle(bi,si,circlePosi,ai, bj,sj,circlePosj,aj, justTest, si.radius, sj.radius); | |
if(this.enableFrictionReduction){ | |
this.enableFriction = enableFrictionBefore; | |
} | |
if(justTest && result){ | |
return true; | |
} | |
numContacts += result; | |
} | |
} | |
if(this.enableFrictionReduction){ | |
// Temporarily turn off friction | |
enableFrictionBefore = this.enableFriction; | |
this.enableFriction = false; | |
} | |
// Check circles against the center boxs | |
var rect = capsuleCapsule_tempRect1; | |
setConvexToCapsuleShapeMiddle(rect,si); | |
var result1 = this.convexCapsule(bi,rect,xi,ai, bj,sj,xj,aj, justTest); | |
if(this.enableFrictionReduction){ | |
this.enableFriction = enableFrictionBefore; | |
} | |
if(justTest && result1){ | |
return true; | |
} | |
numContacts += result1; | |
if(this.enableFrictionReduction){ | |
// Temporarily turn off friction | |
var enableFrictionBefore = this.enableFriction; | |
this.enableFriction = false; | |
} | |
setConvexToCapsuleShapeMiddle(rect,sj); | |
var result2 = this.convexCapsule(bj,rect,xj,aj, bi,si,xi,ai, justTest); | |
if(this.enableFrictionReduction){ | |
this.enableFriction = enableFrictionBefore; | |
} | |
if(justTest && result2){ | |
return true; | |
} | |
numContacts += result2; | |
if(this.enableFrictionReduction){ | |
if(numContacts && this.enableFriction){ | |
this.frictionEquations.push(this.createFrictionFromAverage(numContacts)); | |
} | |
} | |
return numContacts; | |
}; | |
/** | |
* Line/line narrowphase | |
* @method lineLine | |
* @param {Body} bodyA | |
* @param {Line} shapeA | |
* @param {Array} positionA | |
* @param {Number} angleA | |
* @param {Body} bodyB | |
* @param {Line} shapeB | |
* @param {Array} positionB | |
* @param {Number} angleB | |
* @todo Implement me! | |
*/ | |
Narrowphase.prototype[Shape.LINE | Shape.LINE] = | |
Narrowphase.prototype.lineLine = function( | |
bodyA, | |
shapeA, | |
positionA, | |
angleA, | |
bodyB, | |
shapeB, | |
positionB, | |
angleB, | |
justTest | |
){ | |
// TODO | |
if(justTest){ | |
return false; | |
} else { | |
return 0; | |
} | |
}; | |
/** | |
* Plane/line Narrowphase | |
* @method planeLine | |
* @param {Body} planeBody | |
* @param {Plane} planeShape | |
* @param {Array} planeOffset | |
* @param {Number} planeAngle | |
* @param {Body} lineBody | |
* @param {Line} lineShape | |
* @param {Array} lineOffset | |
* @param {Number} lineAngle | |
*/ | |
Narrowphase.prototype[Shape.PLANE | Shape.LINE] = | |
Narrowphase.prototype.planeLine = function(planeBody, planeShape, planeOffset, planeAngle, | |
lineBody, lineShape, lineOffset, lineAngle, justTest){ | |
var worldVertex0 = tmp1, | |
worldVertex1 = tmp2, | |
worldVertex01 = tmp3, | |
worldVertex11 = tmp4, | |
worldEdge = tmp5, | |
worldEdgeUnit = tmp6, | |
dist = tmp7, | |
worldNormal = tmp8, | |
worldTangent = tmp9, | |
verts = tmpArray, | |
numContacts = 0; | |
// Get start and end points | |
vec2.set(worldVertex0, -lineShape.length/2, 0); | |
vec2.set(worldVertex1, lineShape.length/2, 0); | |
// Not sure why we have to use worldVertex*1 here, but it won't work otherwise. Tired. | |
vec2.rotate(worldVertex01, worldVertex0, lineAngle); | |
vec2.rotate(worldVertex11, worldVertex1, lineAngle); | |
add(worldVertex01, worldVertex01, lineOffset); | |
add(worldVertex11, worldVertex11, lineOffset); | |
vec2.copy(worldVertex0,worldVertex01); | |
vec2.copy(worldVertex1,worldVertex11); | |
// Get vector along the line | |
sub(worldEdge, worldVertex1, worldVertex0); | |
vec2.normalize(worldEdgeUnit, worldEdge); | |
// Get tangent to the edge. | |
vec2.rotate90cw(worldTangent, worldEdgeUnit); | |
vec2.rotate(worldNormal, yAxis, planeAngle); | |
// Check line ends | |
verts[0] = worldVertex0; | |
verts[1] = worldVertex1; | |
for(var i=0; i<verts.length; i++){ | |
var v = verts[i]; | |
sub(dist, v, planeOffset); | |
var d = dot(dist,worldNormal); | |
if(d < 0){ | |
if(justTest){ | |
return true; | |
} | |
var c = this.createContactEquation(planeBody,lineBody,planeShape,lineShape); | |
numContacts++; | |
vec2.copy(c.normalA, worldNormal); | |
vec2.normalize(c.normalA,c.normalA); | |
// distance vector along plane normal | |
vec2.scale(dist, worldNormal, d); | |
// Vector from plane center to contact | |
sub(c.contactPointA, v, dist); | |
sub(c.contactPointA, c.contactPointA, planeBody.position); | |
// From line center to contact | |
sub(c.contactPointB, v, lineOffset); | |
add(c.contactPointB, c.contactPointB, lineOffset); | |
sub(c.contactPointB, c.contactPointB, lineBody.position); | |
this.contactEquations.push(c); | |
if(!this.enableFrictionReduction){ | |
if(this.enableFriction){ | |
this.frictionEquations.push(this.createFrictionFromContact(c)); | |
} | |
} | |
} | |
} | |
if(justTest){ | |
return false; | |
} | |
if(!this.enableFrictionReduction){ | |
if(numContacts && this.enableFriction){ | |
this.frictionEquations.push(this.createFrictionFromAverage(numContacts)); | |
} | |
} | |
return numContacts; | |
}; | |
Narrowphase.prototype[Shape.PARTICLE | Shape.CAPSULE] = | |
Narrowphase.prototype.particleCapsule = function( | |
particleBody, | |
particleShape, | |
particlePosition, | |
particleAngle, | |
capsuleBody, | |
capsuleShape, | |
capsulePosition, | |
capsuleAngle, | |
justTest | |
){ | |
return this.circleLine(particleBody,particleShape,particlePosition,particleAngle, capsuleBody,capsuleShape,capsulePosition,capsuleAngle, justTest, capsuleShape.radius, 0); | |
}; | |
/** | |
* Circle/line Narrowphase | |
* @method circleLine | |
* @param {Body} circleBody | |
* @param {Circle} circleShape | |
* @param {Array} circleOffset | |
* @param {Number} circleAngle | |
* @param {Body} lineBody | |
* @param {Line} lineShape | |
* @param {Array} lineOffset | |
* @param {Number} lineAngle | |
* @param {Boolean} justTest If set to true, this function will return the result (intersection or not) without adding equations. | |
* @param {Number} lineRadius Radius to add to the line. Can be used to test Capsules. | |
* @param {Number} circleRadius If set, this value overrides the circle shape radius. | |
*/ | |
Narrowphase.prototype[Shape.CIRCLE | Shape.LINE] = | |
Narrowphase.prototype.circleLine = function( | |
circleBody, | |
circleShape, | |
circleOffset, | |
circleAngle, | |
lineBody, | |
lineShape, | |
lineOffset, | |
lineAngle, | |
justTest, | |
lineRadius, | |
circleRadius | |
){ | |
var lineRadius = lineRadius || 0, | |
circleRadius = typeof(circleRadius)!=="undefined" ? circleRadius : circleShape.radius, | |
orthoDist = tmp1, | |
lineToCircleOrthoUnit = tmp2, | |
projectedPoint = tmp3, | |
centerDist = tmp4, | |
worldTangent = tmp5, | |
worldEdge = tmp6, | |
worldEdgeUnit = tmp7, | |
worldVertex0 = tmp8, | |
worldVertex1 = tmp9, | |
worldVertex01 = tmp10, | |
worldVertex11 = tmp11, | |
dist = tmp12, | |
lineToCircle = tmp13, | |
lineEndToLineRadius = tmp14, | |
verts = tmpArray; | |
// Get start and end points | |
vec2.set(worldVertex0, -lineShape.length/2, 0); | |
vec2.set(worldVertex1, lineShape.length/2, 0); | |
// Not sure why we have to use worldVertex*1 here, but it won't work otherwise. Tired. | |
vec2.rotate(worldVertex01, worldVertex0, lineAngle); | |
vec2.rotate(worldVertex11, worldVertex1, lineAngle); | |
add(worldVertex01, worldVertex01, lineOffset); | |
add(worldVertex11, worldVertex11, lineOffset); | |
vec2.copy(worldVertex0,worldVertex01); | |
vec2.copy(worldVertex1,worldVertex11); | |
// Get vector along the line | |
sub(worldEdge, worldVertex1, worldVertex0); | |
vec2.normalize(worldEdgeUnit, worldEdge); | |
// Get tangent to the edge. | |
vec2.rotate90cw(worldTangent, worldEdgeUnit); | |
// Check distance from the plane spanned by the edge vs the circle | |
sub(dist, circleOffset, worldVertex0); | |
var d = dot(dist, worldTangent); // Distance from center of line to circle center | |
sub(centerDist, worldVertex0, lineOffset); | |
sub(lineToCircle, circleOffset, lineOffset); | |
var radiusSum = circleRadius + lineRadius; | |
if(Math.abs(d) < radiusSum){ | |
// Now project the circle onto the edge | |
vec2.scale(orthoDist, worldTangent, d); | |
sub(projectedPoint, circleOffset, orthoDist); | |
// Add the missing line radius | |
vec2.scale(lineToCircleOrthoUnit, worldTangent, dot(worldTangent, lineToCircle)); | |
vec2.normalize(lineToCircleOrthoUnit,lineToCircleOrthoUnit); | |
vec2.scale(lineToCircleOrthoUnit, lineToCircleOrthoUnit, lineRadius); | |
add(projectedPoint,projectedPoint,lineToCircleOrthoUnit); | |
// Check if the point is within the edge span | |
var pos = dot(worldEdgeUnit, projectedPoint); | |
var pos0 = dot(worldEdgeUnit, worldVertex0); | |
var pos1 = dot(worldEdgeUnit, worldVertex1); | |
if(pos > pos0 && pos < pos1){ | |
// We got contact! | |
if(justTest){ | |
return true; | |
} | |
var c = this.createContactEquation(circleBody,lineBody,circleShape,lineShape); | |
vec2.scale(c.normalA, orthoDist, -1); | |
vec2.normalize(c.normalA, c.normalA); | |
vec2.scale( c.contactPointA, c.normalA, circleRadius); | |
add(c.contactPointA, c.contactPointA, circleOffset); | |
sub(c.contactPointA, c.contactPointA, circleBody.position); | |
sub(c.contactPointB, projectedPoint, lineOffset); | |
add(c.contactPointB, c.contactPointB, lineOffset); | |
sub(c.contactPointB, c.contactPointB, lineBody.position); | |
this.contactEquations.push(c); | |
if(this.enableFriction){ | |
this.frictionEquations.push(this.createFrictionFromContact(c)); | |
} | |
return 1; | |
} | |
} | |
// Add corner | |
verts[0] = worldVertex0; | |
verts[1] = worldVertex1; | |
for(var i=0; i<verts.length; i++){ | |
var v = verts[i]; | |
sub(dist, v, circleOffset); | |
if(vec2.squaredLength(dist) < Math.pow(radiusSum, 2)){ | |
if(justTest){ | |
return true; | |
} | |
var c = this.createContactEquation(circleBody,lineBody,circleShape,lineShape); | |
vec2.copy(c.normalA, dist); | |
vec2.normalize(c.normalA,c.normalA); | |
// Vector from circle to contact point is the normal times the circle radius | |
vec2.scale(c.contactPointA, c.normalA, circleRadius); | |
add(c.contactPointA, c.contactPointA, circleOffset); | |
sub(c.contactPointA, c.contactPointA, circleBody.position); | |
sub(c.contactPointB, v, lineOffset); | |
vec2.scale(lineEndToLineRadius, c.normalA, -lineRadius); | |
add(c.contactPointB, c.contactPointB, lineEndToLineRadius); | |
add(c.contactPointB, c.contactPointB, lineOffset); | |
sub(c.contactPointB, c.contactPointB, lineBody.position); | |
this.contactEquations.push(c); | |
if(this.enableFriction){ | |
this.frictionEquations.push(this.createFrictionFromContact(c)); | |
} | |
return 1; | |
} | |
} | |
return 0; | |
}; | |
/** | |
* Circle/capsule Narrowphase | |
* @method circleCapsule | |
* @param {Body} bi | |
* @param {Circle} si | |
* @param {Array} xi | |
* @param {Number} ai | |
* @param {Body} bj | |
* @param {Line} sj | |
* @param {Array} xj | |
* @param {Number} aj | |
*/ | |
Narrowphase.prototype[Shape.CIRCLE | Shape.CAPSULE] = | |
Narrowphase.prototype.circleCapsule = function(bi,si,xi,ai, bj,sj,xj,aj, justTest){ | |
return this.circleLine(bi,si,xi,ai, bj,sj,xj,aj, justTest, sj.radius); | |
}; | |
/** | |
* Circle/convex Narrowphase. | |
* @method circleConvex | |
* @param {Body} circleBody | |
* @param {Circle} circleShape | |
* @param {Array} circleOffset | |
* @param {Number} circleAngle | |
* @param {Body} convexBody | |
* @param {Convex} convexShape | |
* @param {Array} convexOffset | |
* @param {Number} convexAngle | |
* @param {Boolean} justTest | |
* @param {Number} circleRadius | |
*/ | |
Narrowphase.prototype[Shape.CIRCLE | Shape.CONVEX] = | |
Narrowphase.prototype[Shape.CIRCLE | Shape.BOX] = | |
Narrowphase.prototype.circleConvex = function( | |
circleBody, | |
circleShape, | |
circleOffset, | |
circleAngle, | |
convexBody, | |
convexShape, | |
convexOffset, | |
convexAngle, | |
justTest, | |
circleRadius | |
){ | |
var circleRadius = typeof(circleRadius)==="number" ? circleRadius : circleShape.radius; | |
var worldVertex0 = tmp1, | |
worldVertex1 = tmp2, | |
worldEdge = tmp3, | |
worldEdgeUnit = tmp4, | |
worldNormal = tmp5, | |
centerDist = tmp6, | |
convexToCircle = tmp7, | |
orthoDist = tmp8, | |
projectedPoint = tmp9, | |
dist = tmp10, | |
worldVertex = tmp11, | |
closestEdge = -1, | |
closestEdgeDistance = null, | |
closestEdgeOrthoDist = tmp12, | |
closestEdgeProjectedPoint = tmp13, | |
candidate = tmp14, | |
candidateDist = tmp15, | |
minCandidate = tmp16, | |
found = false, | |
minCandidateDistance = Number.MAX_VALUE; | |
var numReported = 0; | |
// New algorithm: | |
// 1. Check so center of circle is not inside the polygon. If it is, this wont work... | |
// 2. For each edge | |
// 2. 1. Get point on circle that is closest to the edge (scale normal with -radius) | |
// 2. 2. Check if point is inside. | |
var verts = convexShape.vertices; | |
// Check all edges first | |
for(var i=0; i!==verts.length+1; i++){ | |
var v0 = verts[i%verts.length], | |
v1 = verts[(i+1)%verts.length]; | |
vec2.rotate(worldVertex0, v0, convexAngle); | |
vec2.rotate(worldVertex1, v1, convexAngle); | |
add(worldVertex0, worldVertex0, convexOffset); | |
add(worldVertex1, worldVertex1, convexOffset); | |
sub(worldEdge, worldVertex1, worldVertex0); | |
vec2.normalize(worldEdgeUnit, worldEdge); | |
// Get tangent to the edge. Points out of the Convex | |
vec2.rotate90cw(worldNormal, worldEdgeUnit); | |
// Get point on circle, closest to the polygon | |
vec2.scale(candidate,worldNormal,-circleShape.radius); | |
add(candidate,candidate,circleOffset); | |
if(pointInConvex(candidate,convexShape,convexOffset,convexAngle)){ | |
vec2.sub(candidateDist,worldVertex0,candidate); | |
var candidateDistance = Math.abs(vec2.dot(candidateDist,worldNormal)); | |
if(candidateDistance < minCandidateDistance){ | |
vec2.copy(minCandidate,candidate); | |
minCandidateDistance = candidateDistance; | |
vec2.scale(closestEdgeProjectedPoint,worldNormal,candidateDistance); | |
vec2.add(closestEdgeProjectedPoint,closestEdgeProjectedPoint,candidate); | |
found = true; | |
} | |
} | |
} | |
if(found){ | |
if(justTest){ | |
return true; | |
} | |
var c = this.createContactEquation(circleBody,convexBody,circleShape,convexShape); | |
vec2.sub(c.normalA, minCandidate, circleOffset); | |
vec2.normalize(c.normalA, c.normalA); | |
vec2.scale(c.contactPointA, c.normalA, circleRadius); | |
add(c.contactPointA, c.contactPointA, circleOffset); | |
sub(c.contactPointA, c.contactPointA, circleBody.position); | |
sub(c.contactPointB, closestEdgeProjectedPoint, convexOffset); | |
add(c.contactPointB, c.contactPointB, convexOffset); | |
sub(c.contactPointB, c.contactPointB, convexBody.position); | |
this.contactEquations.push(c); | |
if(this.enableFriction){ | |
this.frictionEquations.push( this.createFrictionFromContact(c) ); | |
} | |
return 1; | |
} | |
// Check all vertices | |
if(circleRadius > 0){ | |
for(var i=0; i<verts.length; i++){ | |
var localVertex = verts[i]; | |
vec2.rotate(worldVertex, localVertex, convexAngle); | |
add(worldVertex, worldVertex, convexOffset); | |
sub(dist, worldVertex, circleOffset); | |
if(vec2.squaredLength(dist) < Math.pow(circleRadius, 2)){ | |
if(justTest){ | |
return true; | |
} | |
var c = this.createContactEquation(circleBody,convexBody,circleShape,convexShape); | |
vec2.copy(c.normalA, dist); | |
vec2.normalize(c.normalA,c.normalA); | |
// Vector from circle to contact point is the normal times the circle radius | |
vec2.scale(c.contactPointA, c.normalA, circleRadius); | |
add(c.contactPointA, c.contactPointA, circleOffset); | |
sub(c.contactPointA, c.contactPointA, circleBody.position); | |
sub(c.contactPointB, worldVertex, convexOffset); | |
add(c.contactPointB, c.contactPointB, convexOffset); | |
sub(c.contactPointB, c.contactPointB, convexBody.position); | |
this.contactEquations.push(c); | |
if(this.enableFriction){ | |
this.frictionEquations.push(this.createFrictionFromContact(c)); | |
} | |
return 1; | |
} | |
} | |
} | |
return 0; | |
}; | |
var pic_worldVertex0 = vec2.create(), | |
pic_worldVertex1 = vec2.create(), | |
pic_r0 = vec2.create(), | |
pic_r1 = vec2.create(); | |
/* | |
* Check if a point is in a polygon | |
*/ | |
function pointInConvex(worldPoint,convexShape,convexOffset,convexAngle){ | |
var worldVertex0 = pic_worldVertex0, | |
worldVertex1 = pic_worldVertex1, | |
r0 = pic_r0, | |
r1 = pic_r1, | |
point = worldPoint, | |
verts = convexShape.vertices, | |
lastCross = null; | |
for(var i=0; i!==verts.length+1; i++){ | |
var v0 = verts[i%verts.length], | |
v1 = verts[(i+1)%verts.length]; | |
// Transform vertices to world | |
// @todo The point should be transformed to local coordinates in the convex, no need to transform each vertex | |
vec2.rotate(worldVertex0, v0, convexAngle); | |
vec2.rotate(worldVertex1, v1, convexAngle); | |
add(worldVertex0, worldVertex0, convexOffset); | |
add(worldVertex1, worldVertex1, convexOffset); | |
sub(r0, worldVertex0, point); | |
sub(r1, worldVertex1, point); | |
var cross = vec2.crossLength(r0,r1); | |
if(lastCross===null){ | |
lastCross = cross; | |
} | |
// If we got a different sign of the distance vector, the point is out of the polygon | |
if(cross*lastCross <= 0){ | |
return false; | |
} | |
lastCross = cross; | |
} | |
return true; | |
} | |
/** | |
* Particle/convex Narrowphase | |
* @method particleConvex | |
* @param {Body} particleBody | |
* @param {Particle} particleShape | |
* @param {Array} particleOffset | |
* @param {Number} particleAngle | |
* @param {Body} convexBody | |
* @param {Convex} convexShape | |
* @param {Array} convexOffset | |
* @param {Number} convexAngle | |
* @param {Boolean} justTest | |
* @todo use pointInConvex and code more similar to circleConvex | |
* @todo don't transform each vertex, but transform the particle position to convex-local instead | |
*/ | |
Narrowphase.prototype[Shape.PARTICLE | Shape.CONVEX] = | |
Narrowphase.prototype[Shape.PARTICLE | Shape.BOX] = | |
Narrowphase.prototype.particleConvex = function( | |
particleBody, | |
particleShape, | |
particleOffset, | |
particleAngle, | |
convexBody, | |
convexShape, | |
convexOffset, | |
convexAngle, | |
justTest | |
){ | |
var worldVertex0 = tmp1, | |
worldVertex1 = tmp2, | |
worldEdge = tmp3, | |
worldEdgeUnit = tmp4, | |
worldTangent = tmp5, | |
centerDist = tmp6, | |
convexToparticle = tmp7, | |
orthoDist = tmp8, | |
projectedPoint = tmp9, | |
dist = tmp10, | |
worldVertex = tmp11, | |
closestEdge = -1, | |
closestEdgeDistance = null, | |
closestEdgeOrthoDist = tmp12, | |
closestEdgeProjectedPoint = tmp13, | |
r0 = tmp14, // vector from particle to vertex0 | |
r1 = tmp15, | |
localPoint = tmp16, | |
candidateDist = tmp17, | |
minEdgeNormal = tmp18, | |
minCandidateDistance = Number.MAX_VALUE; | |
var numReported = 0, | |
found = false, | |
verts = convexShape.vertices; | |
// Check if the particle is in the polygon at all | |
if(!pointInConvex(particleOffset,convexShape,convexOffset,convexAngle)){ | |
return 0; | |
} | |
if(justTest){ | |
return true; | |
} | |
// Check edges first | |
var lastCross = null; | |
for(var i=0; i!==verts.length+1; i++){ | |
var v0 = verts[i%verts.length], | |
v1 = verts[(i+1)%verts.length]; | |
// Transform vertices to world | |
vec2.rotate(worldVertex0, v0, convexAngle); | |
vec2.rotate(worldVertex1, v1, convexAngle); | |
add(worldVertex0, worldVertex0, convexOffset); | |
add(worldVertex1, worldVertex1, convexOffset); | |
// Get world edge | |
sub(worldEdge, worldVertex1, worldVertex0); | |
vec2.normalize(worldEdgeUnit, worldEdge); | |
// Get tangent to the edge. Points out of the Convex | |
vec2.rotate90cw(worldTangent, worldEdgeUnit); | |
// Check distance from the infinite line (spanned by the edge) to the particle | |
sub(dist, particleOffset, worldVertex0); | |
var d = dot(dist, worldTangent); | |
sub(centerDist, worldVertex0, convexOffset); | |
sub(convexToparticle, particleOffset, convexOffset); | |
vec2.sub(candidateDist,worldVertex0,particleOffset); | |
var candidateDistance = Math.abs(vec2.dot(candidateDist,worldTangent)); | |
if(candidateDistance < minCandidateDistance){ | |
minCandidateDistance = candidateDistance; | |
vec2.scale(closestEdgeProjectedPoint,worldTangent,candidateDistance); | |
vec2.add(closestEdgeProjectedPoint,closestEdgeProjectedPoint,particleOffset); | |
vec2.copy(minEdgeNormal,worldTangent); | |
found = true; | |
} | |
} | |
if(found){ | |
var c = this.createContactEquation(particleBody,convexBody,particleShape,convexShape); | |
vec2.scale(c.normalA, minEdgeNormal, -1); | |
vec2.normalize(c.normalA, c.normalA); | |
// Particle has no extent to the contact point | |
vec2.set(c.contactPointA, 0, 0); | |
add(c.contactPointA, c.contactPointA, particleOffset); | |
sub(c.contactPointA, c.contactPointA, particleBody.position); | |
// From convex center to point | |
sub(c.contactPointB, closestEdgeProjectedPoint, convexOffset); | |
add(c.contactPointB, c.contactPointB, convexOffset); | |
sub(c.contactPointB, c.contactPointB, convexBody.position); | |
this.contactEquations.push(c); | |
if(this.enableFriction){ | |
this.frictionEquations.push( this.createFrictionFromContact(c) ); | |
} | |
return 1; | |
} | |
return 0; | |
}; | |
/** | |
* Circle/circle Narrowphase | |
* @method circleCircle | |
* @param {Body} bodyA | |
* @param {Circle} shapeA | |
* @param {Array} offsetA | |
* @param {Number} angleA | |
* @param {Body} bodyB | |
* @param {Circle} shapeB | |
* @param {Array} offsetB | |
* @param {Number} angleB | |
* @param {Boolean} justTest | |
* @param {Number} [radiusA] Optional radius to use for shapeA | |
* @param {Number} [radiusB] Optional radius to use for shapeB | |
*/ | |
Narrowphase.prototype[Shape.CIRCLE] = | |
Narrowphase.prototype.circleCircle = function( | |
bodyA, | |
shapeA, | |
offsetA, | |
angleA, | |
bodyB, | |
shapeB, | |
offsetB, | |
angleB, | |
justTest, | |
radiusA, | |
radiusB | |
){ | |
var dist = tmp1, | |
radiusA = radiusA || shapeA.radius, | |
radiusB = radiusB || shapeB.radius; | |
sub(dist,offsetA,offsetB); | |
var r = radiusA + radiusB; | |
if(vec2.squaredLength(dist) > Math.pow(r,2)){ | |
return 0; | |
} | |
if(justTest){ | |
return true; | |
} | |
var c = this.createContactEquation(bodyA,bodyB,shapeA,shapeB); | |
sub(c.normalA, offsetB, offsetA); | |
vec2.normalize(c.normalA,c.normalA); | |
vec2.scale( c.contactPointA, c.normalA, radiusA); | |
vec2.scale( c.contactPointB, c.normalA, -radiusB); | |
add(c.contactPointA, c.contactPointA, offsetA); | |
sub(c.contactPointA, c.contactPointA, bodyA.position); | |
add(c.contactPointB, c.contactPointB, offsetB); | |
sub(c.contactPointB, c.contactPointB, bodyB.position); | |
this.contactEquations.push(c); | |
if(this.enableFriction){ | |
this.frictionEquations.push(this.createFrictionFromContact(c)); | |
} | |
return 1; | |
}; | |
/** | |
* Plane/Convex Narrowphase | |
* @method planeConvex | |
* @param {Body} planeBody | |
* @param {Plane} planeShape | |
* @param {Array} planeOffset | |
* @param {Number} planeAngle | |
* @param {Body} convexBody | |
* @param {Convex} convexShape | |
* @param {Array} convexOffset | |
* @param {Number} convexAngle | |
* @param {Boolean} justTest | |
*/ | |
Narrowphase.prototype[Shape.PLANE | Shape.CONVEX] = | |
Narrowphase.prototype[Shape.PLANE | Shape.BOX] = | |
Narrowphase.prototype.planeConvex = function( | |
planeBody, | |
planeShape, | |
planeOffset, | |
planeAngle, | |
convexBody, | |
convexShape, | |
convexOffset, | |
convexAngle, | |
justTest | |
){ | |
var worldVertex = tmp1, | |
worldNormal = tmp2, | |
dist = tmp3; | |
var numReported = 0; | |
vec2.rotate(worldNormal, yAxis, planeAngle); | |
for(var i=0; i!==convexShape.vertices.length; i++){ | |
var v = convexShape.vertices[i]; | |
vec2.rotate(worldVertex, v, convexAngle); | |
add(worldVertex, worldVertex, convexOffset); | |
sub(dist, worldVertex, planeOffset); | |
if(dot(dist,worldNormal) <= 0){ | |
if(justTest){ | |
return true; | |
} | |
// Found vertex | |
numReported++; | |
var c = this.createContactEquation(planeBody,convexBody,planeShape,convexShape); | |
sub(dist, worldVertex, planeOffset); | |
vec2.copy(c.normalA, worldNormal); | |
var d = dot(dist, c.normalA); | |
vec2.scale(dist, c.normalA, d); | |
// rj is from convex center to contact | |
sub(c.contactPointB, worldVertex, convexBody.position); | |
// ri is from plane center to contact | |
sub( c.contactPointA, worldVertex, dist); | |
sub( c.contactPointA, c.contactPointA, planeBody.position); | |
this.contactEquations.push(c); | |
if(!this.enableFrictionReduction){ | |
if(this.enableFriction){ | |
this.frictionEquations.push(this.createFrictionFromContact(c)); | |
} | |
} | |
} | |
} | |
if(this.enableFrictionReduction){ | |
if(this.enableFriction && numReported){ | |
this.frictionEquations.push(this.createFrictionFromAverage(numReported)); | |
} | |
} | |
return numReported; | |
}; | |
/** | |
* Narrowphase for particle vs plane | |
* @method particlePlane | |
* @param {Body} particleBody | |
* @param {Particle} particleShape | |
* @param {Array} particleOffset | |
* @param {Number} particleAngle | |
* @param {Body} planeBody | |
* @param {Plane} planeShape | |
* @param {Array} planeOffset | |
* @param {Number} planeAngle | |
* @param {Boolean} justTest | |
*/ | |
Narrowphase.prototype[Shape.PARTICLE | Shape.PLANE] = | |
Narrowphase.prototype.particlePlane = function( | |
particleBody, | |
particleShape, | |
particleOffset, | |
particleAngle, | |
planeBody, | |
planeShape, | |
planeOffset, | |
planeAngle, | |
justTest | |
){ | |
var dist = tmp1, | |
worldNormal = tmp2; | |
planeAngle = planeAngle || 0; | |
sub(dist, particleOffset, planeOffset); | |
vec2.rotate(worldNormal, yAxis, planeAngle); | |
var d = dot(dist, worldNormal); | |
if(d > 0){ | |
return 0; | |
} | |
if(justTest){ | |
return true; | |
} | |
var c = this.createContactEquation(planeBody,particleBody,planeShape,particleShape); | |
vec2.copy(c.normalA, worldNormal); | |
vec2.scale( dist, c.normalA, d ); | |
// dist is now the distance vector in the normal direction | |
// ri is the particle position projected down onto the plane, from the plane center | |
sub( c.contactPointA, particleOffset, dist); | |
sub( c.contactPointA, c.contactPointA, planeBody.position); | |
// rj is from the body center to the particle center | |
sub( c.contactPointB, particleOffset, particleBody.position ); | |
this.contactEquations.push(c); | |
if(this.enableFriction){ | |
this.frictionEquations.push(this.createFrictionFromContact(c)); | |
} | |
return 1; | |
}; | |
/** | |
* Circle/Particle Narrowphase | |
* @method circleParticle | |
* @param {Body} circleBody | |
* @param {Circle} circleShape | |
* @param {Array} circleOffset | |
* @param {Number} circleAngle | |
* @param {Body} particleBody | |
* @param {Particle} particleShape | |
* @param {Array} particleOffset | |
* @param {Number} particleAngle | |
* @param {Boolean} justTest | |
*/ | |
Narrowphase.prototype[Shape.CIRCLE | Shape.PARTICLE] = | |
Narrowphase.prototype.circleParticle = function( | |
circleBody, | |
circleShape, | |
circleOffset, | |
circleAngle, | |
particleBody, | |
particleShape, | |
particleOffset, | |
particleAngle, | |
justTest | |
){ | |
var dist = tmp1; | |
sub(dist, particleOffset, circleOffset); | |
if(vec2.squaredLength(dist) > Math.pow(circleShape.radius, 2)){ | |
return 0; | |
} | |
if(justTest){ | |
return true; | |
} | |
var c = this.createContactEquation(circleBody,particleBody,circleShape,particleShape); | |
vec2.copy(c.normalA, dist); | |
vec2.normalize(c.normalA,c.normalA); | |
// Vector from circle to contact point is the normal times the circle radius | |
vec2.scale(c.contactPointA, c.normalA, circleShape.radius); | |
add(c.contactPointA, c.contactPointA, circleOffset); | |
sub(c.contactPointA, c.contactPointA, circleBody.position); | |
// Vector from particle center to contact point is zero | |
sub(c.contactPointB, particleOffset, particleBody.position); | |
this.contactEquations.push(c); | |
if(this.enableFriction){ | |
this.frictionEquations.push(this.createFrictionFromContact(c)); | |
} | |
return 1; | |
}; | |
var planeCapsule_tmpCircle = new Circle({ radius: 1 }), | |
planeCapsule_tmp1 = vec2.create(), | |
planeCapsule_tmp2 = vec2.create(), | |
planeCapsule_tmp3 = vec2.create(); | |
/** | |
* @method planeCapsule | |
* @param {Body} planeBody | |
* @param {Circle} planeShape | |
* @param {Array} planeOffset | |
* @param {Number} planeAngle | |
* @param {Body} capsuleBody | |
* @param {Particle} capsuleShape | |
* @param {Array} capsuleOffset | |
* @param {Number} capsuleAngle | |
* @param {Boolean} justTest | |
*/ | |
Narrowphase.prototype[Shape.PLANE | Shape.CAPSULE] = | |
Narrowphase.prototype.planeCapsule = function( | |
planeBody, | |
planeShape, | |
planeOffset, | |
planeAngle, | |
capsuleBody, | |
capsuleShape, | |
capsuleOffset, | |
capsuleAngle, | |
justTest | |
){ | |
var end1 = planeCapsule_tmp1, | |
end2 = planeCapsule_tmp2, | |
circle = planeCapsule_tmpCircle, | |
dst = planeCapsule_tmp3; | |
// Compute world end positions | |
vec2.set(end1, -capsuleShape.length/2, 0); | |
vec2.rotate(end1,end1,capsuleAngle); | |
add(end1,end1,capsuleOffset); | |
vec2.set(end2, capsuleShape.length/2, 0); | |
vec2.rotate(end2,end2,capsuleAngle); | |
add(end2,end2,capsuleOffset); | |
circle.radius = capsuleShape.radius; | |
var enableFrictionBefore; | |
// Temporarily turn off friction | |
if(this.enableFrictionReduction){ | |
enableFrictionBefore = this.enableFriction; | |
this.enableFriction = false; | |
} | |
// Do Narrowphase as two circles | |
var numContacts1 = this.circlePlane(capsuleBody,circle,end1,0, planeBody,planeShape,planeOffset,planeAngle, justTest), | |
numContacts2 = this.circlePlane(capsuleBody,circle,end2,0, planeBody,planeShape,planeOffset,planeAngle, justTest); | |
// Restore friction | |
if(this.enableFrictionReduction){ | |
this.enableFriction = enableFrictionBefore; | |
} | |
if(justTest){ | |
return numContacts1 || numContacts2; | |
} else { | |
var numTotal = numContacts1 + numContacts2; | |
if(this.enableFrictionReduction){ | |
if(numTotal){ | |
this.frictionEquations.push(this.createFrictionFromAverage(numTotal)); | |
} | |
} | |
return numTotal; | |
} | |
}; | |
/** | |
* Creates ContactEquations and FrictionEquations for a collision. | |
* @method circlePlane | |
* @param {Body} bi The first body that should be connected to the equations. | |
* @param {Circle} si The circle shape participating in the collision. | |
* @param {Array} xi Extra offset to take into account for the Shape, in addition to the one in circleBody.position. Will *not* be rotated by circleBody.angle (maybe it should, for sake of homogenity?). Set to null if none. | |
* @param {Body} bj The second body that should be connected to the equations. | |
* @param {Plane} sj The Plane shape that is participating | |
* @param {Array} xj Extra offset for the plane shape. | |
* @param {Number} aj Extra angle to apply to the plane | |
*/ | |
Narrowphase.prototype[Shape.CIRCLE | Shape.PLANE] = | |
Narrowphase.prototype.circlePlane = function( bi,si,xi,ai, bj,sj,xj,aj, justTest ){ | |
var circleBody = bi, | |
circleShape = si, | |
circleOffset = xi, // Offset from body center, rotated! | |
planeBody = bj, | |
shapeB = sj, | |
planeOffset = xj, | |
planeAngle = aj; | |
planeAngle = planeAngle || 0; | |
// Vector from plane to circle | |
var planeToCircle = tmp1, | |
worldNormal = tmp2, | |
temp = tmp3; | |
sub(planeToCircle, circleOffset, planeOffset); | |
// World plane normal | |
vec2.rotate(worldNormal, yAxis, planeAngle); | |
// Normal direction distance | |
var d = dot(worldNormal, planeToCircle); | |
if(d > circleShape.radius){ | |
return 0; // No overlap. Abort. | |
} | |
if(justTest){ | |
return true; | |
} | |
// Create contact | |
var contact = this.createContactEquation(planeBody,circleBody,sj,si); | |
// ni is the plane world normal | |
vec2.copy(contact.normalA, worldNormal); | |
// rj is the vector from circle center to the contact point | |
vec2.scale(contact.contactPointB, contact.normalA, -circleShape.radius); | |
add(contact.contactPointB, contact.contactPointB, circleOffset); | |
sub(contact.contactPointB, contact.contactPointB, circleBody.position); | |
// ri is the distance from plane center to contact. | |
vec2.scale(temp, contact.normalA, d); | |
sub(contact.contactPointA, planeToCircle, temp ); // Subtract normal distance vector from the distance vector | |
add(contact.contactPointA, contact.contactPointA, planeOffset); | |
sub(contact.contactPointA, contact.contactPointA, planeBody.position); | |
this.contactEquations.push(contact); | |
if(this.enableFriction){ | |
this.frictionEquations.push( this.createFrictionFromContact(contact) ); | |
} | |
return 1; | |
}; | |
/** | |
* Convex/convex Narrowphase.See <a href="http://www.altdevblogaday.com/2011/05/13/contact-generation-between-3d-convex-meshes/">this article</a> for more info. | |
* @method convexConvex | |
* @param {Body} bi | |
* @param {Convex} si | |
* @param {Array} xi | |
* @param {Number} ai | |
* @param {Body} bj | |
* @param {Convex} sj | |
* @param {Array} xj | |
* @param {Number} aj | |
*/ | |
Narrowphase.prototype[Shape.CONVEX] = | |
Narrowphase.prototype[Shape.CONVEX | Shape.BOX] = | |
Narrowphase.prototype[Shape.BOX] = | |
Narrowphase.prototype.convexConvex = function( bi,si,xi,ai, bj,sj,xj,aj, justTest, precision ){ | |
var sepAxis = tmp1, | |
worldPoint = tmp2, | |
worldPoint0 = tmp3, | |
worldPoint1 = tmp4, | |
worldEdge = tmp5, | |
projected = tmp6, | |
penetrationVec = tmp7, | |
dist = tmp8, | |
worldNormal = tmp9, | |
numContacts = 0, | |
precision = typeof(precision) === 'number' ? precision : 0; | |
var found = Narrowphase.findSeparatingAxis(si,xi,ai,sj,xj,aj,sepAxis); | |
if(!found){ | |
return 0; | |
} | |
// Make sure the separating axis is directed from shape i to shape j | |
sub(dist,xj,xi); | |
if(dot(sepAxis,dist) > 0){ | |
vec2.scale(sepAxis,sepAxis,-1); | |
} | |
// Find edges with normals closest to the separating axis | |
var closestEdge1 = Narrowphase.getClosestEdge(si,ai,sepAxis,true), // Flipped axis | |
closestEdge2 = Narrowphase.getClosestEdge(sj,aj,sepAxis); | |
if(closestEdge1 === -1 || closestEdge2 === -1){ | |
return 0; | |
} | |
// Loop over the shapes | |
for(var k=0; k<2; k++){ | |
var closestEdgeA = closestEdge1, | |
closestEdgeB = closestEdge2, | |
shapeA = si, shapeB = sj, | |
offsetA = xi, offsetB = xj, | |
angleA = ai, angleB = aj, | |
bodyA = bi, bodyB = bj; | |
if(k === 0){ | |
// Swap! | |
var tmp; | |
tmp = closestEdgeA; | |
closestEdgeA = closestEdgeB; | |
closestEdgeB = tmp; | |
tmp = shapeA; | |
shapeA = shapeB; | |
shapeB = tmp; | |
tmp = offsetA; | |
offsetA = offsetB; | |
offsetB = tmp; | |
tmp = angleA; | |
angleA = angleB; | |
angleB = tmp; | |
tmp = bodyA; | |
bodyA = bodyB; | |
bodyB = tmp; | |
} | |
// Loop over 2 points in convex B | |
for(var j=closestEdgeB; j<closestEdgeB+2; j++){ | |
// Get world point | |
var v = shapeB.vertices[(j+shapeB.vertices.length)%shapeB.vertices.length]; | |
vec2.rotate(worldPoint, v, angleB); | |
add(worldPoint, worldPoint, offsetB); | |
var insideNumEdges = 0; | |
// Loop over the 3 closest edges in convex A | |
for(var i=closestEdgeA-1; i<closestEdgeA+2; i++){ | |
var v0 = shapeA.vertices[(i +shapeA.vertices.length)%shapeA.vertices.length], | |
v1 = shapeA.vertices[(i+1+shapeA.vertices.length)%shapeA.vertices.length]; | |
// Construct the edge | |
vec2.rotate(worldPoint0, v0, angleA); | |
vec2.rotate(worldPoint1, v1, angleA); | |
add(worldPoint0, worldPoint0, offsetA); | |
add(worldPoint1, worldPoint1, offsetA); | |
sub(worldEdge, worldPoint1, worldPoint0); | |
vec2.rotate90cw(worldNormal, worldEdge); // Normal points out of convex 1 | |
vec2.normalize(worldNormal,worldNormal); | |
sub(dist, worldPoint, worldPoint0); | |
var d = dot(worldNormal,dist); | |
if((i === closestEdgeA && d <= precision) || (i !== closestEdgeA && d <= 0)){ | |
insideNumEdges++; | |
} | |
} | |
if(insideNumEdges >= 3){ | |
if(justTest){ | |
return true; | |
} | |
// worldPoint was on the "inside" side of each of the 3 checked edges. | |
// Project it to the center edge and use the projection direction as normal | |
// Create contact | |
var c = this.createContactEquation(bodyA,bodyB,shapeA,shapeB); | |
numContacts++; | |
// Get center edge from body A | |
var v0 = shapeA.vertices[(closestEdgeA) % shapeA.vertices.length], | |
v1 = shapeA.vertices[(closestEdgeA+1) % shapeA.vertices.length]; | |
// Construct the edge | |
vec2.rotate(worldPoint0, v0, angleA); | |
vec2.rotate(worldPoint1, v1, angleA); | |
add(worldPoint0, worldPoint0, offsetA); | |
add(worldPoint1, worldPoint1, offsetA); | |
sub(worldEdge, worldPoint1, worldPoint0); | |
vec2.rotate90cw(c.normalA, worldEdge); // Normal points out of convex A | |
vec2.normalize(c.normalA,c.normalA); | |
sub(dist, worldPoint, worldPoint0); // From edge point to the penetrating point | |
var d = dot(c.normalA,dist); // Penetration | |
vec2.scale(penetrationVec, c.normalA, d); // Vector penetration | |
sub(c.contactPointA, worldPoint, offsetA); | |
sub(c.contactPointA, c.contactPointA, penetrationVec); | |
add(c.contactPointA, c.contactPointA, offsetA); | |
sub(c.contactPointA, c.contactPointA, bodyA.position); | |
sub(c.contactPointB, worldPoint, offsetB); | |
add(c.contactPointB, c.contactPointB, offsetB); | |
sub(c.contactPointB, c.contactPointB, bodyB.position); | |
this.contactEquations.push(c); | |
// Todo reduce to 1 friction equation if we have 2 contact points | |
if(!this.enableFrictionReduction){ | |
if(this.enableFriction){ | |
this.frictionEquations.push(this.createFrictionFromContact(c)); | |
} | |
} | |
} | |
} | |
} | |
if(this.enableFrictionReduction){ | |
if(this.enableFriction && numContacts){ | |
this.frictionEquations.push(this.createFrictionFromAverage(numContacts)); | |
} | |
} | |
return numContacts; | |
}; | |
// .projectConvex is called by other functions, need local tmp vectors | |
var pcoa_tmp1 = vec2.fromValues(0,0); | |
/** | |
* Project a Convex onto a world-oriented axis | |
* @method projectConvexOntoAxis | |
* @static | |
* @param {Convex} convexShape | |
* @param {Array} convexOffset | |
* @param {Number} convexAngle | |
* @param {Array} worldAxis | |
* @param {Array} result | |
*/ | |
Narrowphase.projectConvexOntoAxis = function(convexShape, convexOffset, convexAngle, worldAxis, result){ | |
var max=null, | |
min=null, | |
v, | |
value, | |
localAxis = pcoa_tmp1; | |
// Convert the axis to local coords of the body | |
vec2.rotate(localAxis, worldAxis, -convexAngle); | |
// Get projected position of all vertices | |
for(var i=0; i<convexShape.vertices.length; i++){ | |
v = convexShape.vertices[i]; | |
value = dot(v,localAxis); | |
if(max === null || value > max){ | |
max = value; | |
} | |
if(min === null || value < min){ | |
min = value; | |
} | |
} | |
if(min > max){ | |
var t = min; | |
min = max; | |
max = t; | |
} | |
// Project the position of the body onto the axis - need to add this to the result | |
var offset = dot(convexOffset, worldAxis); | |
vec2.set( result, min + offset, max + offset); | |
}; | |
// .findSeparatingAxis is called by other functions, need local tmp vectors | |
var fsa_tmp1 = vec2.fromValues(0,0) | |
, fsa_tmp2 = vec2.fromValues(0,0) | |
, fsa_tmp3 = vec2.fromValues(0,0) | |
, fsa_tmp4 = vec2.fromValues(0,0) | |
, fsa_tmp5 = vec2.fromValues(0,0) | |
, fsa_tmp6 = vec2.fromValues(0,0); | |
/** | |
* Find a separating axis between the shapes, that maximizes the separating distance between them. | |
* @method findSeparatingAxis | |
* @static | |
* @param {Convex} c1 | |
* @param {Array} offset1 | |
* @param {Number} angle1 | |
* @param {Convex} c2 | |
* @param {Array} offset2 | |
* @param {Number} angle2 | |
* @param {Array} sepAxis The resulting axis | |
* @return {Boolean} Whether the axis could be found. | |
*/ | |
Narrowphase.findSeparatingAxis = function(c1,offset1,angle1,c2,offset2,angle2,sepAxis){ | |
var maxDist = null, | |
overlap = false, | |
found = false, | |
edge = fsa_tmp1, | |
worldPoint0 = fsa_tmp2, | |
worldPoint1 = fsa_tmp3, | |
normal = fsa_tmp4, | |
span1 = fsa_tmp5, | |
span2 = fsa_tmp6; | |
if(c1 instanceof Box && c2 instanceof Box){ | |
for(var j=0; j!==2; j++){ | |
var c = c1, | |
angle = angle1; | |
if(j===1){ | |
c = c2; | |
angle = angle2; | |
} | |
for(var i=0; i!==2; i++){ | |
// Get the world edge | |
if(i === 0){ | |
vec2.set(normal, 0, 1); | |
} else if(i === 1) { | |
vec2.set(normal, 1, 0); | |
} | |
if(angle !== 0){ | |
vec2.rotate(normal, normal, angle); | |
} | |
// Project hulls onto that normal | |
Narrowphase.projectConvexOntoAxis(c1,offset1,angle1,normal,span1); | |
Narrowphase.projectConvexOntoAxis(c2,offset2,angle2,normal,span2); | |
// Order by span position | |
var a=span1, | |
b=span2, | |
swapped = false; | |
if(span1[0] > span2[0]){ | |
b=span1; | |
a=span2; | |
swapped = true; | |
} | |
// Get separating distance | |
var dist = b[0] - a[1]; | |
overlap = (dist <= 0); | |
if(maxDist===null || dist > maxDist){ | |
vec2.copy(sepAxis, normal); | |
maxDist = dist; | |
found = overlap; | |
} | |
} | |
} | |
} else { | |
for(var j=0; j!==2; j++){ | |
var c = c1, | |
angle = angle1; | |
if(j===1){ | |
c = c2; | |
angle = angle2; | |
} | |
for(var i=0; i!==c.vertices.length; i++){ | |
// Get the world edge | |
vec2.rotate(worldPoint0, c.vertices[i], angle); | |
vec2.rotate(worldPoint1, c.vertices[(i+1)%c.vertices.length], angle); | |
sub(edge, worldPoint1, worldPoint0); | |
// Get normal - just rotate 90 degrees since vertices are given in CCW | |
vec2.rotate90cw(normal, edge); | |
vec2.normalize(normal,normal); | |
// Project hulls onto that normal | |
Narrowphase.projectConvexOntoAxis(c1,offset1,angle1,normal,span1); | |
Narrowphase.projectConvexOntoAxis(c2,offset2,angle2,normal,span2); | |
// Order by span position | |
var a=span1, | |
b=span2, | |
swapped = false; | |
if(span1[0] > span2[0]){ | |
b=span1; | |
a=span2; | |
swapped = true; | |
} | |
// Get separating distance | |
var dist = b[0] - a[1]; | |
overlap = (dist <= 0); | |
if(maxDist===null || dist > maxDist){ | |
vec2.copy(sepAxis, normal); | |
maxDist = dist; | |
found = overlap; | |
} | |
} | |
} | |
} | |
/* | |
// Needs to be tested some more | |
for(var j=0; j!==2; j++){ | |
var c = c1, | |
angle = angle1; | |
if(j===1){ | |
c = c2; | |
angle = angle2; | |
} | |
for(var i=0; i!==c.axes.length; i++){ | |
var normal = c.axes[i]; | |
// Project hulls onto that normal | |
Narrowphase.projectConvexOntoAxis(c1, offset1, angle1, normal, span1); | |
Narrowphase.projectConvexOntoAxis(c2, offset2, angle2, normal, span2); | |
// Order by span position | |
var a=span1, | |
b=span2, | |
swapped = false; | |
if(span1[0] > span2[0]){ | |
b=span1; | |
a=span2; | |
swapped = true; | |
} | |
// Get separating distance | |
var dist = b[0] - a[1]; | |
overlap = (dist <= Narrowphase.convexPrecision); | |
if(maxDist===null || dist > maxDist){ | |
vec2.copy(sepAxis, normal); | |
maxDist = dist; | |
found = overlap; | |
} | |
} | |
} | |
*/ | |
return found; | |
}; | |
// .getClosestEdge is called by other functions, need local tmp vectors | |
var gce_tmp1 = vec2.fromValues(0,0) | |
, gce_tmp2 = vec2.fromValues(0,0) | |
, gce_tmp3 = vec2.fromValues(0,0); | |
/** | |
* Get the edge that has a normal closest to an axis. | |
* @method getClosestEdge | |
* @static | |
* @param {Convex} c | |
* @param {Number} angle | |
* @param {Array} axis | |
* @param {Boolean} flip | |
* @return {Number} Index of the edge that is closest. This index and the next spans the resulting edge. Returns -1 if failed. | |
*/ | |
Narrowphase.getClosestEdge = function(c,angle,axis,flip){ | |
var localAxis = gce_tmp1, | |
edge = gce_tmp2, | |
normal = gce_tmp3; | |
// Convert the axis to local coords of the body | |
vec2.rotate(localAxis, axis, -angle); | |
if(flip){ | |
vec2.scale(localAxis,localAxis,-1); | |
} | |
var closestEdge = -1, | |
N = c.vertices.length, | |
maxDot = -1; | |
for(var i=0; i!==N; i++){ | |
// Get the edge | |
sub(edge, c.vertices[(i+1)%N], c.vertices[i%N]); | |
// Get normal - just rotate 90 degrees since vertices are given in CCW | |
vec2.rotate90cw(normal, edge); | |
vec2.normalize(normal,normal); | |
var d = dot(normal,localAxis); | |
if(closestEdge === -1 || d > maxDot){ | |
closestEdge = i % N; | |
maxDot = d; | |
} | |
} | |
return closestEdge; | |
}; | |
var circleHeightfield_candidate = vec2.create(), | |
circleHeightfield_dist = vec2.create(), | |
circleHeightfield_v0 = vec2.create(), | |
circleHeightfield_v1 = vec2.create(), | |
circleHeightfield_minCandidate = vec2.create(), | |
circleHeightfield_worldNormal = vec2.create(), | |
circleHeightfield_minCandidateNormal = vec2.create(); | |
/** | |
* @method circleHeightfield | |
* @param {Body} bi | |
* @param {Circle} si | |
* @param {Array} xi | |
* @param {Body} bj | |
* @param {Heightfield} sj | |
* @param {Array} xj | |
* @param {Number} aj | |
*/ | |
Narrowphase.prototype[Shape.CIRCLE | Shape.HEIGHTFIELD] = | |
Narrowphase.prototype.circleHeightfield = function( circleBody,circleShape,circlePos,circleAngle, | |
hfBody,hfShape,hfPos,hfAngle, justTest, radius ){ | |
var data = hfShape.heights, | |
radius = radius || circleShape.radius, | |
w = hfShape.elementWidth, | |
dist = circleHeightfield_dist, | |
candidate = circleHeightfield_candidate, | |
minCandidate = circleHeightfield_minCandidate, | |
minCandidateNormal = circleHeightfield_minCandidateNormal, | |
worldNormal = circleHeightfield_worldNormal, | |
v0 = circleHeightfield_v0, | |
v1 = circleHeightfield_v1; | |
// Get the index of the points to test against | |
var idxA = Math.floor( (circlePos[0] - radius - hfPos[0]) / w ), | |
idxB = Math.ceil( (circlePos[0] + radius - hfPos[0]) / w ); | |
/*if(idxB < 0 || idxA >= data.length) | |
return justTest ? false : 0;*/ | |
if(idxA < 0){ | |
idxA = 0; | |
} | |
if(idxB >= data.length){ | |
idxB = data.length-1; | |
} | |
// Get max and min | |
var max = data[idxA], | |
min = data[idxB]; | |
for(var i=idxA; i<idxB; i++){ | |
if(data[i] < min){ | |
min = data[i]; | |
} | |
if(data[i] > max){ | |
max = data[i]; | |
} | |
} | |
if(circlePos[1]-radius > max){ | |
return justTest ? false : 0; | |
} | |
/* | |
if(circlePos[1]+radius < min){ | |
// Below the minimum point... We can just guess. | |
// TODO | |
} | |
*/ | |
// 1. Check so center of circle is not inside the field. If it is, this wont work... | |
// 2. For each edge | |
// 2. 1. Get point on circle that is closest to the edge (scale normal with -radius) | |
// 2. 2. Check if point is inside. | |
var found = false; | |
// Check all edges first | |
for(var i=idxA; i<idxB; i++){ | |
// Get points | |
vec2.set(v0, i*w, data[i] ); | |
vec2.set(v1, (i+1)*w, data[i+1]); | |
vec2.add(v0,v0,hfPos); | |
vec2.add(v1,v1,hfPos); | |
// Get normal | |
vec2.sub(worldNormal, v1, v0); | |
vec2.rotate(worldNormal, worldNormal, Math.PI/2); | |
vec2.normalize(worldNormal,worldNormal); | |
// Get point on circle, closest to the edge | |
vec2.scale(candidate,worldNormal,-radius); | |
vec2.add(candidate,candidate,circlePos); | |
// Distance from v0 to the candidate point | |
vec2.sub(dist,candidate,v0); | |
// Check if it is in the element "stick" | |
var d = vec2.dot(dist,worldNormal); | |
if(candidate[0] >= v0[0] && candidate[0] < v1[0] && d <= 0){ | |
if(justTest){ | |
return true; | |
} | |
found = true; | |
// Store the candidate point, projected to the edge | |
vec2.scale(dist,worldNormal,-d); | |
vec2.add(minCandidate,candidate,dist); | |
vec2.copy(minCandidateNormal,worldNormal); | |
var c = this.createContactEquation(hfBody,circleBody,hfShape,circleShape); | |
// Normal is out of the heightfield | |
vec2.copy(c.normalA, minCandidateNormal); | |
// Vector from circle to heightfield | |
vec2.scale(c.contactPointB, c.normalA, -radius); | |
add(c.contactPointB, c.contactPointB, circlePos); | |
sub(c.contactPointB, c.contactPointB, circleBody.position); | |
vec2.copy(c.contactPointA, minCandidate); | |
vec2.sub(c.contactPointA, c.contactPointA, hfBody.position); | |
this.contactEquations.push(c); | |
if(this.enableFriction){ | |
this.frictionEquations.push( this.createFrictionFromContact(c) ); | |
} | |
} | |
} | |
// Check all vertices | |
found = false; | |
if(radius > 0){ | |
for(var i=idxA; i<=idxB; i++){ | |
// Get point | |
vec2.set(v0, i*w, data[i]); | |
vec2.add(v0,v0,hfPos); | |
vec2.sub(dist, circlePos, v0); | |
if(vec2.squaredLength(dist) < Math.pow(radius, 2)){ | |
if(justTest){ | |
return true; | |
} | |
found = true; | |
var c = this.createContactEquation(hfBody,circleBody,hfShape,circleShape); | |
// Construct normal - out of heightfield | |
vec2.copy(c.normalA, dist); | |
vec2.normalize(c.normalA,c.normalA); | |
vec2.scale(c.contactPointB, c.normalA, -radius); | |
add(c.contactPointB, c.contactPointB, circlePos); | |
sub(c.contactPointB, c.contactPointB, circleBody.position); | |
sub(c.contactPointA, v0, hfPos); | |
add(c.contactPointA, c.contactPointA, hfPos); | |
sub(c.contactPointA, c.contactPointA, hfBody.position); | |
this.contactEquations.push(c); | |
if(this.enableFriction){ | |
this.frictionEquations.push(this.createFrictionFromContact(c)); | |
} | |
} | |
} | |
} | |
if(found){ | |
return 1; | |
} | |
return 0; | |
}; | |
var convexHeightfield_v0 = vec2.create(), | |
convexHeightfield_v1 = vec2.create(), | |
convexHeightfield_tilePos = vec2.create(), | |
convexHeightfield_tempConvexShape = new Convex({ vertices: [vec2.create(),vec2.create(),vec2.create(),vec2.create()] }); | |
/** | |
* @method circleHeightfield | |
* @param {Body} bi | |
* @param {Circle} si | |
* @param {Array} xi | |
* @param {Body} bj | |
* @param {Heightfield} sj | |
* @param {Array} xj | |
* @param {Number} aj | |
*/ | |
Narrowphase.prototype[Shape.BOX | Shape.HEIGHTFIELD] = | |
Narrowphase.prototype[Shape.CONVEX | Shape.HEIGHTFIELD] = | |
Narrowphase.prototype.convexHeightfield = function( convexBody,convexShape,convexPos,convexAngle, | |
hfBody,hfShape,hfPos,hfAngle, justTest ){ | |
var data = hfShape.heights, | |
w = hfShape.elementWidth, | |
v0 = convexHeightfield_v0, | |
v1 = convexHeightfield_v1, | |
tilePos = convexHeightfield_tilePos, | |
tileConvex = convexHeightfield_tempConvexShape; | |
// Get the index of the points to test against | |
var idxA = Math.floor( (convexBody.aabb.lowerBound[0] - hfPos[0]) / w ), | |
idxB = Math.ceil( (convexBody.aabb.upperBound[0] - hfPos[0]) / w ); | |
if(idxA < 0){ | |
idxA = 0; | |
} | |
if(idxB >= data.length){ | |
idxB = data.length-1; | |
} | |
// Get max and min | |
var max = data[idxA], | |
min = data[idxB]; | |
for(var i=idxA; i<idxB; i++){ | |
if(data[i] < min){ | |
min = data[i]; | |
} | |
if(data[i] > max){ | |
max = data[i]; | |
} | |
} | |
if(convexBody.aabb.lowerBound[1] > max){ | |
return justTest ? false : 0; | |
} | |
var found = false; | |
var numContacts = 0; | |
// Loop over all edges | |
// TODO: If possible, construct a convex from several data points (need o check if the points make a convex shape) | |
for(var i=idxA; i<idxB; i++){ | |
// Get points | |
vec2.set(v0, i*w, data[i] ); | |
vec2.set(v1, (i+1)*w, data[i+1]); | |
vec2.add(v0,v0,hfPos); | |
vec2.add(v1,v1,hfPos); | |
// Construct a convex | |
var tileHeight = 100; // todo | |
vec2.set(tilePos, (v1[0] + v0[0])*0.5, (v1[1] + v0[1] - tileHeight)*0.5); | |
vec2.sub(tileConvex.vertices[0], v1, tilePos); | |
vec2.sub(tileConvex.vertices[1], v0, tilePos); | |
vec2.copy(tileConvex.vertices[2], tileConvex.vertices[1]); | |
vec2.copy(tileConvex.vertices[3], tileConvex.vertices[0]); | |
tileConvex.vertices[2][1] -= tileHeight; | |
tileConvex.vertices[3][1] -= tileHeight; | |
// Do convex collision | |
numContacts += this.convexConvex( convexBody, convexShape, convexPos, convexAngle, | |
hfBody, tileConvex, tilePos, 0, justTest); | |
} | |
return numContacts; | |
}; | |
},{"../equations/ContactEquation":21,"../equations/Equation":22,"../equations/FrictionEquation":23,"../math/vec2":30,"../objects/Body":31,"../shapes/Box":37,"../shapes/Circle":39,"../shapes/Convex":40,"../shapes/Shape":45,"../utils/ContactEquationPool":48,"../utils/FrictionEquationPool":49,"../utils/TupleDictionary":56,"../utils/Utils":57}],11:[function(_dereq_,module,exports){ | |
module.exports = Ray; | |
var vec2 = _dereq_('../math/vec2'); | |
var RaycastResult = _dereq_('../collision/RaycastResult'); | |
var Shape = _dereq_('../shapes/Shape'); | |
var AABB = _dereq_('../collision/AABB'); | |
/** | |
* A line with a start and end point that is used to intersect shapes. For an example, see {{#crossLink "World/raycast:method"}}World.raycast{{/crossLink}} | |
* @class Ray | |
* @constructor | |
* @param {object} [options] | |
* @param {array} [options.from] | |
* @param {array} [options.to] | |
* @param {boolean} [options.checkCollisionResponse=true] | |
* @param {boolean} [options.skipBackfaces=false] | |
* @param {number} [options.collisionMask=-1] | |
* @param {number} [options.collisionGroup=-1] | |
* @param {number} [options.mode=Ray.ANY] | |
* @param {number} [options.callback] | |
*/ | |
function Ray(options){ | |
options = options || {}; | |
/** | |
* Ray start point. | |
* @property {array} from | |
*/ | |
this.from = options.from ? vec2.fromValues(options.from[0], options.from[1]) : vec2.create(); | |
/** | |
* Ray end point | |
* @property {array} to | |
*/ | |
this.to = options.to ? vec2.fromValues(options.to[0], options.to[1]) : vec2.create(); | |
/** | |
* Set to true if you want the Ray to take .collisionResponse flags into account on bodies and shapes. | |
* @property {Boolean} checkCollisionResponse | |
*/ | |
this.checkCollisionResponse = options.checkCollisionResponse !== undefined ? options.checkCollisionResponse : true; | |
/** | |
* If set to true, the ray skips any hits with normal.dot(rayDirection) < 0. | |
* @property {Boolean} skipBackfaces | |
*/ | |
this.skipBackfaces = !!options.skipBackfaces; | |
/** | |
* @property {number} collisionMask | |
* @default -1 | |
*/ | |
this.collisionMask = options.collisionMask !== undefined ? options.collisionMask : -1; | |
/** | |
* @property {number} collisionGroup | |
* @default -1 | |
*/ | |
this.collisionGroup = options.collisionGroup !== undefined ? options.collisionGroup : -1; | |
/** | |
* The intersection mode. Should be {{#crossLink "Ray/ANY:property"}}Ray.ANY{{/crossLink}}, {{#crossLink "Ray/ALL:property"}}Ray.ALL{{/crossLink}} or {{#crossLink "Ray/CLOSEST:property"}}Ray.CLOSEST{{/crossLink}}. | |
* @property {number} mode | |
*/ | |
this.mode = options.mode !== undefined ? options.mode : Ray.ANY; | |
/** | |
* Current, user-provided result callback. Will be used if mode is Ray.ALL. | |
* @property {Function} callback | |
*/ | |
this.callback = options.callback || function(result){}; | |
/** | |
* @readOnly | |
* @property {array} direction | |
*/ | |
this.direction = vec2.create(); | |
/** | |
* Length of the ray | |
* @readOnly | |
* @property {number} length | |
*/ | |
this.length = 1; | |
this.update(); | |
} | |
Ray.prototype.constructor = Ray; | |
/** | |
* This raycasting mode will make the Ray traverse through all intersection points and only return the closest one. | |
* @static | |
* @property {Number} CLOSEST | |
*/ | |
Ray.CLOSEST = 1; | |
/** | |
* This raycasting mode will make the Ray stop when it finds the first intersection point. | |
* @static | |
* @property {Number} ANY | |
*/ | |
Ray.ANY = 2; | |
/** | |
* This raycasting mode will traverse all intersection points and executes a callback for each one. | |
* @static | |
* @property {Number} ALL | |
*/ | |
Ray.ALL = 4; | |
/** | |
* Should be called if you change the from or to point. | |
* @method update | |
*/ | |
Ray.prototype.update = function(){ | |
// Update .direction and .length | |
var d = this.direction; | |
vec2.sub(d, this.to, this.from); | |
this.length = vec2.length(d); | |
vec2.normalize(d, d); | |
}; | |
/** | |
* @method intersectBodies | |
* @param {Array} bodies An array of Body objects. | |
*/ | |
Ray.prototype.intersectBodies = function (result, bodies) { | |
for (var i = 0, l = bodies.length; !result.shouldStop(this) && i < l; i++) { | |
var body = bodies[i]; | |
var aabb = body.getAABB(); | |
if(aabb.overlapsRay(this) >= 0 || aabb.containsPoint(this.from)){ | |
this.intersectBody(result, body); | |
} | |
} | |
}; | |
var intersectBody_worldPosition = vec2.create(); | |
/** | |
* Shoot a ray at a body, get back information about the hit. | |
* @method intersectBody | |
* @private | |
* @param {Body} body | |
*/ | |
Ray.prototype.intersectBody = function (result, body) { | |
var checkCollisionResponse = this.checkCollisionResponse; | |
if(checkCollisionResponse && !body.collisionResponse){ | |
return; | |
} | |
var worldPosition = intersectBody_worldPosition; | |
for (var i = 0, N = body.shapes.length; i < N; i++) { | |
var shape = body.shapes[i]; | |
if(checkCollisionResponse && !shape.collisionResponse){ | |
continue; // Skip | |
} | |
if((this.collisionGroup & shape.collisionMask) === 0 || (shape.collisionGroup & this.collisionMask) === 0){ | |
continue; | |
} | |
// Get world angle and position of the shape | |
vec2.rotate(worldPosition, shape.position, body.angle); | |
vec2.add(worldPosition, worldPosition, body.position); | |
var worldAngle = shape.angle + body.angle; | |
this.intersectShape( | |
result, | |
shape, | |
worldAngle, | |
worldPosition, | |
body | |
); | |
if(result.shouldStop(this)){ | |
break; | |
} | |
} | |
}; | |
/** | |
* @method intersectShape | |
* @private | |
* @param {Shape} shape | |
* @param {number} angle | |
* @param {array} position | |
* @param {Body} body | |
*/ | |
Ray.prototype.intersectShape = function(result, shape, angle, position, body){ | |
var from = this.from; | |
// Checking radius | |
var distance = distanceFromIntersectionSquared(from, this.direction, position); | |
if (distance > shape.boundingRadius * shape.boundingRadius) { | |
return; | |
} | |
this._currentBody = body; | |
this._currentShape = shape; | |
shape.raycast(result, this, position, angle); | |
this._currentBody = this._currentShape = null; | |
}; | |
/** | |
* Get the AABB of the ray. | |
* @method getAABB | |
* @param {AABB} aabb | |
*/ | |
Ray.prototype.getAABB = function(result){ | |
var to = this.to; | |
var from = this.from; | |
vec2.set( | |
result.lowerBound, | |
Math.min(to[0], from[0]), | |
Math.min(to[1], from[1]) | |
); | |
vec2.set( | |
result.upperBound, | |
Math.max(to[0], from[0]), | |
Math.max(to[1], from[1]) | |
); | |
}; | |
var hitPointWorld = vec2.create(); | |
/** | |
* @method reportIntersection | |
* @private | |
* @param {number} fraction | |
* @param {array} normal | |
* @param {number} [faceIndex=-1] | |
* @return {boolean} True if the intersections should continue | |
*/ | |
Ray.prototype.reportIntersection = function(result, fraction, normal, faceIndex){ | |
var from = this.from; | |
var to = this.to; | |
var shape = this._currentShape; | |
var body = this._currentBody; | |
// Skip back faces? | |
if(this.skipBackfaces && vec2.dot(normal, this.direction) > 0){ | |
return; | |
} | |
switch(this.mode){ | |
case Ray.ALL: | |
result.set( | |
normal, | |
shape, | |
body, | |
fraction, | |
faceIndex | |
); | |
this.callback(result); | |
break; | |
case Ray.CLOSEST: | |
// Store if closer than current closest | |
if(fraction < result.fraction || !result.hasHit()){ | |
result.set( | |
normal, | |
shape, | |
body, | |
fraction, | |
faceIndex | |
); | |
} | |
break; | |
case Ray.ANY: | |
// Report and stop. | |
result.set( | |
normal, | |
shape, | |
body, | |
fraction, | |
faceIndex | |
); | |
break; | |
} | |
}; | |
var v0 = vec2.create(), | |
intersect = vec2.create(); | |
function distanceFromIntersectionSquared(from, direction, position) { | |
// v0 is vector from from to position | |
vec2.sub(v0, position, from); | |
var dot = vec2.dot(v0, direction); | |
// intersect = direction * dot + from | |
vec2.scale(intersect, direction, dot); | |
vec2.add(intersect, intersect, from); | |
return vec2.squaredDistance(position, intersect); | |
} | |
},{"../collision/AABB":7,"../collision/RaycastResult":12,"../math/vec2":30,"../shapes/Shape":45}],12:[function(_dereq_,module,exports){ | |
var vec2 = _dereq_('../math/vec2'); | |
var Ray = _dereq_('../collision/Ray'); | |
module.exports = RaycastResult; | |
/** | |
* Storage for Ray casting hit data. | |
* @class RaycastResult | |
* @constructor | |
*/ | |
function RaycastResult(){ | |
/** | |
* The normal of the hit, oriented in world space. | |
* @property {array} normal | |
*/ | |
this.normal = vec2.create(); | |
/** | |
* The hit shape, or null. | |
* @property {Shape} shape | |
*/ | |
this.shape = null; | |
/** | |
* The hit body, or null. | |
* @property {Body} body | |
*/ | |
this.body = null; | |
/** | |
* The index of the hit triangle, if the hit shape was indexable. | |
* @property {number} faceIndex | |
* @default -1 | |
*/ | |
this.faceIndex = -1; | |
/** | |
* Distance to the hit, as a fraction. 0 is at the "from" point, 1 is at the "to" point. Will be set to -1 if there was no hit yet. | |
* @property {number} fraction | |
* @default -1 | |
*/ | |
this.fraction = -1; | |
/** | |
* If the ray should stop traversing. | |
* @readonly | |
* @property {Boolean} isStopped | |
*/ | |
this.isStopped = false; | |
} | |
/** | |
* Reset all result data. Must be done before re-using the result object. | |
* @method reset | |
*/ | |
RaycastResult.prototype.reset = function () { | |
vec2.set(this.normal, 0, 0); | |
this.shape = null; | |
this.body = null; | |
this.faceIndex = -1; | |
this.fraction = -1; | |
this.isStopped = false; | |
}; | |
/** | |
* Get the distance to the hit point. | |
* @method getHitDistance | |
* @param {Ray} ray | |
*/ | |
RaycastResult.prototype.getHitDistance = function (ray) { | |
return vec2.distance(ray.from, ray.to) * this.fraction; | |
}; | |
/** | |
* Returns true if the ray hit something since the last reset(). | |
* @method hasHit | |
*/ | |
RaycastResult.prototype.hasHit = function () { | |
return this.fraction !== -1; | |
}; | |
/** | |
* Get world hit point. | |
* @method getHitPoint | |
* @param {array} out | |
* @param {Ray} ray | |
*/ | |
RaycastResult.prototype.getHitPoint = function (out, ray) { | |
vec2.lerp(out, ray.from, ray.to, this.fraction); | |
}; | |
/** | |
* Can be called while iterating over hits to stop searching for hit points. | |
* @method stop | |
*/ | |
RaycastResult.prototype.stop = function(){ | |
this.isStopped = true; | |
}; | |
/** | |
* @method shouldStop | |
* @private | |
* @param {Ray} ray | |
* @return {boolean} | |
*/ | |
RaycastResult.prototype.shouldStop = function(ray){ | |
return this.isStopped || (this.fraction !== -1 && ray.mode === Ray.ANY); | |
}; | |
/** | |
* @method set | |
* @private | |
* @param {array} normal | |
* @param {Shape} shape | |
* @param {Body} body | |
* @param {number} fraction | |
*/ | |
RaycastResult.prototype.set = function( | |
normal, | |
shape, | |
body, | |
fraction, | |
faceIndex | |
){ | |
vec2.copy(this.normal, normal); | |
this.shape = shape; | |
this.body = body; | |
this.fraction = fraction; | |
this.faceIndex = faceIndex; | |
}; | |
},{"../collision/Ray":11,"../math/vec2":30}],13:[function(_dereq_,module,exports){ | |
var Utils = _dereq_('../utils/Utils') | |
, Broadphase = _dereq_('../collision/Broadphase'); | |
module.exports = SAPBroadphase; | |
/** | |
* Sweep and prune broadphase along one axis. | |
* | |
* @class SAPBroadphase | |
* @constructor | |
* @extends Broadphase | |
*/ | |
function SAPBroadphase(){ | |
Broadphase.call(this,Broadphase.SAP); | |
/** | |
* List of bodies currently in the broadphase. | |
* @property axisList | |
* @type {Array} | |
*/ | |
this.axisList = []; | |
/** | |
* The axis to sort along. 0 means x-axis and 1 y-axis. If your bodies are more spread out over the X axis, set axisIndex to 0, and you will gain some performance. | |
* @property axisIndex | |
* @type {Number} | |
*/ | |
this.axisIndex = 0; | |
var that = this; | |
this._addBodyHandler = function(e){ | |
that.axisList.push(e.body); | |
}; | |
this._removeBodyHandler = function(e){ | |
// Remove from list | |
var idx = that.axisList.indexOf(e.body); | |
if(idx !== -1){ | |
that.axisList.splice(idx,1); | |
} | |
}; | |
} | |
SAPBroadphase.prototype = new Broadphase(); | |
SAPBroadphase.prototype.constructor = SAPBroadphase; | |
/** | |
* Change the world | |
* @method setWorld | |
* @param {World} world | |
*/ | |
SAPBroadphase.prototype.setWorld = function(world){ | |
// Clear the old axis array | |
this.axisList.length = 0; | |
// Add all bodies from the new world | |
Utils.appendArray(this.axisList, world.bodies); | |
// Remove old handlers, if any | |
world | |
.off("addBody",this._addBodyHandler) | |
.off("removeBody",this._removeBodyHandler); | |
// Add handlers to update the list of bodies. | |
world.on("addBody",this._addBodyHandler).on("removeBody",this._removeBodyHandler); | |
this.world = world; | |
}; | |
/** | |
* Sorts bodies along an axis. | |
* @method sortAxisList | |
* @param {Array} a | |
* @param {number} axisIndex | |
* @return {Array} | |
*/ | |
SAPBroadphase.sortAxisList = function(a, axisIndex){ | |
axisIndex = axisIndex|0; | |
for(var i=1,l=a.length; i<l; i++) { | |
var v = a[i]; | |
for(var j=i - 1;j>=0;j--) { | |
if(a[j].aabb.lowerBound[axisIndex] <= v.aabb.lowerBound[axisIndex]){ | |
break; | |
} | |
a[j+1] = a[j]; | |
} | |
a[j+1] = v; | |
} | |
return a; | |
}; | |
SAPBroadphase.prototype.sortList = function(){ | |
var bodies = this.axisList, | |
axisIndex = this.axisIndex; | |
// Sort the lists | |
SAPBroadphase.sortAxisList(bodies, axisIndex); | |
}; | |
/** | |
* Get the colliding pairs | |
* @method getCollisionPairs | |
* @param {World} world | |
* @return {Array} | |
*/ | |
SAPBroadphase.prototype.getCollisionPairs = function(world){ | |
var bodies = this.axisList, | |
result = this.result, | |
axisIndex = this.axisIndex; | |
result.length = 0; | |
// Update all AABBs if needed | |
var l = bodies.length; | |
while(l--){ | |
var b = bodies[l]; | |
if(b.aabbNeedsUpdate){ | |
b.updateAABB(); | |
} | |
} | |
// Sort the lists | |
this.sortList(); | |
// Look through the X list | |
for(var i=0, N=bodies.length|0; i!==N; i++){ | |
var bi = bodies[i]; | |
for(var j=i+1; j<N; j++){ | |
var bj = bodies[j]; | |
// Bounds overlap? | |
var overlaps = (bj.aabb.lowerBound[axisIndex] <= bi.aabb.upperBound[axisIndex]); | |
if(!overlaps){ | |
break; | |
} | |
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} | |
*/ | |
SAPBroadphase.prototype.aabbQuery = function(world, aabb, result){ | |
result = result || []; | |
this.sortList(); | |
var axisIndex = this.axisIndex; | |
var axis = 'x'; | |
if(axisIndex === 1){ axis = 'y'; } | |
if(axisIndex === 2){ axis = 'z'; } | |
var axisList = this.axisList; | |
var lower = aabb.lowerBound[axis]; | |
var upper = aabb.upperBound[axis]; | |
for(var i = 0; i < axisList.length; i++){ | |
var b = axisList[i]; | |
if(b.aabbNeedsUpdate){ | |
b.updateAABB(); | |
} | |
if(b.aabb.overlaps(aabb)){ | |
result.push(b); | |
} | |
} | |
return result; | |
}; | |
},{"../collision/Broadphase":8,"../utils/Utils":57}],14:[function(_dereq_,module,exports){ | |
module.exports = Constraint; | |
var Utils = _dereq_('../utils/Utils'); | |
/** | |
* Base constraint class. | |
* | |
* @class Constraint | |
* @constructor | |
* @author schteppe | |
* @param {Body} bodyA | |
* @param {Body} bodyB | |
* @param {Number} type | |
* @param {Object} [options] | |
* @param {Object} [options.collideConnected=true] | |
*/ | |
function Constraint(bodyA, bodyB, type, options){ | |
/** | |
* The type of constraint. May be one of Constraint.DISTANCE, Constraint.GEAR, Constraint.LOCK, Constraint.PRISMATIC or Constraint.REVOLUTE. | |
* @property {number} type | |
*/ | |
this.type = type; | |
options = Utils.defaults(options,{ | |
collideConnected : true, | |
wakeUpBodies : true, | |
}); | |
/** | |
* Equations to be solved in this constraint | |
* | |
* @property equations | |
* @type {Array} | |
*/ | |
this.equations = []; | |
/** | |
* First body participating in the constraint. | |
* @property bodyA | |
* @type {Body} | |
*/ | |
this.bodyA = bodyA; | |
/** | |
* Second body participating in the constraint. | |
* @property bodyB | |
* @type {Body} | |
*/ | |
this.bodyB = bodyB; | |
/** | |
* Set to true if you want the connected bodies to collide. | |
* @property collideConnected | |
* @type {Boolean} | |
* @default true | |
*/ | |
this.collideConnected = options.collideConnected; | |
// Wake up bodies when connected | |
if(options.wakeUpBodies){ | |
if(bodyA){ | |
bodyA.wakeUp(); | |
} | |
if(bodyB){ | |
bodyB.wakeUp(); | |
} | |
} | |
} | |
/** | |
* Updates the internal constraint parameters before solve. | |
* @method update | |
*/ | |
Constraint.prototype.update = function(){ | |
throw new Error("method update() not implmemented in this Constraint subclass!"); | |
}; | |
/** | |
* @static | |
* @property {number} DISTANCE | |
*/ | |
Constraint.DISTANCE = 1; | |
/** | |
* @static | |
* @property {number} GEAR | |
*/ | |
Constraint.GEAR = 2; | |
/** | |
* @static | |
* @property {number} LOCK | |
*/ | |
Constraint.LOCK = 3; | |
/** | |
* @static | |
* @property {number} PRISMATIC | |
*/ | |
Constraint.PRISMATIC = 4; | |
/** | |
* @static | |
* @property {number} REVOLUTE | |
*/ | |
Constraint.REVOLUTE = 5; | |
/** | |
* Set stiffness for this constraint. | |
* @method setStiffness | |
* @param {Number} stiffness | |
*/ | |
Constraint.prototype.setStiffness = function(stiffness){ | |
var eqs = this.equations; | |
for(var i=0; i !== eqs.length; i++){ | |
var eq = eqs[i]; | |
eq.stiffness = stiffness; | |
eq.needsUpdate = true; | |
} | |
}; | |
/** | |
* Set relaxation for this constraint. | |
* @method setRelaxation | |
* @param {Number} relaxation | |
*/ | |
Constraint.prototype.setRelaxation = function(relaxation){ | |
var eqs = this.equations; | |
for(var i=0; i !== eqs.length; i++){ | |
var eq = eqs[i]; | |
eq.relaxation = relaxation; | |
eq.needsUpdate = true; | |
} | |
}; | |
},{"../utils/Utils":57}],15:[function(_dereq_,module,exports){ | |
var Constraint = _dereq_('./Constraint') | |
, Equation = _dereq_('../equations/Equation') | |
, vec2 = _dereq_('../math/vec2') | |
, Utils = _dereq_('../utils/Utils'); | |
module.exports = DistanceConstraint; | |
/** | |
* Constraint that tries to keep the distance between two bodies constant. | |
* | |
* @class DistanceConstraint | |
* @constructor | |
* @author schteppe | |
* @param {Body} bodyA | |
* @param {Body} bodyB | |
* @param {object} [options] | |
* @param {number} [options.distance] The distance to keep between the anchor points. Defaults to the current distance between the bodies. | |
* @param {Array} [options.localAnchorA] The anchor point for bodyA, defined locally in bodyA frame. Defaults to [0,0]. | |
* @param {Array} [options.localAnchorB] The anchor point for bodyB, defined locally in bodyB frame. Defaults to [0,0]. | |
* @param {object} [options.maxForce=Number.MAX_VALUE] Maximum force to apply. | |
* @extends Constraint | |
* | |
* @example | |
* // If distance is not given as an option, then the current distance between the bodies is used. | |
* // In this example, the bodies will be constrained to have a distance of 2 between their centers. | |
* var bodyA = new Body({ mass: 1, position: [-1, 0] }); | |
* var bodyB = new Body({ mass: 1, position: [1, 0] }); | |
* var constraint = new DistanceConstraint(bodyA, bodyB); | |
* world.addConstraint(constraint); | |
* | |
* @example | |
* // Manually set the distance and anchors | |
* var constraint = new DistanceConstraint(bodyA, bodyB, { | |
* distance: 1, // Distance to keep between the points | |
* localAnchorA: [1, 0], // Point on bodyA | |
* localAnchorB: [-1, 0] // Point on bodyB | |
* }); | |
* world.addConstraint(constraint); | |
*/ | |
function DistanceConstraint(bodyA,bodyB,options){ | |
options = Utils.defaults(options,{ | |
localAnchorA:[0,0], | |
localAnchorB:[0,0] | |
}); | |
Constraint.call(this,bodyA,bodyB,Constraint.DISTANCE,options); | |
/** | |
* Local anchor in body A. | |
* @property localAnchorA | |
* @type {Array} | |
*/ | |
this.localAnchorA = vec2.fromValues(options.localAnchorA[0], options.localAnchorA[1]); | |
/** | |
* Local anchor in body B. | |
* @property localAnchorB | |
* @type {Array} | |
*/ | |
this.localAnchorB = vec2.fromValues(options.localAnchorB[0], options.localAnchorB[1]); | |
var localAnchorA = this.localAnchorA; | |
var localAnchorB = this.localAnchorB; | |
/** | |
* The distance to keep. | |
* @property distance | |
* @type {Number} | |
*/ | |
this.distance = 0; | |
if(typeof(options.distance) === 'number'){ | |
this.distance = options.distance; | |
} else { | |
// Use the current world distance between the world anchor points. | |
var worldAnchorA = vec2.create(), | |
worldAnchorB = vec2.create(), | |
r = vec2.create(); | |
// Transform local anchors to world | |
vec2.rotate(worldAnchorA, localAnchorA, bodyA.angle); | |
vec2.rotate(worldAnchorB, localAnchorB, bodyB.angle); | |
vec2.add(r, bodyB.position, worldAnchorB); | |
vec2.sub(r, r, worldAnchorA); | |
vec2.sub(r, r, bodyA.position); | |
this.distance = vec2.length(r); | |
} | |
var maxForce; | |
if(typeof(options.maxForce)==="undefined" ){ | |
maxForce = Number.MAX_VALUE; | |
} else { | |
maxForce = options.maxForce; | |
} | |
var normal = new Equation(bodyA,bodyB,-maxForce,maxForce); // Just in the normal direction | |
this.equations = [ normal ]; | |
/** | |
* Max force to apply. | |
* @property {number} maxForce | |
*/ | |
this.maxForce = maxForce; | |
// g = (xi - xj).dot(n) | |
// dg/dt = (vi - vj).dot(n) = G*W = [n 0 -n 0] * [vi wi vj wj]' | |
// ...and if we were to include offset points: | |
// g = | |
// (xj + rj - xi - ri).dot(n) - distance | |
// | |
// dg/dt = | |
// (vj + wj x rj - vi - wi x ri).dot(n) = | |
// { term 2 is near zero } = | |
// [-n -ri x n n rj x n] * [vi wi vj wj]' = | |
// G * W | |
// | |
// => G = [-n -rixn n rjxn] | |
var r = vec2.create(); | |
var ri = vec2.create(); // worldAnchorA | |
var rj = vec2.create(); // worldAnchorB | |
var that = this; | |
normal.computeGq = function(){ | |
var bodyA = this.bodyA, | |
bodyB = this.bodyB, | |
xi = bodyA.position, | |
xj = bodyB.position; | |
// Transform local anchors to world | |
vec2.rotate(ri, localAnchorA, bodyA.angle); | |
vec2.rotate(rj, localAnchorB, bodyB.angle); | |
vec2.add(r, xj, rj); | |
vec2.sub(r, r, ri); | |
vec2.sub(r, r, xi); | |
//vec2.sub(r, bodyB.position, bodyA.position); | |
return vec2.length(r) - that.distance; | |
}; | |
// Make the contact constraint bilateral | |
this.setMaxForce(maxForce); | |
/** | |
* If the upper limit is enabled or not. | |
* @property {Boolean} upperLimitEnabled | |
*/ | |
this.upperLimitEnabled = false; | |
/** | |
* The upper constraint limit. | |
* @property {number} upperLimit | |
*/ | |
this.upperLimit = 1; | |
/** | |
* If the lower limit is enabled or not. | |
* @property {Boolean} lowerLimitEnabled | |
*/ | |
this.lowerLimitEnabled = false; | |
/** | |
* The lower constraint limit. | |
* @property {number} lowerLimit | |
*/ | |
this.lowerLimit = 0; | |
/** | |
* Current constraint position. This is equal to the current distance between the world anchor points. | |
* @property {number} position | |
*/ | |
this.position = 0; | |
} | |
DistanceConstraint.prototype = new Constraint(); | |
DistanceConstraint.prototype.constructor = DistanceConstraint; | |
/** | |
* Update the constraint equations. Should be done if any of the bodies changed position, before solving. | |
* @method update | |
*/ | |
var n = vec2.create(); | |
var ri = vec2.create(); // worldAnchorA | |
var rj = vec2.create(); // worldAnchorB | |
DistanceConstraint.prototype.update = function(){ | |
var normal = this.equations[0], | |
bodyA = this.bodyA, | |
bodyB = this.bodyB, | |
distance = this.distance, | |
xi = bodyA.position, | |
xj = bodyB.position, | |
normalEquation = this.equations[0], | |
G = normal.G; | |
// Transform local anchors to world | |
vec2.rotate(ri, this.localAnchorA, bodyA.angle); | |
vec2.rotate(rj, this.localAnchorB, bodyB.angle); | |
// Get world anchor points and normal | |
vec2.add(n, xj, rj); | |
vec2.sub(n, n, ri); | |
vec2.sub(n, n, xi); | |
this.position = vec2.length(n); | |
var violating = false; | |
if(this.upperLimitEnabled){ | |
if(this.position > this.upperLimit){ | |
normalEquation.maxForce = 0; | |
normalEquation.minForce = -this.maxForce; | |
this.distance = this.upperLimit; | |
violating = true; | |
} | |
} | |
if(this.lowerLimitEnabled){ | |
if(this.position < this.lowerLimit){ | |
normalEquation.maxForce = this.maxForce; | |
normalEquation.minForce = 0; | |
this.distance = this.lowerLimit; | |
violating = true; | |
} | |
} | |
if((this.lowerLimitEnabled || this.upperLimitEnabled) && !violating){ | |
// No constraint needed. | |
normalEquation.enabled = false; | |
return; | |
} | |
normalEquation.enabled = true; | |
vec2.normalize(n,n); | |
// Caluclate cross products | |
var rixn = vec2.crossLength(ri, n), | |
rjxn = vec2.crossLength(rj, n); | |
// G = [-n -rixn n rjxn] | |
G[0] = -n[0]; | |
G[1] = -n[1]; | |
G[2] = -rixn; | |
G[3] = n[0]; | |
G[4] = n[1]; | |
G[5] = rjxn; | |
}; | |
/** | |
* Set the max force to be used | |
* @method setMaxForce | |
* @param {Number} maxForce | |
*/ | |
DistanceConstraint.prototype.setMaxForce = function(maxForce){ | |
var normal = this.equations[0]; | |
normal.minForce = -maxForce; | |
normal.maxForce = maxForce; | |
}; | |
/** | |
* Get the max force | |
* @method getMaxForce | |
* @return {Number} | |
*/ | |
DistanceConstraint.prototype.getMaxForce = function(){ | |
var normal = this.equations[0]; | |
return normal.maxForce; | |
}; | |
},{"../equations/Equation":22,"../math/vec2":30,"../utils/Utils":57,"./Constraint":14}],16:[function(_dereq_,module,exports){ | |
var Constraint = _dereq_('./Constraint') | |
, Equation = _dereq_('../equations/Equation') | |
, AngleLockEquation = _dereq_('../equations/AngleLockEquation') | |
, vec2 = _dereq_('../math/vec2'); | |
module.exports = GearConstraint; | |
/** | |
* Constrains the angle of two bodies to each other to be equal. If a gear ratio is not one, the angle of bodyA must be a multiple of the angle of bodyB. | |
* @class GearConstraint | |
* @constructor | |
* @author schteppe | |
* @param {Body} bodyA | |
* @param {Body} bodyB | |
* @param {Object} [options] | |
* @param {Number} [options.angle=0] Relative angle between the bodies. Will be set to the current angle between the bodies (the gear ratio is accounted for). | |
* @param {Number} [options.ratio=1] Gear ratio. | |
* @param {Number} [options.maxTorque] Maximum torque to apply. | |
* @extends Constraint | |
* | |
* @example | |
* var constraint = new GearConstraint(bodyA, bodyB); | |
* world.addConstraint(constraint); | |
* | |
* @example | |
* var constraint = new GearConstraint(bodyA, bodyB, { | |
* ratio: 2, | |
* maxTorque: 1000 | |
* }); | |
* world.addConstraint(constraint); | |
*/ | |
function GearConstraint(bodyA, bodyB, options){ | |
options = options || {}; | |
Constraint.call(this, bodyA, bodyB, Constraint.GEAR, options); | |
/** | |
* The gear ratio. | |
* @property ratio | |
* @type {Number} | |
*/ | |
this.ratio = options.ratio !== undefined ? options.ratio : 1; | |
/** | |
* The relative angle | |
* @property angle | |
* @type {Number} | |
*/ | |
this.angle = options.angle !== undefined ? options.angle : bodyB.angle - this.ratio * bodyA.angle; | |
// Send same parameters to the equation | |
options.angle = this.angle; | |
options.ratio = this.ratio; | |
this.equations = [ | |
new AngleLockEquation(bodyA,bodyB,options), | |
]; | |
// Set max torque | |
if(options.maxTorque !== undefined){ | |
this.setMaxTorque(options.maxTorque); | |
} | |
} | |
GearConstraint.prototype = new Constraint(); | |
GearConstraint.prototype.constructor = GearConstraint; | |
GearConstraint.prototype.update = function(){ | |
var eq = this.equations[0]; | |
if(eq.ratio !== this.ratio){ | |
eq.setRatio(this.ratio); | |
} | |
eq.angle = this.angle; | |
}; | |
/** | |
* Set the max torque for the constraint. | |
* @method setMaxTorque | |
* @param {Number} torque | |
*/ | |
GearConstraint.prototype.setMaxTorque = function(torque){ | |
this.equations[0].setMaxTorque(torque); | |
}; | |
/** | |
* Get the max torque for the constraint. | |
* @method getMaxTorque | |
* @return {Number} | |
*/ | |
GearConstraint.prototype.getMaxTorque = function(torque){ | |
return this.equations[0].maxForce; | |
}; | |
},{"../equations/AngleLockEquation":20,"../equations/Equation":22,"../math/vec2":30,"./Constraint":14}],17:[function(_dereq_,module,exports){ | |
var Constraint = _dereq_('./Constraint') | |
, vec2 = _dereq_('../math/vec2') | |
, Equation = _dereq_('../equations/Equation'); | |
module.exports = LockConstraint; | |
/** | |
* Locks the relative position and rotation between two bodies. | |
* | |
* @class LockConstraint | |
* @constructor | |
* @author schteppe | |
* @param {Body} bodyA | |
* @param {Body} bodyB | |
* @param {Object} [options] | |
* @param {Array} [options.localOffsetB] The offset of bodyB in bodyA's frame. If not given the offset is computed from current positions. | |
* @param {number} [options.localAngleB] The angle of bodyB in bodyA's frame. If not given, the angle is computed from current angles. | |
* @param {number} [options.maxForce] | |
* @extends Constraint | |
* | |
* @example | |
* // Locks the relative position and rotation between bodyA and bodyB | |
* var constraint = new LockConstraint(bodyA, bodyB); | |
* world.addConstraint(constraint); | |
*/ | |
function LockConstraint(bodyA, bodyB, options){ | |
options = options || {}; | |
Constraint.call(this,bodyA,bodyB,Constraint.LOCK,options); | |
var maxForce = ( typeof(options.maxForce)==="undefined" ? Number.MAX_VALUE : options.maxForce ); | |
var localAngleB = options.localAngleB || 0; | |
// Use 3 equations: | |
// gx = (xj - xi - l) * xhat = 0 | |
// gy = (xj - xi - l) * yhat = 0 | |
// gr = (xi - xj + r) * that = 0 | |
// | |
// ...where: | |
// l is the localOffsetB vector rotated to world in bodyA frame | |
// r is the same vector but reversed and rotated from bodyB frame | |
// xhat, yhat are world axis vectors | |
// that is the tangent of r | |
// | |
// For the first two constraints, we get | |
// G*W = (vj - vi - ldot ) * xhat | |
// = (vj - vi - wi x l) * xhat | |
// | |
// Since (wi x l) * xhat = (l x xhat) * wi, we get | |
// G*W = [ -1 0 (-l x xhat) 1 0 0] * [vi wi vj wj] | |
// | |
// The last constraint gives | |
// GW = (vi - vj + wj x r) * that | |
// = [ that 0 -that (r x t) ] | |
var x = new Equation(bodyA,bodyB,-maxForce,maxForce), | |
y = new Equation(bodyA,bodyB,-maxForce,maxForce), | |
rot = new Equation(bodyA,bodyB,-maxForce,maxForce); | |
var l = vec2.create(), | |
g = vec2.create(), | |
that = this; | |
x.computeGq = function(){ | |
vec2.rotate(l, that.localOffsetB, bodyA.angle); | |
vec2.sub(g, bodyB.position, bodyA.position); | |
vec2.sub(g, g, l); | |
return g[0]; | |
}; | |
y.computeGq = function(){ | |
vec2.rotate(l, that.localOffsetB, bodyA.angle); | |
vec2.sub(g, bodyB.position, bodyA.position); | |
vec2.sub(g, g, l); | |
return g[1]; | |
}; | |
var r = vec2.create(), | |
t = vec2.create(); | |
rot.computeGq = function(){ | |
vec2.rotate(r, that.localOffsetB, bodyB.angle - that.localAngleB); | |
vec2.scale(r,r,-1); | |
vec2.sub(g,bodyA.position,bodyB.position); | |
vec2.add(g,g,r); | |
vec2.rotate(t,r,-Math.PI/2); | |
vec2.normalize(t,t); | |
return vec2.dot(g,t); | |
}; | |
/** | |
* The offset of bodyB in bodyA's frame. | |
* @property {Array} localOffsetB | |
*/ | |
this.localOffsetB = vec2.create(); | |
if(options.localOffsetB){ | |
vec2.copy(this.localOffsetB, options.localOffsetB); | |
} else { | |
// Construct from current positions | |
vec2.sub(this.localOffsetB, bodyB.position, bodyA.position); | |
vec2.rotate(this.localOffsetB, this.localOffsetB, -bodyA.angle); | |
} | |
/** | |
* The offset angle of bodyB in bodyA's frame. | |
* @property {Number} localAngleB | |
*/ | |
this.localAngleB = 0; | |
if(typeof(options.localAngleB) === 'number'){ | |
this.localAngleB = options.localAngleB; | |
} else { | |
// Construct | |
this.localAngleB = bodyB.angle - bodyA.angle; | |
} | |
this.equations.push(x, y, rot); | |
this.setMaxForce(maxForce); | |
} | |
LockConstraint.prototype = new Constraint(); | |
LockConstraint.prototype.constructor = LockConstraint; | |
/** | |
* Set the maximum force to be applied. | |
* @method setMaxForce | |
* @param {Number} force | |
*/ | |
LockConstraint.prototype.setMaxForce = function(force){ | |
var eqs = this.equations; | |
for(var i=0; i<this.equations.length; i++){ | |
eqs[i].maxForce = force; | |
eqs[i].minForce = -force; | |
} | |
}; | |
/** | |
* Get the max force. | |
* @method getMaxForce | |
* @return {Number} | |
*/ | |
LockConstraint.prototype.getMaxForce = function(){ | |
return this.equations[0].maxForce; | |
}; | |
var l = vec2.create(); | |
var r = vec2.create(); | |
var t = vec2.create(); | |
var xAxis = vec2.fromValues(1,0); | |
var yAxis = vec2.fromValues(0,1); | |
LockConstraint.prototype.update = function(){ | |
var x = this.equations[0], | |
y = this.equations[1], | |
rot = this.equations[2], | |
bodyA = this.bodyA, | |
bodyB = this.bodyB; | |
vec2.rotate(l,this.localOffsetB,bodyA.angle); | |
vec2.rotate(r,this.localOffsetB,bodyB.angle - this.localAngleB); | |
vec2.scale(r,r,-1); | |
vec2.rotate(t,r,Math.PI/2); | |
vec2.normalize(t,t); | |
x.G[0] = -1; | |
x.G[1] = 0; | |
x.G[2] = -vec2.crossLength(l,xAxis); | |
x.G[3] = 1; | |
y.G[0] = 0; | |
y.G[1] = -1; | |
y.G[2] = -vec2.crossLength(l,yAxis); | |
y.G[4] = 1; | |
rot.G[0] = -t[0]; | |
rot.G[1] = -t[1]; | |
rot.G[3] = t[0]; | |
rot.G[4] = t[1]; | |
rot.G[5] = vec2.crossLength(r,t); | |
}; | |
},{"../equations/Equation":22,"../math/vec2":30,"./Constraint":14}],18:[function(_dereq_,module,exports){ | |
var Constraint = _dereq_('./Constraint') | |
, ContactEquation = _dereq_('../equations/ContactEquation') | |
, Equation = _dereq_('../equations/Equation') | |
, vec2 = _dereq_('../math/vec2') | |
, RotationalLockEquation = _dereq_('../equations/RotationalLockEquation'); | |
module.exports = PrismaticConstraint; | |
/** | |
* Constraint that only allows bodies to move along a line, relative to each other. See <a href="http://www.iforce2d.net/b2dtut/joints-prismatic">this tutorial</a>. Also called "slider constraint". | |
* | |
* @class PrismaticConstraint | |
* @constructor | |
* @extends Constraint | |
* @author schteppe | |
* @param {Body} bodyA | |
* @param {Body} bodyB | |
* @param {Object} [options] | |
* @param {Number} [options.maxForce] Max force to be applied by the constraint | |
* @param {Array} [options.localAnchorA] Body A's anchor point, defined in its own local frame. | |
* @param {Array} [options.localAnchorB] Body B's anchor point, defined in its own local frame. | |
* @param {Array} [options.localAxisA] An axis, defined in body A frame, that body B's anchor point may slide along. | |
* @param {Boolean} [options.disableRotationalLock] If set to true, bodyB will be free to rotate around its anchor point. | |
* @param {Number} [options.upperLimit] | |
* @param {Number} [options.lowerLimit] | |
* @todo Ability to create using only a point and a worldAxis | |
*/ | |
function PrismaticConstraint(bodyA, bodyB, options){ | |
options = options || {}; | |
Constraint.call(this,bodyA,bodyB,Constraint.PRISMATIC,options); | |
// Get anchors | |
var localAnchorA = vec2.fromValues(0,0), | |
localAxisA = vec2.fromValues(1,0), | |
localAnchorB = vec2.fromValues(0,0); | |
if(options.localAnchorA){ vec2.copy(localAnchorA, options.localAnchorA); } | |
if(options.localAxisA){ vec2.copy(localAxisA, options.localAxisA); } | |
if(options.localAnchorB){ vec2.copy(localAnchorB, options.localAnchorB); } | |
/** | |
* @property localAnchorA | |
* @type {Array} | |
*/ | |
this.localAnchorA = localAnchorA; | |
/** | |
* @property localAnchorB | |
* @type {Array} | |
*/ | |
this.localAnchorB = localAnchorB; | |
/** | |
* @property localAxisA | |
* @type {Array} | |
*/ | |
this.localAxisA = localAxisA; | |
/* | |
The constraint violation for the common axis point is | |
g = ( xj + rj - xi - ri ) * t := gg*t | |
where r are body-local anchor points, and t is a tangent to the constraint axis defined in body i frame. | |
gdot = ( vj + wj x rj - vi - wi x ri ) * t + ( xj + rj - xi - ri ) * ( wi x t ) | |
Note the use of the chain rule. Now we identify the jacobian | |
G*W = [ -t -ri x t + t x gg t rj x t ] * [vi wi vj wj] | |
The rotational part is just a rotation lock. | |
*/ | |
var maxForce = this.maxForce = typeof(options.maxForce)!=="undefined" ? options.maxForce : Number.MAX_VALUE; | |
// Translational part | |
var trans = new Equation(bodyA,bodyB,-maxForce,maxForce); | |
var ri = new vec2.create(), | |
rj = new vec2.create(), | |
gg = new vec2.create(), | |
t = new vec2.create(); | |
trans.computeGq = function(){ | |
// g = ( xj + rj - xi - ri ) * t | |
return vec2.dot(gg,t); | |
}; | |
trans.updateJacobian = function(){ | |
var G = this.G, | |
xi = bodyA.position, | |
xj = bodyB.position; | |
vec2.rotate(ri,localAnchorA,bodyA.angle); | |
vec2.rotate(rj,localAnchorB,bodyB.angle); | |
vec2.add(gg,xj,rj); | |
vec2.sub(gg,gg,xi); | |
vec2.sub(gg,gg,ri); | |
vec2.rotate(t,localAxisA,bodyA.angle+Math.PI/2); | |
G[0] = -t[0]; | |
G[1] = -t[1]; | |
G[2] = -vec2.crossLength(ri,t) + vec2.crossLength(t,gg); | |
G[3] = t[0]; | |
G[4] = t[1]; | |
G[5] = vec2.crossLength(rj,t); | |
}; | |
this.equations.push(trans); | |
// Rotational part | |
if(!options.disableRotationalLock){ | |
var rot = new RotationalLockEquation(bodyA,bodyB,-maxForce,maxForce); | |
this.equations.push(rot); | |
} | |
/** | |
* The position of anchor A relative to anchor B, along the constraint axis. | |
* @property position | |
* @type {Number} | |
*/ | |
this.position = 0; | |
// Is this one used at all? | |
this.velocity = 0; | |
/** | |
* Set to true to enable lower limit. | |
* @property lowerLimitEnabled | |
* @type {Boolean} | |
*/ | |
this.lowerLimitEnabled = typeof(options.lowerLimit)!=="undefined" ? true : false; | |
/** | |
* Set to true to enable upper limit. | |
* @property upperLimitEnabled | |
* @type {Boolean} | |
*/ | |
this.upperLimitEnabled = typeof(options.upperLimit)!=="undefined" ? true : false; | |
/** | |
* Lower constraint limit. The constraint position is forced to be larger than this value. | |
* @property lowerLimit | |
* @type {Number} | |
*/ | |
this.lowerLimit = typeof(options.lowerLimit)!=="undefined" ? options.lowerLimit : 0; | |
/** | |
* Upper constraint limit. The constraint position is forced to be smaller than this value. | |
* @property upperLimit | |
* @type {Number} | |
*/ | |
this.upperLimit = typeof(options.upperLimit)!=="undefined" ? options.upperLimit : 1; | |
// Equations used for limits | |
this.upperLimitEquation = new ContactEquation(bodyA,bodyB); | |
this.lowerLimitEquation = new ContactEquation(bodyA,bodyB); | |
// Set max/min forces | |
this.upperLimitEquation.minForce = this.lowerLimitEquation.minForce = 0; | |
this.upperLimitEquation.maxForce = this.lowerLimitEquation.maxForce = maxForce; | |
/** | |
* Equation used for the motor. | |
* @property motorEquation | |
* @type {Equation} | |
*/ | |
this.motorEquation = new Equation(bodyA,bodyB); | |
/** | |
* The current motor state. Enable or disable the motor using .enableMotor | |
* @property motorEnabled | |
* @type {Boolean} | |
*/ | |
this.motorEnabled = false; | |
/** | |
* Set the target speed for the motor. | |
* @property motorSpeed | |
* @type {Number} | |
*/ | |
this.motorSpeed = 0; | |
var that = this; | |
var motorEquation = this.motorEquation; | |
var old = motorEquation.computeGW; | |
motorEquation.computeGq = function(){ return 0; }; | |
motorEquation.computeGW = function(){ | |
var G = this.G, | |
bi = this.bodyA, | |
bj = this.bodyB, | |
vi = bi.velocity, | |
vj = bj.velocity, | |
wi = bi.angularVelocity, | |
wj = bj.angularVelocity; | |
return this.gmult(G,vi,wi,vj,wj) + that.motorSpeed; | |
}; | |
} | |
PrismaticConstraint.prototype = new Constraint(); | |
PrismaticConstraint.prototype.constructor = PrismaticConstraint; | |
var worldAxisA = vec2.create(), | |
worldAnchorA = vec2.create(), | |
worldAnchorB = vec2.create(), | |
orientedAnchorA = vec2.create(), | |
orientedAnchorB = vec2.create(), | |
tmp = vec2.create(); | |
/** | |
* Update the constraint equations. Should be done if any of the bodies changed position, before solving. | |
* @method update | |
*/ | |
PrismaticConstraint.prototype.update = function(){ | |
var eqs = this.equations, | |
trans = eqs[0], | |
upperLimit = this.upperLimit, | |
lowerLimit = this.lowerLimit, | |
upperLimitEquation = this.upperLimitEquation, | |
lowerLimitEquation = this.lowerLimitEquation, | |
bodyA = this.bodyA, | |
bodyB = this.bodyB, | |
localAxisA = this.localAxisA, | |
localAnchorA = this.localAnchorA, | |
localAnchorB = this.localAnchorB; | |
trans.updateJacobian(); | |
// Transform local things to world | |
vec2.rotate(worldAxisA, localAxisA, bodyA.angle); | |
vec2.rotate(orientedAnchorA, localAnchorA, bodyA.angle); | |
vec2.add(worldAnchorA, orientedAnchorA, bodyA.position); | |
vec2.rotate(orientedAnchorB, localAnchorB, bodyB.angle); | |
vec2.add(worldAnchorB, orientedAnchorB, bodyB.position); | |
var relPosition = this.position = vec2.dot(worldAnchorB,worldAxisA) - vec2.dot(worldAnchorA,worldAxisA); | |
// Motor | |
if(this.motorEnabled){ | |
// G = [ a a x ri -a -a x rj ] | |
var G = this.motorEquation.G; | |
G[0] = worldAxisA[0]; | |
G[1] = worldAxisA[1]; | |
G[2] = vec2.crossLength(worldAxisA,orientedAnchorB); | |
G[3] = -worldAxisA[0]; | |
G[4] = -worldAxisA[1]; | |
G[5] = -vec2.crossLength(worldAxisA,orientedAnchorA); | |
} | |
/* | |
Limits strategy: | |
Add contact equation, with normal along the constraint axis. | |
min/maxForce is set so the constraint is repulsive in the correct direction. | |
Some offset is added to either equation.contactPointA or .contactPointB to get the correct upper/lower limit. | |
^ | |
| | |
upperLimit x | |
| ------ | |
anchorB x<---| B | | |
| | | | |
------ | ------ | |
| | | | |
| A |-->x anchorA | |
------ | | |
x lowerLimit | |
| | |
axis | |
*/ | |
if(this.upperLimitEnabled && relPosition > upperLimit){ | |
// Update contact constraint normal, etc | |
vec2.scale(upperLimitEquation.normalA, worldAxisA, -1); | |
vec2.sub(upperLimitEquation.contactPointA, worldAnchorA, bodyA.position); | |
vec2.sub(upperLimitEquation.contactPointB, worldAnchorB, bodyB.position); | |
vec2.scale(tmp,worldAxisA,upperLimit); | |
vec2.add(upperLimitEquation.contactPointA,upperLimitEquation.contactPointA,tmp); | |
if(eqs.indexOf(upperLimitEquation) === -1){ | |
eqs.push(upperLimitEquation); | |
} | |
} else { | |
var idx = eqs.indexOf(upperLimitEquation); | |
if(idx !== -1){ | |
eqs.splice(idx,1); | |
} | |
} | |
if(this.lowerLimitEnabled && relPosition < lowerLimit){ | |
// Update contact constraint normal, etc | |
vec2.scale(lowerLimitEquation.normalA, worldAxisA, 1); | |
vec2.sub(lowerLimitEquation.contactPointA, worldAnchorA, bodyA.position); | |
vec2.sub(lowerLimitEquation.contactPointB, worldAnchorB, bodyB.position); | |
vec2.scale(tmp,worldAxisA,lowerLimit); | |
vec2.sub(lowerLimitEquation.contactPointB,lowerLimitEquation.contactPointB,tmp); | |
if(eqs.indexOf(lowerLimitEquation) === -1){ | |
eqs.push(lowerLimitEquation); | |
} | |
} else { | |
var idx = eqs.indexOf(lowerLimitEquation); | |
if(idx !== -1){ | |
eqs.splice(idx,1); | |
} | |
} | |
}; | |
/** | |
* Enable the motor | |
* @method enableMotor | |
*/ | |
PrismaticConstraint.prototype.enableMotor = function(){ | |
if(this.motorEnabled){ | |
return; | |
} | |
this.equations.push(this.motorEquation); | |
this.motorEnabled = true; | |
}; | |
/** | |
* Disable the rotational motor | |
* @method disableMotor | |
*/ | |
PrismaticConstraint.prototype.disableMotor = function(){ | |
if(!this.motorEnabled){ | |
return; | |
} | |
var i = this.equations.indexOf(this.motorEquation); | |
this.equations.splice(i,1); | |
this.motorEnabled = false; | |
}; | |
/** | |
* Set the constraint limits. | |
* @method setLimits | |
* @param {number} lower Lower limit. | |
* @param {number} upper Upper limit. | |
*/ | |
PrismaticConstraint.prototype.setLimits = function (lower, upper) { | |
if(typeof(lower) === 'number'){ | |
this.lowerLimit = lower; | |
this.lowerLimitEnabled = true; | |
} else { | |
this.lowerLimit = lower; | |
this.lowerLimitEnabled = false; | |
} | |
if(typeof(upper) === 'number'){ | |
this.upperLimit = upper; | |
this.upperLimitEnabled = true; | |
} else { | |
this.upperLimit = upper; | |
this.upperLimitEnabled = false; | |
} | |
}; | |
},{"../equations/ContactEquation":21,"../equations/Equation":22,"../equations/RotationalLockEquation":24,"../math/vec2":30,"./Constraint":14}],19:[function(_dereq_,module,exports){ | |
var Constraint = _dereq_('./Constraint') | |
, Equation = _dereq_('../equations/Equation') | |
, RotationalVelocityEquation = _dereq_('../equations/RotationalVelocityEquation') | |
, RotationalLockEquation = _dereq_('../equations/RotationalLockEquation') | |
, vec2 = _dereq_('../math/vec2'); | |
module.exports = RevoluteConstraint; | |
var worldPivotA = vec2.create(), | |
worldPivotB = vec2.create(), | |
xAxis = vec2.fromValues(1,0), | |
yAxis = vec2.fromValues(0,1), | |
g = vec2.create(); | |
/** | |
* Connects two bodies at given offset points, letting them rotate relative to each other around this point. | |
* @class RevoluteConstraint | |
* @constructor | |
* @author schteppe | |
* @param {Body} bodyA | |
* @param {Body} bodyB | |
* @param {Object} [options] | |
* @param {Array} [options.worldPivot] A pivot point given in world coordinates. If specified, localPivotA and localPivotB are automatically computed from this value. | |
* @param {Array} [options.localPivotA] The point relative to the center of mass of bodyA which bodyA is constrained to. | |
* @param {Array} [options.localPivotB] See localPivotA. | |
* @param {Number} [options.maxForce] The maximum force that should be applied to constrain the bodies. | |
* @extends Constraint | |
* | |
* @example | |
* // This will create a revolute constraint between two bodies with pivot point in between them. | |
* var bodyA = new Body({ mass: 1, position: [-1, 0] }); | |
* var bodyB = new Body({ mass: 1, position: [1, 0] }); | |
* var constraint = new RevoluteConstraint(bodyA, bodyB, { | |
* worldPivot: [0, 0] | |
* }); | |
* world.addConstraint(constraint); | |
* | |
* // Using body-local pivot points, the constraint could have been constructed like this: | |
* var constraint = new RevoluteConstraint(bodyA, bodyB, { | |
* localPivotA: [1, 0], | |
* localPivotB: [-1, 0] | |
* }); | |
*/ | |
function RevoluteConstraint(bodyA, bodyB, options){ | |
options = options || {}; | |
Constraint.call(this,bodyA,bodyB,Constraint.REVOLUTE,options); | |
var maxForce = this.maxForce = typeof(options.maxForce) !== "undefined" ? options.maxForce : Number.MAX_VALUE; | |
/** | |
* @property {Array} pivotA | |
*/ | |
this.pivotA = vec2.create(); | |
/** | |
* @property {Array} pivotB | |
*/ | |
this.pivotB = vec2.create(); | |
if(options.worldPivot){ | |
// Compute pivotA and pivotB | |
vec2.sub(this.pivotA, options.worldPivot, bodyA.position); | |
vec2.sub(this.pivotB, options.worldPivot, bodyB.position); | |
// Rotate to local coordinate system | |
vec2.rotate(this.pivotA, this.pivotA, -bodyA.angle); | |
vec2.rotate(this.pivotB, this.pivotB, -bodyB.angle); | |
} else { | |
// Get pivotA and pivotB | |
vec2.copy(this.pivotA, options.localPivotA); | |
vec2.copy(this.pivotB, options.localPivotB); | |
} | |
// Equations to be fed to the solver | |
var eqs = this.equations = [ | |
new Equation(bodyA,bodyB,-maxForce,maxForce), | |
new Equation(bodyA,bodyB,-maxForce,maxForce), | |
]; | |
var x = eqs[0]; | |
var y = eqs[1]; | |
var that = this; | |
x.computeGq = function(){ | |
vec2.rotate(worldPivotA, that.pivotA, bodyA.angle); | |
vec2.rotate(worldPivotB, that.pivotB, bodyB.angle); | |
vec2.add(g, bodyB.position, worldPivotB); | |
vec2.sub(g, g, bodyA.position); | |
vec2.sub(g, g, worldPivotA); | |
return vec2.dot(g,xAxis); | |
}; | |
y.computeGq = function(){ | |
vec2.rotate(worldPivotA, that.pivotA, bodyA.angle); | |
vec2.rotate(worldPivotB, that.pivotB, bodyB.angle); | |
vec2.add(g, bodyB.position, worldPivotB); | |
vec2.sub(g, g, bodyA.position); | |
vec2.sub(g, g, worldPivotA); | |
return vec2.dot(g,yAxis); | |
}; | |
y.minForce = x.minForce = -maxForce; | |
y.maxForce = x.maxForce = maxForce; | |
this.motorEquation = new RotationalVelocityEquation(bodyA,bodyB); | |
/** | |
* Indicates whether the motor is enabled. Use .enableMotor() to enable the constraint motor. | |
* @property {Boolean} motorEnabled | |
* @readOnly | |
*/ | |
this.motorEnabled = false; | |
/** | |
* The constraint position. | |
* @property angle | |
* @type {Number} | |
* @readOnly | |
*/ | |
this.angle = 0; | |
/** | |
* Set to true to enable lower limit | |
* @property lowerLimitEnabled | |
* @type {Boolean} | |
*/ | |
this.lowerLimitEnabled = false; | |
/** | |
* Set to true to enable upper limit | |
* @property upperLimitEnabled | |
* @type {Boolean} | |
*/ | |
this.upperLimitEnabled = false; | |
/** | |
* The lower limit on the constraint angle. | |
* @property lowerLimit | |
* @type {Boolean} | |
*/ | |
this.lowerLimit = 0; | |
/** | |
* The upper limit on the constraint angle. | |
* @property upperLimit | |
* @type {Boolean} | |
*/ | |
this.upperLimit = 0; | |
this.upperLimitEquation = new RotationalLockEquation(bodyA,bodyB); | |
this.lowerLimitEquation = new RotationalLockEquation(bodyA,bodyB); | |
this.upperLimitEquation.minForce = 0; | |
this.lowerLimitEquation.maxForce = 0; | |
} | |
RevoluteConstraint.prototype = new Constraint(); | |
RevoluteConstraint.prototype.constructor = RevoluteConstraint; | |
/** | |
* Set the constraint angle limits. | |
* @method setLimits | |
* @param {number} lower Lower angle limit. | |
* @param {number} upper Upper angle limit. | |
*/ | |
RevoluteConstraint.prototype.setLimits = function (lower, upper) { | |
if(typeof(lower) === 'number'){ | |
this.lowerLimit = lower; | |
this.lowerLimitEnabled = true; | |
} else { | |
this.lowerLimit = lower; | |
this.lowerLimitEnabled = false; | |
} | |
if(typeof(upper) === 'number'){ | |
this.upperLimit = upper; | |
this.upperLimitEnabled = true; | |
} else { | |
this.upperLimit = upper; | |
this.upperLimitEnabled = false; | |
} | |
}; | |
RevoluteConstraint.prototype.update = function(){ | |
var bodyA = this.bodyA, | |
bodyB = this.bodyB, | |
pivotA = this.pivotA, | |
pivotB = this.pivotB, | |
eqs = this.equations, | |
normal = eqs[0], | |
tangent= eqs[1], | |
x = eqs[0], | |
y = eqs[1], | |
upperLimit = this.upperLimit, | |
lowerLimit = this.lowerLimit, | |
upperLimitEquation = this.upperLimitEquation, | |
lowerLimitEquation = this.lowerLimitEquation; | |
var relAngle = this.angle = bodyB.angle - bodyA.angle; | |
if(this.upperLimitEnabled && relAngle > upperLimit){ | |
upperLimitEquation.angle = upperLimit; | |
if(eqs.indexOf(upperLimitEquation) === -1){ | |
eqs.push(upperLimitEquation); | |
} | |
} else { | |
var idx = eqs.indexOf(upperLimitEquation); | |
if(idx !== -1){ | |
eqs.splice(idx,1); | |
} | |
} | |
if(this.lowerLimitEnabled && relAngle < lowerLimit){ | |
lowerLimitEquation.angle = lowerLimit; | |
if(eqs.indexOf(lowerLimitEquation) === -1){ | |
eqs.push(lowerLimitEquation); | |
} | |
} else { | |
var idx = eqs.indexOf(lowerLimitEquation); | |
if(idx !== -1){ | |
eqs.splice(idx,1); | |
} | |
} | |
/* | |
The constraint violation is | |
g = xj + rj - xi - ri | |
...where xi and xj are the body positions and ri and rj world-oriented offset vectors. Differentiate: | |
gdot = vj + wj x rj - vi - wi x ri | |
We split this into x and y directions. (let x and y be unit vectors along the respective axes) | |
gdot * x = ( vj + wj x rj - vi - wi x ri ) * x | |
= ( vj*x + (wj x rj)*x -vi*x -(wi x ri)*x | |
= ( vj*x + (rj x x)*wj -vi*x -(ri x x)*wi | |
= [ -x -(ri x x) x (rj x x)] * [vi wi vj wj] | |
= G*W | |
...and similar for y. We have then identified the jacobian entries for x and y directions: | |
Gx = [ x (rj x x) -x -(ri x x)] | |
Gy = [ y (rj x y) -y -(ri x y)] | |
*/ | |
vec2.rotate(worldPivotA, pivotA, bodyA.angle); | |
vec2.rotate(worldPivotB, pivotB, bodyB.angle); | |
// todo: these are a bit sparse. We could save some computations on making custom eq.computeGW functions, etc | |
x.G[0] = -1; | |
x.G[1] = 0; | |
x.G[2] = -vec2.crossLength(worldPivotA,xAxis); | |
x.G[3] = 1; | |
x.G[4] = 0; | |
x.G[5] = vec2.crossLength(worldPivotB,xAxis); | |
y.G[0] = 0; | |
y.G[1] = -1; | |
y.G[2] = -vec2.crossLength(worldPivotA,yAxis); | |
y.G[3] = 0; | |
y.G[4] = 1; | |
y.G[5] = vec2.crossLength(worldPivotB,yAxis); | |
}; | |
/** | |
* Enable the rotational motor | |
* @method enableMotor | |
*/ | |
RevoluteConstraint.prototype.enableMotor = function(){ | |
if(this.motorEnabled){ | |
return; | |
} | |
this.equations.push(this.motorEquation); | |
this.motorEnabled = true; | |
}; | |
/** | |
* Disable the rotational motor | |
* @method disableMotor | |
*/ | |
RevoluteConstraint.prototype.disableMotor = function(){ | |
if(!this.motorEnabled){ | |
return; | |
} | |
var i = this.equations.indexOf(this.motorEquation); | |
this.equations.splice(i,1); | |
this.motorEnabled = false; | |
}; | |
/** | |
* Check if the motor is enabled. | |
* @method motorIsEnabled | |
* @deprecated use property motorEnabled instead. | |
* @return {Boolean} | |
*/ | |
RevoluteConstraint.prototype.motorIsEnabled = function(){ | |
return !!this.motorEnabled; | |
}; | |
/** | |
* Set the speed of the rotational constraint motor | |
* @method setMotorSpeed | |
* @param {Number} speed | |
*/ | |
RevoluteConstraint.prototype.setMotorSpeed = function(speed){ | |
if(!this.motorEnabled){ | |
return; | |
} | |
var i = this.equations.indexOf(this.motorEquation); | |
this.equations[i].relativeVelocity = speed; | |
}; | |
/** | |
* Get the speed of the rotational constraint motor | |
* @method getMotorSpeed | |
* @return {Number} The current speed, or false if the motor is not enabled. | |
*/ | |
RevoluteConstraint.prototype.getMotorSpeed = function(){ | |
if(!this.motorEnabled){ | |
return false; | |
} | |
return this.motorEquation.relativeVelocity; | |
}; | |
},{"../equations/Equation":22,"../equations/RotationalLockEquation":24,"../equations/RotationalVelocityEquation":25,"../math/vec2":30,"./Constraint":14}],20:[function(_dereq_,module,exports){ | |
var Equation = _dereq_("./Equation"), | |
vec2 = _dereq_('../math/vec2'); | |
module.exports = AngleLockEquation; | |
/** | |
* Locks the relative angle between two bodies. The constraint tries to keep the dot product between two vectors, local in each body, to zero. The local angle in body i is a parameter. | |
* | |
* @class AngleLockEquation | |
* @constructor | |
* @extends Equation | |
* @param {Body} bodyA | |
* @param {Body} bodyB | |
* @param {Object} [options] | |
* @param {Number} [options.angle] Angle to add to the local vector in body A. | |
* @param {Number} [options.ratio] Gear ratio | |
*/ | |
function AngleLockEquation(bodyA, bodyB, options){ | |
options = options || {}; | |
Equation.call(this,bodyA,bodyB,-Number.MAX_VALUE,Number.MAX_VALUE); | |
this.angle = options.angle || 0; | |
/** | |
* The gear ratio. | |
* @property {Number} ratio | |
* @private | |
* @see setRatio | |
*/ | |
this.ratio = typeof(options.ratio)==="number" ? options.ratio : 1; | |
this.setRatio(this.ratio); | |
} | |
AngleLockEquation.prototype = new Equation(); | |
AngleLockEquation.prototype.constructor = AngleLockEquation; | |
AngleLockEquation.prototype.computeGq = function(){ | |
return this.ratio * this.bodyA.angle - this.bodyB.angle + this.angle; | |
}; | |
/** | |
* Set the gear ratio for this equation | |
* @method setRatio | |
* @param {Number} ratio | |
*/ | |
AngleLockEquation.prototype.setRatio = function(ratio){ | |
var G = this.G; | |
G[2] = ratio; | |
G[5] = -1; | |
this.ratio = ratio; | |
}; | |
/** | |
* Set the max force for the equation. | |
* @method setMaxTorque | |
* @param {Number} torque | |
*/ | |
AngleLockEquation.prototype.setMaxTorque = function(torque){ | |
this.maxForce = torque; | |
this.minForce = -torque; | |
}; | |
},{"../math/vec2":30,"./Equation":22}],21:[function(_dereq_,module,exports){ | |
var Equation = _dereq_("./Equation"), | |
vec2 = _dereq_('../math/vec2'); | |
module.exports = ContactEquation; | |
/** | |
* Non-penetration constraint equation. Tries to make the contactPointA and contactPointB vectors coincide, while keeping the applied force repulsive. | |
* | |
* @class ContactEquation | |
* @constructor | |
* @extends Equation | |
* @param {Body} bodyA | |
* @param {Body} bodyB | |
*/ | |
function ContactEquation(bodyA, bodyB){ | |
Equation.call(this, bodyA, bodyB, 0, Number.MAX_VALUE); | |
/** | |
* Vector from body i center of mass to the contact point. | |
* @property contactPointA | |
* @type {Array} | |
*/ | |
this.contactPointA = vec2.create(); | |
this.penetrationVec = vec2.create(); | |
/** | |
* World-oriented vector from body A center of mass to the contact point. | |
* @property contactPointB | |
* @type {Array} | |
*/ | |
this.contactPointB = vec2.create(); | |
/** | |
* The normal vector, pointing out of body i | |
* @property normalA | |
* @type {Array} | |
*/ | |
this.normalA = vec2.create(); | |
/** | |
* The restitution to use (0=no bounciness, 1=max bounciness). | |
* @property restitution | |
* @type {Number} | |
*/ | |
this.restitution = 0; | |
/** | |
* This property is set to true if this is the first impact between the bodies (not persistant contact). | |
* @property firstImpact | |
* @type {Boolean} | |
* @readOnly | |
*/ | |
this.firstImpact = false; | |
/** | |
* The shape in body i that triggered this contact. | |
* @property shapeA | |
* @type {Shape} | |
*/ | |
this.shapeA = null; | |
/** | |
* The shape in body j that triggered this contact. | |
* @property shapeB | |
* @type {Shape} | |
*/ | |
this.shapeB = null; | |
} | |
ContactEquation.prototype = new Equation(); | |
ContactEquation.prototype.constructor = ContactEquation; | |
ContactEquation.prototype.computeB = function(a,b,h){ | |
var bi = this.bodyA, | |
bj = this.bodyB, | |
ri = this.contactPointA, | |
rj = this.contactPointB, | |
xi = bi.position, | |
xj = bj.position; | |
var penetrationVec = this.penetrationVec, | |
n = this.normalA, | |
G = this.G; | |
// Caluclate cross products | |
var rixn = vec2.crossLength(ri,n), | |
rjxn = vec2.crossLength(rj,n); | |
// G = [-n -rixn n rjxn] | |
G[0] = -n[0]; | |
G[1] = -n[1]; | |
G[2] = -rixn; | |
G[3] = n[0]; | |
G[4] = n[1]; | |
G[5] = rjxn; | |
// Calculate q = xj+rj -(xi+ri) i.e. the penetration vector | |
vec2.add(penetrationVec,xj,rj); | |
vec2.sub(penetrationVec,penetrationVec,xi); | |
vec2.sub(penetrationVec,penetrationVec,ri); | |
// Compute iteration | |
var GW, Gq; | |
if(this.firstImpact && this.restitution !== 0){ | |
Gq = 0; | |
GW = (1/b)*(1+this.restitution) * this.computeGW(); | |
} else { | |
Gq = vec2.dot(n,penetrationVec) + this.offset; | |
GW = this.computeGW(); | |
} | |
var GiMf = this.computeGiMf(); | |
var B = - Gq * a - GW * b - h*GiMf; | |
return B; | |
}; | |
},{"../math/vec2":30,"./Equation":22}],22:[function(_dereq_,module,exports){ | |
module.exports = Equation; | |
var vec2 = _dereq_('../math/vec2'), | |
Utils = _dereq_('../utils/Utils'), | |
Body = _dereq_('../objects/Body'); | |
/** | |
* Base class for constraint equations. | |
* @class Equation | |
* @constructor | |
* @param {Body} bodyA First body participating in the equation | |
* @param {Body} bodyB Second body participating in the equation | |
* @param {number} minForce Minimum force to apply. Default: -Number.MAX_VALUE | |
* @param {number} maxForce Maximum force to apply. Default: Number.MAX_VALUE | |
*/ | |
function Equation(bodyA, bodyB, minForce, maxForce){ | |
/** | |
* Minimum force to apply when solving. | |
* @property minForce | |
* @type {Number} | |
*/ | |
this.minForce = typeof(minForce)==="undefined" ? -Number.MAX_VALUE : minForce; | |
/** | |
* Max force to apply when solving. | |
* @property maxForce | |
* @type {Number} | |
*/ | |
this.maxForce = typeof(maxForce)==="undefined" ? Number.MAX_VALUE : maxForce; | |
/** | |
* First body participating in the constraint | |
* @property bodyA | |
* @type {Body} | |
*/ | |
this.bodyA = bodyA; | |
/** | |
* Second body participating in the constraint | |
* @property bodyB | |
* @type {Body} | |
*/ | |
this.bodyB = bodyB; | |
/** | |
* The stiffness of this equation. Typically chosen to a large number (~1e7), but can be chosen somewhat freely to get a stable simulation. | |
* @property stiffness | |
* @type {Number} | |
*/ | |
this.stiffness = Equation.DEFAULT_STIFFNESS; | |
/** | |
* The number of time steps needed to stabilize the constraint equation. Typically between 3 and 5 time steps. | |
* @property relaxation | |
* @type {Number} | |
*/ | |
this.relaxation = Equation.DEFAULT_RELAXATION; | |
/** | |
* The Jacobian entry of this equation. 6 numbers, 3 per body (x,y,angle). | |
* @property G | |
* @type {Array} | |
*/ | |
this.G = new Utils.ARRAY_TYPE(6); | |
for(var i=0; i<6; i++){ | |
this.G[i]=0; | |
} | |
this.offset = 0; | |
this.a = 0; | |
this.b = 0; | |
this.epsilon = 0; | |
this.timeStep = 1/60; | |
/** | |
* Indicates if stiffness or relaxation was changed. | |
* @property {Boolean} needsUpdate | |
*/ | |
this.needsUpdate = true; | |
/** | |
* The resulting constraint multiplier from the last solve. This is mostly equivalent to the force produced by the constraint. | |
* @property multiplier | |
* @type {Number} | |
*/ | |
this.multiplier = 0; | |
/** | |
* Relative velocity. | |
* @property {Number} relativeVelocity | |
*/ | |
this.relativeVelocity = 0; | |
/** | |
* Whether this equation is enabled or not. If true, it will be added to the solver. | |
* @property {Boolean} enabled | |
*/ | |
this.enabled = true; | |
} | |
Equation.prototype.constructor = Equation; | |
/** | |
* The default stiffness when creating a new Equation. | |
* @static | |
* @property {Number} DEFAULT_STIFFNESS | |
* @default 1e6 | |
*/ | |
Equation.DEFAULT_STIFFNESS = 1e6; | |
/** | |
* The default relaxation when creating a new Equation. | |
* @static | |
* @property {Number} DEFAULT_RELAXATION | |
* @default 4 | |
*/ | |
Equation.DEFAULT_RELAXATION = 4; | |
/** | |
* Compute SPOOK parameters .a, .b and .epsilon according to the current parameters. See equations 9, 10 and 11 in the <a href="http://www8.cs.umu.se/kurser/5DV058/VT09/lectures/spooknotes.pdf">SPOOK notes</a>. | |
* @method update | |
*/ | |
Equation.prototype.update = function(){ | |
var k = this.stiffness, | |
d = this.relaxation, | |
h = this.timeStep; | |
this.a = 4.0 / (h * (1 + 4 * d)); | |
this.b = (4.0 * d) / (1 + 4 * d); | |
this.epsilon = 4.0 / (h * h * k * (1 + 4 * d)); | |
this.needsUpdate = false; | |
}; | |
/** | |
* Multiply a jacobian entry with corresponding positions or velocities | |
* @method gmult | |
* @return {Number} | |
*/ | |
Equation.prototype.gmult = function(G,vi,wi,vj,wj){ | |
return G[0] * vi[0] + | |
G[1] * vi[1] + | |
G[2] * wi + | |
G[3] * vj[0] + | |
G[4] * vj[1] + | |
G[5] * wj; | |
}; | |
/** | |
* Computes the RHS of the SPOOK equation | |
* @method computeB | |
* @return {Number} | |
*/ | |
Equation.prototype.computeB = function(a,b,h){ | |
var GW = this.computeGW(); | |
var Gq = this.computeGq(); | |
var GiMf = this.computeGiMf(); | |
return - Gq * a - GW * b - GiMf*h; | |
}; | |
/** | |
* Computes G\*q, where q are the generalized body coordinates | |
* @method computeGq | |
* @return {Number} | |
*/ | |
var qi = vec2.create(), | |
qj = vec2.create(); | |
Equation.prototype.computeGq = function(){ | |
var G = this.G, | |
bi = this.bodyA, | |
bj = this.bodyB, | |
xi = bi.position, | |
xj = bj.position, | |
ai = bi.angle, | |
aj = bj.angle; | |
return this.gmult(G, qi, ai, qj, aj) + this.offset; | |
}; | |
/** | |
* Computes G\*W, where W are the body velocities | |
* @method computeGW | |
* @return {Number} | |
*/ | |
Equation.prototype.computeGW = function(){ | |
var G = this.G, | |
bi = this.bodyA, | |
bj = this.bodyB, | |
vi = bi.velocity, | |
vj = bj.velocity, | |
wi = bi.angularVelocity, | |
wj = bj.angularVelocity; | |
return this.gmult(G,vi,wi,vj,wj) + this.relativeVelocity; | |
}; | |
/** | |
* Computes G\*Wlambda, where W are the body velocities | |
* @method computeGWlambda | |
* @return {Number} | |
*/ | |
Equation.prototype.computeGWlambda = function(){ | |
var G = this.G, | |
bi = this.bodyA, | |
bj = this.bodyB, | |
vi = bi.vlambda, | |
vj = bj.vlambda, | |
wi = bi.wlambda, | |
wj = bj.wlambda; | |
return this.gmult(G,vi,wi,vj,wj); | |
}; | |
/** | |
* Computes G\*inv(M)\*f, where M is the mass matrix with diagonal blocks for each body, and f are the forces on the bodies. | |
* @method computeGiMf | |
* @return {Number} | |
*/ | |
var iMfi = vec2.create(), | |
iMfj = vec2.create(); | |
Equation.prototype.computeGiMf = function(){ | |
var bi = this.bodyA, | |
bj = this.bodyB, | |
fi = bi.force, | |
ti = bi.angularForce, | |
fj = bj.force, | |
tj = bj.angularForce, | |
invMassi = bi.invMassSolve, | |
invMassj = bj.invMassSolve, | |
invIi = bi.invInertiaSolve, | |
invIj = bj.invInertiaSolve, | |
G = this.G; | |
vec2.scale(iMfi, fi, invMassi); | |
vec2.multiply(iMfi, bi.massMultiplier, iMfi); | |
vec2.scale(iMfj, fj,invMassj); | |
vec2.multiply(iMfj, bj.massMultiplier, iMfj); | |
return this.gmult(G,iMfi,ti*invIi,iMfj,tj*invIj); | |
}; | |
/** | |
* Computes G\*inv(M)\*G' | |
* @method computeGiMGt | |
* @return {Number} | |
*/ | |
Equation.prototype.computeGiMGt = function(){ | |
var bi = this.bodyA, | |
bj = this.bodyB, | |
invMassi = bi.invMassSolve, | |
invMassj = bj.invMassSolve, | |
invIi = bi.invInertiaSolve, | |
invIj = bj.invInertiaSolve, | |
G = this.G; | |
return G[0] * G[0] * invMassi * bi.massMultiplier[0] + | |
G[1] * G[1] * invMassi * bi.massMultiplier[1] + | |
G[2] * G[2] * invIi + | |
G[3] * G[3] * invMassj * bj.massMultiplier[0] + | |
G[4] * G[4] * invMassj * bj.massMultiplier[1] + | |
G[5] * G[5] * invIj; | |
}; | |
var addToWlambda_temp = vec2.create(), | |
addToWlambda_Gi = vec2.create(), | |
addToWlambda_Gj = vec2.create(), | |
addToWlambda_ri = vec2.create(), | |
addToWlambda_rj = vec2.create(), | |
addToWlambda_Mdiag = vec2.create(); | |
/** | |
* Add constraint velocity to the bodies. | |
* @method addToWlambda | |
* @param {Number} deltalambda | |
*/ | |
Equation.prototype.addToWlambda = function(deltalambda){ | |
var bi = this.bodyA, | |
bj = this.bodyB, | |
temp = addToWlambda_temp, | |
Gi = addToWlambda_Gi, | |
Gj = addToWlambda_Gj, | |
ri = addToWlambda_ri, | |
rj = addToWlambda_rj, | |
invMassi = bi.invMassSolve, | |
invMassj = bj.invMassSolve, | |
invIi = bi.invInertiaSolve, | |
invIj = bj.invInertiaSolve, | |
Mdiag = addToWlambda_Mdiag, | |
G = this.G; | |
Gi[0] = G[0]; | |
Gi[1] = G[1]; | |
Gj[0] = G[3]; | |
Gj[1] = G[4]; | |
// Add to linear velocity | |
// v_lambda += inv(M) * delta_lamba * G | |
vec2.scale(temp, Gi, invMassi*deltalambda); | |
vec2.multiply(temp, temp, bi.massMultiplier); | |
vec2.add( bi.vlambda, bi.vlambda, temp); | |
// This impulse is in the offset frame | |
// Also add contribution to angular | |
//bi.wlambda -= vec2.crossLength(temp,ri); | |
bi.wlambda += invIi * G[2] * deltalambda; | |
vec2.scale(temp, Gj, invMassj*deltalambda); | |
vec2.multiply(temp, temp, bj.massMultiplier); | |
vec2.add( bj.vlambda, bj.vlambda, temp); | |
//bj.wlambda -= vec2.crossLength(temp,rj); | |
bj.wlambda += invIj * G[5] * deltalambda; | |
}; | |
/** | |
* Compute the denominator part of the SPOOK equation: C = G\*inv(M)\*G' + eps | |
* @method computeInvC | |
* @param {Number} eps | |
* @return {Number} | |
*/ | |
Equation.prototype.computeInvC = function(eps){ | |
return 1.0 / (this.computeGiMGt() + eps); | |
}; | |
},{"../math/vec2":30,"../objects/Body":31,"../utils/Utils":57}],23:[function(_dereq_,module,exports){ | |
var vec2 = _dereq_('../math/vec2') | |
, Equation = _dereq_('./Equation') | |
, Utils = _dereq_('../utils/Utils'); | |
module.exports = FrictionEquation; | |
/** | |
* Constrains the slipping in a contact along a tangent | |
* | |
* @class FrictionEquation | |
* @constructor | |
* @param {Body} bodyA | |
* @param {Body} bodyB | |
* @param {Number} slipForce | |
* @extends Equation | |
*/ | |
function FrictionEquation(bodyA, bodyB, slipForce){ | |
Equation.call(this, bodyA, bodyB, -slipForce, slipForce); | |
/** | |
* Relative vector from center of body A to the contact point, world oriented. | |
* @property contactPointA | |
* @type {Array} | |
*/ | |
this.contactPointA = vec2.create(); | |
/** | |
* Relative vector from center of body B to the contact point, world oriented. | |
* @property contactPointB | |
* @type {Array} | |
*/ | |
this.contactPointB = vec2.create(); | |
/** | |
* Tangent vector that the friction force will act along. World oriented. | |
* @property t | |
* @type {Array} | |
*/ | |
this.t = vec2.create(); | |
/** | |
* ContactEquations connected to this friction equation. The contact equations can be used to rescale the max force for the friction. If more than one contact equation is given, then the max force can be set to the average. | |
* @property contactEquations | |
* @type {ContactEquation} | |
*/ | |
this.contactEquations = []; | |
/** | |
* The shape in body i that triggered this friction. | |
* @property shapeA | |
* @type {Shape} | |
* @todo Needed? The shape can be looked up via contactEquation.shapeA... | |
*/ | |
this.shapeA = null; | |
/** | |
* The shape in body j that triggered this friction. | |
* @property shapeB | |
* @type {Shape} | |
* @todo Needed? The shape can be looked up via contactEquation.shapeB... | |
*/ | |
this.shapeB = null; | |
/** | |
* The friction coefficient to use. | |
* @property frictionCoefficient | |
* @type {Number} | |
*/ | |
this.frictionCoefficient = 0.3; | |
} | |
FrictionEquation.prototype = new Equation(); | |
FrictionEquation.prototype.constructor = FrictionEquation; | |
/** | |
* Set the slipping condition for the constraint. The friction force cannot be | |
* larger than this value. | |
* @method setSlipForce | |
* @param {Number} slipForce | |
*/ | |
FrictionEquation.prototype.setSlipForce = function(slipForce){ | |
this.maxForce = slipForce; | |
this.minForce = -slipForce; | |
}; | |
/** | |
* Get the max force for the constraint. | |
* @method getSlipForce | |
* @return {Number} | |
*/ | |
FrictionEquation.prototype.getSlipForce = function(){ | |
return this.maxForce; | |
}; | |
FrictionEquation.prototype.computeB = function(a,b,h){ | |
var bi = this.bodyA, | |
bj = this.bodyB, | |
ri = this.contactPointA, | |
rj = this.contactPointB, | |
t = this.t, | |
G = this.G; | |
// G = [-t -rixt t rjxt] | |
// And remember, this is a pure velocity constraint, g is always zero! | |
G[0] = -t[0]; | |
G[1] = -t[1]; | |
G[2] = -vec2.crossLength(ri,t); | |
G[3] = t[0]; | |
G[4] = t[1]; | |
G[5] = vec2.crossLength(rj,t); | |
var GW = this.computeGW(), | |
GiMf = this.computeGiMf(); | |
var B = /* - g * a */ - GW * b - h*GiMf; | |
return B; | |
}; | |
},{"../math/vec2":30,"../utils/Utils":57,"./Equation":22}],24:[function(_dereq_,module,exports){ | |
var Equation = _dereq_("./Equation"), | |
vec2 = _dereq_('../math/vec2'); | |
module.exports = RotationalLockEquation; | |
/** | |
* Locks the relative angle between two bodies. The constraint tries to keep the dot product between two vectors, local in each body, to zero. The local angle in body i is a parameter. | |
* | |
* @class RotationalLockEquation | |
* @constructor | |
* @extends Equation | |
* @param {Body} bodyA | |
* @param {Body} bodyB | |
* @param {Object} [options] | |
* @param {Number} [options.angle] Angle to add to the local vector in bodyA. | |
*/ | |
function RotationalLockEquation(bodyA, bodyB, options){ | |
options = options || {}; | |
Equation.call(this, bodyA, bodyB, -Number.MAX_VALUE, Number.MAX_VALUE); | |
/** | |
* @property {number} angle | |
*/ | |
this.angle = options.angle || 0; | |
var G = this.G; | |
G[2] = 1; | |
G[5] = -1; | |
} | |
RotationalLockEquation.prototype = new Equation(); | |
RotationalLockEquation.prototype.constructor = RotationalLockEquation; | |
var worldVectorA = vec2.create(), | |
worldVectorB = vec2.create(), | |
xAxis = vec2.fromValues(1,0), | |
yAxis = vec2.fromValues(0,1); | |
RotationalLockEquation.prototype.computeGq = function(){ | |
vec2.rotate(worldVectorA,xAxis,this.bodyA.angle+this.angle); | |
vec2.rotate(worldVectorB,yAxis,this.bodyB.angle); | |
return vec2.dot(worldVectorA,worldVectorB); | |
}; | |
},{"../math/vec2":30,"./Equation":22}],25:[function(_dereq_,module,exports){ | |
var Equation = _dereq_("./Equation"), | |
vec2 = _dereq_('../math/vec2'); | |
module.exports = RotationalVelocityEquation; | |
/** | |
* Syncs rotational velocity of two bodies, or sets a relative velocity (motor). | |
* | |
* @class RotationalVelocityEquation | |
* @constructor | |
* @extends Equation | |
* @param {Body} bodyA | |
* @param {Body} bodyB | |
*/ | |
function RotationalVelocityEquation(bodyA, bodyB){ | |
Equation.call(this, bodyA, bodyB, -Number.MAX_VALUE, Number.MAX_VALUE); | |
this.relativeVelocity = 1; | |
this.ratio = 1; | |
} | |
RotationalVelocityEquation.prototype = new Equation(); | |
RotationalVelocityEquation.prototype.constructor = RotationalVelocityEquation; | |
RotationalVelocityEquation.prototype.computeB = function(a,b,h){ | |
var G = this.G; | |
G[2] = -1; | |
G[5] = this.ratio; | |
var GiMf = this.computeGiMf(); | |
var GW = this.computeGW(); | |
var B = - GW * b - h*GiMf; | |
return B; | |
}; | |
},{"../math/vec2":30,"./Equation":22}],26:[function(_dereq_,module,exports){ | |
/** | |
* Base class for objects that dispatches events. | |
* @class EventEmitter | |
* @constructor | |
*/ | |
var EventEmitter = function () {}; | |
module.exports = EventEmitter; | |
EventEmitter.prototype = { | |
constructor: EventEmitter, | |
/** | |
* Add an event listener | |
* @method on | |
* @param {String} type | |
* @param {Function} listener | |
* @return {EventEmitter} The self object, for chainability. | |
*/ | |
on: function ( type, listener, context ) { | |
listener.context = context || this; | |
if ( this._listeners === undefined ){ | |
this._listeners = {}; | |
} | |
var listeners = this._listeners; | |
if ( listeners[ type ] === undefined ) { | |
listeners[ type ] = []; | |
} | |
if ( listeners[ type ].indexOf( listener ) === - 1 ) { | |
listeners[ type ].push( listener ); | |
} | |
return this; | |
}, | |
/** | |
* Check if an event listener is added | |
* @method has | |
* @param {String} type | |
* @param {Function} listener | |
* @return {Boolean} | |
*/ | |
has: function ( type, listener ) { | |
if ( this._listeners === undefined ){ | |
return false; | |
} | |
var listeners = this._listeners; | |
if(listener){ | |
if ( listeners[ type ] !== undefined && listeners[ type ].indexOf( listener ) !== - 1 ) { | |
return true; | |
} | |
} else { | |
if ( listeners[ type ] !== undefined ) { | |
return true; | |
} | |
} | |
return false; | |
}, | |
/** | |
* Remove an event listener | |
* @method off | |
* @param {String} type | |
* @param {Function} listener | |
* @return {EventEmitter} The self object, for chainability. | |
*/ | |
off: function ( type, listener ) { | |
if ( this._listeners === undefined ){ | |
return this; | |
} | |
var listeners = this._listeners; | |
var index = listeners[ type ].indexOf( listener ); | |
if ( index !== - 1 ) { | |
listeners[ type ].splice( index, 1 ); | |
} | |
return this; | |
}, | |
/** | |
* Emit an event. | |
* @method emit | |
* @param {Object} event | |
* @param {String} event.type | |
* @return {EventEmitter} The self object, for chainability. | |
*/ | |
emit: function ( event ) { | |
if ( this._listeners === undefined ){ | |
return this; | |
} | |
var listeners = this._listeners; | |
var listenerArray = listeners[ event.type ]; | |
if ( listenerArray !== undefined ) { | |
event.target = this; | |
for ( var i = 0, l = listenerArray.length; i < l; i ++ ) { | |
var listener = listenerArray[ i ]; | |
listener.call( listener.context, event ); | |
} | |
} | |
return this; | |
} | |
}; | |
},{}],27:[function(_dereq_,module,exports){ | |
var Material = _dereq_('./Material'); | |
var Equation = _dereq_('../equations/Equation'); | |
module.exports = ContactMaterial; | |
/** | |
* Defines what happens when two materials meet, such as what friction coefficient to use. You can also set other things such as restitution, surface velocity and constraint parameters. | |
* @class ContactMaterial | |
* @constructor | |
* @param {Material} materialA | |
* @param {Material} materialB | |
* @param {Object} [options] | |
* @param {Number} [options.friction=0.3] Friction coefficient. | |
* @param {Number} [options.restitution=0] Restitution coefficient aka "bounciness". | |
* @param {Number} [options.stiffness] ContactEquation stiffness. | |
* @param {Number} [options.relaxation] ContactEquation relaxation. | |
* @param {Number} [options.frictionStiffness] FrictionEquation stiffness. | |
* @param {Number} [options.frictionRelaxation] FrictionEquation relaxation. | |
* @param {Number} [options.surfaceVelocity=0] Surface velocity. | |
* @author schteppe | |
*/ | |
function ContactMaterial(materialA, materialB, options){ | |
options = options || {}; | |
if(!(materialA instanceof Material) || !(materialB instanceof Material)){ | |
throw new Error("First two arguments must be Material instances."); | |
} | |
/** | |
* The contact material identifier | |
* @property id | |
* @type {Number} | |
*/ | |
this.id = ContactMaterial.idCounter++; | |
/** | |
* First material participating in the contact material | |
* @property materialA | |
* @type {Material} | |
*/ | |
this.materialA = materialA; | |
/** | |
* Second material participating in the contact material | |
* @property materialB | |
* @type {Material} | |
*/ | |
this.materialB = materialB; | |
/** | |
* Friction to use in the contact of these two materials | |
* @property friction | |
* @type {Number} | |
*/ | |
this.friction = typeof(options.friction) !== "undefined" ? Number(options.friction) : 0.3; | |
/** | |
* Restitution to use in the contact of these two materials | |
* @property restitution | |
* @type {Number} | |
*/ | |
this.restitution = typeof(options.restitution) !== "undefined" ? Number(options.restitution) : 0.0; | |
/** | |
* Stiffness of the resulting ContactEquation that this ContactMaterial generate | |
* @property stiffness | |
* @type {Number} | |
*/ | |
this.stiffness = typeof(options.stiffness) !== "undefined" ? Number(options.stiffness) : Equation.DEFAULT_STIFFNESS; | |
/** | |
* Relaxation of the resulting ContactEquation that this ContactMaterial generate | |
* @property relaxation | |
* @type {Number} | |
*/ | |
this.relaxation = typeof(options.relaxation) !== "undefined" ? Number(options.relaxation) : Equation.DEFAULT_RELAXATION; | |
/** | |
* Stiffness of the resulting FrictionEquation that this ContactMaterial generate | |
* @property frictionStiffness | |
* @type {Number} | |
*/ | |
this.frictionStiffness = typeof(options.frictionStiffness) !== "undefined" ? Number(options.frictionStiffness) : Equation.DEFAULT_STIFFNESS; | |
/** | |
* Relaxation of the resulting FrictionEquation that this ContactMaterial generate | |
* @property frictionRelaxation | |
* @type {Number} | |
*/ | |
this.frictionRelaxation = typeof(options.frictionRelaxation) !== "undefined" ? Number(options.frictionRelaxation) : Equation.DEFAULT_RELAXATION; | |
/** | |
* Will add surface velocity to this material. If bodyA rests on top if bodyB, and the surface velocity is positive, bodyA will slide to the right. | |
* @property {Number} surfaceVelocity | |
*/ | |
this.surfaceVelocity = typeof(options.surfaceVelocity) !== "undefined" ? Number(options.surfaceVelocity) : 0; | |
/** | |
* Offset to be set on ContactEquations. A positive value will make the bodies penetrate more into each other. Can be useful in scenes where contacts need to be more persistent, for example when stacking. Aka "cure for nervous contacts". | |
* @property contactSkinSize | |
* @type {Number} | |
*/ | |
this.contactSkinSize = 0.005; | |
} | |
ContactMaterial.idCounter = 0; | |
},{"../equations/Equation":22,"./Material":28}],28:[function(_dereq_,module,exports){ | |
module.exports = Material; | |
/** | |
* Defines a physics material. | |
* @class Material | |
* @constructor | |
* @param {number} id Material identifier | |
* @author schteppe | |
*/ | |
function Material(id){ | |
/** | |
* The material identifier | |
* @property id | |
* @type {Number} | |
*/ | |
this.id = id || Material.idCounter++; | |
} | |
Material.idCounter = 0; | |
},{}],29:[function(_dereq_,module,exports){ | |
/* | |
PolyK library | |
url: http://polyk.ivank.net | |
Released under MIT licence. | |
Copyright (c) 2012 Ivan Kuckir | |
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. | |
*/ | |
var PolyK = {}; | |
/* | |
Is Polygon self-intersecting? | |
O(n^2) | |
*/ | |
/* | |
PolyK.IsSimple = function(p) | |
{ | |
var n = p.length>>1; | |
if(n<4) return true; | |
var a1 = new PolyK._P(), a2 = new PolyK._P(); | |
var b1 = new PolyK._P(), b2 = new PolyK._P(); | |
var c = new PolyK._P(); | |
for(var i=0; i<n; i++) | |
{ | |
a1.x = p[2*i ]; | |
a1.y = p[2*i+1]; | |
if(i==n-1) { a2.x = p[0 ]; a2.y = p[1 ]; } | |
else { a2.x = p[2*i+2]; a2.y = p[2*i+3]; } | |
for(var j=0; j<n; j++) | |
{ | |
if(Math.abs(i-j) < 2) continue; | |
if(j==n-1 && i==0) continue; | |
if(i==n-1 && j==0) continue; | |
b1.x = p[2*j ]; | |
b1.y = p[2*j+1]; | |
if(j==n-1) { b2.x = p[0 ]; b2.y = p[1 ]; } | |
else { b2.x = p[2*j+2]; b2.y = p[2*j+3]; } | |
if(PolyK._GetLineIntersection(a1,a2,b1,b2,c) != null) return false; | |
} | |
} | |
return true; | |
} | |
PolyK.IsConvex = function(p) | |
{ | |
if(p.length<6) return true; | |
var l = p.length - 4; | |
for(var i=0; i<l; i+=2) | |
if(!PolyK._convex(p[i], p[i+1], p[i+2], p[i+3], p[i+4], p[i+5])) return false; | |
if(!PolyK._convex(p[l ], p[l+1], p[l+2], p[l+3], p[0], p[1])) return false; | |
if(!PolyK._convex(p[l+2], p[l+3], p[0 ], p[1 ], p[2], p[3])) return false; | |
return true; | |
} | |
*/ | |
PolyK.GetArea = function(p) | |
{ | |
if(p.length <6) return 0; | |
var l = p.length - 2; | |
var sum = 0; | |
for(var i=0; i<l; i+=2) | |
sum += (p[i+2]-p[i]) * (p[i+1]+p[i+3]); | |
sum += (p[0]-p[l]) * (p[l+1]+p[1]); | |
return - sum * 0.5; | |
} | |
/* | |
PolyK.GetAABB = function(p) | |
{ | |
var minx = Infinity; | |
var miny = Infinity; | |
var maxx = -minx; | |
var maxy = -miny; | |
for(var i=0; i<p.length; i+=2) | |
{ | |
minx = Math.min(minx, p[i ]); | |
maxx = Math.max(maxx, p[i ]); | |
miny = Math.min(miny, p[i+1]); | |
maxy = Math.max(maxy, p[i+1]); | |
} | |
return {x:minx, y:miny, width:maxx-minx, height:maxy-miny}; | |
} | |
*/ | |
PolyK.Triangulate = function(p) | |
{ | |
var n = p.length>>1; | |
if(n<3) return []; | |
var tgs = []; | |
var avl = []; | |
for(var i=0; i<n; i++) avl.push(i); | |
var i = 0; | |
var al = n; | |
while(al > 3) | |
{ | |
var i0 = avl[(i+0)%al]; | |
var i1 = avl[(i+1)%al]; | |
var i2 = avl[(i+2)%al]; | |
var ax = p[2*i0], ay = p[2*i0+1]; | |
var bx = p[2*i1], by = p[2*i1+1]; | |
var cx = p[2*i2], cy = p[2*i2+1]; | |
var earFound = false; | |
if(PolyK._convex(ax, ay, bx, by, cx, cy)) | |
{ | |
earFound = true; | |
for(var j=0; j<al; j++) | |
{ | |
var vi = avl[j]; | |
if(vi==i0 || vi==i1 || vi==i2) continue; | |
if(PolyK._PointInTriangle(p[2*vi], p[2*vi+1], ax, ay, bx, by, cx, cy)) {earFound = false; break;} | |
} | |
} | |
if(earFound) | |
{ | |
tgs.push(i0, i1, i2); | |
avl.splice((i+1)%al, 1); | |
al--; | |
i= 0; | |
} | |
else if(i++ > 3*al) break; // no convex angles :( | |
} | |
tgs.push(avl[0], avl[1], avl[2]); | |
return tgs; | |
} | |
/* | |
PolyK.ContainsPoint = function(p, px, py) | |
{ | |
var n = p.length>>1; | |
var ax, ay, bx = p[2*n-2]-px, by = p[2*n-1]-py; | |
var depth = 0; | |
for(var i=0; i<n; i++) | |
{ | |
ax = bx; ay = by; | |
bx = p[2*i ] - px; | |
by = p[2*i+1] - py; | |
if(ay< 0 && by< 0) continue; // both "up" or both "donw" | |
if(ay>=0 && by>=0) continue; // both "up" or both "donw" | |
if(ax< 0 && bx< 0) continue; | |
var lx = ax + (bx-ax)*(-ay)/(by-ay); | |
if(lx>0) depth++; | |
} | |
return (depth & 1) == 1; | |
} | |
PolyK.Slice = function(p, ax, ay, bx, by) | |
{ | |
if(PolyK.ContainsPoint(p, ax, ay) || PolyK.ContainsPoint(p, bx, by)) return [p.slice(0)]; | |
var a = new PolyK._P(ax, ay); | |
var b = new PolyK._P(bx, by); | |
var iscs = []; // intersections | |
var ps = []; // points | |
for(var i=0; i<p.length; i+=2) ps.push(new PolyK._P(p[i], p[i+1])); | |
for(var i=0; i<ps.length; i++) | |
{ | |
var isc = new PolyK._P(0,0); | |
isc = PolyK._GetLineIntersection(a, b, ps[i], ps[(i+1)%ps.length], isc); | |
if(isc) | |
{ | |
isc.flag = true; | |
iscs.push(isc); | |
ps.splice(i+1,0,isc); | |
i++; | |
} | |
} | |
if(iscs.length == 0) return [p.slice(0)]; | |
var comp = function(u,v) {return PolyK._P.dist(a,u) - PolyK._P.dist(a,v); } | |
iscs.sort(comp); | |
var pgs = []; | |
var dir = 0; | |
while(iscs.length > 0) | |
{ | |
var n = ps.length; | |
var i0 = iscs[0]; | |
var i1 = iscs[1]; | |
var ind0 = ps.indexOf(i0); | |
var ind1 = ps.indexOf(i1); | |
var solved = false; | |
if(PolyK._firstWithFlag(ps, ind0) == ind1) solved = true; | |
else | |
{ | |
i0 = iscs[1]; | |
i1 = iscs[0]; | |
ind0 = ps.indexOf(i0); | |
ind1 = ps.indexOf(i1); | |
if(PolyK._firstWithFlag(ps, ind0) == ind1) solved = true; | |
} | |
if(solved) | |
{ | |
dir--; | |
var pgn = PolyK._getPoints(ps, ind0, ind1); | |
pgs.push(pgn); | |
ps = PolyK._getPoints(ps, ind1, ind0); | |
i0.flag = i1.flag = false; | |
iscs.splice(0,2); | |
if(iscs.length == 0) pgs.push(ps); | |
} | |
else { dir++; iscs.reverse(); } | |
if(dir>1) break; | |
} | |
var result = []; | |
for(var i=0; i<pgs.length; i++) | |
{ | |
var pg = pgs[i]; | |
var npg = []; | |
for(var j=0; j<pg.length; j++) npg.push(pg[j].x, pg[j].y); | |
result.push(npg); | |
} | |
return result; | |
} | |
PolyK.Raycast = function(p, x, y, dx, dy, isc) | |
{ | |
var l = p.length - 2; | |
var tp = PolyK._tp; | |
var a1 = tp[0], a2 = tp[1], | |
b1 = tp[2], b2 = tp[3], c = tp[4]; | |
a1.x = x; a1.y = y; | |
a2.x = x+dx; a2.y = y+dy; | |
if(isc==null) isc = {dist:0, edge:0, norm:{x:0, y:0}, refl:{x:0, y:0}}; | |
isc.dist = Infinity; | |
for(var i=0; i<l; i+=2) | |
{ | |
b1.x = p[i ]; b1.y = p[i+1]; | |
b2.x = p[i+2]; b2.y = p[i+3]; | |
var nisc = PolyK._RayLineIntersection(a1, a2, b1, b2, c); | |
if(nisc) PolyK._updateISC(dx, dy, a1, b1, b2, c, i/2, isc); | |
} | |
b1.x = b2.x; b1.y = b2.y; | |
b2.x = p[0]; b2.y = p[1]; | |
var nisc = PolyK._RayLineIntersection(a1, a2, b1, b2, c); | |
if(nisc) PolyK._updateISC(dx, dy, a1, b1, b2, c, p.length/2, isc); | |
return (isc.dist != Infinity) ? isc : null; | |
} | |
PolyK.ClosestEdge = function(p, x, y, isc) | |
{ | |
var l = p.length - 2; | |
var tp = PolyK._tp; | |
var a1 = tp[0], | |
b1 = tp[2], b2 = tp[3], c = tp[4]; | |
a1.x = x; a1.y = y; | |
if(isc==null) isc = {dist:0, edge:0, point:{x:0, y:0}, norm:{x:0, y:0}}; | |
isc.dist = Infinity; | |
for(var i=0; i<l; i+=2) | |
{ | |
b1.x = p[i ]; b1.y = p[i+1]; | |
b2.x = p[i+2]; b2.y = p[i+3]; | |
PolyK._pointLineDist(a1, b1, b2, i>>1, isc); | |
} | |
b1.x = b2.x; b1.y = b2.y; | |
b2.x = p[0]; b2.y = p[1]; | |
PolyK._pointLineDist(a1, b1, b2, l>>1, isc); | |
var idst = 1/isc.dist; | |
isc.norm.x = (x-isc.point.x)*idst; | |
isc.norm.y = (y-isc.point.y)*idst; | |
return isc; | |
} | |
PolyK._pointLineDist = function(p, a, b, edge, isc) | |
{ | |
var x = p.x, y = p.y, x1 = a.x, y1 = a.y, x2 = b.x, y2 = b.y; | |
var A = x - x1; | |
var B = y - y1; | |
var C = x2 - x1; | |
var D = y2 - y1; | |
var dot = A * C + B * D; | |
var len_sq = C * C + D * D; | |
var param = dot / len_sq; | |
var xx, yy; | |
if (param < 0 || (x1 == x2 && y1 == y2)) { | |
xx = x1; | |
yy = y1; | |
} | |
else if (param > 1) { | |
xx = x2; | |
yy = y2; | |
} | |
else { | |
xx = x1 + param * C; | |
yy = y1 + param * D; | |
} | |
var dx = x - xx; | |
var dy = y - yy; | |
var dst = Math.sqrt(dx * dx + dy * dy); | |
if(dst<isc.dist) | |
{ | |
isc.dist = dst; | |
isc.edge = edge; | |
isc.point.x = xx; | |
isc.point.y = yy; | |
} | |
} | |
PolyK._updateISC = function(dx, dy, a1, b1, b2, c, edge, isc) | |
{ | |
var nrl = PolyK._P.dist(a1, c); | |
if(nrl<isc.dist) | |
{ | |
var ibl = 1/PolyK._P.dist(b1, b2); | |
var nx = -(b2.y-b1.y)*ibl; | |
var ny = (b2.x-b1.x)*ibl; | |
var ddot = 2*(dx*nx+dy*ny); | |
isc.dist = nrl; | |
isc.norm.x = nx; | |
isc.norm.y = ny; | |
isc.refl.x = -ddot*nx+dx; | |
isc.refl.y = -ddot*ny+dy; | |
isc.edge = edge; | |
} | |
} | |
PolyK._getPoints = function(ps, ind0, ind1) | |
{ | |
var n = ps.length; | |
var nps = []; | |
if(ind1<ind0) ind1 += n; | |
for(var i=ind0; i<= ind1; i++) nps.push(ps[i%n]); | |
return nps; | |
} | |
PolyK._firstWithFlag = function(ps, ind) | |
{ | |
var n = ps.length; | |
while(true) | |
{ | |
ind = (ind+1)%n; | |
if(ps[ind].flag) return ind; | |
} | |
} | |
*/ | |
PolyK._PointInTriangle = function(px, py, ax, ay, bx, by, cx, cy) | |
{ | |
var v0x = cx-ax; | |
var v0y = cy-ay; | |
var v1x = bx-ax; | |
var v1y = by-ay; | |
var v2x = px-ax; | |
var v2y = py-ay; | |
var dot00 = v0x*v0x+v0y*v0y; | |
var dot01 = v0x*v1x+v0y*v1y; | |
var dot02 = v0x*v2x+v0y*v2y; | |
var dot11 = v1x*v1x+v1y*v1y; | |
var dot12 = v1x*v2x+v1y*v2y; | |
var invDenom = 1 / (dot00 * dot11 - dot01 * dot01); | |
var u = (dot11 * dot02 - dot01 * dot12) * invDenom; | |
var v = (dot00 * dot12 - dot01 * dot02) * invDenom; | |
// Check if point is in triangle | |
return (u >= 0) && (v >= 0) && (u + v < 1); | |
} | |
/* | |
PolyK._RayLineIntersection = function(a1, a2, b1, b2, c) | |
{ | |
var dax = (a1.x-a2.x), dbx = (b1.x-b2.x); | |
var day = (a1.y-a2.y), dby = (b1.y-b2.y); | |
var Den = dax*dby - day*dbx; | |
if (Den == 0) return null; // parallel | |
var A = (a1.x * a2.y - a1.y * a2.x); | |
var B = (b1.x * b2.y - b1.y * b2.x); | |
var I = c; | |
var iDen = 1/Den; | |
I.x = ( A*dbx - dax*B ) * iDen; | |
I.y = ( A*dby - day*B ) * iDen; | |
if(!PolyK._InRect(I, b1, b2)) return null; | |
if((day>0 && I.y>a1.y) || (day<0 && I.y<a1.y)) return null; | |
if((dax>0 && I.x>a1.x) || (dax<0 && I.x<a1.x)) return null; | |
return I; | |
} | |
PolyK._GetLineIntersection = function(a1, a2, b1, b2, c) | |
{ | |
var dax = (a1.x-a2.x), dbx = (b1.x-b2.x); | |
var day = (a1.y-a2.y), dby = (b1.y-b2.y); | |
var Den = dax*dby - day*dbx; | |
if (Den == 0) return null; // parallel | |
var A = (a1.x * a2.y - a1.y * a2.x); | |
var B = (b1.x * b2.y - b1.y * b2.x); | |
var I = c; | |
I.x = ( A*dbx - dax*B ) / Den; | |
I.y = ( A*dby - day*B ) / Den; | |
if(PolyK._InRect(I, a1, a2) && PolyK._InRect(I, b1, b2)) return I; | |
return null; | |
} | |
PolyK._InRect = function(a, b, c) | |
{ | |
if (b.x == c.x) return (a.y>=Math.min(b.y, c.y) && a.y<=Math.max(b.y, c.y)); | |
if (b.y == c.y) return (a.x>=Math.min(b.x, c.x) && a.x<=Math.max(b.x, c.x)); | |
if(a.x >= Math.min(b.x, c.x) && a.x <= Math.max(b.x, c.x) | |
&& a.y >= Math.min(b.y, c.y) && a.y <= Math.max(b.y, c.y)) | |
return true; | |
return false; | |
} | |
*/ | |
PolyK._convex = function(ax, ay, bx, by, cx, cy) | |
{ | |
return (ay-by)*(cx-bx) + (bx-ax)*(cy-by) >= 0; | |
} | |
/* | |
PolyK._P = function(x,y) | |
{ | |
this.x = x; | |
this.y = y; | |
this.flag = false; | |
} | |
PolyK._P.prototype.toString = function() | |
{ | |
return "Point ["+this.x+", "+this.y+"]"; | |
} | |
PolyK._P.dist = function(a,b) | |
{ | |
var dx = b.x-a.x; | |
var dy = b.y-a.y; | |
return Math.sqrt(dx*dx + dy*dy); | |
} | |
PolyK._tp = []; | |
for(var i=0; i<10; i++) PolyK._tp.push(new PolyK._P(0,0)); | |
*/ | |
module.exports = PolyK; | |
},{}],30:[function(_dereq_,module,exports){ | |
/* Copyright (c) 2013, Brandon Jones, Colin MacKenzie IV. All rights reserved. | |
Redistribution and use in source and binary forms, with or without modification, | |
are permitted provided that the following conditions are met: | |
* Redistributions of source code must retain the above copyright notice, this | |
list of conditions and the following disclaimer. | |
* Redistributions in binary form must reproduce the above copyright notice, | |
this list of conditions and the following disclaimer in the documentation | |
and/or other materials provided with the distribution. | |
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | |
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR | |
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | |
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | |
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |
/** | |
* The vec2 object from glMatrix, with some extensions and some removed methods. See http://glmatrix.net. | |
* @class vec2 | |
*/ | |
var vec2 = module.exports = {}; | |
var Utils = _dereq_('../utils/Utils'); | |
/** | |
* Make a cross product and only return the z component | |
* @method crossLength | |
* @static | |
* @param {Array} a | |
* @param {Array} b | |
* @return {Number} | |
*/ | |
vec2.crossLength = function(a,b){ | |
return a[0] * b[1] - a[1] * b[0]; | |
}; | |
/** | |
* Cross product between a vector and the Z component of a vector | |
* @method crossVZ | |
* @static | |
* @param {Array} out | |
* @param {Array} vec | |
* @param {Number} zcomp | |
* @return {Number} | |
*/ | |
vec2.crossVZ = function(out, vec, zcomp){ | |
vec2.rotate(out,vec,-Math.PI/2);// Rotate according to the right hand rule | |
vec2.scale(out,out,zcomp); // Scale with z | |
return out; | |
}; | |
/** | |
* Cross product between a vector and the Z component of a vector | |
* @method crossZV | |
* @static | |
* @param {Array} out | |
* @param {Number} zcomp | |
* @param {Array} vec | |
* @return {Number} | |
*/ | |
vec2.crossZV = function(out, zcomp, vec){ | |
vec2.rotate(out,vec,Math.PI/2); // Rotate according to the right hand rule | |
vec2.scale(out,out,zcomp); // Scale with z | |
return out; | |
}; | |
/** | |
* Rotate a vector by an angle | |
* @method rotate | |
* @static | |
* @param {Array} out | |
* @param {Array} a | |
* @param {Number} angle | |
*/ | |
vec2.rotate = function(out,a,angle){ | |
if(angle !== 0){ | |
var c = Math.cos(angle), | |
s = Math.sin(angle), | |
x = a[0], | |
y = a[1]; | |
out[0] = c*x -s*y; | |
out[1] = s*x +c*y; | |
} else { | |
out[0] = a[0]; | |
out[1] = a[1]; | |
} | |
}; | |
/** | |
* Rotate a vector 90 degrees clockwise | |
* @method rotate90cw | |
* @static | |
* @param {Array} out | |
* @param {Array} a | |
* @param {Number} angle | |
*/ | |
vec2.rotate90cw = function(out, a) { | |
var x = a[0]; | |
var y = a[1]; | |
out[0] = y; | |
out[1] = -x; | |
}; | |
/** | |
* Transform a point position to local frame. | |
* @method toLocalFrame | |
* @param {Array} out | |
* @param {Array} worldPoint | |
* @param {Array} framePosition | |
* @param {Number} frameAngle | |
*/ | |
vec2.toLocalFrame = function(out, worldPoint, framePosition, frameAngle){ | |
vec2.copy(out, worldPoint); | |
vec2.sub(out, out, framePosition); | |
vec2.rotate(out, out, -frameAngle); | |
}; | |
/** | |
* Transform a point position to global frame. | |
* @method toGlobalFrame | |
* @param {Array} out | |
* @param {Array} localPoint | |
* @param {Array} framePosition | |
* @param {Number} frameAngle | |
*/ | |
vec2.toGlobalFrame = function(out, localPoint, framePosition, frameAngle){ | |
vec2.copy(out, localPoint); | |
vec2.rotate(out, out, frameAngle); | |
vec2.add(out, out, framePosition); | |
}; | |
/** | |
* Transform a vector to local frame. | |
* @method vectorToLocalFrame | |
* @param {Array} out | |
* @param {Array} worldVector | |
* @param {Number} frameAngle | |
*/ | |
vec2.vectorToLocalFrame = function(out, worldVector, frameAngle){ | |
vec2.rotate(out, worldVector, -frameAngle); | |
}; | |
/** | |
* Transform a point position to global frame. | |
* @method toGlobalFrame | |
* @param {Array} out | |
* @param {Array} localVector | |
* @param {Number} frameAngle | |
*/ | |
vec2.vectorToGlobalFrame = function(out, localVector, frameAngle){ | |
vec2.rotate(out, localVector, frameAngle); | |
}; | |
/** | |
* Compute centroid of a triangle spanned by vectors a,b,c. See http://easycalculation.com/analytical/learn-centroid.php | |
* @method centroid | |
* @static | |
* @param {Array} out | |
* @param {Array} a | |
* @param {Array} b | |
* @param {Array} c | |
* @return {Array} The out object | |
*/ | |
vec2.centroid = function(out, a, b, c){ | |
vec2.add(out, a, b); | |
vec2.add(out, out, c); | |
vec2.scale(out, out, 1/3); | |
return out; | |
}; | |
/** | |
* Creates a new, empty vec2 | |
* @static | |
* @method create | |
* @return {Array} a new 2D vector | |
*/ | |
vec2.create = function() { | |
var out = new Utils.ARRAY_TYPE(2); | |
out[0] = 0; | |
out[1] = 0; | |
return out; | |
}; | |
/** | |
* Creates a new vec2 initialized with values from an existing vector | |
* @static | |
* @method clone | |
* @param {Array} a vector to clone | |
* @return {Array} a new 2D vector | |
*/ | |
vec2.clone = function(a) { | |
var out = new Utils.ARRAY_TYPE(2); | |
out[0] = a[0]; | |
out[1] = a[1]; | |
return out; | |
}; | |
/** | |
* Creates a new vec2 initialized with the given values | |
* @static | |
* @method fromValues | |
* @param {Number} x X component | |
* @param {Number} y Y component | |
* @return {Array} a new 2D vector | |
*/ | |
vec2.fromValues = function(x, y) { | |
var out = new Utils.ARRAY_TYPE(2); | |
out[0] = x; | |
out[1] = y; | |
return out; | |
}; | |
/** | |
* Copy the values from one vec2 to another | |
* @static | |
* @method copy | |
* @param {Array} out the receiving vector | |
* @param {Array} a the source vector | |
* @return {Array} out | |
*/ | |
vec2.copy = function(out, a) { | |
out[0] = a[0]; | |
out[1] = a[1]; | |
return out; | |
}; | |
/** | |
* Set the components of a vec2 to the given values | |
* @static | |
* @method set | |
* @param {Array} out the receiving vector | |
* @param {Number} x X component | |
* @param {Number} y Y component | |
* @return {Array} out | |
*/ | |
vec2.set = function(out, x, y) { | |
out[0] = x; | |
out[1] = y; | |
return out; | |
}; | |
/** | |
* Adds two vec2's | |
* @static | |
* @method add | |
* @param {Array} out the receiving vector | |
* @param {Array} a the first operand | |
* @param {Array} b the second operand | |
* @return {Array} out | |
*/ | |
vec2.add = function(out, a, b) { | |
out[0] = a[0] + b[0]; | |
out[1] = a[1] + b[1]; | |
return out; | |
}; | |
/** | |
* Subtracts two vec2's | |
* @static | |
* @method subtract | |
* @param {Array} out the receiving vector | |
* @param {Array} a the first operand | |
* @param {Array} b the second operand | |
* @return {Array} out | |
*/ | |
vec2.subtract = function(out, a, b) { | |
out[0] = a[0] - b[0]; | |
out[1] = a[1] - b[1]; | |
return out; | |
}; | |
/** | |
* Alias for vec2.subtract | |
* @static | |
* @method sub | |
*/ | |
vec2.sub = vec2.subtract; | |
/** | |
* Multiplies two vec2's | |
* @static | |
* @method multiply | |
* @param {Array} out the receiving vector | |
* @param {Array} a the first operand | |
* @param {Array} b the second operand | |
* @return {Array} out | |
*/ | |
vec2.multiply = function(out, a, b) { | |
out[0] = a[0] * b[0]; | |
out[1] = a[1] * b[1]; | |
return out; | |
}; | |
/** | |
* Alias for vec2.multiply | |
* @static | |
* @method mul | |
*/ | |
vec2.mul = vec2.multiply; | |
/** | |
* Divides two vec2's | |
* @static | |
* @method divide | |
* @param {Array} out the receiving vector | |
* @param {Array} a the first operand | |
* @param {Array} b the second operand | |
* @return {Array} out | |
*/ | |
vec2.divide = function(out, a, b) { | |
out[0] = a[0] / b[0]; | |
out[1] = a[1] / b[1]; | |
return out; | |
}; | |
/** | |
* Alias for vec2.divide | |
* @static | |
* @method div | |
*/ | |
vec2.div = vec2.divide; | |
/** | |
* Scales a vec2 by a scalar number | |
* @static | |
* @method scale | |
* @param {Array} out the receiving vector | |
* @param {Array} a the vector to scale | |
* @param {Number} b amount to scale the vector by | |
* @return {Array} out | |
*/ | |
vec2.scale = function(out, a, b) { | |
out[0] = a[0] * b; | |
out[1] = a[1] * b; | |
return out; | |
}; | |
/** | |
* Calculates the euclidian distance between two vec2's | |
* @static | |
* @method distance | |
* @param {Array} a the first operand | |
* @param {Array} b the second operand | |
* @return {Number} distance between a and b | |
*/ | |
vec2.distance = function(a, b) { | |
var x = b[0] - a[0], | |
y = b[1] - a[1]; | |
return Math.sqrt(x*x + y*y); | |
}; | |
/** | |
* Alias for vec2.distance | |
* @static | |
* @method dist | |
*/ | |
vec2.dist = vec2.distance; | |
/** | |
* Calculates the squared euclidian distance between two vec2's | |
* @static | |
* @method squaredDistance | |
* @param {Array} a the first operand | |
* @param {Array} b the second operand | |
* @return {Number} squared distance between a and b | |
*/ | |
vec2.squaredDistance = function(a, b) { | |
var x = b[0] - a[0], | |
y = b[1] - a[1]; | |
return x*x + y*y; | |
}; | |
/** | |
* Alias for vec2.squaredDistance | |
* @static | |
* @method sqrDist | |
*/ | |
vec2.sqrDist = vec2.squaredDistance; | |
/** | |
* Calculates the length of a vec2 | |
* @static | |
* @method length | |
* @param {Array} a vector to calculate length of | |
* @return {Number} length of a | |
*/ | |
vec2.length = function (a) { | |
var x = a[0], | |
y = a[1]; | |
return Math.sqrt(x*x + y*y); | |
}; | |
/** | |
* Alias for vec2.length | |
* @method len | |
* @static | |
*/ | |
vec2.len = vec2.length; | |
/** | |
* Calculates the squared length of a vec2 | |
* @static | |
* @method squaredLength | |
* @param {Array} a vector to calculate squared length of | |
* @return {Number} squared length of a | |
*/ | |
vec2.squaredLength = function (a) { | |
var x = a[0], | |
y = a[1]; | |
return x*x + y*y; | |
}; | |
/** | |
* Alias for vec2.squaredLength | |
* @static | |
* @method sqrLen | |
*/ | |
vec2.sqrLen = vec2.squaredLength; | |
/** | |
* Negates the components of a vec2 | |
* @static | |
* @method negate | |
* @param {Array} out the receiving vector | |
* @param {Array} a vector to negate | |
* @return {Array} out | |
*/ | |
vec2.negate = function(out, a) { | |
out[0] = -a[0]; | |
out[1] = -a[1]; | |
return out; | |
}; | |
/** | |
* Normalize a vec2 | |
* @static | |
* @method normalize | |
* @param {Array} out the receiving vector | |
* @param {Array} a vector to normalize | |
* @return {Array} out | |
*/ | |
vec2.normalize = function(out, a) { | |
var x = a[0], | |
y = a[1]; | |
var len = x*x + y*y; | |
if (len > 0) { | |
//TODO: evaluate use of glm_invsqrt here? | |
len = 1 / Math.sqrt(len); | |
out[0] = a[0] * len; | |
out[1] = a[1] * len; | |
} | |
return out; | |
}; | |
/** | |
* Calculates the dot product of two vec2's | |
* @static | |
* @method dot | |
* @param {Array} a the first operand | |
* @param {Array} b the second operand | |
* @return {Number} dot product of a and b | |
*/ | |
vec2.dot = function (a, b) { | |
return a[0] * b[0] + a[1] * b[1]; | |
}; | |
/** | |
* Returns a string representation of a vector | |
* @static | |
* @method str | |
* @param {Array} vec vector to represent as a string | |
* @return {String} string representation of the vector | |
*/ | |
vec2.str = function (a) { | |
return 'vec2(' + a[0] + ', ' + a[1] + ')'; | |
}; | |
/** | |
* Linearly interpolate/mix two vectors. | |
* @static | |
* @method lerp | |
* @param {Array} out | |
* @param {Array} a First vector | |
* @param {Array} b Second vector | |
* @param {number} t Lerp factor | |
*/ | |
vec2.lerp = function (out, a, b, t) { | |
var ax = a[0], | |
ay = a[1]; | |
out[0] = ax + t * (b[0] - ax); | |
out[1] = ay + t * (b[1] - ay); | |
return out; | |
}; | |
/** | |
* Reflect a vector along a normal. | |
* @static | |
* @method reflect | |
* @param {Array} out | |
* @param {Array} vector | |
* @param {Array} normal | |
*/ | |
vec2.reflect = function(out, vector, normal){ | |
var dot = vector[0] * normal[0] + vector[1] * normal[1]; | |
out[0] = vector[0] - 2 * normal[0] * dot; | |
out[1] = vector[1] - 2 * normal[1] * dot; | |
}; | |
/** | |
* Get the intersection point between two line segments. | |
* @static | |
* @method getLineSegmentsIntersection | |
* @param {Array} out | |
* @param {Array} p0 | |
* @param {Array} p1 | |
* @param {Array} p2 | |
* @param {Array} p3 | |
* @return {boolean} True if there was an intersection, otherwise false. | |
*/ | |
vec2.getLineSegmentsIntersection = function(out, p0, p1, p2, p3) { | |
var t = vec2.getLineSegmentsIntersectionFraction(p0, p1, p2, p3); | |
if(t < 0){ | |
return false; | |
} else { | |
out[0] = p0[0] + (t * (p1[0] - p0[0])); | |
out[1] = p0[1] + (t * (p1[1] - p0[1])); | |
return true; | |
} | |
}; | |
/** | |
* Get the intersection fraction between two line segments. If successful, the intersection is at p0 + t * (p1 - p0) | |
* @static | |
* @method getLineSegmentsIntersectionFraction | |
* @param {Array} p0 | |
* @param {Array} p1 | |
* @param {Array} p2 | |
* @param {Array} p3 | |
* @return {number} A number between 0 and 1 if there was an intersection, otherwise -1. | |
*/ | |
vec2.getLineSegmentsIntersectionFraction = function(p0, p1, p2, p3) { | |
var s1_x = p1[0] - p0[0]; | |
var s1_y = p1[1] - p0[1]; | |
var s2_x = p3[0] - p2[0]; | |
var s2_y = p3[1] - p2[1]; | |
var s, t; | |
s = (-s1_y * (p0[0] - p2[0]) + s1_x * (p0[1] - p2[1])) / (-s2_x * s1_y + s1_x * s2_y); | |
t = ( s2_x * (p0[1] - p2[1]) - s2_y * (p0[0] - p2[0])) / (-s2_x * s1_y + s1_x * s2_y); | |
if (s >= 0 && s <= 1 && t >= 0 && t <= 1) { // Collision detected | |
return t; | |
} | |
return -1; // No collision | |
}; | |
},{"../utils/Utils":57}],31:[function(_dereq_,module,exports){ | |
var vec2 = _dereq_('../math/vec2') | |
, decomp = _dereq_('poly-decomp') | |
, Convex = _dereq_('../shapes/Convex') | |
, RaycastResult = _dereq_('../collision/RaycastResult') | |
, Ray = _dereq_('../collision/Ray') | |
, AABB = _dereq_('../collision/AABB') | |
, EventEmitter = _dereq_('../events/EventEmitter'); | |
module.exports = Body; | |
/** | |
* A rigid body. Has got a center of mass, position, velocity and a number of | |
* shapes that are used for collisions. | |
* | |
* @class Body | |
* @constructor | |
* @extends EventEmitter | |
* @param {Array} [options.force] | |
* @param {Array} [options.position] | |
* @param {Array} [options.velocity] | |
* @param {Boolean} [options.allowSleep] | |
* @param {Boolean} [options.collisionResponse] | |
* @param {Number} [options.angle=0] | |
* @param {Number} [options.angularForce=0] | |
* @param {Number} [options.angularVelocity=0] | |
* @param {Number} [options.ccdIterations=10] | |
* @param {Number} [options.ccdSpeedThreshold=-1] | |
* @param {Number} [options.fixedRotation=false] | |
* @param {Number} [options.gravityScale] | |
* @param {Number} [options.id] | |
* @param {Number} [options.mass=0] A number >= 0. If zero, the .type will be set to Body.STATIC. | |
* @param {Number} [options.sleepSpeedLimit] | |
* @param {Number} [options.sleepTimeLimit] | |
* @param {Object} [options] | |
* | |
* @example | |
* | |
* // Create a typical dynamic body | |
* var body = new Body({ | |
* mass: 1, | |
* position: [0, 0], | |
* angle: 0, | |
* velocity: [0, 0], | |
* angularVelocity: 0 | |
* }); | |
* | |
* // Add a circular shape to the body | |
* body.addShape(new Circle({ radius: 1 })); | |
* | |
* // Add the body to the world | |
* world.addBody(body); | |
*/ | |
function Body(options){ | |
options = options || {}; | |
EventEmitter.call(this); | |
/** | |
* The body identifyer | |
* @property id | |
* @type {Number} | |
*/ | |
this.id = options.id || ++Body._idCounter; | |
/** | |
* The world that this body is added to. This property is set to NULL if the body is not added to any world. | |
* @property world | |
* @type {World} | |
*/ | |
this.world = null; | |
/** | |
* The shapes of the body. | |
* | |
* @property shapes | |
* @type {Array} | |
*/ | |
this.shapes = []; | |
/** | |
* The mass of the body. | |
* @property mass | |
* @type {number} | |
*/ | |
this.mass = options.mass || 0; | |
/** | |
* The inverse mass of the body. | |
* @property invMass | |
* @type {number} | |
*/ | |
this.invMass = 0; | |
/** | |
* The inertia of the body around the Z axis. | |
* @property inertia | |
* @type {number} | |
*/ | |
this.inertia = 0; | |
/** | |
* The inverse inertia of the body. | |
* @property invInertia | |
* @type {number} | |
*/ | |
this.invInertia = 0; | |
this.invMassSolve = 0; | |
this.invInertiaSolve = 0; | |
/** | |
* Set to true if you want to fix the rotation of the body. | |
* @property fixedRotation | |
* @type {Boolean} | |
*/ | |
this.fixedRotation = !!options.fixedRotation; | |
/** | |
* Set to true if you want to fix the body movement along the X axis. The body will still be able to move along Y. | |
* @property {Boolean} fixedX | |
*/ | |
this.fixedX = !!options.fixedX; | |
/** | |
* Set to true if you want to fix the body movement along the Y axis. The body will still be able to move along X. | |
* @property {Boolean} fixedY | |
*/ | |
this.fixedY = !!options.fixedY; | |
/** | |
* @private | |
* @property {array} massMultiplier | |
*/ | |
this.massMultiplier = vec2.create(); | |
/** | |
* The position of the body | |
* @property position | |
* @type {Array} | |
*/ | |
this.position = vec2.fromValues(0,0); | |
if(options.position){ | |
vec2.copy(this.position, options.position); | |
} | |
/** | |
* The interpolated position of the body. Use this for rendering. | |
* @property interpolatedPosition | |
* @type {Array} | |
*/ | |
this.interpolatedPosition = vec2.fromValues(0,0); | |
/** | |
* The interpolated angle of the body. Use this for rendering. | |
* @property interpolatedAngle | |
* @type {Number} | |
*/ | |
this.interpolatedAngle = 0; | |
/** | |
* The previous position of the body. | |
* @property previousPosition | |
* @type {Array} | |
*/ | |
this.previousPosition = vec2.fromValues(0,0); | |
/** | |
* The previous angle of the body. | |
* @property previousAngle | |
* @type {Number} | |
*/ | |
this.previousAngle = 0; | |
/** | |
* The current velocity of the body. | |
* @property velocity | |
* @type {Array} | |
*/ | |
this.velocity = vec2.fromValues(0,0); | |
if(options.velocity){ | |
vec2.copy(this.velocity, options.velocity); | |
} | |
/** | |
* Constraint velocity that was added to the body during the last step. | |
* @property vlambda | |
* @type {Array} | |
*/ | |
this.vlambda = vec2.fromValues(0,0); | |
/** | |
* Angular constraint velocity that was added to the body during last step. | |
* @property wlambda | |
* @type {Array} | |
*/ | |
this.wlambda = 0; | |
/** | |
* The angle of the body, in radians. | |
* @property angle | |
* @type {number} | |
* @example | |
* // The angle property is not normalized to the interval 0 to 2*pi, it can be any value. | |
* // If you need a value between 0 and 2*pi, use the following function to normalize it. | |
* function normalizeAngle(angle){ | |
* angle = angle % (2*Math.PI); | |
* if(angle < 0){ | |
* angle += (2*Math.PI); | |
* } | |
* return angle; | |
* } | |
*/ | |
this.angle = options.angle || 0; | |
/** | |
* The angular velocity of the body, in radians per second. | |
* @property angularVelocity | |
* @type {number} | |
*/ | |
this.angularVelocity = options.angularVelocity || 0; | |
/** | |
* The force acting on the body. Since the body force (and {{#crossLink "Body/angularForce:property"}}{{/crossLink}}) will be zeroed after each step, so you need to set the force before each step. | |
* @property force | |
* @type {Array} | |
* | |
* @example | |
* // This produces a forcefield of 1 Newton in the positive x direction. | |
* for(var i=0; i<numSteps; i++){ | |
* body.force[0] = 1; | |
* world.step(1/60); | |
* } | |
* | |
* @example | |
* // This will apply a rotational force on the body | |
* for(var i=0; i<numSteps; i++){ | |
* body.angularForce = -3; | |
* world.step(1/60); | |
* } | |
*/ | |
this.force = vec2.create(); | |
if(options.force){ | |
vec2.copy(this.force, options.force); | |
} | |
/** | |
* The angular force acting on the body. See {{#crossLink "Body/force:property"}}{{/crossLink}}. | |
* @property angularForce | |
* @type {number} | |
*/ | |
this.angularForce = options.angularForce || 0; | |
/** | |
* The linear damping acting on the body in the velocity direction. Should be a value between 0 and 1. | |
* @property damping | |
* @type {Number} | |
* @default 0.1 | |
*/ | |
this.damping = typeof(options.damping) === "number" ? options.damping : 0.1; | |
/** | |
* The angular force acting on the body. Should be a value between 0 and 1. | |
* @property angularDamping | |
* @type {Number} | |
* @default 0.1 | |
*/ | |
this.angularDamping = typeof(options.angularDamping) === "number" ? options.angularDamping : 0.1; | |
/** | |
* The type of motion this body has. Should be one of: {{#crossLink "Body/STATIC:property"}}Body.STATIC{{/crossLink}}, {{#crossLink "Body/DYNAMIC:property"}}Body.DYNAMIC{{/crossLink}} and {{#crossLink "Body/KINEMATIC:property"}}Body.KINEMATIC{{/crossLink}}. | |
* | |
* * Static bodies do not move, and they do not respond to forces or collision. | |
* * Dynamic bodies body can move and respond to collisions and forces. | |
* * Kinematic bodies only moves according to its .velocity, and does not respond to collisions or force. | |
* | |
* @property type | |
* @type {number} | |
* | |
* @example | |
* // Bodies are static by default. Static bodies will never move. | |
* var body = new Body(); | |
* console.log(body.type == Body.STATIC); // true | |
* | |
* @example | |
* // By setting the mass of a body to a nonzero number, the body | |
* // will become dynamic and will move and interact with other bodies. | |
* var dynamicBody = new Body({ | |
* mass : 1 | |
* }); | |
* console.log(dynamicBody.type == Body.DYNAMIC); // true | |
* | |
* @example | |
* // Kinematic bodies will only move if you change their velocity. | |
* var kinematicBody = new Body({ | |
* type: Body.KINEMATIC // Type can be set via the options object. | |
* }); | |
*/ | |
this.type = Body.STATIC; | |
if(typeof(options.type) !== 'undefined'){ | |
this.type = options.type; | |
} else if(!options.mass){ | |
this.type = Body.STATIC; | |
} else { | |
this.type = Body.DYNAMIC; | |
} | |
/** | |
* Bounding circle radius. | |
* @property boundingRadius | |
* @type {Number} | |
*/ | |
this.boundingRadius = 0; | |
/** | |
* Bounding box of this body. | |
* @property aabb | |
* @type {AABB} | |
*/ | |
this.aabb = new AABB(); | |
/** | |
* Indicates if the AABB needs update. Update it with {{#crossLink "Body/updateAABB:method"}}.updateAABB(){{/crossLink}}. | |
* @property aabbNeedsUpdate | |
* @type {Boolean} | |
* @see updateAABB | |
* | |
* @example | |
* // Force update the AABB | |
* body.aabbNeedsUpdate = true; | |
* body.updateAABB(); | |
* console.log(body.aabbNeedsUpdate); // false | |
*/ | |
this.aabbNeedsUpdate = true; | |
/** | |
* If true, the body will automatically fall to sleep. Note that you need to enable sleeping in the {{#crossLink "World"}}{{/crossLink}} before anything will happen. | |
* @property allowSleep | |
* @type {Boolean} | |
* @default true | |
*/ | |
this.allowSleep = options.allowSleep !== undefined ? options.allowSleep : true; | |
this.wantsToSleep = false; | |
/** | |
* One of {{#crossLink "Body/AWAKE:property"}}Body.AWAKE{{/crossLink}}, {{#crossLink "Body/SLEEPY:property"}}Body.SLEEPY{{/crossLink}} and {{#crossLink "Body/SLEEPING:property"}}Body.SLEEPING{{/crossLink}}. | |
* | |
* The body is initially Body.AWAKE. If its velocity norm is below .sleepSpeedLimit, the sleepState will become Body.SLEEPY. If the body continues to be Body.SLEEPY for .sleepTimeLimit seconds, it will fall asleep (Body.SLEEPY). | |
* | |
* @property sleepState | |
* @type {Number} | |
* @default Body.AWAKE | |
*/ | |
this.sleepState = Body.AWAKE; | |
/** | |
* If the speed (the norm of the velocity) is smaller than this value, the body is considered sleepy. | |
* @property sleepSpeedLimit | |
* @type {Number} | |
* @default 0.2 | |
*/ | |
this.sleepSpeedLimit = options.sleepSpeedLimit !== undefined ? options.sleepSpeedLimit : 0.2; | |
/** | |
* If the body has been sleepy for this sleepTimeLimit seconds, it is considered sleeping. | |
* @property sleepTimeLimit | |
* @type {Number} | |
* @default 1 | |
*/ | |
this.sleepTimeLimit = options.sleepTimeLimit !== undefined ? options.sleepTimeLimit : 1; | |
/** | |
* Gravity scaling factor. If you want the body to ignore gravity, set this to zero. If you want to reverse gravity, set it to -1. | |
* @property {Number} gravityScale | |
* @default 1 | |
*/ | |
this.gravityScale = options.gravityScale !== undefined ? options.gravityScale : 1; | |
/** | |
* Whether to produce contact forces when in contact with other bodies. Note that contacts will be generated, but they will be disabled. That means that this body will move through other bodies, but it will still trigger contact events, etc. | |
* @property {Boolean} collisionResponse | |
*/ | |
this.collisionResponse = options.collisionResponse !== undefined ? options.collisionResponse : true; | |
/** | |
* How long the body has been sleeping. | |
* @property {Number} idleTime | |
*/ | |
this.idleTime = 0; | |
/** | |
* The last time when the body went to SLEEPY state. | |
* @property {Number} timeLastSleepy | |
* @private | |
*/ | |
this.timeLastSleepy = 0; | |
/** | |
* If the body speed exceeds this threshold, CCD (continuous collision detection) will be enabled. Set it to a negative number to disable CCD completely for this body. | |
* @property {number} ccdSpeedThreshold | |
* @default -1 | |
*/ | |
this.ccdSpeedThreshold = options.ccdSpeedThreshold !== undefined ? options.ccdSpeedThreshold : -1; | |
/** | |
* The number of iterations that should be used when searching for the time of impact during CCD. A larger number will assure that there's a small penetration on CCD collision, but a small number will give more performance. | |
* @property {number} ccdIterations | |
* @default 10 | |
*/ | |
this.ccdIterations = options.ccdIterations !== undefined ? options.ccdIterations : 10; | |
this.concavePath = null; | |
this._wakeUpAfterNarrowphase = false; | |
this.updateMassProperties(); | |
} | |
Body.prototype = new EventEmitter(); | |
Body.prototype.constructor = Body; | |
Body._idCounter = 0; | |
/** | |
* @private | |
* @method updateSolveMassProperties | |
*/ | |
Body.prototype.updateSolveMassProperties = function(){ | |
if(this.sleepState === Body.SLEEPING || this.type === Body.KINEMATIC){ | |
this.invMassSolve = 0; | |
this.invInertiaSolve = 0; | |
} else { | |
this.invMassSolve = this.invMass; | |
this.invInertiaSolve = this.invInertia; | |
} | |
}; | |
/** | |
* Set the total density of the body | |
* @method setDensity | |
* @param {number} density | |
*/ | |
Body.prototype.setDensity = function(density) { | |
var totalArea = this.getArea(); | |
this.mass = totalArea * density; | |
this.updateMassProperties(); | |
}; | |
/** | |
* Get the total area of all shapes in the body | |
* @method getArea | |
* @return {Number} | |
*/ | |
Body.prototype.getArea = function() { | |
var totalArea = 0; | |
for(var i=0; i<this.shapes.length; i++){ | |
totalArea += this.shapes[i].area; | |
} | |
return totalArea; | |
}; | |
/** | |
* Get the AABB from the body. The AABB is updated if necessary. | |
* @method getAABB | |
* @return {AABB} The AABB instance (this.aabb) | |
*/ | |
Body.prototype.getAABB = function(){ | |
if(this.aabbNeedsUpdate){ | |
this.updateAABB(); | |
} | |
return this.aabb; | |
}; | |
var shapeAABB = new AABB(), | |
tmp = vec2.create(); | |
/** | |
* Updates the AABB of the Body, and set .aabbNeedsUpdate = false. | |
* @method updateAABB | |
*/ | |
Body.prototype.updateAABB = function() { | |
var shapes = this.shapes, | |
N = shapes.length, | |
offset = tmp, | |
bodyAngle = this.angle; | |
for(var i=0; i!==N; i++){ | |
var shape = shapes[i], | |
angle = shape.angle + bodyAngle; | |
// Get shape world offset | |
vec2.rotate(offset, shape.position, bodyAngle); | |
vec2.add(offset, offset, this.position); | |
// Get shape AABB | |
shape.computeAABB(shapeAABB, offset, angle); | |
if(i===0){ | |
this.aabb.copy(shapeAABB); | |
} else { | |
this.aabb.extend(shapeAABB); | |
} | |
} | |
this.aabbNeedsUpdate = false; | |
}; | |
/** | |
* Update the bounding radius of the body (this.boundingRadius). Should be done if any of the shape dimensions or positions are changed. | |
* @method updateBoundingRadius | |
*/ | |
Body.prototype.updateBoundingRadius = function(){ | |
var shapes = this.shapes, | |
N = shapes.length, | |
radius = 0; | |
for(var i=0; i!==N; i++){ | |
var shape = shapes[i], | |
offset = vec2.length(shape.position), | |
r = shape.boundingRadius; | |
if(offset + r > radius){ | |
radius = offset + r; | |
} | |
} | |
this.boundingRadius = radius; | |
}; | |
/** | |
* Add a shape to the body. You can pass a local transform when adding a shape, | |
* so that the shape gets an offset and angle relative to the body center of mass. | |
* Will automatically update the mass properties and bounding radius. | |
* | |
* @method addShape | |
* @param {Shape} shape | |
* @param {Array} [offset] Local body offset of the shape. | |
* @param {Number} [angle] Local body angle. | |
* | |
* @example | |
* var body = new Body(), | |
* shape = new Circle({ radius: 1 }); | |
* | |
* // Add the shape to the body, positioned in the center | |
* body.addShape(shape); | |
* | |
* // Add another shape to the body, positioned 1 unit length from the body center of mass along the local x-axis. | |
* body.addShape(shape,[1,0]); | |
* | |
* // Add another shape to the body, positioned 1 unit length from the body center of mass along the local y-axis, and rotated 90 degrees CCW. | |
* body.addShape(shape,[0,1],Math.PI/2); | |
*/ | |
Body.prototype.addShape = function(shape, offset, angle){ | |
if(shape.body){ | |
throw new Error('A shape can only be added to one body.'); | |
} | |
shape.body = this; | |
// Copy the offset vector | |
if(offset){ | |
vec2.copy(shape.position, offset); | |
} else { | |
vec2.set(shape.position, 0, 0); | |
} | |
shape.angle = angle || 0; | |
this.shapes.push(shape); | |
this.updateMassProperties(); | |
this.updateBoundingRadius(); | |
this.aabbNeedsUpdate = true; | |
}; | |
/** | |
* Remove a shape | |
* @method removeShape | |
* @param {Shape} shape | |
* @return {Boolean} True if the shape was found and removed, else false. | |
*/ | |
Body.prototype.removeShape = function(shape){ | |
var idx = this.shapes.indexOf(shape); | |
if(idx !== -1){ | |
this.shapes.splice(idx,1); | |
this.aabbNeedsUpdate = true; | |
shape.body = null; | |
return true; | |
} else { | |
return false; | |
} | |
}; | |
/** | |
* Updates .inertia, .invMass, .invInertia for this Body. Should be called when | |
* changing the structure or mass of the Body. | |
* | |
* @method updateMassProperties | |
* | |
* @example | |
* body.mass += 1; | |
* body.updateMassProperties(); | |
*/ | |
Body.prototype.updateMassProperties = function(){ | |
if(this.type === Body.STATIC || this.type === Body.KINEMATIC){ | |
this.mass = Number.MAX_VALUE; | |
this.invMass = 0; | |
this.inertia = Number.MAX_VALUE; | |
this.invInertia = 0; | |
} else { | |
var shapes = this.shapes, | |
N = shapes.length, | |
m = this.mass / N, | |
I = 0; | |
if(!this.fixedRotation){ | |
for(var i=0; i<N; i++){ | |
var shape = shapes[i], | |
r2 = vec2.squaredLength(shape.position), | |
Icm = shape.computeMomentOfInertia(m); | |
I += Icm + m*r2; | |
} | |
this.inertia = I; | |
this.invInertia = I>0 ? 1/I : 0; | |
} else { | |
this.inertia = Number.MAX_VALUE; | |
this.invInertia = 0; | |
} | |
// Inverse mass properties are easy | |
this.invMass = 1 / this.mass; | |
vec2.set( | |
this.massMultiplier, | |
this.fixedX ? 0 : 1, | |
this.fixedY ? 0 : 1 | |
); | |
} | |
}; | |
var Body_applyForce_r = vec2.create(); | |
/** | |
* Apply force to a point relative to the center of mass of the body. This could for example be a point on the RigidBody surface. Applying force this way will add to Body.force and Body.angularForce. If relativePoint is zero, the force will be applied directly on the center of mass, and the torque produced will be zero. | |
* @method applyForce | |
* @param {Array} force The force to add. | |
* @param {Array} [relativePoint] A world point to apply the force on. | |
*/ | |
Body.prototype.applyForce = function(force, relativePoint){ | |
// Add linear force | |
vec2.add(this.force, this.force, force); | |
if(relativePoint){ | |
// Compute produced rotational force | |
var rotForce = vec2.crossLength(relativePoint,force); | |
// Add rotational force | |
this.angularForce += rotForce; | |
} | |
}; | |
/** | |
* Apply force to a body-local point. | |
* @method applyForceLocal | |
* @param {Array} localForce The force vector to add, oriented in local body space. | |
* @param {Array} localPoint A point relative to the body in world space. If not given, it is set to zero and all of the impulse will be excerted on the center of mass. | |
*/ | |
var Body_applyForce_forceWorld = vec2.create(); | |
var Body_applyForce_pointWorld = vec2.create(); | |
var Body_applyForce_pointLocal = vec2.create(); | |
Body.prototype.applyForceLocal = function(localForce, localPoint){ | |
localPoint = localPoint || Body_applyForce_pointLocal; | |
var worldForce = Body_applyForce_forceWorld; | |
var worldPoint = Body_applyForce_pointWorld; | |
this.vectorToWorldFrame(worldForce, localForce); | |
this.vectorToWorldFrame(worldPoint, localPoint); | |
this.applyForce(worldForce, worldPoint); | |
}; | |
/** | |
* Apply impulse to a point relative to the body. This could for example be a point on the Body surface. An impulse is a force added to a body during a short period of time (impulse = force * time). Impulses will be added to Body.velocity and Body.angularVelocity. | |
* @method applyImpulse | |
* @param {Array} impulse The impulse vector to add, oriented in world space. | |
* @param {Array} [relativePoint] A point relative to the body in world space. If not given, it is set to zero and all of the impulse will be excerted on the center of mass. | |
*/ | |
var Body_applyImpulse_velo = vec2.create(); | |
Body.prototype.applyImpulse = function(impulseVector, relativePoint){ | |
if(this.type !== Body.DYNAMIC){ | |
return; | |
} | |
// Compute produced central impulse velocity | |
var velo = Body_applyImpulse_velo; | |
vec2.scale(velo, impulseVector, this.invMass); | |
vec2.multiply(velo, this.massMultiplier, velo); | |
// Add linear impulse | |
vec2.add(this.velocity, velo, this.velocity); | |
if(relativePoint){ | |
// Compute produced rotational impulse velocity | |
var rotVelo = vec2.crossLength(relativePoint, impulseVector); | |
rotVelo *= this.invInertia; | |
// Add rotational Impulse | |
this.angularVelocity += rotVelo; | |
} | |
}; | |
/** | |
* Apply impulse to a point relative to the body. This could for example be a point on the Body surface. An impulse is a force added to a body during a short period of time (impulse = force * time). Impulses will be added to Body.velocity and Body.angularVelocity. | |
* @method applyImpulseLocal | |
* @param {Array} impulse The impulse vector to add, oriented in world space. | |
* @param {Array} [relativePoint] A point relative to the body in world space. If not given, it is set to zero and all of the impulse will be excerted on the center of mass. | |
*/ | |
var Body_applyImpulse_impulseWorld = vec2.create(); | |
var Body_applyImpulse_pointWorld = vec2.create(); | |
var Body_applyImpulse_pointLocal = vec2.create(); | |
Body.prototype.applyImpulseLocal = function(localImpulse, localPoint){ | |
localPoint = localPoint || Body_applyImpulse_pointLocal; | |
var worldImpulse = Body_applyImpulse_impulseWorld; | |
var worldPoint = Body_applyImpulse_pointWorld; | |
this.vectorToWorldFrame(worldImpulse, localImpulse); | |
this.vectorToWorldFrame(worldPoint, localPoint); | |
this.applyImpulse(worldImpulse, worldPoint); | |
}; | |
/** | |
* Transform a world point to local body frame. | |
* @method toLocalFrame | |
* @param {Array} out The vector to store the result in | |
* @param {Array} worldPoint The input world point | |
*/ | |
Body.prototype.toLocalFrame = function(out, worldPoint){ | |
vec2.toLocalFrame(out, worldPoint, this.position, this.angle); | |
}; | |
/** | |
* Transform a local point to world frame. | |
* @method toWorldFrame | |
* @param {Array} out The vector to store the result in | |
* @param {Array} localPoint The input local point | |
*/ | |
Body.prototype.toWorldFrame = function(out, localPoint){ | |
vec2.toGlobalFrame(out, localPoint, this.position, this.angle); | |
}; | |
/** | |
* Transform a world point to local body frame. | |
* @method vectorToLocalFrame | |
* @param {Array} out The vector to store the result in | |
* @param {Array} worldVector The input world vector | |
*/ | |
Body.prototype.vectorToLocalFrame = function(out, worldVector){ | |
vec2.vectorToLocalFrame(out, worldVector, this.angle); | |
}; | |
/** | |
* Transform a local point to world frame. | |
* @method vectorToWorldFrame | |
* @param {Array} out The vector to store the result in | |
* @param {Array} localVector The input local vector | |
*/ | |
Body.prototype.vectorToWorldFrame = function(out, localVector){ | |
vec2.vectorToGlobalFrame(out, localVector, this.angle); | |
}; | |
/** | |
* Reads a polygon shape path, and assembles convex shapes from that and puts them at proper offset points. | |
* @method fromPolygon | |
* @param {Array} path An array of 2d vectors, e.g. [[0,0],[0,1],...] that resembles a concave or convex polygon. The shape must be simple and without holes. | |
* @param {Object} [options] | |
* @param {Boolean} [options.optimalDecomp=false] Set to true if you need optimal decomposition. Warning: very slow for polygons with more than 10 vertices. | |
* @param {Boolean} [options.skipSimpleCheck=false] Set to true if you already know that the path is not intersecting itself. | |
* @param {Boolean|Number} [options.removeCollinearPoints=false] Set to a number (angle threshold value) to remove collinear points, or false to keep all points. | |
* @return {Boolean} True on success, else false. | |
*/ | |
Body.prototype.fromPolygon = function(path,options){ | |
options = options || {}; | |
// Remove all shapes | |
for(var i=this.shapes.length; i>=0; --i){ | |
this.removeShape(this.shapes[i]); | |
} | |
var p = new decomp.Polygon(); | |
p.vertices = path; | |
// Make it counter-clockwise | |
p.makeCCW(); | |
if(typeof(options.removeCollinearPoints) === "number"){ | |
p.removeCollinearPoints(options.removeCollinearPoints); | |
} | |
// Check if any line segment intersects the path itself | |
if(typeof(options.skipSimpleCheck) === "undefined"){ | |
if(!p.isSimple()){ | |
return false; | |
} | |
} | |
// Save this path for later | |
this.concavePath = p.vertices.slice(0); | |
for(var i=0; i<this.concavePath.length; i++){ | |
var v = [0,0]; | |
vec2.copy(v,this.concavePath[i]); | |
this.concavePath[i] = v; | |
} | |
// Slow or fast decomp? | |
var convexes; | |
if(options.optimalDecomp){ | |
convexes = p.decomp(); | |
} else { | |
convexes = p.quickDecomp(); | |
} | |
var cm = vec2.create(); | |
// Add convexes | |
for(var i=0; i!==convexes.length; i++){ | |
// Create convex | |
var c = new Convex({ vertices: convexes[i].vertices }); | |
// Move all vertices so its center of mass is in the local center of the convex | |
for(var j=0; j!==c.vertices.length; j++){ | |
var v = c.vertices[j]; | |
vec2.sub(v,v,c.centerOfMass); | |
} | |
vec2.scale(cm,c.centerOfMass,1); | |
c.updateTriangles(); | |
c.updateCenterOfMass(); | |
c.updateBoundingRadius(); | |
// Add the shape | |
this.addShape(c,cm); | |
} | |
this.adjustCenterOfMass(); | |
this.aabbNeedsUpdate = true; | |
return true; | |
}; | |
var adjustCenterOfMass_tmp1 = vec2.fromValues(0,0), | |
adjustCenterOfMass_tmp2 = vec2.fromValues(0,0), | |
adjustCenterOfMass_tmp3 = vec2.fromValues(0,0), | |
adjustCenterOfMass_tmp4 = vec2.fromValues(0,0); | |
/** | |
* Moves the shape offsets so their center of mass becomes the body center of mass. | |
* @method adjustCenterOfMass | |
*/ | |
Body.prototype.adjustCenterOfMass = function(){ | |
var offset_times_area = adjustCenterOfMass_tmp2, | |
sum = adjustCenterOfMass_tmp3, | |
cm = adjustCenterOfMass_tmp4, | |
totalArea = 0; | |
vec2.set(sum,0,0); | |
for(var i=0; i!==this.shapes.length; i++){ | |
var s = this.shapes[i]; | |
vec2.scale(offset_times_area, s.position, s.area); | |
vec2.add(sum, sum, offset_times_area); | |
totalArea += s.area; | |
} | |
vec2.scale(cm,sum,1/totalArea); | |
// Now move all shapes | |
for(var i=0; i!==this.shapes.length; i++){ | |
var s = this.shapes[i]; | |
vec2.sub(s.position, s.position, cm); | |
} | |
// Move the body position too | |
vec2.add(this.position,this.position,cm); | |
// And concave path | |
for(var i=0; this.concavePath && i<this.concavePath.length; i++){ | |
vec2.sub(this.concavePath[i], this.concavePath[i], cm); | |
} | |
this.updateMassProperties(); | |
this.updateBoundingRadius(); | |
}; | |
/** | |
* Sets the force on the body to zero. | |
* @method setZeroForce | |
*/ | |
Body.prototype.setZeroForce = function(){ | |
vec2.set(this.force,0.0,0.0); | |
this.angularForce = 0.0; | |
}; | |
Body.prototype.resetConstraintVelocity = function(){ | |
var b = this, | |
vlambda = b.vlambda; | |
vec2.set(vlambda,0,0); | |
b.wlambda = 0; | |
}; | |
Body.prototype.addConstraintVelocity = function(){ | |
var b = this, | |
v = b.velocity; | |
vec2.add( v, v, b.vlambda); | |
b.angularVelocity += b.wlambda; | |
}; | |
/** | |
* Apply damping, see <a href="http://code.google.com/p/bullet/issues/detail?id=74">this</a> for details. | |
* @method applyDamping | |
* @param {number} dt Current time step | |
*/ | |
Body.prototype.applyDamping = function(dt){ | |
if(this.type === Body.DYNAMIC){ // Only for dynamic bodies | |
var v = this.velocity; | |
vec2.scale(v, v, Math.pow(1.0 - this.damping,dt)); | |
this.angularVelocity *= Math.pow(1.0 - this.angularDamping,dt); | |
} | |
}; | |
/** | |
* Wake the body up. Normally you should not need this, as the body is automatically awoken at events such as collisions. | |
* Sets the sleepState to {{#crossLink "Body/AWAKE:property"}}Body.AWAKE{{/crossLink}} and emits the wakeUp event if the body wasn't awake before. | |
* @method wakeUp | |
*/ | |
Body.prototype.wakeUp = function(){ | |
var s = this.sleepState; | |
this.sleepState = Body.AWAKE; | |
this.idleTime = 0; | |
if(s !== Body.AWAKE){ | |
this.emit(Body.wakeUpEvent); | |
} | |
}; | |
/** | |
* Force body sleep | |
* @method sleep | |
*/ | |
Body.prototype.sleep = function(){ | |
this.sleepState = Body.SLEEPING; | |
this.angularVelocity = 0; | |
this.angularForce = 0; | |
vec2.set(this.velocity,0,0); | |
vec2.set(this.force,0,0); | |
this.emit(Body.sleepEvent); | |
}; | |
/** | |
* Called every timestep to update internal sleep timer and change sleep state if needed. | |
* @method sleepTick | |
* @param {number} time The world time in seconds | |
* @param {boolean} dontSleep | |
* @param {number} dt | |
*/ | |
Body.prototype.sleepTick = function(time, dontSleep, dt){ | |
if(!this.allowSleep || this.type === Body.SLEEPING){ | |
return; | |
} | |
this.wantsToSleep = false; | |
var sleepState = this.sleepState, | |
speedSquared = vec2.squaredLength(this.velocity) + Math.pow(this.angularVelocity,2), | |
speedLimitSquared = Math.pow(this.sleepSpeedLimit,2); | |
// Add to idle time | |
if(speedSquared >= speedLimitSquared){ | |
this.idleTime = 0; | |
this.sleepState = Body.AWAKE; | |
} else { | |
this.idleTime += dt; | |
this.sleepState = Body.SLEEPY; | |
} | |
if(this.idleTime > this.sleepTimeLimit){ | |
if(!dontSleep){ | |
this.sleep(); | |
} else { | |
this.wantsToSleep = true; | |
} | |
} | |
}; | |
/** | |
* Check if the body is overlapping another body. Note that this method only works if the body was added to a World and if at least one step was taken. | |
* @method overlaps | |
* @param {Body} body | |
* @return {boolean} | |
*/ | |
Body.prototype.overlaps = function(body){ | |
return this.world.overlapKeeper.bodiesAreOverlapping(this, body); | |
}; | |
var integrate_fhMinv = vec2.create(); | |
var integrate_velodt = vec2.create(); | |
/** | |
* Move the body forward in time given its current velocity. | |
* @method integrate | |
* @param {Number} dt | |
*/ | |
Body.prototype.integrate = function(dt){ | |
var minv = this.invMass, | |
f = this.force, | |
pos = this.position, | |
velo = this.velocity; | |
// Save old position | |
vec2.copy(this.previousPosition, this.position); | |
this.previousAngle = this.angle; | |
// Velocity update | |
if(!this.fixedRotation){ | |
this.angularVelocity += this.angularForce * this.invInertia * dt; | |
} | |
vec2.scale(integrate_fhMinv, f, dt * minv); | |
vec2.multiply(integrate_fhMinv, this.massMultiplier, integrate_fhMinv); | |
vec2.add(velo, integrate_fhMinv, velo); | |
// CCD | |
if(!this.integrateToTimeOfImpact(dt)){ | |
// Regular position update | |
vec2.scale(integrate_velodt, velo, dt); | |
vec2.add(pos, pos, integrate_velodt); | |
if(!this.fixedRotation){ | |
this.angle += this.angularVelocity * dt; | |
} | |
} | |
this.aabbNeedsUpdate = true; | |
}; | |
var result = new RaycastResult(); | |
var ray = new Ray({ | |
mode: Ray.ALL | |
}); | |
var direction = vec2.create(); | |
var end = vec2.create(); | |
var startToEnd = vec2.create(); | |
var rememberPosition = vec2.create(); | |
Body.prototype.integrateToTimeOfImpact = function(dt){ | |
if(this.ccdSpeedThreshold < 0 || vec2.squaredLength(this.velocity) < Math.pow(this.ccdSpeedThreshold, 2)){ | |
return false; | |
} | |
vec2.normalize(direction, this.velocity); | |
vec2.scale(end, this.velocity, dt); | |
vec2.add(end, end, this.position); | |
vec2.sub(startToEnd, end, this.position); | |
var startToEndAngle = this.angularVelocity * dt; | |
var len = vec2.length(startToEnd); | |
var timeOfImpact = 1; | |
var hit; | |
var that = this; | |
result.reset(); | |
ray.callback = function (result) { | |
if(result.body === that){ | |
return; | |
} | |
hit = result.body; | |
result.getHitPoint(end, ray); | |
vec2.sub(startToEnd, end, that.position); | |
timeOfImpact = vec2.length(startToEnd) / len; | |
result.stop(); | |
}; | |
vec2.copy(ray.from, this.position); | |
vec2.copy(ray.to, end); | |
ray.update(); | |
this.world.raycast(result, ray); | |
if(!hit){ | |
return false; | |
} | |
var rememberAngle = this.angle; | |
vec2.copy(rememberPosition, this.position); | |
// Got a start and end point. Approximate time of impact using binary search | |
var iter = 0; | |
var tmin = 0; | |
var tmid = 0; | |
var tmax = timeOfImpact; | |
while (tmax >= tmin && iter < this.ccdIterations) { | |
iter++; | |
// calculate the midpoint | |
tmid = (tmax - tmin) / 2; | |
// Move the body to that point | |
vec2.scale(integrate_velodt, startToEnd, timeOfImpact); | |
vec2.add(this.position, rememberPosition, integrate_velodt); | |
this.angle = rememberAngle + startToEndAngle * timeOfImpact; | |
this.updateAABB(); | |
// check overlap | |
var overlaps = this.aabb.overlaps(hit.aabb) && this.world.narrowphase.bodiesOverlap(this, hit); | |
if (overlaps) { | |
// change min to search upper interval | |
tmin = tmid; | |
} else { | |
// change max to search lower interval | |
tmax = tmid; | |
} | |
} | |
timeOfImpact = tmid; | |
vec2.copy(this.position, rememberPosition); | |
this.angle = rememberAngle; | |
// move to TOI | |
vec2.scale(integrate_velodt, startToEnd, timeOfImpact); | |
vec2.add(this.position, this.position, integrate_velodt); | |
if(!this.fixedRotation){ | |
this.angle += startToEndAngle * timeOfImpact; | |
} | |
return true; | |
}; | |
/** | |
* Get velocity of a point in the body. | |
* @method getVelocityAtPoint | |
* @param {Array} result A vector to store the result in | |
* @param {Array} relativePoint A world oriented vector, indicating the position of the point to get the velocity from | |
* @return {Array} The result vector | |
*/ | |
Body.prototype.getVelocityAtPoint = function(result, relativePoint){ | |
vec2.crossVZ(result, relativePoint, this.angularVelocity); | |
vec2.subtract(result, this.velocity, result); | |
return result; | |
}; | |
/** | |
* @event sleepy | |
*/ | |
Body.sleepyEvent = { | |
type: "sleepy" | |
}; | |
/** | |
* @event sleep | |
*/ | |
Body.sleepEvent = { | |
type: "sleep" | |
}; | |
/** | |
* @event wakeup | |
*/ | |
Body.wakeUpEvent = { | |
type: "wakeup" | |
}; | |
/** | |
* Dynamic body. | |
* @property DYNAMIC | |
* @type {Number} | |
* @static | |
*/ | |
Body.DYNAMIC = 1; | |
/** | |
* Static body. | |
* @property STATIC | |
* @type {Number} | |
* @static | |
*/ | |
Body.STATIC = 2; | |
/** | |
* Kinematic body. | |
* @property KINEMATIC | |
* @type {Number} | |
* @static | |
*/ | |
Body.KINEMATIC = 4; | |
/** | |
* @property AWAKE | |
* @type {Number} | |
* @static | |
*/ | |
Body.AWAKE = 0; | |
/** | |
* @property SLEEPY | |
* @type {Number} | |
* @static | |
*/ | |
Body.SLEEPY = 1; | |
/** | |
* @property SLEEPING | |
* @type {Number} | |
* @static | |
*/ | |
Body.SLEEPING = 2; | |
},{"../collision/AABB":7,"../collision/Ray":11,"../collision/RaycastResult":12,"../events/EventEmitter":26,"../math/vec2":30,"../shapes/Convex":40,"poly-decomp":5}],32:[function(_dereq_,module,exports){ | |
var vec2 = _dereq_('../math/vec2'); | |
var Spring = _dereq_('./Spring'); | |
var Utils = _dereq_('../utils/Utils'); | |
module.exports = LinearSpring; | |
/** | |
* A spring, connecting two bodies. | |
* | |
* The Spring explicitly adds force and angularForce to the bodies. | |
* | |
* @class LinearSpring | |
* @extends Spring | |
* @constructor | |
* @param {Body} bodyA | |
* @param {Body} bodyB | |
* @param {Object} [options] | |
* @param {number} [options.restLength] A number > 0. Default is the current distance between the world anchor points. | |
* @param {number} [options.stiffness=100] Spring constant (see Hookes Law). A number >= 0. | |
* @param {number} [options.damping=1] A number >= 0. Default: 1 | |
* @param {Array} [options.worldAnchorA] Where to hook the spring to body A, in world coordinates. Overrides the option "localAnchorA" if given. | |
* @param {Array} [options.worldAnchorB] | |
* @param {Array} [options.localAnchorA] Where to hook the spring to body A, in local body coordinates. Defaults to the body center. | |
* @param {Array} [options.localAnchorB] | |
*/ | |
function LinearSpring(bodyA,bodyB,options){ | |
options = options || {}; | |
Spring.call(this, bodyA, bodyB, options); | |
/** | |
* Anchor for bodyA in local bodyA coordinates. | |
* @property localAnchorA | |
* @type {Array} | |
*/ | |
this.localAnchorA = vec2.fromValues(0,0); | |
/** | |
* Anchor for bodyB in local bodyB coordinates. | |
* @property localAnchorB | |
* @type {Array} | |
*/ | |
this.localAnchorB = vec2.fromValues(0,0); | |
if(options.localAnchorA){ vec2.copy(this.localAnchorA, options.localAnchorA); } | |
if(options.localAnchorB){ vec2.copy(this.localAnchorB, options.localAnchorB); } | |
if(options.worldAnchorA){ this.setWorldAnchorA(options.worldAnchorA); } | |
if(options.worldAnchorB){ this.setWorldAnchorB(options.worldAnchorB); } | |
var worldAnchorA = vec2.create(); | |
var worldAnchorB = vec2.create(); | |
this.getWorldAnchorA(worldAnchorA); | |
this.getWorldAnchorB(worldAnchorB); | |
var worldDistance = vec2.distance(worldAnchorA, worldAnchorB); | |
/** | |
* Rest length of the spring. | |
* @property restLength | |
* @type {number} | |
*/ | |
this.restLength = typeof(options.restLength) === "number" ? options.restLength : worldDistance; | |
} | |
LinearSpring.prototype = new Spring(); | |
LinearSpring.prototype.constructor = LinearSpring; | |
/** | |
* Set the anchor point on body A, using world coordinates. | |
* @method setWorldAnchorA | |
* @param {Array} worldAnchorA | |
*/ | |
LinearSpring.prototype.setWorldAnchorA = function(worldAnchorA){ | |
this.bodyA.toLocalFrame(this.localAnchorA, worldAnchorA); | |
}; | |
/** | |
* Set the anchor point on body B, using world coordinates. | |
* @method setWorldAnchorB | |
* @param {Array} worldAnchorB | |
*/ | |
LinearSpring.prototype.setWorldAnchorB = function(worldAnchorB){ | |
this.bodyB.toLocalFrame(this.localAnchorB, worldAnchorB); | |
}; | |
/** | |
* Get the anchor point on body A, in world coordinates. | |
* @method getWorldAnchorA | |
* @param {Array} result The vector to store the result in. | |
*/ | |
LinearSpring.prototype.getWorldAnchorA = function(result){ | |
this.bodyA.toWorldFrame(result, this.localAnchorA); | |
}; | |
/** | |
* Get the anchor point on body B, in world coordinates. | |
* @method getWorldAnchorB | |
* @param {Array} result The vector to store the result in. | |
*/ | |
LinearSpring.prototype.getWorldAnchorB = function(result){ | |
this.bodyB.toWorldFrame(result, this.localAnchorB); | |
}; | |
var applyForce_r = vec2.create(), | |
applyForce_r_unit = vec2.create(), | |
applyForce_u = vec2.create(), | |
applyForce_f = vec2.create(), | |
applyForce_worldAnchorA = vec2.create(), | |
applyForce_worldAnchorB = vec2.create(), | |
applyForce_ri = vec2.create(), | |
applyForce_rj = vec2.create(), | |
applyForce_tmp = vec2.create(); | |
/** | |
* Apply the spring force to the connected bodies. | |
* @method applyForce | |
*/ | |
LinearSpring.prototype.applyForce = function(){ | |
var k = this.stiffness, | |
d = this.damping, | |
l = this.restLength, | |
bodyA = this.bodyA, | |
bodyB = this.bodyB, | |
r = applyForce_r, | |
r_unit = applyForce_r_unit, | |
u = applyForce_u, | |
f = applyForce_f, | |
tmp = applyForce_tmp; | |
var worldAnchorA = applyForce_worldAnchorA, | |
worldAnchorB = applyForce_worldAnchorB, | |
ri = applyForce_ri, | |
rj = applyForce_rj; | |
// Get world anchors | |
this.getWorldAnchorA(worldAnchorA); | |
this.getWorldAnchorB(worldAnchorB); | |
// Get offset points | |
vec2.sub(ri, worldAnchorA, bodyA.position); | |
vec2.sub(rj, worldAnchorB, bodyB.position); | |
// Compute distance vector between world anchor points | |
vec2.sub(r, worldAnchorB, worldAnchorA); | |
var rlen = vec2.len(r); | |
vec2.normalize(r_unit,r); | |
//console.log(rlen) | |
//console.log("A",vec2.str(worldAnchorA),"B",vec2.str(worldAnchorB)) | |
// Compute relative velocity of the anchor points, u | |
vec2.sub(u, bodyB.velocity, bodyA.velocity); | |
vec2.crossZV(tmp, bodyB.angularVelocity, rj); | |
vec2.add(u, u, tmp); | |
vec2.crossZV(tmp, bodyA.angularVelocity, ri); | |
vec2.sub(u, u, tmp); | |
// F = - k * ( x - L ) - D * ( u ) | |
vec2.scale(f, r_unit, -k*(rlen-l) - d*vec2.dot(u,r_unit)); | |
// Add forces to bodies | |
vec2.sub( bodyA.force, bodyA.force, f); | |
vec2.add( bodyB.force, bodyB.force, f); | |
// Angular force | |
var ri_x_f = vec2.crossLength(ri, f); | |
var rj_x_f = vec2.crossLength(rj, f); | |
bodyA.angularForce -= ri_x_f; | |
bodyB.angularForce += rj_x_f; | |
}; | |
},{"../math/vec2":30,"../utils/Utils":57,"./Spring":34}],33:[function(_dereq_,module,exports){ | |
var vec2 = _dereq_('../math/vec2'); | |
var Spring = _dereq_('./Spring'); | |
module.exports = RotationalSpring; | |
/** | |
* A rotational spring, connecting two bodies rotation. This spring explicitly adds angularForce (torque) to the bodies. | |
* | |
* The spring can be combined with a {{#crossLink "RevoluteConstraint"}}{{/crossLink}} to make, for example, a mouse trap. | |
* | |
* @class RotationalSpring | |
* @extends Spring | |
* @constructor | |
* @param {Body} bodyA | |
* @param {Body} bodyB | |
* @param {Object} [options] | |
* @param {number} [options.restAngle] The relative angle of bodies at which the spring is at rest. If not given, it's set to the current relative angle between the bodies. | |
* @param {number} [options.stiffness=100] Spring constant (see Hookes Law). A number >= 0. | |
* @param {number} [options.damping=1] A number >= 0. | |
*/ | |
function RotationalSpring(bodyA, bodyB, options){ | |
options = options || {}; | |
Spring.call(this, bodyA, bodyB, options); | |
/** | |
* Rest angle of the spring. | |
* @property restAngle | |
* @type {number} | |
*/ | |
this.restAngle = typeof(options.restAngle) === "number" ? options.restAngle : bodyB.angle - bodyA.angle; | |
} | |
RotationalSpring.prototype = new Spring(); | |
RotationalSpring.prototype.constructor = RotationalSpring; | |
/** | |
* Apply the spring force to the connected bodies. | |
* @method applyForce | |
*/ | |
RotationalSpring.prototype.applyForce = function(){ | |
var k = this.stiffness, | |
d = this.damping, | |
l = this.restAngle, | |
bodyA = this.bodyA, | |
bodyB = this.bodyB, | |
x = bodyB.angle - bodyA.angle, | |
u = bodyB.angularVelocity - bodyA.angularVelocity; | |
var torque = - k * (x - l) - d * u * 0; | |
bodyA.angularForce -= torque; | |
bodyB.angularForce += torque; | |
}; | |
},{"../math/vec2":30,"./Spring":34}],34:[function(_dereq_,module,exports){ | |
var vec2 = _dereq_('../math/vec2'); | |
var Utils = _dereq_('../utils/Utils'); | |
module.exports = Spring; | |
/** | |
* A spring, connecting two bodies. The Spring explicitly adds force and angularForce to the bodies and does therefore not put load on the constraint solver. | |
* | |
* @class Spring | |
* @constructor | |
* @param {Body} bodyA | |
* @param {Body} bodyB | |
* @param {Object} [options] | |
* @param {number} [options.stiffness=100] Spring constant (see Hookes Law). A number >= 0. | |
* @param {number} [options.damping=1] A number >= 0. Default: 1 | |
* @param {Array} [options.localAnchorA] Where to hook the spring to body A, in local body coordinates. Defaults to the body center. | |
* @param {Array} [options.localAnchorB] | |
* @param {Array} [options.worldAnchorA] Where to hook the spring to body A, in world coordinates. Overrides the option "localAnchorA" if given. | |
* @param {Array} [options.worldAnchorB] | |
*/ | |
function Spring(bodyA, bodyB, options){ | |
options = Utils.defaults(options,{ | |
stiffness: 100, | |
damping: 1, | |
}); | |
/** | |
* Stiffness of the spring. | |
* @property stiffness | |
* @type {number} | |
*/ | |
this.stiffness = options.stiffness; | |
/** | |
* Damping of the spring. | |
* @property damping | |
* @type {number} | |
*/ | |
this.damping = options.damping; | |
/** | |
* First connected body. | |
* @property bodyA | |
* @type {Body} | |
*/ | |
this.bodyA = bodyA; | |
/** | |
* Second connected body. | |
* @property bodyB | |
* @type {Body} | |
*/ | |
this.bodyB = bodyB; | |
} | |
/** | |
* Apply the spring force to the connected bodies. | |
* @method applyForce | |
*/ | |
Spring.prototype.applyForce = function(){ | |
// To be implemented by subclasses | |
}; | |
},{"../math/vec2":30,"../utils/Utils":57}],35:[function(_dereq_,module,exports){ | |
var vec2 = _dereq_('../math/vec2'); | |
var Utils = _dereq_('../utils/Utils'); | |
var Constraint = _dereq_('../constraints/Constraint'); | |
var FrictionEquation = _dereq_('../equations/FrictionEquation'); | |
var Body = _dereq_('../objects/Body'); | |
module.exports = TopDownVehicle; | |
/** | |
* @class TopDownVehicle | |
* @constructor | |
* @param {Body} chassisBody A dynamic body, already added to the world. | |
* @param {Object} [options] | |
* | |
* @example | |
* | |
* // Create a dynamic body for the chassis | |
* var chassisBody = new Body({ | |
* mass: 1 | |
* }); | |
* var boxShape = new Box({ width: 0.5, height: 1 }); | |
* chassisBody.addShape(boxShape); | |
* world.addBody(chassisBody); | |
* | |
* // Create the vehicle | |
* var vehicle = new TopDownVehicle(chassisBody); | |
* | |
* // Add one front wheel and one back wheel - we don't actually need four :) | |
* var frontWheel = vehicle.addWheel({ | |
* localPosition: [0, 0.5] // front | |
* }); | |
* frontWheel.setSideFriction(4); | |
* | |
* // Back wheel | |
* var backWheel = vehicle.addWheel({ | |
* localPosition: [0, -0.5] // back | |
* }); | |
* backWheel.setSideFriction(3); // Less side friction on back wheel makes it easier to drift | |
* vehicle.addToWorld(world); | |
* | |
* // Steer value zero means straight forward. Positive is left and negative right. | |
* frontWheel.steerValue = Math.PI / 16; | |
* | |
* // Engine force forward | |
* backWheel.engineForce = 10; | |
* backWheel.setBrakeForce(0); | |
*/ | |
function TopDownVehicle(chassisBody, options){ | |
options = options || {}; | |
/** | |
* @property {Body} chassisBody | |
*/ | |
this.chassisBody = chassisBody; | |
/** | |
* @property {Array} wheels | |
*/ | |
this.wheels = []; | |
// A dummy body to constrain the chassis to | |
this.groundBody = new Body({ mass: 0 }); | |
this.world = null; | |
var that = this; | |
this.preStepCallback = function(){ | |
that.update(); | |
}; | |
} | |
/** | |
* @method addToWorld | |
* @param {World} world | |
*/ | |
TopDownVehicle.prototype.addToWorld = function(world){ | |
this.world = world; | |
world.addBody(this.groundBody); | |
world.on('preStep', this.preStepCallback); | |
for (var i = 0; i < this.wheels.length; i++) { | |
var wheel = this.wheels[i]; | |
world.addConstraint(wheel); | |
} | |
}; | |
/** | |
* @method removeFromWorld | |
* @param {World} world | |
*/ | |
TopDownVehicle.prototype.removeFromWorld = function(){ | |
var world = this.world; | |
world.removeBody(this.groundBody); | |
world.off('preStep', this.preStepCallback); | |
for (var i = 0; i < this.wheels.length; i++) { | |
var wheel = this.wheels[i]; | |
world.removeConstraint(wheel); | |
} | |
this.world = null; | |
}; | |
/** | |
* @method addWheel | |
* @param {object} [wheelOptions] | |
* @return {WheelConstraint} | |
*/ | |
TopDownVehicle.prototype.addWheel = function(wheelOptions){ | |
var wheel = new WheelConstraint(this,wheelOptions); | |
this.wheels.push(wheel); | |
return wheel; | |
}; | |
/** | |
* @method update | |
*/ | |
TopDownVehicle.prototype.update = function(){ | |
for (var i = 0; i < this.wheels.length; i++) { | |
this.wheels[i].update(); | |
} | |
}; | |
/** | |
* @class WheelConstraint | |
* @constructor | |
* @extends {Constraint} | |
* @param {Vehicle} vehicle | |
* @param {object} [options] | |
* @param {Array} [options.localForwardVector]The local wheel forward vector in local body space. Default is zero. | |
* @param {Array} [options.localPosition] The local position of the wheen in the chassis body. Default is zero - the center of the body. | |
* @param {Array} [options.sideFriction=5] The max friction force in the sideways direction. | |
*/ | |
function WheelConstraint(vehicle, options){ | |
options = options || {}; | |
this.vehicle = vehicle; | |
this.forwardEquation = new FrictionEquation(vehicle.chassisBody, vehicle.groundBody); | |
this.sideEquation = new FrictionEquation(vehicle.chassisBody, vehicle.groundBody); | |
/** | |
* @property {number} steerValue | |
*/ | |
this.steerValue = 0; | |
/** | |
* @property {number} engineForce | |
*/ | |
this.engineForce = 0; | |
this.setSideFriction(options.sideFriction !== undefined ? options.sideFriction : 5); | |
/** | |
* @property {Array} localForwardVector | |
*/ | |
this.localForwardVector = vec2.fromValues(0, 1); | |
if(options.localForwardVector){ | |
vec2.copy(this.localForwardVector, options.localForwardVector); | |
} | |
/** | |
* @property {Array} localPosition | |
*/ | |
this.localPosition = vec2.fromValues(0, 0); | |
if(options.localPosition){ | |
vec2.copy(this.localPosition, options.localPosition); | |
} | |
Constraint.apply(this, vehicle.chassisBody, vehicle.groundBody); | |
this.equations.push( | |
this.forwardEquation, | |
this.sideEquation | |
); | |
this.setBrakeForce(0); | |
} | |
WheelConstraint.prototype = new Constraint(); | |
/** | |
* @method setForwardFriction | |
*/ | |
WheelConstraint.prototype.setBrakeForce = function(force){ | |
this.forwardEquation.setSlipForce(force); | |
}; | |
/** | |
* @method setSideFriction | |
*/ | |
WheelConstraint.prototype.setSideFriction = function(force){ | |
this.sideEquation.setSlipForce(force); | |
}; | |
var worldVelocity = vec2.create(); | |
var relativePoint = vec2.create(); | |
/** | |
* @method getSpeed | |
*/ | |
WheelConstraint.prototype.getSpeed = function(){ | |
this.vehicle.chassisBody.vectorToWorldFrame(relativePoint, this.localForwardVector); | |
this.vehicle.chassisBody.getVelocityAtPoint(worldVelocity, relativePoint); | |
return vec2.dot(worldVelocity, relativePoint); | |
}; | |
var tmpVec = vec2.create(); | |
/** | |
* @method update | |
*/ | |
WheelConstraint.prototype.update = function(){ | |
// Directional | |
this.vehicle.chassisBody.vectorToWorldFrame(this.forwardEquation.t, this.localForwardVector); | |
vec2.rotate(this.sideEquation.t, this.localForwardVector, Math.PI / 2); | |
this.vehicle.chassisBody.vectorToWorldFrame(this.sideEquation.t, this.sideEquation.t); | |
vec2.rotate(this.forwardEquation.t, this.forwardEquation.t, this.steerValue); | |
vec2.rotate(this.sideEquation.t, this.sideEquation.t, this.steerValue); | |
// Attachment point | |
this.vehicle.chassisBody.toWorldFrame(this.forwardEquation.contactPointB, this.localPosition); | |
vec2.copy(this.sideEquation.contactPointB, this.forwardEquation.contactPointB); | |
this.vehicle.chassisBody.vectorToWorldFrame(this.forwardEquation.contactPointA, this.localPosition); | |
vec2.copy(this.sideEquation.contactPointA, this.forwardEquation.contactPointA); | |
// Add engine force | |
vec2.normalize(tmpVec, this.forwardEquation.t); | |
vec2.scale(tmpVec, tmpVec, this.engineForce); | |
this.vehicle.chassisBody.applyForce(tmpVec, this.forwardEquation.contactPointA); | |
}; | |
},{"../constraints/Constraint":14,"../equations/FrictionEquation":23,"../math/vec2":30,"../objects/Body":31,"../utils/Utils":57}],36:[function(_dereq_,module,exports){ | |
// Export p2 classes | |
var p2 = module.exports = { | |
AABB : _dereq_('./collision/AABB'), | |
AngleLockEquation : _dereq_('./equations/AngleLockEquation'), | |
Body : _dereq_('./objects/Body'), | |
Broadphase : _dereq_('./collision/Broadphase'), | |
Capsule : _dereq_('./shapes/Capsule'), | |
Circle : _dereq_('./shapes/Circle'), | |
Constraint : _dereq_('./constraints/Constraint'), | |
ContactEquation : _dereq_('./equations/ContactEquation'), | |
ContactEquationPool : _dereq_('./utils/ContactEquationPool'), | |
ContactMaterial : _dereq_('./material/ContactMaterial'), | |
Convex : _dereq_('./shapes/Convex'), | |
DistanceConstraint : _dereq_('./constraints/DistanceConstraint'), | |
Equation : _dereq_('./equations/Equation'), | |
EventEmitter : _dereq_('./events/EventEmitter'), | |
FrictionEquation : _dereq_('./equations/FrictionEquation'), | |
FrictionEquationPool : _dereq_('./utils/FrictionEquationPool'), | |
GearConstraint : _dereq_('./constraints/GearConstraint'), | |
GSSolver : _dereq_('./solver/GSSolver'), | |
Heightfield : _dereq_('./shapes/Heightfield'), | |
Line : _dereq_('./shapes/Line'), | |
LockConstraint : _dereq_('./constraints/LockConstraint'), | |
Material : _dereq_('./material/Material'), | |
Narrowphase : _dereq_('./collision/Narrowphase'), | |
NaiveBroadphase : _dereq_('./collision/NaiveBroadphase'), | |
Particle : _dereq_('./shapes/Particle'), | |
Plane : _dereq_('./shapes/Plane'), | |
Pool : _dereq_('./utils/Pool'), | |
RevoluteConstraint : _dereq_('./constraints/RevoluteConstraint'), | |
PrismaticConstraint : _dereq_('./constraints/PrismaticConstraint'), | |
Ray : _dereq_('./collision/Ray'), | |
RaycastResult : _dereq_('./collision/RaycastResult'), | |
Box : _dereq_('./shapes/Box'), | |
RotationalVelocityEquation : _dereq_('./equations/RotationalVelocityEquation'), | |
SAPBroadphase : _dereq_('./collision/SAPBroadphase'), | |
Shape : _dereq_('./shapes/Shape'), | |
Solver : _dereq_('./solver/Solver'), | |
Spring : _dereq_('./objects/Spring'), | |
TopDownVehicle : _dereq_('./objects/TopDownVehicle'), | |
LinearSpring : _dereq_('./objects/LinearSpring'), | |
RotationalSpring : _dereq_('./objects/RotationalSpring'), | |
Utils : _dereq_('./utils/Utils'), | |
World : _dereq_('./world/World'), | |
vec2 : _dereq_('./math/vec2'), | |
version : _dereq_('../package.json').version, | |
}; | |
Object.defineProperty(p2, 'Rectangle', { | |
get: function() { | |
console.warn('The Rectangle class has been renamed to Box.'); | |
return this.Box; | |
} | |
}); | |
},{"../package.json":6,"./collision/AABB":7,"./collision/Broadphase":8,"./collision/NaiveBroadphase":9,"./collision/Narrowphase":10,"./collision/Ray":11,"./collision/RaycastResult":12,"./collision/SAPBroadphase":13,"./constraints/Constraint":14,"./constraints/DistanceConstraint":15,"./constraints/GearConstraint":16,"./constraints/LockConstraint":17,"./constraints/PrismaticConstraint":18,"./constraints/RevoluteConstraint":19,"./equations/AngleLockEquation":20,"./equations/ContactEquation":21,"./equations/Equation":22,"./equations/FrictionEquation":23,"./equations/RotationalVelocityEquation":25,"./events/EventEmitter":26,"./material/ContactMaterial":27,"./material/Material":28,"./math/vec2":30,"./objects/Body":31,"./objects/LinearSpring":32,"./objects/RotationalSpring":33,"./objects/Spring":34,"./objects/TopDownVehicle":35,"./shapes/Box":37,"./shapes/Capsule":38,"./shapes/Circle":39,"./shapes/Convex":40,"./shapes/Heightfield":41,"./shapes/Line":42,"./shapes/Particle":43,"./shapes/Plane":44,"./shapes/Shape":45,"./solver/GSSolver":46,"./solver/Solver":47,"./utils/ContactEquationPool":48,"./utils/FrictionEquationPool":49,"./utils/Pool":55,"./utils/Utils":57,"./world/World":61}],37:[function(_dereq_,module,exports){ | |
var vec2 = _dereq_('../math/vec2') | |
, Shape = _dereq_('./Shape') | |
, Convex = _dereq_('./Convex'); | |
module.exports = Box; | |
/** | |
* Box shape class. | |
* @class Box | |
* @constructor | |
* @param {object} [options] (Note that this options object will be passed on to the {{#crossLink "Shape"}}{{/crossLink}} constructor.) | |
* @param {Number} [options.width=1] Total width of the box | |
* @param {Number} [options.height=1] Total height of the box | |
* @extends Convex | |
*/ | |
function Box(options){ | |
if(typeof(arguments[0]) === 'number' && typeof(arguments[1]) === 'number'){ | |
options = { | |
width: arguments[0], | |
height: arguments[1] | |
}; | |
console.warn('The Rectangle has been renamed to Box and its constructor signature has changed. Please use the following format: new Box({ width: 1, height: 1, ... })'); | |
} | |
options = options || {}; | |
/** | |
* Total width of the box | |
* @property width | |
* @type {Number} | |
*/ | |
var width = this.width = options.width || 1; | |
/** | |
* Total height of the box | |
* @property height | |
* @type {Number} | |
*/ | |
var height = this.height = options.height || 1; | |
var verts = [ | |
vec2.fromValues(-width/2, -height/2), | |
vec2.fromValues( width/2, -height/2), | |
vec2.fromValues( width/2, height/2), | |
vec2.fromValues(-width/2, height/2) | |
]; | |
var axes = [ | |
vec2.fromValues(1, 0), | |
vec2.fromValues(0, 1) | |
]; | |
options.vertices = verts; | |
options.axes = axes; | |
options.type = Shape.BOX; | |
Convex.call(this, options); | |
} | |
Box.prototype = new Convex(); | |
Box.prototype.constructor = Box; | |
/** | |
* Compute moment of inertia | |
* @method computeMomentOfInertia | |
* @param {Number} mass | |
* @return {Number} | |
*/ | |
Box.prototype.computeMomentOfInertia = function(mass){ | |
var w = this.width, | |
h = this.height; | |
return mass * (h*h + w*w) / 12; | |
}; | |
/** | |
* Update the bounding radius | |
* @method updateBoundingRadius | |
*/ | |
Box.prototype.updateBoundingRadius = function(){ | |
var w = this.width, | |
h = this.height; | |
this.boundingRadius = Math.sqrt(w*w + h*h) / 2; | |
}; | |
var corner1 = vec2.create(), | |
corner2 = vec2.create(), | |
corner3 = vec2.create(), | |
corner4 = vec2.create(); | |
/** | |
* @method computeAABB | |
* @param {AABB} out The resulting AABB. | |
* @param {Array} position | |
* @param {Number} angle | |
*/ | |
Box.prototype.computeAABB = function(out, position, angle){ | |
out.setFromPoints(this.vertices,position,angle,0); | |
}; | |
Box.prototype.updateArea = function(){ | |
this.area = this.width * this.height; | |
}; | |
},{"../math/vec2":30,"./Convex":40,"./Shape":45}],38:[function(_dereq_,module,exports){ | |
var Shape = _dereq_('./Shape') | |
, vec2 = _dereq_('../math/vec2'); | |
module.exports = Capsule; | |
/** | |
* Capsule shape class. | |
* @class Capsule | |
* @constructor | |
* @extends Shape | |
* @param {object} [options] (Note that this options object will be passed on to the {{#crossLink "Shape"}}{{/crossLink}} constructor.) | |
* @param {Number} [options.length=1] The distance between the end points | |
* @param {Number} [options.radius=1] Radius of the capsule | |
* @example | |
* var capsuleShape = new Capsule({ | |
* length: 1, | |
* radius: 2 | |
* }); | |
* body.addShape(capsuleShape); | |
*/ | |
function Capsule(options){ | |
if(typeof(arguments[0]) === 'number' && typeof(arguments[1]) === 'number'){ | |
options = { | |
length: arguments[0], | |
radius: arguments[1] | |
}; | |
console.warn('The Capsule constructor signature has changed. Please use the following format: new Capsule({ radius: 1, length: 1 })'); | |
} | |
options = options || {}; | |
/** | |
* The distance between the end points. | |
* @property {Number} length | |
*/ | |
this.length = options.length || 1; | |
/** | |
* The radius of the capsule. | |
* @property {Number} radius | |
*/ | |
this.radius = options.radius || 1; | |
options.type = Shape.CAPSULE; | |
Shape.call(this, options); | |
} | |
Capsule.prototype = new Shape(); | |
Capsule.prototype.constructor = Capsule; | |
/** | |
* Compute the mass moment of inertia of the Capsule. | |
* @method conputeMomentOfInertia | |
* @param {Number} mass | |
* @return {Number} | |
* @todo | |
*/ | |
Capsule.prototype.computeMomentOfInertia = function(mass){ | |
// Approximate with rectangle | |
var r = this.radius, | |
w = this.length + r, // 2*r is too much, 0 is too little | |
h = r*2; | |
return mass * (h*h + w*w) / 12; | |
}; | |
/** | |
* @method updateBoundingRadius | |
*/ | |
Capsule.prototype.updateBoundingRadius = function(){ | |
this.boundingRadius = this.radius + this.length/2; | |
}; | |
/** | |
* @method updateArea | |
*/ | |
Capsule.prototype.updateArea = function(){ | |
this.area = Math.PI * this.radius * this.radius + this.radius * 2 * this.length; | |
}; | |
var r = vec2.create(); | |
/** | |
* @method computeAABB | |
* @param {AABB} out The resulting AABB. | |
* @param {Array} position | |
* @param {Number} angle | |
*/ | |
Capsule.prototype.computeAABB = function(out, position, angle){ | |
var radius = this.radius; | |
// Compute center position of one of the the circles, world oriented, but with local offset | |
vec2.set(r,this.length / 2,0); | |
if(angle !== 0){ | |
vec2.rotate(r,r,angle); | |
} | |
// Get bounds | |
vec2.set(out.upperBound, Math.max(r[0]+radius, -r[0]+radius), | |
Math.max(r[1]+radius, -r[1]+radius)); | |
vec2.set(out.lowerBound, Math.min(r[0]-radius, -r[0]-radius), | |
Math.min(r[1]-radius, -r[1]-radius)); | |
// Add offset | |
vec2.add(out.lowerBound, out.lowerBound, position); | |
vec2.add(out.upperBound, out.upperBound, position); | |
}; | |
var intersectCapsule_hitPointWorld = vec2.create(); | |
var intersectCapsule_normal = vec2.create(); | |
var intersectCapsule_l0 = vec2.create(); | |
var intersectCapsule_l1 = vec2.create(); | |
var intersectCapsule_unit_y = vec2.fromValues(0,1); | |
/** | |
* @method raycast | |
* @param {RaycastResult} result | |
* @param {Ray} ray | |
* @param {array} position | |
* @param {number} angle | |
*/ | |
Capsule.prototype.raycast = function(result, ray, position, angle){ | |
var from = ray.from; | |
var to = ray.to; | |
var direction = ray.direction; | |
var hitPointWorld = intersectCapsule_hitPointWorld; | |
var normal = intersectCapsule_normal; | |
var l0 = intersectCapsule_l0; | |
var l1 = intersectCapsule_l1; | |
// The sides | |
var halfLen = this.length / 2; | |
for(var i=0; i<2; i++){ | |
// get start and end of the line | |
var y = this.radius * (i*2-1); | |
vec2.set(l0, -halfLen, y); | |
vec2.set(l1, halfLen, y); | |
vec2.toGlobalFrame(l0, l0, position, angle); | |
vec2.toGlobalFrame(l1, l1, position, angle); | |
var delta = vec2.getLineSegmentsIntersectionFraction(from, to, l0, l1); | |
if(delta >= 0){ | |
vec2.rotate(normal, intersectCapsule_unit_y, angle); | |
vec2.scale(normal, normal, (i*2-1)); | |
ray.reportIntersection(result, delta, normal, -1); | |
if(result.shouldStop(ray)){ | |
return; | |
} | |
} | |
} | |
// Circles | |
var diagonalLengthSquared = Math.pow(this.radius, 2) + Math.pow(halfLen, 2); | |
for(var i=0; i<2; i++){ | |
vec2.set(l0, halfLen * (i*2-1), 0); | |
vec2.toGlobalFrame(l0, l0, position, angle); | |
var a = Math.pow(to[0] - from[0], 2) + Math.pow(to[1] - from[1], 2); | |
var b = 2 * ((to[0] - from[0]) * (from[0] - l0[0]) + (to[1] - from[1]) * (from[1] - l0[1])); | |
var c = Math.pow(from[0] - l0[0], 2) + Math.pow(from[1] - l0[1], 2) - Math.pow(this.radius, 2); | |
var delta = Math.pow(b, 2) - 4 * a * c; | |
if(delta < 0){ | |
// No intersection | |
continue; | |
} else if(delta === 0){ | |
// single intersection point | |
vec2.lerp(hitPointWorld, from, to, delta); | |
if(vec2.squaredDistance(hitPointWorld, position) > diagonalLengthSquared){ | |
vec2.sub(normal, hitPointWorld, l0); | |
vec2.normalize(normal,normal); | |
ray.reportIntersection(result, delta, normal, -1); | |
if(result.shouldStop(ray)){ | |
return; | |
} | |
} | |
} else { | |
var sqrtDelta = Math.sqrt(delta); | |
var inv2a = 1 / (2 * a); | |
var d1 = (- b - sqrtDelta) * inv2a; | |
var d2 = (- b + sqrtDelta) * inv2a; | |
if(d1 >= 0 && d1 <= 1){ | |
vec2.lerp(hitPointWorld, from, to, d1); | |
if(vec2.squaredDistance(hitPointWorld, position) > diagonalLengthSquared){ | |
vec2.sub(normal, hitPointWorld, l0); | |
vec2.normalize(normal,normal); | |
ray.reportIntersection(result, d1, normal, -1); | |
if(result.shouldStop(ray)){ | |
return; | |
} | |
} | |
} | |
if(d2 >= 0 && d2 <= 1){ | |
vec2.lerp(hitPointWorld, from, to, d2); | |
if(vec2.squaredDistance(hitPointWorld, position) > diagonalLengthSquared){ | |
vec2.sub(normal, hitPointWorld, l0); | |
vec2.normalize(normal,normal); | |
ray.reportIntersection(result, d2, normal, -1); | |
if(result.shouldStop(ray)){ | |
return; | |
} | |
} | |
} | |
} | |
} | |
}; | |
},{"../math/vec2":30,"./Shape":45}],39:[function(_dereq_,module,exports){ | |
var Shape = _dereq_('./Shape') | |
, vec2 = _dereq_('../math/vec2'); | |
module.exports = Circle; | |
/** | |
* Circle shape class. | |
* @class Circle | |
* @extends Shape | |
* @constructor | |
* @param {options} [options] (Note that this options object will be passed on to the {{#crossLink "Shape"}}{{/crossLink}} constructor.) | |
* @param {number} [options.radius=1] The radius of this circle | |
* | |
* @example | |
* var circleShape = new Circle({ radius: 1 }); | |
* body.addShape(circleShape); | |
*/ | |
function Circle(options){ | |
if(typeof(arguments[0]) === 'number'){ | |
options = { | |
radius: arguments[0] | |
}; | |
console.warn('The Circle constructor signature has changed. Please use the following format: new Circle({ radius: 1 })'); | |
} | |
options = options || {}; | |
/** | |
* The radius of the circle. | |
* @property radius | |
* @type {number} | |
*/ | |
this.radius = options.radius || 1; | |
options.type = Shape.CIRCLE; | |
Shape.call(this, options); | |
} | |
Circle.prototype = new Shape(); | |
Circle.prototype.constructor = Circle; | |
/** | |
* @method computeMomentOfInertia | |
* @param {Number} mass | |
* @return {Number} | |
*/ | |
Circle.prototype.computeMomentOfInertia = function(mass){ | |
var r = this.radius; | |
return mass * r * r / 2; | |
}; | |
/** | |
* @method updateBoundingRadius | |
* @return {Number} | |
*/ | |
Circle.prototype.updateBoundingRadius = function(){ | |
this.boundingRadius = this.radius; | |
}; | |
/** | |
* @method updateArea | |
* @return {Number} | |
*/ | |
Circle.prototype.updateArea = function(){ | |
this.area = Math.PI * this.radius * this.radius; | |
}; | |
/** | |
* @method computeAABB | |
* @param {AABB} out The resulting AABB. | |
* @param {Array} position | |
* @param {Number} angle | |
*/ | |
Circle.prototype.computeAABB = function(out, position, angle){ | |
var r = this.radius; | |
vec2.set(out.upperBound, r, r); | |
vec2.set(out.lowerBound, -r, -r); | |
if(position){ | |
vec2.add(out.lowerBound, out.lowerBound, position); | |
vec2.add(out.upperBound, out.upperBound, position); | |
} | |
}; | |
var Ray_intersectSphere_intersectionPoint = vec2.create(); | |
var Ray_intersectSphere_normal = vec2.create(); | |
/** | |
* @method raycast | |
* @param {RaycastResult} result | |
* @param {Ray} ray | |
* @param {array} position | |
* @param {number} angle | |
*/ | |
Circle.prototype.raycast = function(result, ray, position, angle){ | |
var from = ray.from, | |
to = ray.to, | |
r = this.radius; | |
var a = Math.pow(to[0] - from[0], 2) + Math.pow(to[1] - from[1], 2); | |
var b = 2 * ((to[0] - from[0]) * (from[0] - position[0]) + (to[1] - from[1]) * (from[1] - position[1])); | |
var c = Math.pow(from[0] - position[0], 2) + Math.pow(from[1] - position[1], 2) - Math.pow(r, 2); | |
var delta = Math.pow(b, 2) - 4 * a * c; | |
var intersectionPoint = Ray_intersectSphere_intersectionPoint; | |
var normal = Ray_intersectSphere_normal; | |
if(delta < 0){ | |
// No intersection | |
return; | |
} else if(delta === 0){ | |
// single intersection point | |
vec2.lerp(intersectionPoint, from, to, delta); | |
vec2.sub(normal, intersectionPoint, position); | |
vec2.normalize(normal,normal); | |
ray.reportIntersection(result, delta, normal, -1); | |
} else { | |
var sqrtDelta = Math.sqrt(delta); | |
var inv2a = 1 / (2 * a); | |
var d1 = (- b - sqrtDelta) * inv2a; | |
var d2 = (- b + sqrtDelta) * inv2a; | |
if(d1 >= 0 && d1 <= 1){ | |
vec2.lerp(intersectionPoint, from, to, d1); | |
vec2.sub(normal, intersectionPoint, position); | |
vec2.normalize(normal,normal); | |
ray.reportIntersection(result, d1, normal, -1); | |
if(result.shouldStop(ray)){ | |
return; | |
} | |
} | |
if(d2 >= 0 && d2 <= 1){ | |
vec2.lerp(intersectionPoint, from, to, d2); | |
vec2.sub(normal, intersectionPoint, position); | |
vec2.normalize(normal,normal); | |
ray.reportIntersection(result, d2, normal, -1); | |
} | |
} | |
}; | |
},{"../math/vec2":30,"./Shape":45}],40:[function(_dereq_,module,exports){ | |
var Shape = _dereq_('./Shape') | |
, vec2 = _dereq_('../math/vec2') | |
, polyk = _dereq_('../math/polyk') | |
, decomp = _dereq_('poly-decomp'); | |
module.exports = Convex; | |
/** | |
* Convex shape class. | |
* @class Convex | |
* @constructor | |
* @extends Shape | |
* @param {object} [options] (Note that this options object will be passed on to the {{#crossLink "Shape"}}{{/crossLink}} constructor.) | |
* @param {Array} [options.vertices] An array of vertices that span this shape. Vertices are given in counter-clockwise (CCW) direction. | |
* @param {Array} [options.axes] An array of unit length vectors, representing the symmetry axes in the convex. | |
* @example | |
* // Create a box | |
* var vertices = [[-1,-1], [1,-1], [1,1], [-1,1]]; | |
* var convexShape = new Convex({ vertices: vertices }); | |
* body.addShape(convexShape); | |
*/ | |
function Convex(options){ | |
if(Array.isArray(arguments[0])){ | |
options = { | |
vertices: arguments[0], | |
axes: arguments[1] | |
}; | |
console.warn('The Convex constructor signature has changed. Please use the following format: new Convex({ vertices: [...], ... })'); | |
} | |
options = options || {}; | |
/** | |
* Vertices defined in the local frame. | |
* @property vertices | |
* @type {Array} | |
*/ | |
this.vertices = []; | |
// Copy the verts | |
var vertices = options.vertices !== undefined ? options.vertices : []; | |
for(var i=0; i < vertices.length; i++){ | |
var v = vec2.create(); | |
vec2.copy(v, vertices[i]); | |
this.vertices.push(v); | |
} | |
/** | |
* Axes defined in the local frame. | |
* @property axes | |
* @type {Array} | |
*/ | |
this.axes = []; | |
if(options.axes){ | |
// Copy the axes | |
for(var i=0; i < options.axes.length; i++){ | |
var axis = vec2.create(); | |
vec2.copy(axis, options.axes[i]); | |
this.axes.push(axis); | |
} | |
} else { | |
// Construct axes from the vertex data | |
for(var i = 0; i < this.vertices.length; i++){ | |
// Get the world edge | |
var worldPoint0 = this.vertices[i]; | |
var worldPoint1 = this.vertices[(i+1) % this.vertices.length]; | |
var normal = vec2.create(); | |
vec2.sub(normal, worldPoint1, worldPoint0); | |
// Get normal - just rotate 90 degrees since vertices are given in CCW | |
vec2.rotate90cw(normal, normal); | |
vec2.normalize(normal, normal); | |
this.axes.push(normal); | |
} | |
} | |
/** | |
* The center of mass of the Convex | |
* @property centerOfMass | |
* @type {Array} | |
*/ | |
this.centerOfMass = vec2.fromValues(0,0); | |
/** | |
* Triangulated version of this convex. The structure is Array of 3-Arrays, and each subarray contains 3 integers, referencing the vertices. | |
* @property triangles | |
* @type {Array} | |
*/ | |
this.triangles = []; | |
if(this.vertices.length){ | |
this.updateTriangles(); | |
this.updateCenterOfMass(); | |
} | |
/** | |
* The bounding radius of the convex | |
* @property boundingRadius | |
* @type {Number} | |
*/ | |
this.boundingRadius = 0; | |
options.type = Shape.CONVEX; | |
Shape.call(this, options); | |
this.updateBoundingRadius(); | |
this.updateArea(); | |
if(this.area < 0){ | |
throw new Error("Convex vertices must be given in conter-clockwise winding."); | |
} | |
} | |
Convex.prototype = new Shape(); | |
Convex.prototype.constructor = Convex; | |
var tmpVec1 = vec2.create(); | |
var tmpVec2 = vec2.create(); | |
/** | |
* Project a Convex onto a world-oriented axis | |
* @method projectOntoAxis | |
* @static | |
* @param {Array} offset | |
* @param {Array} localAxis | |
* @param {Array} result | |
*/ | |
Convex.prototype.projectOntoLocalAxis = function(localAxis, result){ | |
var max=null, | |
min=null, | |
v, | |
value, | |
localAxis = tmpVec1; | |
// Get projected position of all vertices | |
for(var i=0; i<this.vertices.length; i++){ | |
v = this.vertices[i]; | |
value = vec2.dot(v, localAxis); | |
if(max === null || value > max){ | |
max = value; | |
} | |
if(min === null || value < min){ | |
min = value; | |
} | |
} | |
if(min > max){ | |
var t = min; | |
min = max; | |
max = t; | |
} | |
vec2.set(result, min, max); | |
}; | |
Convex.prototype.projectOntoWorldAxis = function(localAxis, shapeOffset, shapeAngle, result){ | |
var worldAxis = tmpVec2; | |
this.projectOntoLocalAxis(localAxis, result); | |
// Project the position of the body onto the axis - need to add this to the result | |
if(shapeAngle !== 0){ | |
vec2.rotate(worldAxis, localAxis, shapeAngle); | |
} else { | |
worldAxis = localAxis; | |
} | |
var offset = vec2.dot(shapeOffset, worldAxis); | |
vec2.set(result, result[0] + offset, result[1] + offset); | |
}; | |
/** | |
* Update the .triangles property | |
* @method updateTriangles | |
*/ | |
Convex.prototype.updateTriangles = function(){ | |
this.triangles.length = 0; | |
// Rewrite on polyk notation, array of numbers | |
var polykVerts = []; | |
for(var i=0; i<this.vertices.length; i++){ | |
var v = this.vertices[i]; | |
polykVerts.push(v[0],v[1]); | |
} | |
// Triangulate | |
var triangles = polyk.Triangulate(polykVerts); | |
// Loop over all triangles, add their inertia contributions to I | |
for(var i=0; i<triangles.length; i+=3){ | |
var id1 = triangles[i], | |
id2 = triangles[i+1], | |
id3 = triangles[i+2]; | |
// Add to triangles | |
this.triangles.push([id1,id2,id3]); | |
} | |
}; | |
var updateCenterOfMass_centroid = vec2.create(), | |
updateCenterOfMass_centroid_times_mass = vec2.create(), | |
updateCenterOfMass_a = vec2.create(), | |
updateCenterOfMass_b = vec2.create(), | |
updateCenterOfMass_c = vec2.create(), | |
updateCenterOfMass_ac = vec2.create(), | |
updateCenterOfMass_ca = vec2.create(), | |
updateCenterOfMass_cb = vec2.create(), | |
updateCenterOfMass_n = vec2.create(); | |
/** | |
* Update the .centerOfMass property. | |
* @method updateCenterOfMass | |
*/ | |
Convex.prototype.updateCenterOfMass = function(){ | |
var triangles = this.triangles, | |
verts = this.vertices, | |
cm = this.centerOfMass, | |
centroid = updateCenterOfMass_centroid, | |
n = updateCenterOfMass_n, | |
a = updateCenterOfMass_a, | |
b = updateCenterOfMass_b, | |
c = updateCenterOfMass_c, | |
ac = updateCenterOfMass_ac, | |
ca = updateCenterOfMass_ca, | |
cb = updateCenterOfMass_cb, | |
centroid_times_mass = updateCenterOfMass_centroid_times_mass; | |
vec2.set(cm,0,0); | |
var totalArea = 0; | |
for(var i=0; i!==triangles.length; i++){ | |
var t = triangles[i], | |
a = verts[t[0]], | |
b = verts[t[1]], | |
c = verts[t[2]]; | |
vec2.centroid(centroid,a,b,c); | |
// Get mass for the triangle (density=1 in this case) | |
// http://math.stackexchange.com/questions/80198/area-of-triangle-via-vectors | |
var m = Convex.triangleArea(a,b,c); | |
totalArea += m; | |
// Add to center of mass | |
vec2.scale(centroid_times_mass, centroid, m); | |
vec2.add(cm, cm, centroid_times_mass); | |
} | |
vec2.scale(cm,cm,1/totalArea); | |
}; | |
/** | |
* Compute the mass moment of inertia of the Convex. | |
* @method computeMomentOfInertia | |
* @param {Number} mass | |
* @return {Number} | |
* @see http://www.gamedev.net/topic/342822-moment-of-inertia-of-a-polygon-2d/ | |
*/ | |
Convex.prototype.computeMomentOfInertia = function(mass){ | |
var denom = 0.0, | |
numer = 0.0, | |
N = this.vertices.length; | |
for(var j = N-1, i = 0; i < N; j = i, i ++){ | |
var p0 = this.vertices[j]; | |
var p1 = this.vertices[i]; | |
var a = Math.abs(vec2.crossLength(p0,p1)); | |
var b = vec2.dot(p1,p1) + vec2.dot(p1,p0) + vec2.dot(p0,p0); | |
denom += a * b; | |
numer += a; | |
} | |
return (mass / 6.0) * (denom / numer); | |
}; | |
/** | |
* Updates the .boundingRadius property | |
* @method updateBoundingRadius | |
*/ | |
Convex.prototype.updateBoundingRadius = function(){ | |
var verts = this.vertices, | |
r2 = 0; | |
for(var i=0; i!==verts.length; i++){ | |
var l2 = vec2.squaredLength(verts[i]); | |
if(l2 > r2){ | |
r2 = l2; | |
} | |
} | |
this.boundingRadius = Math.sqrt(r2); | |
}; | |
/** | |
* Get the area of the triangle spanned by the three points a, b, c. The area is positive if the points are given in counter-clockwise order, otherwise negative. | |
* @static | |
* @method triangleArea | |
* @param {Array} a | |
* @param {Array} b | |
* @param {Array} c | |
* @return {Number} | |
*/ | |
Convex.triangleArea = function(a,b,c){ | |
return (((b[0] - a[0])*(c[1] - a[1]))-((c[0] - a[0])*(b[1] - a[1]))) * 0.5; | |
}; | |
/** | |
* Update the .area | |
* @method updateArea | |
*/ | |
Convex.prototype.updateArea = function(){ | |
this.updateTriangles(); | |
this.area = 0; | |
var triangles = this.triangles, | |
verts = this.vertices; | |
for(var i=0; i!==triangles.length; i++){ | |
var t = triangles[i], | |
a = verts[t[0]], | |
b = verts[t[1]], | |
c = verts[t[2]]; | |
// Get mass for the triangle (density=1 in this case) | |
var m = Convex.triangleArea(a,b,c); | |
this.area += m; | |
} | |
}; | |
/** | |
* @method computeAABB | |
* @param {AABB} out | |
* @param {Array} position | |
* @param {Number} angle | |
*/ | |
Convex.prototype.computeAABB = function(out, position, angle){ | |
out.setFromPoints(this.vertices, position, angle, 0); | |
}; | |
var intersectConvex_rayStart = vec2.create(); | |
var intersectConvex_rayEnd = vec2.create(); | |
var intersectConvex_normal = vec2.create(); | |
/** | |
* @method raycast | |
* @param {RaycastResult} result | |
* @param {Ray} ray | |
* @param {array} position | |
* @param {number} angle | |
*/ | |
Convex.prototype.raycast = function(result, ray, position, angle){ | |
var rayStart = intersectConvex_rayStart; | |
var rayEnd = intersectConvex_rayEnd; | |
var normal = intersectConvex_normal; | |
var vertices = this.vertices; | |
// Transform to local shape space | |
vec2.toLocalFrame(rayStart, ray.from, position, angle); | |
vec2.toLocalFrame(rayEnd, ray.to, position, angle); | |
var n = vertices.length; | |
for (var i = 0; i < n && !result.shouldStop(ray); i++) { | |
var q1 = vertices[i]; | |
var q2 = vertices[(i+1) % n]; | |
var delta = vec2.getLineSegmentsIntersectionFraction(rayStart, rayEnd, q1, q2); | |
if(delta >= 0){ | |
vec2.sub(normal, q2, q1); | |
vec2.rotate(normal, normal, -Math.PI / 2 + angle); | |
vec2.normalize(normal, normal); | |
ray.reportIntersection(result, delta, normal, i); | |
} | |
} | |
}; | |
},{"../math/polyk":29,"../math/vec2":30,"./Shape":45,"poly-decomp":5}],41:[function(_dereq_,module,exports){ | |
var Shape = _dereq_('./Shape') | |
, vec2 = _dereq_('../math/vec2') | |
, Utils = _dereq_('../utils/Utils'); | |
module.exports = Heightfield; | |
/** | |
* Heightfield shape class. Height data is given as an array. These data points are spread out evenly with a distance "elementWidth". | |
* @class Heightfield | |
* @extends Shape | |
* @constructor | |
* @param {object} [options] (Note that this options object will be passed on to the {{#crossLink "Shape"}}{{/crossLink}} constructor.) | |
* @param {array} [options.heights] An array of Y values that will be used to construct the terrain. | |
* @param {Number} [options.minValue] Minimum value of the data points in the data array. Will be computed automatically if not given. | |
* @param {Number} [options.maxValue] Maximum value. | |
* @param {Number} [options.elementWidth=0.1] World spacing between the data points in X direction. | |
* | |
* @example | |
* // Generate some height data (y-values). | |
* var heights = []; | |
* for(var i = 0; i < 1000; i++){ | |
* var y = 0.5 * Math.cos(0.2 * i); | |
* heights.push(y); | |
* } | |
* | |
* // Create the heightfield shape | |
* var heightfieldShape = new Heightfield({ | |
* heights: heights, | |
* elementWidth: 1 // Distance between the data points in X direction | |
* }); | |
* var heightfieldBody = new Body(); | |
* heightfieldBody.addShape(heightfieldShape); | |
* world.addBody(heightfieldBody); | |
* | |
* @todo Should use a scale property with X and Y direction instead of just elementWidth | |
*/ | |
function Heightfield(options){ | |
if(Array.isArray(arguments[0])){ | |
options = { | |
heights: arguments[0] | |
}; | |
if(typeof(arguments[1]) === 'object'){ | |
for(var key in arguments[1]){ | |
options[key] = arguments[1][key]; | |
} | |
} | |
console.warn('The Heightfield constructor signature has changed. Please use the following format: new Heightfield({ heights: [...], ... })'); | |
} | |
options = options || {}; | |
/** | |
* An array of numbers, or height values, that are spread out along the x axis. | |
* @property {array} heights | |
*/ | |
this.heights = options.heights ? options.heights.slice(0) : []; | |
/** | |
* Max value of the heights | |
* @property {number} maxValue | |
*/ | |
this.maxValue = options.maxValue || null; | |
/** | |
* Max value of the heights | |
* @property {number} minValue | |
*/ | |
this.minValue = options.minValue || null; | |
/** | |
* The width of each element | |
* @property {number} elementWidth | |
*/ | |
this.elementWidth = options.elementWidth || 0.1; | |
if(options.maxValue === undefined || options.minValue === undefined){ | |
this.updateMaxMinValues(); | |
} | |
options.type = Shape.HEIGHTFIELD; | |
Shape.call(this, options); | |
} | |
Heightfield.prototype = new Shape(); | |
Heightfield.prototype.constructor = Heightfield; | |
/** | |
* Update the .minValue and the .maxValue | |
* @method updateMaxMinValues | |
*/ | |
Heightfield.prototype.updateMaxMinValues = function(){ | |
var data = this.heights; | |
var maxValue = data[0]; | |
var minValue = data[0]; | |
for(var i=0; i !== data.length; i++){ | |
var v = data[i]; | |
if(v > maxValue){ | |
maxValue = v; | |
} | |
if(v < minValue){ | |
minValue = v; | |
} | |
} | |
this.maxValue = maxValue; | |
this.minValue = minValue; | |
}; | |
/** | |
* @method computeMomentOfInertia | |
* @param {Number} mass | |
* @return {Number} | |
*/ | |
Heightfield.prototype.computeMomentOfInertia = function(mass){ | |
return Number.MAX_VALUE; | |
}; | |
Heightfield.prototype.updateBoundingRadius = function(){ | |
this.boundingRadius = Number.MAX_VALUE; | |
}; | |
Heightfield.prototype.updateArea = function(){ | |
var data = this.heights, | |
area = 0; | |
for(var i=0; i<data.length-1; i++){ | |
area += (data[i]+data[i+1]) / 2 * this.elementWidth; | |
} | |
this.area = area; | |
}; | |
var points = [ | |
vec2.create(), | |
vec2.create(), | |
vec2.create(), | |
vec2.create() | |
]; | |
/** | |
* @method computeAABB | |
* @param {AABB} out The resulting AABB. | |
* @param {Array} position | |
* @param {Number} angle | |
*/ | |
Heightfield.prototype.computeAABB = function(out, position, angle){ | |
vec2.set(points[0], 0, this.maxValue); | |
vec2.set(points[1], this.elementWidth * this.heights.length, this.maxValue); | |
vec2.set(points[2], this.elementWidth * this.heights.length, this.minValue); | |
vec2.set(points[3], 0, this.minValue); | |
out.setFromPoints(points, position, angle); | |
}; | |
/** | |
* Get a line segment in the heightfield | |
* @method getLineSegment | |
* @param {array} start Where to store the resulting start point | |
* @param {array} end Where to store the resulting end point | |
* @param {number} i | |
*/ | |
Heightfield.prototype.getLineSegment = function(start, end, i){ | |
var data = this.heights; | |
var width = this.elementWidth; | |
vec2.set(start, i * width, data[i]); | |
vec2.set(end, (i + 1) * width, data[i + 1]); | |
}; | |
Heightfield.prototype.getSegmentIndex = function(position){ | |
return Math.floor(position[0] / this.elementWidth); | |
}; | |
Heightfield.prototype.getClampedSegmentIndex = function(position){ | |
var i = this.getSegmentIndex(position); | |
i = Math.min(this.heights.length, Math.max(i, 0)); // clamp | |
return i; | |
}; | |
var intersectHeightfield_hitPointWorld = vec2.create(); | |
var intersectHeightfield_worldNormal = vec2.create(); | |
var intersectHeightfield_l0 = vec2.create(); | |
var intersectHeightfield_l1 = vec2.create(); | |
var intersectHeightfield_localFrom = vec2.create(); | |
var intersectHeightfield_localTo = vec2.create(); | |
var intersectHeightfield_unit_y = vec2.fromValues(0,1); | |
// Returns 1 if the lines intersect, otherwise 0. | |
function getLineSegmentsIntersection (out, p0, p1, p2, p3) { | |
var s1_x, s1_y, s2_x, s2_y; | |
s1_x = p1[0] - p0[0]; | |
s1_y = p1[1] - p0[1]; | |
s2_x = p3[0] - p2[0]; | |
s2_y = p3[1] - p2[1]; | |
var s, t; | |
s = (-s1_y * (p0[0] - p2[0]) + s1_x * (p0[1] - p2[1])) / (-s2_x * s1_y + s1_x * s2_y); | |
t = ( s2_x * (p0[1] - p2[1]) - s2_y * (p0[0] - p2[0])) / (-s2_x * s1_y + s1_x * s2_y); | |
if (s >= 0 && s <= 1 && t >= 0 && t <= 1) { // Collision detected | |
var intX = p0[0] + (t * s1_x); | |
var intY = p0[1] + (t * s1_y); | |
out[0] = intX; | |
out[1] = intY; | |
return t; | |
} | |
return -1; // No collision | |
} | |
/** | |
* @method raycast | |
* @param {RayResult} result | |
* @param {Ray} ray | |
* @param {array} position | |
* @param {number} angle | |
*/ | |
Heightfield.prototype.raycast = function(result, ray, position, angle){ | |
var from = ray.from; | |
var to = ray.to; | |
var direction = ray.direction; | |
var hitPointWorld = intersectHeightfield_hitPointWorld; | |
var worldNormal = intersectHeightfield_worldNormal; | |
var l0 = intersectHeightfield_l0; | |
var l1 = intersectHeightfield_l1; | |
var localFrom = intersectHeightfield_localFrom; | |
var localTo = intersectHeightfield_localTo; | |
// get local ray start and end | |
vec2.toLocalFrame(localFrom, from, position, angle); | |
vec2.toLocalFrame(localTo, to, position, angle); | |
// Get the segment range | |
var i0 = this.getClampedSegmentIndex(localFrom); | |
var i1 = this.getClampedSegmentIndex(localTo); | |
if(i0 > i1){ | |
var tmp = i0; | |
i0 = i1; | |
i1 = tmp; | |
} | |
// The segments | |
for(var i=0; i<this.heights.length - 1; i++){ | |
this.getLineSegment(l0, l1, i); | |
var t = vec2.getLineSegmentsIntersectionFraction(localFrom, localTo, l0, l1); | |
if(t >= 0){ | |
vec2.sub(worldNormal, l1, l0); | |
vec2.rotate(worldNormal, worldNormal, angle + Math.PI / 2); | |
vec2.normalize(worldNormal, worldNormal); | |
ray.reportIntersection(result, t, worldNormal, -1); | |
if(result.shouldStop(ray)){ | |
return; | |
} | |
} | |
} | |
}; | |
},{"../math/vec2":30,"../utils/Utils":57,"./Shape":45}],42:[function(_dereq_,module,exports){ | |
var Shape = _dereq_('./Shape') | |
, vec2 = _dereq_('../math/vec2'); | |
module.exports = Line; | |
/** | |
* Line shape class. The line shape is along the x direction, and stretches from [-length/2, 0] to [length/2,0]. | |
* @class Line | |
* @param {object} [options] (Note that this options object will be passed on to the {{#crossLink "Shape"}}{{/crossLink}} constructor.) | |
* @param {Number} [options.length=1] The total length of the line | |
* @extends Shape | |
* @constructor | |
*/ | |
function Line(options){ | |
if(typeof(arguments[0]) === 'number'){ | |
options = { | |
length: arguments[0] | |
}; | |
console.warn('The Line constructor signature has changed. Please use the following format: new Line({ length: 1, ... })'); | |
} | |
options = options || {}; | |
/** | |
* Length of this line | |
* @property {Number} length | |
* @default 1 | |
*/ | |
this.length = options.length || 1; | |
options.type = Shape.LINE; | |
Shape.call(this, options); | |
} | |
Line.prototype = new Shape(); | |
Line.prototype.constructor = Line; | |
Line.prototype.computeMomentOfInertia = function(mass){ | |
return mass * Math.pow(this.length,2) / 12; | |
}; | |
Line.prototype.updateBoundingRadius = function(){ | |
this.boundingRadius = this.length/2; | |
}; | |
var points = [vec2.create(),vec2.create()]; | |
/** | |
* @method computeAABB | |
* @param {AABB} out The resulting AABB. | |
* @param {Array} position | |
* @param {Number} angle | |
*/ | |
Line.prototype.computeAABB = function(out, position, angle){ | |
var l2 = this.length / 2; | |
vec2.set(points[0], -l2, 0); | |
vec2.set(points[1], l2, 0); | |
out.setFromPoints(points,position,angle,0); | |
}; | |
var raycast_hitPoint = vec2.create(); | |
var raycast_normal = vec2.create(); | |
var raycast_l0 = vec2.create(); | |
var raycast_l1 = vec2.create(); | |
var raycast_unit_y = vec2.fromValues(0,1); | |
/** | |
* @method raycast | |
* @param {RaycastResult} result | |
* @param {Ray} ray | |
* @param {number} angle | |
* @param {array} position | |
*/ | |
Line.prototype.raycast = function(result, ray, position, angle){ | |
var from = ray.from; | |
var to = ray.to; | |
var l0 = raycast_l0; | |
var l1 = raycast_l1; | |
// get start and end of the line | |
var halfLen = this.length / 2; | |
vec2.set(l0, -halfLen, 0); | |
vec2.set(l1, halfLen, 0); | |
vec2.toGlobalFrame(l0, l0, position, angle); | |
vec2.toGlobalFrame(l1, l1, position, angle); | |
var fraction = vec2.getLineSegmentsIntersectionFraction(l0, l1, from, to); | |
if(fraction >= 0){ | |
var normal = raycast_normal; | |
vec2.rotate(normal, raycast_unit_y, angle); // todo: this should depend on which side the ray comes from | |
ray.reportIntersection(result, fraction, normal, -1); | |
} | |
}; | |
},{"../math/vec2":30,"./Shape":45}],43:[function(_dereq_,module,exports){ | |
var Shape = _dereq_('./Shape') | |
, vec2 = _dereq_('../math/vec2'); | |
module.exports = Particle; | |
/** | |
* Particle shape class. | |
* @class Particle | |
* @constructor | |
* @param {object} [options] (Note that this options object will be passed on to the {{#crossLink "Shape"}}{{/crossLink}} constructor.) | |
* @extends Shape | |
*/ | |
function Particle(options){ | |
options = options || {}; | |
options.type = Shape.PARTICLE; | |
Shape.call(this, options); | |
} | |
Particle.prototype = new Shape(); | |
Particle.prototype.constructor = Particle; | |
Particle.prototype.computeMomentOfInertia = function(mass){ | |
return 0; // Can't rotate a particle | |
}; | |
Particle.prototype.updateBoundingRadius = function(){ | |
this.boundingRadius = 0; | |
}; | |
/** | |
* @method computeAABB | |
* @param {AABB} out | |
* @param {Array} position | |
* @param {Number} angle | |
*/ | |
Particle.prototype.computeAABB = function(out, position, angle){ | |
vec2.copy(out.lowerBound, position); | |
vec2.copy(out.upperBound, position); | |
}; | |
},{"../math/vec2":30,"./Shape":45}],44:[function(_dereq_,module,exports){ | |
var Shape = _dereq_('./Shape') | |
, vec2 = _dereq_('../math/vec2') | |
, Utils = _dereq_('../utils/Utils'); | |
module.exports = Plane; | |
/** | |
* Plane shape class. The plane is facing in the Y direction. | |
* @class Plane | |
* @extends Shape | |
* @constructor | |
* @param {object} [options] (Note that this options object will be passed on to the {{#crossLink "Shape"}}{{/crossLink}} constructor.) | |
*/ | |
function Plane(options){ | |
options = options || {}; | |
options.type = Shape.PLANE; | |
Shape.call(this, options); | |
} | |
Plane.prototype = new Shape(); | |
Plane.prototype.constructor = Plane; | |
/** | |
* Compute moment of inertia | |
* @method computeMomentOfInertia | |
*/ | |
Plane.prototype.computeMomentOfInertia = function(mass){ | |
return 0; // Plane is infinite. The inertia should therefore be infinty but by convention we set 0 here | |
}; | |
/** | |
* Update the bounding radius | |
* @method updateBoundingRadius | |
*/ | |
Plane.prototype.updateBoundingRadius = function(){ | |
this.boundingRadius = Number.MAX_VALUE; | |
}; | |
/** | |
* @method computeAABB | |
* @param {AABB} out | |
* @param {Array} position | |
* @param {Number} angle | |
*/ | |
Plane.prototype.computeAABB = function(out, position, angle){ | |
var a = angle % (2 * Math.PI); | |
var set = vec2.set; | |
var max = Number.MAX_VALUE; | |
var lowerBound = out.lowerBound; | |
var upperBound = out.upperBound; | |
if(a === 0){ | |
// y goes from -inf to 0 | |
set(lowerBound, -max, -max); | |
set(upperBound, max, 0); | |
} else if(a === Math.PI / 2){ | |
// x goes from 0 to inf | |
set(lowerBound, 0, -max); | |
set(upperBound, max, max); | |
} else if(a === Math.PI){ | |
// y goes from 0 to inf | |
set(lowerBound, -max, 0); | |
set(upperBound, max, max); | |
} else if(a === 3*Math.PI/2){ | |
// x goes from -inf to 0 | |
set(lowerBound, -max, -max); | |
set(upperBound, 0, max); | |
} else { | |
// Set max bounds | |
set(lowerBound, -max, -max); | |
set(upperBound, max, max); | |
} | |
vec2.add(lowerBound, lowerBound, position); | |
vec2.add(upperBound, upperBound, position); | |
}; | |
Plane.prototype.updateArea = function(){ | |
this.area = Number.MAX_VALUE; | |
}; | |
var intersectPlane_planePointToFrom = vec2.create(); | |
var intersectPlane_dir_scaled_with_t = vec2.create(); | |
var intersectPlane_hitPoint = vec2.create(); | |
var intersectPlane_normal = vec2.create(); | |
var intersectPlane_len = vec2.create(); | |
/** | |
* @method raycast | |
* @param {RayResult} result | |
* @param {Ray} ray | |
* @param {array} position | |
* @param {number} angle | |
*/ | |
Plane.prototype.raycast = function(result, ray, position, angle){ | |
var from = ray.from; | |
var to = ray.to; | |
var direction = ray.direction; | |
var planePointToFrom = intersectPlane_planePointToFrom; | |
var dir_scaled_with_t = intersectPlane_dir_scaled_with_t; | |
var hitPoint = intersectPlane_hitPoint; | |
var normal = intersectPlane_normal; | |
var len = intersectPlane_len; | |
// Get plane normal | |
vec2.set(normal, 0, 1); | |
vec2.rotate(normal, normal, angle); | |
vec2.sub(len, from, position); | |
var planeToFrom = vec2.dot(len, normal); | |
vec2.sub(len, to, position); | |
var planeToTo = vec2.dot(len, normal); | |
if(planeToFrom * planeToTo > 0){ | |
// "from" and "to" are on the same side of the plane... bail out | |
return; | |
} | |
if(vec2.squaredDistance(from, to) < planeToFrom * planeToFrom){ | |
return; | |
} | |
var n_dot_dir = vec2.dot(normal, direction); | |
vec2.sub(planePointToFrom, from, position); | |
var t = -vec2.dot(normal, planePointToFrom) / n_dot_dir / ray.length; | |
ray.reportIntersection(result, t, normal, -1); | |
}; | |
},{"../math/vec2":30,"../utils/Utils":57,"./Shape":45}],45:[function(_dereq_,module,exports){ | |
module.exports = Shape; | |
var vec2 = _dereq_('../math/vec2'); | |
/** | |
* Base class for shapes. | |
* @class Shape | |
* @constructor | |
* @param {object} [options] | |
* @param {array} [options.position] | |
* @param {number} [options.angle=0] | |
* @param {number} [options.collisionGroup=1] | |
* @param {number} [options.collisionMask=1] | |
* @param {boolean} [options.sensor=false] | |
* @param {boolean} [options.collisionResponse=true] | |
* @param {object} [options.type=0] | |
*/ | |
function Shape(options){ | |
options = options || {}; | |
/** | |
* The body this shape is attached to. A shape can only be attached to a single body. | |
* @property {Body} body | |
*/ | |
this.body = null; | |
/** | |
* Body-local position of the shape. | |
* @property {Array} position | |
*/ | |
this.position = vec2.fromValues(0,0); | |
if(options.position){ | |
vec2.copy(this.position, options.position); | |
} | |
/** | |
* Body-local angle of the shape. | |
* @property {number} angle | |
*/ | |
this.angle = options.angle || 0; | |
/** | |
* The type of the shape. One of: | |
* | |
* * {{#crossLink "Shape/CIRCLE:property"}}Shape.CIRCLE{{/crossLink}} | |
* * {{#crossLink "Shape/PARTICLE:property"}}Shape.PARTICLE{{/crossLink}} | |
* * {{#crossLink "Shape/PLANE:property"}}Shape.PLANE{{/crossLink}} | |
* * {{#crossLink "Shape/CONVEX:property"}}Shape.CONVEX{{/crossLink}} | |
* * {{#crossLink "Shape/LINE:property"}}Shape.LINE{{/crossLink}} | |
* * {{#crossLink "Shape/BOX:property"}}Shape.BOX{{/crossLink}} | |
* * {{#crossLink "Shape/CAPSULE:property"}}Shape.CAPSULE{{/crossLink}} | |
* * {{#crossLink "Shape/HEIGHTFIELD:property"}}Shape.HEIGHTFIELD{{/crossLink}} | |
* | |
* @property {number} type | |
*/ | |
this.type = options.type || 0; | |
/** | |
* Shape object identifier. | |
* @type {Number} | |
* @property id | |
*/ | |
this.id = Shape.idCounter++; | |
/** | |
* Bounding circle radius of this shape | |
* @property boundingRadius | |
* @type {Number} | |
*/ | |
this.boundingRadius = 0; | |
/** | |
* Collision group that this shape belongs to (bit mask). See <a href="http://www.aurelienribon.com/blog/2011/07/box2d-tutorial-collision-filtering/">this tutorial</a>. | |
* @property collisionGroup | |
* @type {Number} | |
* @example | |
* // Setup bits for each available group | |
* var PLAYER = Math.pow(2,0), | |
* ENEMY = Math.pow(2,1), | |
* GROUND = Math.pow(2,2) | |
* | |
* // Put shapes into their groups | |
* player1Shape.collisionGroup = PLAYER; | |
* player2Shape.collisionGroup = PLAYER; | |
* enemyShape .collisionGroup = ENEMY; | |
* groundShape .collisionGroup = GROUND; | |
* | |
* // Assign groups that each shape collide with. | |
* // Note that the players can collide with ground and enemies, but not with other players. | |
* player1Shape.collisionMask = ENEMY | GROUND; | |
* player2Shape.collisionMask = ENEMY | GROUND; | |
* enemyShape .collisionMask = PLAYER | GROUND; | |
* groundShape .collisionMask = PLAYER | ENEMY; | |
* | |
* @example | |
* // How collision check is done | |
* if(shapeA.collisionGroup & shapeB.collisionMask)!=0 && (shapeB.collisionGroup & shapeA.collisionMask)!=0){ | |
* // The shapes will collide | |
* } | |
*/ | |
this.collisionGroup = options.collisionGroup !== undefined ? options.collisionGroup : 1; | |
/** | |
* Whether to produce contact forces when in contact with other bodies. Note that contacts will be generated, but they will be disabled. That means that this shape will move through other body shapes, but it will still trigger contact events, etc. | |
* @property {Boolean} collisionResponse | |
*/ | |
this.collisionResponse = options.collisionResponse !== undefined ? options.collisionResponse : true; | |
/** | |
* Collision mask of this shape. See .collisionGroup. | |
* @property collisionMask | |
* @type {Number} | |
*/ | |
this.collisionMask = options.collisionMask !== undefined ? options.collisionMask : 1; | |
/** | |
* Material to use in collisions for this Shape. If this is set to null, the world will use default material properties instead. | |
* @property material | |
* @type {Material} | |
*/ | |
this.material = options.material || null; | |
/** | |
* Area of this shape. | |
* @property area | |
* @type {Number} | |
*/ | |
this.area = 0; | |
/** | |
* Set to true if you want this shape to be a sensor. A sensor does not generate contacts, but it still reports contact events. This is good if you want to know if a shape is overlapping another shape, without them generating contacts. | |
* @property {Boolean} sensor | |
*/ | |
this.sensor = options.sensor !== undefined ? options.sensor : false; | |
if(this.type){ | |
this.updateBoundingRadius(); | |
} | |
this.updateArea(); | |
} | |
Shape.idCounter = 0; | |
/** | |
* @static | |
* @property {Number} CIRCLE | |
*/ | |
Shape.CIRCLE = 1; | |
/** | |
* @static | |
* @property {Number} PARTICLE | |
*/ | |
Shape.PARTICLE = 2; | |
/** | |
* @static | |
* @property {Number} PLANE | |
*/ | |
Shape.PLANE = 4; | |
/** | |
* @static | |
* @property {Number} CONVEX | |
*/ | |
Shape.CONVEX = 8; | |
/** | |
* @static | |
* @property {Number} LINE | |
*/ | |
Shape.LINE = 16; | |
/** | |
* @static | |
* @property {Number} BOX | |
*/ | |
Shape.BOX = 32; | |
Object.defineProperty(Shape, 'RECTANGLE', { | |
get: function() { | |
console.warn('Shape.RECTANGLE is deprecated, use Shape.BOX instead.'); | |
return Shape.BOX; | |
} | |
}); | |
/** | |
* @static | |
* @property {Number} CAPSULE | |
*/ | |
Shape.CAPSULE = 64; | |
/** | |
* @static | |
* @property {Number} HEIGHTFIELD | |
*/ | |
Shape.HEIGHTFIELD = 128; | |
/** | |
* Should return the moment of inertia around the Z axis of the body given the total mass. See <a href="http://en.wikipedia.org/wiki/List_of_moments_of_inertia">Wikipedia's list of moments of inertia</a>. | |
* @method computeMomentOfInertia | |
* @param {Number} mass | |
* @return {Number} If the inertia is infinity or if the object simply isn't possible to rotate, return 0. | |
*/ | |
Shape.prototype.computeMomentOfInertia = function(mass){}; | |
/** | |
* Returns the bounding circle radius of this shape. | |
* @method updateBoundingRadius | |
* @return {Number} | |
*/ | |
Shape.prototype.updateBoundingRadius = function(){}; | |
/** | |
* Update the .area property of the shape. | |
* @method updateArea | |
*/ | |
Shape.prototype.updateArea = function(){ | |
// To be implemented in all subclasses | |
}; | |
/** | |
* Compute the world axis-aligned bounding box (AABB) of this shape. | |
* @method computeAABB | |
* @param {AABB} out The resulting AABB. | |
* @param {Array} position World position of the shape. | |
* @param {Number} angle World angle of the shape. | |
*/ | |
Shape.prototype.computeAABB = function(out, position, angle){ | |
// To be implemented in each subclass | |
}; | |
/** | |
* Perform raycasting on this shape. | |
* @method raycast | |
* @param {RayResult} result Where to store the resulting data. | |
* @param {Ray} ray The Ray that you want to use for raycasting. | |
* @param {array} position World position of the shape (the .position property will be ignored). | |
* @param {number} angle World angle of the shape (the .angle property will be ignored). | |
*/ | |
Shape.prototype.raycast = function(result, ray, position, angle){ | |
// To be implemented in each subclass | |
}; | |
},{"../math/vec2":30}],46:[function(_dereq_,module,exports){ | |
var vec2 = _dereq_('../math/vec2') | |
, Solver = _dereq_('./Solver') | |
, Utils = _dereq_('../utils/Utils') | |
, FrictionEquation = _dereq_('../equations/FrictionEquation'); | |
module.exports = GSSolver; | |
/** | |
* Iterative Gauss-Seidel constraint equation solver. | |
* | |
* @class GSSolver | |
* @constructor | |
* @extends Solver | |
* @param {Object} [options] | |
* @param {Number} [options.iterations=10] | |
* @param {Number} [options.tolerance=0] | |
*/ | |
function GSSolver(options){ | |
Solver.call(this,options,Solver.GS); | |
options = options || {}; | |
/** | |
* The max number of iterations to do when solving. More gives better results, but is more expensive. | |
* @property iterations | |
* @type {Number} | |
*/ | |
this.iterations = options.iterations || 10; | |
/** | |
* The error tolerance, per constraint. If the total error is below this limit, the solver will stop iterating. Set to zero for as good solution as possible, but to something larger than zero to make computations faster. | |
* @property tolerance | |
* @type {Number} | |
* @default 1e-7 | |
*/ | |
this.tolerance = options.tolerance || 1e-7; | |
this.arrayStep = 30; | |
this.lambda = new Utils.ARRAY_TYPE(this.arrayStep); | |
this.Bs = new Utils.ARRAY_TYPE(this.arrayStep); | |
this.invCs = new Utils.ARRAY_TYPE(this.arrayStep); | |
/** | |
* Set to true to set all right hand side terms to zero when solving. Can be handy for a few applications. | |
* @property useZeroRHS | |
* @type {Boolean} | |
*/ | |
this.useZeroRHS = false; | |
/** | |
* Number of solver iterations that are done to approximate normal forces. When these iterations are done, friction force will be computed from the contact normal forces. These friction forces will override any other friction forces set from the World for example. | |
* The solver will use less iterations if the solution is below the .tolerance. | |
* @property frictionIterations | |
* @type {Number} | |
*/ | |
this.frictionIterations = 0; | |
/** | |
* The number of iterations that were made during the last solve. If .tolerance is zero, this value will always be equal to .iterations, but if .tolerance is larger than zero, and the solver can quit early, then this number will be somewhere between 1 and .iterations. | |
* @property {Number} usedIterations | |
*/ | |
this.usedIterations = 0; | |
} | |
GSSolver.prototype = new Solver(); | |
GSSolver.prototype.constructor = GSSolver; | |
function setArrayZero(array){ | |
var l = array.length; | |
while(l--){ | |
array[l] = +0.0; | |
} | |
} | |
/** | |
* Solve the system of equations | |
* @method solve | |
* @param {Number} h Time step | |
* @param {World} world World to solve | |
*/ | |
GSSolver.prototype.solve = function(h, world){ | |
this.sortEquations(); | |
var iter = 0, | |
maxIter = this.iterations, | |
maxFrictionIter = this.frictionIterations, | |
equations = this.equations, | |
Neq = equations.length, | |
tolSquared = Math.pow(this.tolerance*Neq, 2), | |
bodies = world.bodies, | |
Nbodies = world.bodies.length, | |
add = vec2.add, | |
set = vec2.set, | |
useZeroRHS = this.useZeroRHS, | |
lambda = this.lambda; | |
this.usedIterations = 0; | |
if(Neq){ | |
for(var i=0; i!==Nbodies; i++){ | |
var b = bodies[i]; | |
// Update solve mass | |
b.updateSolveMassProperties(); | |
} | |
} | |
// Things that does not change during iteration can be computed once | |
if(lambda.length < Neq){ | |
lambda = this.lambda = new Utils.ARRAY_TYPE(Neq + this.arrayStep); | |
this.Bs = new Utils.ARRAY_TYPE(Neq + this.arrayStep); | |
this.invCs = new Utils.ARRAY_TYPE(Neq + this.arrayStep); | |
} | |
setArrayZero(lambda); | |
var invCs = this.invCs, | |
Bs = this.Bs, | |
lambda = this.lambda; | |
for(var i=0; i!==equations.length; i++){ | |
var c = equations[i]; | |
if(c.timeStep !== h || c.needsUpdate){ | |
c.timeStep = h; | |
c.update(); | |
} | |
Bs[i] = c.computeB(c.a,c.b,h); | |
invCs[i] = c.computeInvC(c.epsilon); | |
} | |
var q, B, c, deltalambdaTot,i,j; | |
if(Neq !== 0){ | |
for(i=0; i!==Nbodies; i++){ | |
var b = bodies[i]; | |
// Reset vlambda | |
b.resetConstraintVelocity(); | |
} | |
if(maxFrictionIter){ | |
// Iterate over contact equations to get normal forces | |
for(iter=0; iter!==maxFrictionIter; iter++){ | |
// Accumulate the total error for each iteration. | |
deltalambdaTot = 0.0; | |
for(j=0; j!==Neq; j++){ | |
c = equations[j]; | |
var deltalambda = GSSolver.iterateEquation(j,c,c.epsilon,Bs,invCs,lambda,useZeroRHS,h,iter); | |
deltalambdaTot += Math.abs(deltalambda); | |
} | |
this.usedIterations++; | |
// If the total error is small enough - stop iterate | |
if(deltalambdaTot*deltalambdaTot <= tolSquared){ | |
break; | |
} | |
} | |
GSSolver.updateMultipliers(equations, lambda, 1/h); | |
// Set computed friction force | |
for(j=0; j!==Neq; j++){ | |
var eq = equations[j]; | |
if(eq instanceof FrictionEquation){ | |
var f = 0.0; | |
for(var k=0; k!==eq.contactEquations.length; k++){ | |
f += eq.contactEquations[k].multiplier; | |
} | |
f *= eq.frictionCoefficient / eq.contactEquations.length; | |
eq.maxForce = f; | |
eq.minForce = -f; | |
} | |
} | |
} | |
// Iterate over all equations | |
for(iter=0; iter!==maxIter; iter++){ | |
// Accumulate the total error for each iteration. | |
deltalambdaTot = 0.0; | |
for(j=0; j!==Neq; j++){ | |
c = equations[j]; | |
var deltalambda = GSSolver.iterateEquation(j,c,c.epsilon,Bs,invCs,lambda,useZeroRHS,h,iter); | |
deltalambdaTot += Math.abs(deltalambda); | |
} | |
this.usedIterations++; | |
// If the total error is small enough - stop iterate | |
if(deltalambdaTot*deltalambdaTot <= tolSquared){ | |
break; | |
} | |
} | |
// Add result to velocity | |
for(i=0; i!==Nbodies; i++){ | |
bodies[i].addConstraintVelocity(); | |
} | |
GSSolver.updateMultipliers(equations, lambda, 1/h); | |
} | |
}; | |
// Sets the .multiplier property of each equation | |
GSSolver.updateMultipliers = function(equations, lambda, invDt){ | |
// Set the .multiplier property of each equation | |
var l = equations.length; | |
while(l--){ | |
equations[l].multiplier = lambda[l] * invDt; | |
} | |
}; | |
GSSolver.iterateEquation = function(j,eq,eps,Bs,invCs,lambda,useZeroRHS,dt,iter){ | |
// Compute iteration | |
var B = Bs[j], | |
invC = invCs[j], | |
lambdaj = lambda[j], | |
GWlambda = eq.computeGWlambda(); | |
var maxForce = eq.maxForce, | |
minForce = eq.minForce; | |
if(useZeroRHS){ | |
B = 0; | |
} | |
var deltalambda = invC * ( B - GWlambda - eps * lambdaj ); | |
// Clamp if we are not within the min/max interval | |
var lambdaj_plus_deltalambda = lambdaj + deltalambda; | |
if(lambdaj_plus_deltalambda < minForce*dt){ | |
deltalambda = minForce*dt - lambdaj; | |
} else if(lambdaj_plus_deltalambda > maxForce*dt){ | |
deltalambda = maxForce*dt - lambdaj; | |
} | |
lambda[j] += deltalambda; | |
eq.addToWlambda(deltalambda); | |
return deltalambda; | |
}; | |
},{"../equations/FrictionEquation":23,"../math/vec2":30,"../utils/Utils":57,"./Solver":47}],47:[function(_dereq_,module,exports){ | |
var Utils = _dereq_('../utils/Utils') | |
, EventEmitter = _dereq_('../events/EventEmitter'); | |
module.exports = Solver; | |
/** | |
* Base class for constraint solvers. | |
* @class Solver | |
* @constructor | |
* @extends EventEmitter | |
*/ | |
function Solver(options,type){ | |
options = options || {}; | |
EventEmitter.call(this); | |
this.type = type; | |
/** | |
* Current equations in the solver. | |
* | |
* @property equations | |
* @type {Array} | |
*/ | |
this.equations = []; | |
/** | |
* Function that is used to sort all equations before each solve. | |
* @property equationSortFunction | |
* @type {function|boolean} | |
*/ | |
this.equationSortFunction = options.equationSortFunction || false; | |
} | |
Solver.prototype = new EventEmitter(); | |
Solver.prototype.constructor = Solver; | |
/** | |
* Method to be implemented in each subclass | |
* @method solve | |
* @param {Number} dt | |
* @param {World} world | |
*/ | |
Solver.prototype.solve = function(dt,world){ | |
throw new Error("Solver.solve should be implemented by subclasses!"); | |
}; | |
var mockWorld = {bodies:[]}; | |
/** | |
* Solves all constraints in an island. | |
* @method solveIsland | |
* @param {Number} dt | |
* @param {Island} island | |
*/ | |
Solver.prototype.solveIsland = function(dt,island){ | |
this.removeAllEquations(); | |
if(island.equations.length){ | |
// Add equations to solver | |
this.addEquations(island.equations); | |
mockWorld.bodies.length = 0; | |
island.getBodies(mockWorld.bodies); | |
// Solve | |
if(mockWorld.bodies.length){ | |
this.solve(dt,mockWorld); | |
} | |
} | |
}; | |
/** | |
* Sort all equations using the .equationSortFunction. Should be called by subclasses before solving. | |
* @method sortEquations | |
*/ | |
Solver.prototype.sortEquations = function(){ | |
if(this.equationSortFunction){ | |
this.equations.sort(this.equationSortFunction); | |
} | |
}; | |
/** | |
* Add an equation to be solved. | |
* | |
* @method addEquation | |
* @param {Equation} eq | |
*/ | |
Solver.prototype.addEquation = function(eq){ | |
if(eq.enabled){ | |
this.equations.push(eq); | |
} | |
}; | |
/** | |
* Add equations. Same as .addEquation, but this time the argument is an array of Equations | |
* | |
* @method addEquations | |
* @param {Array} eqs | |
*/ | |
Solver.prototype.addEquations = function(eqs){ | |
//Utils.appendArray(this.equations,eqs); | |
for(var i=0, N=eqs.length; i!==N; i++){ | |
var eq = eqs[i]; | |
if(eq.enabled){ | |
this.equations.push(eq); | |
} | |
} | |
}; | |
/** | |
* Remove an equation. | |
* | |
* @method removeEquation | |
* @param {Equation} eq | |
*/ | |
Solver.prototype.removeEquation = function(eq){ | |
var i = this.equations.indexOf(eq); | |
if(i !== -1){ | |
this.equations.splice(i,1); | |
} | |
}; | |
/** | |
* Remove all currently added equations. | |
* | |
* @method removeAllEquations | |
*/ | |
Solver.prototype.removeAllEquations = function(){ | |
this.equations.length=0; | |
}; | |
Solver.GS = 1; | |
Solver.ISLAND = 2; | |
},{"../events/EventEmitter":26,"../utils/Utils":57}],48:[function(_dereq_,module,exports){ | |
var ContactEquation = _dereq_('../equations/ContactEquation'); | |
var Pool = _dereq_('./Pool'); | |
module.exports = ContactEquationPool; | |
/** | |
* @class | |
*/ | |
function ContactEquationPool() { | |
Pool.apply(this, arguments); | |
} | |
ContactEquationPool.prototype = new Pool(); | |
ContactEquationPool.prototype.constructor = ContactEquationPool; | |
/** | |
* @method create | |
* @return {ContactEquation} | |
*/ | |
ContactEquationPool.prototype.create = function () { | |
return new ContactEquation(); | |
}; | |
/** | |
* @method destroy | |
* @param {ContactEquation} equation | |
* @return {ContactEquationPool} | |
*/ | |
ContactEquationPool.prototype.destroy = function (equation) { | |
equation.bodyA = equation.bodyB = null; | |
return this; | |
}; | |
},{"../equations/ContactEquation":21,"./Pool":55}],49:[function(_dereq_,module,exports){ | |
var FrictionEquation = _dereq_('../equations/FrictionEquation'); | |
var Pool = _dereq_('./Pool'); | |
module.exports = FrictionEquationPool; | |
/** | |
* @class | |
*/ | |
function FrictionEquationPool() { | |
Pool.apply(this, arguments); | |
} | |
FrictionEquationPool.prototype = new Pool(); | |
FrictionEquationPool.prototype.constructor = FrictionEquationPool; | |
/** | |
* @method create | |
* @return {FrictionEquation} | |
*/ | |
FrictionEquationPool.prototype.create = function () { | |
return new FrictionEquation(); | |
}; | |
/** | |
* @method destroy | |
* @param {FrictionEquation} equation | |
* @return {FrictionEquationPool} | |
*/ | |
FrictionEquationPool.prototype.destroy = function (equation) { | |
equation.bodyA = equation.bodyB = null; | |
return this; | |
}; | |
},{"../equations/FrictionEquation":23,"./Pool":55}],50:[function(_dereq_,module,exports){ | |
var IslandNode = _dereq_('../world/IslandNode'); | |
var Pool = _dereq_('./Pool'); | |
module.exports = IslandNodePool; | |
/** | |
* @class | |
*/ | |
function IslandNodePool() { | |
Pool.apply(this, arguments); | |
} | |
IslandNodePool.prototype = new Pool(); | |
IslandNodePool.prototype.constructor = IslandNodePool; | |
/** | |
* @method create | |
* @return {IslandNode} | |
*/ | |
IslandNodePool.prototype.create = function () { | |
return new IslandNode(); | |
}; | |
/** | |
* @method destroy | |
* @param {IslandNode} node | |
* @return {IslandNodePool} | |
*/ | |
IslandNodePool.prototype.destroy = function (node) { | |
node.reset(); | |
return this; | |
}; | |
},{"../world/IslandNode":60,"./Pool":55}],51:[function(_dereq_,module,exports){ | |
var Island = _dereq_('../world/Island'); | |
var Pool = _dereq_('./Pool'); | |
module.exports = IslandPool; | |
/** | |
* @class | |
*/ | |
function IslandPool() { | |
Pool.apply(this, arguments); | |
} | |
IslandPool.prototype = new Pool(); | |
IslandPool.prototype.constructor = IslandPool; | |
/** | |
* @method create | |
* @return {Island} | |
*/ | |
IslandPool.prototype.create = function () { | |
return new Island(); | |
}; | |
/** | |
* @method destroy | |
* @param {Island} island | |
* @return {IslandPool} | |
*/ | |
IslandPool.prototype.destroy = function (island) { | |
island.reset(); | |
return this; | |
}; | |
},{"../world/Island":58,"./Pool":55}],52:[function(_dereq_,module,exports){ | |
var TupleDictionary = _dereq_('./TupleDictionary'); | |
var OverlapKeeperRecord = _dereq_('./OverlapKeeperRecord'); | |
var OverlapKeeperRecordPool = _dereq_('./OverlapKeeperRecordPool'); | |
var Utils = _dereq_('./Utils'); | |
module.exports = OverlapKeeper; | |
/** | |
* Keeps track of overlaps in the current state and the last step state. | |
* @class OverlapKeeper | |
* @constructor | |
*/ | |
function OverlapKeeper() { | |
this.overlappingShapesLastState = new TupleDictionary(); | |
this.overlappingShapesCurrentState = new TupleDictionary(); | |
this.recordPool = new OverlapKeeperRecordPool({ size: 16 }); | |
this.tmpDict = new TupleDictionary(); | |
this.tmpArray1 = []; | |
} | |
/** | |
* Ticks one step forward in time. This will move the current overlap state to the "old" overlap state, and create a new one as current. | |
* @method tick | |
*/ | |
OverlapKeeper.prototype.tick = function() { | |
var last = this.overlappingShapesLastState; | |
var current = this.overlappingShapesCurrentState; | |
// Save old objects into pool | |
var l = last.keys.length; | |
while(l--){ | |
var key = last.keys[l]; | |
var lastObject = last.getByKey(key); | |
var currentObject = current.getByKey(key); | |
if(lastObject){ | |
// The record is only used in the "last" dict, and will be removed. We might as well pool it. | |
this.recordPool.release(lastObject); | |
} | |
} | |
// Clear last object | |
last.reset(); | |
// Transfer from new object to old | |
last.copy(current); | |
// Clear current object | |
current.reset(); | |
}; | |
/** | |
* @method setOverlapping | |
* @param {Body} bodyA | |
* @param {Body} shapeA | |
* @param {Body} bodyB | |
* @param {Body} shapeB | |
*/ | |
OverlapKeeper.prototype.setOverlapping = function(bodyA, shapeA, bodyB, shapeB) { | |
var last = this.overlappingShapesLastState; | |
var current = this.overlappingShapesCurrentState; | |
// Store current contact state | |
if(!current.get(shapeA.id, shapeB.id)){ | |
var data = this.recordPool.get(); | |
data.set(bodyA, shapeA, bodyB, shapeB); | |
current.set(shapeA.id, shapeB.id, data); | |
} | |
}; | |
OverlapKeeper.prototype.getNewOverlaps = function(result){ | |
return this.getDiff(this.overlappingShapesLastState, this.overlappingShapesCurrentState, result); | |
}; | |
OverlapKeeper.prototype.getEndOverlaps = function(result){ | |
return this.getDiff(this.overlappingShapesCurrentState, this.overlappingShapesLastState, result); | |
}; | |
/** | |
* Checks if two bodies are currently overlapping. | |
* @method bodiesAreOverlapping | |
* @param {Body} bodyA | |
* @param {Body} bodyB | |
* @return {boolean} | |
*/ | |
OverlapKeeper.prototype.bodiesAreOverlapping = function(bodyA, bodyB){ | |
var current = this.overlappingShapesCurrentState; | |
var l = current.keys.length; | |
while(l--){ | |
var key = current.keys[l]; | |
var data = current.data[key]; | |
if((data.bodyA === bodyA && data.bodyB === bodyB) || data.bodyA === bodyB && data.bodyB === bodyA){ | |
return true; | |
} | |
} | |
return false; | |
}; | |
OverlapKeeper.prototype.getDiff = function(dictA, dictB, result){ | |
var result = result || []; | |
var last = dictA; | |
var current = dictB; | |
result.length = 0; | |
var l = current.keys.length; | |
while(l--){ | |
var key = current.keys[l]; | |
var data = current.data[key]; | |
if(!data){ | |
throw new Error('Key '+key+' had no data!'); | |
} | |
var lastData = last.data[key]; | |
if(!lastData){ | |
// Not overlapping in last state, but in current. | |
result.push(data); | |
} | |
} | |
return result; | |
}; | |
OverlapKeeper.prototype.isNewOverlap = function(shapeA, shapeB){ | |
var idA = shapeA.id|0, | |
idB = shapeB.id|0; | |
var last = this.overlappingShapesLastState; | |
var current = this.overlappingShapesCurrentState; | |
// Not in last but in new | |
return !!!last.get(idA, idB) && !!current.get(idA, idB); | |
}; | |
OverlapKeeper.prototype.getNewBodyOverlaps = function(result){ | |
this.tmpArray1.length = 0; | |
var overlaps = this.getNewOverlaps(this.tmpArray1); | |
return this.getBodyDiff(overlaps, result); | |
}; | |
OverlapKeeper.prototype.getEndBodyOverlaps = function(result){ | |
this.tmpArray1.length = 0; | |
var overlaps = this.getEndOverlaps(this.tmpArray1); | |
return this.getBodyDiff(overlaps, result); | |
}; | |
OverlapKeeper.prototype.getBodyDiff = function(overlaps, result){ | |
result = result || []; | |
var accumulator = this.tmpDict; | |
var l = overlaps.length; | |
while(l--){ | |
var data = overlaps[l]; | |
// Since we use body id's for the accumulator, these will be a subset of the original one | |
accumulator.set(data.bodyA.id|0, data.bodyB.id|0, data); | |
} | |
l = accumulator.keys.length; | |
while(l--){ | |
var data = accumulator.getByKey(accumulator.keys[l]); | |
if(data){ | |
result.push(data.bodyA, data.bodyB); | |
} | |
} | |
accumulator.reset(); | |
return result; | |
}; | |
},{"./OverlapKeeperRecord":53,"./OverlapKeeperRecordPool":54,"./TupleDictionary":56,"./Utils":57}],53:[function(_dereq_,module,exports){ | |
module.exports = OverlapKeeperRecord; | |
/** | |
* Overlap data container for the OverlapKeeper | |
* @class OverlapKeeperRecord | |
* @constructor | |
* @param {Body} bodyA | |
* @param {Shape} shapeA | |
* @param {Body} bodyB | |
* @param {Shape} shapeB | |
*/ | |
function OverlapKeeperRecord(bodyA, shapeA, bodyB, shapeB){ | |
/** | |
* @property {Shape} shapeA | |
*/ | |
this.shapeA = shapeA; | |
/** | |
* @property {Shape} shapeB | |
*/ | |
this.shapeB = shapeB; | |
/** | |
* @property {Body} bodyA | |
*/ | |
this.bodyA = bodyA; | |
/** | |
* @property {Body} bodyB | |
*/ | |
this.bodyB = bodyB; | |
} | |
/** | |
* Set the data for the record | |
* @method set | |
* @param {Body} bodyA | |
* @param {Shape} shapeA | |
* @param {Body} bodyB | |
* @param {Shape} shapeB | |
*/ | |
OverlapKeeperRecord.prototype.set = function(bodyA, shapeA, bodyB, shapeB){ | |
OverlapKeeperRecord.call(this, bodyA, shapeA, bodyB, shapeB); | |
}; | |
},{}],54:[function(_dereq_,module,exports){ | |
var OverlapKeeperRecord = _dereq_('./OverlapKeeperRecord'); | |
var Pool = _dereq_('./Pool'); | |
module.exports = OverlapKeeperRecordPool; | |
/** | |
* @class | |
*/ | |
function OverlapKeeperRecordPool() { | |
Pool.apply(this, arguments); | |
} | |
OverlapKeeperRecordPool.prototype = new Pool(); | |
OverlapKeeperRecordPool.prototype.constructor = OverlapKeeperRecordPool; | |
/** | |
* @method create | |
* @return {OverlapKeeperRecord} | |
*/ | |
OverlapKeeperRecordPool.prototype.create = function () { | |
return new OverlapKeeperRecord(); | |
}; | |
/** | |
* @method destroy | |
* @param {OverlapKeeperRecord} record | |
* @return {OverlapKeeperRecordPool} | |
*/ | |
OverlapKeeperRecordPool.prototype.destroy = function (record) { | |
record.bodyA = record.bodyB = record.shapeA = record.shapeB = null; | |
return this; | |
}; | |
},{"./OverlapKeeperRecord":53,"./Pool":55}],55:[function(_dereq_,module,exports){ | |
module.exports = Pool; | |
/** | |
* @class Object pooling utility. | |
*/ | |
function Pool(options) { | |
options = options || {}; | |
/** | |
* @property {Array} objects | |
* @type {Array} | |
*/ | |
this.objects = []; | |
if(options.size !== undefined){ | |
this.resize(options.size); | |
} | |
} | |
/** | |
* @method resize | |
* @param {number} size | |
* @return {Pool} Self, for chaining | |
*/ | |
Pool.prototype.resize = function (size) { | |
var objects = this.objects; | |
while (objects.length > size) { | |
objects.pop(); | |
} | |
while (objects.length < size) { | |
objects.push(this.create()); | |
} | |
return this; | |
}; | |
/** | |
* Get an object from the pool or create a new instance. | |
* @method get | |
* @return {Object} | |
*/ | |
Pool.prototype.get = function () { | |
var objects = this.objects; | |
return objects.length ? objects.pop() : this.create(); | |
}; | |
/** | |
* Clean up and put the object back into the pool for later use. | |
* @method release | |
* @param {Object} object | |
* @return {Pool} Self for chaining | |
*/ | |
Pool.prototype.release = function (object) { | |
this.destroy(object); | |
this.objects.push(object); | |
return this; | |
}; | |
},{}],56:[function(_dereq_,module,exports){ | |
var Utils = _dereq_('./Utils'); | |
module.exports = TupleDictionary; | |
/** | |
* @class TupleDictionary | |
* @constructor | |
*/ | |
function TupleDictionary() { | |
/** | |
* The data storage | |
* @property data | |
* @type {Object} | |
*/ | |
this.data = {}; | |
/** | |
* Keys that are currently used. | |
* @property {Array} keys | |
*/ | |
this.keys = []; | |
} | |
/** | |
* Generate a key given two integers | |
* @method getKey | |
* @param {number} i | |
* @param {number} j | |
* @return {string} | |
*/ | |
TupleDictionary.prototype.getKey = function(id1, id2) { | |
id1 = id1|0; | |
id2 = id2|0; | |
if ( (id1|0) === (id2|0) ){ | |
return -1; | |
} | |
// valid for values < 2^16 | |
return ((id1|0) > (id2|0) ? | |
(id1 << 16) | (id2 & 0xFFFF) : | |
(id2 << 16) | (id1 & 0xFFFF))|0 | |
; | |
}; | |
/** | |
* @method getByKey | |
* @param {Number} key | |
* @return {Object} | |
*/ | |
TupleDictionary.prototype.getByKey = function(key) { | |
key = key|0; | |
return this.data[key]; | |
}; | |
/** | |
* @method get | |
* @param {Number} i | |
* @param {Number} j | |
* @return {Number} | |
*/ | |
TupleDictionary.prototype.get = function(i, j) { | |
return this.data[this.getKey(i, j)]; | |
}; | |
/** | |
* Set a value. | |
* @method set | |
* @param {Number} i | |
* @param {Number} j | |
* @param {Number} value | |
*/ | |
TupleDictionary.prototype.set = function(i, j, value) { | |
if(!value){ | |
throw new Error("No data!"); | |
} | |
var key = this.getKey(i, j); | |
// Check if key already exists | |
if(!this.data[key]){ | |
this.keys.push(key); | |
} | |
this.data[key] = value; | |
return key; | |
}; | |
/** | |
* Remove all data. | |
* @method reset | |
*/ | |
TupleDictionary.prototype.reset = function() { | |
var data = this.data, | |
keys = this.keys; | |
var l = keys.length; | |
while(l--) { | |
delete data[keys[l]]; | |
} | |
keys.length = 0; | |
}; | |
/** | |
* Copy another TupleDictionary. Note that all data in this dictionary will be removed. | |
* @method copy | |
* @param {TupleDictionary} dict The TupleDictionary to copy into this one. | |
*/ | |
TupleDictionary.prototype.copy = function(dict) { | |
this.reset(); | |
Utils.appendArray(this.keys, dict.keys); | |
var l = dict.keys.length; | |
while(l--){ | |
var key = dict.keys[l]; | |
this.data[key] = dict.data[key]; | |
} | |
}; | |
},{"./Utils":57}],57:[function(_dereq_,module,exports){ | |
/* global P2_ARRAY_TYPE */ | |
module.exports = Utils; | |
/** | |
* Misc utility functions | |
* @class Utils | |
* @constructor | |
*/ | |
function Utils(){} | |
/** | |
* Append the values in array b to the array a. See <a href="http://stackoverflow.com/questions/1374126/how-to-append-an-array-to-an-existing-javascript-array/1374131#1374131">this</a> for an explanation. | |
* @method appendArray | |
* @static | |
* @param {Array} a | |
* @param {Array} b | |
*/ | |
Utils.appendArray = function(a,b){ | |
if (b.length < 150000) { | |
a.push.apply(a, b); | |
} else { | |
for (var i = 0, len = b.length; i !== len; ++i) { | |
a.push(b[i]); | |
} | |
} | |
}; | |
/** | |
* Garbage free Array.splice(). Does not allocate a new array. | |
* @method splice | |
* @static | |
* @param {Array} array | |
* @param {Number} index | |
* @param {Number} howmany | |
*/ | |
Utils.splice = function(array,index,howmany){ | |
howmany = howmany || 1; | |
for (var i=index, len=array.length-howmany; i < len; i++){ | |
array[i] = array[i + howmany]; | |
} | |
array.length = len; | |
}; | |
/** | |
* The array type to use for internal numeric computations throughout the library. Float32Array is used if it is available, but falls back on Array. If you want to set array type manually, inject it via the global variable P2_ARRAY_TYPE. See example below. | |
* @static | |
* @property {function} ARRAY_TYPE | |
* @example | |
* <script> | |
* <!-- Inject your preferred array type before loading p2.js --> | |
* P2_ARRAY_TYPE = Array; | |
* </script> | |
* <script src="p2.js"></script> | |
*/ | |
if(typeof P2_ARRAY_TYPE !== 'undefined') { | |
Utils.ARRAY_TYPE = P2_ARRAY_TYPE; | |
} else if (typeof Float32Array !== 'undefined'){ | |
Utils.ARRAY_TYPE = Float32Array; | |
} else { | |
Utils.ARRAY_TYPE = Array; | |
} | |
/** | |
* Extend an object with the properties of another | |
* @static | |
* @method extend | |
* @param {object} a | |
* @param {object} b | |
*/ | |
Utils.extend = function(a,b){ | |
for(var key in b){ | |
a[key] = b[key]; | |
} | |
}; | |
/** | |
* Extend an options object with default values. | |
* @static | |
* @method defaults | |
* @param {object} options The options object. May be falsy: in this case, a new object is created and returned. | |
* @param {object} defaults An object containing default values. | |
* @return {object} The modified options object. | |
*/ | |
Utils.defaults = function(options, defaults){ | |
options = options || {}; | |
for(var key in defaults){ | |
if(!(key in options)){ | |
options[key] = defaults[key]; | |
} | |
} | |
return options; | |
}; | |
},{}],58:[function(_dereq_,module,exports){ | |
var Body = _dereq_('../objects/Body'); | |
module.exports = Island; | |
/** | |
* An island of bodies connected with equations. | |
* @class Island | |
* @constructor | |
*/ | |
function Island(){ | |
/** | |
* Current equations in this island. | |
* @property equations | |
* @type {Array} | |
*/ | |
this.equations = []; | |
/** | |
* Current bodies in this island. | |
* @property bodies | |
* @type {Array} | |
*/ | |
this.bodies = []; | |
} | |
/** | |
* Clean this island from bodies and equations. | |
* @method reset | |
*/ | |
Island.prototype.reset = function(){ | |
this.equations.length = this.bodies.length = 0; | |
}; | |
var bodyIds = []; | |
/** | |
* Get all unique bodies in this island. | |
* @method getBodies | |
* @return {Array} An array of Body | |
*/ | |
Island.prototype.getBodies = function(result){ | |
var bodies = result || [], | |
eqs = this.equations; | |
bodyIds.length = 0; | |
for(var i=0; i!==eqs.length; i++){ | |
var eq = eqs[i]; | |
if(bodyIds.indexOf(eq.bodyA.id)===-1){ | |
bodies.push(eq.bodyA); | |
bodyIds.push(eq.bodyA.id); | |
} | |
if(bodyIds.indexOf(eq.bodyB.id)===-1){ | |
bodies.push(eq.bodyB); | |
bodyIds.push(eq.bodyB.id); | |
} | |
} | |
return bodies; | |
}; | |
/** | |
* Check if the entire island wants to sleep. | |
* @method wantsToSleep | |
* @return {Boolean} | |
*/ | |
Island.prototype.wantsToSleep = function(){ | |
for(var i=0; i<this.bodies.length; i++){ | |
var b = this.bodies[i]; | |
if(b.type === Body.DYNAMIC && !b.wantsToSleep){ | |
return false; | |
} | |
} | |
return true; | |
}; | |
/** | |
* Make all bodies in the island sleep. | |
* @method sleep | |
*/ | |
Island.prototype.sleep = function(){ | |
for(var i=0; i<this.bodies.length; i++){ | |
var b = this.bodies[i]; | |
b.sleep(); | |
} | |
return true; | |
}; | |
},{"../objects/Body":31}],59:[function(_dereq_,module,exports){ | |
var vec2 = _dereq_('../math/vec2') | |
, Island = _dereq_('./Island') | |
, IslandNode = _dereq_('./IslandNode') | |
, IslandNodePool = _dereq_('./../utils/IslandNodePool') | |
, IslandPool = _dereq_('./../utils/IslandPool') | |
, Body = _dereq_('../objects/Body'); | |
module.exports = IslandManager; | |
/** | |
* Splits the system of bodies and equations into independent islands | |
* | |
* @class IslandManager | |
* @constructor | |
* @param {Object} [options] | |
* @extends Solver | |
*/ | |
function IslandManager(options){ | |
/** | |
* @property nodePool | |
* @type {IslandNodePool} | |
*/ | |
this.nodePool = new IslandNodePool({ size: 16 }); | |
/** | |
* @property islandPool | |
* @type {IslandPool} | |
*/ | |
this.islandPool = new IslandPool({ size: 8 }); | |
/** | |
* The equations to split. Manually fill this array before running .split(). | |
* @property {Array} equations | |
*/ | |
this.equations = []; | |
/** | |
* The resulting {{#crossLink "Island"}}{{/crossLink}}s. | |
* @property {Array} islands | |
*/ | |
this.islands = []; | |
/** | |
* The resulting graph nodes. | |
* @property {Array} nodes | |
*/ | |
this.nodes = []; | |
/** | |
* The node queue, used when traversing the graph of nodes. | |
* @private | |
* @property {Array} queue | |
*/ | |
this.queue = []; | |
} | |
/** | |
* Get an unvisited node from a list of nodes. | |
* @static | |
* @method getUnvisitedNode | |
* @param {Array} nodes | |
* @return {IslandNode|boolean} The node if found, else false. | |
*/ | |
IslandManager.getUnvisitedNode = function(nodes){ | |
var Nnodes = nodes.length; | |
for(var i=0; i!==Nnodes; i++){ | |
var node = nodes[i]; | |
if(!node.visited && node.body.type === Body.DYNAMIC){ | |
return node; | |
} | |
} | |
return false; | |
}; | |
/** | |
* Visit a node. | |
* @method visit | |
* @param {IslandNode} node | |
* @param {Array} bds | |
* @param {Array} eqs | |
*/ | |
IslandManager.prototype.visit = function (node,bds,eqs){ | |
bds.push(node.body); | |
var Neqs = node.equations.length; | |
for(var i=0; i!==Neqs; i++){ | |
var eq = node.equations[i]; | |
if(eqs.indexOf(eq) === -1){ // Already added? | |
eqs.push(eq); | |
} | |
} | |
}; | |
/** | |
* Runs the search algorithm, starting at a root node. The resulting bodies and equations will be stored in the provided arrays. | |
* @method bfs | |
* @param {IslandNode} root The node to start from | |
* @param {Array} bds An array to append resulting Bodies to. | |
* @param {Array} eqs An array to append resulting Equations to. | |
*/ | |
IslandManager.prototype.bfs = function(root,bds,eqs){ | |
// Reset the visit queue | |
var queue = this.queue; | |
queue.length = 0; | |
// Add root node to queue | |
queue.push(root); | |
root.visited = true; | |
this.visit(root,bds,eqs); | |
// Process all queued nodes | |
while(queue.length) { | |
// Get next node in the queue | |
var node = queue.pop(); | |
// Visit unvisited neighboring nodes | |
var child; | |
while((child = IslandManager.getUnvisitedNode(node.neighbors))) { | |
child.visited = true; | |
this.visit(child,bds,eqs); | |
// Only visit the children of this node if it's dynamic | |
if(child.body.type === Body.DYNAMIC){ | |
queue.push(child); | |
} | |
} | |
} | |
}; | |
/** | |
* Split the world into independent islands. The result is stored in .islands. | |
* @method split | |
* @param {World} world | |
* @return {Array} The generated islands | |
*/ | |
IslandManager.prototype.split = function(world){ | |
var bodies = world.bodies, | |
nodes = this.nodes, | |
equations = this.equations; | |
// Move old nodes to the node pool | |
while(nodes.length){ | |
this.nodePool.release(nodes.pop()); | |
} | |
// Create needed nodes, reuse if possible | |
for(var i=0; i!==bodies.length; i++){ | |
var node = this.nodePool.get(); | |
node.body = bodies[i]; | |
nodes.push(node); | |
// if(this.nodePool.length){ | |
// var node = this.nodePool.pop(); | |
// node.reset(); | |
// node.body = bodies[i]; | |
// nodes.push(node); | |
// } else { | |
// nodes.push(new IslandNode(bodies[i])); | |
// } | |
} | |
// Add connectivity data. Each equation connects 2 bodies. | |
for(var k=0; k!==equations.length; k++){ | |
var eq=equations[k], | |
i=bodies.indexOf(eq.bodyA), | |
j=bodies.indexOf(eq.bodyB), | |
ni=nodes[i], | |
nj=nodes[j]; | |
ni.neighbors.push(nj); | |
nj.neighbors.push(ni); | |
ni.equations.push(eq); | |
nj.equations.push(eq); | |
} | |
// Move old islands to the island pool | |
var islands = this.islands; | |
for(var i=0; i<islands.length; i++){ | |
this.islandPool.release(islands[i]); | |
} | |
islands.length = 0; | |
// Get islands | |
var child; | |
while((child = IslandManager.getUnvisitedNode(nodes))){ | |
// Create new island | |
var island = this.islandPool.get(); | |
// Get all equations and bodies in this island | |
this.bfs(child, island.bodies, island.equations); | |
islands.push(island); | |
} | |
return islands; | |
}; | |
},{"../math/vec2":30,"../objects/Body":31,"./../utils/IslandNodePool":50,"./../utils/IslandPool":51,"./Island":58,"./IslandNode":60}],60:[function(_dereq_,module,exports){ | |
module.exports = IslandNode; | |
/** | |
* Holds a body and keeps track of some additional properties needed for graph traversal. | |
* @class IslandNode | |
* @constructor | |
* @param {Body} body | |
*/ | |
function IslandNode(body){ | |
/** | |
* The body that is contained in this node. | |
* @property {Body} body | |
*/ | |
this.body = body; | |
/** | |
* Neighboring IslandNodes | |
* @property {Array} neighbors | |
*/ | |
this.neighbors = []; | |
/** | |
* Equations connected to this node. | |
* @property {Array} equations | |
*/ | |
this.equations = []; | |
/** | |
* If this node was visiting during the graph traversal. | |
* @property visited | |
* @type {Boolean} | |
*/ | |
this.visited = false; | |
} | |
/** | |
* Clean this node from bodies and equations. | |
* @method reset | |
*/ | |
IslandNode.prototype.reset = function(){ | |
this.equations.length = 0; | |
this.neighbors.length = 0; | |
this.visited = false; | |
this.body = null; | |
}; | |
},{}],61:[function(_dereq_,module,exports){ | |
var GSSolver = _dereq_('../solver/GSSolver') | |
, Solver = _dereq_('../solver/Solver') | |
, Ray = _dereq_('../collision/Ray') | |
, vec2 = _dereq_('../math/vec2') | |
, Circle = _dereq_('../shapes/Circle') | |
, Convex = _dereq_('../shapes/Convex') | |
, Line = _dereq_('../shapes/Line') | |
, Plane = _dereq_('../shapes/Plane') | |
, Capsule = _dereq_('../shapes/Capsule') | |
, Particle = _dereq_('../shapes/Particle') | |
, EventEmitter = _dereq_('../events/EventEmitter') | |
, Body = _dereq_('../objects/Body') | |
, Shape = _dereq_('../shapes/Shape') | |
, LinearSpring = _dereq_('../objects/LinearSpring') | |
, Material = _dereq_('../material/Material') | |
, ContactMaterial = _dereq_('../material/ContactMaterial') | |
, DistanceConstraint = _dereq_('../constraints/DistanceConstraint') | |
, Constraint = _dereq_('../constraints/Constraint') | |
, LockConstraint = _dereq_('../constraints/LockConstraint') | |
, RevoluteConstraint = _dereq_('../constraints/RevoluteConstraint') | |
, PrismaticConstraint = _dereq_('../constraints/PrismaticConstraint') | |
, GearConstraint = _dereq_('../constraints/GearConstraint') | |
, pkg = _dereq_('../../package.json') | |
, Broadphase = _dereq_('../collision/Broadphase') | |
, AABB = _dereq_('../collision/AABB') | |
, SAPBroadphase = _dereq_('../collision/SAPBroadphase') | |
, Narrowphase = _dereq_('../collision/Narrowphase') | |
, Utils = _dereq_('../utils/Utils') | |
, OverlapKeeper = _dereq_('../utils/OverlapKeeper') | |
, IslandManager = _dereq_('./IslandManager') | |
, RotationalSpring = _dereq_('../objects/RotationalSpring'); | |
module.exports = World; | |
/** | |
* The dynamics world, where all bodies and constraints live. | |
* | |
* @class World | |
* @constructor | |
* @param {Object} [options] | |
* @param {Solver} [options.solver] Defaults to GSSolver. | |
* @param {Array} [options.gravity] Defaults to y=-9.78. | |
* @param {Broadphase} [options.broadphase] Defaults to SAPBroadphase | |
* @param {Boolean} [options.islandSplit=true] | |
* @extends EventEmitter | |
* | |
* @example | |
* var world = new World({ | |
* gravity: [0, -10], | |
* broadphase: new SAPBroadphase() | |
* }); | |
* world.addBody(new Body()); | |
*/ | |
function World(options){ | |
EventEmitter.apply(this); | |
options = options || {}; | |
/** | |
* All springs in the world. To add a spring to the world, use {{#crossLink "World/addSpring:method"}}{{/crossLink}}. | |
* | |
* @property springs | |
* @type {Array} | |
*/ | |
this.springs = []; | |
/** | |
* All bodies in the world. To add a body to the world, use {{#crossLink "World/addBody:method"}}{{/crossLink}}. | |
* @property {Array} bodies | |
*/ | |
this.bodies = []; | |
/** | |
* Disabled body collision pairs. See {{#crossLink "World/disableBodyCollision:method"}}. | |
* @private | |
* @property {Array} disabledBodyCollisionPairs | |
*/ | |
this.disabledBodyCollisionPairs = []; | |
/** | |
* The solver used to satisfy constraints and contacts. Default is {{#crossLink "GSSolver"}}{{/crossLink}}. | |
* @property {Solver} solver | |
*/ | |
this.solver = options.solver || new GSSolver(); | |
/** | |
* The narrowphase to use to generate contacts. | |
* | |
* @property narrowphase | |
* @type {Narrowphase} | |
*/ | |
this.narrowphase = new Narrowphase(this); | |
/** | |
* The island manager of this world. | |
* @property {IslandManager} islandManager | |
*/ | |
this.islandManager = new IslandManager(); | |
/** | |
* Gravity in the world. This is applied on all bodies in the beginning of each step(). | |
* | |
* @property gravity | |
* @type {Array} | |
*/ | |
this.gravity = vec2.fromValues(0, -9.78); | |
if(options.gravity){ | |
vec2.copy(this.gravity, options.gravity); | |
} | |
/** | |
* Gravity to use when approximating the friction max force (mu*mass*gravity). | |
* @property {Number} frictionGravity | |
*/ | |
this.frictionGravity = vec2.length(this.gravity) || 10; | |
/** | |
* Set to true if you want .frictionGravity to be automatically set to the length of .gravity. | |
* @property {Boolean} useWorldGravityAsFrictionGravity | |
* @default true | |
*/ | |
this.useWorldGravityAsFrictionGravity = true; | |
/** | |
* If the length of .gravity is zero, and .useWorldGravityAsFrictionGravity=true, then switch to using .frictionGravity for friction instead. This fallback is useful for gravityless games. | |
* @property {Boolean} useFrictionGravityOnZeroGravity | |
* @default true | |
*/ | |
this.useFrictionGravityOnZeroGravity = true; | |
/** | |
* The broadphase algorithm to use. | |
* | |
* @property broadphase | |
* @type {Broadphase} | |
*/ | |
this.broadphase = options.broadphase || new SAPBroadphase(); | |
this.broadphase.setWorld(this); | |
/** | |
* User-added constraints. | |
* | |
* @property constraints | |
* @type {Array} | |
*/ | |
this.constraints = []; | |
/** | |
* Dummy default material in the world, used in .defaultContactMaterial | |
* @property {Material} defaultMaterial | |
*/ | |
this.defaultMaterial = new Material(); | |
/** | |
* The default contact material to use, if no contact material was set for the colliding materials. | |
* @property {ContactMaterial} defaultContactMaterial | |
*/ | |
this.defaultContactMaterial = new ContactMaterial(this.defaultMaterial,this.defaultMaterial); | |
/** | |
* For keeping track of what time step size we used last step | |
* @property lastTimeStep | |
* @type {Number} | |
*/ | |
this.lastTimeStep = 1/60; | |
/** | |
* Enable to automatically apply spring forces each step. | |
* @property applySpringForces | |
* @type {Boolean} | |
* @default true | |
*/ | |
this.applySpringForces = true; | |
/** | |
* Enable to automatically apply body damping each step. | |
* @property applyDamping | |
* @type {Boolean} | |
* @default true | |
*/ | |
this.applyDamping = true; | |
/** | |
* Enable to automatically apply gravity each step. | |
* @property applyGravity | |
* @type {Boolean} | |
* @default true | |
*/ | |
this.applyGravity = true; | |
/** | |
* Enable/disable constraint solving in each step. | |
* @property solveConstraints | |
* @type {Boolean} | |
* @default true | |
*/ | |
this.solveConstraints = true; | |
/** | |
* The ContactMaterials added to the World. | |
* @property contactMaterials | |
* @type {Array} | |
*/ | |
this.contactMaterials = []; | |
/** | |
* World time. | |
* @property time | |
* @type {Number} | |
*/ | |
this.time = 0.0; | |
this.accumulator = 0; | |
/** | |
* Is true during step(). | |
* @property {Boolean} stepping | |
*/ | |
this.stepping = false; | |
/** | |
* Bodies that are scheduled to be removed at the end of the step. | |
* @property {Array} bodiesToBeRemoved | |
* @private | |
*/ | |
this.bodiesToBeRemoved = []; | |
/** | |
* Whether to enable island splitting. Island splitting can be an advantage for both precision and performance. See {{#crossLink "IslandManager"}}{{/crossLink}}. | |
* @property {Boolean} islandSplit | |
* @default true | |
*/ | |
this.islandSplit = typeof(options.islandSplit)!=="undefined" ? !!options.islandSplit : true; | |
/** | |
* Set to true if you want to the world to emit the "impact" event. Turning this off could improve performance. | |
* @property emitImpactEvent | |
* @type {Boolean} | |
* @default true | |
*/ | |
this.emitImpactEvent = true; | |
// Id counters | |
this._constraintIdCounter = 0; | |
this._bodyIdCounter = 0; | |
/** | |
* Fired after the step(). | |
* @event postStep | |
*/ | |
this.postStepEvent = { | |
type : "postStep" | |
}; | |
/** | |
* Fired when a body is added to the world. | |
* @event addBody | |
* @param {Body} body | |
*/ | |
this.addBodyEvent = { | |
type : "addBody", | |
body : null | |
}; | |
/** | |
* Fired when a body is removed from the world. | |
* @event removeBody | |
* @param {Body} body | |
*/ | |
this.removeBodyEvent = { | |
type : "removeBody", | |
body : null | |
}; | |
/** | |
* Fired when a spring is added to the world. | |
* @event addSpring | |
* @param {Spring} spring | |
*/ | |
this.addSpringEvent = { | |
type : "addSpring", | |
spring : null | |
}; | |
/** | |
* Fired when a first contact is created between two bodies. This event is fired after the step has been done. | |
* @event impact | |
* @param {Body} bodyA | |
* @param {Body} bodyB | |
*/ | |
this.impactEvent = { | |
type: "impact", | |
bodyA : null, | |
bodyB : null, | |
shapeA : null, | |
shapeB : null, | |
contactEquation : null | |
}; | |
/** | |
* Fired after the Broadphase has collected collision pairs in the world. | |
* Inside the event handler, you can modify the pairs array as you like, to | |
* prevent collisions between objects that you don't want. | |
* @event postBroadphase | |
* @param {Array} pairs An array of collision pairs. If this array is [body1,body2,body3,body4], then the body pairs 1,2 and 3,4 would advance to narrowphase. | |
*/ | |
this.postBroadphaseEvent = { | |
type: "postBroadphase", | |
pairs: null | |
}; | |
/** | |
* How to deactivate bodies during simulation. Possible modes are: {{#crossLink "World/NO_SLEEPING:property"}}World.NO_SLEEPING{{/crossLink}}, {{#crossLink "World/BODY_SLEEPING:property"}}World.BODY_SLEEPING{{/crossLink}} and {{#crossLink "World/ISLAND_SLEEPING:property"}}World.ISLAND_SLEEPING{{/crossLink}}. | |
* If sleeping is enabled, you might need to {{#crossLink "Body/wakeUp:method"}}wake up{{/crossLink}} the bodies if they fall asleep when they shouldn't. If you want to enable sleeping in the world, but want to disable it for a particular body, see {{#crossLink "Body/allowSleep:property"}}Body.allowSleep{{/crossLink}}. | |
* @property sleepMode | |
* @type {number} | |
* @default World.NO_SLEEPING | |
*/ | |
this.sleepMode = World.NO_SLEEPING; | |
/** | |
* Fired when two shapes starts start to overlap. Fired in the narrowphase, during step. | |
* @event beginContact | |
* @param {Shape} shapeA | |
* @param {Shape} shapeB | |
* @param {Body} bodyA | |
* @param {Body} bodyB | |
* @param {Array} contactEquations | |
*/ | |
this.beginContactEvent = { | |
type: "beginContact", | |
shapeA: null, | |
shapeB: null, | |
bodyA: null, | |
bodyB: null, | |
contactEquations: [] | |
}; | |
/** | |
* Fired when two shapes stop overlapping, after the narrowphase (during step). | |
* @event endContact | |
* @param {Shape} shapeA | |
* @param {Shape} shapeB | |
* @param {Body} bodyA | |
* @param {Body} bodyB | |
*/ | |
this.endContactEvent = { | |
type: "endContact", | |
shapeA: null, | |
shapeB: null, | |
bodyA: null, | |
bodyB: null | |
}; | |
/** | |
* Fired just before equations are added to the solver to be solved. Can be used to control what equations goes into the solver. | |
* @event preSolve | |
* @param {Array} contactEquations An array of contacts to be solved. | |
* @param {Array} frictionEquations An array of friction equations to be solved. | |
*/ | |
this.preSolveEvent = { | |
type: "preSolve", | |
contactEquations: null, | |
frictionEquations: null | |
}; | |
// For keeping track of overlapping shapes | |
this.overlappingShapesLastState = { keys:[] }; | |
this.overlappingShapesCurrentState = { keys:[] }; | |
/** | |
* @property {OverlapKeeper} overlapKeeper | |
*/ | |
this.overlapKeeper = new OverlapKeeper(); | |
} | |
World.prototype = new Object(EventEmitter.prototype); | |
World.prototype.constructor = World; | |
/** | |
* Never deactivate bodies. | |
* @static | |
* @property {number} NO_SLEEPING | |
*/ | |
World.NO_SLEEPING = 1; | |
/** | |
* Deactivate individual bodies if they are sleepy. | |
* @static | |
* @property {number} BODY_SLEEPING | |
*/ | |
World.BODY_SLEEPING = 2; | |
/** | |
* Deactivates bodies that are in contact, if all of them are sleepy. Note that you must enable {{#crossLink "World/islandSplit:property"}}.islandSplit{{/crossLink}} for this to work. | |
* @static | |
* @property {number} ISLAND_SLEEPING | |
*/ | |
World.ISLAND_SLEEPING = 4; | |
/** | |
* Add a constraint to the simulation. | |
* | |
* @method addConstraint | |
* @param {Constraint} constraint | |
* @example | |
* var constraint = new LockConstraint(bodyA, bodyB); | |
* world.addConstraint(constraint); | |
*/ | |
World.prototype.addConstraint = function(constraint){ | |
this.constraints.push(constraint); | |
}; | |
/** | |
* Add a ContactMaterial to the simulation. | |
* @method addContactMaterial | |
* @param {ContactMaterial} contactMaterial | |
*/ | |
World.prototype.addContactMaterial = function(contactMaterial){ | |
this.contactMaterials.push(contactMaterial); | |
}; | |
/** | |
* Removes a contact material | |
* | |
* @method removeContactMaterial | |
* @param {ContactMaterial} cm | |
*/ | |
World.prototype.removeContactMaterial = function(cm){ | |
var idx = this.contactMaterials.indexOf(cm); | |
if(idx!==-1){ | |
Utils.splice(this.contactMaterials,idx,1); | |
} | |
}; | |
/** | |
* Get a contact material given two materials | |
* @method getContactMaterial | |
* @param {Material} materialA | |
* @param {Material} materialB | |
* @return {ContactMaterial} The matching ContactMaterial, or false on fail. | |
* @todo Use faster hash map to lookup from material id's | |
*/ | |
World.prototype.getContactMaterial = function(materialA,materialB){ | |
var cmats = this.contactMaterials; | |
for(var i=0, N=cmats.length; i!==N; i++){ | |
var cm = cmats[i]; | |
if( (cm.materialA.id === materialA.id) && (cm.materialB.id === materialB.id) || | |
(cm.materialA.id === materialB.id) && (cm.materialB.id === materialA.id) ){ | |
return cm; | |
} | |
} | |
return false; | |
}; | |
/** | |
* Removes a constraint | |
* | |
* @method removeConstraint | |
* @param {Constraint} constraint | |
*/ | |
World.prototype.removeConstraint = function(constraint){ | |
var idx = this.constraints.indexOf(constraint); | |
if(idx!==-1){ | |
Utils.splice(this.constraints,idx,1); | |
} | |
}; | |
var step_r = vec2.create(), | |
step_runit = vec2.create(), | |
step_u = vec2.create(), | |
step_f = vec2.create(), | |
step_fhMinv = vec2.create(), | |
step_velodt = vec2.create(), | |
step_mg = vec2.create(), | |
xiw = vec2.fromValues(0,0), | |
xjw = vec2.fromValues(0,0), | |
zero = vec2.fromValues(0,0), | |
interpvelo = vec2.fromValues(0,0); | |
/** | |
* Step the physics world forward in time. | |
* | |
* There are two modes. The simple mode is fixed timestepping without interpolation. In this case you only use the first argument. The second case uses interpolation. In that you also provide the time since the function was last used, as well as the maximum fixed timesteps to take. | |
* | |
* @method step | |
* @param {Number} dt The fixed time step size to use. | |
* @param {Number} [timeSinceLastCalled=0] The time elapsed since the function was last called. | |
* @param {Number} [maxSubSteps=10] Maximum number of fixed steps to take per function call. | |
* | |
* @example | |
* // Simple fixed timestepping without interpolation | |
* var fixedTimeStep = 1 / 60; | |
* var world = new World(); | |
* var body = new Body({ mass: 1 }); | |
* world.addBody(body); | |
* | |
* function animate(){ | |
* requestAnimationFrame(animate); | |
* world.step(fixedTimeStep); | |
* renderBody(body.position, body.angle); | |
* } | |
* | |
* // Start animation loop | |
* requestAnimationFrame(animate); | |
* | |
* @example | |
* // Fixed timestepping with interpolation | |
* var maxSubSteps = 10; | |
* var lastTimeSeconds; | |
* | |
* function animate(t){ | |
* requestAnimationFrame(animate); | |
* timeSeconds = t / 1000; | |
* lastTimeSeconds = lastTimeSeconds || timeSeconds; | |
* | |
* deltaTime = timeSeconds - lastTimeSeconds; | |
* world.step(fixedTimeStep, deltaTime, maxSubSteps); | |
* | |
* renderBody(body.interpolatedPosition, body.interpolatedAngle); | |
* } | |
* | |
* // Start animation loop | |
* requestAnimationFrame(animate); | |
* | |
* @see http://bulletphysics.org/mediawiki-1.5.8/index.php/Stepping_The_World | |
*/ | |
World.prototype.step = function(dt,timeSinceLastCalled,maxSubSteps){ | |
maxSubSteps = maxSubSteps || 10; | |
timeSinceLastCalled = timeSinceLastCalled || 0; | |
if(timeSinceLastCalled === 0){ // Fixed, simple stepping | |
this.internalStep(dt); | |
// Increment time | |
this.time += dt; | |
} else { | |
this.accumulator += timeSinceLastCalled; | |
var substeps = 0; | |
while (this.accumulator >= dt && substeps < maxSubSteps) { | |
// Do fixed steps to catch up | |
this.internalStep(dt); | |
this.time += dt; | |
this.accumulator -= dt; | |
substeps++; | |
} | |
var t = (this.accumulator % dt) / dt; | |
for(var j=0; j!==this.bodies.length; j++){ | |
var b = this.bodies[j]; | |
vec2.lerp(b.interpolatedPosition, b.previousPosition, b.position, t); | |
b.interpolatedAngle = b.previousAngle + t * (b.angle - b.previousAngle); | |
} | |
} | |
}; | |
var endOverlaps = []; | |
/** | |
* Make a fixed step. | |
* @method internalStep | |
* @param {number} dt | |
* @private | |
*/ | |
World.prototype.internalStep = function(dt){ | |
this.stepping = true; | |
var that = this, | |
Nsprings = this.springs.length, | |
springs = this.springs, | |
bodies = this.bodies, | |
g = this.gravity, | |
solver = this.solver, | |
Nbodies = this.bodies.length, | |
broadphase = this.broadphase, | |
np = this.narrowphase, | |
constraints = this.constraints, | |
t0, t1, | |
fhMinv = step_fhMinv, | |
velodt = step_velodt, | |
mg = step_mg, | |
scale = vec2.scale, | |
add = vec2.add, | |
rotate = vec2.rotate, | |
islandManager = this.islandManager; | |
this.overlapKeeper.tick(); | |
this.lastTimeStep = dt; | |
// Update approximate friction gravity. | |
if(this.useWorldGravityAsFrictionGravity){ | |
var gravityLen = vec2.length(this.gravity); | |
if(!(gravityLen === 0 && this.useFrictionGravityOnZeroGravity)){ | |
// Nonzero gravity. Use it. | |
this.frictionGravity = gravityLen; | |
} | |
} | |
// Add gravity to bodies | |
if(this.applyGravity){ | |
for(var i=0; i!==Nbodies; i++){ | |
var b = bodies[i], | |
fi = b.force; | |
if(b.type !== Body.DYNAMIC || b.sleepState === Body.SLEEPING){ | |
continue; | |
} | |
vec2.scale(mg,g,b.mass*b.gravityScale); // F=m*g | |
add(fi,fi,mg); | |
} | |
} | |
// Add spring forces | |
if(this.applySpringForces){ | |
for(var i=0; i!==Nsprings; i++){ | |
var s = springs[i]; | |
s.applyForce(); | |
} | |
} | |
if(this.applyDamping){ | |
for(var i=0; i!==Nbodies; i++){ | |
var b = bodies[i]; | |
if(b.type === Body.DYNAMIC){ | |
b.applyDamping(dt); | |
} | |
} | |
} | |
// Broadphase | |
var result = broadphase.getCollisionPairs(this); | |
// Remove ignored collision pairs | |
var ignoredPairs = this.disabledBodyCollisionPairs; | |
for(var i=ignoredPairs.length-2; i>=0; i-=2){ | |
for(var j=result.length-2; j>=0; j-=2){ | |
if( (ignoredPairs[i] === result[j] && ignoredPairs[i+1] === result[j+1]) || | |
(ignoredPairs[i+1] === result[j] && ignoredPairs[i] === result[j+1])){ | |
result.splice(j,2); | |
} | |
} | |
} | |
// Remove constrained pairs with collideConnected == false | |
var Nconstraints = constraints.length; | |
for(i=0; i!==Nconstraints; i++){ | |
var c = constraints[i]; | |
if(!c.collideConnected){ | |
for(var j=result.length-2; j>=0; j-=2){ | |
if( (c.bodyA === result[j] && c.bodyB === result[j+1]) || | |
(c.bodyB === result[j] && c.bodyA === result[j+1])){ | |
result.splice(j,2); | |
} | |
} | |
} | |
} | |
// postBroadphase event | |
this.postBroadphaseEvent.pairs = result; | |
this.emit(this.postBroadphaseEvent); | |
this.postBroadphaseEvent.pairs = null; | |
// Narrowphase | |
np.reset(this); | |
for(var i=0, Nresults=result.length; i!==Nresults; i+=2){ | |
var bi = result[i], | |
bj = result[i+1]; | |
// Loop over all shapes of body i | |
for(var k=0, Nshapesi=bi.shapes.length; k!==Nshapesi; k++){ | |
var si = bi.shapes[k], | |
xi = si.position, | |
ai = si.angle; | |
// All shapes of body j | |
for(var l=0, Nshapesj=bj.shapes.length; l!==Nshapesj; l++){ | |
var sj = bj.shapes[l], | |
xj = sj.position, | |
aj = sj.angle; | |
var cm = this.defaultContactMaterial; | |
if(si.material && sj.material){ | |
var tmp = this.getContactMaterial(si.material,sj.material); | |
if(tmp){ | |
cm = tmp; | |
} | |
} | |
this.runNarrowphase(np,bi,si,xi,ai,bj,sj,xj,aj,cm,this.frictionGravity); | |
} | |
} | |
} | |
// Wake up bodies | |
for(var i=0; i!==Nbodies; i++){ | |
var body = bodies[i]; | |
if(body._wakeUpAfterNarrowphase){ | |
body.wakeUp(); | |
body._wakeUpAfterNarrowphase = false; | |
} | |
} | |
// Emit end overlap events | |
if(this.has('endContact')){ | |
this.overlapKeeper.getEndOverlaps(endOverlaps); | |
var e = this.endContactEvent; | |
var l = endOverlaps.length; | |
while(l--){ | |
var data = endOverlaps[l]; | |
e.shapeA = data.shapeA; | |
e.shapeB = data.shapeB; | |
e.bodyA = data.bodyA; | |
e.bodyB = data.bodyB; | |
this.emit(e); | |
} | |
endOverlaps.length = 0; | |
} | |
var preSolveEvent = this.preSolveEvent; | |
preSolveEvent.contactEquations = np.contactEquations; | |
preSolveEvent.frictionEquations = np.frictionEquations; | |
this.emit(preSolveEvent); | |
preSolveEvent.contactEquations = preSolveEvent.frictionEquations = null; | |
// update constraint equations | |
var Nconstraints = constraints.length; | |
for(i=0; i!==Nconstraints; i++){ | |
constraints[i].update(); | |
} | |
if(np.contactEquations.length || np.frictionEquations.length || Nconstraints){ | |
if(this.islandSplit){ | |
// Split into islands | |
islandManager.equations.length = 0; | |
Utils.appendArray(islandManager.equations, np.contactEquations); | |
Utils.appendArray(islandManager.equations, np.frictionEquations); | |
for(i=0; i!==Nconstraints; i++){ | |
Utils.appendArray(islandManager.equations, constraints[i].equations); | |
} | |
islandManager.split(this); | |
for(var i=0; i!==islandManager.islands.length; i++){ | |
var island = islandManager.islands[i]; | |
if(island.equations.length){ | |
solver.solveIsland(dt,island); | |
} | |
} | |
} else { | |
// Add contact equations to solver | |
solver.addEquations(np.contactEquations); | |
solver.addEquations(np.frictionEquations); | |
// Add user-defined constraint equations | |
for(i=0; i!==Nconstraints; i++){ | |
solver.addEquations(constraints[i].equations); | |
} | |
if(this.solveConstraints){ | |
solver.solve(dt,this); | |
} | |
solver.removeAllEquations(); | |
} | |
} | |
// Step forward | |
for(var i=0; i!==Nbodies; i++){ | |
var body = bodies[i]; | |
// if(body.sleepState !== Body.SLEEPING && body.type !== Body.STATIC){ | |
body.integrate(dt); | |
// } | |
} | |
// Reset force | |
for(var i=0; i!==Nbodies; i++){ | |
bodies[i].setZeroForce(); | |
} | |
// Emit impact event | |
if(this.emitImpactEvent && this.has('impact')){ | |
var ev = this.impactEvent; | |
for(var i=0; i!==np.contactEquations.length; i++){ | |
var eq = np.contactEquations[i]; | |
if(eq.firstImpact){ | |
ev.bodyA = eq.bodyA; | |
ev.bodyB = eq.bodyB; | |
ev.shapeA = eq.shapeA; | |
ev.shapeB = eq.shapeB; | |
ev.contactEquation = eq; | |
this.emit(ev); | |
} | |
} | |
} | |
// Sleeping update | |
if(this.sleepMode === World.BODY_SLEEPING){ | |
for(i=0; i!==Nbodies; i++){ | |
bodies[i].sleepTick(this.time, false, dt); | |
} | |
} else if(this.sleepMode === World.ISLAND_SLEEPING && this.islandSplit){ | |
// Tell all bodies to sleep tick but dont sleep yet | |
for(i=0; i!==Nbodies; i++){ | |
bodies[i].sleepTick(this.time, true, dt); | |
} | |
// Sleep islands | |
for(var i=0; i<this.islandManager.islands.length; i++){ | |
var island = this.islandManager.islands[i]; | |
if(island.wantsToSleep()){ | |
island.sleep(); | |
} | |
} | |
} | |
this.stepping = false; | |
// Remove bodies that are scheduled for removal | |
var bodiesToBeRemoved = this.bodiesToBeRemoved; | |
for(var i=0; i!==bodiesToBeRemoved.length; i++){ | |
this.removeBody(bodiesToBeRemoved[i]); | |
} | |
bodiesToBeRemoved.length = 0; | |
this.emit(this.postStepEvent); | |
}; | |
/** | |
* Runs narrowphase for the shape pair i and j. | |
* @method runNarrowphase | |
* @param {Narrowphase} np | |
* @param {Body} bi | |
* @param {Shape} si | |
* @param {Array} xi | |
* @param {Number} ai | |
* @param {Body} bj | |
* @param {Shape} sj | |
* @param {Array} xj | |
* @param {Number} aj | |
* @param {Number} mu | |
*/ | |
World.prototype.runNarrowphase = function(np,bi,si,xi,ai,bj,sj,xj,aj,cm,glen){ | |
// Check collision groups and masks | |
if(!((si.collisionGroup & sj.collisionMask) !== 0 && (sj.collisionGroup & si.collisionMask) !== 0)){ | |
return; | |
} | |
// Get world position and angle of each shape | |
vec2.rotate(xiw, xi, bi.angle); | |
vec2.rotate(xjw, xj, bj.angle); | |
vec2.add(xiw, xiw, bi.position); | |
vec2.add(xjw, xjw, bj.position); | |
var aiw = ai + bi.angle; | |
var ajw = aj + bj.angle; | |
np.enableFriction = cm.friction > 0; | |
np.frictionCoefficient = cm.friction; | |
var reducedMass; | |
if(bi.type === Body.STATIC || bi.type === Body.KINEMATIC){ | |
reducedMass = bj.mass; | |
} else if(bj.type === Body.STATIC || bj.type === Body.KINEMATIC){ | |
reducedMass = bi.mass; | |
} else { | |
reducedMass = (bi.mass*bj.mass)/(bi.mass+bj.mass); | |
} | |
np.slipForce = cm.friction*glen*reducedMass; | |
np.restitution = cm.restitution; | |
np.surfaceVelocity = cm.surfaceVelocity; | |
np.frictionStiffness = cm.frictionStiffness; | |
np.frictionRelaxation = cm.frictionRelaxation; | |
np.stiffness = cm.stiffness; | |
np.relaxation = cm.relaxation; | |
np.contactSkinSize = cm.contactSkinSize; | |
np.enabledEquations = bi.collisionResponse && bj.collisionResponse && si.collisionResponse && sj.collisionResponse; | |
var resolver = np[si.type | sj.type], | |
numContacts = 0; | |
if (resolver) { | |
var sensor = si.sensor || sj.sensor; | |
var numFrictionBefore = np.frictionEquations.length; | |
if (si.type < sj.type) { | |
numContacts = resolver.call(np, bi,si,xiw,aiw, bj,sj,xjw,ajw, sensor); | |
} else { | |
numContacts = resolver.call(np, bj,sj,xjw,ajw, bi,si,xiw,aiw, sensor); | |
} | |
var numFrictionEquations = np.frictionEquations.length - numFrictionBefore; | |
if(numContacts){ | |
if( bi.allowSleep && | |
bi.type === Body.DYNAMIC && | |
bi.sleepState === Body.SLEEPING && | |
bj.sleepState === Body.AWAKE && | |
bj.type !== Body.STATIC | |
){ | |
var speedSquaredB = vec2.squaredLength(bj.velocity) + Math.pow(bj.angularVelocity,2); | |
var speedLimitSquaredB = Math.pow(bj.sleepSpeedLimit,2); | |
if(speedSquaredB >= speedLimitSquaredB*2){ | |
bi._wakeUpAfterNarrowphase = true; | |
} | |
} | |
if( bj.allowSleep && | |
bj.type === Body.DYNAMIC && | |
bj.sleepState === Body.SLEEPING && | |
bi.sleepState === Body.AWAKE && | |
bi.type !== Body.STATIC | |
){ | |
var speedSquaredA = vec2.squaredLength(bi.velocity) + Math.pow(bi.angularVelocity,2); | |
var speedLimitSquaredA = Math.pow(bi.sleepSpeedLimit,2); | |
if(speedSquaredA >= speedLimitSquaredA*2){ | |
bj._wakeUpAfterNarrowphase = true; | |
} | |
} | |
this.overlapKeeper.setOverlapping(bi, si, bj, sj); | |
if(this.has('beginContact') && this.overlapKeeper.isNewOverlap(si, sj)){ | |
// Report new shape overlap | |
var e = this.beginContactEvent; | |
e.shapeA = si; | |
e.shapeB = sj; | |
e.bodyA = bi; | |
e.bodyB = bj; | |
// Reset contact equations | |
e.contactEquations.length = 0; | |
if(typeof(numContacts)==="number"){ | |
for(var i=np.contactEquations.length-numContacts; i<np.contactEquations.length; i++){ | |
e.contactEquations.push(np.contactEquations[i]); | |
} | |
} | |
this.emit(e); | |
} | |
// divide the max friction force by the number of contacts | |
if(typeof(numContacts)==="number" && numFrictionEquations > 1){ // Why divide by 1? | |
for(var i=np.frictionEquations.length-numFrictionEquations; i<np.frictionEquations.length; i++){ | |
var f = np.frictionEquations[i]; | |
f.setSlipForce(f.getSlipForce() / numFrictionEquations); | |
} | |
} | |
} | |
} | |
}; | |
/** | |
* Add a spring to the simulation | |
* | |
* @method addSpring | |
* @param {Spring} spring | |
*/ | |
World.prototype.addSpring = function(spring){ | |
this.springs.push(spring); | |
var evt = this.addSpringEvent; | |
evt.spring = spring; | |
this.emit(evt); | |
evt.spring = null; | |
}; | |
/** | |
* Remove a spring | |
* | |
* @method removeSpring | |
* @param {Spring} spring | |
*/ | |
World.prototype.removeSpring = function(spring){ | |
var idx = this.springs.indexOf(spring); | |
if(idx !== -1){ | |
Utils.splice(this.springs,idx,1); | |
} | |
}; | |
/** | |
* Add a body to the simulation | |
* | |
* @method addBody | |
* @param {Body} body | |
* | |
* @example | |
* var world = new World(), | |
* body = new Body(); | |
* world.addBody(body); | |
* @todo What if this is done during step? | |
*/ | |
World.prototype.addBody = function(body){ | |
if(this.bodies.indexOf(body) === -1){ | |
this.bodies.push(body); | |
body.world = this; | |
var evt = this.addBodyEvent; | |
evt.body = body; | |
this.emit(evt); | |
evt.body = null; | |
} | |
}; | |
/** | |
* Remove a body from the simulation. If this method is called during step(), the body removal is scheduled to after the step. | |
* | |
* @method removeBody | |
* @param {Body} body | |
*/ | |
World.prototype.removeBody = function(body){ | |
if(this.stepping){ | |
this.bodiesToBeRemoved.push(body); | |
} else { | |
body.world = null; | |
var idx = this.bodies.indexOf(body); | |
if(idx!==-1){ | |
Utils.splice(this.bodies,idx,1); | |
this.removeBodyEvent.body = body; | |
body.resetConstraintVelocity(); | |
this.emit(this.removeBodyEvent); | |
this.removeBodyEvent.body = null; | |
} | |
} | |
}; | |
/** | |
* Get a body by its id. | |
* @method getBodyById | |
* @param {number} id | |
* @return {Body} The body, or false if it was not found. | |
*/ | |
World.prototype.getBodyById = function(id){ | |
var bodies = this.bodies; | |
for(var i=0; i<bodies.length; i++){ | |
var b = bodies[i]; | |
if(b.id === id){ | |
return b; | |
} | |
} | |
return false; | |
}; | |
/** | |
* Disable collision between two bodies | |
* @method disableBodyCollision | |
* @param {Body} bodyA | |
* @param {Body} bodyB | |
*/ | |
World.prototype.disableBodyCollision = function(bodyA,bodyB){ | |
this.disabledBodyCollisionPairs.push(bodyA,bodyB); | |
}; | |
/** | |
* Enable collisions between the given two bodies | |
* @method enableBodyCollision | |
* @param {Body} bodyA | |
* @param {Body} bodyB | |
*/ | |
World.prototype.enableBodyCollision = function(bodyA,bodyB){ | |
var pairs = this.disabledBodyCollisionPairs; | |
for(var i=0; i<pairs.length; i+=2){ | |
if((pairs[i] === bodyA && pairs[i+1] === bodyB) || (pairs[i+1] === bodyA && pairs[i] === bodyB)){ | |
pairs.splice(i,2); | |
return; | |
} | |
} | |
}; | |
/** | |
* Resets the World, removes all bodies, constraints and springs. | |
* | |
* @method clear | |
*/ | |
World.prototype.clear = function(){ | |
this.time = 0; | |
// Remove all solver equations | |
if(this.solver && this.solver.equations.length){ | |
this.solver.removeAllEquations(); | |
} | |
// Remove all constraints | |
var cs = this.constraints; | |
for(var i=cs.length-1; i>=0; i--){ | |
this.removeConstraint(cs[i]); | |
} | |
// Remove all bodies | |
var bodies = this.bodies; | |
for(var i=bodies.length-1; i>=0; i--){ | |
this.removeBody(bodies[i]); | |
} | |
// Remove all springs | |
var springs = this.springs; | |
for(var i=springs.length-1; i>=0; i--){ | |
this.removeSpring(springs[i]); | |
} | |
// Remove all contact materials | |
var cms = this.contactMaterials; | |
for(var i=cms.length-1; i>=0; i--){ | |
this.removeContactMaterial(cms[i]); | |
} | |
World.apply(this); | |
}; | |
var hitTest_tmp1 = vec2.create(), | |
hitTest_zero = vec2.fromValues(0,0), | |
hitTest_tmp2 = vec2.fromValues(0,0); | |
/** | |
* Test if a world point overlaps bodies | |
* @method hitTest | |
* @param {Array} worldPoint Point to use for intersection tests | |
* @param {Array} bodies A list of objects to check for intersection | |
* @param {Number} precision Used for matching against particles and lines. Adds some margin to these infinitesimal objects. | |
* @return {Array} Array of bodies that overlap the point | |
* @todo Should use an api similar to the raycast function | |
* @todo Should probably implement a .containsPoint method for all shapes. Would be more efficient | |
*/ | |
World.prototype.hitTest = function(worldPoint,bodies,precision){ | |
precision = precision || 0; | |
// Create a dummy particle body with a particle shape to test against the bodies | |
var pb = new Body({ position:worldPoint }), | |
ps = new Particle(), | |
px = worldPoint, | |
pa = 0, | |
x = hitTest_tmp1, | |
zero = hitTest_zero, | |
tmp = hitTest_tmp2; | |
pb.addShape(ps); | |
var n = this.narrowphase, | |
result = []; | |
// Check bodies | |
for(var i=0, N=bodies.length; i!==N; i++){ | |
var b = bodies[i]; | |
for(var j=0, NS=b.shapes.length; j!==NS; j++){ | |
var s = b.shapes[j]; | |
// Get shape world position + angle | |
vec2.rotate(x, s.position, b.angle); | |
vec2.add(x, x, b.position); | |
var a = s.angle + b.angle; | |
if( (s instanceof Circle && n.circleParticle (b,s,x,a, pb,ps,px,pa, true)) || | |
(s instanceof Convex && n.particleConvex (pb,ps,px,pa, b,s,x,a, true)) || | |
(s instanceof Plane && n.particlePlane (pb,ps,px,pa, b,s,x,a, true)) || | |
(s instanceof Capsule && n.particleCapsule (pb,ps,px,pa, b,s,x,a, true)) || | |
(s instanceof Particle && vec2.squaredLength(vec2.sub(tmp,x,worldPoint)) < precision*precision) | |
){ | |
result.push(b); | |
} | |
} | |
} | |
return result; | |
}; | |
/** | |
* Set the stiffness for all equations and contact materials. | |
* @method setGlobalStiffness | |
* @param {Number} stiffness | |
*/ | |
World.prototype.setGlobalStiffness = function(stiffness){ | |
// Set for all constraints | |
var constraints = this.constraints; | |
for(var i=0; i !== constraints.length; i++){ | |
var c = constraints[i]; | |
for(var j=0; j !== c.equations.length; j++){ | |
var eq = c.equations[j]; | |
eq.stiffness = stiffness; | |
eq.needsUpdate = true; | |
} | |
} | |
// Set for all contact materials | |
var contactMaterials = this.contactMaterials; | |
for(var i=0; i !== contactMaterials.length; i++){ | |
var c = contactMaterials[i]; | |
c.stiffness = c.frictionStiffness = stiffness; | |
} | |
// Set for default contact material | |
var c = this.defaultContactMaterial; | |
c.stiffness = c.frictionStiffness = stiffness; | |
}; | |
/** | |
* Set the relaxation for all equations and contact materials. | |
* @method setGlobalRelaxation | |
* @param {Number} relaxation | |
*/ | |
World.prototype.setGlobalRelaxation = function(relaxation){ | |
// Set for all constraints | |
for(var i=0; i !== this.constraints.length; i++){ | |
var c = this.constraints[i]; | |
for(var j=0; j !== c.equations.length; j++){ | |
var eq = c.equations[j]; | |
eq.relaxation = relaxation; | |
eq.needsUpdate = true; | |
} | |
} | |
// Set for all contact materials | |
for(var i=0; i !== this.contactMaterials.length; i++){ | |
var c = this.contactMaterials[i]; | |
c.relaxation = c.frictionRelaxation = relaxation; | |
} | |
// Set for default contact material | |
var c = this.defaultContactMaterial; | |
c.relaxation = c.frictionRelaxation = relaxation; | |
}; | |
var tmpAABB = new AABB(); | |
var tmpArray = []; | |
/** | |
* Ray cast against all bodies in the world. | |
* @method raycast | |
* @param {RaycastResult} result | |
* @param {Ray} ray | |
* @return {boolean} True if any body was hit. | |
* | |
* @example | |
* var ray = new Ray({ | |
* mode: Ray.CLOSEST, // or ANY | |
* from: [0, 0], | |
* to: [10, 0], | |
* }); | |
* var result = new RaycastResult(); | |
* world.raycast(result, ray); | |
* | |
* // Get the hit point | |
* var hitPoint = vec2.create(); | |
* result.getHitPoint(hitPoint, ray); | |
* console.log('Hit point: ', hitPoint[0], hitPoint[1], ' at distance ' + result.getHitDistance(ray)); | |
* | |
* @example | |
* var ray = new Ray({ | |
* mode: Ray.ALL, | |
* from: [0, 0], | |
* to: [10, 0], | |
* callback: function(result){ | |
* | |
* // Print some info about the hit | |
* console.log('Hit body and shape: ', result.body, result.shape); | |
* | |
* // Get the hit point | |
* var hitPoint = vec2.create(); | |
* result.getHitPoint(hitPoint, ray); | |
* console.log('Hit point: ', hitPoint[0], hitPoint[1], ' at distance ' + result.getHitDistance(ray)); | |
* | |
* // If you are happy with the hits you got this far, you can stop the traversal here: | |
* result.stop(); | |
* } | |
* }); | |
* var result = new RaycastResult(); | |
* world.raycast(result, ray); | |
*/ | |
World.prototype.raycast = function(result, ray){ | |
// Get all bodies within the ray AABB | |
ray.getAABB(tmpAABB); | |
this.broadphase.aabbQuery(this, tmpAABB, tmpArray); | |
ray.intersectBodies(result, tmpArray); | |
tmpArray.length = 0; | |
return result.hasHit(); | |
}; | |
},{"../../package.json":6,"../collision/AABB":7,"../collision/Broadphase":8,"../collision/Narrowphase":10,"../collision/Ray":11,"../collision/SAPBroadphase":13,"../constraints/Constraint":14,"../constraints/DistanceConstraint":15,"../constraints/GearConstraint":16,"../constraints/LockConstraint":17,"../constraints/PrismaticConstraint":18,"../constraints/RevoluteConstraint":19,"../events/EventEmitter":26,"../material/ContactMaterial":27,"../material/Material":28,"../math/vec2":30,"../objects/Body":31,"../objects/LinearSpring":32,"../objects/RotationalSpring":33,"../shapes/Capsule":38,"../shapes/Circle":39,"../shapes/Convex":40,"../shapes/Line":42,"../shapes/Particle":43,"../shapes/Plane":44,"../shapes/Shape":45,"../solver/GSSolver":46,"../solver/Solver":47,"../utils/OverlapKeeper":52,"../utils/Utils":57,"./IslandManager":59}]},{},[36]) | |
(36) | |
}); | |
/** | |
* @author Mat Groves http://matgroves.com/ @Doormat23 | |
*/ | |
(function(){ | |
var root = this; | |
/** | |
* @author Mat Groves http://matgroves.com/ @Doormat23 | |
*/ | |
/** | |
* The [pixi.js](http://www.pixijs.com/) module/namespace. | |
* | |
* @module PIXI | |
*/ | |
/** | |
* Namespace-class for [pixi.js](http://www.pixijs.com/). | |
* | |
* Contains assorted static properties and enumerations. | |
* | |
* @class PIXI | |
* @static | |
*/ | |
var PIXI = PIXI || {}; | |
/** | |
* A reference to the Phaser Game instance that owns this Pixi renderer. | |
* @property {Phaser.Game} game | |
* @static | |
*/ | |
PIXI.game = null; | |
/** | |
* @property {Number} WEBGL_RENDERER | |
* @protected | |
* @static | |
*/ | |
PIXI.WEBGL_RENDERER = 0; | |
/** | |
* @property {Number} CANVAS_RENDERER | |
* @protected | |
* @static | |
*/ | |
PIXI.CANVAS_RENDERER = 1; | |
/** | |
* Version of pixi that is loaded. | |
* @property {String} VERSION | |
* @static | |
*/ | |
PIXI.VERSION = "v2.2.9"; | |
// used to create uids for various pixi objects. | |
PIXI._UID = 0; | |
if (typeof(Float32Array) != 'undefined') | |
{ | |
PIXI.Float32Array = Float32Array; | |
PIXI.Uint16Array = Uint16Array; | |
// Uint32Array and ArrayBuffer only used by WebGL renderer | |
// We can suppose that if WebGL is supported then typed arrays are supported too | |
// as they predate WebGL support for all browsers: | |
// see typed arrays support: http://caniuse.com/#search=TypedArrays | |
// see WebGL support: http://caniuse.com/#search=WebGL | |
PIXI.Uint32Array = Uint32Array; | |
PIXI.ArrayBuffer = ArrayBuffer; | |
} | |
else | |
{ | |
PIXI.Float32Array = Array; | |
PIXI.Uint16Array = Array; | |
} | |
/** | |
* @property {Number} PI_2 | |
* @static | |
*/ | |
PIXI.PI_2 = Math.PI * 2; | |
/** | |
* @property {Number} RAD_TO_DEG | |
* @static | |
*/ | |
PIXI.RAD_TO_DEG = 180 / Math.PI; | |
/** | |
* @property {Number} DEG_TO_RAD | |
* @static | |
*/ | |
PIXI.DEG_TO_RAD = Math.PI / 180; | |
/** | |
* @property {String} RETINA_PREFIX | |
* @protected | |
* @static | |
*/ | |
PIXI.RETINA_PREFIX = "@2x"; | |
/** | |
* The default render options if none are supplied to | |
* {{#crossLink "WebGLRenderer"}}{{/crossLink}} or {{#crossLink "CanvasRenderer"}}{{/crossLink}}. | |
* | |
* @property {Object} defaultRenderOptions | |
* @property {Object} defaultRenderOptions.view=null | |
* @property {Boolean} defaultRenderOptions.transparent=false | |
* @property {Boolean} defaultRenderOptions.antialias=false | |
* @property {Boolean} defaultRenderOptions.preserveDrawingBuffer=false | |
* @property {Number} defaultRenderOptions.resolution=1 | |
* @property {Boolean} defaultRenderOptions.clearBeforeRender=true | |
* @property {Boolean} defaultRenderOptions.autoResize=false | |
* @static | |
PIXI.defaultRenderOptions = { | |
view: null, | |
transparent: false, | |
antialias: false, | |
preserveDrawingBuffer: false, | |
resolution: 1, | |
clearBeforeRender: true, | |
autoResize: false | |
}; | |
*/ | |
/** | |
* @author Mat Groves http://matgroves.com @Doormat23 | |
* @author Richard Davey <[email protected]> | |
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} | |
*/ | |
/** | |
* The base class for all objects that are rendered. Contains properties for position, scaling, | |
* rotation, masks and cache handling. | |
* | |
* This is an abstract class and should not be used on its own, rather it should be extended. | |
* | |
* It is used internally by the likes of PIXI.Sprite. | |
* | |
* @class PIXI.DisplayObject | |
* @constructor | |
*/ | |
PIXI.DisplayObject = function () { | |
/** | |
* The coordinates, in pixels, of this DisplayObject, relative to its parent container. | |
* | |
* The value of this property does not reflect any positioning happening further up the display list. | |
* To obtain that value please see the `worldPosition` property. | |
* | |
* @property {PIXI.Point} position | |
* @default | |
*/ | |
this.position = new PIXI.Point(0, 0); | |
/** | |
* The scale of this DisplayObject. A scale of 1:1 represents the DisplayObject | |
* at its default size. A value of 0.5 would scale this DisplayObject by half, and so on. | |
* | |
* The value of this property does not reflect any scaling happening further up the display list. | |
* To obtain that value please see the `worldScale` property. | |
* | |
* @property {PIXI.Point} scale | |
* @default | |
*/ | |
this.scale = new PIXI.Point(1, 1); | |
/** | |
* The pivot point of this DisplayObject that it rotates around. The values are expressed | |
* in pixel values. | |
* @property {PIXI.Point} pivot | |
* @default | |
*/ | |
this.pivot = new PIXI.Point(0, 0); | |
/** | |
* The rotation of this DisplayObject. The value is given, and expressed, in radians, and is based on | |
* a right-handed orientation. | |
* | |
* The value of this property does not reflect any rotation happening further up the display list. | |
* To obtain that value please see the `worldRotation` property. | |
* | |
* @property {number} rotation | |
* @default | |
*/ | |
this.rotation = 0; | |
/** | |
* The alpha value of this DisplayObject. A value of 1 is fully opaque. A value of 0 is transparent. | |
* Please note that an object with an alpha value of 0 is skipped during the render pass. | |
* | |
* The value of this property does not reflect any alpha values set further up the display list. | |
* To obtain that value please see the `worldAlpha` property. | |
* | |
* @property {number} alpha | |
* @default | |
*/ | |
this.alpha = 1; | |
/** | |
* The visibility of this DisplayObject. A value of `false` makes the object invisible. | |
* A value of `true` makes it visible. Please note that an object with a visible value of | |
* `false` is skipped during the render pass. Equally a DisplayObject with visible false will | |
* not render any of its children. | |
* | |
* The value of this property does not reflect any visible values set further up the display list. | |
* To obtain that value please see the `worldVisible` property. | |
* | |
* @property {boolean} visible | |
* @default | |
*/ | |
this.visible = true; | |
/** | |
* This is the defined area that will pick up mouse / touch events. It is null by default. | |
* Setting it is a neat way of optimising the hitTest function that the interactionManager will use (as it will not need to hit test all the children) | |
* | |
* @property hitArea | |
* @type Rectangle|Circle|Ellipse|Polygon | |
*/ | |
this.hitArea = null; | |
/** | |
* Should this DisplayObject be rendered by the renderer? An object with a renderable value of | |
* `false` is skipped during the render pass. | |
* | |
* @property {boolean} renderable | |
* @default | |
*/ | |
this.renderable = false; | |
/** | |
* The parent DisplayObjectContainer that this DisplayObject is a child of. | |
* All DisplayObjects must belong to a parent in order to be rendered. | |
* The root parent is the Stage object. This property is set automatically when the | |
* DisplayObject is added to, or removed from, a DisplayObjectContainer. | |
* | |
* @property {PIXI.DisplayObjectContainer} parent | |
* @default | |
* @readOnly | |
*/ | |
this.parent = null; | |
/** | |
* The multiplied alpha value of this DisplayObject. A value of 1 is fully opaque. A value of 0 is transparent. | |
* This value is the calculated total, based on the alpha values of all parents of this DisplayObjects | |
* in the display list. | |
* | |
* To obtain, and set, the local alpha value, see the `alpha` property. | |
* | |
* Note: This property is only updated at the end of the `updateTransform` call, once per render. Until | |
* that happens this property will contain values based on the previous frame. Be mindful of this if | |
* accessing this property outside of the normal game flow, i.e. from an asynchronous event callback. | |
* | |
* @property {number} worldAlpha | |
* @readOnly | |
*/ | |
this.worldAlpha = 1; | |
/** | |
* The current transform of this DisplayObject. | |
* | |
* This property contains the calculated total, based on the transforms of all parents of this | |
* DisplayObject in the display list. | |
* | |
* Note: This property is only updated at the end of the `updateTransform` call, once per render. Until | |
* that happens this property will contain values based on the previous frame. Be mindful of this if | |
* accessing this property outside of the normal game flow, i.e. from an asynchronous event callback. | |
* | |
* @property {PIXI.Matrix} worldTransform | |
* @readOnly | |
*/ | |
this.worldTransform = new PIXI.Matrix(); | |
/** | |
* The coordinates, in pixels, of this DisplayObject within the world. | |
* | |
* This property contains the calculated total, based on the positions of all parents of this | |
* DisplayObject in the display list. | |
* | |
* Note: This property is only updated at the end of the `updateTransform` call, once per render. Until | |
* that happens this property will contain values based on the previous frame. Be mindful of this if | |
* accessing this property outside of the normal game flow, i.e. from an asynchronous event callback. | |
* | |
* @property {PIXI.Point} worldPosition | |
* @readOnly | |
*/ | |
this.worldPosition = new PIXI.Point(0, 0); | |
/** | |
* The global scale of this DisplayObject. | |
* | |
* This property contains the calculated total, based on the scales of all parents of this | |
* DisplayObject in the display list. | |
* | |
* Note: This property is only updated at the end of the `updateTransform` call, once per render. Until | |
* that happens this property will contain values based on the previous frame. Be mindful of this if | |
* accessing this property outside of the normal game flow, i.e. from an asynchronous event callback. | |
* | |
* @property {PIXI.Point} worldScale | |
* @readOnly | |
*/ | |
this.worldScale = new PIXI.Point(1, 1); | |
/** | |
* The rotation, in radians, of this DisplayObject. | |
* | |
* This property contains the calculated total, based on the rotations of all parents of this | |
* DisplayObject in the display list. | |
* | |
* Note: This property is only updated at the end of the `updateTransform` call, once per render. Until | |
* that happens this property will contain values based on the previous frame. Be mindful of this if | |
* accessing this property outside of the normal game flow, i.e. from an asynchronous event callback. | |
* | |
* @property {number} worldRotation | |
* @readOnly | |
*/ | |
this.worldRotation = 0; | |
/** | |
* The rectangular area used by filters when rendering a shader for this DisplayObject. | |
* | |
* @property {PIXI.Rectangle} filterArea | |
* @type Rectangle | |
* @default | |
*/ | |
this.filterArea = null; | |
/** | |
* @property {number} _sr - Cached rotation value. | |
* @private | |
*/ | |
this._sr = 0; | |
/** | |
* @property {number} _cr - Cached rotation value. | |
* @private | |
*/ | |
this._cr = 1; | |
/** | |
* @property {PIXI.Rectangle} _bounds - The cached bounds of this object. | |
* @private | |
*/ | |
this._bounds = new PIXI.Rectangle(0, 0, 0, 0); | |
/** | |
* @property {PIXI.Rectangle} _currentBounds - The most recently calculated bounds of this object. | |
* @private | |
*/ | |
this._currentBounds = null; | |
/** | |
* @property {PIXI.Rectangle} _mask - The cached mask of this object. | |
* @private | |
*/ | |
this._mask = null; | |
/** | |
* @property {boolean} _cacheAsBitmap - Internal cache as bitmap flag. | |
* @private | |
*/ | |
this._cacheAsBitmap = false; | |
/** | |
* @property {boolean} _cacheIsDirty - Internal dirty cache flag. | |
* @private | |
*/ | |
this._cacheIsDirty = false; | |
}; | |
PIXI.DisplayObject.prototype.constructor = PIXI.DisplayObject; | |
PIXI.DisplayObject.prototype = { | |
/** | |
* Destroy this DisplayObject. | |
* | |
* Removes any cached sprites, sets renderable flag to false, and nulls filters, bounds and mask. | |
* | |
* Also iteratively calls `destroy` on any children. | |
* | |
* @method PIXI.DisplayObject#destroy | |
*/ | |
destroy: function () { | |
if (this.children) | |
{ | |
var i = this.children.length; | |
while (i--) | |
{ | |
this.children[i].destroy(); | |
} | |
this.children = []; | |
} | |
this.hitArea = null; | |
this.parent = null; | |
this.worldTransform = null; | |
this.filterArea = null; | |
this.renderable = false; | |
this._bounds = null; | |
this._currentBounds = null; | |
this._mask = null; | |
this._destroyCachedSprite(); | |
}, | |
/* | |
* Updates the transform matrix this DisplayObject uses for rendering. | |
* | |
* If the object has no parent, and no parent parameter is provided, it will default to | |
* Phaser.Game.World as the parent transform to use. If that is unavailable the transform fails to take place. | |
* | |
* The `parent` parameter has priority over the actual parent. Use it as a parent override. | |
* Setting it does **not** change the actual parent of this DisplayObject. | |
* | |
* Calling this method updates the `worldTransform`, `worldAlpha`, `worldPosition`, `worldScale` | |
* and `worldRotation` properties. | |
* | |
* If a `transformCallback` has been specified, it is called at the end of this method, and is passed | |
* the new, updated, worldTransform property, along with the parent transform used. | |
* | |
* @method PIXI.DisplayObject#updateTransform | |
* @param {PIXI.DisplayObjectContainer} [parent] - Optional parent to calculate this DisplayObjects transform from. | |
* @return {PIXI.DisplayObject} - A reference to this DisplayObject. | |
*/ | |
updateTransform: function (parent) { | |
if (!parent && !this.parent && !this.game) | |
{ | |
return this; | |
} | |
var p = this.parent; | |
if (parent) | |
{ | |
p = parent; | |
} | |
else if (!this.parent) | |
{ | |
p = this.game.world; | |
} | |
// create some matrix refs for easy access | |
var pt = p.worldTransform; | |
var wt = this.worldTransform; | |
// temporary matrix variables | |
var a, b, c, d, tx, ty; | |
// so if rotation is between 0 then we can simplify the multiplication process.. | |
if (this.rotation % PIXI.PI_2) | |
{ | |
// check to see if the rotation is the same as the previous render. This means we only need to use sin and cos when rotation actually changes | |
if (this.rotation !== this.rotationCache) | |
{ | |
this.rotationCache = this.rotation; | |
this._sr = Math.sin(this.rotation); | |
this._cr = Math.cos(this.rotation); | |
} | |
// get the matrix values of the displayobject based on its transform properties.. | |
a = this._cr * this.scale.x; | |
b = this._sr * this.scale.x; | |
c = -this._sr * this.scale.y; | |
d = this._cr * this.scale.y; | |
tx = this.position.x; | |
ty = this.position.y; | |
// check for pivot.. not often used so geared towards that fact! | |
if (this.pivot.x || this.pivot.y) | |
{ | |
tx -= this.pivot.x * a + this.pivot.y * c; | |
ty -= this.pivot.x * b + this.pivot.y * d; | |
} | |
// concat the parent matrix with the objects transform. | |
wt.a = a * pt.a + b * pt.c; | |
wt.b = a * pt.b + b * pt.d; | |
wt.c = c * pt.a + d * pt.c; | |
wt.d = c * pt.b + d * pt.d; | |
wt.tx = tx * pt.a + ty * pt.c + pt.tx; | |
wt.ty = tx * pt.b + ty * pt.d + pt.ty; | |
} | |
else | |
{ | |
// lets do the fast version as we know there is no rotation.. | |
a = this.scale.x; | |
d = this.scale.y; | |
tx = this.position.x - this.pivot.x * a; | |
ty = this.position.y - this.pivot.y * d; | |
wt.a = a * pt.a; | |
wt.b = a * pt.b; | |
wt.c = d * pt.c; | |
wt.d = d * pt.d; | |
wt.tx = tx * pt.a + ty * pt.c + pt.tx; | |
wt.ty = tx * pt.b + ty * pt.d + pt.ty; | |
} | |
// Set the World values | |
this.worldAlpha = this.alpha * p.worldAlpha; | |
this.worldPosition.set(wt.tx, wt.ty); | |
this.worldScale.set(this.scale.x * Math.sqrt(wt.a * wt.a + wt.c * wt.c), this.scale.y * Math.sqrt(wt.b * wt.b + wt.d * wt.d)); | |
this.worldRotation = Math.atan2(-wt.c, wt.d); | |
// reset the bounds each time this is called! | |
this._currentBounds = null; | |
// Custom callback? | |
if (this.transformCallback) | |
{ | |
this.transformCallback.call(this.transformCallbackContext, wt, pt); | |
} | |
return this; | |
}, | |
/** | |
* To be overridden by classes that require it. | |
* | |
* @method PIXI.DisplayObject#preUpdate | |
*/ | |
preUpdate: function () { | |
}, | |
/** | |
* Generates a RenderTexture based on this DisplayObject, which can they be used to texture other Sprites. | |
* This can be useful if your DisplayObject is static, or complicated, and needs to be reused multiple times. | |
* | |
* Please note that no garbage collection takes place on old textures. It is up to you to destroy old textures, | |
* and references to them, so they don't linger in memory. | |
* | |
* @method PIXI.DisplayObject#generateTexture | |
* @param {number} [resolution=1] - The resolution of the texture being generated. | |
* @param {number} [scaleMode=PIXI.scaleModes.DEFAULT] - See {{#crossLink "PIXI/scaleModes:property"}}PIXI.scaleModes{{/crossLink}} for possible values. | |
* @param {PIXI.CanvasRenderer|PIXI.WebGLRenderer} renderer - The renderer used to generate the texture. | |
* @return {PIXI.RenderTexture} - A RenderTexture containing an image of this DisplayObject at the time it was invoked. | |
*/ | |
generateTexture: function (resolution, scaleMode, renderer) { | |
var bounds = this.getLocalBounds(); | |
var renderTexture = new PIXI.RenderTexture(bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); | |
PIXI.DisplayObject._tempMatrix.tx = -bounds.x; | |
PIXI.DisplayObject._tempMatrix.ty = -bounds.y; | |
renderTexture.render(this, PIXI.DisplayObject._tempMatrix); | |
return renderTexture; | |
}, | |
/** | |
* If this DisplayObject has a cached Sprite, this method generates and updates it. | |
* | |
* @method PIXI.DisplayObject#updateCache | |
* @return {PIXI.DisplayObject} - A reference to this DisplayObject. | |
*/ | |
updateCache: function () { | |
this._generateCachedSprite(); | |
return this; | |
}, | |
/** | |
* Calculates the global position of this DisplayObject, based on the position given. | |
* | |
* @method PIXI.DisplayObject#toGlobal | |
* @param {PIXI.Point} position - The global position to calculate from. | |
* @return {PIXI.Point} - A point object representing the position of this DisplayObject based on the global position given. | |
*/ | |
toGlobal: function (position) { | |
this.updateTransform(); | |
return this.worldTransform.apply(position); | |
}, | |
/** | |
* Calculates the local position of this DisplayObject, relative to another point. | |
* | |
* @method PIXI.DisplayObject#toLocal | |
* @param {PIXI.Point} position - The world origin to calculate from. | |
* @param {PIXI.DisplayObject} [from] - An optional DisplayObject to calculate the global position from. | |
* @return {PIXI.Point} - A point object representing the position of this DisplayObject based on the global position given. | |
*/ | |
toLocal: function (position, from) { | |
if (from) | |
{ | |
position = from.toGlobal(position); | |
} | |
this.updateTransform(); | |
return this.worldTransform.applyInverse(position); | |
}, | |
/** | |
* Internal method. | |
* | |
* @method PIXI.DisplayObject#_renderCachedSprite | |
* @private | |
* @param {Object} renderSession - The render session | |
*/ | |
_renderCachedSprite: function (renderSession) { | |
this._cachedSprite.worldAlpha = this.worldAlpha; | |
if (renderSession.gl) | |
{ | |
PIXI.Sprite.prototype._renderWebGL.call(this._cachedSprite, renderSession); | |
} | |
else | |
{ | |
PIXI.Sprite.prototype._renderCanvas.call(this._cachedSprite, renderSession); | |
} | |
}, | |
/** | |
* Internal method. | |
* | |
* @method PIXI.DisplayObject#_generateCachedSprite | |
* @private | |
*/ | |
_generateCachedSprite: function () { | |
this._cacheAsBitmap = false; | |
var bounds = this.getLocalBounds(); | |
// Round it off and force non-zero dimensions | |
bounds.width = Math.max(1, Math.ceil(bounds.width)); | |
bounds.height = Math.max(1, Math.ceil(bounds.height)); | |
this.updateTransform(); | |
if (!this._cachedSprite) | |
{ | |
var renderTexture = new PIXI.RenderTexture(bounds.width, bounds.height); | |
this._cachedSprite = new PIXI.Sprite(renderTexture); | |
this._cachedSprite.worldTransform = this.worldTransform; | |
} | |
else | |
{ | |
this._cachedSprite.texture.resize(bounds.width, bounds.height); | |
} | |
// Remove filters | |
var tempFilters = this._filters; | |
this._filters = null; | |
this._cachedSprite.filters = tempFilters; | |
PIXI.DisplayObject._tempMatrix.tx = -bounds.x; | |
PIXI.DisplayObject._tempMatrix.ty = -bounds.y; | |
this._cachedSprite.texture.render(this, PIXI.DisplayObject._tempMatrix, true); | |
this._cachedSprite.anchor.x = -(bounds.x / bounds.width); | |
this._cachedSprite.anchor.y = -(bounds.y / bounds.height); | |
this._filters = tempFilters; | |
this._cacheAsBitmap = true; | |
}, | |
/** | |
* Destroys a cached Sprite. | |
* | |
* @method PIXI.DisplayObject#_destroyCachedSprite | |
* @private | |
*/ | |
_destroyCachedSprite: function () { | |
if (!this._cachedSprite) | |
{ | |
return; | |
} | |
this._cachedSprite.texture.destroy(true); | |
this._cachedSprite = null; | |
} | |
}; | |
// Alias for updateTransform. As used in DisplayObject container, etc. | |
PIXI.DisplayObject.prototype.displayObjectUpdateTransform = PIXI.DisplayObject.prototype.updateTransform; | |
Object.defineProperties(PIXI.DisplayObject.prototype, { | |
/** | |
* The horizontal position of the DisplayObject, in pixels, relative to its parent. | |
* If you need the world position of the DisplayObject, use `DisplayObject.worldPosition` instead. | |
* @name PIXI.DisplayObject#x | |
* @property {number} x - The horizontal position of the DisplayObject, in pixels, relative to its parent. | |
*/ | |
'x': { | |
get: function () { | |
return this.position.x; | |
}, | |
set: function (value) { | |
this.position.x = value; | |
} | |
}, | |
/** | |
* The vertical position of the DisplayObject, in pixels, relative to its parent. | |
* If you need the world position of the DisplayObject, use `DisplayObject.worldPosition` instead. | |
* @name PIXI.DisplayObject#y | |
* @property {number} y - The vertical position of the DisplayObject, in pixels, relative to its parent. | |
*/ | |
'y': { | |
get: function () { | |
return this.position.y; | |
}, | |
set: function (value) { | |
this.position.y = value; | |
} | |
}, | |
/** | |
* Indicates if this DisplayObject is visible, based on it, and all of its parents, `visible` property values. | |
* @name PIXI.DisplayObject#worldVisible | |
* @property {boolean} worldVisible - Indicates if this DisplayObject is visible, based on it, and all of its parents, `visible` property values. | |
*/ | |
'worldVisible': { | |
get: function () { | |
if (!this.visible) | |
{ | |
return false; | |
} | |
else | |
{ | |
var item = this.parent; | |
if (!item) | |
{ | |
return this.visible; | |
} | |
else | |
{ | |
do | |
{ | |
if (!item.visible) | |
{ | |
return false; | |
} | |
item = item.parent; | |
} | |
while (item); | |
} | |
return true; | |
} | |
} | |
}, | |
/** | |
* Sets a mask for this DisplayObject. A mask is an instance of a Graphics object. | |
* When applied it limits the visible area of this DisplayObject to the shape of the mask. | |
* Under a Canvas renderer it uses shape clipping. Under a WebGL renderer it uses a Stencil Buffer. | |
* To remove a mask, set this property to `null`. | |
* | |
* @name PIXI.DisplayObject#mask | |
* @property {PIXI.Graphics} mask - The mask applied to this DisplayObject. Set to `null` to remove an existing mask. | |
*/ | |
'mask': { | |
get: function () { | |
return this._mask; | |
}, | |
set: function (value) { | |
if (this._mask) | |
{ | |
this._mask.isMask = false; | |
} | |
this._mask = value; | |
if (value) | |
{ | |
this._mask.isMask = true; | |
} | |
} | |
}, | |
/** | |
* Sets the filters for this DisplayObject. This is a WebGL only feature, and is ignored by the Canvas | |
* Renderer. A filter is a shader applied to this DisplayObject. You can modify the placement of the filter | |
* using `DisplayObject.filterArea`. | |
* | |
* To remove filters, set this property to `null`. | |
* | |
* Note: You cannot have a filter set, and a MULTIPLY Blend Mode active, at the same time. Setting a | |
* filter will reset this DisplayObjects blend mode to NORMAL. | |
* | |
* @name PIXI.DisplayObject#filters | |
* @property {Array} filters - An Array of PIXI.AbstractFilter objects, or objects that extend them. | |
*/ | |
'filters': { | |
get: function () { | |
return this._filters; | |
}, | |
set: function (value) { | |
if (Array.isArray(value)) | |
{ | |
// Put all the passes in one place. | |
var passes = []; | |
for (var i = 0; i < value.length; i++) | |
{ | |
var filterPasses = value[i].passes; | |
for (var j = 0; j < filterPasses.length; j++) | |
{ | |
passes.push(filterPasses[j]); | |
} | |
} | |
// Needed any more? | |
this._filterBlock = { target: this, filterPasses: passes }; | |
} | |
this._filters = value; | |
if (this.blendMode && this.blendMode === PIXI.blendModes.MULTIPLY) | |
{ | |
this.blendMode = PIXI.blendModes.NORMAL; | |
} | |
} | |
}, | |
/** | |
* Sets if this DisplayObject should be cached as a bitmap. | |
* | |
* When invoked it will take a snapshot of the DisplayObject, as it is at that moment, and store it | |
* in a RenderTexture. This is then used whenever this DisplayObject is rendered. It can provide a | |
* performance benefit for complex, but static, DisplayObjects. I.e. those with lots of children. | |
* | |
* Cached Bitmaps do not track their parents. If you update a property of this DisplayObject, it will not | |
* re-generate the cached bitmap automatically. To do that you need to call `DisplayObject.updateCache`. | |
* | |
* To remove a cached bitmap, set this property to `null`. | |
* | |
* @name PIXI.DisplayObject#cacheAsBitmap | |
* @property {boolean} cacheAsBitmap - Cache this DisplayObject as a Bitmap. Set to `null` to remove an existing cached bitmap. | |
*/ | |
'cacheAsBitmap': { | |
get: function () { | |
return this._cacheAsBitmap; | |
}, | |
set: function (value) { | |
if (this._cacheAsBitmap === value) | |
{ | |
return; | |
} | |
if (value) | |
{ | |
this._generateCachedSprite(); | |
} | |
else | |
{ | |
this._destroyCachedSprite(); | |
} | |
this._cacheAsBitmap = value; | |
} | |
} | |
}); | |
/** | |
* @author Mat Groves http://matgroves.com/ @Doormat23 | |
*/ | |
/** | |
* A DisplayObjectContainer represents a collection of display objects. | |
* It is the base class of all display objects that act as a container for other objects. | |
* | |
* @class DisplayObjectContainer | |
* @extends DisplayObject | |
* @constructor | |
*/ | |
PIXI.DisplayObjectContainer = function () { | |
PIXI.DisplayObject.call(this); | |
/** | |
* [read-only] The array of children of this container. | |
* | |
* @property children | |
* @type Array(DisplayObject) | |
* @readOnly | |
*/ | |
this.children = []; | |
/** | |
* If `ignoreChildInput` is `false` it will allow this objects _children_ to be considered as valid for Input events. | |
* | |
* If this property is `true` then the children will _not_ be considered as valid for Input events. | |
* | |
* Note that this property isn't recursive: only immediate children are influenced, it doesn't scan further down. | |
* @property {boolean} ignoreChildInput | |
* @default | |
*/ | |
this.ignoreChildInput = false; | |
}; | |
PIXI.DisplayObjectContainer.prototype = Object.create( PIXI.DisplayObject.prototype ); | |
PIXI.DisplayObjectContainer.prototype.constructor = PIXI.DisplayObjectContainer; | |
/** | |
* Adds a child to the container. | |
* | |
* @method addChild | |
* @param child {DisplayObject} The DisplayObject to add to the container | |
* @return {DisplayObject} The child that was added. | |
*/ | |
PIXI.DisplayObjectContainer.prototype.addChild = function (child) { | |
return this.addChildAt(child, this.children.length); | |
}; | |
/** | |
* Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown | |
* | |
* @method addChildAt | |
* @param child {DisplayObject} The child to add | |
* @param index {Number} The index to place the child in | |
* @return {DisplayObject} The child that was added. | |
*/ | |
PIXI.DisplayObjectContainer.prototype.addChildAt = function (child, index) { | |
if (index >= 0 && index <= this.children.length) | |
{ | |
if (child.parent) | |
{ | |
child.parent.removeChild(child); | |
} | |
child.parent = this; | |
this.children.splice(index, 0, child); | |
return child; | |
} | |
else | |
{ | |
throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); | |
} | |
}; | |
/** | |
* Swaps the position of 2 Display Objects within this container. | |
* | |
* @method swapChildren | |
* @param child {DisplayObject} | |
* @param child2 {DisplayObject} | |
*/ | |
PIXI.DisplayObjectContainer.prototype.swapChildren = function (child, child2) { | |
if (child === child2) | |
{ | |
return; | |
} | |
var index1 = this.getChildIndex(child); | |
var index2 = this.getChildIndex(child2); | |
if (index1 < 0 || index2 < 0) | |
{ | |
throw new Error('swapChildren: Both the supplied DisplayObjects must be a child of the caller.'); | |
} | |
this.children[index1] = child2; | |
this.children[index2] = child; | |
}; | |
/** | |
* Returns the index position of a child DisplayObject instance | |
* | |
* @method getChildIndex | |
* @param child {DisplayObject} The DisplayObject instance to identify | |
* @return {Number} The index position of the child display object to identify | |
*/ | |
PIXI.DisplayObjectContainer.prototype.getChildIndex = function (child) { | |
var index = this.children.indexOf(child); | |
if (index === -1) | |
{ | |
throw new Error('The supplied DisplayObject must be a child of the caller'); | |
} | |
return index; | |
}; | |
/** | |
* Changes the position of an existing child in the display object container | |
* | |
* @method setChildIndex | |
* @param child {DisplayObject} The child DisplayObject instance for which you want to change the index number | |
* @param index {Number} The resulting index number for the child display object | |
*/ | |
PIXI.DisplayObjectContainer.prototype.setChildIndex = function (child, index) { | |
if (index < 0 || index >= this.children.length) | |
{ | |
throw new Error('The supplied index is out of bounds'); | |
} | |
var currentIndex = this.getChildIndex(child); | |
this.children.splice(currentIndex, 1); //remove from old position | |
this.children.splice(index, 0, child); //add at new position | |
}; | |
/** | |
* Returns the child at the specified index | |
* | |
* @method getChildAt | |
* @param index {Number} The index to get the child from | |
* @return {DisplayObject} The child at the given index, if any. | |
*/ | |
PIXI.DisplayObjectContainer.prototype.getChildAt = function (index) { | |
if (index < 0 || index >= this.children.length) | |
{ | |
throw new Error('getChildAt: Supplied index '+ index +' does not exist in the child list, or the supplied DisplayObject must be a child of the caller'); | |
} | |
return this.children[index]; | |
}; | |
/** | |
* Removes a child from the container. | |
* | |
* @method removeChild | |
* @param child {DisplayObject} The DisplayObject to remove | |
* @return {DisplayObject} The child that was removed. | |
*/ | |
PIXI.DisplayObjectContainer.prototype.removeChild = function (child) { | |
var index = this.children.indexOf(child); | |
if (index === -1) | |
{ | |
return; | |
} | |
return this.removeChildAt(index); | |
}; | |
/** | |
* Removes a child from the specified index position. | |
* | |
* @method removeChildAt | |
* @param index {Number} The index to get the child from | |
* @return {DisplayObject} The child that was removed. | |
*/ | |
PIXI.DisplayObjectContainer.prototype.removeChildAt = function (index) { | |
var child = this.getChildAt(index); | |
if (child) | |
{ | |
child.parent = undefined; | |
this.children.splice(index, 1); | |
} | |
return child; | |
}; | |
/** | |
* Removes all children from this container that are within the begin and end indexes. | |
* | |
* @method removeChildren | |
* @param beginIndex {Number} The beginning position. Default value is 0. | |
* @param endIndex {Number} The ending position. Default value is size of the container. | |
*/ | |
PIXI.DisplayObjectContainer.prototype.removeChildren = function (beginIndex, endIndex) { | |
if (beginIndex === undefined) { beginIndex = 0; } | |
if (endIndex === undefined) { endIndex = this.children.length; } | |
var range = endIndex - beginIndex; | |
if (range > 0 && range <= endIndex) | |
{ | |
var removed = this.children.splice(begin, range); | |
for (var i = 0; i < removed.length; i++) | |
{ | |
var child = removed[i]; | |
child.parent = undefined; | |
} | |
return removed; | |
} | |
else if (range === 0 && this.children.length === 0) | |
{ | |
return []; | |
} | |
else | |
{ | |
throw new Error( 'removeChildren: Range Error, numeric values are outside the acceptable range' ); | |
} | |
}; | |
/* | |
* Updates the transform on all children of this container for rendering | |
* | |
* @method updateTransform | |
* @private | |
*/ | |
PIXI.DisplayObjectContainer.prototype.updateTransform = function () { | |
if (!this.visible) | |
{ | |
return; | |
} | |
this.displayObjectUpdateTransform(); | |
if (this._cacheAsBitmap) | |
{ | |
return; | |
} | |
for (var i = 0; i < this.children.length; i++) | |
{ | |
this.children[i].updateTransform(); | |
} | |
}; | |
// performance increase to avoid using call.. (10x faster) | |
PIXI.DisplayObjectContainer.prototype.displayObjectContainerUpdateTransform = PIXI.DisplayObjectContainer.prototype.updateTransform; | |
/** | |
* Retrieves the global bounds of the displayObjectContainer as a rectangle. The bounds calculation takes all visible children into consideration. | |
* | |
* @method getBounds | |
* @param {PIXI.DisplayObject|PIXI.Matrix} [targetCoordinateSpace] Returns a rectangle that defines the area of the display object relative to the coordinate system of the targetCoordinateSpace object. | |
* @return {Rectangle} The rectangular bounding area | |
*/ | |
PIXI.DisplayObjectContainer.prototype.getBounds = function (targetCoordinateSpace) { | |
var isTargetCoordinateSpaceDisplayObject = (targetCoordinateSpace && targetCoordinateSpace instanceof PIXI.DisplayObject); | |
var isTargetCoordinateSpaceThisOrParent = true; | |
if (!isTargetCoordinateSpaceDisplayObject) | |
{ | |
targetCoordinateSpace = this; | |
} | |
else if (targetCoordinateSpace instanceof PIXI.DisplayObjectContainer) | |
{ | |
isTargetCoordinateSpaceThisOrParent = targetCoordinateSpace.contains(this); | |
} | |
else | |
{ | |
isTargetCoordinateSpaceThisOrParent = false; | |
} | |
var i; | |
if (isTargetCoordinateSpaceDisplayObject) | |
{ | |
var matrixCache = targetCoordinateSpace.worldTransform; | |
targetCoordinateSpace.worldTransform = PIXI.identityMatrix; | |
for (i = 0; i < targetCoordinateSpace.children.length; i++) | |
{ | |
targetCoordinateSpace.children[i].updateTransform(); | |
} | |
} | |
var minX = Infinity; | |
var minY = Infinity; | |
var maxX = -Infinity; | |
var maxY = -Infinity; | |
var childBounds; | |
var childMaxX; | |
var childMaxY; | |
var childVisible = false; | |
for (i = 0; i < this.children.length; i++) | |
{ | |
var child = this.children[i]; | |
if (!child.visible) | |
{ | |
continue; | |
} | |
childVisible = true; | |
childBounds = this.children[i].getBounds(); | |
minX = (minX < childBounds.x) ? minX : childBounds.x; | |
minY = (minY < childBounds.y) ? minY : childBounds.y; | |
childMaxX = childBounds.width + childBounds.x; | |
childMaxY = childBounds.height + childBounds.y; | |
maxX = (maxX > childMaxX) ? maxX : childMaxX; | |
maxY = (maxY > childMaxY) ? maxY : childMaxY; | |
} | |
var bounds = this._bounds; | |
if (!childVisible) | |
{ | |
bounds = new PIXI.Rectangle(); | |
var w0 = bounds.x; | |
var w1 = bounds.width + bounds.x; | |
var h0 = bounds.y; | |
var h1 = bounds.height + bounds.y; | |
var worldTransform = this.worldTransform; | |
var a = worldTransform.a; | |
var b = worldTransform.b; | |
var c = worldTransform.c; | |
var d = worldTransform.d; | |
var tx = worldTransform.tx; | |
var ty = worldTransform.ty; | |
var x1 = a * w1 + c * h1 + tx; | |
var y1 = d * h1 + b * w1 + ty; | |
var x2 = a * w0 + c * h1 + tx; | |
var y2 = d * h1 + b * w0 + ty; | |
var x3 = a * w0 + c * h0 + tx; | |
var y3 = d * h0 + b * w0 + ty; | |
var x4 = a * w1 + c * h0 + tx; | |
var y4 = d * h0 + b * w1 + ty; | |
maxX = x1; | |
maxY = y1; | |
minX = x1; | |
minY = y1; | |
minX = x2 < minX ? x2 : minX; | |
minX = x3 < minX ? x3 : minX; | |
minX = x4 < minX ? x4 : minX; | |
minY = y2 < minY ? y2 : minY; | |
minY = y3 < minY ? y3 : minY; | |
minY = y4 < minY ? y4 : minY; | |
maxX = x2 > maxX ? x2 : maxX; | |
maxX = x3 > maxX ? x3 : maxX; | |
maxX = x4 > maxX ? x4 : maxX; | |
maxY = y2 > maxY ? y2 : maxY; | |
maxY = y3 > maxY ? y3 : maxY; | |
maxY = y4 > maxY ? y4 : maxY; | |
} | |
bounds.x = minX; | |
bounds.y = minY; | |
bounds.width = maxX - minX; | |
bounds.height = maxY - minY; | |
if (isTargetCoordinateSpaceDisplayObject) | |
{ | |
targetCoordinateSpace.worldTransform = matrixCache; | |
for (i = 0; i < targetCoordinateSpace.children.length; i++) | |
{ | |
targetCoordinateSpace.children[i].updateTransform(); | |
} | |
} | |
if (!isTargetCoordinateSpaceThisOrParent) | |
{ | |
var targetCoordinateSpaceBounds = targetCoordinateSpace.getBounds(); | |
bounds.x -= targetCoordinateSpaceBounds.x; | |
bounds.y -= targetCoordinateSpaceBounds.y; | |
} | |
return bounds; | |
}; | |
/** | |
* Retrieves the non-global local bounds of the displayObjectContainer as a rectangle without any transformations. The calculation takes all visible children into consideration. | |
* | |
* @method getLocalBounds | |
* @return {Rectangle} The rectangular bounding area | |
*/ | |
PIXI.DisplayObjectContainer.prototype.getLocalBounds = function () { | |
return this.getBounds(this); | |
}; | |
/** | |
* Determines whether the specified display object is a child of the DisplayObjectContainer instance or the instance itself. | |
* | |
* @method contains | |
* @param {DisplayObject} child | |
* @returns {boolean} | |
*/ | |
PIXI.DisplayObjectContainer.prototype.contains = function (child) { | |
if (!child) | |
{ | |
return false; | |
} | |
else if (child === this) | |
{ | |
return true; | |
} | |
else | |
{ | |
return this.contains(child.parent); | |
} | |
}; | |
/** | |
* Renders the object using the WebGL renderer | |
* | |
* @method _renderWebGL | |
* @param renderSession {RenderSession} | |
* @private | |
*/ | |
PIXI.DisplayObjectContainer.prototype._renderWebGL = function (renderSession) { | |
if (!this.visible || this.alpha <= 0) | |
{ | |
return; | |
} | |
if (this._cacheAsBitmap) | |
{ | |
this._renderCachedSprite(renderSession); | |
return; | |
} | |
var i; | |
if (this._mask || this._filters) | |
{ | |
// push filter first as we need to ensure the stencil buffer is correct for any masking | |
if (this._filters) | |
{ | |
renderSession.spriteBatch.flush(); | |
renderSession.filterManager.pushFilter(this._filterBlock); | |
} | |
if (this._mask) | |
{ | |
renderSession.spriteBatch.stop(); | |
renderSession.maskManager.pushMask(this.mask, renderSession); | |
renderSession.spriteBatch.start(); | |
} | |
// simple render children! | |
for (i = 0; i < this.children.length; i++) | |
{ | |
this.children[i]._renderWebGL(renderSession); | |
} | |
renderSession.spriteBatch.stop(); | |
if (this._mask) renderSession.maskManager.popMask(this._mask, renderSession); | |
if (this._filters) renderSession.filterManager.popFilter(); | |
renderSession.spriteBatch.start(); | |
} | |
else | |
{ | |
// simple render children! | |
for (i = 0; i < this.children.length; i++) | |
{ | |
this.children[i]._renderWebGL(renderSession); | |
} | |
} | |
}; | |
/** | |
* Renders the object using the Canvas renderer | |
* | |
* @method _renderCanvas | |
* @param renderSession {RenderSession} | |
* @private | |
*/ | |
PIXI.DisplayObjectContainer.prototype._renderCanvas = function (renderSession) { | |
if (this.visible === false || this.alpha === 0) | |
{ | |
return; | |
} | |
if (this._cacheAsBitmap) | |
{ | |
this._renderCachedSprite(renderSession); | |
return; | |
} | |
if (this._mask) | |
{ | |
renderSession.maskManager.pushMask(this._mask, renderSession); | |
} | |
for (var i = 0; i < this.children.length; i++) | |
{ | |
this.children[i]._renderCanvas(renderSession); | |
} | |
if (this._mask) | |
{ | |
renderSession.maskManager.popMask(renderSession); | |
} | |
}; | |
/** | |
* The width of the displayObjectContainer, setting this will actually modify the scale to achieve the value set | |
* | |
* @property width | |
* @type Number | |
*/ | |
Object.defineProperty(PIXI.DisplayObjectContainer.prototype, 'width', { | |
get: function() { | |
return this.getLocalBounds().width * this.scale.x; | |
}, | |
set: function(value) { | |
var width = this.getLocalBounds().width; | |
if (width !== 0) | |
{ | |
this.scale.x = value / width; | |
} | |
else | |
{ | |
this.scale.x = 1; | |
} | |
this._width = value; | |
} | |
}); | |
/** | |
* The height of the displayObjectContainer, setting this will actually modify the scale to achieve the value set | |
* | |
* @property height | |
* @type Number | |
*/ | |
Object.defineProperty(PIXI.DisplayObjectContainer.prototype, 'height', { | |
get: function() { | |
return this.getLocalBounds().height * this.scale.y; | |
}, | |
set: function(value) { | |
var height = this.getLocalBounds().height; | |
if (height !== 0) | |
{ | |
this.scale.y = value / height; | |
} | |
else | |
{ | |
this.scale.y = 1; | |
} | |
this._height = value; | |
} | |
}); | |
/** | |
* @author Mat Groves http://matgroves.com/ @Doormat23 | |
*/ | |
/** | |
* The Sprite object is the base for all textured objects that are rendered to the screen | |
* | |
* @class Sprite | |
* @extends DisplayObjectContainer | |
* @constructor | |
* @param texture {Texture} The texture for this sprite | |
*/ | |
PIXI.Sprite = function (texture) { | |
PIXI.DisplayObjectContainer.call(this); | |
/** | |
* The anchor sets the origin point of the texture. | |
* The default is 0,0 this means the texture's origin is the top left | |
* Setting than anchor to 0.5,0.5 means the textures origin is centered | |
* Setting the anchor to 1,1 would mean the textures origin points will be the bottom right corner | |
* | |
* @property anchor | |
* @type Point | |
*/ | |
this.anchor = new PIXI.Point(); | |
/** | |
* The texture that the sprite is using | |
* | |
* @property texture | |
* @type Texture | |
*/ | |
this.texture = texture || PIXI.Texture.emptyTexture; | |
/** | |
* The width of the sprite (this is initially set by the texture) | |
* | |
* @property _width | |
* @type Number | |
* @private | |
*/ | |
this._width = 0; | |
/** | |
* The height of the sprite (this is initially set by the texture) | |
* | |
* @property _height | |
* @type Number | |
* @private | |
*/ | |
this._height = 0; | |
/** | |
* The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. | |
* | |
* @property tint | |
* @type Number | |
* @default 0xFFFFFF | |
*/ | |
this.tint = 0xFFFFFF; | |
/** | |
* The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. | |
* | |
* @property cachedTint | |
* @private | |
* @type Number | |
* @default -1 | |
*/ | |
this.cachedTint = -1; | |
/** | |
* A canvas that contains the tinted version of the Sprite (in Canvas mode, WebGL doesn't populate this) | |
* | |
* @property tintedTexture | |
* @type Canvas | |
* @default null | |
*/ | |
this.tintedTexture = null; | |
/** | |
* The blend mode to be applied to the sprite. Set to PIXI.blendModes.NORMAL to remove any blend mode. | |
* | |
* Warning: You cannot have a blend mode and a filter active on the same Sprite. Doing so will render the sprite invisible. | |
* | |
* @property blendMode | |
* @type Number | |
* @default PIXI.blendModes.NORMAL; | |
*/ | |
this.blendMode = PIXI.blendModes.NORMAL; | |
/** | |
* The shader that will be used to render this Sprite. | |
* Set to null to remove a current shader. | |
* | |
* @property shader | |
* @type AbstractFilter | |
* @default null | |
*/ | |
this.shader = null; | |
/** | |
* Controls if this Sprite is processed by the core Phaser game loops and Group loops. | |
* | |
* @property exists | |
* @type Boolean | |
* @default true | |
*/ | |
this.exists = true; | |
if (this.texture.baseTexture.hasLoaded) | |
{ | |
this.onTextureUpdate(); | |
} | |
this.renderable = true; | |
}; | |
// constructor | |
PIXI.Sprite.prototype = Object.create(PIXI.DisplayObjectContainer.prototype); | |
PIXI.Sprite.prototype.constructor = PIXI.Sprite; | |
/** | |
* The width of the sprite, setting this will actually modify the scale to achieve the value set | |
* | |
* @property width | |
* @type Number | |
*/ | |
Object.defineProperty(PIXI.Sprite.prototype, 'width', { | |
get: function() { | |
return this.scale.x * this.texture.frame.width; | |
}, | |
set: function(value) { | |
this.scale.x = value / this.texture.frame.width; | |
this._width = value; | |
} | |
}); | |
/** | |
* The height of the sprite, setting this will actually modify the scale to achieve the value set | |
* | |
* @property height | |
* @type Number | |
*/ | |
Object.defineProperty(PIXI.Sprite.prototype, 'height', { | |
get: function() { | |
return this.scale.y * this.texture.frame.height; | |
}, | |
set: function(value) { | |
this.scale.y = value / this.texture.frame.height; | |
this._height = value; | |
} | |
}); | |
/** | |
* Sets the texture of the sprite. Be warned that this doesn't remove or destroy the previous | |
* texture this Sprite was using. | |
* | |
* @method setTexture | |
* @param texture {Texture} The PIXI texture that is displayed by the sprite | |
* @param [destroy=false] {boolean} Call Texture.destroy on the current texture before replacing it with the new one? | |
*/ | |
PIXI.Sprite.prototype.setTexture = function(texture, destroyBase) | |
{ | |
if (destroyBase !== undefined) | |
{ | |
this.texture.baseTexture.destroy(); | |
} | |
// Over-ridden by loadTexture as needed | |
this.texture.baseTexture.skipRender = false; | |
this.texture = texture; | |
this.texture.valid = true; | |
this.cachedTint = -1; | |
}; | |
/** | |
* When the texture is updated, this event will fire to update the scale and frame | |
* | |
* @method onTextureUpdate | |
* @param event | |
* @private | |
*/ | |
PIXI.Sprite.prototype.onTextureUpdate = function() | |
{ | |
// so if _width is 0 then width was not set.. | |
if (this._width) this.scale.x = this._width / this.texture.frame.width; | |
if (this._height) this.scale.y = this._height / this.texture.frame.height; | |
}; | |
/** | |
* Returns the bounds of the Sprite as a rectangle. | |
* The bounds calculation takes the worldTransform into account. | |
* | |
* It is important to note that the transform is not updated when you call this method. | |
* So if this Sprite is the child of a Display Object which has had its transform | |
* updated since the last render pass, those changes will not yet have been applied | |
* to this Sprites worldTransform. If you need to ensure that all parent transforms | |
* are factored into this getBounds operation then you should call `updateTransform` | |
* on the root most object in this Sprites display list first. | |
* | |
* @method getBounds | |
* @param matrix {Matrix} the transformation matrix of the sprite | |
* @return {Rectangle} the framing rectangle | |
*/ | |
PIXI.Sprite.prototype.getBounds = function(matrix) | |
{ | |
var width = this.texture.frame.width; | |
var height = this.texture.frame.height; | |
var w0 = width * (1-this.anchor.x); | |
var w1 = width * -this.anchor.x; | |
var h0 = height * (1-this.anchor.y); | |
var h1 = height * -this.anchor.y; | |
var worldTransform = matrix || this.worldTransform; | |
var a = worldTransform.a; | |
var b = worldTransform.b; | |
var c = worldTransform.c; | |
var d = worldTransform.d; | |
var tx = worldTransform.tx; | |
var ty = worldTransform.ty; | |
var maxX = -Infinity; | |
var maxY = -Infinity; | |
var minX = Infinity; | |
var minY = Infinity; | |
if (b === 0 && c === 0) | |
{ | |
// scale may be negative! | |
if (a < 0) | |
{ | |
a *= -1; | |
var temp = w0; | |
w0 = -w1; | |
w1 = -temp; | |
} | |
if (d < 0) | |
{ | |
d *= -1; | |
var temp = h0; | |
h0 = -h1; | |
h1 = -temp; | |
} | |
// this means there is no rotation going on right? RIGHT? | |
// if thats the case then we can avoid checking the bound values! yay | |
minX = a * w1 + tx; | |
maxX = a * w0 + tx; | |
minY = d * h1 + ty; | |
maxY = d * h0 + ty; | |
} | |
else | |
{ | |
var x1 = a * w1 + c * h1 + tx; | |
var y1 = d * h1 + b * w1 + ty; | |
var x2 = a * w0 + c * h1 + tx; | |
var y2 = d * h1 + b * w0 + ty; | |
var x3 = a * w0 + c * h0 + tx; | |
var y3 = d * h0 + b * w0 + ty; | |
var x4 = a * w1 + c * h0 + tx; | |
var y4 = d * h0 + b * w1 + ty; | |
minX = x1 < minX ? x1 : minX; | |
minX = x2 < minX ? x2 : minX; | |
minX = x3 < minX ? x3 : minX; | |
minX = x4 < minX ? x4 : minX; | |
minY = y1 < minY ? y1 : minY; | |
minY = y2 < minY ? y2 : minY; | |
minY = y3 < minY ? y3 : minY; | |
minY = y4 < minY ? y4 : minY; | |
maxX = x1 > maxX ? x1 : maxX; | |
maxX = x2 > maxX ? x2 : maxX; | |
maxX = x3 > maxX ? x3 : maxX; | |
maxX = x4 > maxX ? x4 : maxX; | |
maxY = y1 > maxY ? y1 : maxY; | |
maxY = y2 > maxY ? y2 : maxY; | |
maxY = y3 > maxY ? y3 : maxY; | |
maxY = y4 > maxY ? y4 : maxY; | |
} | |
var bounds = this._bounds; | |
bounds.x = minX; | |
bounds.width = maxX - minX; | |
bounds.y = minY; | |
bounds.height = maxY - minY; | |
// store a reference so that if this function gets called again in the render cycle we do not have to recalculate | |
this._currentBounds = bounds; | |
return bounds; | |
}; | |
/** | |
* Retrieves the non-global local bounds of the Sprite as a rectangle. The calculation takes all visible children into consideration. | |
* | |
* @method getLocalBounds | |
* @return {Rectangle} The rectangular bounding area | |
*/ | |
PIXI.Sprite.prototype.getLocalBounds = function () { | |
var matrixCache = this.worldTransform; | |
this.worldTransform = PIXI.identityMatrix; | |
for (var i = 0; i < this.children.length; i++) | |
{ | |
this.children[i].updateTransform(); | |
} | |
var bounds = this.getBounds(); | |
this.worldTransform = matrixCache; | |
for (i = 0; i < this.children.length; i++) | |
{ | |
this.children[i].updateTransform(); | |
} | |
return bounds; | |
}; | |
/** | |
* Renders the object using the WebGL renderer | |
* | |
* @method _renderWebGL | |
* @param renderSession {RenderSession} | |
* @param {Matrix} [matrix] - Optional matrix. If provided the Display Object will be rendered using this matrix, otherwise it will use its worldTransform. | |
* @private | |
*/ | |
PIXI.Sprite.prototype._renderWebGL = function(renderSession, matrix) | |
{ | |
// if the sprite is not visible or the alpha is 0 then no need to render this element | |
if (!this.visible || this.alpha <= 0 || !this.renderable) return; | |
// They provided an alternative rendering matrix, so use it | |
var wt = this.worldTransform; | |
if (matrix) | |
{ | |
wt = matrix; | |
} | |
// A quick check to see if this element has a mask or a filter. | |
if (this._mask || this._filters) | |
{ | |
var spriteBatch = renderSession.spriteBatch; | |
// push filter first as we need to ensure the stencil buffer is correct for any masking | |
if (this._filters) | |
{ | |
spriteBatch.flush(); | |
renderSession.filterManager.pushFilter(this._filterBlock); | |
} | |
if (this._mask) | |
{ | |
spriteBatch.stop(); | |
renderSession.maskManager.pushMask(this.mask, renderSession); | |
spriteBatch.start(); | |
} | |
// add this sprite to the batch | |
spriteBatch.render(this); | |
// now loop through the children and make sure they get rendered | |
for (var i = 0; i < this.children.length; i++) | |
{ | |
this.children[i]._renderWebGL(renderSession); | |
} | |
// time to stop the sprite batch as either a mask element or a filter draw will happen next | |
spriteBatch.stop(); | |
if (this._mask) renderSession.maskManager.popMask(this._mask, renderSession); | |
if (this._filters) renderSession.filterManager.popFilter(); | |
spriteBatch.start(); | |
} | |
else | |
{ | |
renderSession.spriteBatch.render(this); | |
// Render children! | |
for (var i = 0; i < this.children.length; i++) | |
{ | |
this.children[i]._renderWebGL(renderSession, wt); | |
} | |
} | |
}; | |
/** | |
* Renders the object using the Canvas renderer | |
* | |
* @method _renderCanvas | |
* @param renderSession {RenderSession} | |
* @param {Matrix} [matrix] - Optional matrix. If provided the Display Object will be rendered using this matrix, otherwise it will use its worldTransform. | |
* @private | |
*/ | |
PIXI.Sprite.prototype._renderCanvas = function(renderSession, matrix) | |
{ | |
// If the sprite is not visible or the alpha is 0 then no need to render this element | |
if (!this.visible || this.alpha === 0 || !this.renderable || this.texture.crop.width <= 0 || this.texture.crop.height <= 0) | |
{ | |
return; | |
} | |
var wt = this.worldTransform; | |
// If they provided an alternative rendering matrix then use it | |
if (matrix) | |
{ | |
wt = matrix; | |
} | |
if (this.blendMode !== renderSession.currentBlendMode) | |
{ | |
renderSession.currentBlendMode = this.blendMode; | |
renderSession.context.globalCompositeOperation = PIXI.blendModesCanvas[renderSession.currentBlendMode]; | |
} | |
if (this._mask) | |
{ | |
renderSession.maskManager.pushMask(this._mask, renderSession); | |
} | |
// Ignore null sources | |
if (this.texture.valid) | |
{ | |
var resolution = this.texture.baseTexture.resolution / renderSession.resolution; | |
renderSession.context.globalAlpha = this.worldAlpha; | |
// If smoothingEnabled is supported and we need to change the smoothing property for this texture | |
if (renderSession.smoothProperty && renderSession.scaleMode !== this.texture.baseTexture.scaleMode) | |
{ | |
renderSession.scaleMode = this.texture.baseTexture.scaleMode; | |
renderSession.context[renderSession.smoothProperty] = (renderSession.scaleMode === PIXI.scaleModes.LINEAR); | |
} | |
// If the texture is trimmed we offset by the trim x/y, otherwise we use the frame dimensions | |
var dx = (this.texture.trim) ? this.texture.trim.x - this.anchor.x * this.texture.trim.width : this.anchor.x * -this.texture.frame.width; | |
var dy = (this.texture.trim) ? this.texture.trim.y - this.anchor.y * this.texture.trim.height : this.anchor.y * -this.texture.frame.height; | |
var tx = (wt.tx * renderSession.resolution) + renderSession.shakeX; | |
var ty = (wt.ty * renderSession.resolution) + renderSession.shakeY; | |
// Allow for pixel rounding | |
if (renderSession.roundPixels) | |
{ | |
renderSession.context.setTransform(wt.a, wt.b, wt.c, wt.d, tx | 0, ty | 0); | |
dx |= 0; | |
dy |= 0; | |
} | |
else | |
{ | |
renderSession.context.setTransform(wt.a, wt.b, wt.c, wt.d, tx, ty); | |
} | |
var cw = this.texture.crop.width; | |
var ch = this.texture.crop.height; | |
dx /= resolution; | |
dy /= resolution; | |
if (this.tint !== 0xFFFFFF) | |
{ | |
if (this.texture.requiresReTint || this.cachedTint !== this.tint) | |
{ | |
this.tintedTexture = PIXI.CanvasTinter.getTintedTexture(this, this.tint); | |
this.cachedTint = this.tint; | |
this.texture.requiresReTint = false; | |
} | |
renderSession.context.drawImage(this.tintedTexture, 0, 0, cw, ch, dx, dy, cw / resolution, ch / resolution); | |
} | |
else | |
{ | |
var cx = this.texture.crop.x; | |
var cy = this.texture.crop.y; | |
renderSession.context.drawImage(this.texture.baseTexture.source, cx, cy, cw, ch, dx, dy, cw / resolution, ch / resolution); | |
} | |
} | |
for (var i = 0; i < this.children.length; i++) | |
{ | |
this.children[i]._renderCanvas(renderSession); | |
} | |
if (this._mask) | |
{ | |
renderSession.maskManager.popMask(renderSession); | |
} | |
}; | |
/** | |
* @author Mat Groves http://matgroves.com/ | |
*/ | |
/** | |
* The SpriteBatch class is a really fast version of the DisplayObjectContainer | |
* built solely for speed, so use when you need a lot of sprites or particles. | |
* And it's extremely easy to use : | |
var container = new PIXI.SpriteBatch(); | |
for(var i = 0; i < 100; i++) | |
{ | |
var sprite = new PIXI.Sprite.fromImage("myImage.png"); | |
container.addChild(sprite); | |
} | |
* And here you have a hundred sprites that will be renderer at the speed of light | |
* | |
* @class SpriteBatch | |
* @constructor | |
* @param texture {Texture} | |
*/ | |
PIXI.SpriteBatch = function(texture) | |
{ | |
PIXI.DisplayObjectContainer.call( this); | |
this.textureThing = texture; | |
this.ready = false; | |
}; | |
PIXI.SpriteBatch.prototype = Object.create(PIXI.DisplayObjectContainer.prototype); | |
PIXI.SpriteBatch.prototype.constructor = PIXI.SpriteBatch; | |
/* | |
* Initialises the spriteBatch | |
* | |
* @method initWebGL | |
* @param gl {WebGLContext} the current WebGL drawing context | |
*/ | |
PIXI.SpriteBatch.prototype.initWebGL = function(gl) | |
{ | |
// TODO only one needed for the whole engine really? | |
this.fastSpriteBatch = new PIXI.WebGLFastSpriteBatch(gl); | |
this.ready = true; | |
}; | |
/* | |
* Updates the object transform for rendering | |
* | |
* @method updateTransform | |
* @private | |
*/ | |
PIXI.SpriteBatch.prototype.updateTransform = function() | |
{ | |
// TODO don't need to! | |
this.displayObjectUpdateTransform(); | |
// PIXI.DisplayObjectContainer.prototype.updateTransform.call( this ); | |
}; | |
/** | |
* Renders the object using the WebGL renderer | |
* | |
* @method _renderWebGL | |
* @param renderSession {RenderSession} | |
* @private | |
*/ | |
PIXI.SpriteBatch.prototype._renderWebGL = function(renderSession) | |
{ | |
if (!this.visible || this.alpha <= 0 || !this.children.length) return; | |
if (!this.ready) | |
{ | |
this.initWebGL(renderSession.gl); | |
} | |
if (this.fastSpriteBatch.gl !== renderSession.gl) | |
{ | |
this.fastSpriteBatch.setContext(renderSession.gl); | |
} | |
renderSession.spriteBatch.stop(); | |
renderSession.shaderManager.setShader(renderSession.shaderManager.fastShader); | |
this.fastSpriteBatch.begin(this, renderSession); | |
this.fastSpriteBatch.render(this); | |
renderSession.spriteBatch.start(); | |
}; | |
/** | |
* Renders the object using the Canvas renderer | |
* | |
* @method _renderCanvas | |
* @param renderSession {RenderSession} | |
* @private | |
*/ | |
PIXI.SpriteBatch.prototype._renderCanvas = function(renderSession) | |
{ | |
if (!this.visible || this.alpha <= 0 || !this.children.length) return; | |
var context = renderSession.context; | |
context.globalAlpha = this.worldAlpha; | |
this.displayObjectUpdateTransform(); | |
var transform = this.worldTransform; | |
var isRotated = true; | |
for (var i = 0; i < this.children.length; i++) | |
{ | |
var child = this.children[i]; | |
if (!child.visible) continue; | |
var texture = child.texture; | |
var frame = texture.frame; | |
context.globalAlpha = this.worldAlpha * child.alpha; | |
if (child.rotation % (Math.PI * 2) === 0) | |
{ | |
if (isRotated) | |
{ | |
context.setTransform(transform.a, transform.b, transform.c, transform.d, transform.tx, transform.ty); | |
isRotated = false; | |
} | |
// this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call | |
context.drawImage(texture.baseTexture.source, | |
frame.x, | |
frame.y, | |
frame.width, | |
frame.height, | |
((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5 + renderSession.shakeX) | 0, | |
((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5 + renderSession.shakeY) | 0, | |
frame.width * child.scale.x, | |
frame.height * child.scale.y); | |
} | |
else | |
{ | |
if (!isRotated) isRotated = true; | |
child.displayObjectUpdateTransform(); | |
var childTransform = child.worldTransform; | |
var tx = (childTransform.tx * renderSession.resolution) + renderSession.shakeX; | |
var ty = (childTransform.ty * renderSession.resolution) + renderSession.shakeY; | |
// allow for trimming | |
if (renderSession.roundPixels) | |
{ | |
context.setTransform(childTransform.a, childTransform.b, childTransform.c, childTransform.d, tx | 0, ty | 0); | |
} | |
else | |
{ | |
context.setTransform(childTransform.a, childTransform.b, childTransform.c, childTransform.d, tx, ty); | |
} | |
context.drawImage(texture.baseTexture.source, | |
frame.x, | |
frame.y, | |
frame.width, | |
frame.height, | |
((child.anchor.x) * (-frame.width) + 0.5) | 0, | |
((child.anchor.y) * (-frame.height) + 0.5) | 0, | |
frame.width, | |
frame.height); | |
} | |
} | |
}; | |
/** | |
* @author Mat Groves http://matgroves.com/ @Doormat23 | |
*/ | |
/** | |
* Converts a hex color number to an [R, G, B] array | |
* | |
* @method hex2rgb | |
* @param hex {Number} | |
*/ | |
PIXI.hex2rgb = function(hex) { | |
return [(hex >> 16 & 0xFF) / 255, ( hex >> 8 & 0xFF) / 255, (hex & 0xFF)/ 255]; | |
}; | |
/** | |
* Converts a color as an [R, G, B] array to a hex number | |
* | |
* @method rgb2hex | |
* @param rgb {Array} | |
*/ | |
PIXI.rgb2hex = function(rgb) { | |
return ((rgb[0]*255 << 16) + (rgb[1]*255 << 8) + rgb[2]*255); | |
}; | |
/** | |
* Checks whether the Canvas BlendModes are supported by the current browser for drawImage | |
* | |
* @method canUseNewCanvasBlendModes | |
* @return {Boolean} whether they are supported | |
*/ | |
PIXI.canUseNewCanvasBlendModes = function() | |
{ | |
if (document === undefined) return false; | |
var pngHead = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAABAQMAAADD8p2OAAAAA1BMVEX/'; | |
var pngEnd = 'AAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg=='; | |
var magenta = new Image(); | |
magenta.src = pngHead + 'AP804Oa6' + pngEnd; | |
var yellow = new Image(); | |
yellow.src = pngHead + '/wCKxvRF' + pngEnd; | |
var canvas = PIXI.CanvasPool.create(this, 6, 1); | |
var context = canvas.getContext('2d'); | |
context.globalCompositeOperation = 'multiply'; | |
context.drawImage(magenta, 0, 0); | |
context.drawImage(yellow, 2, 0); | |
if (!context.getImageData(2,0,1,1)) | |
{ | |
return false; | |
} | |
var data = context.getImageData(2,0,1,1).data; | |
PIXI.CanvasPool.remove(this); | |
return (data[0] === 255 && data[1] === 0 && data[2] === 0); | |
}; | |
/** | |
* Given a number, this function returns the closest number that is a power of two | |
* this function is taken from Starling Framework as its pretty neat ;) | |
* | |
* @method getNextPowerOfTwo | |
* @param number {Number} | |
* @return {Number} the closest number that is a power of two | |
*/ | |
PIXI.getNextPowerOfTwo = function(number) | |
{ | |
if (number > 0 && (number & (number - 1)) === 0) // see: http://goo.gl/D9kPj | |
return number; | |
else | |
{ | |
var result = 1; | |
while (result < number) result <<= 1; | |
return result; | |
} | |
}; | |
/** | |
* checks if the given width and height make a power of two texture | |
* @method isPowerOfTwo | |
* @param width {Number} | |
* @param height {Number} | |
* @return {Boolean} | |
*/ | |
PIXI.isPowerOfTwo = function(width, height) | |
{ | |
return (width > 0 && (width & (width - 1)) === 0 && height > 0 && (height & (height - 1)) === 0); | |
}; | |
/** | |
* @author Richard Davey <[email protected]> | |
* @copyright 2016 Photon Storm Ltd. | |
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} | |
*/ | |
/** | |
* The CanvasPool is a global static object that allows Pixi and Phaser to pool canvas DOM elements. | |
* | |
* @class CanvasPool | |
* @static | |
*/ | |
PIXI.CanvasPool = { | |
/** | |
* Creates a new Canvas DOM element, or pulls one from the pool if free. | |
* | |
* @method create | |
* @static | |
* @param parent {any} The parent of the canvas element. | |
* @param width {number} The width of the canvas element. | |
* @param height {number} The height of the canvas element. | |
* @return {HTMLCanvasElement} The canvas element. | |
*/ | |
create: function (parent, width, height) { | |
var idx = PIXI.CanvasPool.getFirst(); | |
var canvas; | |
if (idx === -1) | |
{ | |
var container = { | |
parent: parent, | |
canvas: document.createElement('canvas') | |
} | |
PIXI.CanvasPool.pool.push(container); | |
canvas = container.canvas; | |
} | |
else | |
{ | |
PIXI.CanvasPool.pool[idx].parent = parent; | |
canvas = PIXI.CanvasPool.pool[idx].canvas; | |
} | |
if (width !== undefined) | |
{ | |
canvas.width = width; | |
canvas.height = height; | |
} | |
return canvas; | |
}, | |
/** | |
* Gets the first free canvas index from the pool. | |
* | |
* @method getFirst | |
* @static | |
* @return {number} | |
*/ | |
getFirst: function () { | |
var pool = PIXI.CanvasPool.pool; | |
for (var i = 0; i < pool.length; i++) | |
{ | |
if (!pool[i].parent) | |
{ | |
return i; | |
} | |
} | |
return -1; | |
}, | |
/** | |
* Removes the parent from a canvas element from the pool, freeing it up for re-use. | |
* | |
* @method remove | |
* @param parent {any} The parent of the canvas element. | |
* @static | |
*/ | |
remove: function (parent) { | |
var pool = PIXI.CanvasPool.pool; | |
for (var i = 0; i < pool.length; i++) | |
{ | |
if (pool[i].parent === parent) | |
{ | |
pool[i].parent = null; | |
pool[i].canvas.width = 1; | |
pool[i].canvas.height = 1; | |
} | |
} | |
}, | |
/** | |
* Removes the parent from a canvas element from the pool, freeing it up for re-use. | |
* | |
* @method removeByCanvas | |
* @param canvas {HTMLCanvasElement} The canvas element to remove | |
* @static | |
*/ | |
removeByCanvas: function (canvas) { | |
var pool = PIXI.CanvasPool.pool; | |
for (var i = 0; i < pool.length; i++) | |
{ | |
if (pool[i].canvas === canvas) | |
{ | |
pool[i].parent = null; | |
pool[i].canvas.width = 1; | |
pool[i].canvas.height = 1; | |
} | |
} | |
}, | |
/** | |
* Gets the total number of used canvas elements in the pool. | |
* | |
* @method getTotal | |
* @static | |
* @return {number} The number of in-use (parented) canvas elements in the pool. | |
*/ | |
getTotal: function () { | |
var pool = PIXI.CanvasPool.pool; | |
var c = 0; | |
for (var i = 0; i < pool.length; i++) | |
{ | |
if (pool[i].parent) | |
{ | |
c++; | |
} | |
} | |
return c; | |
}, | |
/** | |
* Gets the total number of free canvas elements in the pool. | |
* | |
* @method getFree | |
* @static | |
* @return {number} The number of free (un-parented) canvas elements in the pool. | |
*/ | |
getFree: function () { | |
var pool = PIXI.CanvasPool.pool; | |
var c = 0; | |
for (var i = 0; i < pool.length; i++) | |
{ | |
if (!pool[i].parent) | |
{ | |
c++; | |
} | |
} | |
return c; | |
} | |
}; | |
/** | |
* The pool into which the canvas dom elements are placed. | |
* | |
* @property pool | |
* @type Array | |
* @static | |
*/ | |
PIXI.CanvasPool.pool = []; | |
/** | |
* @author Mat Groves http://matgroves.com/ @Doormat23 | |
*/ | |
/** | |
* @method initDefaultShaders | |
* @static | |
* @private | |
*/ | |
PIXI.initDefaultShaders = function() | |
{ | |
}; | |
/** | |
* @method CompileVertexShader | |
* @static | |
* @param gl {WebGLContext} the current WebGL drawing context | |
* @param shaderSrc {Array} | |
* @return {Any} | |
*/ | |
PIXI.CompileVertexShader = function(gl, shaderSrc) | |
{ | |
return PIXI._CompileShader(gl, shaderSrc, gl.VERTEX_SHADER); | |
}; | |
/** | |
* @method CompileFragmentShader | |
* @static | |
* @param gl {WebGLContext} the current WebGL drawing context | |
* @param shaderSrc {Array} | |
* @return {Any} | |
*/ | |
PIXI.CompileFragmentShader = function(gl, shaderSrc) | |
{ | |
return PIXI._CompileShader(gl, shaderSrc, gl.FRAGMENT_SHADER); | |
}; | |
/** | |
* @method _CompileShader | |
* @static | |
* @private | |
* @param gl {WebGLContext} the current WebGL drawing context | |
* @param shaderSrc {Array} | |
* @param shaderType {Number} | |
* @return {Any} | |
*/ | |
PIXI._CompileShader = function(gl, shaderSrc, shaderType) | |
{ | |
var src = shaderSrc; | |
if (Array.isArray(shaderSrc)) | |
{ | |
src = shaderSrc.join("\n"); | |
} | |
var shader = gl.createShader(shaderType); | |
gl.shaderSource(shader, src); | |
gl.compileShader(shader); | |
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) | |
{ | |
window.console.log(gl.getShaderInfoLog(shader)); | |
return null; | |
} | |
return shader; | |
}; | |
/** | |
* @method compileProgram | |
* @static | |
* @param gl {WebGLContext} the current WebGL drawing context | |
* @param vertexSrc {Array} | |
* @param fragmentSrc {Array} | |
* @return {Any} | |
*/ | |
PIXI.compileProgram = function(gl, vertexSrc, fragmentSrc) | |
{ | |
var fragmentShader = PIXI.CompileFragmentShader(gl, fragmentSrc); | |
var vertexShader = PIXI.CompileVertexShader(gl, vertexSrc); | |
var shaderProgram = gl.createProgram(); | |
gl.attachShader(shaderProgram, vertexShader); | |
gl.attachShader(shaderProgram, fragmentShader); | |
gl.linkProgram(shaderProgram); | |
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) | |
{ | |
window.console.log(gl.getProgramInfoLog(shaderProgram)); | |
window.console.log("Could not initialise shaders"); | |
} | |
return shaderProgram; | |
}; | |
/** | |
* @author Mat Groves http://matgroves.com/ @Doormat23 | |
* @author Richard Davey http://www.photonstorm.com @photonstorm | |
*/ | |
/** | |
* @class PixiShader | |
* @constructor | |
* @param gl {WebGLContext} the current WebGL drawing context | |
*/ | |
PIXI.PixiShader = function(gl) | |
{ | |
/** | |
* @property _UID | |
* @type Number | |
* @private | |
*/ | |
this._UID = PIXI._UID++; | |
/** | |
* @property gl | |
* @type WebGLContext | |
*/ | |
this.gl = gl; | |
/** | |
* The WebGL program. | |
* @property program | |
* @type Any | |
*/ | |
this.program = null; | |
/** | |
* The fragment shader. | |
* @property fragmentSrc | |
* @type Array | |
*/ | |
this.fragmentSrc = [ | |
'precision lowp float;', | |
'varying vec2 vTextureCoord;', | |
'varying vec4 vColor;', | |
'uniform sampler2D uSampler;', | |
'void main(void) {', | |
' gl_FragColor = texture2D(uSampler, vTextureCoord) * vColor ;', | |
'}' | |
]; | |
/** | |
* A local texture counter for multi-texture shaders. | |
* @property textureCount | |
* @type Number | |
*/ | |
this.textureCount = 0; | |
/** | |
* A local flag | |
* @property firstRun | |
* @type Boolean | |
* @private | |
*/ | |
this.firstRun = true; | |
/** | |
* A dirty flag | |
* @property dirty | |
* @type Boolean | |
*/ | |
this.dirty = true; | |
/** | |
* Uniform attributes cache. | |
* @property attributes | |
* @type Array | |
* @private | |
*/ | |
this.attributes = []; | |
this.init(); | |
}; | |
PIXI.PixiShader.prototype.constructor = PIXI.PixiShader; | |
/** | |
* Initialises the shader. | |
* | |
* @method init | |
*/ | |
PIXI.PixiShader.prototype.init = function() | |
{ | |
var gl = this.gl; | |
var program = PIXI.compileProgram(gl, this.vertexSrc || PIXI.PixiShader.defaultVertexSrc, this.fragmentSrc); | |
gl.useProgram(program); | |
// get and store the uniforms for the shader | |
this.uSampler = gl.getUniformLocation(program, 'uSampler'); | |
this.projectionVector = gl.getUniformLocation(program, 'projectionVector'); | |
this.offsetVector = gl.getUniformLocation(program, 'offsetVector'); | |
this.dimensions = gl.getUniformLocation(program, 'dimensions'); | |
// get and store the attributes | |
this.aVertexPosition = gl.getAttribLocation(program, 'aVertexPosition'); | |
this.aTextureCoord = gl.getAttribLocation(program, 'aTextureCoord'); | |
this.colorAttribute = gl.getAttribLocation(program, 'aColor'); | |
// Begin worst hack eva // | |
// WHY??? ONLY on my chrome pixel the line above returns -1 when using filters? | |
// maybe its something to do with the current state of the gl context. | |
// I'm convinced this is a bug in the chrome browser as there is NO reason why this should be returning -1 especially as it only manifests on my chrome pixel | |
// If theres any webGL people that know why could happen please help :) | |
if(this.colorAttribute === -1) | |
{ | |
this.colorAttribute = 2; | |
} | |
this.attributes = [this.aVertexPosition, this.aTextureCoord, this.colorAttribute]; | |
// End worst hack eva // | |
// add those custom shaders! | |
for (var key in this.uniforms) | |
{ | |
// get the uniform locations.. | |
this.uniforms[key].uniformLocation = gl.getUniformLocation(program, key); | |
} | |
this.initUniforms(); | |
this.program = program; | |
}; | |
/** | |
* Initialises the shader uniform values. | |
* | |
* Uniforms are specified in the GLSL_ES Specification: http://www.khronos.org/registry/webgl/specs/latest/1.0/ | |
* http://www.khronos.org/registry/gles/specs/2.0/GLSL_ES_Specification_1.0.17.pdf | |
* | |
* @method initUniforms | |
*/ | |
PIXI.PixiShader.prototype.initUniforms = function() | |
{ | |
this.textureCount = 1; | |
var gl = this.gl; | |
var uniform; | |
for (var key in this.uniforms) | |
{ | |
uniform = this.uniforms[key]; | |
var type = uniform.type; | |
if (type === 'sampler2D') | |
{ | |
uniform._init = false; | |
if (uniform.value !== null) | |
{ | |
this.initSampler2D(uniform); | |
} | |
} | |
else if (type === 'mat2' || type === 'mat3' || type === 'mat4') | |
{ | |
// These require special handling | |
uniform.glMatrix = true; | |
uniform.glValueLength = 1; | |
if (type === 'mat2') | |
{ | |
uniform.glFunc = gl.uniformMatrix2fv; | |
} | |
else if (type === 'mat3') | |
{ | |
uniform.glFunc = gl.uniformMatrix3fv; | |
} | |
else if (type === 'mat4') | |
{ | |
uniform.glFunc = gl.uniformMatrix4fv; | |
} | |
} | |
else | |
{ | |
// GL function reference | |
uniform.glFunc = gl['uniform' + type]; | |
if (type === '2f' || type === '2i') | |
{ | |
uniform.glValueLength = 2; | |
} | |
else if (type === '3f' || type === '3i') | |
{ | |
uniform.glValueLength = 3; | |
} | |
else if (type === '4f' || type === '4i') | |
{ | |
uniform.glValueLength = 4; | |
} | |
else | |
{ | |
uniform.glValueLength = 1; | |
} | |
} | |
} | |
}; | |
/** | |
* Initialises a Sampler2D uniform (which may only be available later on after initUniforms once the texture has loaded) | |
* | |
* @method initSampler2D | |
*/ | |
PIXI.PixiShader.prototype.initSampler2D = function(uniform) | |
{ | |
if (!uniform.value || !uniform.value.baseTexture || !uniform.value.baseTexture.hasLoaded) | |
{ | |
return; | |
} | |
var gl = this.gl; | |
gl.activeTexture(gl['TEXTURE' + this.textureCount]); | |
gl.bindTexture(gl.TEXTURE_2D, uniform.value.baseTexture._glTextures[gl.id]); | |
// Extended texture data | |
if (uniform.textureData) | |
{ | |
var data = uniform.textureData; | |
// GLTexture = mag linear, min linear_mipmap_linear, wrap repeat + gl.generateMipmap(gl.TEXTURE_2D); | |
// GLTextureLinear = mag/min linear, wrap clamp | |
// GLTextureNearestRepeat = mag/min NEAREST, wrap repeat | |
// GLTextureNearest = mag/min nearest, wrap clamp | |
// AudioTexture = whatever + luminance + width 512, height 2, border 0 | |
// KeyTexture = whatever + luminance + width 256, height 2, border 0 | |
// magFilter can be: gl.LINEAR, gl.LINEAR_MIPMAP_LINEAR or gl.NEAREST | |
// wrapS/T can be: gl.CLAMP_TO_EDGE or gl.REPEAT | |
var magFilter = (data.magFilter) ? data.magFilter : gl.LINEAR; | |
var minFilter = (data.minFilter) ? data.minFilter : gl.LINEAR; | |
var wrapS = (data.wrapS) ? data.wrapS : gl.CLAMP_TO_EDGE; | |
var wrapT = (data.wrapT) ? data.wrapT : gl.CLAMP_TO_EDGE; | |
var format = (data.luminance) ? gl.LUMINANCE : gl.RGBA; | |
if (data.repeat) | |
{ | |
wrapS = gl.REPEAT; | |
wrapT = gl.REPEAT; | |
} | |
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, !!data.flipY); | |
if (data.width) | |
{ | |
var width = (data.width) ? data.width : 512; | |
var height = (data.height) ? data.height : 2; | |
var border = (data.border) ? data.border : 0; | |
// void texImage2D(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, ArrayBufferView? pixels); | |
gl.texImage2D(gl.TEXTURE_2D, 0, format, width, height, border, format, gl.UNSIGNED_BYTE, null); | |
} | |
else | |
{ | |
// void texImage2D(GLenum target, GLint level, GLenum internalformat, GLenum format, GLenum type, ImageData? pixels); | |
gl.texImage2D(gl.TEXTURE_2D, 0, format, gl.RGBA, gl.UNSIGNED_BYTE, uniform.value.baseTexture.source); | |
} | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, magFilter); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, minFilter); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, wrapS); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, wrapT); | |
} | |
gl.uniform1i(uniform.uniformLocation, this.textureCount); | |
uniform._init = true; | |
this.textureCount++; | |
}; | |
/** | |
* Updates the shader uniform values. | |
* | |
* @method syncUniforms | |
*/ | |
PIXI.PixiShader.prototype.syncUniforms = function() | |
{ | |
this.textureCount = 1; | |
var uniform; | |
var gl = this.gl; | |
// This would probably be faster in an array and it would guarantee key order | |
for (var key in this.uniforms) | |
{ | |
uniform = this.uniforms[key]; | |
if (uniform.glValueLength === 1) | |
{ | |
if (uniform.glMatrix === true) | |
{ | |
uniform.glFunc.call(gl, uniform.uniformLocation, uniform.transpose, uniform.value); | |
} | |
else | |
{ | |
uniform.glFunc.call(gl, uniform.uniformLocation, uniform.value); | |
} | |
} | |
else if (uniform.glValueLength === 2) | |
{ | |
uniform.glFunc.call(gl, uniform.uniformLocation, uniform.value.x, uniform.value.y); | |
} | |
else if (uniform.glValueLength === 3) | |
{ | |
uniform.glFunc.call(gl, uniform.uniformLocation, uniform.value.x, uniform.value.y, uniform.value.z); | |
} | |
else if (uniform.glValueLength === 4) | |
{ | |
uniform.glFunc.call(gl, uniform.uniformLocation, uniform.value.x, uniform.value.y, uniform.value.z, uniform.value.w); | |
} | |
else if (uniform.type === 'sampler2D') | |
{ | |
if (uniform._init) | |
{ | |
gl.activeTexture(gl['TEXTURE' + this.textureCount]); | |
if(uniform.value.baseTexture._dirty[gl.id]) | |
{ | |
PIXI.instances[gl.id].updateTexture(uniform.value.baseTexture); | |
} | |
else | |
{ | |
// bind the current texture | |
gl.bindTexture(gl.TEXTURE_2D, uniform.value.baseTexture._glTextures[gl.id]); | |
} | |
// gl.bindTexture(gl.TEXTURE_2D, uniform.value.baseTexture._glTextures[gl.id] || PIXI.createWebGLTexture( uniform.value.baseTexture, gl)); | |
gl.uniform1i(uniform.uniformLocation, this.textureCount); | |
this.textureCount++; | |
} | |
else | |
{ | |
this.initSampler2D(uniform); | |
} | |
} | |
} | |
}; | |
/** | |
* Destroys the shader. | |
* | |
* @method destroy | |
*/ | |
PIXI.PixiShader.prototype.destroy = function() | |
{ | |
this.gl.deleteProgram( this.program ); | |
this.uniforms = null; | |
this.gl = null; | |
this.attributes = null; | |
}; | |
/** | |
* The Default Vertex shader source. | |
* | |
* @property defaultVertexSrc | |
* @type String | |
*/ | |
PIXI.PixiShader.defaultVertexSrc = [ | |
'attribute vec2 aVertexPosition;', | |
'attribute vec2 aTextureCoord;', | |
'attribute vec4 aColor;', | |
'uniform vec2 projectionVector;', | |
'uniform vec2 offsetVector;', | |
'varying vec2 vTextureCoord;', | |
'varying vec4 vColor;', | |
'const vec2 center = vec2(-1.0, 1.0);', | |
'void main(void) {', | |
' gl_Position = vec4( ((aVertexPosition + offsetVector) / projectionVector) + center , 0.0, 1.0);', | |
' vTextureCoord = aTextureCoord;', | |
' vColor = vec4(aColor.rgb * aColor.a, aColor.a);', | |
'}' | |
]; | |
/** | |
* @author Mat Groves http://matgroves.com/ @Doormat23 | |
*/ | |
/** | |
* @class PixiFastShader | |
* @constructor | |
* @param gl {WebGLContext} the current WebGL drawing context | |
*/ | |
PIXI.PixiFastShader = function(gl) | |
{ | |
/** | |
* @property _UID | |
* @type Number | |
* @private | |
*/ | |
this._UID = PIXI._UID++; | |
/** | |
* @property gl | |
* @type WebGLContext | |
*/ | |
this.gl = gl; | |
/** | |
* The WebGL program. | |
* @property program | |
* @type Any | |
*/ | |
this.program = null; | |
/** | |
* The fragment shader. | |
* @property fragmentSrc | |
* @type Array | |
*/ | |
this.fragmentSrc = [ | |
'precision lowp float;', | |
'varying vec2 vTextureCoord;', | |
'varying float vColor;', | |
'uniform sampler2D uSampler;', | |
'void main(void) {', | |
' gl_FragColor = texture2D(uSampler, vTextureCoord) * vColor ;', | |
'}' | |
]; | |
/** | |
* The vertex shader. | |
* @property vertexSrc | |
* @type Array | |
*/ | |
this.vertexSrc = [ | |
'attribute vec2 aVertexPosition;', | |
'attribute vec2 aPositionCoord;', | |
'attribute vec2 aScale;', | |
'attribute float aRotation;', | |
'attribute vec2 aTextureCoord;', | |
'attribute float aColor;', | |
'uniform vec2 projectionVector;', | |
'uniform vec2 offsetVector;', | |
'uniform mat3 uMatrix;', | |
'varying vec2 vTextureCoord;', | |
'varying float vColor;', | |
'const vec2 center = vec2(-1.0, 1.0);', | |
'void main(void) {', | |
' vec2 v;', | |
' vec2 sv = aVertexPosition * aScale;', | |
' v.x = (sv.x) * cos(aRotation) - (sv.y) * sin(aRotation);', | |
' v.y = (sv.x) * sin(aRotation) + (sv.y) * cos(aRotation);', | |
' v = ( uMatrix * vec3(v + aPositionCoord , 1.0) ).xy ;', | |
' gl_Position = vec4( ( v / projectionVector) + center , 0.0, 1.0);', | |
' vTextureCoord = aTextureCoord;', | |
// ' vec3 color = mod(vec3(aColor.y/65536.0, aColor.y/256.0, aColor.y), 256.0) / 256.0;', | |
' vColor = aColor;', | |
'}' | |
]; | |
/** | |
* A local texture counter for multi-texture shaders. | |
* @property textureCount | |
* @type Number | |
*/ | |
this.textureCount = 0; | |
this.init(); | |
}; | |
PIXI.PixiFastShader.prototype.constructor = PIXI.PixiFastShader; | |
/** | |
* Initialises the shader. | |
* | |
* @method init | |
*/ | |
PIXI.PixiFastShader.prototype.init = function() | |
{ | |
var gl = this.gl; | |
var program = PIXI.compileProgram(gl, this.vertexSrc, this.fragmentSrc); | |
gl.useProgram(program); | |
// get and store the uniforms for the shader | |
this.uSampler = gl.getUniformLocation(program, 'uSampler'); | |
this.projectionVector = gl.getUniformLocation(program, 'projectionVector'); | |
this.offsetVector = gl.getUniformLocation(program, 'offsetVector'); | |
this.dimensions = gl.getUniformLocation(program, 'dimensions'); | |
this.uMatrix = gl.getUniformLocation(program, 'uMatrix'); | |
// get and store the attributes | |
this.aVertexPosition = gl.getAttribLocation(program, 'aVertexPosition'); | |
this.aPositionCoord = gl.getAttribLocation(program, 'aPositionCoord'); | |
this.aScale = gl.getAttribLocation(program, 'aScale'); | |
this.aRotation = gl.getAttribLocation(program, 'aRotation'); | |
this.aTextureCoord = gl.getAttribLocation(program, 'aTextureCoord'); | |
this.colorAttribute = gl.getAttribLocation(program, 'aColor'); | |
// Begin worst hack eva // | |
// WHY??? ONLY on my chrome pixel the line above returns -1 when using filters? | |
// maybe its somthing to do with the current state of the gl context. | |
// Im convinced this is a bug in the chrome browser as there is NO reason why this should be returning -1 especially as it only manifests on my chrome pixel | |
// If theres any webGL people that know why could happen please help :) | |
if(this.colorAttribute === -1) | |
{ | |
this.colorAttribute = 2; | |
} | |
this.attributes = [this.aVertexPosition, this.aPositionCoord, this.aScale, this.aRotation, this.aTextureCoord, this.colorAttribute]; | |
// End worst hack eva // | |
this.program = program; | |
}; | |
/** | |
* Destroys the shader. | |
* | |
* @method destroy | |
*/ | |
PIXI.PixiFastShader.prototype.destroy = function() | |
{ | |
this.gl.deleteProgram( this.program ); | |
this.uniforms = null; | |
this.gl = null; | |
this.attributes = null; | |
}; | |
/** | |
* @author Mat Groves http://matgroves.com/ @Doormat23 | |
*/ | |
/** | |
* @class StripShader | |
* @constructor | |
* @param gl {WebGLContext} the current WebGL drawing context | |
*/ | |
PIXI.StripShader = function(gl) | |
{ | |
/** | |
* @property _UID | |
* @type Number | |
* @private | |
*/ | |
this._UID = PIXI._UID++; | |
/** | |
* @property gl | |
* @type WebGLContext | |
*/ | |
this.gl = gl; | |
/** | |
* The WebGL program. | |
* @property program | |
* @type Any | |
*/ | |
this.program = null; | |
/** | |
* The fragment shader. | |
* @property fragmentSrc | |
* @type Array | |
*/ | |
this.fragmentSrc = [ | |
'precision mediump float;', | |
'varying vec2 vTextureCoord;', | |
// 'varying float vColor;', | |
'uniform float alpha;', | |
'uniform sampler2D uSampler;', | |
'void main(void) {', | |
' gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y)) * alpha;', | |
// ' gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);',//gl_FragColor * alpha;', | |
'}' | |
]; | |
/** | |
* The vertex shader. | |
* @property vertexSrc | |
* @type Array | |
*/ | |
this.vertexSrc = [ | |
'attribute vec2 aVertexPosition;', | |
'attribute vec2 aTextureCoord;', | |
'uniform mat3 translationMatrix;', | |
'uniform vec2 projectionVector;', | |
'uniform vec2 offsetVector;', | |
// 'uniform float alpha;', | |
// 'uniform vec3 tint;', | |
'varying vec2 vTextureCoord;', | |
// 'varying vec4 vColor;', | |
'void main(void) {', | |
' vec3 v = translationMatrix * vec3(aVertexPosition , 1.0);', | |
' v -= offsetVector.xyx;', | |
' gl_Position = vec4( v.x / projectionVector.x -1.0, v.y / -projectionVector.y + 1.0 , 0.0, 1.0);', | |
' vTextureCoord = aTextureCoord;', | |
// ' vColor = aColor * vec4(tint * alpha, alpha);', | |
'}' | |
]; | |
this.init(); | |
}; | |
PIXI.StripShader.prototype.constructor = PIXI.StripShader; | |
/** | |
* Initialises the shader. | |
* | |
* @method init | |
*/ | |
PIXI.StripShader.prototype.init = function() | |
{ | |
var gl = this.gl; | |
var program = PIXI.compileProgram(gl, this.vertexSrc, this.fragmentSrc); | |
gl.useProgram(program); | |
// get and store the uniforms for the shader | |
this.uSampler = gl.getUniformLocation(program, 'uSampler'); | |
this.projectionVector = gl.getUniformLocation(program, 'projectionVector'); | |
this.offsetVector = gl.getUniformLocation(program, 'offsetVector'); | |
this.colorAttribute = gl.getAttribLocation(program, 'aColor'); | |
//this.dimensions = gl.getUniformLocation(this.program, 'dimensions'); | |
// get and store the attributes | |
this.aVertexPosition = gl.getAttribLocation(program, 'aVertexPosition'); | |
this.aTextureCoord = gl.getAttribLocation(program, 'aTextureCoord'); | |
this.attributes = [this.aVertexPosition, this.aTextureCoord]; | |
this.translationMatrix = gl.getUniformLocation(program, 'translationMatrix'); | |
this.alpha = gl.getUniformLocation(program, 'alpha'); | |
this.program = program; | |
}; | |
/** | |
* Destroys the shader. | |
* | |
* @method destroy | |
*/ | |
PIXI.StripShader.prototype.destroy = function() | |
{ | |
this.gl.deleteProgram( this.program ); | |
this.uniforms = null; | |
this.gl = null; | |
this.attribute = null; | |
}; | |
/** | |
* @author Mat Groves http://matgroves.com/ @Doormat23 | |
*/ | |
/** | |
* @class PrimitiveShader | |
* @constructor | |
* @param gl {WebGLContext} the current WebGL drawing context | |
*/ | |
PIXI.PrimitiveShader = function(gl) | |
{ | |
/** | |
* @property _UID | |
* @type Number | |
* @private | |
*/ | |
this._UID = PIXI._UID++; | |
/** | |
* @property gl | |
* @type WebGLContext | |
*/ | |
this.gl = gl; | |
/** | |
* The WebGL program. | |
* @property program | |
* @type Any | |
*/ | |
this.program = null; | |
/** | |
* The fragment shader. | |
* @property fragmentSrc | |
* @type Array | |
*/ | |
this.fragmentSrc = [ | |
'precision mediump float;', | |
'varying vec4 vColor;', | |
'void main(void) {', | |
' gl_FragColor = vColor;', | |
'}' | |
]; | |
/** | |
* The vertex shader. | |
* @property vertexSrc | |
* @type Array | |
*/ | |
this.vertexSrc = [ | |
'attribute vec2 aVertexPosition;', | |
'attribute vec4 aColor;', | |
'uniform mat3 translationMatrix;', | |
'uniform vec2 projectionVector;', | |
'uniform vec2 offsetVector;', | |
'uniform float alpha;', | |
'uniform float flipY;', | |
'uniform vec3 tint;', | |
'varying vec4 vColor;', | |
'void main(void) {', | |
' vec3 v = translationMatrix * vec3(aVertexPosition , 1.0);', | |
' v -= offsetVector.xyx;', | |
' gl_Position = vec4( v.x / projectionVector.x -1.0, (v.y / projectionVector.y * -flipY) + flipY , 0.0, 1.0);', | |
' vColor = aColor * vec4(tint * alpha, alpha);', | |
'}' | |
]; | |
this.init(); | |
}; | |
PIXI.PrimitiveShader.prototype.constructor = PIXI.PrimitiveShader; | |
/** | |
* Initialises the shader. | |
* | |
* @method init | |
*/ | |
PIXI.PrimitiveShader.prototype.init = function() | |
{ | |
var gl = this.gl; | |
var program = PIXI.compileProgram(gl, this.vertexSrc, this.fragmentSrc); | |
gl.useProgram(program); | |
// get and store the uniforms for the shader | |
this.projectionVector = gl.getUniformLocation(program, 'projectionVector'); | |
this.offsetVector = gl.getUniformLocation(program, 'offsetVector'); | |
this.tintColor = gl.getUniformLocation(program, 'tint'); | |
this.flipY = gl.getUniformLocation(program, 'flipY'); | |
// get and store the attributes | |
this.aVertexPosition = gl.getAttribLocation(program, 'aVertexPosition'); | |
this.colorAttribute = gl.getAttribLocation(program, 'aColor'); | |
this.attributes = [this.aVertexPosition, this.colorAttribute]; | |
this.translationMatrix = gl.getUniformLocation(program, 'translationMatrix'); | |
this.alpha = gl.getUniformLocation(program, 'alpha'); | |
this.program = program; | |
}; | |
/** | |
* Destroys the shader. | |
* | |
* @method destroy | |
*/ | |
PIXI.PrimitiveShader.prototype.destroy = function() | |
{ | |
this.gl.deleteProgram( this.program ); | |
this.uniforms = null; | |
this.gl = null; | |
this.attributes = null; | |
}; | |
/** | |
* @author Mat Groves http://matgroves.com/ @Doormat23 | |
*/ | |
/** | |
* @class ComplexPrimitiveShader | |
* @constructor | |
* @param gl {WebGLContext} the current WebGL drawing context | |
*/ | |
PIXI.ComplexPrimitiveShader = function(gl) | |
{ | |
/** | |
* @property _UID | |
* @type Number | |
* @private | |
*/ | |
this._UID = PIXI._UID++; | |
/** | |
* @property gl | |
* @type WebGLContext | |
*/ | |
this.gl = gl; | |
/** | |
* The WebGL program. | |
* @property program | |
* @type Any | |
*/ | |
this.program = null; | |
/** | |
* The fragment shader. | |
* @property fragmentSrc | |
* @type Array | |
*/ | |
this.fragmentSrc = [ | |
'precision mediump float;', | |
'varying vec4 vColor;', | |
'void main(void) {', | |
' gl_FragColor = vColor;', | |
'}' | |
]; | |
/** | |
* The vertex shader. | |
* @property vertexSrc | |
* @type Array | |
*/ | |
this.vertexSrc = [ | |
'attribute vec2 aVertexPosition;', | |
//'attribute vec4 aColor;', | |
'uniform mat3 translationMatrix;', | |
'uniform vec2 projectionVector;', | |
'uniform vec2 offsetVector;', | |
'uniform vec3 tint;', | |
'uniform float alpha;', | |
'uniform vec3 color;', | |
'uniform float flipY;', | |
'varying vec4 vColor;', | |
'void main(void) {', | |
' vec3 v = translationMatrix * vec3(aVertexPosition , 1.0);', | |
' v -= offsetVector.xyx;', | |
' gl_Position = vec4( v.x / projectionVector.x -1.0, (v.y / projectionVector.y * -flipY) + flipY , 0.0, 1.0);', | |
' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', | |
'}' | |
]; | |
this.init(); | |
}; | |
PIXI.ComplexPrimitiveShader.prototype.constructor = PIXI.ComplexPrimitiveShader; | |
/** | |
* Initialises the shader. | |
* | |
* @method init | |
*/ | |
PIXI.ComplexPrimitiveShader.prototype.init = function() | |
{ | |
var gl = this.gl; | |
var program = PIXI.compileProgram(gl, this.vertexSrc, this.fragmentSrc); | |
gl.useProgram(program); | |
// get and store the uniforms for the shader | |
this.projectionVector = gl.getUniformLocation(program, 'projectionVector'); | |
this.offsetVector = gl.getUniformLocation(program, 'offsetVector'); | |
this.tintColor = gl.getUniformLocation(program, 'tint'); | |
this.color = gl.getUniformLocation(program, 'color'); | |
this.flipY = gl.getUniformLocation(program, 'flipY'); | |
// get and store the attributes | |
this.aVertexPosition = gl.getAttribLocation(program, 'aVertexPosition'); | |
// this.colorAttribute = gl.getAttribLocation(program, 'aColor'); | |
this.attributes = [this.aVertexPosition, this.colorAttribute]; | |
this.translationMatrix = gl.getUniformLocation(program, 'translationMatrix'); | |
this.alpha = gl.getUniformLocation(program, 'alpha'); | |
this.program = program; | |
}; | |
/** | |
* Destroys the shader. | |
* | |
* @method destroy | |
*/ | |
PIXI.ComplexPrimitiveShader.prototype.destroy = function() | |
{ | |
this.gl.deleteProgram( this.program ); | |
this.uniforms = null; | |
this.gl = null; | |
this.attribute = null; | |
}; | |
/** | |
* @author Mat Groves http://matgroves.com/ @Doormat23 | |
*/ | |
PIXI.glContexts = []; // this is where we store the webGL contexts for easy access. | |
PIXI.instances = []; | |
/** | |
* The WebGLRenderer draws the stage and all its content onto a webGL enabled canvas. This renderer | |
* should be used for browsers that support webGL. This Render works by automatically managing webGLBatchs. | |
* So no need for Sprite Batches or Sprite Clouds. | |
* Don't forget to add the view to your DOM or you will not see anything :) | |
* | |
* @class WebGLRenderer | |
* @constructor | |
* @param game {Phaser.Game} A reference to the Phaser Game instance | |
*/ | |
PIXI.WebGLRenderer = function(game) { | |
/** | |
* @property {Phaser.Game} game - A reference to the Phaser Game instance. | |
*/ | |
this.game = game; | |
if (!PIXI.defaultRenderer) | |
{ | |
PIXI.defaultRenderer = this; | |
} | |
/** | |
* @property type | |
* @type Number | |
*/ | |
this.type = PIXI.WEBGL_RENDERER; | |
/** | |
* The resolution of the renderer | |
* | |
* @property resolution | |
* @type Number | |
* @default 1 | |
*/ | |
this.resolution = game.resolution; | |
/** | |
* Whether the render view is transparent | |
* | |
* @property transparent | |
* @type Boolean | |
*/ | |
this.transparent = game.transparent; | |
/** | |
* Whether the render view should be resized automatically | |
* | |
* @property autoResize | |
* @type Boolean | |
*/ | |
this.autoResize = false; | |
/** | |
* The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. | |
* | |
* @property preserveDrawingBuffer | |
* @type Boolean | |
*/ | |
this.preserveDrawingBuffer = game.preserveDrawingBuffer; | |
/** | |
* This sets if the WebGLRenderer will clear the context texture or not before the new render pass. If true: | |
* If the Stage is NOT transparent, Pixi will clear to alpha (0, 0, 0, 0). | |
* If the Stage is transparent, Pixi will clear to the target Stage's background color. | |
* Disable this by setting this to false. For example: if your game has a canvas filling background image, you often don't need this set. | |
* | |
* @property clearBeforeRender | |
* @type Boolean | |
* @default | |
*/ | |
this.clearBeforeRender = game.clearBeforeRender; | |
/** | |
* The width of the canvas view | |
* | |
* @property width | |
* @type Number | |
*/ | |
this.width = game.width; | |
/** | |
* The height of the canvas view | |
* | |
* @property height | |
* @type Number | |
*/ | |
this.height = game.height; | |
/** | |
* The canvas element that everything is drawn to | |
* | |
* @property view | |
* @type HTMLCanvasElement | |
*/ | |
this.view = game.canvas; | |
/** | |
* @property _contextOptions | |
* @type Object | |
* @private | |
*/ | |
this._contextOptions = { | |
alpha: this.transparent, | |
antialias: game.antialias, | |
premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', | |
stencil: true, | |
preserveDrawingBuffer: this.preserveDrawingBuffer | |
}; | |
/** | |
* @property projection | |
* @type Point | |
*/ | |
this.projection = new PIXI.Point(); | |
/** | |
* @property offset | |
* @type Point | |
*/ | |
this.offset = new PIXI.Point(); | |
// time to create the render managers! each one focuses on managing a state in webGL | |
/** | |
* Deals with managing the shader programs and their attribs | |
* @property shaderManager | |
* @type WebGLShaderManager | |
*/ | |
this.shaderManager = new PIXI.WebGLShaderManager(); | |
/** | |
* Manages the rendering of sprites | |
* @property spriteBatch | |
* @type WebGLSpriteBatch | |
*/ | |
this.spriteBatch = new PIXI.WebGLSpriteBatch(); | |
/** | |
* Manages the masks using the stencil buffer | |
* @property maskManager | |
* @type WebGLMaskManager | |
*/ | |
this.maskManager = new PIXI.WebGLMaskManager(); | |
/** | |
* Manages the filters | |
* @property filterManager | |
* @type WebGLFilterManager | |
*/ | |
this.filterManager = new PIXI.WebGLFilterManager(); | |
/** | |
* Manages the stencil buffer | |
* @property stencilManager | |
* @type WebGLStencilManager | |
*/ | |
this.stencilManager = new PIXI.WebGLStencilManager(); | |
/** | |
* Manages the blendModes | |
* @property blendModeManager | |
* @type WebGLBlendModeManager | |
*/ | |
this.blendModeManager = new PIXI.WebGLBlendModeManager(); | |
/** | |
* @property renderSession | |
* @type Object | |
*/ | |
this.renderSession = {}; | |
// Needed? | |
this.renderSession.game = this.game; | |
this.renderSession.gl = this.gl; | |
this.renderSession.drawCount = 0; | |
this.renderSession.shaderManager = this.shaderManager; | |
this.renderSession.maskManager = this.maskManager; | |
this.renderSession.filterManager = this.filterManager; | |
this.renderSession.blendModeManager = this.blendModeManager; | |
this.renderSession.spriteBatch = this.spriteBatch; | |
this.renderSession.stencilManager = this.stencilManager; | |
this.renderSession.renderer = this; | |
this.renderSession.resolution = this.resolution; | |
// time init the context.. | |
this.initContext(); | |
// map some webGL blend modes.. | |
this.mapBlendModes(); | |
}; | |
// constructor | |
PIXI.WebGLRenderer.prototype.constructor = PIXI.WebGLRenderer; | |
/** | |
* @method initContext | |
*/ | |
PIXI.WebGLRenderer.prototype.initContext = function() | |
{ | |
var gl = this.view.getContext('webgl', this._contextOptions) || this.view.getContext('experimental-webgl', this._contextOptions); | |
this.gl = gl; | |
if (!gl) { | |
// fail, not able to get a context | |
throw new Error('This browser does not support webGL. Try using the canvas renderer'); | |
} | |
this.glContextId = gl.id = PIXI.WebGLRenderer.glContextId++; | |
PIXI.glContexts[this.glContextId] = gl; | |
PIXI.instances[this.glContextId] = this; | |
// set up the default pixi settings.. | |
gl.disable(gl.DEPTH_TEST); | |
gl.disable(gl.CULL_FACE); | |
gl.enable(gl.BLEND); | |
// need to set the context for all the managers... | |
this.shaderManager.setContext(gl); | |
this.spriteBatch.setContext(gl); | |
this.maskManager.setContext(gl); | |
this.filterManager.setContext(gl); | |
this.blendModeManager.setContext(gl); | |
this.stencilManager.setContext(gl); | |
this.renderSession.gl = this.gl; | |
// now resize and we are good to go! | |
this.resize(this.width, this.height); | |
}; | |
/** | |
* Renders the stage to its webGL view | |
* | |
* @method render | |
* @param stage {Stage} the Stage element to be rendered | |
*/ | |
PIXI.WebGLRenderer.prototype.render = function(stage) | |
{ | |
// no point rendering if our context has been blown up! | |
if (this.contextLost) | |
{ | |
return; | |
} | |
var gl = this.gl; | |
// -- Does this need to be set every frame? -- // | |
gl.viewport(0, 0, this.width, this.height); | |
// make sure we are bound to the main frame buffer | |
gl.bindFramebuffer(gl.FRAMEBUFFER, null); | |
if (this.game.clearBeforeRender) | |
{ | |
gl.clearColor(stage._bgColor.r, stage._bgColor.g, stage._bgColor.b, stage._bgColor.a); | |
gl.clear(gl.COLOR_BUFFER_BIT); | |
} | |
this.offset.x = this.game.camera._shake.x; | |
this.offset.y = this.game.camera._shake.y; | |
this.renderDisplayObject(stage, this.projection); | |
}; | |
/** | |
* Renders a Display Object. | |
* | |
* @method renderDisplayObject | |
* @param displayObject {DisplayObject} The DisplayObject to render | |
* @param projection {Point} The projection | |
* @param buffer {Array} a standard WebGL buffer | |
*/ | |
PIXI.WebGLRenderer.prototype.renderDisplayObject = function(displayObject, projection, buffer, matrix) | |
{ | |
this.renderSession.blendModeManager.setBlendMode(PIXI.blendModes.NORMAL); | |
// reset the render session data.. | |
this.renderSession.drawCount = 0; | |
// make sure to flip the Y if using a render texture.. | |
this.renderSession.flipY = buffer ? -1 : 1; | |
// set the default projection | |
this.renderSession.projection = projection; | |
//set the default offset | |
this.renderSession.offset = this.offset; | |
// start the sprite batch | |
this.spriteBatch.begin(this.renderSession); | |
// start the filter manager | |
this.filterManager.begin(this.renderSession, buffer); | |
// render the scene! | |
displayObject._renderWebGL(this.renderSession, matrix); | |
// finish the sprite batch | |
this.spriteBatch.end(); | |
}; | |
/** | |
* Resizes the webGL view to the specified width and height. | |
* | |
* @method resize | |
* @param width {Number} the new width of the webGL view | |
* @param height {Number} the new height of the webGL view | |
*/ | |
PIXI.WebGLRenderer.prototype.resize = function(width, height) | |
{ | |
this.width = width * this.resolution; | |
this.height = height * this.resolution; | |
this.view.width = this.width; | |
this.view.height = this.height; | |
if (this.autoResize) { | |
this.view.style.width = this.width / this.resolution + 'px'; | |
this.view.style.height = this.height / this.resolution + 'px'; | |
} | |
this.gl.viewport(0, 0, this.width, this.height); | |
this.projection.x = this.width / 2 / this.resolution; | |
this.projection.y = -this.height / 2 / this.resolution; | |
}; | |
/** | |
* Updates and Creates a WebGL texture for the renderers context. | |
* | |
* @method updateTexture | |
* @param texture {Texture} the texture to update | |
* @return {boolean} True if the texture was successfully bound, otherwise false. | |
*/ | |
PIXI.WebGLRenderer.prototype.updateTexture = function(texture) | |
{ | |
if (!texture.hasLoaded) | |
{ | |
return false; | |
} | |
var gl = this.gl; | |
if (!texture._glTextures[gl.id]) | |
{ | |
texture._glTextures[gl.id] = gl.createTexture(); | |
} | |
gl.bindTexture(gl.TEXTURE_2D, texture._glTextures[gl.id]); | |
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultipliedAlpha); | |
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.source); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, texture.scaleMode === PIXI.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); | |
if (texture.mipmap && PIXI.isPowerOfTwo(texture.width, texture.height)) | |
{ | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, texture.scaleMode === PIXI.scaleModes.LINEAR ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); | |
gl.generateMipmap(gl.TEXTURE_2D); | |
} | |
else | |
{ | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, texture.scaleMode === PIXI.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); | |
} | |
if (!texture._powerOf2) | |
{ | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); | |
} | |
else | |
{ | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); | |
} | |
texture._dirty[gl.id] = false; | |
// return texture._glTextures[gl.id]; | |
return true; | |
}; | |
/** | |
* Removes everything from the renderer (event listeners, spritebatch, etc...) | |
* | |
* @method destroy | |
*/ | |
PIXI.WebGLRenderer.prototype.destroy = function() | |
{ | |
PIXI.glContexts[this.glContextId] = null; | |
this.projection = null; | |
this.offset = null; | |
this.shaderManager.destroy(); | |
this.spriteBatch.destroy(); | |
this.maskManager.destroy(); | |
this.filterManager.destroy(); | |
this.shaderManager = null; | |
this.spriteBatch = null; | |
this.maskManager = null; | |
this.filterManager = null; | |
this.gl = null; | |
this.renderSession = null; | |
PIXI.CanvasPool.remove(this); | |
PIXI.instances[this.glContextId] = null; | |
PIXI.WebGLRenderer.glContextId--; | |
}; | |
/** | |
* Maps Pixi blend modes to WebGL blend modes. | |
* | |
* @method mapBlendModes | |
*/ | |
PIXI.WebGLRenderer.prototype.mapBlendModes = function() | |
{ | |
var gl = this.gl; | |
if (!PIXI.blendModesWebGL) | |
{ | |
var b = []; | |
var modes = PIXI.blendModes; | |
b[modes.NORMAL] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; | |
b[modes.ADD] = [gl.SRC_ALPHA, gl.DST_ALPHA]; | |
b[modes.MULTIPLY] = [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA]; | |
b[modes.SCREEN] = [gl.SRC_ALPHA, gl.ONE]; | |
b[modes.OVERLAY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; | |
b[modes.DARKEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; | |
b[modes.LIGHTEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; | |
b[modes.COLOR_DODGE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; | |
b[modes.COLOR_BURN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; | |
b[modes.HARD_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; | |
b[modes.SOFT_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; | |
b[modes.DIFFERENCE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; | |
b[modes.EXCLUSION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; | |
b[modes.HUE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; | |
b[modes.SATURATION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; | |
b[modes.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; | |
b[modes.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; | |
PIXI.blendModesWebGL = b; | |
} | |
}; | |
PIXI.WebGLRenderer.glContextId = 0; | |
/** | |
* @author Mat Groves http://matgroves.com/ @Doormat23 | |
*/ | |
/** | |
* @class WebGLBlendModeManager | |
* @constructor | |
* @param gl {WebGLContext} the current WebGL drawing context | |
*/ | |
PIXI.WebGLBlendModeManager = function() | |
{ | |
/** | |
* @property currentBlendMode | |
* @type Number | |
*/ | |
this.currentBlendMode = 99999; | |
}; | |
PIXI.WebGLBlendModeManager.prototype.constructor = PIXI.WebGLBlendModeManager; | |
/** | |
* Sets the WebGL Context. | |
* | |
* @method setContext | |
* @param gl {WebGLContext} the current WebGL drawing context | |
*/ | |
PIXI.WebGLBlendModeManager.prototype.setContext = function(gl) | |
{ | |
this.gl = gl; | |
}; | |
/** | |
* Sets-up the given blendMode from WebGL's point of view. | |
* | |
* @method setBlendMode | |
* @param blendMode {Number} the blendMode, should be a Pixi const, such as PIXI.BlendModes.ADD | |
*/ | |
PIXI.WebGLBlendModeManager.prototype.setBlendMode = function(blendMode) | |
{ | |
if(this.currentBlendMode === blendMode)return false; | |
this.currentBlendMode = blendMode; | |
var blendModeWebGL = PIXI.blendModesWebGL[this.currentBlendMode]; | |
if (blendModeWebGL) | |
{ | |
this.gl.blendFunc(blendModeWebGL[0], blendModeWebGL[1]); | |
} | |
return true; | |
}; | |
/** | |
* Destroys this object. | |
* | |
* @method destroy | |
*/ | |
PIXI.WebGLBlendModeManager.prototype.destroy = function() | |
{ | |
this.gl = null; | |
}; | |
/** | |
* @author Mat Groves http://matgroves.com/ @Doormat23 | |
*/ | |
/** | |
* @class WebGLMaskManager | |
* @constructor | |
* @private | |
*/ | |
PIXI.WebGLMaskManager = function() | |
{ | |
}; | |
PIXI.WebGLMaskManager.prototype.constructor = PIXI.WebGLMaskManager; | |
/** | |
* Sets the drawing context to the one given in parameter. | |
* | |
* @method setContext | |
* @param gl {WebGLContext} the current WebGL drawing context | |
*/ | |
PIXI.WebGLMaskManager.prototype.setContext = function(gl) | |
{ | |
this.gl = gl; | |
}; | |
/** | |
* Applies the Mask and adds it to the current filter stack. | |
* | |
* @method pushMask | |
* @param maskData {Array} | |
* @param renderSession {Object} | |
*/ | |
PIXI.WebGLMaskManager.prototype.pushMask = function(maskData, renderSession) | |
{ | |
var gl = renderSession.gl; | |
if (maskData.dirty) | |
{ | |
PIXI.WebGLGraphics.updateGraphics(maskData, gl); | |
} | |
if (maskData._webGL[gl.id] === undefined || maskData._webGL[gl.id].data === undefined || maskData._webGL[gl.id].data.length === 0) | |
{ | |
return; | |
} | |
renderSession.stencilManager.pushStencil(maskData, maskData._webGL[gl.id].data[0], renderSession); | |
}; | |
/** | |
* Removes the last filter from the filter stack and doesn't return it. | |
* | |
* @method popMask | |
* @param maskData {Array} | |
* @param renderSession {Object} an object containing all the useful parameters | |
*/ | |
PIXI.WebGLMaskManager.prototype.popMask = function(maskData, renderSession) | |
{ | |
var gl = this.gl; | |
if (maskData._webGL[gl.id] === undefined || maskData._webGL[gl.id].data === undefined || maskData._webGL[gl.id].data.length === 0) | |
{ | |
return; | |
} | |
renderSession.stencilManager.popStencil(maskData, maskData._webGL[gl.id].data[0], renderSession); | |
}; | |
/** | |
* Destroys the mask stack. | |
* | |
* @method destroy | |
*/ | |
PIXI.WebGLMaskManager.prototype.destroy = function() | |
{ | |
this.gl = null; | |
}; | |
/** | |
* @author Mat Groves http://matgroves.com/ @Doormat23 | |
*/ | |
/** | |
* @class WebGLStencilManager | |
* @constructor | |
* @private | |
*/ | |
PIXI.WebGLStencilManager = function() | |
{ | |
this.stencilStack = []; | |
this.reverse = true; | |
this.count = 0; | |
}; | |
/** | |
* Sets the drawing context to the one given in parameter. | |
* | |
* @method setContext | |
* @param gl {WebGLContext} the current WebGL drawing context | |
*/ | |
PIXI.WebGLStencilManager.prototype.setContext = function(gl) | |
{ | |
this.gl = gl; | |
}; | |
/** | |
* Applies the Mask and adds it to the current filter stack. | |
* | |
* @method pushMask | |
* @param graphics {Graphics} | |
* @param webGLData {Array} | |
* @param renderSession {Object} | |
*/ | |
PIXI.WebGLStencilManager.prototype.pushStencil = function(graphics, webGLData, renderSession) | |
{ | |
var gl = this.gl; | |
this.bindGraphics(graphics, webGLData, renderSession); | |
if(this.stencilStack.length === 0) | |
{ | |
gl.enable(gl.STENCIL_TEST); | |
gl.clear(gl.STENCIL_BUFFER_BIT); | |
this.reverse = true; | |
this.count = 0; | |
} | |
this.stencilStack.push(webGLData); | |
var level = this.count; | |
gl.colorMask(false, false, false, false); | |
gl.stencilFunc(gl.ALWAYS,0,0xFF); | |
gl.stencilOp(gl.KEEP,gl.KEEP,gl.INVERT); | |
// draw the triangle strip! | |
if(webGLData.mode === 1) | |
{ | |
gl.drawElements(gl.TRIANGLE_FAN, webGLData.indices.length - 4, gl.UNSIGNED_SHORT, 0 ); | |
if(this.reverse) | |
{ | |
gl.stencilFunc(gl.EQUAL, 0xFF - level, 0xFF); | |
gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); | |
} | |
else | |
{ | |
gl.stencilFunc(gl.EQUAL,level, 0xFF); | |
gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); | |
} | |
// draw a quad to increment.. | |
gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); | |
if(this.reverse) | |
{ | |
gl.stencilFunc(gl.EQUAL,0xFF-(level+1), 0xFF); | |
} | |
else | |
{ | |
gl.stencilFunc(gl.EQUAL,level+1, 0xFF); | |
} | |
this.reverse = !this.reverse; | |
} | |
else | |
{ | |
if(!this.reverse) | |
{ | |
gl.stencilFunc(gl.EQUAL, 0xFF - level, 0xFF); | |
gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); | |
} | |
else | |
{ | |
gl.stencilFunc(gl.EQUAL,level, 0xFF); | |
gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); | |
} | |
gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); | |
if(!this.reverse) | |
{ | |
gl.stencilFunc(gl.EQUAL,0xFF-(level+1), 0xFF); | |
} | |
else | |
{ | |
gl.stencilFunc(gl.EQUAL,level+1, 0xFF); | |
} | |
} | |
gl.colorMask(true, true, true, true); | |
gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); | |
this.count++; | |
}; | |
/** | |
* TODO this does not belong here! | |
* | |
* @method bindGraphics | |
* @param graphics {Graphics} | |
* @param webGLData {Array} | |
* @param renderSession {Object} | |
*/ | |
PIXI.WebGLStencilManager.prototype.bindGraphics = function(graphics, webGLData, renderSession) | |
{ | |
//if(this._currentGraphics === graphics)return; | |
this._currentGraphics = graphics; | |
var gl = this.gl; | |
// bind the graphics object.. | |
var projection = renderSession.projection, | |
offset = renderSession.offset, | |
shader;// = renderSession.shaderManager.primitiveShader; | |
if(webGLData.mode === 1) | |
{ | |
shader = renderSession.shaderManager.complexPrimitiveShader; | |
renderSession.shaderManager.setShader( shader ); | |
gl.uniform1f(shader.flipY, renderSession.flipY); | |
gl.uniformMatrix3fv(shader.translationMatrix, false, graphics.worldTransform.toArray(true)); | |
gl.uniform2f(shader.projectionVector, projection.x, -projection.y); | |
gl.uniform2f(shader.offsetVector, -offset.x, -offset.y); | |
gl.uniform3fv(shader.tintColor, PIXI.hex2rgb(graphics.tint)); | |
gl.uniform3fv(shader.color, webGLData.color); | |
gl.uniform1f(shader.alpha, graphics.worldAlpha * webGLData.alpha); | |
gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); | |
gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 4 * 2, 0); | |
// now do the rest.. | |
// set the index buffer! | |
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); | |
} | |
else | |
{ | |
//renderSession.shaderManager.activatePrimitiveShader(); | |
shader = renderSession.shaderManager.primitiveShader; | |
renderSession.shaderManager.setShader( shader ); | |
gl.uniformMatrix3fv(shader.translationMatrix, false, graphics.worldTransform.toArray(true)); | |
gl.uniform1f(shader.flipY, renderSession.flipY); | |
gl.uniform2f(shader.projectionVector, projection.x, -projection.y); | |
gl.uniform2f(shader.offsetVector, -offset.x, -offset.y); | |
gl.uniform3fv(shader.tintColor, PIXI.hex2rgb(graphics.tint)); | |
gl.uniform1f(shader.alpha, graphics.worldAlpha); | |
gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); | |
gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 4 * 6, 0); | |
gl.vertexAttribPointer(shader.colorAttribute, 4, gl.FLOAT, false,4 * 6, 2 * 4); | |
// set the index buffer! | |
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); | |
} | |
}; | |
/** | |
* @method popStencil | |
* @param graphics {Graphics} | |
* @param webGLData {Array} | |
* @param renderSession {Object} | |
*/ | |
PIXI.WebGLStencilManager.prototype.popStencil = function(graphics, webGLData, renderSession) | |
{ | |
var gl = this.gl; | |
this.stencilStack.pop(); | |
this.count--; | |
if(this.stencilStack.length === 0) | |
{ | |
// the stack is empty! | |
gl.disable(gl.STENCIL_TEST); | |
} | |
else | |
{ | |
var level = this.count; | |
this.bindGraphics(graphics, webGLData, renderSession); | |
gl.colorMask(false, false, false, false); | |
if(webGLData.mode === 1) | |
{ | |
this.reverse = !this.reverse; | |
if(this.reverse) | |
{ | |
gl.stencilFunc(gl.EQUAL, 0xFF - (level+1), 0xFF); | |
gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); | |
} | |
else | |
{ | |
gl.stencilFunc(gl.EQUAL,level+1, 0xFF); | |
gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); | |
} | |
// draw a quad to increment.. | |
gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); | |
gl.stencilFunc(gl.ALWAYS,0,0xFF); | |
gl.stencilOp(gl.KEEP,gl.KEEP,gl.INVERT); | |
// draw the triangle strip! | |
gl.drawElements(gl.TRIANGLE_FAN, webGLData.indices.length - 4, gl.UNSIGNED_SHORT, 0 ); | |
if(!this.reverse) | |
{ | |
gl.stencilFunc(gl.EQUAL,0xFF-(level), 0xFF); | |
} | |
else | |
{ | |
gl.stencilFunc(gl.EQUAL,level, 0xFF); | |
} | |
} | |
else | |
{ | |
// console.log("<<>>") | |
if(!this.reverse) | |
{ | |
gl.stencilFunc(gl.EQUAL, 0xFF - (level+1), 0xFF); | |
gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); | |
} | |
else | |
{ | |
gl.stencilFunc(gl.EQUAL,level+1, 0xFF); | |
gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); | |
} | |
gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); | |
if(!this.reverse) | |
{ | |
gl.stencilFunc(gl.EQUAL,0xFF-(level), 0xFF); | |
} | |
else | |
{ | |
gl.stencilFunc(gl.EQUAL,level, 0xFF); | |
} | |
} | |
gl.colorMask(true, true, true, true); | |
gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); | |
} | |
}; | |
/** | |
* Destroys the mask stack. | |
* | |
* @method destroy | |
*/ | |
PIXI.WebGLStencilManager.prototype.destroy = function() | |
{ | |
this.stencilStack = null; | |
this.gl = null; | |
}; | |
/** | |
* @author Mat Groves http://matgroves.com/ @Doormat23 | |
*/ | |
/** | |
* @class WebGLShaderManager | |
* @constructor | |
* @private | |
*/ | |
PIXI.WebGLShaderManager = function() | |
{ | |
/** | |
* @property maxAttibs | |
* @type Number | |
*/ | |
this.maxAttibs = 10; | |
/** | |
* @property attribState | |
* @type Array | |
*/ | |
this.attribState = []; | |
/** | |
* @property tempAttribState | |
* @type Array | |
*/ | |
this.tempAttribState = []; | |
for (var i = 0; i < this.maxAttibs; i++) | |
{ | |
this.attribState[i] = false; | |
} | |
/** | |
* @property stack | |
* @type Array | |
*/ | |
this.stack = []; | |
}; | |
PIXI.WebGLShaderManager.prototype.constructor = PIXI.WebGLShaderManager; | |
/** | |
* Initialises the context and the properties. | |
* | |
* @method setContext | |
* @param gl {WebGLContext} the current WebGL drawing context | |
*/ | |
PIXI.WebGLShaderManager.prototype.setContext = function(gl) | |
{ | |
this.gl = gl; | |
// the next one is used for rendering primitives | |
this.primitiveShader = new PIXI.PrimitiveShader(gl); | |
// the next one is used for rendering triangle strips | |
this.complexPrimitiveShader = new PIXI.ComplexPrimitiveShader(gl); | |
// this shader is used for the default sprite rendering | |
this.defaultShader = new PIXI.PixiShader(gl); | |
// this shader is used for the fast sprite rendering | |
this.fastShader = new PIXI.PixiFastShader(gl); | |
// the next one is used for rendering triangle strips | |
this.stripShader = new PIXI.StripShader(gl); | |
this.setShader(this.defaultShader); | |
}; | |
/** | |
* Takes the attributes given in parameters. | |
* | |
* @method setAttribs | |
* @param attribs {Array} attribs | |
*/ | |
PIXI.WebGLShaderManager.prototype.setAttribs = function(attribs) | |
{ | |
// reset temp state | |
var i; | |
for (i = 0; i < this.tempAttribState.length; i++) | |
{ | |
this.tempAttribState[i] = false; | |
} | |
// set the new attribs | |
for (i = 0; i < attribs.length; i++) | |
{ | |
var attribId = attribs[i]; | |
this.tempAttribState[attribId] = true; | |
} | |
var gl = this.gl; | |
for (i = 0; i < this.attribState.length; i++) | |
{ | |
if(this.attribState[i] !== this.tempAttribState[i]) | |
{ | |
this.attribState[i] = this.tempAttribState[i]; | |
if(this.tempAttribState[i]) | |
{ | |
gl.enableVertexAttribArray(i); | |
} | |
else | |
{ | |
gl.disableVertexAttribArray(i); | |
} | |
} | |
} | |
}; | |
/** | |
* Sets the current shader. | |
* | |
* @method setShader | |
* @param shader {Any} | |
*/ | |
PIXI.WebGLShaderManager.prototype.setShader = function(shader) | |
{ | |
if(this._currentId === shader._UID)return false; | |
this._currentId = shader._UID; | |
this.currentShader = shader; | |
this.gl.useProgram(shader.program); | |
this.setAttribs(shader.attributes); | |
return true; | |
}; | |
/** | |
* Destroys this object. | |
* | |
* @method destroy | |
*/ | |
PIXI.WebGLShaderManager.prototype.destroy = function() | |
{ | |
this.attribState = null; | |
this.tempAttribState = null; | |
this.primitiveShader.destroy(); | |
this.complexPrimitiveShader.destroy(); | |
this.defaultShader.destroy(); | |
this.fastShader.destroy(); | |
this.stripShader.destroy(); | |
this.gl = null; | |
}; | |
/** | |
* @author Mat Groves | |
* | |
* Big thanks to the very clever Matt DesLauriers <mattdesl> https://github.com/mattdesl/ | |
* for creating the original pixi version! | |
* Also a thanks to https://github.com/bchevalier for tweaking the tint and alpha so that they now share 4 bytes on the vertex buffer | |
* | |
* Heavily inspired by LibGDX's WebGLSpriteBatch: | |
* https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/WebGLSpriteBatch.java | |
*/ | |
/** | |
* | |
* @class WebGLSpriteBatch | |
* @private | |
* @constructor | |
*/ | |
PIXI.WebGLSpriteBatch = function() | |
{ | |
/** | |
* @property vertSize | |
* @type Number | |
*/ | |
this.vertSize = 5; | |
/** | |
* The number of images in the SpriteBatch before it flushes | |
* @property size | |
* @type Number | |
*/ | |
this.size = 2000;//Math.pow(2, 16) / this.vertSize; | |
//the total number of bytes in our batch | |
var numVerts = this.size * 4 * 4 * this.vertSize; | |
//the total number of indices in our batch | |
var numIndices = this.size * 6; | |
/** | |
* Holds the vertices | |
* | |
* @property vertices | |
* @type ArrayBuffer | |
*/ | |
this.vertices = new PIXI.ArrayBuffer(numVerts); | |
/** | |
* View on the vertices as a Float32Array | |
* | |
* @property positions | |
* @type Float32Array | |
*/ | |
this.positions = new PIXI.Float32Array(this.vertices); | |
/** | |
* View on the vertices as a Uint32Array | |
* | |
* @property colors | |
* @type Uint32Array | |
*/ | |
this.colors = new PIXI.Uint32Array(this.vertices); | |
/** | |
* Holds the indices | |
* | |
* @property indices | |
* @type Uint16Array | |
*/ | |
this.indices = new PIXI.Uint16Array(numIndices); | |
/** | |
* @property lastIndexCount | |
* @type Number | |
*/ | |
this.lastIndexCount = 0; | |
for (var i=0, j=0; i < numIndices; i += 6, j += 4) | |
{ | |
this.indices[i + 0] = j + 0; | |
this.indices[i + 1] = j + 1; | |
this.indices[i + 2] = j + 2; | |
this.indices[i + 3] = j + 0; | |
this.indices[i + 4] = j + 2; | |
this.indices[i + 5] = j + 3; | |
} | |
/** | |
* @property drawing | |
* @type Boolean | |
*/ | |
this.drawing = false; | |
/** | |
* @property currentBatchSize | |
* @type Number | |
*/ | |
this.currentBatchSize = 0; | |
/** | |
* @property currentBaseTexture | |
* @type BaseTexture | |
*/ | |
this.currentBaseTexture = null; | |
/** | |
* @property dirty | |
* @type Boolean | |
*/ | |
this.dirty = true; | |
/** | |
* @property textures | |
* @type Array | |
*/ | |
this.textures = []; | |
/** | |
* @property blendModes | |
* @type Array | |
*/ | |
this.blendModes = []; | |
/** | |
* @property shaders | |
* @type Array | |
*/ | |
this.shaders = []; | |
/** | |
* @property sprites | |
* @type Array | |
*/ | |
this.sprites = []; | |
/** | |
* @property defaultShader | |
* @type AbstractFilter | |
*/ | |
this.defaultShader = new PIXI.AbstractFilter([ | |
'precision lowp float;', | |
'varying vec2 vTextureCoord;', | |
'varying vec4 vColor;', | |
'uniform sampler2D uSampler;', | |
'void main(void) {', | |
' gl_FragColor = texture2D(uSampler, vTextureCoord) * vColor ;', | |
'}' | |
]); | |
}; | |
/** | |
* @method setContext | |
* @param gl {WebGLContext} the current WebGL drawing context | |
*/ | |
PIXI.WebGLSpriteBatch.prototype.setContext = function(gl) | |
{ | |
this.gl = gl; | |
// create a couple of buffers | |
this.vertexBuffer = gl.createBuffer(); | |
this.indexBuffer = gl.createBuffer(); | |
// 65535 is max index, so 65535 / 6 = 10922. | |
//upload the index data | |
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); | |
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indices, gl.STATIC_DRAW); | |
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); | |
gl.bufferData(gl.ARRAY_BUFFER, this.vertices, gl.DYNAMIC_DRAW); | |
this.currentBlendMode = 99999; | |
var shader = new PIXI.PixiShader(gl); | |
shader.fragmentSrc = this.defaultShader.fragmentSrc; | |
shader.uniforms = {}; | |
shader.init(); | |
this.defaultShader.shaders[gl.id] = shader; | |
}; | |
/** | |
* @method begin | |
* @param renderSession {Object} The RenderSession object | |
*/ | |
PIXI.WebGLSpriteBatch.prototype.begin = function(renderSession) | |
{ | |
this.renderSession = renderSession; | |
this.shader = this.renderSession.shaderManager.defaultShader; | |
this.start(); | |
}; | |
/** | |
* @method end | |
*/ | |
PIXI.WebGLSpriteBatch.prototype.end = function() | |
{ | |
this.flush(); | |
}; | |
/** | |
* @method render | |
* @param sprite {Sprite} the sprite to render when using this spritebatch | |
* @param {Matrix} [matrix] - Optional matrix. If provided the Display Object will be rendered using this matrix, otherwise it will use its worldTransform. | |
*/ | |
PIXI.WebGLSpriteBatch.prototype.render = function(sprite, matrix) | |
{ | |
var texture = sprite.texture; | |
// They provided an alternative rendering matrix, so use it | |
var wt = sprite.worldTransform; | |
if (matrix) | |
{ | |
wt = matrix; | |
} | |
// check texture.. | |
if (this.currentBatchSize >= this.size) | |
{ | |
this.flush(); | |
this.currentBaseTexture = texture.baseTexture; | |
} | |
// get the uvs for the texture | |
var uvs = texture._uvs; | |
// if the uvs have not updated then no point rendering just yet! | |
if (!uvs) | |
{ | |
return; | |
} | |
var aX = sprite.anchor.x; | |
var aY = sprite.anchor.y; | |
var w0, w1, h0, h1; | |
if (texture.trim) | |
{ | |
// if the sprite is trimmed then we need to add the extra space before transforming the sprite coords. | |
var trim = texture.trim; | |
w1 = trim.x - aX * trim.width; | |
w0 = w1 + texture.crop.width; | |
h1 = trim.y - aY * trim.height; | |
h0 = h1 + texture.crop.height; | |
} | |
else | |
{ | |
w0 = (texture.frame.width) * (1-aX); | |
w1 = (texture.frame.width) * -aX; | |
h0 = texture.frame.height * (1-aY); | |
h1 = texture.frame.height * -aY; | |
} | |
var i = this.currentBatchSize * 4 * this.vertSize; | |
var resolution = texture.baseTexture.resolution; | |
var a = wt.a / resolution; | |
var b = wt.b / resolution; | |
var c = wt.c / resolution; | |
var d = wt.d / resolution; | |
var tx = wt.tx; | |
var ty = wt.ty; | |
var colors = this.colors; | |
var positions = this.positions; | |
if (this.renderSession.roundPixels) | |
{ | |
// xy | |
positions[i] = a * w1 + c * h1 + tx | 0; | |
positions[i+1] = d * h1 + b * w1 + ty | 0; | |
// xy | |
positions[i+5] = a * w0 + c * h1 + tx | 0; | |
positions[i+6] = d * h1 + b * w0 + ty | 0; | |
// xy | |
positions[i+10] = a * w0 + c * h0 + tx | 0; | |
positions[i+11] = d * h0 + b * w0 + ty | 0; | |
// xy | |
positions[i+15] = a * w1 + c * h0 + tx | 0; | |
positions[i+16] = d * h0 + b * w1 + ty | 0; | |
} | |
else | |
{ | |
// xy | |
positions[i] = a * w1 + c * h1 + tx; | |
positions[i+1] = d * h1 + b * w1 + ty; | |
// xy | |
positions[i+5] = a * w0 + c * h1 + tx; | |
positions[i+6] = d * h1 + b * w0 + ty; | |
// xy | |
positions[i+10] = a * w0 + c * h0 + tx; | |
positions[i+11] = d * h0 + b * w0 + ty; | |
// xy | |
positions[i+15] = a * w1 + c * h0 + tx; | |
positions[i+16] = d * h0 + b * w1 + ty; | |
} | |
// uv | |
positions[i+2] = uvs.x0; | |
positions[i+3] = uvs.y0; | |
// uv | |
positions[i+7] = uvs.x1; | |
positions[i+8] = uvs.y1; | |
// uv | |
positions[i+12] = uvs.x2; | |
positions[i+13] = uvs.y2; | |
// uv | |
positions[i+17] = uvs.x3; | |
positions[i+18] = uvs.y3; | |
// color and alpha | |
var tint = sprite.tint; | |
colors[i+4] = colors[i+9] = colors[i+14] = colors[i+19] = (tint >> 16) + (tint & 0xff00) + ((tint & 0xff) << 16) + (sprite.worldAlpha * 255 << 24); | |
// increment the batchsize | |
this.sprites[this.currentBatchSize++] = sprite; | |
}; | |
/** | |
* Renders a TilingSprite using the spriteBatch. | |
* | |
* @method renderTilingSprite | |
* @param sprite {TilingSprite} the sprite to render | |
*/ | |
PIXI.WebGLSpriteBatch.prototype.renderTilingSprite = function(sprite) | |
{ | |
var texture = sprite.tilingTexture; | |
// check texture.. | |
if (this.currentBatchSize >= this.size) | |
{ | |
this.flush(); | |
this.currentBaseTexture = texture.baseTexture; | |
} | |
// set the textures uvs temporarily | |
if (!sprite._uvs) | |
{ | |
sprite._uvs = new PIXI.TextureUvs(); | |
} | |
var uvs = sprite._uvs; | |
var w = texture.baseTexture.width; | |
var h = texture.baseTexture.height; | |
// var w = sprite._frame.sourceSizeW; | |
// var h = sprite._frame.sourceSizeH; | |
// w = 16; | |
// h = 16; | |
sprite.tilePosition.x %= w * sprite.tileScaleOffset.x; | |
sprite.tilePosition.y %= h * sprite.tileScaleOffset.y; | |
var offsetX = sprite.tilePosition.x / (w * sprite.tileScaleOffset.x); | |
var offsetY = sprite.tilePosition.y / (h * sprite.tileScaleOffset.y); | |
var scaleX = (sprite.width / w) / (sprite.tileScale.x * sprite.tileScaleOffset.x); | |
var scaleY = (sprite.height / h) / (sprite.tileScale.y * sprite.tileScaleOffset.y); | |
uvs.x0 = 0 - offsetX; | |
uvs.y0 = 0 - offsetY; | |
uvs.x1 = (1 * scaleX) - offsetX; | |
uvs.y1 = 0 - offsetY; | |
uvs.x2 = (1 * scaleX) - offsetX; | |
uvs.y2 = (1 * scaleY) - offsetY; | |
uvs.x3 = 0 - offsetX; | |
uvs.y3 = (1 * scaleY) - offsetY; | |
// Get the sprites current alpha and tint and combine them into a single color | |
var tint = sprite.tint; | |
var color = (tint >> 16) + (tint & 0xff00) + ((tint & 0xff) << 16) + (sprite.worldAlpha * 255 << 24); | |
var positions = this.positions; | |
var colors = this.colors; | |
var width = sprite.width; | |
var height = sprite.height; | |
// TODO trim?? | |
var aX = sprite.anchor.x; | |
var aY = sprite.anchor.y; | |
var w0 = width * (1-aX); | |
var w1 = width * -aX; | |
var h0 = height * (1-aY); | |
var h1 = height * -aY; | |
var i = this.currentBatchSize * 4 * this.vertSize; | |
var resolution = texture.baseTexture.resolution; | |
var wt = sprite.worldTransform; | |
var a = wt.a / resolution; | |
var b = wt.b / resolution; | |
var c = wt.c / resolution; | |
var d = wt.d / resolution; | |
var tx = wt.tx; | |
var ty = wt.ty; | |
// xy | |
positions[i++] = a * w1 + c * h1 + tx; | |
positions[i++] = d * h1 + b * w1 + ty; | |
// uv | |
positions[i++] = uvs.x0; | |
positions[i++] = uvs.y0; | |
// color | |
colors[i++] = color; | |
// xy | |
positions[i++] = (a * w0 + c * h1 + tx); | |
positions[i++] = d * h1 + b * w0 + ty; | |
// uv | |
positions[i++] = uvs.x1; | |
positions[i++] = uvs.y1; | |
// color | |
colors[i++] = color; | |
// xy | |
positions[i++] = a * w0 + c * h0 + tx; | |
positions[i++] = d * h0 + b * w0 + ty; | |
// uv | |
positions[i++] = uvs.x2; | |
positions[i++] = uvs.y2; | |
// color | |
colors[i++] = color; | |
// xy | |
positions[i++] = a * w1 + c * h0 + tx; | |
positions[i++] = d * h0 + b * w1 + ty; | |
// uv | |
positions[i++] = uvs.x3; | |
positions[i++] = uvs.y3; | |
// color | |
colors[i++] = color; | |
// increment the batchsize | |
this.sprites[this.currentBatchSize++] = sprite; | |
}; | |
/** | |
* Renders the content and empties the current batch. | |
* | |
* @method flush | |
*/ | |
PIXI.WebGLSpriteBatch.prototype.flush = function() | |
{ | |
// If the batch is length 0 then return as there is nothing to draw | |
if (this.currentBatchSize === 0) | |
{ | |
return; | |
} | |
var gl = this.gl; | |
var shader; | |
if (this.dirty) | |
{ | |
this.dirty = false; | |
// bind the main texture | |
gl.activeTexture(gl.TEXTURE0); | |
// bind the buffers | |
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); | |
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); | |
shader = this.defaultShader.shaders[gl.id]; | |
// this is the same for each shader? | |
var stride = this.vertSize * 4; | |
gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, stride, 0); | |
gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, stride, 2 * 4); | |
// color attributes will be interpreted as unsigned bytes and normalized | |
gl.vertexAttribPointer(shader.colorAttribute, 4, gl.UNSIGNED_BYTE, true, stride, 4 * 4); | |
} | |
// upload the verts to the buffer | |
if (this.currentBatchSize > (this.size * 0.5)) | |
{ | |
gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertices); | |
} | |
else | |
{ | |
var view = this.positions.subarray(0, this.currentBatchSize * 4 * this.vertSize); | |
gl.bufferSubData(gl.ARRAY_BUFFER, 0, view); | |
} | |
var nextTexture, nextBlendMode, nextShader; | |
var batchSize = 0; | |
var start = 0; | |
var currentBaseTexture = null; | |
var currentBlendMode = this.renderSession.blendModeManager.currentBlendMode; | |
var currentShader = null; | |
var blendSwap = false; | |
var shaderSwap = false; | |
var sprite; | |
for (var i = 0, j = this.currentBatchSize; i < j; i++) { | |
sprite = this.sprites[i]; | |
if (sprite.tilingTexture) | |
{ | |
nextTexture = sprite.tilingTexture.baseTexture; | |
} | |
else | |
{ | |
nextTexture = sprite.texture.baseTexture; | |
} | |
nextBlendMode = sprite.blendMode; | |
nextShader = sprite.shader || this.defaultShader; | |
blendSwap = currentBlendMode !== nextBlendMode; | |
shaderSwap = currentShader !== nextShader; // should I use _UIDS??? | |
var skip = nextTexture.skipRender; | |
if (skip && sprite.children.length > 0) | |
{ | |
skip = false; | |
} | |
if ((currentBaseTexture !== nextTexture && !skip) || blendSwap || shaderSwap) | |
{ | |
this.renderBatch(currentBaseTexture, batchSize, start); | |
start = i; | |
batchSize = 0; | |
currentBaseTexture = nextTexture; | |
if (blendSwap) | |
{ | |
currentBlendMode = nextBlendMode; | |
this.renderSession.blendModeManager.setBlendMode(currentBlendMode); | |
} | |
if (shaderSwap) | |
{ | |
currentShader = nextShader; | |
shader = currentShader.shaders[gl.id]; | |
if (!shader) | |
{ | |
shader = new PIXI.PixiShader(gl); | |
shader.fragmentSrc = currentShader.fragmentSrc; | |
shader.uniforms = currentShader.uniforms; | |
shader.init(); | |
currentShader.shaders[gl.id] = shader; | |
} | |
// set shader function??? | |
this.renderSession.shaderManager.setShader(shader); | |
if (shader.dirty) | |
{ | |
shader.syncUniforms(); | |
} | |
// both these only need to be set if they are changing.. | |
// set the projection | |
var projection = this.renderSession.projection; | |
gl.uniform2f(shader.projectionVector, projection.x, projection.y); | |
// TODO - this is temporary! | |
var offsetVector = this.renderSession.offset; | |
gl.uniform2f(shader.offsetVector, offsetVector.x, offsetVector.y); | |
// set the pointers | |
} | |
} | |
batchSize++; | |
} | |
this.renderBatch(currentBaseTexture, batchSize, start); | |
// then reset the batch! | |
this.currentBatchSize = 0; | |
}; | |
/** | |
* @method renderBatch | |
* @param texture {Texture} | |
* @param size {Number} | |
* @param startIndex {Number} | |
*/ | |
PIXI.WebGLSpriteBatch.prototype.renderBatch = function(texture, size, startIndex) | |
{ | |
if (size === 0) | |
{ | |
return; | |
} | |
var gl = this.gl; | |
// check if a texture is dirty.. | |
if (texture._dirty[gl.id]) | |
{ | |
if (!this.renderSession.renderer.updateTexture(texture)) | |
{ | |
// If updateTexture returns false then we cannot render it, so bail out now | |
return; | |
} | |
} | |
else | |
{ | |
// bind the current texture | |
gl.bindTexture(gl.TEXTURE_2D, texture._glTextures[gl.id]); | |
} | |
// now draw those suckas! | |
gl.drawElements(gl.TRIANGLES, size * 6, gl.UNSIGNED_SHORT, startIndex * 6 * 2); | |
// increment the draw count | |
this.renderSession.drawCount++; | |
}; | |
/** | |
* @method stop | |
*/ | |
PIXI.WebGLSpriteBatch.prototype.stop = function() | |
{ | |
this.flush(); | |
this.dirty = true; | |
}; | |
/** | |
* @method start | |
*/ | |
PIXI.WebGLSpriteBatch.prototype.start = function() | |
{ | |
this.dirty = true; | |
}; | |
/** | |
* Destroys the SpriteBatch. | |
* | |
* @method destroy | |
*/ | |
PIXI.WebGLSpriteBatch.prototype.destroy = function() | |
{ | |
this.vertices = null; | |
this.indices = null; | |
this.gl.deleteBuffer(this.vertexBuffer); | |
this.gl.deleteBuffer(this.indexBuffer); | |
this.currentBaseTexture = null; | |
this.gl = null; | |
}; | |
/** | |
* @author Mat Groves | |
* | |
* Big thanks to the very clever Matt DesLauriers <mattdesl> https://github.com/mattdesl/ | |
* for creating the original pixi version! | |
* | |
* Heavily inspired by LibGDX's WebGLSpriteBatch: | |
* https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/WebGLSpriteBatch.java | |
*/ | |
/** | |
* @class WebGLFastSpriteBatch | |
* @constructor | |
*/ | |
PIXI.WebGLFastSpriteBatch = function(gl) | |
{ | |
/** | |
* @property vertSize | |
* @type Number | |
*/ | |
this.vertSize = 10; | |
/** | |
* @property maxSize | |
* @type Number | |
*/ | |
this.maxSize = 6000;//Math.pow(2, 16) / this.vertSize; | |
/** | |
* @property size | |
* @type Number | |
*/ | |
this.size = this.maxSize; | |
//the total number of floats in our batch | |
var numVerts = this.size * 4 * this.vertSize; | |
//the total number of indices in our batch | |
var numIndices = this.maxSize * 6; | |
/** | |
* Vertex data | |
* @property vertices | |
* @type Float32Array | |
*/ | |
this.vertices = new PIXI.Float32Array(numVerts); | |
/** | |
* Index data | |
* @property indices | |
* @type Uint16Array | |
*/ | |
this.indices = new PIXI.Uint16Array(numIndices); | |
/** | |
* @property vertexBuffer | |
* @type Object | |
*/ | |
this.vertexBuffer = null; | |
/** | |
* @property indexBuffer | |
* @type Object | |
*/ | |
this.indexBuffer = null; | |
/** | |
* @property lastIndexCount | |
* @type Number | |
*/ | |
this.lastIndexCount = 0; | |
for (var i=0, j=0; i < numIndices; i += 6, j += 4) | |
{ | |
this.indices[i + 0] = j + 0; | |
this.indices[i + 1] = j + 1; | |
this.indices[i + 2] = j + 2; | |
this.indices[i + 3] = j + 0; | |
this.indices[i + 4] = j + 2; | |
this.indices[i + 5] = j + 3; | |
} | |
/** | |
* @property drawing | |
* @type Boolean | |
*/ | |
this.drawing = false; | |
/** | |
* @property currentBatchSize | |
* @type Number | |
*/ | |
this.currentBatchSize = 0; | |
/** | |
* @property currentBaseTexture | |
* @type BaseTexture | |
*/ | |
this.currentBaseTexture = null; | |
/** | |
* @property currentBlendMode | |
* @type Number | |
*/ | |
this.currentBlendMode = 0; | |
/** | |
* @property renderSession | |
* @type Object | |
*/ | |
this.renderSession = null; | |
/** | |
* @property shader | |
* @type Object | |
*/ | |
this.shader = null; | |
/** | |
* @property matrix | |
* @type Matrix | |
*/ | |
this.matrix = null; | |
this.setContext(gl); | |
}; | |
PIXI.WebGLFastSpriteBatch.prototype.constructor = PIXI.WebGLFastSpriteBatch; | |
/** | |
* Sets the WebGL Context. | |
* | |
* @method setContext | |
* @param gl {WebGLContext} the current WebGL drawing context | |
*/ | |
PIXI.WebGLFastSpriteBatch.prototype.setContext = function(gl) | |
{ | |
this.gl = gl; | |
// create a couple of buffers | |
this.vertexBuffer = gl.createBuffer(); | |
this.indexBuffer = gl.createBuffer(); | |
// 65535 is max index, so 65535 / 6 = 10922. | |
//upload the index data | |
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); | |
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indices, gl.STATIC_DRAW); | |
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); | |
gl.bufferData(gl.ARRAY_BUFFER, this.vertices, gl.DYNAMIC_DRAW); | |
}; | |
/** | |
* @method begin | |
* @param spriteBatch {WebGLSpriteBatch} | |
* @param renderSession {Object} | |
*/ | |
PIXI.WebGLFastSpriteBatch.prototype.begin = function(spriteBatch, renderSession) | |
{ | |
this.renderSession = renderSession; | |
this.shader = this.renderSession.shaderManager.fastShader; | |
this.matrix = spriteBatch.worldTransform.toArray(true); | |
this.start(); | |
}; | |
/** | |
* @method end | |
*/ | |
PIXI.WebGLFastSpriteBatch.prototype.end = function() | |
{ | |
this.flush(); | |
}; | |
/** | |
* @method render | |
* @param spriteBatch {WebGLSpriteBatch} | |
*/ | |
PIXI.WebGLFastSpriteBatch.prototype.render = function(spriteBatch) | |
{ | |
var children = spriteBatch.children; | |
var sprite = children[0]; | |
// if the uvs have not updated then no point rendering just yet! | |
// check texture. | |
if(!sprite.texture._uvs)return; | |
this.currentBaseTexture = sprite.texture.baseTexture; | |
// check blend mode | |
if(sprite.blendMode !== this.renderSession.blendModeManager.currentBlendMode) | |
{ | |
this.flush(); | |
this.renderSession.blendModeManager.setBlendMode(sprite.blendMode); | |
} | |
for(var i=0,j= children.length; i<j; i++) | |
{ | |
this.renderSprite(children[i]); | |
} | |
this.flush(); | |
}; | |
/** | |
* @method renderSprite | |
* @param sprite {Sprite} | |
*/ | |
PIXI.WebGLFastSpriteBatch.prototype.renderSprite = function(sprite) | |
{ | |
//sprite = children[i]; | |
if(!sprite.visible)return; | |
// TODO trim?? | |
if(sprite.texture.baseTexture !== this.currentBaseTexture && !sprite.texture.baseTexture.skipRender) | |
{ | |
this.flush(); | |
this.currentBaseTexture = sprite.texture.baseTexture; | |
if(!sprite.texture._uvs)return; | |
} | |
var uvs, vertices = this.vertices, width, height, w0, w1, h0, h1, index; | |
uvs = sprite.texture._uvs; | |
width = sprite.texture.frame.width; | |
height = sprite.texture.frame.height; | |
if (sprite.texture.trim) | |
{ | |
// if the sprite is trimmed then we need to add the extra space before transforming the sprite coords.. | |
var trim = sprite.texture.trim; | |
w1 = trim.x - sprite.anchor.x * trim.width; | |
w0 = w1 + sprite.texture.crop.width; | |
h1 = trim.y - sprite.anchor.y * trim.height; | |
h0 = h1 + sprite.texture.crop.height; | |
} | |
else | |
{ | |
w0 = (sprite.texture.frame.width ) * (1-sprite.anchor.x); | |
w1 = (sprite.texture.frame.width ) * -sprite.anchor.x; | |
h0 = sprite.texture.frame.height * (1-sprite.anchor.y); | |
h1 = sprite.texture.frame.height * -sprite.anchor.y; | |
} | |
index = this.currentBatchSize * 4 * this.vertSize; | |
// xy | |
vertices[index++] = w1; | |
vertices[index++] = h1; | |
vertices[index++] = sprite.position.x; | |
vertices[index++] = sprite.position.y; | |
//scale | |
vertices[index++] = sprite.scale.x; | |
vertices[index++] = sprite.scale.y; | |
//rotation | |
vertices[index++] = sprite.rotation; | |
// uv | |
vertices[index++] = uvs.x0; | |
vertices[index++] = uvs.y1; | |
// color | |
vertices[index++] = sprite.alpha; | |
// xy | |
vertices[index++] = w0; | |
vertices[index++] = h1; | |
vertices[index++] = sprite.position.x; | |
vertices[index++] = sprite.position.y; | |
//scale | |
vertices[index++] = sprite.scale.x; | |
vertices[index++] = sprite.scale.y; | |
//rotation | |
vertices[index++] = sprite.rotation; | |
// uv | |
vertices[index++] = uvs.x1; | |
vertices[index++] = uvs.y1; | |
// color | |
vertices[index++] = sprite.alpha; | |
// xy | |
vertices[index++] = w0; | |
vertices[index++] = h0; | |
vertices[index++] = sprite.position.x; | |
vertices[index++] = sprite.position.y; | |
//scale | |
vertices[index++] = sprite.scale.x; | |
vertices[index++] = sprite.scale.y; | |
//rotation | |
vertices[index++] = sprite.rotation; | |
// uv | |
vertices[index++] = uvs.x2; | |
vertices[index++] = uvs.y2; | |
// color | |
vertices[index++] = sprite.alpha; | |
// xy | |
vertices[index++] = w1; | |
vertices[index++] = h0; | |
vertices[index++] = sprite.position.x; | |
vertices[index++] = sprite.position.y; | |
//scale | |
vertices[index++] = sprite.scale.x; | |
vertices[index++] = sprite.scale.y; | |
//rotation | |
vertices[index++] = sprite.rotation; | |
// uv | |
vertices[index++] = uvs.x3; | |
vertices[index++] = uvs.y3; | |
// color | |
vertices[index++] = sprite.alpha; | |
// increment the batchs | |
this.currentBatchSize++; | |
if(this.currentBatchSize >= this.size) | |
{ | |
this.flush(); | |
} | |
}; | |
/** | |
* @method flush | |
*/ | |
PIXI.WebGLFastSpriteBatch.prototype.flush = function() | |
{ | |
// If the batch is length 0 then return as there is nothing to draw | |
if (this.currentBatchSize===0)return; | |
var gl = this.gl; | |
// bind the current texture | |
if(!this.currentBaseTexture._glTextures[gl.id])this.renderSession.renderer.updateTexture(this.currentBaseTexture, gl); | |
gl.bindTexture(gl.TEXTURE_2D, this.currentBaseTexture._glTextures[gl.id]); | |
// upload the verts to the buffer | |
if(this.currentBatchSize > ( this.size * 0.5 ) ) | |
{ | |
gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertices); | |
} | |
else | |
{ | |
var view = this.vertices.subarray(0, this.currentBatchSize * 4 * this.vertSize); | |
gl.bufferSubData(gl.ARRAY_BUFFER, 0, view); | |
} | |
// now draw those suckas! | |
gl.drawElements(gl.TRIANGLES, this.currentBatchSize * 6, gl.UNSIGNED_SHORT, 0); | |
// then reset the batch! | |
this.currentBatchSize = 0; | |
// increment the draw count | |
this.renderSession.drawCount++; | |
}; | |
/** | |
* @method stop | |
*/ | |
PIXI.WebGLFastSpriteBatch.prototype.stop = function() | |
{ | |
this.flush(); | |
}; | |
/** | |
* @method start | |
*/ | |
PIXI.WebGLFastSpriteBatch.prototype.start = function() | |
{ | |
var gl = this.gl; | |
// bind the main texture | |
gl.activeTexture(gl.TEXTURE0); | |
// bind the buffers | |
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); | |
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); | |
// set the projection | |
var projection = this.renderSession.projection; | |
gl.uniform2f(this.shader.projectionVector, projection.x, projection.y); | |
// set the matrix | |
gl.uniformMatrix3fv(this.shader.uMatrix, false, this.matrix); | |
// set the pointers | |
var stride = this.vertSize * 4; | |
gl.vertexAttribPointer(this.shader.aVertexPosition, 2, gl.FLOAT, false, stride, 0); | |
gl.vertexAttribPointer(this.shader.aPositionCoord, 2, gl.FLOAT, false, stride, 2 * 4); | |
gl.vertexAttribPointer(this.shader.aScale, 2, gl.FLOAT, false, stride, 4 * 4); | |
gl.vertexAttribPointer(this.shader.aRotation, 1, gl.FLOAT, false, stride, 6 * 4); | |
gl.vertexAttribPointer(this.shader.aTextureCoord, 2, gl.FLOAT, false, stride, 7 * 4); | |
gl.vertexAttribPointer(this.shader.colorAttribute, 1, gl.FLOAT, false, stride, 9 * 4); | |
}; | |
/** | |
* @author Mat Groves http://matgroves.com/ @Doormat23 | |
*/ | |
/** | |
* @class WebGLFilterManager | |
* @constructor | |
*/ | |
PIXI.WebGLFilterManager = function() | |
{ | |
/** | |
* @property filterStack | |
* @type Array | |
*/ | |
this.filterStack = []; | |
/** | |
* @property offsetX | |
* @type Number | |
*/ | |
this.offsetX = 0; | |
/** | |
* @property offsetY | |
* @type Number | |
*/ | |
this.offsetY = 0; | |
}; | |
PIXI.WebGLFilterManager.prototype.constructor = PIXI.WebGLFilterManager; | |
/** | |
* Initialises the context and the properties. | |
* | |
* @method setContext | |
* @param gl {WebGLContext} the current WebGL drawing context | |
*/ | |
PIXI.WebGLFilterManager.prototype.setContext = function(gl) | |
{ | |
this.gl = gl; | |
this.texturePool = []; | |
this.initShaderBuffers(); | |
}; | |
/** | |
* @method begin | |
* @param renderSession {RenderSession} | |
* @param buffer {ArrayBuffer} | |
*/ | |
PIXI.WebGLFilterManager.prototype.begin = function(renderSession, buffer) | |
{ | |
this.renderSession = renderSession; | |
this.defaultShader = renderSession.shaderManager.defaultShader; | |
var projection = this.renderSession.projection; | |
this.width = projection.x * 2; | |
this.height = -projection.y * 2; | |
this.buffer = buffer; | |
}; | |
/** | |
* Applies the filter and adds it to the current filter stack. | |
* | |
* @method pushFilter | |
* @param filterBlock {Object} the filter that will be pushed to the current filter stack | |
*/ | |
PIXI.WebGLFilterManager.prototype.pushFilter = function(filterBlock) | |
{ | |
var gl = this.gl; | |
var projection = this.renderSession.projection; | |
var offset = this.renderSession.offset; | |
filterBlock._filterArea = filterBlock.target.filterArea || filterBlock.target.getBounds(); | |
// >>> modify by nextht | |
filterBlock._previous_stencil_mgr = this.renderSession.stencilManager; | |
this.renderSession.stencilManager = new PIXI.WebGLStencilManager(); | |
this.renderSession.stencilManager.setContext(gl); | |
gl.disable(gl.STENCIL_TEST); | |
// <<< modify by nextht | |
// filter program | |
// OPTIMISATION - the first filter is free if its a simple color change? | |
this.filterStack.push(filterBlock); | |
var filter = filterBlock.filterPasses[0]; | |
this.offsetX += filterBlock._filterArea.x; | |
this.offsetY += filterBlock._filterArea.y; | |
var texture = this.texturePool.pop(); | |
if(!texture) | |
{ | |
texture = new PIXI.FilterTexture(this.gl, this.width * this.renderSession.resolution, this.height * this.renderSession.resolution); | |
} | |
else | |
{ | |
texture.resize(this.width * this.renderSession.resolution, this.height * this.renderSession.resolution); | |
} | |
gl.bindTexture(gl.TEXTURE_2D, texture.texture); | |
var filterArea = filterBlock._filterArea;// filterBlock.target.getBounds();///filterBlock.target.filterArea; | |
var padding = filter.padding; | |
filterArea.x -= padding; | |
filterArea.y -= padding; | |
filterArea.width += padding * 2; | |
filterArea.height += padding * 2; | |
// cap filter to screen size.. | |
if(filterArea.x < 0)filterArea.x = 0; | |
if(filterArea.width > this.width)filterArea.width = this.width; | |
if(filterArea.y < 0)filterArea.y = 0; | |
if(filterArea.height > this.height)filterArea.height = this.height; | |
//gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, filterArea.width, filterArea.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); | |
gl.bindFramebuffer(gl.FRAMEBUFFER, texture.frameBuffer); | |
// set view port | |
gl.viewport(0, 0, filterArea.width * this.renderSession.resolution, filterArea.height * this.renderSession.resolution); | |
projection.x = filterArea.width/2; | |
projection.y = -filterArea.height/2; | |
offset.x = -filterArea.x; | |
offset.y = -filterArea.y; | |
// update projection | |
// now restore the regular shader.. | |
// this.renderSession.shaderManager.setShader(this.defaultShader); | |
//gl.uniform2f(this.defaultShader.projectionVector, filterArea.width/2, -filterArea.height/2); | |
//gl.uniform2f(this.defaultShader.offsetVector, -filterArea.x, -filterArea.y); | |
gl.colorMask(true, true, true, true); | |
gl.clearColor(0,0,0, 0); | |
gl.clear(gl.COLOR_BUFFER_BIT); | |
filterBlock._glFilterTexture = texture; | |
}; | |
/** | |
* Removes the last filter from the filter stack and doesn't return it. | |
* | |
* @method popFilter | |
*/ | |
PIXI.WebGLFilterManager.prototype.popFilter = function() | |
{ | |
var gl = this.gl; | |
var filterBlock = this.filterStack.pop(); | |
var filterArea = filterBlock._filterArea; | |
var texture = filterBlock._glFilterTexture; | |
var projection = this.renderSession.projection; | |
var offset = this.renderSession.offset; | |
if(filterBlock.filterPasses.length > 1) | |
{ | |
gl.viewport(0, 0, filterArea.width * this.renderSession.resolution, filterArea.height * this.renderSession.resolution); | |
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); | |
this.vertexArray[0] = 0; | |
this.vertexArray[1] = filterArea.height; | |
this.vertexArray[2] = filterArea.width; | |
this.vertexArray[3] = filterArea.height; | |
this.vertexArray[4] = 0; | |
this.vertexArray[5] = 0; | |
this.vertexArray[6] = filterArea.width; | |
this.vertexArray[7] = 0; | |
gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); | |
gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); | |
// now set the uvs.. | |
this.uvArray[2] = filterArea.width/this.width; | |
this.uvArray[5] = filterArea.height/this.height; | |
this.uvArray[6] = filterArea.width/this.width; | |
this.uvArray[7] = filterArea.height/this.height; | |
gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); | |
var inputTexture = texture; | |
var outputTexture = this.texturePool.pop(); | |
if(!outputTexture)outputTexture = new PIXI.FilterTexture(this.gl, this.width * this.renderSession.resolution, this.height * this.renderSession.resolution); | |
outputTexture.resize(this.width * this.renderSession.resolution, this.height * this.renderSession.resolution); | |
// need to clear this FBO as it may have some left over elements from a previous filter. | |
gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); | |
gl.clear(gl.COLOR_BUFFER_BIT); | |
gl.disable(gl.BLEND); | |
for (var i = 0; i < filterBlock.filterPasses.length-1; i++) | |
{ | |
var filterPass = filterBlock.filterPasses[i]; | |
gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); | |
// set texture | |
gl.activeTexture(gl.TEXTURE0); | |
gl.bindTexture(gl.TEXTURE_2D, inputTexture.texture); | |
// draw texture.. | |
//filterPass.applyFilterPass(filterArea.width, filterArea.height); | |
this.applyFilterPass(filterPass, filterArea, filterArea.width, filterArea.height); | |
// swap the textures.. | |
var temp = inputTexture; | |
inputTexture = outputTexture; | |
outputTexture = temp; | |
} | |
gl.enable(gl.BLEND); | |
texture = inputTexture; | |
this.texturePool.push(outputTexture); | |
} | |
var filter = filterBlock.filterPasses[filterBlock.filterPasses.length-1]; | |
this.offsetX -= filterArea.x; | |
this.offsetY -= filterArea.y; | |
var sizeX = this.width; | |
var sizeY = this.height; | |
var offsetX = 0; | |
var offsetY = 0; | |
var buffer = this.buffer; | |
// time to render the filters texture to the previous scene | |
if(this.filterStack.length === 0) | |
{ | |
gl.colorMask(true, true, true, true);//this.transparent); | |
} | |
else | |
{ | |
var currentFilter = this.filterStack[this.filterStack.length-1]; | |
filterArea = currentFilter._filterArea; | |
sizeX = filterArea.width; | |
sizeY = filterArea.height; | |
offsetX = filterArea.x; | |
offsetY = filterArea.y; | |
buffer = currentFilter._glFilterTexture.frameBuffer; | |
} | |
// TODO need to remove these global elements.. | |
projection.x = sizeX/2; | |
projection.y = -sizeY/2; | |
offset.x = offsetX; | |
offset.y = offsetY; | |
filterArea = filterBlock._filterArea; | |
var x = filterArea.x-offsetX; | |
var y = filterArea.y-offsetY; | |
// update the buffers.. | |
// make sure to flip the y! | |
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); | |
this.vertexArray[0] = x; | |
this.vertexArray[1] = y + filterArea.height; | |
this.vertexArray[2] = x + filterArea.width; | |
this.vertexArray[3] = y + filterArea.height; | |
this.vertexArray[4] = x; | |
this.vertexArray[5] = y; | |
this.vertexArray[6] = x + filterArea.width; | |
this.vertexArray[7] = y; | |
gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); | |
gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); | |
this.uvArray[2] = filterArea.width/this.width; | |
this.uvArray[5] = filterArea.height/this.height; | |
this.uvArray[6] = filterArea.width/this.width; | |
this.uvArray[7] = filterArea.height/this.height; | |
gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); | |
gl.viewport(0, 0, sizeX * this.renderSession.resolution, sizeY * this.renderSession.resolution); | |
// bind the buffer | |
gl.bindFramebuffer(gl.FRAMEBUFFER, buffer ); | |
// set the blend mode! | |
//gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA) | |
// set texture | |
gl.activeTexture(gl.TEXTURE0); | |
gl.bindTexture(gl.TEXTURE_2D, texture.texture); | |
// >>> modify by nextht | |
if (this.renderSession.stencilManager) { | |
this.renderSession.stencilManager.destroy(); | |
} | |
this.renderSession.stencilManager = filterBlock._previous_stencil_mgr; | |
filterBlock._previous_stencil_mgr = null; | |
if (this.renderSession.stencilManager.count > 0) { | |
gl.enable(gl.STENCIL_TEST); | |
} | |
else { | |
gl.disable(gl.STENCIL_TEST); | |
} | |
// <<< modify by nextht | |
// apply! | |
this.applyFilterPass(filter, filterArea, sizeX, sizeY); | |
// now restore the regular shader.. should happen automatically now.. | |
// this.renderSession.shaderManager.setShader(this.defaultShader); | |
// gl.uniform2f(this.defaultShader.projectionVector, sizeX/2, -sizeY/2); | |
// gl.uniform2f(this.defaultShader.offsetVector, -offsetX, -offsetY); | |
// return the texture to the pool | |
this.texturePool.push(texture); | |
filterBlock._glFilterTexture = null; | |
}; | |
/** | |
* Applies the filter to the specified area. | |
* | |
* @method applyFilterPass | |
* @param filter {AbstractFilter} the filter that needs to be applied | |
* @param filterArea {Texture} TODO - might need an update | |
* @param width {Number} the horizontal range of the filter | |
* @param height {Number} the vertical range of the filter | |
*/ | |
PIXI.WebGLFilterManager.prototype.applyFilterPass = function(filter, filterArea, width, height) | |
{ | |
// use program | |
var gl = this.gl; | |
var shader = filter.shaders[gl.id]; | |
if(!shader) | |
{ | |
shader = new PIXI.PixiShader(gl); | |
shader.fragmentSrc = filter.fragmentSrc; | |
shader.uniforms = filter.uniforms; | |
shader.init(); | |
filter.shaders[gl.id] = shader; | |
} | |
// set the shader | |
this.renderSession.shaderManager.setShader(shader); | |
// gl.useProgram(shader.program); | |
gl.uniform2f(shader.projectionVector, width/2, -height/2); | |
gl.uniform2f(shader.offsetVector, 0,0); | |
if(filter.uniforms.dimensions) | |
{ | |
filter.uniforms.dimensions.value[0] = this.width;//width; | |
filter.uniforms.dimensions.value[1] = this.height;//height; | |
filter.uniforms.dimensions.value[2] = this.vertexArray[0]; | |
filter.uniforms.dimensions.value[3] = this.vertexArray[5];//filterArea.height; | |
} | |
shader.syncUniforms(); | |
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); | |
gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 0, 0); | |
gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); | |
gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, 0, 0); | |
gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); | |
gl.vertexAttribPointer(shader.colorAttribute, 2, gl.FLOAT, false, 0, 0); | |
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); | |
// draw the filter... | |
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 ); | |
this.renderSession.drawCount++; | |
}; | |
/** | |
* Initialises the shader buffers. | |
* | |
* @method initShaderBuffers | |
*/ | |
PIXI.WebGLFilterManager.prototype.initShaderBuffers = function() | |
{ | |
var gl = this.gl; | |
// create some buffers | |
this.vertexBuffer = gl.createBuffer(); | |
this.uvBuffer = gl.createBuffer(); | |
this.colorBuffer = gl.createBuffer(); | |
this.indexBuffer = gl.createBuffer(); | |
// bind and upload the vertexs.. | |
// keep a reference to the vertexFloatData.. | |
this.vertexArray = new PIXI.Float32Array([0.0, 0.0, | |
1.0, 0.0, | |
0.0, 1.0, | |
1.0, 1.0]); | |
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); | |
gl.bufferData(gl.ARRAY_BUFFER, this.vertexArray, gl.STATIC_DRAW); | |
// bind and upload the uv buffer | |
this.uvArray = new PIXI.Float32Array([0.0, 0.0, | |
1.0, 0.0, | |
0.0, 1.0, | |
1.0, 1.0]); | |
gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); | |
gl.bufferData(gl.ARRAY_BUFFER, this.uvArray, gl.STATIC_DRAW); | |
this.colorArray = new PIXI.Float32Array([1.0, 0xFFFFFF, | |
1.0, 0xFFFFFF, | |
1.0, 0xFFFFFF, | |
1.0, 0xFFFFFF]); | |
gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); | |
gl.bufferData(gl.ARRAY_BUFFER, this.colorArray, gl.STATIC_DRAW); | |
// bind and upload the index | |
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); | |
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 1, 3, 2]), gl.STATIC_DRAW); | |
}; | |
/** | |
* Destroys the filter and removes it from the filter stack. | |
* | |
* @method destroy | |
*/ | |
PIXI.WebGLFilterManager.prototype.destroy = function() | |
{ | |
var gl = this.gl; | |
this.filterStack = null; | |
this.offsetX = 0; | |
this.offsetY = 0; | |
// destroy textures | |
for (var i = 0; i < this.texturePool.length; i++) { | |
this.texturePool[i].destroy(); | |
} | |
this.texturePool = null; | |
//destroy buffers.. | |
gl.deleteBuffer(this.vertexBuffer); | |
gl.deleteBuffer(this.uvBuffer); | |
gl.deleteBuffer(this.colorBuffer); | |
gl.deleteBuffer(this.indexBuffer); | |
}; | |
/** | |
* @author Mat Groves http://matgroves.com/ @Doormat23 | |
*/ | |
/** | |
* @class FilterTexture | |
* @constructor | |
* @param gl {WebGLContext} the current WebGL drawing context | |
* @param width {Number} the horizontal range of the filter | |
* @param height {Number} the vertical range of the filter | |
* @param scaleMode {Number} See {{#crossLink "PIXI/scaleModes:property"}}PIXI.scaleModes{{/crossLink}} for possible values | |
*/ | |
PIXI.FilterTexture = function(gl, width, height, scaleMode) | |
{ | |
/** | |
* @property gl | |
* @type WebGLContext | |
*/ | |
this.gl = gl; | |
// next time to create a frame buffer and texture | |
/** | |
* @property frameBuffer | |
* @type Any | |
*/ | |
this.frameBuffer = gl.createFramebuffer(); | |
/** | |
* @property texture | |
* @type Any | |
*/ | |
this.texture = gl.createTexture(); | |
/** | |
* @property scaleMode | |
* @type Number | |
*/ | |
scaleMode = scaleMode || PIXI.scaleModes.DEFAULT; | |
gl.bindTexture(gl.TEXTURE_2D, this.texture); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, scaleMode === PIXI.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, scaleMode === PIXI.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); | |
gl.bindFramebuffer(gl.FRAMEBUFFER, this.frameBuffer ); | |
gl.bindFramebuffer(gl.FRAMEBUFFER, this.frameBuffer ); | |
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.texture, 0); | |
// required for masking a mask?? | |
this.renderBuffer = gl.createRenderbuffer(); | |
gl.bindRenderbuffer(gl.RENDERBUFFER, this.renderBuffer); | |
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, this.renderBuffer); | |
this.resize(width, height); | |
}; | |
PIXI.FilterTexture.prototype.constructor = PIXI.FilterTexture; | |
/** | |
* Clears the filter texture. | |
* | |
* @method clear | |
*/ | |
PIXI.FilterTexture.prototype.clear = function() | |
{ | |
var gl = this.gl; | |
gl.clearColor(0,0,0, 0); | |
gl.clear(gl.COLOR_BUFFER_BIT); | |
}; | |
/** | |
* Resizes the texture to the specified width and height | |
* | |
* @method resize | |
* @param width {Number} the new width of the texture | |
* @param height {Number} the new height of the texture | |
*/ | |
PIXI.FilterTexture.prototype.resize = function(width, height) | |
{ | |
if(this.width === width && this.height === height) return; | |
this.width = width; | |
this.height = height; | |
var gl = this.gl; | |
gl.bindTexture(gl.TEXTURE_2D, this.texture); | |
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width , height , 0, gl.RGBA, gl.UNSIGNED_BYTE, null); | |
// update the stencil buffer width and height | |
gl.bindRenderbuffer(gl.RENDERBUFFER, this.renderBuffer); | |
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, width , height ); | |
}; | |
/** | |
* Destroys the filter texture. | |
* | |
* @method destroy | |
*/ | |
PIXI.FilterTexture.prototype.destroy = function() | |
{ | |
var gl = this.gl; | |
gl.deleteFramebuffer( this.frameBuffer ); | |
gl.deleteTexture( this.texture ); | |
this.frameBuffer = null; | |
this.texture = null; | |
}; | |
/** | |
* @author Mat Groves http://matgroves.com/ @Doormat23 | |
*/ | |
/** | |
* Creates a Canvas element of the given size. | |
* | |
* @class CanvasBuffer | |
* @constructor | |
* @param width {Number} the width for the newly created canvas | |
* @param height {Number} the height for the newly created canvas | |
*/ | |
PIXI.CanvasBuffer = function(width, height) | |
{ | |
/** | |
* The width of the Canvas in pixels. | |
* | |
* @property width | |
* @type Number | |
*/ | |
this.width = width; | |
/** | |
* The height of the Canvas in pixels. | |
* | |
* @property height | |
* @type Number | |
*/ | |
this.height = height; | |
/** | |
* The Canvas object that belongs to this CanvasBuffer. | |
* | |
* @property canvas | |
* @type HTMLCanvasElement | |
*/ | |
this.canvas = PIXI.CanvasPool.create(this, this.width, this.height); | |
/** | |
* A CanvasRenderingContext2D object representing a two-dimensional rendering context. | |
* | |
* @property context | |
* @type CanvasRenderingContext2D | |
*/ | |
this.context = this.canvas.getContext("2d"); | |
this.canvas.width = width; | |
this.canvas.height = height; | |
}; | |
PIXI.CanvasBuffer.prototype.constructor = PIXI.CanvasBuffer; | |
/** | |
* Clears the canvas that was created by the CanvasBuffer class. | |
* | |
* @method clear | |
* @private | |
*/ | |
PIXI.CanvasBuffer.prototype.clear = function() | |
{ | |
this.context.setTransform(1, 0, 0, 1, 0, 0); | |
this.context.clearRect(0,0, this.width, this.height); | |
}; | |
/** | |
* Resizes the canvas to the specified width and height. | |
* | |
* @method resize | |
* @param width {Number} the new width of the canvas | |
* @param height {Number} the new height of the canvas | |
*/ | |
PIXI.CanvasBuffer.prototype.resize = function(width, height) | |
{ | |
this.width = this.canvas.width = width; | |
this.height = this.canvas.height = height; | |
}; | |
/** | |
* Frees the canvas up for use again. | |
* | |
* @method destroy | |
*/ | |
PIXI.CanvasBuffer.prototype.destroy = function() | |
{ | |
PIXI.CanvasPool.remove(this); | |
}; | |
/** | |
* @author Mat Groves http://matgroves.com/ @Doormat23 | |
*/ | |
/** | |
* A set of functions used to handle masking. | |
* | |
* @class CanvasMaskManager | |
* @constructor | |
*/ | |
PIXI.CanvasMaskManager = function() | |
{ | |
}; | |
PIXI.CanvasMaskManager.prototype.constructor = PIXI.CanvasMaskManager; | |
/** | |
* This method adds it to the current stack of masks. | |
* | |
* @method pushMask | |
* @param maskData {Object} the maskData that will be pushed | |
* @param renderSession {Object} The renderSession whose context will be used for this mask manager. | |
*/ | |
PIXI.CanvasMaskManager.prototype.pushMask = function(maskData, renderSession) { | |
var context = renderSession.context; | |
context.save(); | |
var cacheAlpha = maskData.alpha; | |
var transform = maskData.worldTransform; | |
var resolution = renderSession.resolution; | |
context.setTransform(transform.a * resolution, | |
transform.b * resolution, | |
transform.c * resolution, | |
transform.d * resolution, | |
transform.tx * resolution, | |
transform.ty * resolution); | |
PIXI.CanvasGraphics.renderGraphicsMask(maskData, context); | |
context.clip(); | |
maskData.worldAlpha = cacheAlpha; | |
}; | |
/** | |
* Restores the current drawing context to the state it was before the mask was applied. | |
* | |
* @method popMask | |
* @param renderSession {Object} The renderSession whose context will be used for this mask manager. | |
*/ | |
PIXI.CanvasMaskManager.prototype.popMask = function(renderSession) | |
{ | |
renderSession.context.restore(); | |
}; | |
/** | |
* @author Mat Groves http://matgroves.com/ @Doormat23 | |
*/ | |
/** | |
* Utility methods for Sprite/Texture tinting. | |
* | |
* @class CanvasTinter | |
* @static | |
*/ | |
PIXI.CanvasTinter = function() {}; | |
/** | |
* Basically this method just needs a sprite and a color and tints the sprite with the given color. | |
* | |
* @method getTintedTexture | |
* @static | |
* @param sprite {Sprite} the sprite to tint | |
* @param color {Number} the color to use to tint the sprite with | |
* @return {HTMLCanvasElement} The tinted canvas | |
*/ | |
PIXI.CanvasTinter.getTintedTexture = function(sprite, color) | |
{ | |
var canvas = sprite.tintedTexture || PIXI.CanvasPool.create(this); | |
PIXI.CanvasTinter.tintMethod(sprite.texture, color, canvas); | |
return canvas; | |
}; | |
/** | |
* Tint a texture using the "multiply" operation. | |
* | |
* @method tintWithMultiply | |
* @static | |
* @param texture {Texture} the texture to tint | |
* @param color {Number} the color to use to tint the sprite with | |
* @param canvas {HTMLCanvasElement} the current canvas | |
*/ | |
PIXI.CanvasTinter.tintWithMultiply = function(texture, color, canvas) | |
{ | |
var context = canvas.getContext("2d"); | |
var crop = texture.crop; | |
if (canvas.width !== crop.width || canvas.height !== crop.height) | |
{ | |
canvas.width = crop.width; | |
canvas.height = crop.height; | |
} | |
context.clearRect(0, 0, crop.width, crop.height); | |
context.fillStyle = "#" + ("00000" + (color | 0).toString(16)).substr(-6); | |
context.fillRect(0, 0, crop.width, crop.height); | |
context.globalCompositeOperation = "multiply"; | |
context.drawImage(texture.baseTexture.source, crop.x, crop.y, crop.width, crop.height, 0, 0, crop.width, crop.height); | |
context.globalCompositeOperation = "destination-atop"; | |
context.drawImage(texture.baseTexture.source, crop.x, crop.y, crop.width, crop.height, 0, 0, crop.width, crop.height); | |
}; | |
/** | |
* Tint a texture pixel per pixel. | |
* | |
* @method tintPerPixel | |
* @static | |
* @param texture {Texture} the texture to tint | |
* @param color {Number} the color to use to tint the sprite with | |
* @param canvas {HTMLCanvasElement} the current canvas | |
*/ | |
PIXI.CanvasTinter.tintWithPerPixel = function(texture, color, canvas) | |
{ | |
var context = canvas.getContext("2d"); | |
var crop = texture.crop; | |
canvas.width = crop.width; | |
canvas.height = crop.height; | |
context.globalCompositeOperation = "copy"; | |
context.drawImage(texture.baseTexture.source, crop.x, crop.y, crop.width, crop.height, 0, 0, crop.width, crop.height); | |
var rgbValues = PIXI.hex2rgb(color); | |
var r = rgbValues[0], g = rgbValues[1], b = rgbValues[2]; | |
var pixelData = context.getImageData(0, 0, crop.width, crop.height); | |
var pixels = pixelData.data; | |
for (var i = 0; i < pixels.length; i += 4) | |
{ | |
pixels[i + 0] *= r; | |
pixels[i + 1] *= g; | |
pixels[i + 2] *= b; | |
if (!PIXI.CanvasTinter.canHandleAlpha) | |
{ | |
var alpha = pixels[i + 3]; | |
pixels[i + 0] /= 255 / alpha; | |
pixels[i + 1] /= 255 / alpha; | |
pixels[i + 2] /= 255 / alpha; | |
} | |
} | |
context.putImageData(pixelData, 0, 0); | |
}; | |
/** | |
* Checks if the browser correctly supports putImageData alpha channels. | |
* | |
* @method checkInverseAlpha | |
* @static | |
*/ | |
PIXI.CanvasTinter.checkInverseAlpha = function() | |
{ | |
var canvas = new PIXI.CanvasBuffer(2, 1); | |
canvas.context.fillStyle = "rgba(10, 20, 30, 0.5)"; | |
// Draw a single pixel | |
canvas.context.fillRect(0, 0, 1, 1); | |
// Get the color values | |
var s1 = canvas.context.getImageData(0, 0, 1, 1); | |
if (s1 === null) | |
{ | |
return false; | |
} | |
// Plot them to x2 | |
canvas.context.putImageData(s1, 1, 0); | |
// Get those values | |
var s2 = canvas.context.getImageData(1, 0, 1, 1); | |
// Compare and return | |
return (s2.data[0] === s1.data[0] && s2.data[1] === s1.data[1] && s2.data[2] === s1.data[2] && s2.data[3] === s1.data[3]); | |
}; | |
/** | |
* If the browser isn't capable of handling tinting with alpha this will be false. | |
* This property is only applicable if using tintWithPerPixel. | |
* | |
* @property canHandleAlpha | |
* @type Boolean | |
* @static | |
*/ | |
PIXI.CanvasTinter.canHandleAlpha = PIXI.CanvasTinter.checkInverseAlpha(); | |
/** | |
* Whether or not the Canvas BlendModes are supported, consequently the ability to tint using the multiply method. | |
* | |
* @property canUseMultiply | |
* @type Boolean | |
* @static | |
*/ | |
PIXI.CanvasTinter.canUseMultiply = PIXI.canUseNewCanvasBlendModes(); | |
/** | |
* The tinting method that will be used. | |
* | |
* @method tintMethod | |
* @static | |
*/ | |
PIXI.CanvasTinter.tintMethod = PIXI.CanvasTinter.canUseMultiply ? PIXI.CanvasTinter.tintWithMultiply : PIXI.CanvasTinter.tintWithPerPixel; | |
/** | |
* @author Mat Groves http://matgroves.com/ @Doormat23 | |
*/ | |
/** | |
* The CanvasRenderer draws the Stage and all its content onto a 2d canvas. This renderer should be used for browsers that do not support webGL. | |
* Don't forget to add the CanvasRenderer.view to your DOM or you will not see anything :) | |
* | |
* @class CanvasRenderer | |
* @constructor | |
* @param game {Phaser.Game} A reference to the Phaser Game instance | |
*/ | |
PIXI.CanvasRenderer = function (game) { | |
/** | |
* @property {Phaser.Game} game - A reference to the Phaser Game instance. | |
*/ | |
this.game = game; | |
if (!PIXI.defaultRenderer) | |
{ | |
PIXI.defaultRenderer = this; | |
} | |
/** | |
* The renderer type. | |
* | |
* @property type | |
* @type Number | |
*/ | |
this.type = PIXI.CANVAS_RENDERER; | |
/** | |
* The resolution of the canvas. | |
* | |
* @property resolution | |
* @type Number | |
*/ | |
this.resolution = game.resolution; | |
/** | |
* This sets if the CanvasRenderer will clear the canvas or not before the new render pass. | |
* If the Stage is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. | |
* If the Stage is transparent Pixi will use clearRect to clear the canvas every frame. | |
* Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. | |
* | |
* @property clearBeforeRender | |
* @type Boolean | |
* @default | |
*/ | |
this.clearBeforeRender = game.clearBeforeRender; | |
/** | |
* Whether the render view is transparent | |
* | |
* @property transparent | |
* @type Boolean | |
*/ | |
this.transparent = game.transparent; | |
/** | |
* Whether the render view should be resized automatically | |
* | |
* @property autoResize | |
* @type Boolean | |
*/ | |
this.autoResize = false; | |
/** | |
* The width of the canvas view | |
* | |
* @property width | |
* @type Number | |
* @default 800 | |
*/ | |
this.width = game.width * this.resolution; | |
/** | |
* The height of the canvas view | |
* | |
* @property height | |
* @type Number | |
* @default 600 | |
*/ | |
this.height = game.height * this.resolution; | |
/** | |
* The canvas element that everything is drawn to. | |
* | |
* @property view | |
* @type HTMLCanvasElement | |
*/ | |
this.view = game.canvas; | |
/** | |
* The canvas 2d context that everything is drawn with | |
* @property context | |
* @type CanvasRenderingContext2D | |
*/ | |
this.context = this.view.getContext("2d", { alpha: this.transparent } ); | |
/** | |
* Boolean flag controlling canvas refresh. | |
* | |
* @property refresh | |
* @type Boolean | |
*/ | |
this.refresh = true; | |
/** | |
* Internal var. | |
* | |
* @property count | |
* @type Number | |
*/ | |
this.count = 0; | |
/** | |
* Instance of a PIXI.CanvasMaskManager, handles masking when using the canvas renderer | |
* @property CanvasMaskManager | |
* @type CanvasMaskManager | |
*/ | |
this.maskManager = new PIXI.CanvasMaskManager(); | |
/** | |
* The render session is just a bunch of parameter used for rendering | |
* @property renderSession | |
* @type Object | |
*/ | |
this.renderSession = { | |
context: this.context, | |
maskManager: this.maskManager, | |
scaleMode: null, | |
smoothProperty: Phaser.Canvas.getSmoothingPrefix(this.context), | |
/** | |
* If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. | |
* Handy for crisp pixel art and speed on legacy devices. | |
*/ | |
roundPixels: false | |
}; | |
this.mapBlendModes(); | |
this.resize(this.width, this.height); | |
}; | |
// constructor | |
PIXI.CanvasRenderer.prototype.constructor = PIXI.CanvasRenderer; | |
/** | |
* Renders the DisplayObjectContainer, usually the Phaser.Stage, to this canvas view. | |
* | |
* @method render | |
* @param root {Phaser.Stage|PIXI.DisplayObjectContainer} The root element to be rendered. | |
*/ | |
PIXI.CanvasRenderer.prototype.render = function (root) { | |
this.context.setTransform(1, 0, 0, 1, 0, 0); | |
this.context.globalAlpha = 1; | |
this.renderSession.currentBlendMode = 0; | |
this.renderSession.shakeX = this.game.camera._shake.x; | |
this.renderSession.shakeY = this.game.camera._shake.y; | |
this.context.globalCompositeOperation = 'source-over'; | |
if (navigator.isCocoonJS && this.view.screencanvas) | |
{ | |
this.context.fillStyle = "black"; | |
this.context.clear(); | |
} | |
if (this.clearBeforeRender) | |
{ | |
if (this.transparent) | |
{ | |
this.context.clearRect(0, 0, this.width, this.height); | |
} | |
else if (root._bgColor) | |
{ | |
this.context.fillStyle = root._bgColor.rgba; | |
this.context.fillRect(0, 0, this.width , this.height); | |
} | |
} | |
this.renderDisplayObject(root); | |
}; | |
/** | |
* Removes everything from the renderer and optionally removes the Canvas DOM element. | |
* | |
* @method destroy | |
* @param [removeView=true] {boolean} Removes the Canvas element from the DOM. | |
*/ | |
PIXI.CanvasRenderer.prototype.destroy = function (removeView) { | |
if (removeView === undefined) { removeView = true; } | |
if (removeView && this.view.parent) | |
{ | |
this.view.parent.removeChild(this.view); | |
} | |
this.view = null; | |
this.context = null; | |
this.maskManager = null; | |
this.renderSession = null; | |
}; | |
/** | |
* Resizes the canvas view to the specified width and height | |
* | |
* @method resize | |
* @param width {Number} the new width of the canvas view | |
* @param height {Number} the new height of the canvas view | |
*/ | |
PIXI.CanvasRenderer.prototype.resize = function (width, height) { | |
this.width = width * this.resolution; | |
this.height = height * this.resolution; | |
this.view.width = this.width; | |
this.view.height = this.height; | |
if (this.autoResize) | |
{ | |
this.view.style.width = this.width / this.resolution + "px"; | |
this.view.style.height = this.height / this.resolution + "px"; | |
} | |
if (this.renderSession.smoothProperty) | |
{ | |
this.context[this.renderSession.smoothProperty] = (this.renderSession.scaleMode === PIXI.scaleModes.LINEAR); | |
} | |
}; | |
/** | |
* Renders a display object | |
* | |
* @method renderDisplayObject | |
* @param displayObject {DisplayObject} The displayObject to render | |
* @param context {CanvasRenderingContext2D} the context 2d method of the canvas | |
* @param [matrix] {Matrix} Optional matrix to apply to the display object before rendering. | |
* @private | |
*/ | |
PIXI.CanvasRenderer.prototype.renderDisplayObject = function (displayObject, context, matrix) { | |
this.renderSession.context = context || this.context; | |
this.renderSession.resolution = this.resolution; | |
displayObject._renderCanvas(this.renderSession, matrix); | |
}; | |
/** | |
* Maps Pixi blend modes to canvas blend modes. | |
* | |
* @method mapBlendModes | |
* @private | |
*/ | |
PIXI.CanvasRenderer.prototype.mapBlendModes = function () { | |
if (!PIXI.blendModesCanvas) | |
{ | |
var b = []; | |
var modes = PIXI.blendModes; | |
var useNew = PIXI.canUseNewCanvasBlendModes(); | |
b[modes.NORMAL] = 'source-over'; | |
b[modes.ADD] = 'lighter'; | |
b[modes.MULTIPLY] = (useNew) ? 'multiply' : 'source-over'; | |
b[modes.SCREEN] = (useNew) ? 'screen' : 'source-over'; | |
b[modes.OVERLAY] = (useNew) ? 'overlay' : 'source-over'; | |
b[modes.DARKEN] = (useNew) ? 'darken' : 'source-over'; | |
b[modes.LIGHTEN] = (useNew) ? 'lighten' : 'source-over'; | |
b[modes.COLOR_DODGE] = (useNew) ? 'color-dodge' : 'source-over'; | |
b[modes.COLOR_BURN] = (useNew) ? 'color-burn' : 'source-over'; | |
b[modes.HARD_LIGHT] = (useNew) ? 'hard-light' : 'source-over'; | |
b[modes.SOFT_LIGHT] = (useNew) ? 'soft-light' : 'source-over'; | |
b[modes.DIFFERENCE] = (useNew) ? 'difference' : 'source-over'; | |
b[modes.EXCLUSION] = (useNew) ? 'exclusion' : 'source-over'; | |
b[modes.HUE] = (useNew) ? 'hue' : 'source-over'; | |
b[modes.SATURATION] = (useNew) ? 'saturation' : 'source-over'; | |
b[modes.COLOR] = (useNew) ? 'color' : 'source-over'; | |
b[modes.LUMINOSITY] = (useNew) ? 'luminosity' : 'source-over'; | |
PIXI.blendModesCanvas = b; | |
} | |
}; | |
/** | |
* @author Mat Groves http://matgroves.com/ @Doormat23 | |
*/ | |
/** | |
* A texture stores the information that represents an image. All textures have a base texture. | |
* | |
* @class BaseTexture | |
* @uses EventTarget | |
* @constructor | |
* @param source {String|Canvas} the source object (image or canvas) | |
* @param scaleMode {Number} See {{#crossLink "PIXI/scaleModes:property"}}PIXI.scaleModes{{/crossLink}} for possible values | |
*/ | |
PIXI.BaseTexture = function(source, scaleMode) | |
{ | |
/** | |
* The Resolution of the texture. | |
* | |
* @property resolution | |
* @type Number | |
*/ | |
this.resolution = 1; | |
/** | |
* [read-only] The width of the base texture set when the image has loaded | |
* | |
* @property width | |
* @type Number | |
* @readOnly | |
*/ | |
this.width = 100; | |
/** | |
* [read-only] The height of the base texture set when the image has loaded | |
* | |
* @property height | |
* @type Number | |
* @readOnly | |
*/ | |
this.height = 100; | |
/** | |
* The scale mode to apply when scaling this texture | |
* | |
* @property scaleMode | |
* @type {Number} | |
* @default PIXI.scaleModes.LINEAR | |
*/ | |
this.scaleMode = scaleMode || PIXI.scaleModes.DEFAULT; | |
/** | |
* [read-only] Set to true once the base texture has loaded | |
* | |
* @property hasLoaded | |
* @type Boolean | |
* @readOnly | |
*/ | |
this.hasLoaded = false; | |
/** | |
* The image source that is used to create the texture. | |
* | |
* @property source | |
* @type Image | |
*/ | |
this.source = source; | |
/** | |
* Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) | |
* | |
* @property premultipliedAlpha | |
* @type Boolean | |
* @default true | |
*/ | |
this.premultipliedAlpha = true; | |
// used for webGL | |
/** | |
* @property _glTextures | |
* @type Array | |
* @private | |
*/ | |
this._glTextures = []; | |
/** | |
* Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used | |
* Also the texture must be a power of two size to work | |
* | |
* @property mipmap | |
* @type {Boolean} | |
*/ | |
this.mipmap = false; | |
/** | |
* @property _dirty | |
* @type Array | |
* @private | |
*/ | |
this._dirty = [true, true, true, true]; | |
if (!source) | |
{ | |
return; | |
} | |
if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) | |
{ | |
this.hasLoaded = true; | |
this.width = this.source.naturalWidth || this.source.width; | |
this.height = this.source.naturalHeight || this.source.height; | |
this.dirty(); | |
} | |
/** | |
* A BaseTexture can be set to skip the rendering phase in the WebGL Sprite Batch. | |
* | |
* You may want to do this if you have a parent Sprite with no visible texture (i.e. uses the internal `__default` texture) | |
* that has children that you do want to render, without causing a batch flush in the process. | |
* | |
* @property skipRender | |
* @type Boolean | |
*/ | |
this.skipRender = false; | |
/** | |
* @property _powerOf2 | |
* @type Boolean | |
* @private | |
*/ | |
this._powerOf2 = false; | |
}; | |
PIXI.BaseTexture.prototype.constructor = PIXI.BaseTexture; | |
/** | |
* Forces this BaseTexture to be set as loaded, with the given width and height. | |
* Then calls BaseTexture.dirty. | |
* Important for when you don't want to modify the source object by forcing in `complete` or dimension properties it may not have. | |
* | |
* @method forceLoaded | |
* @param {number} width - The new width to force the BaseTexture to be. | |
* @param {number} height - The new height to force the BaseTexture to be. | |
*/ | |
PIXI.BaseTexture.prototype.forceLoaded = function(width, height) | |
{ | |
this.hasLoaded = true; | |
this.width = width; | |
this.height = height; | |
this.dirty(); | |
}; | |
/** | |
* Destroys this base texture | |
* | |
* @method destroy | |
*/ | |
PIXI.BaseTexture.prototype.destroy = function() | |
{ | |
if (this.source) | |
{ | |
PIXI.CanvasPool.removeByCanvas(this.source); | |
} | |
this.source = null; | |
this.unloadFromGPU(); | |
}; | |
/** | |
* Changes the source image of the texture | |
* | |
* @method updateSourceImage | |
* @param newSrc {String} the path of the image | |
* @deprecated This method is deprecated. Please use Phaser.Sprite.loadTexture instead. | |
*/ | |
PIXI.BaseTexture.prototype.updateSourceImage = function(newSrc) | |
{ | |
console.warn("PIXI.BaseTexture.updateSourceImage is deprecated. Use Phaser.Sprite.loadTexture instead."); | |
}; | |
/** | |
* Sets all glTextures to be dirty. | |
* | |
* @method dirty | |
*/ | |
PIXI.BaseTexture.prototype.dirty = function() | |
{ | |
for (var i = 0; i < this._glTextures.length; i++) | |
{ | |
this._dirty[i] = true; | |
} | |
}; | |
/** | |
* Removes the base texture from the GPU, useful for managing resources on the GPU. | |
* Atexture is still 100% usable and will simply be reuploaded if there is a sprite on screen that is using it. | |
* | |
* @method unloadFromGPU | |
*/ | |
PIXI.BaseTexture.prototype.unloadFromGPU = function() | |
{ | |
this.dirty(); | |
// delete the webGL textures if any. | |
for (var i = this._glTextures.length - 1; i >= 0; i--) | |
{ | |
var glTexture = this._glTextures[i]; | |
var gl = PIXI.glContexts[i]; | |
if(gl && glTexture) | |
{ | |
gl.deleteTexture(glTexture); | |
} | |
} | |
this._glTextures.length = 0; | |
this.dirty(); | |
}; | |
/** | |
* Helper function that creates a base texture from the given canvas element. | |
* | |
* @static | |
* @method fromCanvas | |
* @param canvas {Canvas} The canvas element source of the texture | |
* @param scaleMode {Number} See {{#crossLink "PIXI/scaleModes:property"}}PIXI.scaleModes{{/crossLink}} for possible values | |
* @return {BaseTexture} | |
*/ | |
PIXI.BaseTexture.fromCanvas = function(canvas, scaleMode) | |
{ | |
if (canvas.width === 0) | |
{ | |
canvas.width = 1; | |
} | |
if (canvas.height === 0) | |
{ | |
canvas.height = 1; | |
} | |
return new PIXI.BaseTexture(canvas, scaleMode); | |
}; | |
/** | |
* @author Mat Groves http://matgroves.com/ @Doormat23 | |
*/ | |
/** | |
* TextureSilentFail is a boolean that defaults to `false`. | |
* If `true` then `PIXI.Texture.setFrame` will no longer throw an error if the texture dimensions are incorrect. | |
* Instead `Texture.valid` will be set to `false` (#1556) | |
* | |
* @type {boolean} | |
*/ | |
PIXI.TextureSilentFail = false; | |
/** | |
* A texture stores the information that represents an image or part of an image. It cannot be added | |
* to the display list directly. Instead use it as the texture for a PIXI.Sprite. If no frame is provided then the whole image is used. | |
* | |
* @class Texture | |
* @uses EventTarget | |
* @constructor | |
* @param baseTexture {BaseTexture} The base texture source to create the texture from | |
* @param frame {Rectangle} The rectangle frame of the texture to show | |
* @param [crop] {Rectangle} The area of original texture | |
* @param [trim] {Rectangle} Trimmed texture rectangle | |
*/ | |
PIXI.Texture = function(baseTexture, frame, crop, trim) | |
{ | |
/** | |
* Does this Texture have any frame data assigned to it? | |
* | |
* @property noFrame | |
* @type Boolean | |
*/ | |
this.noFrame = false; | |
if (!frame) | |
{ | |
this.noFrame = true; | |
frame = new PIXI.Rectangle(0,0,1,1); | |
} | |
if (baseTexture instanceof PIXI.Texture) | |
{ | |
baseTexture = baseTexture.baseTexture; | |
} | |
/** | |
* The base texture that this texture uses. | |
* | |
* @property baseTexture | |
* @type BaseTexture | |
*/ | |
this.baseTexture = baseTexture; | |
/** | |
* The frame specifies the region of the base texture that this texture uses | |
* | |
* @property frame | |
* @type Rectangle | |
*/ | |
this.frame = frame; | |
/** | |
* The texture trim data. | |
* | |
* @property trim | |
* @type Rectangle | |
*/ | |
this.trim = trim; | |
/** | |
* This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. | |
* | |
* @property valid | |
* @type Boolean | |
*/ | |
this.valid = false; | |
/** | |
* Is this a tiling texture? As used by the likes of a TilingSprite. | |
* | |
* @property isTiling | |
* @type Boolean | |
*/ | |
this.isTiling = false; | |
/** | |
* This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) | |
* | |
* @property requiresUpdate | |
* @type Boolean | |
*/ | |
this.requiresUpdate = false; | |
/** | |
* This will let a renderer know that a tinted parent has updated its texture. | |
* | |
* @property requiresReTint | |
* @type Boolean | |
*/ | |
this.requiresReTint = false; | |
/** | |
* The WebGL UV data cache. | |
* | |
* @property _uvs | |
* @type Object | |
* @private | |
*/ | |
this._uvs = null; | |
/** | |
* The width of the Texture in pixels. | |
* | |
* @property width | |
* @type Number | |
*/ | |
this.width = 0; | |
/** | |
* The height of the Texture in pixels. | |
* | |
* @property height | |
* @type Number | |
*/ | |
this.height = 0; | |
/** | |
* This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, | |
* irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) | |
* | |
* @property crop | |
* @type Rectangle | |
*/ | |
this.crop = crop || new PIXI.Rectangle(0, 0, 1, 1); | |
if (baseTexture.hasLoaded) | |
{ | |
if (this.noFrame) frame = new PIXI.Rectangle(0, 0, baseTexture.width, baseTexture.height); | |
this.setFrame(frame); | |
} | |
}; | |
PIXI.Texture.prototype.constructor = PIXI.Texture; | |
/** | |
* Called when the base texture is loaded | |
* | |
* @method onBaseTextureLoaded | |
* @private | |
*/ | |
PIXI.Texture.prototype.onBaseTextureLoaded = function() | |
{ | |
var baseTexture = this.baseTexture; | |
if (this.noFrame) | |
{ | |
this.frame = new PIXI.Rectangle(0, 0, baseTexture.width, baseTexture.height); | |
} | |
this.setFrame(this.frame); | |
}; | |
/** | |
* Destroys this texture | |
* | |
* @method destroy | |
* @param destroyBase {Boolean} Whether to destroy the base texture as well | |
*/ | |
PIXI.Texture.prototype.destroy = function(destroyBase) | |
{ | |
if (destroyBase) this.baseTexture.destroy(); | |
this.valid = false; | |
}; | |
/** | |
* Specifies the region of the baseTexture that this texture will use. | |
* | |
* @method setFrame | |
* @param frame {Rectangle} The frame of the texture to set it to | |
*/ | |
PIXI.Texture.prototype.setFrame = function(frame) | |
{ | |
this.noFrame = false; | |
this.frame = frame; | |
this.width = frame.width; | |
this.height = frame.height; | |
this.crop.x = frame.x; | |
this.crop.y = frame.y; | |
this.crop.width = frame.width; | |
this.crop.height = frame.height; | |
if (!this.trim && (frame.x + frame.width > this.baseTexture.width || frame.y + frame.height > this.baseTexture.height)) | |
{ | |
if (!PIXI.TextureSilentFail) | |
{ | |
throw new Error('Texture Error: frame does not fit inside the base Texture dimensions ' + this); | |
} | |
this.valid = false; | |
return; | |
} | |
this.valid = frame && frame.width && frame.height && this.baseTexture.source && this.baseTexture.hasLoaded; | |
if (this.trim) | |
{ | |
this.width = this.trim.width; | |
this.height = this.trim.height; | |
this.frame.width = this.trim.width; | |
this.frame.height = this.trim.height; | |
} | |
if (this.valid) this._updateUvs(); | |
}; | |
/** | |
* Updates the internal WebGL UV cache. | |
* | |
* @method _updateUvs | |
* @private | |
*/ | |
PIXI.Texture.prototype._updateUvs = function() | |
{ | |
if(!this._uvs)this._uvs = new PIXI.TextureUvs(); | |
var frame = this.crop; | |
var tw = this.baseTexture.width; | |
var th = this.baseTexture.height; | |
this._uvs.x0 = frame.x / tw; | |
this._uvs.y0 = frame.y / th; | |
this._uvs.x1 = (frame.x + frame.width) / tw; | |
this._uvs.y1 = frame.y / th; | |
this._uvs.x2 = (frame.x + frame.width) / tw; | |
this._uvs.y2 = (frame.y + frame.height) / th; | |
this._uvs.x3 = frame.x / tw; | |
this._uvs.y3 = (frame.y + frame.height) / th; | |
}; | |
/** | |
* Helper function that creates a new a Texture based on the given canvas element. | |
* | |
* @static | |
* @method fromCanvas | |
* @param canvas {Canvas} The canvas element source of the texture | |
* @param scaleMode {Number} See {{#crossLink "PIXI/scaleModes:property"}}PIXI.scaleModes{{/crossLink}} for possible values | |
* @return {Texture} | |
*/ | |
PIXI.Texture.fromCanvas = function(canvas, scaleMode) | |
{ | |
var baseTexture = PIXI.BaseTexture.fromCanvas(canvas, scaleMode); | |
return new PIXI.Texture(baseTexture); | |
}; | |
PIXI.TextureUvs = function() | |
{ | |
this.x0 = 0; | |
this.y0 = 0; | |
this.x1 = 0; | |
this.y1 = 0; | |
this.x2 = 0; | |
this.y2 = 0; | |
this.x3 = 0; | |
this.y3 = 0; | |
}; | |
/** | |
* @author Mat Groves http://matgroves.com/ @Doormat23 | |
*/ | |
/** | |
* A RenderTexture is a special texture that allows any Pixi display object to be rendered to it. | |
* | |
* __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded otherwise black rectangles will be drawn instead. | |
* | |
* A RenderTexture takes a snapshot of any Display Object given to its render method. The position and rotation of the given Display Objects is ignored. For example: | |
* | |
* var renderTexture = new PIXI.RenderTexture(800, 600); | |
* var sprite = PIXI.Sprite.fromImage("spinObj_01.png"); | |
* sprite.position.x = 800/2; | |
* sprite.position.y = 600/2; | |
* sprite.anchor.x = 0.5; | |
* sprite.anchor.y = 0.5; | |
* renderTexture.render(sprite); | |
* | |
* The Sprite in this case will be rendered to a position of 0,0. To render this sprite at its actual position a DisplayObjectContainer should be used: | |
* | |
* var doc = new PIXI.DisplayObjectContainer(); | |
* doc.addChild(sprite); | |
* renderTexture.render(doc); // Renders to center of renderTexture | |
* | |
* @class RenderTexture | |
* @extends Texture | |
* @constructor | |
* @param width {Number} The width of the render texture | |
* @param height {Number} The height of the render texture | |
* @param renderer {CanvasRenderer|WebGLRenderer} The renderer used for this RenderTexture | |
* @param scaleMode {Number} See {{#crossLink "PIXI/scaleModes:property"}}PIXI.scaleModes{{/crossLink}} for possible values | |
* @param resolution {Number} The resolution of the texture being generated | |
*/ | |
PIXI.RenderTexture = function(width, height, renderer, scaleMode, resolution) | |
{ | |
/** | |
* The with of the render texture | |
* | |
* @property width | |
* @type Number | |
*/ | |
this.width = width || 100; | |
/** | |
* The height of the render texture | |
* | |
* @property height | |
* @type Number | |
*/ | |
this.height = height || 100; | |
/** | |
* The Resolution of the texture. | |
* | |
* @property resolution | |
* @type Number | |
*/ | |
this.resolution = resolution || 1; | |
/** | |
* The framing rectangle of the render texture | |
* | |
* @property frame | |
* @type Rectangle | |
*/ | |
this.frame = new PIXI.Rectangle(0, 0, this.width * this.resolution, this.height * this.resolution); | |
/** | |
* This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, | |
* irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) | |
* | |
* @property crop | |
* @type Rectangle | |
*/ | |
this.crop = new PIXI.Rectangle(0, 0, this.width * this.resolution, this.height * this.resolution); | |
/** | |
* The base texture object that this texture uses | |
* | |
* @property baseTexture | |
* @type BaseTexture | |
*/ | |
this.baseTexture = new PIXI.BaseTexture(); | |
this.baseTexture.width = this.width * this.resolution; | |
this.baseTexture.height = this.height * this.resolution; | |
this.baseTexture._glTextures = []; | |
this.baseTexture.resolution = this.resolution; | |
this.baseTexture.scaleMode = scaleMode || PIXI.scaleModes.DEFAULT; | |
this.baseTexture.hasLoaded = true; | |
PIXI.Texture.call(this, | |
this.baseTexture, | |
new PIXI.Rectangle(0, 0, this.width * this.resolution, this.height * this.resolution) | |
); | |
/** | |
* The renderer this RenderTexture uses. A RenderTexture can only belong to one renderer at the moment if its webGL. | |
* | |
* @property renderer | |
* @type CanvasRenderer|WebGLRenderer | |
*/ | |
this.renderer = renderer || PIXI.defaultRenderer; | |
if (this.renderer.type === PIXI.WEBGL_RENDERER) | |
{ | |
var gl = this.renderer.gl; | |
this.baseTexture._dirty[gl.id] = false; | |
this.textureBuffer = new PIXI.FilterTexture(gl, this.width, this.height, this.baseTexture.scaleMode); | |
this.baseTexture._glTextures[gl.id] = this.textureBuffer.texture; | |
this.render = this.renderWebGL; | |
this.projection = new PIXI.Point(this.width * 0.5, -this.height * 0.5); | |
} | |
else | |
{ | |
this.render = this.renderCanvas; | |
this.textureBuffer = new PIXI.CanvasBuffer(this.width * this.resolution, this.height * this.resolution); | |
this.baseTexture.source = this.textureBuffer.canvas; | |
} | |
/** | |
* @property valid | |
* @type Boolean | |
*/ | |
this.valid = true; | |
this.tempMatrix = new Phaser.Matrix(); | |
this._updateUvs(); | |
}; | |
PIXI.RenderTexture.prototype = Object.create(PIXI.Texture.prototype); | |
PIXI.RenderTexture.prototype.constructor = PIXI.RenderTexture; | |
/** | |
* Resizes the RenderTexture. | |
* | |
* @method resize | |
* @param width {Number} The width to resize to. | |
* @param height {Number} The height to resize to. | |
* @param updateBase {Boolean} Should the baseTexture.width and height values be resized as well? | |
*/ | |
PIXI.RenderTexture.prototype.resize = function(width, height, updateBase) | |
{ | |
if (width === this.width && height === this.height)return; | |
this.valid = (width > 0 && height > 0); | |
this.width = width; | |
this.height = height; | |
this.frame.width = this.crop.width = width * this.resolution; | |
this.frame.height = this.crop.height = height * this.resolution; | |
if (updateBase) | |
{ | |
this.baseTexture.width = this.width * this.resolution; | |
this.baseTexture.height = this.height * this.resolution; | |
} | |
if (this.renderer.type === PIXI.WEBGL_RENDERER) | |
{ | |
this.projection.x = this.width / 2; | |
this.projection.y = -this.height / 2; | |
} | |
if(!this.valid)return; | |
this.textureBuffer.resize(this.width, this.height); | |
}; | |
/** | |
* Clears the RenderTexture. | |
* | |
* @method clear | |
*/ | |
PIXI.RenderTexture.prototype.clear = function() | |
{ | |
if (!this.valid) | |
{ | |
return; | |
} | |
if (this.renderer.type === PIXI.WEBGL_RENDERER) | |
{ | |
this.renderer.gl.bindFramebuffer(this.renderer.gl.FRAMEBUFFER, this.textureBuffer.frameBuffer); | |
} | |
this.textureBuffer.clear(); | |
}; | |
/** | |
* This function will draw the display object to the texture. | |
* | |
* @method renderWebGL | |
* @param displayObject {DisplayObject} The display object to render this texture on | |
* @param [matrix] {Matrix} Optional matrix to apply to the display object before rendering. | |
* @param [clear] {Boolean} If true the texture will be cleared before the displayObject is drawn | |
* @private | |
*/ | |
PIXI.RenderTexture.prototype.renderWebGL = function(displayObject, matrix, clear) | |
{ | |
if (!this.valid || displayObject.alpha === 0) | |
{ | |
return; | |
} | |
// Let's create a nice matrix to apply to our display object. | |
// Frame buffers come in upside down so we need to flip the matrix. | |
var wt = displayObject.worldTransform; | |
wt.identity(); | |
wt.translate(0, this.projection.y * 2); | |
if (matrix) | |
{ | |
wt.append(matrix); | |
} | |
wt.scale(1, -1); | |
// Time to update all the children of the displayObject with the new matrix. | |
for (var i = 0; i < displayObject.children.length; i++) | |
{ | |
displayObject.children[i].updateTransform(); | |
} | |
// Time for the webGL fun stuff! | |
var gl = this.renderer.gl; | |
gl.viewport(0, 0, this.width * this.resolution, this.height * this.resolution); | |
gl.bindFramebuffer(gl.FRAMEBUFFER, this.textureBuffer.frameBuffer ); | |
if (clear) | |
{ | |
this.textureBuffer.clear(); | |
} | |
this.renderer.spriteBatch.dirty = true; | |
this.renderer.renderDisplayObject(displayObject, this.projection, this.textureBuffer.frameBuffer, matrix); | |
this.renderer.spriteBatch.dirty = true; | |
}; | |
/** | |
* This function will draw the display object to the texture. | |
* | |
* @method renderCanvas | |
* @param displayObject {DisplayObject} The display object to render this texture on | |
* @param [matrix] {Matrix} Optional matrix to apply to the display object before rendering. | |
* @param [clear] {Boolean} If true the texture will be cleared before the displayObject is drawn | |
* @private | |
*/ | |
PIXI.RenderTexture.prototype.renderCanvas = function(displayObject, matrix, clear) | |
{ | |
if (!this.valid || displayObject.alpha === 0) | |
{ | |
return; | |
} | |
// Let's create a nice matrix to apply to our display object. | |
// Frame buffers come in upside down so we need to flip the matrix. | |
var wt = displayObject.worldTransform; | |
wt.identity(); | |
if (matrix) | |
{ | |
wt.append(matrix); | |
} | |
// Time to update all the children of the displayObject with the new matrix (what new matrix? there isn't one!) | |
for (var i = 0; i < displayObject.children.length; i++) | |
{ | |
displayObject.children[i].updateTransform(); | |
} | |
if (clear) | |
{ | |
this.textureBuffer.clear(); | |
} | |
var realResolution = this.renderer.resolution; | |
this.renderer.resolution = this.resolution; | |
this.renderer.renderDisplayObject(displayObject, this.textureBuffer.context, matrix); | |
this.renderer.resolution = realResolution; | |
}; | |
/** | |
* Will return a HTML Image of the texture | |
* | |
* @method getImage | |
* @return {Image} | |
*/ | |
PIXI.RenderTexture.prototype.getImage = function() | |
{ | |
var image = new Image(); | |
image.src = this.getBase64(); | |
return image; | |
}; | |
/** | |
* Will return a base64 encoded string of this texture. It works by calling RenderTexture.getCanvas and then running toDataURL on that. | |
* | |
* @method getBase64 | |
* @return {String} A base64 encoded string of the texture. | |
*/ | |
PIXI.RenderTexture.prototype.getBase64 = function() | |
{ | |
return this.getCanvas().toDataURL(); | |
}; | |
/** | |
* Creates a Canvas element, renders this RenderTexture to it and then returns it. | |
* | |
* @method getCanvas | |
* @return {HTMLCanvasElement} A Canvas element with the texture rendered on. | |
*/ | |
PIXI.RenderTexture.prototype.getCanvas = function() | |
{ | |
if (this.renderer.type === PIXI.WEBGL_RENDERER) | |
{ | |
var gl = this.renderer.gl; | |
var width = this.textureBuffer.width; | |
var height = this.textureBuffer.height; | |
var webGLPixels = new Uint8Array(4 * width * height); | |
gl.bindFramebuffer(gl.FRAMEBUFFER, this.textureBuffer.frameBuffer); | |
gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); | |
gl.bindFramebuffer(gl.FRAMEBUFFER, null); | |
var tempCanvas = new PIXI.CanvasBuffer(width, height); | |
var canvasData = tempCanvas.context.getImageData(0, 0, width, height); | |
canvasData.data.set(webGLPixels); | |
tempCanvas.context.putImageData(canvasData, 0, 0); | |
return tempCanvas.canvas; | |
} | |
else | |
{ | |
return this.textureBuffer.canvas; | |
} | |
}; | |
/** | |
* @author Mat Groves http://matgroves.com/ @Doormat23 | |
*/ | |
/** | |
* This is the base class for creating a PIXI filter. Currently only webGL supports filters. | |
* If you want to make a custom filter this should be your base class. | |
* | |
* @class AbstractFilter | |
* @constructor | |
* @param fragmentSrc {Array} The fragment source in an array of strings. | |
* @param uniforms {Object} An object containing the uniforms for this filter. | |
*/ | |
PIXI.AbstractFilter = function(fragmentSrc, uniforms) | |
{ | |
/** | |
* An array of passes - some filters contain a few steps this array simply stores the steps in a liniear fashion. | |
* For example the blur filter has two passes blurX and blurY. | |
* @property passes | |
* @type Array | |
* @private | |
*/ | |
this.passes = [this]; | |
/** | |
* @property shaders | |
* @type Array | |
* @private | |
*/ | |
this.shaders = []; | |
/** | |
* @property dirty | |
* @type Boolean | |
*/ | |
this.dirty = true; | |
/** | |
* @property padding | |
* @type Number | |
*/ | |
this.padding = 0; | |
/** | |
* @property uniforms | |
* @type Object | |
* @private | |
*/ | |
this.uniforms = uniforms || {}; | |
/** | |
* @property fragmentSrc | |
* @type Array | |
* @private | |
*/ | |
this.fragmentSrc = fragmentSrc || []; | |
}; | |
PIXI.AbstractFilter.prototype.constructor = PIXI.AbstractFilter; | |
/** | |
* Syncs the uniforms between the class object and the shaders. | |
* | |
* @method syncUniforms | |
*/ | |
PIXI.AbstractFilter.prototype.syncUniforms = function() | |
{ | |
for(var i=0,j=this.shaders.length; i<j; i++) | |
{ | |
this.shaders[i].dirty = true; | |
} | |
}; | |
/** | |
* @author Mat Groves http://matgroves.com/ | |
*/ | |
/** | |
* | |
* @class Strip | |
* @extends DisplayObjectContainer | |
* @constructor | |
* @param texture {Texture} The texture to use | |
* @param width {Number} the width | |
* @param height {Number} the height | |
* | |
*/ | |
PIXI.Strip = function(texture) | |
{ | |
PIXI.DisplayObjectContainer.call( this ); | |
/** | |
* The texture of the strip | |
* | |
* @property texture | |
* @type Texture | |
*/ | |
this.texture = texture; | |
// set up the main bits.. | |
this.uvs = new PIXI.Float32Array([0, 1, | |
1, 1, | |
1, 0, | |
0, 1]); | |
this.vertices = new PIXI.Float32Array([0, 0, | |
100, 0, | |
100, 100, | |
0, 100]); | |
this.colors = new PIXI.Float32Array([1, 1, 1, 1]); | |
this.indices = new PIXI.Uint16Array([0, 1, 2, 3]); | |
/** | |
* Whether the strip is dirty or not | |
* | |
* @property dirty | |
* @type Boolean | |
*/ | |
this.dirty = true; | |
/** | |
* The blend mode to be applied to the sprite. Set to PIXI.blendModes.NORMAL to remove any blend mode. | |
* | |
* @property blendMode | |
* @type Number | |
* @default PIXI.blendModes.NORMAL; | |
*/ | |
this.blendMode = PIXI.blendModes.NORMAL; | |
/** | |
* Triangles in canvas mode are automatically antialiased, use this value to force triangles to overlap a bit with each other. | |
* | |
* @property canvasPadding | |
* @type Number | |
*/ | |
this.canvasPadding = 0; | |
this.drawMode = PIXI.Strip.DrawModes.TRIANGLE_STRIP; | |
}; | |
// constructor | |
PIXI.Strip.prototype = Object.create(PIXI.DisplayObjectContainer.prototype); | |
PIXI.Strip.prototype.constructor = PIXI.Strip; | |
PIXI.Strip.prototype._renderWebGL = function(renderSession) | |
{ | |
// if the sprite is not visible or the alpha is 0 then no need to render this element | |
if(!this.visible || this.alpha <= 0)return; | |
// render triangle strip.. | |
renderSession.spriteBatch.stop(); | |
// init! init! | |
if(!this._vertexBuffer)this._initWebGL(renderSession); | |
renderSession.shaderManager.setShader(renderSession.shaderManager.stripShader); | |
this._renderStrip(renderSession); | |
///renderSession.shaderManager.activateDefaultShader(); | |
renderSession.spriteBatch.start(); | |
//TODO check culling | |
}; | |
PIXI.Strip.prototype._initWebGL = function(renderSession) | |
{ | |
// build the strip! | |
var gl = renderSession.gl; | |
this._vertexBuffer = gl.createBuffer(); | |
this._indexBuffer = gl.createBuffer(); | |
this._uvBuffer = gl.createBuffer(); | |
this._colorBuffer = gl.createBuffer(); | |
gl.bindBuffer(gl.ARRAY_BUFFER, this._vertexBuffer); | |
gl.bufferData(gl.ARRAY_BUFFER, this.vertices, gl.DYNAMIC_DRAW); | |
gl.bindBuffer(gl.ARRAY_BUFFER, this._uvBuffer); | |
gl.bufferData(gl.ARRAY_BUFFER, this.uvs, gl.STATIC_DRAW); | |
gl.bindBuffer(gl.ARRAY_BUFFER, this._colorBuffer); | |
gl.bufferData(gl.ARRAY_BUFFER, this.colors, gl.STATIC_DRAW); | |
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._indexBuffer); | |
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indices, gl.STATIC_DRAW); | |
}; | |
PIXI.Strip.prototype._renderStrip = function(renderSession) | |
{ | |
var gl = renderSession.gl; | |
var projection = renderSession.projection, | |
offset = renderSession.offset, | |
shader = renderSession.shaderManager.stripShader; | |
var drawMode = this.drawMode === PIXI.Strip.DrawModes.TRIANGLE_STRIP ? gl.TRIANGLE_STRIP : gl.TRIANGLES; | |
// gl.uniformMatrix4fv(shaderProgram.mvMatrixUniform, false, mat4Real); | |
renderSession.blendModeManager.setBlendMode(this.blendMode); | |
// set uniforms | |
gl.uniformMatrix3fv(shader.translationMatrix, false, this.worldTransform.toArray(true)); | |
gl.uniform2f(shader.projectionVector, projection.x, -projection.y); | |
gl.uniform2f(shader.offsetVector, -offset.x, -offset.y); | |
gl.uniform1f(shader.alpha, this.worldAlpha); | |
if(!this.dirty) | |
{ | |
gl.bindBuffer(gl.ARRAY_BUFFER, this._vertexBuffer); | |
gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertices); | |
gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 0, 0); | |
// update the uvs | |
gl.bindBuffer(gl.ARRAY_BUFFER, this._uvBuffer); | |
gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, 0, 0); | |
gl.activeTexture(gl.TEXTURE0); | |
// check if a texture is dirty.. | |
if(this.texture.baseTexture._dirty[gl.id]) | |
{ | |
renderSession.renderer.updateTexture(this.texture.baseTexture); | |
} | |
else | |
{ | |
// bind the current texture | |
gl.bindTexture(gl.TEXTURE_2D, this.texture.baseTexture._glTextures[gl.id]); | |
} | |
// dont need to upload! | |
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._indexBuffer); | |
} | |
else | |
{ | |
this.dirty = false; | |
gl.bindBuffer(gl.ARRAY_BUFFER, this._vertexBuffer); | |
gl.bufferData(gl.ARRAY_BUFFER, this.vertices, gl.STATIC_DRAW); | |
gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 0, 0); | |
// update the uvs | |
gl.bindBuffer(gl.ARRAY_BUFFER, this._uvBuffer); | |
gl.bufferData(gl.ARRAY_BUFFER, this.uvs, gl.STATIC_DRAW); | |
gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, 0, 0); | |
gl.activeTexture(gl.TEXTURE0); | |
// check if a texture is dirty.. | |
if(this.texture.baseTexture._dirty[gl.id]) | |
{ | |
renderSession.renderer.updateTexture(this.texture.baseTexture); | |
} | |
else | |
{ | |
gl.bindTexture(gl.TEXTURE_2D, this.texture.baseTexture._glTextures[gl.id]); | |
} | |
// dont need to upload! | |
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._indexBuffer); | |
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indices, gl.STATIC_DRAW); | |
} | |
//console.log(gl.TRIANGLE_STRIP) | |
// | |
// | |
gl.drawElements(drawMode, this.indices.length, gl.UNSIGNED_SHORT, 0); | |
}; | |
PIXI.Strip.prototype._renderCanvas = function(renderSession) | |
{ | |
var context = renderSession.context; | |
var transform = this.worldTransform; | |
var tx = (transform.tx * renderSession.resolution) + renderSession.shakeX; | |
var ty = (transform.ty * renderSession.resolution) + renderSession.shakeY; | |
if (renderSession.roundPixels) | |
{ | |
context.setTransform(transform.a, transform.b, transform.c, transform.d, tx | 0, ty | 0); | |
} | |
else | |
{ | |
context.setTransform(transform.a, transform.b, transform.c, transform.d, tx, ty); | |
} | |
if (this.drawMode === PIXI.Strip.DrawModes.TRIANGLE_STRIP) | |
{ | |
this._renderCanvasTriangleStrip(context); | |
} | |
else | |
{ | |
this._renderCanvasTriangles(context); | |
} | |
}; | |
PIXI.Strip.prototype._renderCanvasTriangleStrip = function(context) | |
{ | |
// draw triangles!! | |
var vertices = this.vertices; | |
var uvs = this.uvs; | |
var length = vertices.length / 2; | |
this.count++; | |
for (var i = 0; i < length - 2; i++) { | |
// draw some triangles! | |
var index = i * 2; | |
this._renderCanvasDrawTriangle(context, vertices, uvs, index, (index + 2), (index + 4)); | |
} | |
}; | |
PIXI.Strip.prototype._renderCanvasTriangles = function(context) | |
{ | |
// draw triangles!! | |
var vertices = this.vertices; | |
var uvs = this.uvs; | |
var indices = this.indices; | |
var length = indices.length; | |
this.count++; | |
for (var i = 0; i < length; i += 3) { | |
// draw some triangles! | |
var index0 = indices[i] * 2, index1 = indices[i + 1] * 2, index2 = indices[i + 2] * 2; | |
this._renderCanvasDrawTriangle(context, vertices, uvs, index0, index1, index2); | |
} | |
}; | |
PIXI.Strip.prototype._renderCanvasDrawTriangle = function(context, vertices, uvs, index0, index1, index2) | |
{ | |
var textureSource = this.texture.baseTexture.source; | |
var textureWidth = this.texture.width; | |
var textureHeight = this.texture.height; | |
var x0 = vertices[index0], x1 = vertices[index1], x2 = vertices[index2]; | |
var y0 = vertices[index0 + 1], y1 = vertices[index1 + 1], y2 = vertices[index2 + 1]; | |
var u0 = uvs[index0] * textureWidth, u1 = uvs[index1] * textureWidth, u2 = uvs[index2] * textureWidth; | |
var v0 = uvs[index0 + 1] * textureHeight, v1 = uvs[index1 + 1] * textureHeight, v2 = uvs[index2 + 1] * textureHeight; | |
if (this.canvasPadding > 0) { | |
var paddingX = this.canvasPadding / this.worldTransform.a; | |
var paddingY = this.canvasPadding / this.worldTransform.d; | |
var centerX = (x0 + x1 + x2) / 3; | |
var centerY = (y0 + y1 + y2) / 3; | |
var normX = x0 - centerX; | |
var normY = y0 - centerY; | |
var dist = Math.sqrt(normX * normX + normY * normY); | |
x0 = centerX + (normX / dist) * (dist + paddingX); | |
y0 = centerY + (normY / dist) * (dist + paddingY); | |
// | |
normX = x1 - centerX; | |
normY = y1 - centerY; | |
dist = Math.sqrt(normX * normX + normY * normY); | |
x1 = centerX + (normX / dist) * (dist + paddingX); | |
y1 = centerY + (normY / dist) * (dist + paddingY); | |
normX = x2 - centerX; | |
normY = y2 - centerY; | |
dist = Math.sqrt(normX * normX + normY * normY); | |
x2 = centerX + (normX / dist) * (dist + paddingX); | |
y2 = centerY + (normY / dist) * (dist + paddingY); | |
} | |
context.save(); | |
context.beginPath(); | |
context.moveTo(x0, y0); | |
context.lineTo(x1, y1); | |
context.lineTo(x2, y2); | |
context.closePath(); | |
context.clip(); | |
// Compute matrix transform | |
var delta = (u0 * v1) + (v0 * u2) + (u1 * v2) - (v1 * u2) - (v0 * u1) - (u0 * v2); | |
var deltaA = (x0 * v1) + (v0 * x2) + (x1 * v2) - (v1 * x2) - (v0 * x1) - (x0 * v2); | |
var deltaB = (u0 * x1) + (x0 * u2) + (u1 * x2) - (x1 * u2) - (x0 * u1) - (u0 * x2); | |
var deltaC = (u0 * v1 * x2) + (v0 * x1 * u2) + (x0 * u1 * v2) - (x0 * v1 * u2) - (v0 * u1 * x2) - (u0 * x1 * v2); | |
var deltaD = (y0 * v1) + (v0 * y2) + (y1 * v2) - (v1 * y2) - (v0 * y1) - (y0 * v2); | |
var deltaE = (u0 * y1) + (y0 * u2) + (u1 * y2) - (y1 * u2) - (y0 * u1) - (u0 * y2); | |
var deltaF = (u0 * v1 * y2) + (v0 * y1 * u2) + (y0 * u1 * v2) - (y0 * v1 * u2) - (v0 * u1 * y2) - (u0 * y1 * v2); | |
context.transform(deltaA / delta, deltaD / delta, | |
deltaB / delta, deltaE / delta, | |
deltaC / delta, deltaF / delta); | |
context.drawImage(textureSource, 0, 0); | |
context.restore(); | |
}; | |
/** | |
* Renders a flat strip | |
* | |
* @method renderStripFlat | |
* @param strip {Strip} The Strip to render | |
* @private | |
*/ | |
PIXI.Strip.prototype.renderStripFlat = function(strip) | |
{ | |
var context = this.context; | |
var vertices = strip.vertices; | |
var length = vertices.length/2; | |
this.count++; | |
context.beginPath(); | |
for (var i=1; i < length-2; i++) | |
{ | |
// draw some triangles! | |
var index = i*2; | |
var x0 = vertices[index], x1 = vertices[index+2], x2 = vertices[index+4]; | |
var y0 = vertices[index+1], y1 = vertices[index+3], y2 = vertices[index+5]; | |
context.moveTo(x0, y0); | |
context.lineTo(x1, y1); | |
context.lineTo(x2, y2); | |
} | |
context.fillStyle = '#FF0000'; | |
context.fill(); | |
context.closePath(); | |
}; | |
/* | |
PIXI.Strip.prototype.setTexture = function(texture) | |
{ | |
//TODO SET THE TEXTURES | |
//TODO VISIBILITY | |
// stop current texture | |
this.texture = texture; | |
this.width = texture.frame.width; | |
this.height = texture.frame.height; | |
this.updateFrame = true; | |
}; | |
*/ | |
/** | |
* When the texture is updated, this event will fire to update the scale and frame | |
* | |
* @method onTextureUpdate | |
* @param event | |
* @private | |
*/ | |
PIXI.Strip.prototype.onTextureUpdate = function() | |
{ | |
this.updateFrame = true; | |
}; | |
/** | |
* Returns the bounds of the mesh as a rectangle. The bounds calculation takes the worldTransform into account. | |
* | |
* @method getBounds | |
* @param matrix {Matrix} the transformation matrix of the sprite | |
* @return {Rectangle} the framing rectangle | |
*/ | |
PIXI.Strip.prototype.getBounds = function(matrix) | |
{ | |
var worldTransform = matrix || this.worldTransform; | |
var a = worldTransform.a; | |
var b = worldTransform.b; | |
var c = worldTransform.c; | |
var d = worldTransform.d; | |
var tx = worldTransform.tx; | |
var ty = worldTransform.ty; | |
var maxX = -Infinity; | |
var maxY = -Infinity; | |
var minX = Infinity; | |
var minY = Infinity; | |
var vertices = this.vertices; | |
for (var i = 0, n = vertices.length; i < n; i += 2) | |
{ | |
var rawX = vertices[i], rawY = vertices[i + 1]; | |
var x = (a * rawX) + (c * rawY) + tx; | |
var y = (d * rawY) + (b * rawX) + ty; | |
minX = x < minX ? x : minX; | |
minY = y < minY ? y : minY; | |
maxX = x > maxX ? x : maxX; | |
maxY = y > maxY ? y : maxY; | |
} | |
if (minX === -Infinity || maxY === Infinity) | |
{ | |
return PIXI.EmptyRectangle; | |
} | |
var bounds = this._bounds; | |
bounds.x = minX; | |
bounds.width = maxX - minX; | |
bounds.y = minY; | |
bounds.height = maxY - minY; | |
// store a reference so that if this function gets called again in the render cycle we do not have to recalculate | |
this._currentBounds = bounds; | |
return bounds; | |
}; | |
/** | |
* Different drawing buffer modes supported | |
* | |
* @property | |
* @type {{TRIANGLE_STRIP: number, TRIANGLES: number}} | |
* @static | |
*/ | |
PIXI.Strip.DrawModes = { | |
TRIANGLE_STRIP: 0, | |
TRIANGLES: 1 | |
}; | |
/** | |
* @author Mat Groves http://matgroves.com/ @Doormat23 | |
* @copyright Mat Groves, Rovanion Luckey | |
*/ | |
/** | |
* | |
* @class Rope | |
* @constructor | |
* @extends Strip | |
* @param {Texture} texture - The texture to use on the rope. | |
* @param {Array} points - An array of {PIXI.Point}. | |
* | |
*/ | |
PIXI.Rope = function(texture, points) | |
{ | |
PIXI.Strip.call( this, texture ); | |
this.points = points; | |
this.vertices = new PIXI.Float32Array(points.length * 4); | |
this.uvs = new PIXI.Float32Array(points.length * 4); | |
this.colors = new PIXI.Float32Array(points.length * 2); | |
this.indices = new PIXI.Uint16Array(points.length * 2); | |
this.refresh(); | |
}; | |
// constructor | |
PIXI.Rope.prototype = Object.create( PIXI.Strip.prototype ); | |
PIXI.Rope.prototype.constructor = PIXI.Rope; | |
/* | |
* Refreshes | |
* | |
* @method refresh | |
*/ | |
PIXI.Rope.prototype.refresh = function() | |
{ | |
var points = this.points; | |
if(points.length < 1) return; | |
var uvs = this.uvs; | |
var lastPoint = points[0]; | |
var indices = this.indices; | |
var colors = this.colors; | |
this.count-=0.2; | |
uvs[0] = 0; | |
uvs[1] = 0; | |
uvs[2] = 0; | |
uvs[3] = 1; | |
colors[0] = 1; | |
colors[1] = 1; | |
indices[0] = 0; | |
indices[1] = 1; | |
var total = points.length, | |
point, index, amount; | |
for (var i = 1; i < total; i++) | |
{ | |
point = points[i]; | |
index = i * 4; | |
// time to do some smart drawing! | |
amount = i / (total-1); | |
if(i%2) | |
{ | |
uvs[index] = amount; | |
uvs[index+1] = 0; | |
uvs[index+2] = amount; | |
uvs[index+3] = 1; | |
} | |
else | |
{ | |
uvs[index] = amount; | |
uvs[index+1] = 0; | |
uvs[index+2] = amount; | |
uvs[index+3] = 1; | |
} | |
index = i * 2; | |
colors[index] = 1; | |
colors[index+1] = 1; | |
index = i * 2; | |
indices[index] = index; | |
indices[index + 1] = index + 1; | |
lastPoint = point; | |
} | |
}; | |
/* | |
* Updates the object transform for rendering | |
* | |
* @method updateTransform | |
* @private | |
*/ | |
PIXI.Rope.prototype.updateTransform = function() | |
{ | |
var points = this.points; | |
if(points.length < 1)return; | |
var lastPoint = points[0]; | |
var nextPoint; | |
var perp = {x:0, y:0}; | |
this.count-=0.2; | |
var vertices = this.vertices; | |
var total = points.length, | |
point, index, ratio, perpLength, num; | |
for (var i = 0; i < total; i++) | |
{ | |
point = points[i]; | |
index = i * 4; | |
if(i < points.length-1) | |
{ | |
nextPoint = points[i+1]; | |
} | |
else | |
{ | |
nextPoint = point; | |
} | |
perp.y = -(nextPoint.x - lastPoint.x); | |
perp.x = nextPoint.y - lastPoint.y; | |
ratio = (1 - (i / (total-1))) * 10; | |
if(ratio > 1) ratio = 1; | |
perpLength = Math.sqrt(perp.x * perp.x + perp.y * perp.y); | |
num = this.texture.height / 2; //(20 + Math.abs(Math.sin((i + this.count) * 0.3) * 50) )* ratio; | |
perp.x /= perpLength; | |
perp.y /= perpLength; | |
perp.x *= num; | |
perp.y *= num; | |
vertices[index] = point.x + perp.x; | |
vertices[index+1] = point.y + perp.y; | |
vertices[index+2] = point.x - perp.x; | |
vertices[index+3] = point.y - perp.y; | |
lastPoint = point; | |
} | |
PIXI.DisplayObjectContainer.prototype.updateTransform.call( this ); | |
}; | |
/* | |
* Sets the texture that the Rope will use | |
* | |
* @method setTexture | |
* @param texture {Texture} the texture that will be used | |
*/ | |
PIXI.Rope.prototype.setTexture = function(texture) | |
{ | |
// stop current texture | |
this.texture = texture; | |
//this.updateFrame = true; | |
}; | |
/** | |
* @author Mat Groves http://matgroves.com/ | |
*/ | |
/** | |
* A tiling sprite is a fast way of rendering a tiling image | |
* | |
* @class TilingSprite | |
* @extends Sprite | |
* @constructor | |
* @param texture {Texture} the texture of the tiling sprite | |
* @param width {Number} the width of the tiling sprite | |
* @param height {Number} the height of the tiling sprite | |
*/ | |
PIXI.TilingSprite = function(texture, width, height) | |
{ | |
PIXI.Sprite.call(this, texture); | |
/** | |
* The width of the tiling sprite | |
* | |
* @property width | |
* @type Number | |
*/ | |
this._width = width || 128; | |
/** | |
* The height of the tiling sprite | |
* | |
* @property height | |
* @type Number | |
*/ | |
this._height = height || 128; | |
/** | |
* The scaling of the image that is being tiled | |
* | |
* @property tileScale | |
* @type Point | |
*/ | |
this.tileScale = new PIXI.Point(1, 1); | |
/** | |
* A point that represents the scale of the texture object | |
* | |
* @property tileScaleOffset | |
* @type Point | |
*/ | |
this.tileScaleOffset = new PIXI.Point(1, 1); | |
/** | |
* The offset position of the image that is being tiled | |
* | |
* @property tilePosition | |
* @type Point | |
*/ | |
this.tilePosition = new PIXI.Point(); | |
/** | |
* Whether this sprite is renderable or not | |
* | |
* @property renderable | |
* @type Boolean | |
* @default true | |
*/ | |
this.renderable = true; | |
/** | |
* The tint applied to the sprite. This is a hex value | |
* | |
* @property tint | |
* @type Number | |
* @default 0xFFFFFF | |
*/ | |
this.tint = 0xFFFFFF; | |
/** | |
* If enabled a green rectangle will be drawn behind the generated tiling texture, allowing you to visually | |
* debug the texture being used. | |
* | |
* @property textureDebug | |
* @type Boolean | |
*/ | |
this.textureDebug = false; | |
/** | |
* The blend mode to be applied to the sprite | |
* | |
* @property blendMode | |
* @type Number | |
* @default PIXI.blendModes.NORMAL; | |
*/ | |
this.blendMode = PIXI.blendModes.NORMAL; | |
/** | |
* The CanvasBuffer object that the tiled texture is drawn to. | |
* | |
* @property canvasBuffer | |
* @type PIXI.CanvasBuffer | |
*/ | |
this.canvasBuffer = null; | |
/** | |
* An internal Texture object that holds the tiling texture that was generated from TilingSprite.texture. | |
* | |
* @property tilingTexture | |
* @type PIXI.Texture | |
*/ | |
this.tilingTexture = null; | |
/** | |
* The Context fill pattern that is used to draw the TilingSprite in Canvas mode only (will be null in WebGL). | |
* | |
* @property tilePattern | |
* @type PIXI.Texture | |
*/ | |
this.tilePattern = null; | |
/** | |
* If true the TilingSprite will run generateTexture on its **next** render pass. | |
* This is set by the likes of Phaser.LoadTexture.setFrame. | |
* | |
* @property refreshTexture | |
* @type Boolean | |
* @default true | |
*/ | |
this.refreshTexture = true; | |
this.frameWidth = 0; | |
this.frameHeight = 0; | |
}; | |
PIXI.TilingSprite.prototype = Object.create(PIXI.Sprite.prototype); | |
PIXI.TilingSprite.prototype.constructor = PIXI.TilingSprite; | |
PIXI.TilingSprite.prototype.setTexture = function(texture) | |
{ | |
if (this.texture !== texture) | |
{ | |
this.texture = texture; | |
this.refreshTexture = true; | |
this.cachedTint = 0xFFFFFF; | |
} | |
}; | |
/** | |
* Renders the object using the WebGL renderer | |
* | |
* @method _renderWebGL | |
* @param renderSession {RenderSession} | |
* @private | |
*/ | |
PIXI.TilingSprite.prototype._renderWebGL = function(renderSession) | |
{ | |
if (!this.visible || !this.renderable || this.alpha === 0) | |
{ | |
return; | |
} | |
if (this._mask) | |
{ | |
renderSession.spriteBatch.stop(); | |
renderSession.maskManager.pushMask(this.mask, renderSession); | |
renderSession.spriteBatch.start(); | |
} | |
if (this._filters) | |
{ | |
renderSession.spriteBatch.flush(); | |
renderSession.filterManager.pushFilter(this._filterBlock); | |
} | |
if (this.refreshTexture) | |
{ | |
this.generateTilingTexture(true, renderSession); | |
if (this.tilingTexture) | |
{ | |
if (this.tilingTexture.needsUpdate) | |
{ | |
renderSession.renderer.updateTexture(this.tilingTexture.baseTexture); | |
this.tilingTexture.needsUpdate = false; | |
} | |
} | |
else | |
{ | |
return; | |
} | |
} | |
renderSession.spriteBatch.renderTilingSprite(this); | |
for (var i = 0; i < this.children.length; i++) | |
{ | |
this.children[i]._renderWebGL(renderSession); | |
} | |
renderSession.spriteBatch.stop(); | |
if (this._filters) | |
{ | |
renderSession.filterManager.popFilter(); | |
} | |
if (this._mask) | |
{ | |
renderSession.maskManager.popMask(this._mask, renderSession); | |
} | |
renderSession.spriteBatch.start(); | |
}; | |
/** | |
* Renders the object using the Canvas renderer | |
* | |
* @method _renderCanvas | |
* @param renderSession {RenderSession} | |
* @private | |
*/ | |
PIXI.TilingSprite.prototype._renderCanvas = function(renderSession) | |
{ | |
if (!this.visible || !this.renderable || this.alpha === 0) | |
{ | |
return; | |
} | |
var context = renderSession.context; | |
if (this._mask) | |
{ | |
renderSession.maskManager.pushMask(this._mask, renderSession); | |
} | |
context.globalAlpha = this.worldAlpha; | |
var wt = this.worldTransform; | |
var resolution = renderSession.resolution; | |
var tx = (wt.tx * resolution) + renderSession.shakeX; | |
var ty = (wt.ty * resolution) + renderSession.shakeY; | |
context.setTransform(wt.a * resolution, wt.b * resolution, wt.c * resolution, wt.d * resolution, tx, ty); | |
if (this.refreshTexture) | |
{ | |
this.generateTilingTexture(false, renderSession); | |
if (this.tilingTexture) | |
{ | |
this.tilePattern = context.createPattern(this.tilingTexture.baseTexture.source, 'repeat'); | |
} | |
else | |
{ | |
return; | |
} | |
} | |
var sessionBlendMode = renderSession.currentBlendMode; | |
// Check blend mode | |
if (this.blendMode !== renderSession.currentBlendMode) | |
{ | |
renderSession.currentBlendMode = this.blendMode; | |
context.globalCompositeOperation = PIXI.blendModesCanvas[renderSession.currentBlendMode]; | |
} | |
var tilePosition = this.tilePosition; | |
var tileScale = this.tileScale; | |
tilePosition.x %= this.tilingTexture.baseTexture.width; | |
tilePosition.y %= this.tilingTexture.baseTexture.height; | |
// Translate | |
context.scale(tileScale.x, tileScale.y); | |
context.translate(tilePosition.x + (this.anchor.x * -this._width), tilePosition.y + (this.anchor.y * -this._height)); | |
context.fillStyle = this.tilePattern; | |
var tx = -tilePosition.x; | |
var ty = -tilePosition.y; | |
var tw = this._width / tileScale.x; | |
var th = this._height / tileScale.y; | |
// Allow for pixel rounding | |
if (renderSession.roundPixels) | |
{ | |
tx |= 0; | |
ty |= 0; | |
tw |= 0; | |
th |= 0; | |
} | |
context.fillRect(tx, ty, tw, th); | |
// Translate back again | |
context.scale(1 / tileScale.x, 1 / tileScale.y); | |
context.translate(-tilePosition.x + (this.anchor.x * this._width), -tilePosition.y + (this.anchor.y * this._height)); | |
if (this._mask) | |
{ | |
renderSession.maskManager.popMask(renderSession); | |
} | |
for (var i = 0; i < this.children.length; i++) | |
{ | |
this.children[i]._renderCanvas(renderSession); | |
} | |
// Reset blend mode | |
if (sessionBlendMode !== this.blendMode) | |
{ | |
renderSession.currentBlendMode = sessionBlendMode; | |
context.globalCompositeOperation = PIXI.blendModesCanvas[sessionBlendMode]; | |
} | |
}; | |
/** | |
* When the texture is updated, this event will fire to update the scale and frame | |
* | |
* @method onTextureUpdate | |
* @param event | |
* @private | |
*/ | |
PIXI.TilingSprite.prototype.onTextureUpdate = function() | |
{ | |
// overriding the sprite version of this! | |
}; | |
/** | |
* | |
* @method generateTilingTexture | |
* | |
* @param forcePowerOfTwo {Boolean} Whether we want to force the texture to be a power of two | |
* @param renderSession {RenderSession} | |
*/ | |
PIXI.TilingSprite.prototype.generateTilingTexture = function(forcePowerOfTwo, renderSession) | |
{ | |
if (!this.texture.baseTexture.hasLoaded) | |
{ | |
return; | |
} | |
var texture = this.texture; | |
var frame = texture.frame; | |
var targetWidth = this._frame.sourceSizeW || this._frame.width; | |
var targetHeight = this._frame.sourceSizeH || this._frame.height; | |
var dx = 0; | |
var dy = 0; | |
if (this._frame.trimmed) | |
{ | |
dx = this._frame.spriteSourceSizeX; | |
dy = this._frame.spriteSourceSizeY; | |
} | |
if (forcePowerOfTwo) | |
{ | |
targetWidth = PIXI.getNextPowerOfTwo(targetWidth); | |
targetHeight = PIXI.getNextPowerOfTwo(targetHeight); | |
} | |
if (this.canvasBuffer) | |
{ | |
this.canvasBuffer.resize(targetWidth, targetHeight); | |
this.tilingTexture.baseTexture.width = targetWidth; | |
this.tilingTexture.baseTexture.height = targetHeight; | |
this.tilingTexture.needsUpdate = true; | |
} | |
else | |
{ | |
this.canvasBuffer = new PIXI.CanvasBuffer(targetWidth, targetHeight); | |
this.tilingTexture = PIXI.Texture.fromCanvas(this.canvasBuffer.canvas); | |
this.tilingTexture.isTiling = true; | |
this.tilingTexture.needsUpdate = true; | |
} | |
if (this.textureDebug) | |
{ | |
this.canvasBuffer.context.strokeStyle = '#00ff00'; | |
this.canvasBuffer.context.strokeRect(0, 0, targetWidth, targetHeight); | |
} | |
// If a sprite sheet we need this: | |
var w = texture.crop.width; | |
var h = texture.crop.height; | |
if (w !== targetWidth || h !== targetHeight) | |
{ | |
w = targetWidth; | |
h = targetHeight; | |
} | |
this.canvasBuffer.context.drawImage(texture.baseTexture.source, | |
texture.crop.x, | |
texture.crop.y, | |
texture.crop.width, | |
texture.crop.height, | |
dx, | |
dy, | |
w, | |
h); | |
this.tileScaleOffset.x = frame.width / targetWidth; | |
this.tileScaleOffset.y = frame.height / targetHeight; | |
this.refreshTexture = false; | |
this.tilingTexture.baseTexture._powerOf2 = true; | |
}; | |
/** | |
* Returns the framing rectangle of the sprite as a PIXI.Rectangle object | |
* | |
* @method getBounds | |
* @return {Rectangle} the framing rectangle | |
*/ | |
PIXI.TilingSprite.prototype.getBounds = function() | |
{ | |
var width = this._width; | |
var height = this._height; | |
var w0 = width * (1-this.anchor.x); | |
var w1 = width * -this.anchor.x; | |
var h0 = height * (1-this.anchor.y); | |
var h1 = height * -this.anchor.y; | |
var worldTransform = this.worldTransform; | |
var a = worldTransform.a; | |
var b = worldTransform.b; | |
var c = worldTransform.c; | |
var d = worldTransform.d; | |
var tx = worldTransform.tx; | |
var ty = worldTransform.ty; | |
var x1 = a * w1 + c * h1 + tx; | |
var y1 = d * h1 + b * w1 + ty; | |
var x2 = a * w0 + c * h1 + tx; | |
var y2 = d * h1 + b * w0 + ty; | |
var x3 = a * w0 + c * h0 + tx; | |
var y3 = d * h0 + b * w0 + ty; | |
var x4 = a * w1 + c * h0 + tx; | |
var y4 = d * h0 + b * w1 + ty; | |
var maxX = -Infinity; | |
var maxY = -Infinity; | |
var minX = Infinity; | |
var minY = Infinity; | |
minX = x1 < minX ? x1 : minX; | |
minX = x2 < minX ? x2 : minX; | |
minX = x3 < minX ? x3 : minX; | |
minX = x4 < minX ? x4 : minX; | |
minY = y1 < minY ? y1 : minY; | |
minY = y2 < minY ? y2 : minY; | |
minY = y3 < minY ? y3 : minY; | |
minY = y4 < minY ? y4 : minY; | |
maxX = x1 > maxX ? x1 : maxX; | |
maxX = x2 > maxX ? x2 : maxX; | |
maxX = x3 > maxX ? x3 : maxX; | |
maxX = x4 > maxX ? x4 : maxX; | |
maxY = y1 > maxY ? y1 : maxY; | |
maxY = y2 > maxY ? y2 : maxY; | |
maxY = y3 > maxY ? y3 : maxY; | |
maxY = y4 > maxY ? y4 : maxY; | |
var bounds = this._bounds; | |
bounds.x = minX; | |
bounds.width = maxX - minX; | |
bounds.y = minY; | |
bounds.height = maxY - minY; | |
// store a reference so that if this function gets called again in the render cycle we do not have to recalculate | |
this._currentBounds = bounds; | |
return bounds; | |
}; | |
PIXI.TilingSprite.prototype.destroy = function () { | |
PIXI.Sprite.prototype.destroy.call(this); | |
if (this.canvasBuffer) | |
{ | |
this.canvasBuffer.destroy(); | |
this.canvasBuffer = null; | |
} | |
this.tileScale = null; | |
this.tileScaleOffset = null; | |
this.tilePosition = null; | |
if (this.tilingTexture) | |
{ | |
this.tilingTexture.destroy(true); | |
this.tilingTexture = null; | |
} | |
}; | |
/** | |
* The width of the sprite, setting this will actually modify the scale to achieve the value set | |
* | |
* @property width | |
* @type Number | |
*/ | |
Object.defineProperty(PIXI.TilingSprite.prototype, 'width', { | |
get: function() { | |
return this._width; | |
}, | |
set: function(value) { | |
this._width = value; | |
} | |
}); | |
/** | |
* The height of the TilingSprite, setting this will actually modify the scale to achieve the value set | |
* | |
* @property height | |
* @type Number | |
*/ | |
Object.defineProperty(PIXI.TilingSprite.prototype, 'height', { | |
get: function() { | |
return this._height; | |
}, | |
set: function(value) { | |
this._height = value; | |
} | |
}); | |
/** | |
* @author Mat Groves http://matgroves.com/ @Doormat23 | |
*/ | |
if (typeof exports !== 'undefined') { | |
if (typeof module !== 'undefined' && module.exports) { | |
exports = module.exports = PIXI; | |
} | |
exports.PIXI = PIXI; | |
} else if (typeof define !== 'undefined' && define.amd) { | |
define('PIXI', (function() { return root.PIXI = PIXI; })() ); | |
} else { | |
root.PIXI = PIXI; | |
} | |
return PIXI; | |
}).call(this); | |
/** | |
* @author Richard Davey <[email protected]> | |
* @copyright 2016 Photon Storm Ltd. | |
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} | |
*/ | |
(function(){ | |
var root = this; | |
/** | |
* @author Richard Davey <[email protected]> | |
* @copyright 2016 Photon Storm Ltd. | |
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} | |
*/ | |
/** | |
* @namespace Phaser | |
*/ | |
var Phaser = Phaser || { // jshint ignore:line | |
/** | |
* The Phaser version number. | |
* @constant | |
* @type {string} | |
*/ | |
VERSION: '2.6.2', | |
/** | |
* An array of Phaser game instances. | |
* @constant | |
* @type {array} | |
*/ | |
GAMES: [], | |
/** | |
* AUTO renderer - picks between WebGL or Canvas based on device. | |
* @constant | |
* @type {integer} | |
*/ | |
AUTO: 0, | |
/** | |
* Canvas Renderer. | |
* @constant | |
* @type {integer} | |
*/ | |
CANVAS: 1, | |
/** | |
* WebGL Renderer. | |
* @constant | |
* @type {integer} | |
*/ | |
WEBGL: 2, | |
/** | |
* Headless renderer (not visual output) | |
* @constant | |
* @type {integer} | |
*/ | |
HEADLESS: 3, | |
/** | |
* Direction constant. | |
* @constant | |
* @type {integer} | |
*/ | |
NONE: 0, | |
/** | |
* Direction constant. | |
* @constant | |
* @type {integer} | |
*/ | |
LEFT: 1, | |
/** | |
* Direction constant. | |
* @constant | |
* @type {integer} | |
*/ | |
RIGHT: 2, | |
/** | |
* Direction constant. | |
* @constant | |
* @type {integer} | |
*/ | |
UP: 3, | |
/** | |
* Direction constant. | |
* @constant | |
* @type {integer} | |
*/ | |
DOWN: 4, | |
/** | |
* Game Object type. | |
* @constant | |
* @type {integer} | |
*/ | |
SPRITE: 0, | |
/** | |
* Game Object type. | |
* @constant | |
* @type {integer} | |
*/ | |
BUTTON: 1, | |
/** | |
* Game Object type. | |
* @constant | |
* @type {integer} | |
*/ | |
IMAGE: 2, | |
/** | |
* Game Object type. | |
* @constant | |
* @type {integer} | |
*/ | |
GRAPHICS: 3, | |
/** | |
* Game Object type. | |
* @constant | |
* @type {integer} | |
*/ | |
TEXT: 4, | |
/** | |
* Game Object type. | |
* @constant | |
* @type {integer} | |
*/ | |
TILESPRITE: 5, | |
/** | |
* Game Object type. | |
* @constant | |
* @type {integer} | |
*/ | |
BITMAPTEXT: 6, | |
/** | |
* Game Object type. | |
* @constant | |
* @type {integer} | |
*/ | |
GROUP: 7, | |
/** | |
* Game Object type. | |
* @constant | |
* @type {integer} | |
*/ | |
RENDERTEXTURE: 8, | |
/** | |
* Game Object type. | |
* @constant | |
* @type {integer} | |
*/ | |
TILEMAP: 9, | |
/** | |
* Game Object type. | |
* @constant | |
* @type {integer} | |
*/ | |
TILEMAPLAYER: 10, | |
/** | |
* Game Object type. | |
* @constant | |
* @type {integer} | |
*/ | |
EMITTER: 11, | |
/** | |
* Game Object type. | |
* @constant | |
* @type {integer} | |
*/ | |
POLYGON: 12, | |
/** | |
* Game Object type. | |
* @constant | |
* @type {integer} | |
*/ | |
BITMAPDATA: 13, | |
/** | |
* Game Object type. | |
* @constant | |
* @type {integer} | |
*/ | |
CANVAS_FILTER: 14, | |
/** | |
* Game Object type. | |
* @constant | |
* @type {integer} | |
*/ | |
WEBGL_FILTER: 15, | |
/** | |
* Game Object type. | |
* @constant | |
* @type {integer} | |
*/ | |
ELLIPSE: 16, | |
/** | |
* Game Object type. | |
* @constant | |
* @type {integer} | |
*/ | |
SPRITEBATCH: 17, | |
/** | |
* Game Object type. | |
* @constant | |
* @type {integer} | |
*/ | |
RETROFONT: 18, | |
/** | |
* Game Object type. | |
* @constant | |
* @type {integer} | |
*/ | |
POINTER: 19, | |
/** | |
* Game Object type. | |
* @constant | |
* @type {integer} | |
*/ | |
ROPE: 20, | |
/** | |
* Game Object type. | |
* @constant | |
* @type {integer} | |
*/ | |
CIRCLE: 21, | |
/** | |
* Game Object type. | |
* @constant | |
* @type {integer} | |
*/ | |
RECTANGLE: 22, | |
/** | |
* Game Object type. | |
* @constant | |
* @type {integer} | |
*/ | |
LINE: 23, | |
/** | |
* Game Object type. | |
* @constant | |
* @type {integer} | |
*/ | |
MATRIX: 24, | |
/** | |
* Game Object type. | |
* @constant | |
* @type {integer} | |
*/ | |
POINT: 25, | |
/** | |
* Game Object type. | |
* @constant | |
* @type {integer} | |
*/ | |
ROUNDEDRECTANGLE: 26, | |
/** | |
* Game Object type. | |
* @constant | |
* @type {integer} | |
*/ | |
CREATURE: 27, | |
/** | |
* Game Object type. | |
* @constant | |
* @type {integer} | |
*/ | |
VIDEO: 28, | |
/** | |
* Game Object type. | |
* @constant | |
* @type {integer} | |
*/ | |
PENDING_ATLAS: -1, | |
/** | |
* A horizontal orientation | |
* @constant | |
* @type {integer} | |
*/ | |
HORIZONTAL: 0, | |
/** | |
* A vertical orientation | |
* @constant | |
* @type {integer} | |
*/ | |
VERTICAL: 1, | |
/** | |
* A landscape orientation | |
* @constant | |
* @type {integer} | |
*/ | |
LANDSCAPE: 0, | |
/** | |
* A portrait orientation | |
* @constant | |
* @type {integer} | |
*/ | |
PORTRAIT: 1, | |
/** | |
* The Angle (in degrees) a Game Object needs to be set to in order to face up. | |
* @constant | |
* @type {integer} | |
*/ | |
ANGLE_UP: 270, | |
/** | |
* The Angle (in degrees) a Game Object needs to be set to in order to face down. | |
* @constant | |
* @type {integer} | |
*/ | |
ANGLE_DOWN: 90, | |
/** | |
* The Angle (in degrees) a Game Object needs to be set to in order to face left. | |
* @constant | |
* @type {integer} | |
*/ | |
ANGLE_LEFT: 180, | |
/** | |
* The Angle (in degrees) a Game Object needs to be set to in order to face right. | |
* @constant | |
* @type {integer} | |
*/ | |
ANGLE_RIGHT: 0, | |
/** | |
* The Angle (in degrees) a Game Object needs to be set to in order to face north east. | |
* @constant | |
* @type {integer} | |
*/ | |
ANGLE_NORTH_EAST: 315, | |
/** | |
* The Angle (in degrees) a Game Object needs to be set to in order to face north west. | |
* @constant | |
* @type {integer} | |
*/ | |
ANGLE_NORTH_WEST: 225, | |
/** | |
* The Angle (in degrees) a Game Object needs to be set to in order to face south east. | |
* @constant | |
* @type {integer} | |
*/ | |
ANGLE_SOUTH_EAST: 45, | |
/** | |
* The Angle (in degrees) a Game Object needs to be set to in order to face south west. | |
* @constant | |
* @type {integer} | |
*/ | |
ANGLE_SOUTH_WEST: 135, | |
/** | |
* A constant representing a top-left alignment or position. | |
* @constant | |
* @type {integer} | |
*/ | |
TOP_LEFT: 0, | |
/** | |
* A constant representing a top-center alignment or position. | |
* @constant | |
* @type {integer} | |
*/ | |
TOP_CENTER: 1, | |
/** | |
* A constant representing a top-right alignment or position. | |
* @constant | |
* @type {integer} | |
*/ | |
TOP_RIGHT: 2, | |
/** | |
* A constant representing a left-top alignment or position. | |
* @constant | |
* @type {integer} | |
*/ | |
LEFT_TOP: 3, | |
/** | |
* A constant representing a left-center alignment or position. | |
* @constant | |
* @type {integer} | |
*/ | |
LEFT_CENTER: 4, | |
/** | |
* A constant representing a left-bottom alignment or position. | |
* @constant | |
* @type {integer} | |
*/ | |
LEFT_BOTTOM: 5, | |
/** | |
* A constant representing a center alignment or position. | |
* @constant | |
* @type {integer} | |
*/ | |
CENTER: 6, | |
/** | |
* A constant representing a right-top alignment or position. | |
* @constant | |
* @type {integer} | |
*/ | |
RIGHT_TOP: 7, | |
/** | |
* A constant representing a right-center alignment or position. | |
* @constant | |
* @type {integer} | |
*/ | |
RIGHT_CENTER: 8, | |
/** | |
* A constant representing a right-bottom alignment or position. | |
* @constant | |
* @type {integer} | |
*/ | |
RIGHT_BOTTOM: 9, | |
/** | |
* A constant representing a bottom-left alignment or position. | |
* @constant | |
* @type {integer} | |
*/ | |
BOTTOM_LEFT: 10, | |
/** | |
* A constant representing a bottom-center alignment or position. | |
* @constant | |
* @type {integer} | |
*/ | |
BOTTOM_CENTER: 11, | |
/** | |
* A constant representing a bottom-right alignment or position. | |
* @constant | |
* @type {integer} | |
*/ | |
BOTTOM_RIGHT: 12, | |
/** | |
* Various blend modes supported by Pixi. | |
* | |
* IMPORTANT: The WebGL renderer only supports the NORMAL, ADD, MULTIPLY and SCREEN blend modes. | |
* | |
* @constant | |
* @property {Number} blendModes.NORMAL | |
* @property {Number} blendModes.ADD | |
* @property {Number} blendModes.MULTIPLY | |
* @property {Number} blendModes.SCREEN | |
* @property {Number} blendModes.OVERLAY | |
* @property {Number} blendModes.DARKEN | |
* @property {Number} blendModes.LIGHTEN | |
* @property {Number} blendModes.COLOR_DODGE | |
* @property {Number} blendModes.COLOR_BURN | |
* @property {Number} blendModes.HARD_LIGHT | |
* @property {Number} blendModes.SOFT_LIGHT | |
* @property {Number} blendModes.DIFFERENCE | |
* @property {Number} blendModes.EXCLUSION | |
* @property {Number} blendModes.HUE | |
* @property {Number} blendModes.SATURATION | |
* @property {Number} blendModes.COLOR | |
* @property {Number} blendModes.LUMINOSITY | |
* @static | |
*/ | |
blendModes: { | |
NORMAL:0, | |
ADD:1, | |
MULTIPLY:2, | |
SCREEN:3, | |
OVERLAY:4, | |
DARKEN:5, | |
LIGHTEN:6, | |
COLOR_DODGE:7, | |
COLOR_BURN:8, | |
HARD_LIGHT:9, | |
SOFT_LIGHT:10, | |
DIFFERENCE:11, | |
EXCLUSION:12, | |
HUE:13, | |
SATURATION:14, | |
COLOR:15, | |
LUMINOSITY:16 | |
}, | |
/** | |
* The scale modes that are supported by Pixi. | |
* | |
* The DEFAULT scale mode affects the default scaling mode of future operations. | |
* It can be re-assigned to either LINEAR or NEAREST, depending upon suitability. | |
* | |
* @constant | |
* @property {Object} Phaser.scaleModes | |
* @property {Number} scaleModes.DEFAULT=LINEAR | |
* @property {Number} scaleModes.LINEAR Smooth scaling | |
* @property {Number} scaleModes.NEAREST Pixelating scaling | |
* @static | |
*/ | |
scaleModes: { | |
DEFAULT:0, | |
LINEAR:0, | |
NEAREST:1 | |
}, | |
PIXI: PIXI || {} | |
}; | |
/** | |
* @copyright 2016 Photon Storm Ltd. | |
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} | |
*/ | |
// ES6 Math.trunc - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/trunc | |
if (!Math.trunc) { | |
Math.trunc = function trunc(x) { | |
return x < 0 ? Math.ceil(x) : Math.floor(x); | |
}; | |
} | |
/** | |
* A polyfill for Function.prototype.bind | |
*/ | |
if (!Function.prototype.bind) { | |
/* jshint freeze: false */ | |
Function.prototype.bind = (function () { | |
var slice = Array.prototype.slice; | |
return function (thisArg) { | |
var target = this, boundArgs = slice.call(arguments, 1); | |
if (typeof target !== 'function') | |
{ | |
throw new TypeError(); | |
} | |
function bound() { | |
var args = boundArgs.concat(slice.call(arguments)); | |
target.apply(this instanceof bound ? this : thisArg, args); | |
} | |
bound.prototype = (function F(proto) { | |
if (proto) | |
{ | |
F.prototype = proto; | |
} | |
if (!(this instanceof F)) | |
{ | |
/* jshint supernew: true */ | |
return new F; | |
} | |
})(target.prototype); | |
return bound; | |
}; | |
})(); | |
} | |
/** | |
* A polyfill for Array.isArray | |
*/ | |
if (!Array.isArray) | |
{ | |
Array.isArray = function (arg) | |
{ | |
return Object.prototype.toString.call(arg) === '[object Array]'; | |
}; | |
} | |
/** | |
* A polyfill for Array.forEach | |
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach | |
*/ | |
if (!Array.prototype.forEach) | |
{ | |
Array.prototype.forEach = function(fun /*, thisArg */) | |
{ | |
"use strict"; | |
if (this === void 0 || this === null) | |
{ | |
throw new TypeError(); | |
} | |
var t = Object(this); | |
var len = t.length >>> 0; | |
if (typeof fun !== "function") | |
{ | |
throw new TypeError(); | |
} | |
var thisArg = arguments.length >= 2 ? arguments[1] : void 0; | |
for (var i = 0; i < len; i++) | |
{ | |
if (i in t) | |
{ | |
fun.call(thisArg, t[i], i, t); | |
} | |
} | |
}; | |
} | |
/** | |
* Low-budget Float32Array knock-off, suitable for use with P2.js in IE9 | |
* Source: http://www.html5gamedevs.com/topic/5988-phaser-12-ie9/ | |
* Cameron Foale (http://www.kibibu.com) | |
*/ | |
if (typeof window.Uint32Array !== "function" && typeof window.Uint32Array !== "object") | |
{ | |
var CheapArray = function(type) | |
{ | |
var proto = new Array(); // jshint ignore:line | |
window[type] = function(arg) { | |
if (typeof(arg) === "number") | |
{ | |
Array.call(this, arg); | |
this.length = arg; | |
for (var i = 0; i < this.length; i++) | |
{ | |
this[i] = 0; | |
} | |
} | |
else | |
{ | |
Array.call(this, arg.length); | |
this.length = arg.length; | |
for (var i = 0; i < this.length; i++) | |
{ | |
this[i] = arg[i]; | |
} | |
} | |
}; | |
window[type].prototype = proto; | |
window[type].constructor = window[type]; | |
}; | |
CheapArray('Uint32Array'); // jshint ignore:line | |
CheapArray('Int16Array'); // jshint ignore:line | |
} | |
/** | |
* Also fix for the absent console in IE9 | |
*/ | |
if (!window.console) | |
{ | |
window.console = {}; | |
window.console.log = window.console.assert = function(){}; | |
window.console.warn = window.console.assert = function(){}; | |
} | |
/** | |
* @author Richard Davey <[email protected]> | |
* @copyright 2016 Photon Storm Ltd. | |
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} | |
*/ | |
/** | |
* @class Phaser.Utils | |
* @static | |
*/ | |
Phaser.Utils = { | |
/** | |
* Takes the given string and reverses it, returning the reversed string. | |
* For example if given the string `Atari 520ST` it would return `TS025 iratA`. | |
* | |
* @method Phaser.Utils.reverseString | |
* @param {string} string - The string to be reversed. | |
* @return {string} The reversed string. | |
*/ | |
reverseString: function (string) { | |
return string.split('').reverse().join(''); | |
}, | |
/** | |
* Gets an objects property by string. | |
* | |
* @method Phaser.Utils.getProperty | |
* @param {object} obj - The object to traverse. | |
* @param {string} prop - The property whose value will be returned. | |
* @return {*} the value of the property or null if property isn't found . | |
*/ | |
getProperty: function(obj, prop) { | |
var parts = prop.split('.'), | |
last = parts.pop(), | |
l = parts.length, | |
i = 1, | |
current = parts[0]; | |
while (i < l && (obj = obj[current])) | |
{ | |
current = parts[i]; | |
i++; | |
} | |
if (obj) | |
{ | |
return obj[last]; | |
} | |
else | |
{ | |
return null; | |
} | |
}, | |
/** | |
* Sets an objects property by string. | |
* | |
* @method Phaser.Utils.setProperty | |
* @param {object} obj - The object to traverse | |
* @param {string} prop - The property whose value will be changed | |
* @return {object} The object on which the property was set. | |
*/ | |
setProperty: function(obj, prop, value) { | |
var parts = prop.split('.'), | |
last = parts.pop(), | |
l = parts.length, | |
i = 1, | |
current = parts[0]; | |
while (i < l && (obj = obj[current])) | |
{ | |
current = parts[i]; | |
i++; | |
} | |
if (obj) | |
{ | |
obj[last] = value; | |
} | |
return obj; | |
}, | |
/** | |
* Generate a random bool result based on the chance value. | |
* | |
* Returns true or false based on the chance value (default 50%). For example if you wanted a player to have a 30% chance | |
* of getting a bonus, call chanceRoll(30) - true means the chance passed, false means it failed. | |
* | |
* @method Phaser.Utils#chanceRoll | |
* @param {number} chance - The chance of receiving the value. A number between 0 and 100 (effectively 0% to 100%). | |
* @return {boolean} True if the roll passed, or false otherwise. | |
*/ | |
chanceRoll: function (chance) { | |
if (chance === undefined) { chance = 50; } | |
return chance > 0 && (Math.random() * 100 <= chance); | |
}, | |
/** | |
* Choose between one of two values randomly. | |
* | |
* @method Phaser.Utils#randomChoice | |
* @param {any} choice1 | |
* @param {any} choice2 | |
* @return {any} The randomly selected choice | |
*/ | |
randomChoice: function (choice1, choice2) { | |
return (Math.random() < 0.5) ? choice1 : choice2; | |
}, | |
/** | |
* Get a unit dimension from a string. | |
* | |
* @method Phaser.Utils.parseDimension | |
* @param {string|number} size - The size to parse. | |
* @param {number} dimension - The window dimension to check. | |
* @return {number} The parsed dimension. | |
*/ | |
parseDimension: function (size, dimension) { | |
var f = 0; | |
var px = 0; | |
if (typeof size === 'string') | |
{ | |
// %? | |
if (size.substr(-1) === '%') | |
{ | |
f = parseInt(size, 10) / 100; | |
if (dimension === 0) | |
{ | |
px = window.innerWidth * f; | |
} | |
else | |
{ | |
px = window.innerHeight * f; | |
} | |
} | |
else | |
{ | |
px = parseInt(size, 10); | |
} | |
} | |
else | |
{ | |
px = size; | |
} | |
return px; | |
}, | |
/** | |
* Takes the given string and pads it out, to the length required, using the character | |
* specified. For example if you need a string to be 6 characters long, you can call: | |
* | |
* `pad('bob', 6, '-', 2)` | |
* | |
* This would return: `bob---` as it has padded it out to 6 characters, using the `-` on the right. | |
* | |
* You can also use it to pad numbers (they are always returned as strings): | |
* | |
* `pad(512, 6, '0', 1)` | |
* | |
* Would return: `000512` with the string padded to the left. | |
* | |
* If you don't specify a direction it'll pad to both sides: | |
* | |
* `pad('c64', 7, '*')` | |
* | |
* Would return: `**c64**` | |
* | |
* @method Phaser.Utils.pad | |
* @param {string} str - The target string. `toString()` will be called on the string, which means you can also pass in common data types like numbers. | |
* @param {integer} [len=0] - The number of characters to be added. | |
* @param {string} [pad=" "] - The string to pad it out with (defaults to a space). | |
* @param {integer} [dir=3] - The direction dir = 1 (left), 2 (right), 3 (both). | |
* @return {string} The padded string. | |
*/ | |
pad: function (str, len, pad, dir) { | |
if (len === undefined) { var len = 0; } | |
if (pad === undefined) { var pad = ' '; } | |
if (dir === undefined) { var dir = 3; } | |
str = str.toString(); | |
var padlen = 0; | |
if (len + 1 >= str.length) | |
{ | |
switch (dir) | |
{ | |
case 1: | |
str = new Array(len + 1 - str.length).join(pad) + str; | |
break; | |
case 3: | |
var right = Math.ceil((padlen = len - str.length) / 2); | |
var left = padlen - right; | |
str = new Array(left+1).join(pad) + str + new Array(right+1).join(pad); | |
break; | |
default: | |
str = str + new Array(len + 1 - str.length).join(pad); | |
break; | |
} | |
} | |
return str; | |
}, | |
/** | |
* This is a slightly modified version of jQuery.isPlainObject. | |
* A plain object is an object whose internal class property is [object Object]. | |
* @method Phaser.Utils.isPlainObject | |
* @param {object} obj - The object to inspect. | |
* @return {boolean} - true if the object is plain, otherwise false. | |
*/ | |
isPlainObject: function (obj) { | |
// Not plain objects: | |
// - Any object or value whose internal [[Class]] property is not "[object Object]" | |
// - DOM nodes | |
// - window | |
if (typeof(obj) !== "object" || obj.nodeType || obj === obj.window) | |
{ | |
return false; | |
} | |
// Support: Firefox <20 | |
// The try/catch suppresses exceptions thrown when attempting to access | |
// the "constructor" property of certain host objects, ie. |window.location| | |
// https://bugzilla.mozilla.org/show_bug.cgi?id=814622 | |
try { | |
if (obj.constructor && !({}).hasOwnProperty.call(obj.constructor.prototype, "isPrototypeOf")) | |
{ | |
return false; | |
} | |
} catch (e) { | |
return false; | |
} | |
// If the function hasn't returned already, we're confident that | |
// |obj| is a plain object, created by {} or constructed with new Object | |
return true; | |
}, | |
/** | |
* This is a slightly modified version of http://api.jquery.com/jQuery.extend/ | |
* | |
* @method Phaser.Utils.extend | |
* @param {boolean} deep - Perform a deep copy? | |
* @param {object} target - The target object to copy to. | |
* @return {object} The extended object. | |
*/ | |
extend: function () { | |
var options, name, src, copy, copyIsArray, clone, | |
target = arguments[0] || {}, | |
i = 1, | |
length = arguments.length, | |
deep = false; | |
// Handle a deep copy situation | |
if (typeof target === "boolean") | |
{ | |
deep = target; | |
target = arguments[1] || {}; | |
// skip the boolean and the target | |
i = 2; | |
} | |
// extend Phaser if only one argument is passed | |
if (length === i) | |
{ | |
target = this; | |
--i; | |
} | |
for (; i < length; i++) | |
{ | |
// Only deal with non-null/undefined values | |
if ((options = arguments[i]) != null) | |
{ | |
// Extend the base object | |
for (name in options) | |
{ | |
src = target[name]; | |
copy = options[name]; | |
// Prevent never-ending loop | |
if (target === copy) | |
{ | |
continue; | |
} | |
// Recurse if we're merging plain objects or arrays | |
if (deep && copy && (Phaser.Utils.isPlainObject(copy) || (copyIsArray = Array.isArray(copy)))) | |
{ | |
if (copyIsArray) | |
{ | |
copyIsArray = false; | |
clone = src && Array.isArray(src) ? src : []; | |
} | |
else | |
{ | |
clone = src && Phaser.Utils.isPlainObject(src) ? src : {}; | |
} | |
// Never move original objects, clone them | |
target[name] = Phaser.Utils.extend(deep, clone, copy); | |
// Don't bring in undefined values | |
} | |
else if (copy !== undefined) | |
{ | |
target[name] = copy; | |
} | |
} | |
} | |
} | |
// Return the modified object | |
return target; | |
}, | |
/** | |
* Mixes in an existing mixin object with the target. | |
* | |
* Values in the mixin that have either `get` or `set` functions are created as properties via `defineProperty` | |
* _except_ if they also define a `clone` method - if a clone method is defined that is called instead and | |
* the result is assigned directly. | |
* | |
* @method Phaser.Utils.mixinPrototype | |
* @param {object} target - The target object to receive the new functions. | |
* @param {object} mixin - The object to copy the functions from. | |
* @param {boolean} [replace=false] - If the target object already has a matching function should it be overwritten or not? | |
*/ | |
mixinPrototype: function (target, mixin, replace) { | |
if (replace === undefined) { replace = false; } | |
var mixinKeys = Object.keys(mixin); | |
for (var i = 0; i < mixinKeys.length; i++) | |
{ | |
var key = mixinKeys[i]; | |
var value = mixin[key]; | |
if (!replace && (key in target)) | |
{ | |
// Not overwriting existing property | |
continue; | |
} | |
else | |
{ | |
if (value && | |
(typeof value.get === 'function' || typeof value.set === 'function')) | |
{ | |
// Special case for classes like Phaser.Point which has a 'set' function! | |
if (typeof value.clone === 'function') | |
{ | |
target[key] = value.clone(); | |
} | |
else | |
{ | |
Object.defineProperty(target, key, value); | |
} | |
} | |
else | |
{ | |
target[key] = value; | |
} | |
} | |
} | |
}, | |
/** | |
* Mixes the source object into the destination object, returning the newly modified destination object. | |
* Based on original code by @mudcube | |
* | |
* @method Phaser.Utils.mixin | |
* @param {object} from - The object to copy (the source object). | |
* @param {object} to - The object to copy to (the destination object). | |
* @return {object} The modified destination object. | |
*/ | |
mixin: function (from, to) { | |
if (!from || typeof (from) !== "object") | |
{ | |
return to; | |
} | |
for (var key in from) | |
{ | |
var o = from[key]; | |
if (o.childNodes || o.cloneNode) | |
{ | |
continue; | |
} | |
var type = typeof (from[key]); | |
if (!from[key] || type !== "object") | |
{ | |
to[key] = from[key]; | |
} | |
else | |
{ | |
// Clone sub-object | |
if (typeof (to[key]) === type) | |
{ | |
to[key] = Phaser.Utils.mixin(from[key], to[key]); | |
} | |
else | |
{ | |
to[key] = Phaser.Utils.mixin(from[key], new o.constructor()); | |
} | |
} | |
} | |
return to; | |
} | |
}; | |
/** | |
* @author Richard Davey <[email protected]> | |
* @copyright 2016 Photon Storm Ltd. | |
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} | |
*/ | |
/** | |
* Creates a new Circle object with the center coordinate specified by the x and y parameters and the diameter specified by the diameter parameter. | |
* If you call this function without parameters, a circle with x, y, diameter and radius properties set to 0 is created. | |
* | |
* @class Phaser.Circle | |
* @constructor | |
* @param {number} [x=0] - The x coordinate of the center of the circle. | |
* @param {number} [y=0] - The y coordinate of the center of the circle. | |
* @param {number} [diameter=0] - The diameter of the circle. | |
*/ | |
Phaser.Circle = function (x, y, diameter) { | |
x = x || 0; | |
y = y || 0; | |
diameter = diameter || 0; | |
/** | |
* @property {number} x - The x coordinate of the center of the circle. | |
*/ | |
this.x = x; | |
/** | |
* @property {number} y - The y coordinate of the center of the circle. | |
*/ | |
this.y = y; | |
/** | |
* @property {number} _diameter - The diameter of the circle. | |
* @private | |
*/ | |
this._diameter = diameter; | |
/** | |
* @property {number} _radius - The radius of the circle. | |
* @private | |
*/ | |
this._radius = 0; | |
if (diameter > 0) | |
{ | |
this._radius = diameter * 0.5; | |
} | |
/** | |
* @property {number} type - The const type of this object. | |
* @readonly | |
*/ | |
this.type = Phaser.CIRCLE; | |
}; | |
Phaser.Circle.prototype = { | |
/** | |
* The circumference of the circle. | |
* | |
* @method Phaser.Circle#circumference | |
* @return {number} The circumference of the circle. | |
*/ | |
circumference: function () { | |
return 2 * (Math.PI * this._radius); | |
}, | |
/** | |
* Returns a uniformly distributed random point from anywhere within this Circle. | |
* | |
* @method Phaser.Circle#random | |
* @param {Phaser.Point|object} [out] - A Phaser.Point, or any object with public x/y properties, that the values will be set in. | |
* If no object is provided a new Phaser.Point object will be created. In high performance areas avoid this by re-using an existing object. | |
* @return {Phaser.Point} An object containing the random point in its `x` and `y` properties. | |
*/ | |
random: function (out) { | |
if (out === undefined) { out = new Phaser.Point(); } | |
var t = 2 * Math.PI * Math.random(); | |
var u = Math.random() + Math.random(); | |
var r = (u > 1) ? 2 - u : u; | |
var x = r * Math.cos(t); | |
var y = r * Math.sin(t); | |
out.x = this.x + (x * this.radius); | |
out.y = this.y + (y * this.radius); | |
return out; | |
}, | |
/** | |
* Returns the framing rectangle of the circle as a Phaser.Rectangle object. | |
* | |
* @method Phaser.Circle#getBounds | |
* @return {Phaser.Rectangle} The bounds of the Circle. | |
*/ | |
getBounds: function () { | |
return new Phaser.Rectangle(this.x - this.radius, this.y - this.radius, this.diameter, this.diameter); | |
}, | |
/** | |
* Sets the members of Circle to the specified values. | |
* @method Phaser.Circle#setTo | |
* @param {number} x - The x coordinate of the center of the circle. | |
* @param {number} y - The y coordinate of the center of the circle. | |
* @param {number} diameter - The diameter of the circle. | |
* @return {Circle} This circle object. | |
*/ | |
setTo: function (x, y, diameter) { | |
this.x = x; | |
this.y = y; | |
this._diameter = diameter; | |
this._radius = diameter * 0.5; | |
return this; | |
}, | |
/** | |
* Copies the x, y and diameter properties from any given object to this Circle. | |
* @method Phaser.Circle#copyFrom | |
* @param {any} source - The object to copy from. | |
* @return {Circle} This Circle object. | |
*/ | |
copyFrom: function (source) { | |
return this.setTo(source.x, source.y, source.diameter); | |
}, | |
/** | |
* Copies the x, y and diameter properties from this Circle to any given object. | |
* @method Phaser.Circle#copyTo | |
* @param {any} dest - The object to copy to. | |
* @return {object} This dest object. | |
*/ | |
copyTo: function (dest) { | |
dest.x = this.x; | |
dest.y = this.y; | |
dest.diameter = this._diameter; | |
return dest; | |
}, | |
/** | |
* Returns the distance from the center of the Circle object to the given object | |
* (can be Circle, Point or anything with x/y properties) | |
* @method Phaser.Circle#distance | |
* @param {object} dest - The target object. Must have visible x and y properties that represent the center of the object. | |
* @param {boolean} [round=false] - Round the distance to the nearest integer. | |
* @return {number} The distance between this Point object and the destination Point object. | |
*/ | |
distance: function (dest, round) { | |
var distance = Phaser.Math.distance(this.x, this.y, dest.x, dest.y); | |
return round ? Math.round(distance) : distance; | |
}, | |
/** | |
* Returns a new Circle object with the same values for the x, y, width, and height properties as this Circle object. | |
* @method Phaser.Circle#clone | |
* @param {Phaser.Circle} output - Optional Circle object. If given the values will be set into the object, otherwise a brand new Circle object will be created and returned. | |
* @return {Phaser.Circle} The cloned Circle object. | |
*/ | |
clone: function (output) { | |
if (output === undefined || output === null) | |
{ | |
output = new Phaser.Circle(this.x, this.y, this.diameter); | |
} | |
else | |
{ | |
output.setTo(this.x, this.y, this.diameter); | |
} | |
return output; | |
}, | |
/** | |
* Return true if the given x/y coordinates are within this Circle object. | |
* @method Phaser.Circle#contains | |
* @param {number} x - The X value of the coordinate to test. | |
* @param {number} y - The Y value of the coordinate to test. | |
* @return {boolean} True if the coordinates are within this circle, otherwise false. | |
*/ | |
contains: function (x, y) { | |
return Phaser.Circle.contains(this, x, y); | |
}, | |
/** | |
* Returns a Point object containing the coordinates of a point on the circumference of the Circle based on the given angle. | |
* @method Phaser.Circle#circumferencePoint | |
* @param {number} angle - The angle in radians (unless asDegrees is true) to return the point from. | |
* @param {boolean} [asDegrees=false] - Is the given angle in radians (false) or degrees (true)? | |
* @param {Phaser.Point} [out] - An optional Point object to put the result in to. If none specified a new Point object will be created. | |
* @return {Phaser.Point} The Point object holding the result. | |
*/ | |
circumferencePoint: function (angle, asDegrees, out) { | |
return Phaser.Circle.circumferencePoint(this, angle, asDegrees, out); | |
}, | |
/** | |
* Adjusts the location of the Circle object, as determined by its center coordinate, by the specified amounts. | |
* @method Phaser.Circle#offset | |
* @param {number} dx - Moves the x value of the Circle object by this amount. | |
* @param {number} dy - Moves the y value of the Circle object by this amount. | |
* @return {Circle} This Circle object. | |
*/ | |
offset: function (dx, dy) { | |
this.x += dx; | |
this.y += dy; | |
return this; | |
}, | |
/** | |
* Adjusts the location of the Circle object using a Point object as a parameter. This method is similar to the Circle.offset() method, except that it takes a Point object as a parameter. | |
* @method Phaser.Circle#offsetPoint | |
* @param {Point} point A Point object to use to offset this Circle object (or any valid object with exposed x and y properties). | |
* @return {Circle} This Circle object. | |
*/ | |
offsetPoint: function (point) { | |
return this.offset(point.x, point.y); | |
}, | |
/** | |
* Returns a string representation of this object. | |
* @method Phaser.Circle#toString | |
* @return {string} a string representation of the instance. | |
*/ | |
toString: function () { | |
return "[{Phaser.Circle (x=" + this.x + " y=" + this.y + " diameter=" + this.diameter + " radius=" + this.radius + ")}]"; | |
} | |
}; | |
Phaser.Circle.prototype.constructor = Phaser.Circle; | |
/** | |
* The largest distance between any two points on the circle. The same as the radius * 2. | |
* | |
* @name Phaser.Circle#diameter | |
* @property {number} diameter - Gets or sets the diameter of the circle. | |
*/ | |
Object.defineProperty(Phaser.Circle.prototype, "diameter", { | |
get: function () { | |
return this._diameter; | |
}, | |
set: function (value) { | |
if (value > 0) | |
{ | |
this._diameter = value; | |
this._radius = value * 0.5; | |
} | |
} | |
}); | |
/** | |
* The length of a line extending from the center of the circle to any point on the circle itself. The same as half the diameter. | |
* @name Phaser.Circle#radius | |
* @property {number} radius - Gets or sets the radius of the circle. | |
*/ | |
Object.defineProperty(Phaser.Circle.prototype, "radius", { | |
get: function () { | |
return this._radius; | |
}, | |
set: function (value) { | |
if (value > 0) | |
{ | |
this._radius = value; | |
this._diameter = value * 2; | |
} | |
} | |
}); | |
/** | |
* The x coordinate of the leftmost point of the circle. Changing the left property of a Circle object has no effect on the x and y properties. However it does affect the diameter, whereas changing the x value does not affect the diameter property. | |
* @name Phaser.Circle#left | |
* @propety {number} left - Gets or sets the value of the leftmost point of the circle. | |
*/ | |
Object.defineProperty(Phaser.Circle.prototype, "left", { | |
get: function () { | |
return this.x - this._radius; | |
}, | |
set: function (value) { | |
if (value > this.x) | |
{ | |
this._radius = 0; | |
this._diameter = 0; | |
} | |
else | |
{ | |
this.radius = this.x - value; | |
} | |
} | |
}); | |
/** | |
* The x coordinate of the rightmost point of the circle. Changing the right property of a Circle object has no effect on the x and y properties. However it does affect the diameter, whereas changing the x value does not affect the diameter property. | |
* @name Phaser.Circle#right | |
* @property {number} right - Gets or sets the value of the rightmost point of the circle. | |
*/ | |
Object.defineProperty(Phaser.Circle.prototype, "right", { | |
get: function () { | |
return this.x + this._radius; | |
}, | |
set: function (value) { | |
if (value < this.x) | |
{ | |
this._radius = 0; | |
this._diameter = 0; | |
} | |
else | |
{ | |
this.radius = value - this.x; | |
} | |
} | |
}); | |
/** | |
* The sum of the y minus the radius property. Changing the top property of a Circle object has no effect on the x and y properties, but does change the diameter. | |
* @name Phaser.Circle#top | |
* @property {number} top - Gets or sets the top of the circle. | |
*/ | |
Object.defineProperty(Phaser.Circle.prototype, "top", { | |
get: function () { | |
return this.y - this._radius; | |
}, | |
set: function (value) { | |
if (value > this.y) | |
{ | |
this._radius = 0; | |
this._diameter = 0; | |
} | |
else | |
{ | |
this.radius = this.y - value; | |
} | |
} | |
}); | |
/** | |
* The sum of the y and radius properties. Changing the bottom property of a Circle object has no effect on the x and y properties, but does change the diameter. | |
* @name Phaser.Circle#bottom | |
* @property {number} bottom - Gets or sets the bottom of the circle. | |
*/ | |
Object.defineProperty(Phaser.Circle.prototype, "bottom", { | |
get: function () { | |
return this.y + this._radius; | |
}, | |
set: function (value) { | |
if (value < this.y) | |
{ | |
this._radius = 0; | |
this._diameter = 0; | |
} | |
else | |
{ | |
this.radius = value - this.y; | |
} | |
} | |
}); | |
/** | |
* The area of this Circle. | |
* @name Phaser.Circle#area | |
* @property {number} area - The area of this circle. | |
* @readonly | |
*/ | |
Object.defineProperty(Phaser.Circle.prototype, "area", { | |
get: function () { | |
if (this._radius > 0) | |
{ | |
return Math.PI * this._radius * this._radius; | |
} | |
else | |
{ | |
return 0; | |
} | |
} | |
}); | |
/** | |
* Determines whether or not this Circle object is empty. Will return a value of true if the Circle objects diameter is less than or equal to 0; otherwise false. | |
* If set to true it will reset all of the Circle objects properties to 0. A Circle object is empty if its diameter is less than or equal to 0. | |
* @name Phaser.Circle#empty | |
* @property {boolean} empty - Gets or sets the empty state of the circle. | |
*/ | |
Object.defineProperty(Phaser.Circle.prototype, "empty", { | |
get: function () { | |
return (this._diameter === 0); | |
}, | |
set: function (value) { | |
if (value === true) | |
{ | |
this.setTo(0, 0, 0); | |
} | |
} | |
}); | |
/** | |
* Return true if the given x/y coordinates are within the Circle object. | |
* @method Phaser.Circle.contains | |
* @param {Phaser.Circle} a - The Circle to be checked. | |
* @param {number} x - The X value of the coordinate to test. | |
* @param {number} y - The Y value of the coordinate to test. | |
* @return {boolean} True if the coordinates are within this circle, otherwise false. | |
*/ | |
Phaser.Circle.contains = function (a, x, y) { | |
// Check if x/y are within the bounds first | |
if (a.radius > 0 && x >= a.left && x <= a.right && y >= a.top && y <= a.bottom) | |
{ | |
var dx = (a.x - x) * (a.x - x); | |
var dy = (a.y - y) * (a.y - y); | |
return (dx + dy) <= (a.radius * a.radius); | |
} | |
else | |
{ | |
return false; | |
} | |
}; | |
/** | |
* Determines whether the two Circle objects match. This method compares the x, y and diameter properties. | |
* @method Phaser.Circle.equals | |
* @param {Phaser.Circle} a - The first Circle object. | |
* @param {Phaser.Circle} b - The second Circle object. | |
* @return {boolean} A value of true if the object has exactly the same values for the x, y and diameter properties as this Circle object; otherwise false. | |
*/ | |
Phaser.Circle.equals = function (a, b) { | |
return (a.x === b.x && a.y === b.y && a.diameter === b.diameter); | |
}; | |
/** | |
* Determines whether the two Circle objects intersect. | |
* This method checks the radius distances between the two Circle objects to see if they intersect. | |
* @method Phaser.Circle.intersects | |
* @param {Phaser.Circle} a - The first Circle object. | |
* @param {Phaser.Circle} b - The second Circle object. | |
* @return {boolean} A value of true if the specified object intersects with this Circle object; otherwise false. | |
*/ | |
Phaser.Circle.intersects = function (a, b) { | |
return (Phaser.Math.distance(a.x, a.y, b.x, b.y) <= (a.radius + b.radius)); | |
}; | |
/** | |
* Returns a Point object containing the coordinates of a point on the circumference of the Circle based on the given angle. | |
* @method Phaser.Circle.circumferencePoint | |
* @param {Phaser.Circle} a - The first Circle object. | |
* @param {number} angle - The angle in radians (unless asDegrees is true) to return the point from. | |
* @param {boolean} [asDegrees=false] - Is the given angle in radians (false) or degrees (true)? | |
* @param {Phaser.Point} [out] - An optional Point object to put the result in to. If none specified a new Point object will be created. | |
* @return {Phaser.Point} The Point object holding the result. | |
*/ | |
Phaser.Circle.circumferencePoint = function (a, angle, asDegrees, out) { | |
if (asDegrees === undefined) { asDegrees = false; } | |
if (out === undefined) { out = new Phaser.Point(); } | |
if (asDegrees === true) | |
{ | |
angle = Phaser.Math.degToRad(angle); | |
} | |
out.x = a.x + a.radius * Math.cos(angle); | |
out.y = a.y + a.radius * Math.sin(angle); | |
return out; | |
}; | |
/** | |
* Checks if the given Circle and Rectangle objects intersect. | |
* @method Phaser.Circle.intersectsRectangle | |
* @param {Phaser.Circle} c - The Circle object to test. | |
* @param {Phaser.Rectangle} r - The Rectangle object to test. | |
* @return {boolean} True if the two objects intersect, otherwise false. | |
*/ | |
Phaser.Circle.intersectsRectangle = function (c, r) { | |
var cx = Math.abs(c.x - r.x - r.halfWidth); | |
var xDist = r.halfWidth + c.radius; | |
if (cx > xDist) | |
{ | |
return false; | |
} | |
var cy = Math.abs(c.y - r.y - r.halfHeight); | |
var yDist = r.halfHeight + c.radius; | |
if (cy > yDist) | |
{ | |
return false; | |
} | |
if (cx <= r.halfWidth || cy <= r.halfHeight) | |
{ | |
return true; | |
} | |
var xCornerDist = cx - r.halfWidth; | |
var yCornerDist = cy - r.halfHeight; | |
var xCornerDistSq = xCornerDist * xCornerDist; | |
var yCornerDistSq = yCornerDist * yCornerDist; | |
var maxCornerDistSq = c.radius * c.radius; | |
return xCornerDistSq + yCornerDistSq <= maxCornerDistSq; | |
}; | |
// Because PIXI uses its own Circle, we'll replace it with ours to avoid duplicating code or confusion. | |
PIXI.Circle = Phaser.Circle; | |
/** | |
* @author Richard Davey <[email protected]> | |
* @author Chad Engler <[email protected]> | |
* @copyright 2016 Photon Storm Ltd. | |
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} | |
*/ | |
/** | |
* Creates a Ellipse object. A curve on a plane surrounding two focal points. | |
* | |
* @class Phaser.Ellipse | |
* @constructor | |
* @param {number} [x=0] - The X coordinate of the upper-left corner of the framing rectangle of this ellipse. | |
* @param {number} [y=0] - The Y coordinate of the upper-left corner of the framing rectangle of this ellipse. | |
* @param {number} [width=0] - The overall width of this ellipse. | |
* @param {number} [height=0] - The overall height of this ellipse. | |
*/ | |
Phaser.Ellipse = function (x, y, width, height) { | |
x = x || 0; | |
y = y || 0; | |
width = width || 0; | |
height = height || 0; | |
/** | |
* @property {number} x - The X coordinate of the upper-left corner of the framing rectangle of this ellipse. | |
*/ | |
this.x = x; | |
/** | |
* @property {number} y - The Y coordinate of the upper-left corner of the framing rectangle of this ellipse. | |
*/ | |
this.y = y; | |
/** | |
* @property {number} width - The overall width of this ellipse. | |
*/ | |
this.width = width; | |
/** | |
* @property {number} height - The overall height of this ellipse. | |
*/ | |
this.height = height; | |
/** | |
* @property {number} type - The const type of this object. | |
* @readonly | |
*/ | |
this.type = Phaser.ELLIPSE; | |
}; | |
Phaser.Ellipse.prototype = { | |
/** | |
* Sets the members of the Ellipse to the specified values. | |
* @method Phaser.Ellipse#setTo | |
* @param {number} x - The X coordinate of the upper-left corner of the framing rectangle of this ellipse. | |
* @param {number} y - The Y coordinate of the upper-left corner of the framing rectangle of this ellipse. | |
* @param {number} width - The overall width of this ellipse. | |
* @param {number} height - The overall height of this ellipse. | |
* @return {Phaser.Ellipse} This Ellipse object. | |
*/ | |
setTo: function (x, y, width, height) { | |
this.x = x; | |
this.y = y; | |
this.width = width; | |
this.height = height; | |
return this; | |
}, | |
/** | |
* Returns the framing rectangle of the ellipse as a Phaser.Rectangle object. | |
* | |
* @method Phaser.Ellipse#getBounds | |
* @return {Phaser.Rectangle} The bounds of the Ellipse. | |
*/ | |
getBounds: function () { | |
return new Phaser.Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); | |
}, | |
/** | |
* Copies the x, y, width and height properties from any given object to this Ellipse. | |
* | |
* @method Phaser.Ellipse#copyFrom | |
* @param {any} source - The object to copy from. | |
* @return {Phaser.Ellipse} This Ellipse object. | |
*/ | |
copyFrom: function (source) { | |
return this.setTo(source.x, source.y, source.width, source.height); | |
}, | |
/** | |
* Copies the x, y, width and height properties from this Ellipse to any given object. | |
* @method Phaser.Ellipse#copyTo | |
* @param {any} dest - The object to copy to. | |
* @return {object} This dest object. | |
*/ | |
copyTo: function(dest) { | |
dest.x = this.x; | |
dest.y = this.y; | |
dest.width = this.width; | |
dest.height = this.height; | |
return dest; | |
}, | |
/** | |
* Returns a new Ellipse object with the same values for the x, y, width, and height properties as this Ellipse object. | |
* @method Phaser.Ellipse#clone | |
* @param {Phaser.Ellipse} output - Optional Ellipse object. If given the values will be set into the object, otherwise a brand new Ellipse object will be created and returned. | |
* @return {Phaser.Ellipse} The cloned Ellipse object. | |
*/ | |
clone: function(output) { | |
if (output === undefined || output === null) | |
{ | |
output = new Phaser.Ellipse(this.x, this.y, this.width, this.height); | |
} | |
else | |
{ | |
output.setTo(this.x, this.y, this.width, this.height); | |
} | |
return output; | |
}, | |
/** | |
* Return true if the given x/y coordinates are within this Ellipse object. | |
* | |
* @method Phaser.Ellipse#contains | |
* @param {number} x - The X value of the coordinate to test. | |
* @param {number} y - The Y value of the coordinate to test. | |
* @return {boolean} True if the coordinates are within this ellipse, otherwise false. | |
*/ | |
contains: function (x, y) { | |
return Phaser.Ellipse.contains(this, x, y); | |
}, | |
/** | |
* Returns a uniformly distributed random point from anywhere within this Ellipse. | |
* | |
* @method Phaser.Ellipse#random | |
* @param {Phaser.Point|object} [out] - A Phaser.Point, or any object with public x/y properties, that the values will be set in. | |
* If no object is provided a new Phaser.Point object will be created. In high performance areas avoid this by re-using an existing object. | |
* @return {Phaser.Point} An object containing the random point in its `x` and `y` properties. | |
*/ | |
random: function (out) { | |
if (out === undefined) { out = new Phaser.Point(); } | |
var p = Math.random() * Math.PI * 2; | |
var r = Math.random(); | |
out.x = Math.sqrt(r) * Math.cos(p); | |
out.y = Math.sqrt(r) * Math.sin(p); | |
out.x = this.x + (out.x * this.width / 2.0); | |
out.y = this.y + (out.y * this.height / 2.0); | |
return out; | |
}, | |
/** | |
* Returns a string representation of this object. | |
* @method Phaser.Ellipse#toString | |
* @return {string} A string representation of the instance. | |
*/ | |
toString: function () { | |
return "[{Phaser.Ellipse (x=" + this.x + " y=" + this.y + " width=" + this.width + " height=" + this.height + ")}]"; | |
} | |
}; | |
Phaser.Ellipse.prototype.constructor = Phaser.Ellipse; | |
/** | |
* The left coordinate of the Ellipse. The same as the X coordinate. | |
* @name Phaser.Ellipse#left | |
* @propety {number} left - Gets or sets the value of the leftmost point of the ellipse. | |
*/ | |
Object.defineProperty(Phaser.Ellipse.prototype, "left", { | |
get: function () { | |
return this.x; | |
}, | |
set: function (value) { | |
this.x = value; | |
} | |
}); | |
/** | |
* The x coordinate of the rightmost point of the Ellipse. Changing the right property of an Ellipse object has no effect on the x property, but does adjust the width. | |
* @name Phaser.Ellipse#right | |
* @property {number} right - Gets or sets the value of the rightmost point of the ellipse. | |
*/ | |
Object.defineProperty(Phaser.Ellipse.prototype, "right", { | |
get: function () { | |
return this.x + this.width; | |
}, | |
set: function (value) { | |
if (value < this.x) | |
{ | |
this.width = 0; | |
} | |
else | |
{ | |
this.width = value - this.x; | |
} | |
} | |
}); | |
/** | |
* The top of the Ellipse. The same as its y property. | |
* @name Phaser.Ellipse#top | |
* @property {number} top - Gets or sets the top of the ellipse. | |
*/ | |
Object.defineProperty(Phaser.Ellipse.prototype, "top", { | |
get: function () { | |
return this.y; | |
}, | |
set: function (value) { | |
this.y = value; | |
} | |
}); | |
/** | |
* The sum of the y and height properties. Changing the bottom property of an Ellipse doesn't adjust the y property, but does change the height. | |
* @name Phaser.Ellipse#bottom | |
* @property {number} bottom - Gets or sets the bottom of the ellipse. | |
*/ | |
Object.defineProperty(Phaser.Ellipse.prototype, "bottom", { | |
get: function () { | |
return this.y + this.height; | |
}, | |
set: function (value) { | |
if (value < this.y) | |
{ | |
this.height = 0; | |
} | |
else | |
{ | |
this.height = value - this.y; | |
} | |
} | |
}); | |
/** | |
* Determines whether or not this Ellipse object is empty. Will return a value of true if the Ellipse objects dimensions are less than or equal to 0; otherwise false. | |
* If set to true it will reset all of the Ellipse objects properties to 0. An Ellipse object is empty if its width or height is less than or equal to 0. | |
* @name Phaser.Ellipse#empty | |
* @property {boolean} empty - Gets or sets the empty state of the ellipse. | |
*/ | |
Object.defineProperty(Phaser.Ellipse.prototype, "empty", { | |
get: function () { | |
return (this.width === 0 || this.height === 0); | |
}, | |
set: function (value) { | |
if (value === true) | |
{ | |
this.setTo(0, 0, 0, 0); | |
} | |
} | |
}); | |
/** | |
* Return true if the given x/y coordinates are within the Ellipse object. | |
* | |
* @method Phaser.Ellipse.contains | |
* @param {Phaser.Ellipse} a - The Ellipse to be checked. | |
* @param {number} x - The X value of the coordinate to test. | |
* @param {number} y - The Y value of the coordinate to test. | |
* @return {boolean} True if the coordinates are within this ellipse, otherwise false. | |
*/ | |
Phaser.Ellipse.contains = function (a, x, y) { | |
if (a.width <= 0 || a.height <= 0) { | |
return false; | |
} | |
// Normalize the coords to an ellipse with center 0,0 and a radius of 0.5 | |
var normx = ((x - a.x) / a.width) - 0.5; | |
var normy = ((y - a.y) / a.height) - 0.5; | |
normx *= normx; | |
normy *= normy; | |
return (normx + normy < 0.25); | |
}; | |
// Because PIXI uses its own Ellipse, we'll replace it with ours to avoid duplicating code or confusion. | |
PIXI.Ellipse = Phaser.Ellipse; | |
/** | |
* @author Richard Davey <[email protected]> | |
* @copyright 2016 Photon Storm Ltd. | |
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} | |
*/ | |
/** | |
* Creates a new Line object with a start and an end point. | |
* | |
* @class Phaser.Line | |
* @constructor | |
* @param {number} [x1=0] - The x coordinate of the start of the line. | |
* @param {number} [y1=0] - The y coordinate of the start of the line. | |
* @param {number} [x2=0] - The x coordinate of the end of the line. | |
* @param {number} [y2=0] - The y coordinate of the end of the line. | |
*/ | |
Phaser.Line = function (x1, y1, x2, y2) { | |
x1 = x1 || 0; | |
y1 = y1 || 0; | |
x2 = x2 || 0; | |
y2 = y2 || 0; | |
/** | |
* @property {Phaser.Point} start - The start point of the line. | |
*/ | |
this.start = new Phaser.Point(x1, y1); | |
/** | |
* @property {Phaser.Point} end - The end point of the line. | |
*/ | |
this.end = new Phaser.Point(x2, y2); | |
/** | |
* @property {number} type - The const type of this object. | |
* @readonly | |
*/ | |
this.type = Phaser.LINE; | |
}; | |
Phaser.Line.prototype = { | |
/** | |
* Sets the components of the Line to the specified values. | |
* | |
* @method Phaser.Line#setTo | |
* @param {number} [x1=0] - The x coordinate of the start of the line. | |
* @param {number} [y1=0] - The y coordinate of the start of the line. | |
* @param {number} [x2=0] - The x coordinate of the end of the line. | |
* @param {number} [y2=0] - The y coordinate of the end of the line. | |
* @return {Phaser.Line} This line object | |
*/ | |
setTo: function (x1, y1, x2, y2) { | |
this.start.setTo(x1, y1); | |
this.end.setTo(x2, y2); | |
return this; | |
}, | |
/** | |
* Sets the line to match the x/y coordinates of the two given sprites. | |
* Can optionally be calculated from their center coordinates. | |
* | |
* @method Phaser.Line#fromSprite | |
* @param {Phaser.Sprite} startSprite - The coordinates of this Sprite will be set to the Line.start point. | |
* @param {Phaser.Sprite} endSprite - The coordinates of this Sprite will be set to the Line.start point. | |
* @param {boolean} [useCenter=false] - If true it will use startSprite.center.x, if false startSprite.x. Note that Sprites don't have a center property by default, so only enable if you've over-ridden your Sprite with a custom class. | |
* @return {Phaser.Line} This line object | |
*/ | |
fromSprite: function (startSprite, endSprite, useCenter) { | |
if (useCenter === undefined) { useCenter = false; } | |
if (useCenter) | |
{ | |
return this.setTo(startSprite.center.x, startSprite.center.y, endSprite.center.x, endSprite.center.y); | |
} | |
return this.setTo(startSprite.x, startSprite.y, endSprite.x, endSprite.y); | |
}, | |
/** | |
* Sets this line to start at the given `x` and `y` coordinates and for the segment to extend at `angle` for the given `length`. | |
* | |
* @method Phaser.Line#fromAngle | |
* @param {number} x - The x coordinate of the start of the line. | |
* @param {number} y - The y coordinate of the start of the line. | |
* @param {number} angle - The angle of the line in radians. | |
* @param {number} length - The length of the line in pixels. | |
* @return {Phaser.Line} This line object | |
*/ | |
fromAngle: function (x, y, angle, length) { | |
this.start.setTo(x, y); | |
this.end.setTo(x + (Math.cos(angle) * length), y + (Math.sin(angle) * length)); | |
return this; | |
}, | |
/** | |
* Rotates the line by the amount specified in `angle`. | |
* | |
* Rotation takes place from the center of the line. | |
* If you wish to rotate around a different point see Line.rotateAround. | |
* | |
* If you wish to rotate the ends of the Line then see Line.start.rotate or Line.end.rotate. | |
* | |
* @method Phaser.Line#rotate | |
* @param {number} angle - The angle in radians (unless asDegrees is true) to rotate the line by. | |
* @param {boolean} [asDegrees=false] - Is the given angle in radians (false) or degrees (true)? | |
* @return {Phaser.Line} This line object | |
*/ | |
rotate: function (angle, asDegrees) { | |
var cx = (this.start.x + this.end.x) / 2; | |
var cy = (this.start.y + this.end.y) / 2; | |
this.start.rotate(cx, cy, angle, asDegrees); | |
this.end.rotate(cx, cy, angle, asDegrees); | |
return this; | |
}, | |
/** | |
* Rotates the line by the amount specified in `angle`. | |
* | |
* Rotation takes place around the coordinates given. | |
* | |
* @method Phaser.Line#rotateAround | |
* @param {number} x - The x coordinate to offset the rotation from. | |
* @param {number} y - The y coordinate to offset the rotation from. | |
* @param {number} angle - The angle in radians (unless asDegrees is true) to rotate the line by. | |
* @param {boolean} [asDegrees=false] - Is the given angle in radians (false) or degrees (true)? | |
* @return {Phaser.Line} This line object | |
*/ | |
rotateAround: function (x, y, angle, asDegrees) { | |
this.start.rotate(x, y, angle, asDegrees); | |
this.end.rotate(x, y, angle, asDegrees); | |
return this; | |
}, | |
/** | |
* Checks for intersection between this line and another Line. | |
* If asSegment is true it will check for segment intersection. If asSegment is false it will check for line intersection. | |
* Returns the intersection segment of AB and EF as a Point, or null if there is no intersection. | |
* | |
* @method Phaser.Line#intersects | |
* @param {Phaser.Line} line - The line to check against this one. | |
* @param {boolean} [asSegment=true] - If true it will check for segment intersection, otherwise full line intersection. | |
* @param {Phaser.Point} [result] - A Point object to store the result in, if not given a new one will be created. | |
* @return {Phaser.Point} The intersection segment of the two lines as a Point, or null if there is no intersection. | |
*/ | |
intersects: function (line, asSegment, result) { | |
return Phaser.Line.intersectsPoints(this.start, this.end, line.start, line.end, asSegment, result); | |
}, | |
/** | |
* Returns the reflected angle between two lines. | |
* This is the outgoing angle based on the angle of this line and the normalAngle of the given line. | |
* | |
* @method Phaser.Line#reflect | |
* @param {Phaser.Line} line - The line to reflect off this line. | |
* @return {number} The reflected angle in radians. | |
*/ | |
reflect: function (line) { | |
return Phaser.Line.reflect(this, line); | |
}, | |
/** | |
* Returns a Point object where the x and y values correspond to the center (or midpoint) of the Line segment. | |
* | |
* @method Phaser.Line#midPoint | |
* @param {Phaser.Point} [out] - A Phaser.Point object into which the result will be populated. If not given a new Point object is created. | |
* @return {Phaser.Point} A Phaser.Point object with the x and y values set to the center of the line segment. | |
*/ | |
midPoint: function (out) { | |
if (out === undefined) { out = new Phaser.Point(); } | |
out.x = (this.start.x + this.end.x) / 2; | |
out.y = (this.start.y + this.end.y) / 2; | |
return out; | |
}, | |
/** | |
* Centers this Line on the given coordinates. | |
* | |
* The line is centered by positioning the start and end points so that the lines midpoint matches | |
* the coordinates given. | |
* | |
* @method Phaser.Line#centerOn | |
* @param {number} x - The x position to center the line on. | |
* @param {number} y - The y position to center the line on. | |
* @return {Phaser.Line} This line object | |
*/ | |
centerOn: function (x, y) { | |
var cx = (this.start.x + this.end.x) / 2; | |
var cy = (this.start.y + this.end.y) / 2; | |
var tx = x - cx; | |
var ty = y - cy; | |
this.start.add(tx, ty); | |
this.end.add(tx, ty); | |
}, | |
/** | |
* Tests if the given coordinates fall on this line. See pointOnSegment to test against just the line segment. | |
* | |
* @method Phaser.Line#pointOnLine | |
* @param {number} x - The line to check against this one. | |
* @param {number} y - The line to check against this one. | |
* @return {boolean} True if the point is on the line, false if not. | |
*/ | |
pointOnLine: function (x, y) { | |
return ((x - this.start.x) * (this.end.y - this.start.y) === (this.end.x - this.start.x) * (y - this.start.y)); | |
}, | |
/** | |
* Tests if the given coordinates fall on this line and within the segment. See pointOnLine to test against just the line. | |
* | |
* @method Phaser.Line#pointOnSegment | |
* @param {number} x - The line to check against this one. | |
* @param {number} y - The line to check against this one. | |
* @return {boolean} True if the point is on the line and segment, false if not. | |
*/ | |
pointOnSegment: function (x, y) { | |
var xMin = Math.min(this.start.x, this.end.x); | |
var xMax = Math.max(this.start.x, this.end.x); | |
var yMin = Math.min(this.start.y, this.end.y); | |
var yMax = Math.max(this.start.y, this.end.y); | |
return (this.pointOnLine(x, y) && (x >= xMin && x <= xMax) && (y >= yMin && y <= yMax)); | |
}, | |
/** | |
* Picks a random point from anywhere on the Line segment and returns it. | |
* | |
* @method Phaser.Line#random | |
* @param {Phaser.Point|object} [out] - A Phaser.Point, or any object with public x/y properties, that the values will be set in. | |
* If no object is provided a new Phaser.Point object will be created. In high performance areas avoid this by re-using an object. | |
* @return {Phaser.Point} An object containing the random point in its `x` and `y` properties. | |
*/ | |
random: function (out) { | |
if (out === undefined) { out = new Phaser.Point(); } | |
var t = Math.random(); | |
out.x = this.start.x + t * (this.end.x - this.start.x); | |
out.y = this.start.y + t * (this.end.y - this.start.y); | |
return out; | |
}, | |
/** | |
* Using Bresenham's line algorithm this will return an array of all coordinates on this line. | |
* The start and end points are rounded before this runs as the algorithm works on integers. | |
* | |
* @method Phaser.Line#coordinatesOnLine | |
* @param {number} [stepRate=1] - How many steps will we return? 1 = every coordinate on the line, 2 = every other coordinate, etc. | |
* @param {array} [results] - The array to store the results in. If not provided a new one will be generated. | |
* @return {array} An array of coordinates. | |
*/ | |
coordinatesOnLine: function (stepRate, results) { | |
if (stepRate === undefined) { stepRate = 1; } | |
if (results === undefined) { results = []; } | |
var x1 = Math.round(this.start.x); | |
var y1 = Math.round(this.start.y); | |
var x2 = Math.round(this.end.x); | |
var y2 = Math.round(this.end.y); | |
var dx = Math.abs(x2 - x1); | |
var dy = Math.abs(y2 - y1); | |
var sx = (x1 < x2) ? 1 : -1; | |
var sy = (y1 < y2) ? 1 : -1; | |
var err = dx - dy; | |
results.push([x1, y1]); | |
var i = 1; | |
while (!((x1 === x2) && (y1 === y2))) | |
{ | |
var e2 = err << 1; | |
if (e2 > -dy) | |
{ | |
err -= dy; | |
x1 += sx; | |
} | |
if (e2 < dx) | |
{ | |
err += dx; | |
y1 += sy; | |
} | |
if (i % stepRate === 0) | |
{ | |
results.push([x1, y1]); | |
} | |
i++; | |
} | |
return results; | |
}, | |
/** | |
* Returns a new Line object with the same values for the start and end properties as this Line object. | |
* @method Phaser.Line#clone | |
* @param {Phaser.Line} output - Optional Line object. If given the values will be set into the object, otherwise a brand new Line object will be created and returned. | |
* @return {Phaser.Line} The cloned Line object. | |
*/ | |
clone: function (output) { | |
if (output === undefined || output === null) | |
{ | |
output = new Phaser.Line(this.start.x, this.start.y, this.end.x, this.end.y); | |
} | |
else | |
{ | |
output.setTo(this.start.x, this.start.y, this.end.x, this.end.y); | |
} | |
return output; | |
} | |
}; | |
/** | |
* @name Phaser.Line#length | |
* @property {number} length - Gets the length of the line segment. | |
* @readonly | |
*/ | |
Object.defineProperty(Phaser.Line.prototype, "length", { | |
get: function () { | |
return Math.sqrt((this.end.x - this.start.x) * (this.end.x - this.start.x) + (this.end.y - this.start.y) * (this.end.y - this.start.y)); | |
} | |
}); | |
/** | |
* @name Phaser.Line#angle | |
* @property {number} angle - Gets the angle of the line in radians. | |
* @readonly | |
*/ | |
Object.defineProperty(Phaser.Line.prototype, "angle", { | |
get: function () { | |
return Math.atan2(this.end.y - this.start.y, this.end.x - this.start.x); | |
} | |
}); | |
/** | |
* @name Phaser.Line#slope | |
* @property {number} slope - Gets the slope of the line (y/x). | |
* @readonly | |
*/ | |
Object.defineProperty(Phaser.Line.prototype, "slope", { | |
get: function () { | |
return (this.end.y - this.start.y) / (this.end.x - this.start.x); | |
} | |
}); | |
/** | |
* @name Phaser.Line#perpSlope | |
* @property {number} perpSlope - Gets the perpendicular slope of the line (x/y). | |
* @readonly | |
*/ | |
Object.defineProperty(Phaser.Line.prototype, "perpSlope", { | |
get: function () { | |
return -((this.end.x - this.start.x) / (this.end.y - this.start.y)); | |
} | |
}); | |
/** | |
* @name Phaser.Line#x | |
* @property {number} x - Gets the x coordinate of the top left of the bounds around this line. | |
* @readonly | |
*/ | |
Object.defineProperty(Phaser.Line.prototype, "x", { | |
get: function () { | |
return Math.min(this.start.x, this.end.x); | |
} | |
}); | |
/** | |
* @name Phaser.Line#y | |
* @property {number} y - Gets the y coordinate of the top left of the bounds around this line. | |
* @readonly | |
*/ | |
Object.defineProperty(Phaser.Line.prototype, "y", { | |
get: function () { | |
return Math.min(this.start.y, this.end.y); | |
} | |
}); | |
/** | |
* @name Phaser.Line#left | |
* @property {number} left - Gets the left-most point of this line. | |
* @readonly | |
*/ | |
Object.defineProperty(Phaser.Line.prototype, "left", { | |
get: function () { | |
return Math.min(this.start.x, this.end.x); | |
} | |
}); | |
/** | |
* @name Phaser.Line#right | |
* @property {number} right - Gets the right-most point of this line. | |
* @readonly | |
*/ | |
Object.defineProperty(Phaser.Line.prototype, "right", { | |
get: function () { | |
return Math.max(this.start.x, this.end.x); | |
} | |
}); | |
/** | |
* @name Phaser.Line#top | |
* @property {number} top - Gets the top-most point of this line. | |
* @readonly | |
*/ | |
Object.defineProperty(Phaser.Line.prototype, "top", { | |
get: function () { | |
return Math.min(this.start.y, this.end.y); | |
} | |
}); | |
/** | |
* @name Phaser.Line#bottom | |
* @property {number} bottom - Gets the bottom-most point of this line. | |
* @readonly | |
*/ | |
Object.defineProperty(Phaser.Line.prototype, "bottom", { | |
get: function () { | |
return Math.max(this.start.y, this.end.y); | |
} | |
}); | |
/** | |
* @name Phaser.Line#width | |
* @property {number} width - Gets the width of this bounds of this line. | |
* @readonly | |
*/ | |
Object.defineProperty(Phaser.Line.prototype, "width", { | |
get: function () { | |
return Math.abs(this.start.x - this.end.x); | |
} | |
}); | |
/** | |
* @name Phaser.Line#height | |
* @property {number} height - Gets the height of this bounds of this line. | |
* @readonly | |
*/ | |
Object.defineProperty(Phaser.Line.prototype, "height", { | |
get: function () { | |
return Math.abs(this.start.y - this.end.y); | |
} | |
}); | |
/** | |
* @name Phaser.Line#normalX | |
* @property {number} normalX - Gets the x component of the left-hand normal of this line. | |
* @readonly | |
*/ | |
Object.defineProperty(Phaser.Line.prototype, "normalX", { | |
get: function () { | |
return Math.cos(this.angle - 1.5707963267948966); | |
} | |
}); | |
/** | |
* @name Phaser.Line#normalY | |
* @property {number} normalY - Gets the y component of the left-hand normal of this line. | |
* @readonly | |
*/ | |
Object.defineProperty(Phaser.Line.prototype, "normalY", { | |
get: function () { | |
return Math.sin(this.angle - 1.5707963267948966); | |
} | |
}); | |
/** | |
* @name Phaser.Line#normalAngle | |
* @property {number} normalAngle - Gets the angle in radians of the normal of this line (line.angle - 90 degrees.) | |
* @readonly | |
*/ | |
Object.defineProperty(Phaser.Line.prototype, "normalAngle", { | |
get: function () { | |
return Phaser.Math.wrap(this.angle - 1.5707963267948966, -Math.PI, Math.PI); | |
} | |
}); | |
/** | |
* Checks for intersection between two lines as defined by the given start and end points. | |
* If asSegment is true it will check for line segment intersection. If asSegment is false it will check for line intersection. | |
* Returns the intersection segment of AB and EF as a Point, or null if there is no intersection. | |
* Adapted from code by Keith Hair | |
* | |
* @method Phaser.Line.intersectsPoints | |
* @param {Phaser.Point} a - The start of the first Line to be checked. | |
* @param {Phaser.Point} b - The end of the first line to be checked. | |
* @param {Phaser.Point} e - The start of the second Line to be checked. | |
* @param {Phaser.Point} f - The end of the second line to be checked. | |
* @param {boolean} [asSegment=true] - If true it will check for segment intersection, otherwise full line intersection. | |
* @param {Phaser.Point|object} [result] - A Point object to store the result in, if not given a new one will be created. | |
* @return {Phaser.Point} The intersection segment of the two lines as a Point, or null if there is no intersection. | |
*/ | |
Phaser.Line.intersectsPoints = function (a, b, e, f, asSegment, result) { | |
if (asSegment === undefined) { asSegment = true; } | |
if (result === undefined) { result = new Phaser.Point(); } | |
var a1 = b.y - a.y; | |
var a2 = f.y - e.y; | |
var b1 = a.x - b.x; | |
var b2 = e.x - f.x; | |
var c1 = (b.x * a.y) - (a.x * b.y); | |
var c2 = (f.x * e.y) - (e.x * f.y); | |
var denom = (a1 * b2) - (a2 * b1); | |
if (denom === 0) | |
{ | |
return null; | |
} | |
result.x = ((b1 * c2) - (b2 * c1)) / denom; | |
result.y = ((a2 * c1) - (a1 * c2)) / denom; | |
if (asSegment) | |
{ | |
var uc = ((f.y - e.y) * (b.x - a.x) - (f.x - e.x) * (b.y - a.y)); | |
var ua = (((f.x - e.x) * (a.y - e.y)) - (f.y - e.y) * (a.x - e.x)) / uc; | |
var ub = (((b.x - a.x) * (a.y - e.y)) - ((b.y - a.y) * (a.x - e.x))) / uc; | |
if (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1) | |
{ | |
return result; | |
} | |
else | |
{ | |
return null; | |
} | |
} | |
return result; | |
}; | |
/** | |
* Checks for intersection between two lines. | |
* If asSegment is true it will check for segment intersection. | |
* If asSegment is false it will check for line intersection. | |
* Returns the intersection segment of AB and EF as a Point, or null if there is no intersection. | |
* Adapted from code by Keith Hair | |
* | |
* @method Phaser.Line.intersects | |
* @param {Phaser.Line} a - The first Line to be checked. | |
* @param {Phaser.Line} b - The second Line to be checked. | |
* @param {boolean} [asSegment=true] - If true it will check for segment intersection, otherwise full line intersection. | |
* @param {Phaser.Point} [result] - A Point object to store the result in, if not given a new one will be created. | |
* @return {Phaser.Point} The intersection segment of the two lines as a Point, or null if there is no intersection. | |
*/ | |
Phaser.Line.intersects = function (a, b, asSegment, result) { | |
return Phaser.Line.intersectsPoints(a.start, a.end, b.start, b.end, asSegment, result); | |
}; | |
/** | |
* Checks for intersection between the Line and a Rectangle shape, or a rectangle-like | |
* object, with public `x`, `y`, `right` and `bottom` properties, such as a Sprite or Body. | |
* | |
* An intersection is considered valid if: | |
* | |
* The line starts within, or ends within, the Rectangle. | |
* The line segment intersects one of the 4 rectangle edges. | |
* | |
* The for the purposes of this function rectangles are considered 'solid'. | |
* | |
* @method Phaser.Line.intersectsRectangle | |
* @param {Phaser.Line} line - The line to check for intersection with. | |
* @param {Phaser.Rectangle|object} rect - The rectangle, or rectangle-like object, to check for intersection with. | |
* @return {boolean} True if the line intersects with the rectangle edges, or starts or ends within the rectangle. | |
*/ | |
Phaser.Line.intersectsRectangle = function (line, rect) { | |
// Quick bail out of the Line and Rect bounds don't intersect | |
if (!Phaser.Rectangle.intersects(line, rect)) | |
{ | |
return false; | |
} | |
var x1 = line.start.x; | |
var y1 = line.start.y; | |
var x2 = line.end.x; | |
var y2 = line.end.y; | |
var bx1 = rect.x; | |
var by1 = rect.y; | |
var bx2 = rect.right; | |
var by2 = rect.bottom; | |
var t = 0; | |
// If the start or end of the line is inside the rect then we assume | |
// collision, as rects are solid for our use-case. | |
if ((x1 >= bx1 && x1 <= bx2 && y1 >= by1 && y1 <= by2) || | |
(x2 >= bx1 && x2 <= bx2 && y2 >= by1 && y2 <= by2)) | |
{ | |
return true; | |
} | |
if (x1 < bx1 && x2 >= bx1) | |
{ | |
// Left edge | |
t = y1 + (y2 - y1) * (bx1 - x1) / (x2 - x1); | |
if (t > by1 && t <= by2) | |
{ | |
return true; | |
} | |
} | |
else if (x1 > bx2 && x2 <= bx2) | |
{ | |
// Right edge | |
t = y1 + (y2 - y1) * (bx2 - x1) / (x2 - x1); | |
if (t >= by1 && t <= by2) | |
{ | |
return true; | |
} | |
} | |
if (y1 < by1 && y2 >= by1) | |
{ | |
// Top edge | |
t = x1 + (x2 - x1) * (by1 - y1) / (y2 - y1); | |
if (t >= bx1 && t <= bx2) | |
{ | |
return true; | |
} | |
} | |
else if (y1 > by2 && y2 <= by2) | |
{ | |
// Bottom edge | |
t = x1 + (x2 - x1) * (by2 - y1) / (y2 - y1); | |
if (t >= bx1 && t <= bx2) | |
{ | |
return true; | |
} | |
} | |
return false; | |
}; | |
/** | |
* Returns the reflected angle between two lines. | |
* This is the outgoing angle based on the angle of Line 1 and the normalAngle of Line 2. | |
* | |
* @method Phaser.Line.reflect | |
* @param {Phaser.Line} a - The base line. | |
* @param {Phaser.Line} b - The line to be reflected from the base line. | |
* @return {number} The reflected angle in radians. | |
*/ | |
Phaser.Line.reflect = function (a, b) { | |
return 2 * b.normalAngle - 3.141592653589793 - a.angle; | |
}; | |
/** | |
* @author Mat Groves http://matgroves.com/ @Doormat23 | |
* @author Richard Davey <[email protected]> | |
* @copyright 2016 Photon Storm Ltd. | |
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} | |
*/ | |
/** | |
* The Matrix is a 3x3 matrix mostly used for display transforms within the renderer. | |
* | |
* It is represented like so: | |
* | |
* | a | b | tx | | |
* | c | d | ty | | |
* | 0 | 0 | 1 | | |
* | |
* @class Phaser.Matrix | |
* @constructor | |
* @param {number} [a=1] - Horizontal scaling | |
* @param {number} [b=0] - Horizontal skewing | |
* @param {number} [c=0] - Vertical skewing | |
* @param {number} [d=1] - Vertical scaling | |
* @param {number} [tx=0] - Horizontal translation | |
* @param {number} [ty=0] - Vertical translation | |
*/ | |
Phaser.Matrix = function (a, b, c, d, tx, ty) { | |
if (a === undefined || a === null) { a = 1; } | |
if (b === undefined || b === null) { b = 0; } | |
if (c === undefined || c === null) { c = 0; } | |
if (d === undefined || d === null) { d = 1; } | |
if (tx === undefined || tx === null) { tx = 0; } | |
if (ty === undefined || ty === null) { ty = 0; } | |
/** | |
* @property {number} a | |
* @default 1 | |
*/ | |
this.a = a; | |
/** | |
* @property {number} b | |
* @default 0 | |
*/ | |
this.b = b; | |
/** | |
* @property {number} c | |
* @default 0 | |
*/ | |
this.c = c; | |
/** | |
* @property {number} d | |
* @default 1 | |
*/ | |
this.d = d; | |
/** | |
* @property {number} tx | |
* @default 0 | |
*/ | |
this.tx = tx; | |
/** | |
* @property {number} ty | |
* @default 0 | |
*/ | |
this.ty = ty; | |
/** | |
* @property {number} type - The const type of this object. | |
* @readonly | |
*/ | |
this.type = Phaser.MATRIX; | |
}; | |
Phaser.Matrix.prototype = { | |
/** | |
* Sets the values of this Matrix to the values in the given array. | |
* | |
* The Array elements should be set as follows: | |
* | |
* a = array[0] | |
* b = array[1] | |
* c = array[3] | |
* d = array[4] | |
* tx = array[2] | |
* ty = array[5] | |
* | |
* @method Phaser.Matrix#fromArray | |
* @param {Array} array - The array to copy from. | |
* @return {Phaser.Matrix} This Matrix object. | |
*/ | |
fromArray: function (array) { | |
return this.setTo(array[0], array[1], array[3], array[4], array[2], array[5]); | |
}, | |
/** | |
* Sets the values of this Matrix to the given values. | |
* | |
* @method Phaser.Matrix#setTo | |
* @param {number} a - Horizontal scaling | |
* @param {number} b - Horizontal skewing | |
* @param {number} c - Vertical skewing | |
* @param {number} d - Vertical scaling | |
* @param {number} tx - Horizontal translation | |
* @param {number} ty - Vertical translation | |
* @return {Phaser.Matrix} This Matrix object. | |
*/ | |
setTo: function (a, b, c, d, tx, ty) { | |
this.a = a; | |
this.b = b; | |
this.c = c; | |
this.d = d; | |
this.tx = tx; | |
this.ty = ty; | |
return this; | |
}, | |
/** | |
* Creates a new Matrix object based on the values of this Matrix. | |
* If you provide the output parameter the values of this Matrix will be copied over to it. | |
* If the output parameter is blank a new Matrix object will be created. | |
* | |
* @method Phaser.Matrix#clone | |
* @param {Phaser.Matrix} [output] - If provided the values of this Matrix will be copied to it, otherwise a new Matrix object is created. | |
* @return {Phaser.Matrix} A clone of this Matrix. | |
*/ | |
clone: function (output) { | |
if (output === undefined || output === null) | |
{ | |
output = new Phaser.Matrix(this.a, this.b, this.c, this.d, this.tx, this.ty); | |
} | |
else | |
{ | |
output.a = this.a; | |
output.b = this.b; | |
output.c = this.c; | |
output.d = this.d; | |
output.tx = this.tx; | |
output.ty = this.ty; | |
} | |
return output; | |
}, | |
/** | |
* Copies the properties from this Matrix to the given Matrix. | |
* | |
* @method Phaser.Matrix#copyTo | |
* @param {Phaser.Matrix} matrix - The Matrix to copy from. | |
* @return {Phaser.Matrix} The destination Matrix object. | |
*/ | |
copyTo: function (matrix) { | |
matrix.copyFrom(this); | |
return matrix; | |
}, | |
/** | |
* Copies the properties from the given Matrix into this Matrix. | |
* | |
* @method Phaser.Matrix#copyFrom | |
* @param {Phaser.Matrix} matrix - The Matrix to copy from. | |
* @return {Phaser.Matrix} This Matrix object. | |
*/ | |
copyFrom: function (matrix) { | |
this.a = matrix.a; | |
this.b = matrix.b; | |
this.c = matrix.c; | |
this.d = matrix.d; | |
this.tx = matrix.tx; | |
this.ty = matrix.ty; | |
return this; | |
}, | |
/** | |
* Creates a Float32 Array with values populated from this Matrix object. | |
* | |
* @method Phaser.Matrix#toArray | |
* @param {boolean} [transpose=false] - Whether the values in the array are transposed or not. | |
* @param {PIXI.Float32Array} [array] - If provided the values will be set into this array, otherwise a new Float32Array is created. | |
* @return {PIXI.Float32Array} The newly created array which contains the matrix. | |
*/ | |
toArray: function (transpose, array) { | |
if (array === undefined) { array = new PIXI.Float32Array(9); } | |
if (transpose) | |
{ | |
array[0] = this.a; | |
array[1] = this.b; | |
array[2] = 0; | |
array[3] = this.c; | |
array[4] = this.d; | |
array[5] = 0; | |
array[6] = this.tx; | |
array[7] = this.ty; | |
array[8] = 1; | |
} | |
else | |
{ | |
array[0] = this.a; | |
array[1] = this.c; | |
array[2] = this.tx; | |
array[3] = this.b; | |
array[4] = this.d; | |
array[5] = this.ty; | |
array[6] = 0; | |
array[7] = 0; | |
array[8] = 1; | |
} | |
return array; | |
}, | |
/** | |
* Get a new position with the current transformation applied. | |
* | |
* Can be used to go from a childs coordinate space to the world coordinate space (e.g. rendering) | |
* | |
* @method Phaser.Matrix#apply | |
* @param {Phaser.Point} pos - The origin Point. | |
* @param {Phaser.Point} [newPos] - The point that the new position is assigned to. This can be same as input point. | |
* @return {Phaser.Point} The new point, transformed through this matrix. | |
*/ | |
apply: function (pos, newPos) { | |
if (newPos === undefined) { newPos = new Phaser.Point(); } | |
newPos.x = this.a * pos.x + this.c * pos.y + this.tx; | |
newPos.y = this.b * pos.x + this.d * pos.y + this.ty; | |
return newPos; | |
}, | |
/** | |
* Get a new position with the inverse of the current transformation applied. | |
* | |
* Can be used to go from the world coordinate space to a childs coordinate space. (e.g. input) | |
* | |
* @method Phaser.Matrix#applyInverse | |
* @param {Phaser.Point} pos - The origin Point. | |
* @param {Phaser.Point} [newPos] - The point that the new position is assigned to. This can be same as input point. | |
* @return {Phaser.Point} The new point, inverse transformed through this matrix. | |
*/ | |
applyInverse: function (pos, newPos) { | |
if (newPos === undefined) { newPos = new Phaser.Point(); } | |
var id = 1 / (this.a * this.d + this.c * -this.b); | |
var x = pos.x; | |
var y = pos.y; | |
newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; | |
newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; | |
return newPos; | |
}, | |
/** | |
* Translates the matrix on the x and y. | |
* This is the same as Matrix.tx += x. | |
* | |
* @method Phaser.Matrix#translate | |
* @param {number} x - The x value to translate on. | |
* @param {number} y - The y value to translate on. | |
* @return {Phaser.Matrix} This Matrix object. | |
*/ | |
translate: function (x, y) { | |
this.tx += x; | |
this.ty += y; | |
return this; | |
}, | |
/** | |
* Applies a scale transformation to this matrix. | |
* | |
* @method Phaser.Matrix#scale | |
* @param {number} x - The amount to scale horizontally. | |
* @param {number} y - The amount to scale vertically. | |
* @return {Phaser.Matrix} This Matrix object. | |
*/ | |
scale: function (x, y) { | |
this.a *= x; | |
this.d *= y; | |
this.c *= x; | |
this.b *= y; | |
this.tx *= x; | |
this.ty *= y; | |
return this; | |
}, | |
/** | |
* Applies a rotation transformation to this matrix. | |
* | |
* @method Phaser.Matrix#rotate | |
* @param {number} angle - The angle to rotate by, given in radians. | |
* @return {Phaser.Matrix} This Matrix object. | |
*/ | |
rotate: function (angle) { | |
var cos = Math.cos(angle); | |
var sin = Math.sin(angle); | |
var a1 = this.a; | |
var c1 = this.c; | |
var tx1 = this.tx; | |
this.a = a1 * cos-this.b * sin; | |
this.b = a1 * sin+this.b * cos; | |
this.c = c1 * cos-this.d * sin; | |
this.d = c1 * sin+this.d * cos; | |
this.tx = tx1 * cos - this.ty * sin; | |
this.ty = tx1 * sin + this.ty * cos; | |
return this; | |
}, | |
/** | |
* Appends the given Matrix to this Matrix. | |
* | |
* @method Phaser.Matrix#append | |
* @param {Phaser.Matrix} matrix - The matrix to append to this one. | |
* @return {Phaser.Matrix} This Matrix object. | |
*/ | |
append: function (matrix) { | |
var a1 = this.a; | |
var b1 = this.b; | |
var c1 = this.c; | |
var d1 = this.d; | |
this.a = matrix.a * a1 + matrix.b * c1; | |
this.b = matrix.a * b1 + matrix.b * d1; | |
this.c = matrix.c * a1 + matrix.d * c1; | |
this.d = matrix.c * b1 + matrix.d * d1; | |
this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; | |
this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; | |
return this; | |
}, | |
/** | |
* Resets this Matrix to an identity (default) matrix. | |
* | |
* @method Phaser.Matrix#identity | |
* @return {Phaser.Matrix} This Matrix object. | |
*/ | |
identity: function () { | |
return this.setTo(1, 0, 0, 1, 0, 0); | |
} | |
}; | |
Phaser.identityMatrix = new Phaser.Matrix(); | |
// Because PIXI uses its own type, we'll replace it with ours to avoid duplicating code or confusion. | |
PIXI.Matrix = Phaser.Matrix; | |
PIXI.identityMatrix = Phaser.identityMatrix; | |
/** | |
* @author Richard Davey <[email protected]> | |
* @copyright 2016 Photon Storm Ltd. | |
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} | |
*/ | |
/** | |
* A Point object represents a location in a two-dimensional coordinate system, where x represents the horizontal axis and y represents the vertical axis. | |
* The following code creates a point at (0,0): | |
* `var myPoint = new Phaser.Point();` | |
* You can also use them as 2D Vectors and you'll find different vector related methods in this class. | |
* | |
* @class Phaser.Point | |
* @constructor | |
* @param {number} [x=0] - The horizontal position of this Point. | |
* @param {number} [y=0] - The vertical position of this Point. | |
*/ | |
Phaser.Point = function (x, y) { | |
x = x || 0; | |
y = y || 0; | |
/** | |
* @property {number} x - The x value of the point. | |
*/ | |
this.x = x; | |
/** | |
* @property {number} y - The y value of the point. | |
*/ | |
this.y = y; | |
/** | |
* @property {number} type - The const type of this object. | |
* @readonly | |
*/ | |
this.type = Phaser.POINT; | |
}; | |
Phaser.Point.prototype = { | |
/** | |
* Copies the x and y properties from any given object to this Point. | |
* | |
* @method Phaser.Point#copyFrom | |
* @param {any} source - The object to copy from. | |
* @return {Phaser.Point} This Point object. | |
*/ | |
copyFrom: function (source) { | |
return this.setTo(source.x, source.y); | |
}, | |
/** | |
* Inverts the x and y values of this Point | |
* | |
* @method Phaser.Point#invert | |
* @return {Phaser.Point} This Point object. | |
*/ | |
invert: function () { | |
return this.setTo(this.y, this.x); | |
}, | |
/** | |
* Sets the `x` and `y` values of this Point object to the given values. | |
* If you omit the `y` value then the `x` value will be applied to both, for example: | |
* `Point.setTo(2)` is the same as `Point.setTo(2, 2)` | |
* | |
* @method Phaser.Point#setTo | |
* @param {number} x - The horizontal value of this point. | |
* @param {number} [y] - The vertical value of this point. If not given the x value will be used in its place. | |
* @return {Phaser.Point} This Point object. Useful for chaining method calls. | |
*/ | |
setTo: function (x, y) { | |
this.x = x || 0; | |
this.y = y || ( (y !== 0) ? this.x : 0 ); | |
return this; | |
}, | |
/** | |
* Sets the `x` and `y` values of this Point object to the given values. | |
* If you omit the `y` value then the `x` value will be applied to both, for example: | |
* `Point.set(2)` is the same as `Point.set(2, 2)` | |
* | |
* @method Phaser.Point#set | |
* @param {number} x - The horizontal value of this point. | |
* @param {number} [y] - The vertical value of this point. If not given the x value will be used in its place. | |
* @return {Phaser.Point} This Point object. Useful for chaining method calls. | |
*/ | |
set: function (x, y) { | |
this.x = x || 0; | |
this.y = y || ( (y !== 0) ? this.x : 0 ); | |
return this; | |
}, | |
/** | |
* Adds the given x and y values to this Point. | |
* | |
* @method Phaser.Point#add | |
* @param {number} x - The value to add to Point.x. | |
* @param {number} y - The value to add to Point.y. | |
* @return {Phaser.Point} This Point object. Useful for chaining method calls. | |
*/ | |
add: function (x, y) { | |
this.x += x; | |
this.y += y; | |
return this; | |
}, | |
/** | |
* Subtracts the given x and y values from this Point. | |
* | |
* @method Phaser.Point#subtract | |
* @param {number} x - The value to subtract from Point.x. | |
* @param {number} y - The value to subtract from Point.y. | |
* @return {Phaser.Point} This Point object. Useful for chaining method calls. | |
*/ | |
subtract: function (x, y) { | |
this.x -= x; | |
this.y -= y; | |
return this; | |
}, | |
/** | |
* Multiplies Point.x and Point.y by the given x and y values. Sometimes known as `Scale`. | |
* | |
* @method Phaser.Point#multiply | |
* @param {number} x - The value to multiply Point.x by. | |
* @param {number} y - The value to multiply Point.x by. | |
* @return {Phaser.Point} This Point object. Useful for chaining method calls. | |
*/ | |
multiply: function (x, y) { | |
this.x *= x; | |
this.y *= y; | |
return this; | |
}, | |
/** | |
* Divides Point.x and Point.y by the given x and y values. | |
* | |
* @method Phaser.Point#divide | |
* @param {number} x - The value to divide Point.x by. | |
* @param {number} y - The value to divide Point.x by. | |
* @return {Phaser.Point} This Point object. Useful for chaining method calls. | |
*/ | |
divide: function (x, y) { | |
this.x /= x; | |
this.y /= y; | |
return this; | |
}, | |
/** | |
* Clamps the x value of this Point to be between the given min and max. | |
* | |
* @method Phaser.Point#clampX | |
* @param {number} min - The minimum value to clamp this Point to. | |
* @param {number} max - The maximum value to clamp this Point to. | |
* @return {Phaser.Point} This Point object. | |
*/ | |
clampX: function (min, max) { | |
this.x = Phaser.Math.clamp(this.x, min, max); | |
return this; | |
}, | |
/** | |
* Clamps the y value of this Point to be between the given min and max | |
* | |
* @method Phaser.Point#clampY | |
* @param {number} min - The minimum value to clamp this Point to. | |
* @param {number} max - The maximum value to clamp this Point to. | |
* @return {Phaser.Point} This Point object. | |
*/ | |
clampY: function (min, max) { | |
this.y = Phaser.Math.clamp(this.y, min, max); | |
return this; | |
}, | |
/** | |
* Clamps this Point object values to be between the given min and max. | |
* | |
* @method Phaser.Point#clamp | |
* @param {number} min - The minimum value to clamp this Point to. | |
* @param {number} max - The maximum value to clamp this Point to. | |
* @return {Phaser.Point} This Point object. | |
*/ | |
clamp: function (min, max) { | |
this.x = Phaser.Math.clamp(this.x, min, max); | |
this.y = Phaser.Math.clamp(this.y, min, max); | |
return this; | |
}, | |
/** | |
* Creates a copy of the given Point. | |
* | |
* @method Phaser.Point#clone | |
* @param {Phaser.Point} [output] Optional Point object. If given the values will be set into this object, otherwise a brand new Point object will be created and returned. | |
* @return {Phaser.Point} The new Point object. | |
*/ | |
clone: function (output) { | |
if (output === undefined || output === null) | |
{ | |
output = new Phaser.Point(this.x, this.y); | |
} | |
else | |
{ | |
output.setTo(this.x, this.y); | |
} | |
return output; | |
}, | |
/** | |
* Copies the x and y properties from this Point to any given object. | |
* | |
* @method Phaser.Point#copyTo | |
* @param {any} dest - The object to copy to. | |
* @return {object} The dest object. | |
*/ | |
copyTo: function (dest) { | |
dest.x = this.x; | |
dest.y = this.y; | |
return dest; | |
}, | |
/** | |
* Returns the distance of this Point object to the given object (can be a Circle, Point or anything with x/y properties) | |
* | |
* @method Phaser.Point#distance | |
* @param {object} dest - The target object. Must have visible x and y properties that represent the center of the object. | |
* @param {boolean} [round] - Round the distance to the nearest integer (default false). | |
* @return {number} The distance between this Point object and the destination Point object. | |
*/ | |
distance: function (dest, round) { | |
return Phaser.Point.distance(this, dest, round); | |
}, | |
/** | |
* Determines whether the given objects x/y values are equal to this Point object. | |
* | |
* @method Phaser.Point#equals | |
* @param {Phaser.Point|any} a - The object to compare with this Point. | |
* @return {boolean} A value of true if the x and y points are equal, otherwise false. | |
*/ | |
equals: function (a) { | |
return (a.x === this.x && a.y === this.y); | |
}, | |
/** | |
* Returns the angle between this Point object and another object with public x and y properties. | |
* | |
* @method Phaser.Point#angle | |
* @param {Phaser.Point|any} a - The object to get the angle from this Point to. | |
* @param {boolean} [asDegrees=false] - Is the given angle in radians (false) or degrees (true)? | |
* @return {number} The angle between the two objects. | |
*/ | |
angle: function (a, asDegrees) { | |
if (asDegrees === undefined) { asDegrees = false; } | |
if (asDegrees) | |
{ | |
return Phaser.Math.radToDeg(Math.atan2(a.y - this.y, a.x - this.x)); | |
} | |
else | |
{ | |
return Math.atan2(a.y - this.y, a.x - this.x); | |
} | |
}, | |
/** | |
* Rotates this Point around the x/y coordinates given to the desired angle. | |
* | |
* @method Phaser.Point#rotate | |
* @param {number} x - The x coordinate of the anchor point. | |
* @param {number} y - The y coordinate of the anchor point. | |
* @param {number} angle - The angle in radians (unless asDegrees is true) to rotate the Point to. | |
* @param {boolean} [asDegrees=false] - Is the given angle in radians (false) or degrees (true)? | |
* @param {number} [distance] - An optional distance constraint between the Point and the anchor. | |
* @return {Phaser.Point} The modified point object. | |
*/ | |
rotate: function (x, y, angle, asDegrees, distance) { | |
return Phaser.Point.rotate(this, x, y, angle, asDegrees, distance); | |
}, | |
/** | |
* Calculates the length of the Point object. | |
* | |
* @method Phaser.Point#getMagnitude | |
* @return {number} The length of the Point. | |
*/ | |
getMagnitude: function () { | |
return Math.sqrt((this.x * this.x) + (this.y * this.y)); | |
}, | |
/** | |
* Calculates the length squared of the Point object. | |
* | |
* @method Phaser.Point#getMagnitudeSq | |
* @return {number} The length ^ 2 of the Point. | |
*/ | |
getMagnitudeSq: function () { | |
return (this.x * this.x) + (this.y * this.y); | |
}, | |
/** | |
* Alters the length of the Point without changing the direction. | |
* | |
* @method Phaser.Point#setMagnitude | |
* @param {number} magnitude - The desired magnitude of the resulting Point. | |
* @return {Phaser.Point} This Point object. | |
*/ | |
setMagnitude: function (magnitude) { | |
return this.normalize().multiply(magnitude, magnitude); | |
}, | |
/** | |
* Alters the Point object so that its length is 1, but it retains the same direction. | |
* | |
* @method Phaser.Point#normalize | |
* @return {Phaser.Point} This Point object. | |
*/ | |
normalize: function () { | |
if (!this.isZero()) | |
{ | |
var m = this.getMagnitude(); | |
this.x /= m; | |
this.y /= m; | |
} | |
return this; | |
}, | |
/** | |
* Determine if this point is at 0,0. | |
* | |
* @method Phaser.Point#isZero | |
* @return {boolean} True if this Point is 0,0, otherwise false. | |
*/ | |
isZero: function () { | |
return (this.x === 0 && this.y === 0); | |
}, | |
/** | |
* The dot product of this and another Point object. | |
* | |
* @method Phaser.Point#dot | |
* @param {Phaser.Point} a - The Point object to get the dot product combined with this Point. | |
* @return {number} The result. | |
*/ | |
dot: function (a) { | |
return ((this.x * a.x) + (this.y * a.y)); | |
}, | |
/** | |
* The cross product of this and another Point object. | |
* | |
* @method Phaser.Point#cross | |
* @param {Phaser.Point} a - The Point object to get the cross product combined with this Point. | |
* @return {number} The result. | |
*/ | |
cross: function (a) { | |
return ((this.x * a.y) - (this.y * a.x)); | |
}, | |
/** | |
* Make this Point perpendicular (90 degrees rotation) | |
* | |
* @method Phaser.Point#perp | |
* @return {Phaser.Point} This Point object. | |
*/ | |
perp: function () { | |
return this.setTo(-this.y, this.x); | |
}, | |
/** | |
* Make this Point perpendicular (-90 degrees rotation) | |
* | |
* @method Phaser.Point#rperp | |
* @return {Phaser.Point} This Point object. | |
*/ | |
rperp: function () { | |
return this.setTo(this.y, -this.x); | |
}, | |
/** | |
* Right-hand normalize (make unit length) this Point. | |
* | |
* @method Phaser.Point#normalRightHand | |
* @return {Phaser.Point} This Point object. | |
*/ | |
normalRightHand: function () { | |
return this.setTo(this.y * -1, this.x); | |
}, | |
/** | |
* Math.floor() both the x and y properties of this Point. | |
* | |
* @method Phaser.Point#floor | |
* @return {Phaser.Point} This Point object. | |
*/ | |
floor: function () { | |
return this.setTo(Math.floor(this.x), Math.floor(this.y)); | |
}, | |
/** | |
* Math.ceil() both the x and y properties of this Point. | |
* | |
* @method Phaser.Point#ceil | |
* @return {Phaser.Point} This Point object. | |
*/ | |
ceil: function () { | |
return this.setTo(Math.ceil(this.x), Math.ceil(this.y)); | |
}, | |
/** | |
* Returns a string representation of this object. | |
* | |
* @method Phaser.Point#toString | |
* @return {string} A string representation of the instance. | |
*/ | |
toString: function () { | |
return '[{Point (x=' + this.x + ' y=' + this.y + ')}]'; | |
} | |
}; | |
Phaser.Point.prototype.constructor = Phaser.Point; | |
/** | |
* Adds the coordinates of two points together to create a new point. | |
* | |
* @method Phaser.Point.add | |
* @param {Phaser.Point} a - The first Point object. | |
* @param {Phaser.Point} b - The second Point object. | |
* @param {Phaser.Point} [out] - Optional Point to store the value in, if not supplied a new Point object will be created. | |
* @return {Phaser.Point} The new Point object. | |
*/ | |
Phaser.Point.add = function (a, b, out) { | |
if (out === undefined) { out = new Phaser.Point(); } | |
out.x = a.x + b.x; | |
out.y = a.y + b.y; | |
return out; | |
}; | |
/** | |
* Subtracts the coordinates of two points to create a new point. | |
* | |
* @method Phaser.Point.subtract | |
* @param {Phaser.Point} a - The first Point object. | |
* @param {Phaser.Point} b - The second Point object. | |
* @param {Phaser.Point} [out] - Optional Point to store the value in, if not supplied a new Point object will be created. | |
* @return {Phaser.Point} The new Point object. | |
*/ | |
Phaser.Point.subtract = function (a, b, out) { | |
if (out === undefined) { out = new Phaser.Point(); } | |
out.x = a.x - b.x; | |
out.y = a.y - b.y; | |
return out; | |
}; | |
/** | |
* Multiplies the coordinates of two points to create a new point. | |
* | |
* @method Phaser.Point.multiply | |
* @param {Phaser.Point} a - The first Point object. | |
* @param {Phaser.Point} b - The second Point object. | |
* @param {Phaser.Point} [out] - Optional Point to store the value in, if not supplied a new Point object will be created. | |
* @return {Phaser.Point} The new Point object. | |
*/ | |
Phaser.Point.multiply = function (a, b, out) { | |
if (out === undefined) { out = new Phaser.Point(); } | |
out.x = a.x * b.x; | |
out.y = a.y * b.y; | |
return out; | |
}; | |
/** | |
* Divides the coordinates of two points to create a new point. | |
* | |
* @method Phaser.Point.divide | |
* @param {Phaser.Point} a - The first Point object. | |
* @param {Phaser.Point} b - The second Point object. | |
* @param {Phaser.Point} [out] - Optional Point to store the value in, if not supplied a new Point object will be created. | |
* @return {Phaser.Point} The new Point object. | |
*/ | |
Phaser.Point.divide = function (a, b, out) { | |
if (out === undefined) { out = new Phaser.Point(); } | |
out.x = a.x / b.x; | |
out.y = a.y / b.y; | |
return out; | |
}; | |
/** | |
* Determines whether the two given Point objects are equal. They are considered equal if they have the same x and y values. | |
* | |
* @method Phaser.Point.equals | |
* @param {Phaser.Point} a - The first Point object. | |
* @param {Phaser.Point} b - The second Point object. | |
* @return {boolean} A value of true if the Points are equal, otherwise false. | |
*/ | |
Phaser.Point.equals = function (a, b) { | |
return (a.x === b.x && a.y === b.y); | |
}; | |
/** | |
* Returns the angle between two Point objects. | |
* | |
* @method Phaser.Point.angle | |
* @param {Phaser.Point} a - The first Point object. | |
* @param {Phaser.Point} b - The second Point object. | |
* @return {number} The angle between the two Points. | |
*/ | |
Phaser.Point.angle = function (a, b) { | |
// return Math.atan2(a.x * b.y - a.y * b.x, a.x * b.x + a.y * b.y); | |
return Math.atan2(a.y - b.y, a.x - b.x); | |
}; | |
/** | |
* Creates a negative Point. | |
* | |
* @method Phaser.Point.negative | |
* @param {Phaser.Point} a - The first Point object. | |
* @param {Phaser.Point} [out] - Optional Point to store the value in, if not supplied a new Point object will be created. | |
* @return {Phaser.Point} The new Point object. | |
*/ | |
Phaser.Point.negative = function (a, out) { | |
if (out === undefined) { out = new Phaser.Point(); } | |
return out.setTo(-a.x, -a.y); | |
}; | |
/** | |
* Adds two 2D Points together and multiplies the result by the given scalar. | |
* | |
* @method Phaser.Point.multiplyAdd | |
* @param {Phaser.Point} a - The first Point object. | |
* @param {Phaser.Point} b - The second Point object. | |
* @param {number} s - The scaling value. | |
* @param {Phaser.Point} [out] - Optional Point to store the value in, if not supplied a new Point object will be created. | |
* @return {Phaser.Point} The new Point object. | |
*/ | |
Phaser.Point.multiplyAdd = function (a, b, s, out) { | |
if (out === undefined) { out = new Phaser.Point(); } | |
return out.setTo(a.x + b.x * s, a.y + b.y * s); | |
}; | |
/** | |
* Interpolates the two given Points, based on the `f` value (between 0 and 1) and returns a new Point. | |
* | |
* @method Phaser.Point.interpolate | |
* @param {Phaser.Point} a - The first Point object. | |
* @param {Phaser.Point} b - The second Point object. | |
* @param {number} f - The level of interpolation between the two points. Indicates where the new point will be, along the line between pt1 and pt2. If f=1, pt1 is returned; if f=0, pt2 is returned. | |
* @param {Phaser.Point} [out] - Optional Point to store the value in, if not supplied a new Point object will be created. | |
* @return {Phaser.Point} The new Point object. | |
*/ | |
Phaser.Point.interpolate = function (a, b, f, out) { | |
if (out === undefined) { out = new Phaser.Point(); } | |
return out.setTo(a.x + (b.x - a.x) * f, a.y + (b.y - a.y) * f); | |
}; | |
/** | |
* Return a perpendicular vector (90 degrees rotation) | |
* | |
* @method Phaser.Point.perp | |
* @param {Phaser.Point} a - The Point object. | |
* @param {Phaser.Point} [out] - Optional Point to store the value in, if not supplied a new Point object will be created. | |
* @return {Phaser.Point} The new Point object. | |
*/ | |
Phaser.Point.perp = function (a, out) { | |
if (out === undefined) { out = new Phaser.Point(); } | |
return out.setTo(-a.y, a.x); | |
}; | |
/** | |
* Return a perpendicular vector (-90 degrees rotation) | |
* | |
* @method Phaser.Point.rperp | |
* @param {Phaser.Point} a - The Point object. | |
* @param {Phaser.Point} [out] - Optional Point to store the value in, if not supplied a new Point object will be created. | |
* @return {Phaser.Point} The new Point object. | |
*/ | |
Phaser.Point.rperp = function (a, out) { | |
if (out === undefined) { out = new Phaser.Point(); } | |
return out.setTo(a.y, -a.x); | |
}; | |
/** | |
* Returns the euclidian distance of this Point object to the given object (can be a Circle, Point or anything with x/y properties). | |
* | |
* @method Phaser.Point.distance | |
* @param {object} a - The target object. Must have visible x and y properties that represent the center of the object. | |
* @param {object} b - The target object. Must have visible x and y properties that represent the center of the object. | |
* @param {boolean} [round=false] - Round the distance to the nearest integer. | |
* @return {number} The distance between this Point object and the destination Point object. | |
*/ | |
Phaser.Point.distance = function (a, b, round) { | |
var distance = Phaser.Math.distance(a.x, a.y, b.x, b.y); | |
return round ? Math.round(distance) : distance; | |
}; | |
/** | |
* Project two Points onto another Point. | |
* | |
* @method Phaser.Point.project | |
* @param {Phaser.Point} a - The first Point object. | |
* @param {Phaser.Point} b - The second Point object. | |
* @param {Phaser.Point} [out] - Optional Point to store the value in, if not supplied a new Point object will be created. | |
* @return {Phaser.Point} The new Point object. | |
*/ | |
Phaser.Point.project = function (a, b, out) { | |
if (out === undefined) { out = new Phaser.Point(); } | |
var amt = a.dot(b) / b.getMagnitudeSq(); | |
if (amt !== 0) | |
{ | |
out.setTo(amt * b.x, amt * b.y); | |
} | |
return out; | |
}; | |
/** | |
* Project two Points onto a Point of unit length. | |
* | |
* @method Phaser.Point.projectUnit | |
* @param {Phaser.Point} a - The first Point object. | |
* @param {Phaser.Point} b - The second Point object. | |
* @param {Phaser.Point} [out] - Optional Point to store the value in, if not supplied a new Point object will be created. | |
* @return {Phaser.Point} The new Point object. | |
*/ | |
Phaser.Point.projectUnit = function (a, b, out) { | |
if (out === undefined) { out = new Phaser.Point(); } | |
var amt = a.dot(b); | |
if (amt !== 0) | |
{ | |
out.setTo(amt * b.x, amt * b.y); | |
} | |
return out; | |
}; | |
/** | |
* Right-hand normalize (make unit length) a Point. | |
* | |
* @method Phaser.Point.normalRightHand | |
* @param {Phaser.Point} a - The Point object. | |
* @param {Phaser.Point} [out] - Optional Point to store the value in, if not supplied a new Point object will be created. | |
* @return {Phaser.Point} The new Point object. | |
*/ | |
Phaser.Point.normalRightHand = function (a, out) { | |
if (out === undefined) { out = new Phaser.Point(); } | |
return out.setTo(a.y * -1, a.x); | |
}; | |
/** | |
* Normalize (make unit length) a Point. | |
* | |
* @method Phaser.Point.normalize | |
* @param {Phaser.Point} a - The Point object. | |
* @param {Phaser.Point} [out] - Optional Point to store the value in, if not supplied a new Point object will be created. | |
* @return {Phaser.Point} The new Point object. | |
*/ | |
Phaser.Point.normalize = function (a, out) { | |
if (out === undefined) { out = new Phaser.Point(); } | |
var m = a.getMagnitude(); | |
if (m !== 0) | |
{ | |
out.setTo(a.x / m, a.y / m); | |
} | |
return out; | |
}; | |
/** | |
* Rotates a Point object, or any object with exposed x/y properties, around the given coordinates by | |
* the angle specified. If the angle between the point and coordinates was 45 deg and the angle argument | |
* is 45 deg then the resulting angle will be 90 deg, as the angle argument is added to the current angle. | |
* | |
* The distance allows you to specify a distance constraint for the rotation between the point and the | |
* coordinates. If none is given the distance between the two is calculated and used. | |
* | |
* @method Phaser.Point.rotate | |
* @param {Phaser.Point} a - The Point object to rotate. | |
* @param {number} x - The x coordinate of the anchor point | |
* @param {number} y - The y coordinate of the anchor point | |
* @param {number} angle - The angle in radians (unless asDegrees is true) to rotate the Point by. | |
* @param {boolean} [asDegrees=false] - Is the given angle in radians (false) or degrees (true)? | |
* @param {number} [distance] - An optional distance constraint between the Point and the anchor. | |
* @return {Phaser.Point} The modified point object. | |
*/ | |
Phaser.Point.rotate = function (a, x, y, angle, asDegrees, distance) { | |
if (asDegrees) { angle = Phaser.Math.degToRad(angle); } | |
if (distance === undefined) | |
{ | |
a.subtract(x, y); | |
var s = Math.sin(angle); | |
var c = Math.cos(angle); | |
var tx = c * a.x - s * a.y; | |
var ty = s * a.x + c * a.y; | |
a.x = tx + x; | |
a.y = ty + y; | |
} | |
else | |
{ | |
var t = angle + Math.atan2(a.y - y, a.x - x); | |
a.x = x + distance * Math.cos(t); | |
a.y = y + distance * Math.sin(t); | |
} | |
return a; | |
}; | |
/** | |
* Calculates centroid (or midpoint) from an array of points. If only one point is provided, that point is returned. | |
* | |
* @method Phaser.Point.centroid | |
* @param {Phaser.Point[]} points - The array of one or more points. | |
* @param {Phaser.Point} [out] - Optional Point to store the value in, if not supplied a new Point object will be created. | |
* @return {Phaser.Point} The new Point object. | |
*/ | |
Phaser.Point.centroid = function (points, out) { | |
if (out === undefined) { out = new Phaser.Point(); } | |
if (Object.prototype.toString.call(points) !== '[object Array]') | |
{ | |
throw new Error("Phaser.Point. Parameter 'points' must be an array"); | |
} | |
var pointslength = points.length; | |
if (pointslength < 1) | |
{ | |
throw new Error("Phaser.Point. Parameter 'points' array must not be empty"); | |
} | |
if (pointslength === 1) | |
{ | |
out.copyFrom(points[0]); | |
return out; | |
} | |
for (var i = 0; i < pointslength; i++) | |
{ | |
Phaser.Point.add(out, points[i], out); | |
} | |
out.divide(pointslength, pointslength); | |
return out; | |
}; | |
/** | |
* Parses an object for x and/or y properties and returns a new Phaser.Point with matching values. | |
* If the object doesn't contain those properties a Point with x/y of zero will be returned. | |
* | |
* @method Phaser.Point.parse | |
* @static | |
* @param {object} obj - The object to parse. | |
* @param {string} [xProp='x'] - The property used to set the Point.x value. | |
* @param {string} [yProp='y'] - The property used to set the Point.y value. | |
* @return {Phaser.Point} The new Point object. | |
*/ | |
Phaser.Point.parse = function(obj, xProp, yProp) { | |
xProp = xProp || 'x'; | |
yProp = yProp || 'y'; | |
var point = new Phaser.Point(); | |
if (obj[xProp]) | |
{ | |
point.x = parseInt(obj[xProp], 10); | |
} | |
if (obj[yProp]) | |
{ | |
point.y = parseInt(obj[yProp], 10); | |
} | |
return point; | |
}; | |
// Because PIXI uses its own Point, we'll replace it with ours to avoid duplicating code or confusion. | |
PIXI.Point = Phaser.Point; | |
/** | |
* @author Richard Davey <[email protected]> | |
* @author Adrien Brault <[email protected]> | |
* @copyright 2016 Photon Storm Ltd. | |
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} | |
*/ | |
/** | |
* Creates a new Polygon. | |
* | |
* The points can be set from a variety of formats: | |
* | |
* - An array of Point objects: `[new Phaser.Point(x1, y1), ...]` | |
* - An array of objects with public x/y properties: `[obj1, obj2, ...]` | |
* - An array of paired numbers that represent point coordinates: `[x1,y1, x2,y2, ...]` | |
* - As separate Point arguments: `setTo(new Phaser.Point(x1, y1), ...)` | |
* - As separate objects with public x/y properties arguments: `setTo(obj1, obj2, ...)` | |
* - As separate arguments representing point coordinates: `setTo(x1,y1, x2,y2, ...)` | |
* | |
* @class Phaser.Polygon | |
* @constructor | |
* @param {Phaser.Point[]|number[]|...Phaser.Point|...number} points - The points to set. | |
*/ | |
Phaser.Polygon = function () { | |
/** | |
* @property {number} area - The area of this Polygon. | |
*/ | |
this.area = 0; | |
/** | |
* @property {array} _points - An array of Points that make up this Polygon. | |
* @private | |
*/ | |
this._points = []; | |
if (arguments.length > 0) | |
{ | |
this.setTo.apply(this, arguments); | |
} | |
/** | |
* @property {boolean} closed - Is the Polygon closed or not? | |
*/ | |
this.closed = true; | |
/** | |
* @property {boolean} flattened - Has this Polygon been flattened by a call to `Polygon.flatten` ? | |
*/ | |
this.flattened = false; | |
/** | |
* @property {number} type - The base object type. | |
*/ | |
this.type = Phaser.POLYGON; | |
}; | |
Phaser.Polygon.prototype = { | |
/** | |
* Export the points as an array of flat numbers, following the sequence [ x,y, x,y, x,y ] | |
* | |
* @method Phaser.Polygon#toNumberArray | |
* @param {array} [output] - The array to append the points to. If not specified a new array will be created. | |
* @return {array} The flattened array. | |
*/ | |
toNumberArray: function (output) { | |
if (output === undefined) { output = []; } | |
for (var i = 0; i < this._points.length; i++) | |
{ | |
if (typeof this._points[i] === 'number') | |
{ | |
output.push(this._points[i]); | |
output.push(this._points[i + 1]); | |
i++; | |
} | |
else | |
{ | |
output.push(this._points[i].x); | |
output.push(this._points[i].y); | |
} | |
} | |
return output; | |
}, | |
/** | |
* Flattens this Polygon so the points are a sequence of numbers. | |
* Any Point objects found are removed and replaced with two numbers. | |
* Also sets the Polygon.flattened property to `true`. | |
* | |
* @method Phaser.Polygon#flatten | |
* @return {Phaser.Polygon} This Polygon object | |
*/ | |
flatten: function () { | |
this._points = this.toNumberArray(); | |
this.flattened = true; | |
return this; | |
}, | |
/** | |
* Creates a copy of the given Polygon. | |
* This is a deep clone, the resulting copy contains new Phaser.Point objects | |
* | |
* @method Phaser.Polygon#clone | |
* @param {Phaser.Polygon} [output=(new Polygon)] - The polygon to update. If not specified a new polygon will be created. | |
* @return {Phaser.Polygon} The cloned (`output`) polygon object. | |
*/ | |
clone: function (output) { | |
var points = this._points.slice(); | |
if (output === undefined || output === null) | |
{ | |
output = new Phaser.Polygon(points); | |
} | |
else | |
{ | |
output.setTo(points); | |
} | |
return output; | |
}, | |
/** | |
* Checks whether the x and y coordinates are contained within this polygon. | |
* | |
* @method Phaser.Polygon#contains | |
* @param {number} x - The X value of the coordinate to test. | |
* @param {number} y - The Y value of the coordinate to test. | |
* @return {boolean} True if the coordinates are within this polygon, otherwise false. | |
*/ | |
contains: function (x, y) { | |
// Adapted from http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html by Jonas Raoni Soares Silva | |
var inside = false; | |
if (this.flattened) | |
{ | |
for (var i = -2, j = this._points.length - 2; (i += 2) < this._points.length; j = i) | |
{ | |
var ix = this._points[i]; | |
var iy = this._points[i + 1]; | |
var jx = this._points[j]; | |
var jy = this._points[j + 1]; | |
if (((iy <= y && y < jy) || (jy <= y && y < iy)) && (x < (jx - ix) * (y - iy) / (jy - iy) + ix)) | |
{ | |
inside = !inside; | |
} | |
} | |
} | |
else | |
{ | |
for (var i = -1, j = this._points.length - 1; ++i < this._points.length; j = i) | |
{ | |
var ix = this._points[i].x; | |
var iy = this._points[i].y; | |
var jx = this._points[j].x; | |
var jy = this._points[j].y; | |
if (((iy <= y && y < jy) || (jy <= y && y < iy)) && (x < (jx - ix) * (y - iy) / (jy - iy) + ix)) | |
{ | |
inside = !inside; | |
} | |
} | |
} | |
return inside; | |
}, | |
/** | |
* Sets this Polygon to the given points. | |
* | |
* The points can be set from a variety of formats: | |
* | |
* - An array of Point objects: `[new Phaser.Point(x1, y1), ...]` | |
* - An array of objects with public x/y properties: `[obj1, obj2, ...]` | |
* - An array of paired numbers that represent point coordinates: `[x1,y1, x2,y2, ...]` | |
* - An array of arrays with two elements representing x/y coordinates: `[[x1, y1], [x2, y2], ...]` | |
* - As separate Point arguments: `setTo(new Phaser.Point(x1, y1), ...)` | |
* - As separate objects with public x/y properties arguments: `setTo(obj1, obj2, ...)` | |
* - As separate arguments representing point coordinates: `setTo(x1,y1, x2,y2, ...)` | |
* | |
* `setTo` may also be called without any arguments to remove all points. | |
* | |
* @method Phaser.Polygon#setTo | |
* @param {Phaser.Point[]|number[]|...Phaser.Point|...number} points - The points to set. | |
* @return {Phaser.Polygon} This Polygon object | |
*/ | |
setTo: function (points) { | |
this.area = 0; | |
this._points = []; | |
if (arguments.length > 0) | |
{ | |
// If points isn't an array, use arguments as the array | |
if (!Array.isArray(points)) | |
{ | |
points = Array.prototype.slice.call(arguments); | |
} | |
var y0 = Number.MAX_VALUE; | |
// Allows for mixed-type arguments | |
for (var i = 0, len = points.length; i < len; i++) | |
{ | |
if (typeof points[i] === 'number') | |
{ | |
var p = new PIXI.Point(points[i], points[i + 1]); | |
i++; | |
} | |
else if (Array.isArray(points[i])) | |
{ | |
var p = new PIXI.Point(points[i][0], points[i][1]); | |
} | |
else | |
{ | |
var p = new PIXI.Point(points[i].x, points[i].y); | |
} | |
this._points.push(p); | |
// Lowest boundary | |
if (p.y < y0) | |
{ | |
y0 = p.y; | |
} | |
} | |
this.calculateArea(y0); | |
} | |
return this; | |
}, | |
/** | |
* Calcuates the area of the Polygon. This is available in the property Polygon.area | |
* | |
* @method Phaser.Polygon#calculateArea | |
* @private | |
* @param {number} y0 - The lowest boundary | |
* @return {number} The area of the Polygon. | |
*/ | |
calculateArea: function (y0) { | |
var p1; | |
var p2; | |
var avgHeight; | |
var width; | |
for (var i = 0, len = this._points.length; i < len; i++) | |
{ | |
p1 = this._points[i]; | |
if (i === len - 1) | |
{ | |
p2 = this._points[0]; | |
} | |
else | |
{ | |
p2 = this._points[i + 1]; | |
} | |
avgHeight = ((p1.y - y0) + (p2.y - y0)) / 2; | |
width = p1.x - p2.x; | |
this.area += avgHeight * width; | |
} | |
return this.area; | |
} | |
}; | |
Phaser.Polygon.prototype.constructor = Phaser.Polygon; | |
/** | |
* Sets and modifies the points of this polygon. | |
* | |
* See {@link Phaser.Polygon#setTo setTo} for the different kinds of arrays formats that can be assigned. | |
* | |
* @name Phaser.Polygon#points | |
* @property {Phaser.Point[]} points - The array of vertex points. | |
* @deprecated Use `setTo`. | |
*/ | |
Object.defineProperty(Phaser.Polygon.prototype, 'points', { | |
get: function() { | |
return this._points; | |
}, | |
set: function(points) { | |
if (points != null) | |
{ | |
this.setTo(points); | |
} | |
else | |
{ | |
// Clear the points | |
this.setTo(); | |
} | |
} | |
}); | |
// Because PIXI uses its own type, we'll replace it with ours to avoid duplicating code or confusion. | |
PIXI.Polygon = Phaser.Polygon; | |
/** | |
* @author Richard Davey <[email protected]> | |
* @copyright 2016 Photon Storm Ltd. | |
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} | |
*/ | |
/** | |
* Creates a new Rectangle object with the top-left corner specified by the x and y parameters and with the specified width and height parameters. | |
* If you call this function without parameters, a Rectangle with x, y, width, and height properties set to 0 is created. | |
* | |
* @class Phaser.Rectangle | |
* @constructor | |
* @param {number} x - The x coordinate of the top-left corner of the Rectangle. | |
* @param {number} y - The y coordinate of the top-left corner of the Rectangle. | |
* @param {number} width - The width of the Rectangle. Should always be either zero or a positive value. | |
* @param {number} height - The height of the Rectangle. Should always be either zero or a positive value. | |
*/ | |
Phaser.Rectangle = function (x, y, width, height) { | |
x = x || 0; | |
y = y || 0; | |
width = width || 0; | |
height = height || 0; | |
/** | |
* @property {number} x - The x coordinate of the top-left corner of the Rectangle. | |
*/ | |
this.x = x; | |
/** | |
* @property {number} y - The y coordinate of the top-left corner of the Rectangle. | |
*/ | |
this.y = y; | |
/** | |
* @property {number} width - The width of the Rectangle. This value should never be set to a negative. | |
*/ | |
this.width = width; | |
/** | |
* @property {number} height - The height of the Rectangle. This value should never be set to a negative. | |
*/ | |
this.height = height; | |
/** | |
* @property {number} type - The const type of this object. | |
* @readonly | |
*/ | |
this.type = Phaser.RECTANGLE; | |
}; | |
Phaser.Rectangle.prototype = { | |
/** | |
* Adjusts the location of the Rectangle object, as determined by its top-left corner, by the specified amounts. | |
* @method Phaser.Rectangle#offset | |
* @param {number} dx - Moves the x value of the Rectangle object by this amount. | |
* @param {number} dy - Moves the y value of the Rectangle object by this amount. | |
* @return {Phaser.Rectangle} This Rectangle object. | |
*/ | |
offset: function (dx, dy) { | |
this.x += dx; | |
this.y += dy; | |
return this; | |
}, | |
/** | |
* Adjusts the location of the Rectangle object using a Point object as a parameter. This method is similar to the Rectangle.offset() method, except that it takes a Point object as a parameter. | |
* @method Phaser.Rectangle#offsetPoint | |
* @param {Phaser.Point} point - A Point object to use to offset this Rectangle object. | |
* @return {Phaser.Rectangle} This Rectangle object. | |
*/ | |
offsetPoint: function (point) { | |
return this.offset(point.x, point.y); | |
}, | |
/** | |
* Sets the members of Rectangle to the specified values. | |
* @method Phaser.Rectangle#setTo | |
* @param {number} x - The x coordinate of the top-left corner of the Rectangle. | |
* @param {number} y - The y coordinate of the top-left corner of the Rectangle. | |
* @param {number} width - The width of the Rectangle. Should always be either zero or a positive value. | |
* @param {number} height - The height of the Rectangle. Should always be either zero or a positive value. | |
* @return {Phaser.Rectangle} This Rectangle object | |
*/ | |
setTo: function (x, y, width, height) { | |
this.x = x; | |
this.y = y; | |
this.width = width; | |
this.height = height; | |
return this; | |
}, | |
/** | |
* Scales the width and height of this Rectangle by the given amounts. | |
* | |
* @method Phaser.Rectangle#scale | |
* @param {number} x - The amount to scale the width of the Rectangle by. A value of 0.5 would reduce by half, a value of 2 would double the width, etc. | |
* @param {number} [y] - The amount to scale the height of the Rectangle by. A value of 0.5 would reduce by half, a value of 2 would double the height, etc. | |
* @return {Phaser.Rectangle} This Rectangle object | |
*/ | |
scale: function (x, y) { | |
if (y === undefined) { y = x; } | |
this.width *= x; | |
this.height *= y; | |
return this; | |
}, | |
/** | |
* Centers this Rectangle so that the center coordinates match the given x and y values. | |
* | |
* @method Phaser.Rectangle#centerOn | |
* @param {number} x - The x coordinate to place the center of the Rectangle at. | |
* @param {number} y - The y coordinate to place the center of the Rectangle at. | |
* @return {Phaser.Rectangle} This Rectangle object | |
*/ | |
centerOn: function (x, y) { | |
this.centerX = x; | |
this.centerY = y; | |
return this; | |
}, | |
/** | |
* Runs Math.floor() on both the x and y values of this Rectangle. | |
* @method Phaser.Rectangle#floor | |
*/ | |
floor: function () { | |
this.x = Math.floor(this.x); | |
this.y = Math.floor(this.y); | |
}, | |
/** | |
* Runs Math.floor() on the x, y, width and height values of this Rectangle. | |
* @method Phaser.Rectangle#floorAll | |
*/ | |
floorAll: function () { | |
this.x = Math.floor(this.x); | |
this.y = Math.floor(this.y); | |
this.width = Math.floor(this.width); | |
this.height = Math.floor(this.height); | |
}, | |
/** | |
* Runs Math.ceil() on both the x and y values of this Rectangle. | |
* @method Phaser.Rectangle#ceil | |
*/ | |
ceil: function () { | |
this.x = Math.ceil(this.x); | |
this.y = Math.ceil(this.y); | |
}, | |
/** | |
* Runs Math.ceil() on the x, y, width and height values of this Rectangle. | |
* @method Phaser.Rectangle#ceilAll | |
*/ | |
ceilAll: function () { | |
this.x = Math.ceil(this.x); | |
this.y = Math.ceil(this.y); | |
this.width = Math.ceil(this.width); | |
this.height = Math.ceil(this.height); | |
}, | |
/** | |
* Copies the x, y, width and height properties from any given object to this Rectangle. | |
* @method Phaser.Rectangle#copyFrom | |
* @param {any} source - The object to copy from. | |
* @return {Phaser.Rectangle} This Rectangle object. | |
*/ | |
copyFrom: function (source) { | |
return this.setTo(source.x, source.y, source.width, source.height); | |
}, | |
/** | |
* Copies the x, y, width and height properties from this Rectangle to any given object. | |
* @method Phaser.Rectangle#copyTo | |
* @param {any} source - The object to copy to. | |
* @return {object} This object. | |
*/ | |
copyTo: function (dest) { | |
dest.x = this.x; | |
dest.y = this.y; | |
dest.width = this.width; | |
dest.height = this.height; | |
return dest; | |
}, | |
/** | |
* Increases the size of the Rectangle object by the specified amounts. The center point of the Rectangle object stays the same, and its size increases to the left and right by the dx value, and to the top and the bottom by the dy value. | |
* @method Phaser.Rectangle#inflate | |
* @param {number} dx - The amount to be added to the left side of the Rectangle. | |
* @param {number} dy - The amount to be added to the bottom side of the Rectangle. | |
* @return {Phaser.Rectangle} This Rectangle object. | |
*/ | |
inflate: function (dx, dy) { | |
return Phaser.Rectangle.inflate(this, dx, dy); | |
}, | |
/** | |
* The size of the Rectangle object, expressed as a Point object with the values of the width and height properties. | |
* @method Phaser.Rectangle#size | |
* @param {Phaser.Point} [output] - Optional Point object. If given the values will be set into the object, otherwise a brand new Point object will be created and returned. | |
* @return {Phaser.Point} The size of the Rectangle object. | |
*/ | |
size: function (output) { | |
return Phaser.Rectangle.size(this, output); | |
}, | |
/** | |
* Resize the Rectangle by providing a new width and height. | |
* The x and y positions remain unchanged. | |
* | |
* @method Phaser.Rectangle#resize | |
* @param {number} width - The width of the Rectangle. Should always be either zero or a positive value. | |
* @param {number} height - The height of the Rectangle. Should always be either zero or a positive value. | |
* @return {Phaser.Rectangle} This Rectangle object | |
*/ | |
resize: function (width, height) { | |
this.width = width; | |
this.height = height; | |
return this; | |
}, | |
/** | |
* Returns a new Rectangle object with the same values for the x, y, width, and height properties as the original Rectangle object. | |
* @method Phaser.Rectangle#clone | |
* @param {Phaser.Rectangle} [output] - Optional Rectangle object. If given the values will be set into the object, otherwise a brand new Rectangle object will be created and returned. | |
* @return {Phaser.Rectangle} | |
*/ | |
clone: function (output) { | |
return Phaser.Rectangle.clone(this, output); | |
}, | |
/** | |
* Determines whether the specified coordinates are contained within the region defined by this Rectangle object. | |
* @method Phaser.Rectangle#contains | |
* @param {number} x - The x coordinate of the point to test. | |
* @param {number} y - The y coordinate of the point to test. | |
* @return {boolean} A value of true if the Rectangle object contains the specified point; otherwise false. | |
*/ | |
contains: function (x, y) { | |
return Phaser.Rectangle.contains(this, x, y); | |
}, | |
/** | |
* Determines whether the first Rectangle object is fully contained within the second Rectangle object. | |
* A Rectangle object is said to contain another if the second Rectangle object falls entirely within the boundaries of the first. | |
* @method Phaser.Rectangle#containsRect | |
* @param {Phaser.Rectangle} b - The second Rectangle object. | |
* @return {boolean} A value of true if the Rectangle object contains the specified point; otherwise false. | |
*/ | |
containsRect: function (b) { | |
return Phaser.Rectangle.containsRect(b, this); | |
}, | |
/** | |
* Determines whether the two Rectangles are equal. | |
* This method compares the x, y, width and height properties of each Rectangle. | |
* @method Phaser.Rectangle#equals | |
* @param {Phaser.Rectangle} b - The second Rectangle object. | |
* @return {boolean} A value of true if the two Rectangles have exactly the same values for the x, y, width and height properties; otherwise false. | |
*/ | |
equals: function (b) { | |
return Phaser.Rectangle.equals(this, b); | |
}, | |
/** | |
* If the Rectangle object specified in the toIntersect parameter intersects with this Rectangle object, returns the area of intersection as a Rectangle object. If the Rectangles do not intersect, this method returns an empty Rectangle object with its properties set to 0. | |
* @method Phaser.Rectangle#intersection | |
* @param {Phaser.Rectangle} b - The second Rectangle object. | |
* @param {Phaser.Rectangle} out - Optional Rectangle object. If given the intersection values will be set into this object, otherwise a brand new Rectangle object will be created and returned. | |
* @return {Phaser.Rectangle} A Rectangle object that equals the area of intersection. If the Rectangles do not intersect, this method returns an empty Rectangle object; that is, a Rectangle with its x, y, width, and height properties set to 0. | |
*/ | |
intersection: function (b, out) { | |
return Phaser.Rectangle.intersection(this, b, out); | |
}, | |
/** | |
* Determines whether this Rectangle and another given Rectangle intersect with each other. | |
* This method checks the x, y, width, and height properties of the two Rectangles. | |
* | |
* @method Phaser.Rectangle#intersects | |
* @param {Phaser.Rectangle} b - The second Rectangle object. | |
* @return {boolean} A value of true if the specified object intersects with this Rectangle object; otherwise false. | |
*/ | |
intersects: function (b) { | |
return Phaser.Rectangle.intersects(this, b); | |
}, | |
/** | |
* Determines whether the coordinates given intersects (overlaps) with this Rectangle. | |
* | |
* @method Phaser.Rectangle#intersectsRaw | |
* @param {number} left - The x coordinate of the left of the area. | |
* @param {number} right - The right coordinate of the area. | |
* @param {number} top - The y coordinate of the area. | |
* @param {number} bottom - The bottom coordinate of the area. | |
* @param {number} tolerance - A tolerance value to allow for an intersection test with padding, default to 0 | |
* @return {boolean} A value of true if the specified object intersects with the Rectangle; otherwise false. | |
*/ | |
intersectsRaw: function (left, right, top, bottom, tolerance) { | |
return Phaser.Rectangle.intersectsRaw(this, left, right, top, bottom, tolerance); | |
}, | |
/** | |
* Adds two Rectangles together to create a new Rectangle object, by filling in the horizontal and vertical space between the two Rectangles. | |
* @method Phaser.Rectangle#union | |
* @param {Phaser.Rectangle} b - The second Rectangle object. | |
* @param {Phaser.Rectangle} [out] - Optional Rectangle object. If given the new values will be set into this object, otherwise a brand new Rectangle object will be created and returned. | |
* @return {Phaser.Rectangle} A Rectangle object that is the union of the two Rectangles. | |
*/ | |
union: function (b, out) { | |
return Phaser.Rectangle.union(this, b, out); | |
}, | |
/** | |
* Returns a uniformly distributed random point from anywhere within this Rectangle. | |
* | |
* @method Phaser.Rectangle#random | |
* @param {Phaser.Point|object} [out] - A Phaser.Point, or any object with public x/y properties, that the values will be set in. | |
* If no object is provided a new Phaser.Point object will be created. In high performance areas avoid this by re-using an existing object. | |
* @return {Phaser.Point} An object containing the random point in its `x` and `y` properties. | |
*/ | |
random: function (out) { | |
if (out === undefined) { out = new Phaser.Point(); } | |
out.x = this.randomX; | |
out.y = this.randomY; | |
return out; | |
}, | |
/** | |
* Returns a point based on the given position constant, which can be one of: | |
* | |
* `Phaser.TOP_LEFT`, `Phaser.TOP_CENTER`, `Phaser.TOP_RIGHT`, `Phaser.LEFT_CENTER`, | |
* `Phaser.CENTER`, `Phaser.RIGHT_CENTER`, `Phaser.BOTTOM_LEFT`, `Phaser.BOTTOM_CENTER` | |
* and `Phaser.BOTTOM_RIGHT`. | |
* | |
* This method returns the same values as calling Rectangle.bottomLeft, etc, but those | |
* calls always create a new Point object, where-as this one allows you to use your own. | |
* | |
* @method Phaser.Rectangle#getPoint | |
* @param {integer} [position] - One of the Phaser position constants, such as `Phaser.TOP_RIGHT`. | |
* @param {Phaser.Point} [out] - A Phaser.Point that the values will be set in. | |
* If no object is provided a new Phaser.Point object will be created. In high performance areas avoid this by re-using an existing object. | |
* @return {Phaser.Point} An object containing the point in its `x` and `y` properties. | |
*/ | |
getPoint: function (position, out) { | |
if (out === undefined) { out = new Phaser.Point(); } | |
switch (position) | |
{ | |
default: | |
case Phaser.TOP_LEFT: | |
return out.set(this.x, this.y); | |
case Phaser.TOP_CENTER: | |
return out.set(this.centerX, this.y); | |
case Phaser.TOP_RIGHT: | |
return out.set(this.right, this.y); | |
case Phaser.LEFT_CENTER: | |
return out.set(this.x, this.centerY); | |
case Phaser.CENTER: | |
return out.set(this.centerX, this.centerY); | |
case Phaser.RIGHT_CENTER: | |
return out.set(this.right, this.centerY); | |
case Phaser.BOTTOM_LEFT: | |
return out.set(this.x, this.bottom); | |
case Phaser.BOTTOM_CENTER: | |
return out.set(this.centerX, this.bottom); | |
case Phaser.BOTTOM_RIGHT: | |
return out.set(this.right, this.bottom); | |
} | |
}, | |
/** | |
* Returns a string representation of this object. | |
* @method Phaser.Rectangle#toString | |
* @return {string} A string representation of the instance. | |
*/ | |
toString: function () { | |
return "[{Rectangle (x=" + this.x + " y=" + this.y + " width=" + this.width + " height=" + this.height + " empty=" + this.empty + ")}]"; | |
} | |
}; | |
/** | |
* @name Phaser.Rectangle#halfWidth | |
* @property {number} halfWidth - Half of the width of the Rectangle. | |
* @readonly | |
*/ | |
Object.defineProperty(Phaser.Rectangle.prototype, "halfWidth", { | |
get: function () { | |
return Math.round(this.width / 2); | |
} | |
}); | |
/** | |
* @name Phaser.Rectangle#halfHeight | |
* @property {number} halfHeight - Half of the height of the Rectangle. | |
* @readonly | |
*/ | |
Object.defineProperty(Phaser.Rectangle.prototype, "halfHeight", { | |
get: function () { | |
return Math.round(this.height / 2); | |
} | |
}); | |
/** | |
* The sum of the y and height properties. Changing the bottom property of a Rectangle object has no effect on the x, y and width properties, but does change the height property. | |
* @name Phaser.Rectangle#bottom | |
* @property {number} bottom - The sum of the y and height properties. | |
*/ | |
Object.defineProperty(Phaser.Rectangle.prototype, "bottom", { | |
get: function () { | |
return this.y + this.height; | |
}, | |
set: function (value) { | |
if (value <= this.y) | |
{ | |
this.height = 0; | |
} | |
else | |
{ | |
this.height = value - this.y; | |
} | |
} | |
}); | |
/** | |
* The location of the Rectangles bottom left corner as a Point object. | |
* @name Phaser.Rectangle#bottomLeft | |
* @property {Phaser.Point} bottomLeft - Gets or sets the location of the Rectangles bottom left corner as a Point object. | |
*/ | |
Object.defineProperty(Phaser.Rectangle.prototype, "bottomLeft", { | |
get: function () { | |
return new Phaser.Point(this.x, this.bottom); | |
}, | |
set: function (value) { | |
this.x = value.x; | |
this.bottom = value.y; | |
} | |
}); | |
/** | |
* The location of the Rectangles bottom right corner as a Point object. | |
* @name Phaser.Rectangle#bottomRight | |
* @property {Phaser.Point} bottomRight - Gets or sets the location of the Rectangles bottom right corner as a Point object. | |
*/ | |
Object.defineProperty(Phaser.Rectangle.prototype, "bottomRight", { | |
get: function () { | |
return new Phaser.Point(this.right, this.bottom); | |
}, | |
set: function (value) { | |
this.right = value.x; | |
this.bottom = value.y; | |
} | |
}); | |
/** | |
* The x coordinate of the left of the Rectangle. Changing the left property of a Rectangle object has no effect on the y and height properties. However it does affect the width property, whereas changing the x value does not affect the width property. | |
* @name Phaser.Rectangle#left | |
* @property {number} left - The x coordinate of the left of the Rectangle. | |
*/ | |
Object.defineProperty(Phaser.Rectangle.prototype, "left", { | |
get: function () { | |
return this.x; | |
}, | |
set: function (value) { | |
if (value >= this.right) { | |
this.width = 0; | |
} else { | |
this.width = this.right - value; | |
} | |
this.x = value; | |
} | |
}); | |
/** | |
* The sum of the x and width properties. Changing the right property of a Rectangle object has no effect on the x, y and height properties, however it does affect the width property. | |
* @name Phaser.Rectangle#right | |
* @property {number} right - The sum of the x and width properties. | |
*/ | |
Object.defineProperty(Phaser.Rectangle.prototype, "right", { | |
get: function () { | |
return this.x + this.width; | |
}, | |
set: function (value) { | |
if (value <= this.x) { | |
this.width = 0; | |
} else { | |
this.width = value - this.x; | |
} | |
} | |
}); | |
/** | |
* The volume of the Rectangle derived from width * height. | |
* @name Phaser.Rectangle#volume | |
* @property {number} volume - The volume of the Rectangle derived from width * height. | |
* @readonly | |
*/ | |
Object.defineProperty(Phaser.Rectangle.prototype, "volume", { | |
get: function () { | |
return this.width * this.height; | |
} | |
}); | |
/** | |
* The perimeter size of the Rectangle. This is the sum of all 4 sides. | |
* @name Phaser.Rectangle#perimeter | |
* @property {number} perimeter - The perimeter size of the Rectangle. This is the sum of all 4 sides. | |
* @readonly | |
*/ | |
Object.defineProperty(Phaser.Rectangle.prototype, "perimeter", { | |
get: function () { | |
return (this.width * 2) + (this.height * 2); | |
} | |
}); | |
/** | |
* The x coordinate of the center of the Rectangle. | |
* @name Phaser.Rectangle#centerX | |
* @property {number} centerX - The x coordinate of the center of the Rectangle. | |
*/ | |
Object.defineProperty(Phaser.Rectangle.prototype, "centerX", { | |
get: function () { | |
return this.x + this.halfWidth; | |
}, | |
set: function (value) { | |
this.x = value - this.halfWidth; | |
} | |
}); | |
/** | |
* The y coordinate of the center of the Rectangle. | |
* @name Phaser.Rectangle#centerY | |
* @property {number} centerY - The y coordinate of the center of the Rectangle. | |
*/ | |
Object.defineProperty(Phaser.Rectangle.prototype, "centerY", { | |
get: function () { | |
return this.y + this.halfHeight; | |
}, | |
set: function (value) { | |
this.y = value - this.halfHeight; | |
} | |
}); | |
/** | |
* A random value between the left and right values (inclusive) of the Rectangle. | |
* | |
* @name Phaser.Rectangle#randomX | |
* @property {number} randomX - A random value between the left and right values (inclusive) of the Rectangle. | |
*/ | |
Object.defineProperty(Phaser.Rectangle.prototype, "randomX", { | |
get: function () { | |
return this.x + (Math.random() * this.width); | |
} | |
}); | |
/** | |
* A random value between the top and bottom values (inclusive) of the Rectangle. | |
* | |
* @name Phaser.Rectangle#randomY | |
* @property {number} randomY - A random value between the top and bottom values (inclusive) of the Rectangle. | |
*/ | |
Object.defineProperty(Phaser.Rectangle.prototype, "randomY", { | |
get: function () { | |
return this.y + (Math.random() * this.height); | |
} | |
}); | |
/** | |
* The y coordinate of the top of the Rectangle. Changing the top property of a Rectangle object has no effect on the x and width properties. | |
* However it does affect the height property, whereas changing the y value does not affect the height property. | |
* @name Phaser.Rectangle#top | |
* @property {number} top - The y coordinate of the top of the Rectangle. | |
*/ | |
Object.defineProperty(Phaser.Rectangle.prototype, "top", { | |
get: function () { | |
return this.y; | |
}, | |
set: function (value) { | |
if (value >= this.bottom) { | |
this.height = 0; | |
this.y = value; | |
} else { | |
this.height = (this.bottom - value); | |
} | |
} | |
}); | |
/** | |
* The location of the Rectangles top left corner as a Point object. | |
* @name Phaser.Rectangle#topLeft | |
* @property {Phaser.Point} topLeft - The location of the Rectangles top left corner as a Point object. | |
*/ | |
Object.defineProperty(Phaser.Rectangle.prototype, "topLeft", { | |
get: function () { | |
return new Phaser.Point(this.x, this.y); | |
}, | |
set: function (value) { | |
this.x = value.x; | |
this.y = value.y; | |
} | |
}); | |
/** | |
* The location of the Rectangles top right corner as a Point object. | |
* @name Phaser.Rectangle#topRight | |
* @property {Phaser.Point} topRight - The location of the Rectangles top left corner as a Point object. | |
*/ | |
Object.defineProperty(Phaser.Rectangle.prototype, "topRight", { | |
get: function () { | |
return new Phaser.Point(this.x + this.width, this.y); | |
}, | |
set: function (value) { | |
this.right = value.x; | |
this.y = value.y; | |
} | |
}); | |
/** | |
* Determines whether or not this Rectangle object is empty. A Rectangle object is empty if its width or height is less than or equal to 0. | |
* If set to true then all of the Rectangle properties are set to 0. | |
* @name Phaser.Rectangle#empty | |
* @property {boolean} empty - Gets or sets the Rectangles empty state. | |
*/ | |
Object.defineProperty(Phaser.Rectangle.prototype, "empty", { | |
get: function () { | |
return (!this.width || !this.height); | |
}, | |
set: function (value) { | |
if (value === true) | |
{ | |
this.setTo(0, 0, 0, 0); | |
} | |
} | |
}); | |
Phaser.Rectangle.prototype.constructor = Phaser.Rectangle; | |
/** | |
* Increases the size of the Rectangle object by the specified amounts. The center point of the Rectangle object stays the same, and its size increases to the left and right by the dx value, and to the top and the bottom by the dy value. | |
* @method Phaser.Rectangle.inflate | |
* @param {Phaser.Rectangle} a - The Rectangle object. | |
* @param {number} dx - The amount to be added to the left side of the Rectangle. | |
* @param {number} dy - The amount to be added to the bottom side of the Rectangle. | |
* @return {Phaser.Rectangle} This Rectangle object. | |
*/ | |
Phaser.Rectangle.inflate = function (a, dx, dy) { | |
a.x -= dx; | |
a.width += 2 * dx; | |
a.y -= dy; | |
a.height += 2 * dy; | |
return a; | |
}; | |
/** | |
* Increases the size of the Rectangle object. This method is similar to the Rectangle.inflate() method except it takes a Point object as a parameter. | |
* @method Phaser.Rectangle.inflatePoint | |
* @param {Phaser.Rectangle} a - The Rectangle object. | |
* @param {Phaser.Point} point - The x property of this Point object is used to increase the horizontal dimension of the Rectangle object. The y property is used to increase the vertical dimension of the Rectangle object. | |
* @return {Phaser.Rectangle} The Rectangle object. | |
*/ | |
Phaser.Rectangle.inflatePoint = function (a, point) { | |
return Phaser.Rectangle.inflate(a, point.x, point.y); | |
}; | |
/** | |
* The size of the Rectangle object, expressed as a Point object with the values of the width and height properties. | |
* @method Phaser.Rectangle.size | |
* @param {Phaser.Rectangle} a - The Rectangle object. | |
* @param {Phaser.Point} [output] - Optional Point object. If given the values will be set into the object, otherwise a brand new Point object will be created and returned. | |
* @return {Phaser.Point} The size of the Rectangle object | |
*/ | |
Phaser.Rectangle.size = function (a, output) { | |
if (output === undefined || output === null) | |
{ | |
output = new Phaser.Point(a.width, a.height); | |
} | |
else | |
{ | |
output.setTo(a.width, a.height); | |
} | |
return output; | |
}; | |
/** | |
* Returns a new Rectangle object with the same values for the x, y, width, and height properties as the original Rectangle object. | |
* @method Phaser.Rectangle.clone | |
* @param {Phaser.Rectangle} a - The Rectangle object. | |
* @param {Phaser.Rectangle} [output] - Optional Rectangle object. If given the values will be set into the object, otherwise a brand new Rectangle object will be created and returned. | |
* @return {Phaser.Rectangle} | |
*/ | |
Phaser.Rectangle.clone = function (a, output) { | |
if (output === undefined || output === null) | |
{ | |
output = new Phaser.Rectangle(a.x, a.y, a.width, a.height); | |
} | |
else | |
{ | |
output.setTo(a.x, a.y, a.width, a.height); | |
} | |
return output; | |
}; | |
/** | |
* Determines whether the specified coordinates are contained within the region defined by this Rectangle object. | |
* @method Phaser.Rectangle.contains | |
* @param {Phaser.Rectangle} a - The Rectangle object. | |
* @param {number} x - The x coordinate of the point to test. | |
* @param {number} y - The y coordinate of the point to test. | |
* @return {boolean} A value of true if the Rectangle object contains the specified point; otherwise false. | |
*/ | |
Phaser.Rectangle.contains = function (a, x, y) { | |
if (a.width <= 0 || a.height <= 0) | |
{ | |
return false; | |
} | |
return (x >= a.x && x < a.right && y >= a.y && y < a.bottom); | |
}; | |
/** | |
* Determines whether the specified coordinates are contained within the region defined by the given raw values. | |
* @method Phaser.Rectangle.containsRaw | |
* @param {number} rx - The x coordinate of the top left of the area. | |
* @param {number} ry - The y coordinate of the top left of the area. | |
* @param {number} rw - The width of the area. | |
* @param {number} rh - The height of the area. | |
* @param {number} x - The x coordinate of the point to test. | |
* @param {number} y - The y coordinate of the point to test. | |
* @return {boolean} A value of true if the Rectangle object contains the specified point; otherwise false. | |
*/ | |
Phaser.Rectangle.containsRaw = function (rx, ry, rw, rh, x, y) { | |
return (x >= rx && x < (rx + rw) && y >= ry && y < (ry + rh)); | |
}; | |
/** | |
* Determines whether the specified point is contained within the rectangular region defined by this Rectangle object. This method is similar to the Rectangle.contains() method, except that it takes a Point object as a parameter. | |
* @method Phaser.Rectangle.containsPoint | |
* @param {Phaser.Rectangle} a - The Rectangle object. | |
* @param {Phaser.Point} point - The point object being checked. Can be Point or any object with .x and .y values. | |
* @return {boolean} A value of true if the Rectangle object contains the specified point; otherwise false. | |
*/ | |
Phaser.Rectangle.containsPoint = function (a, point) { | |
return Phaser.Rectangle.contains(a, point.x, point.y); | |
}; | |
/** | |
* Determines whether the first Rectangle object is fully contained within the second Rectangle object. | |
* A Rectangle object is said to contain another if the second Rectangle object falls entirely within the boundaries of the first. | |
* @method Phaser.Rectangle.containsRect | |
* @param {Phaser.Rectangle} a - The first Rectangle object. | |
* @param {Phaser.Rectangle} b - The second Rectangle object. | |
* @return {boolean} A value of true if the Rectangle object contains the specified point; otherwise false. | |
*/ | |
Phaser.Rectangle.containsRect = function (a, b) { | |
// If the given rect has a larger volume than this one then it can never contain it | |
if (a.volume > b.volume) | |
{ | |
return false; | |
} | |
return (a.x >= b.x && a.y >= b.y && a.right < b.right && a.bottom < b.bottom); | |
}; | |
/** | |
* Determines whether the two Rectangles are equal. | |
* This method compares the x, y, width and height properties of each Rectangle. | |
* @method Phaser.Rectangle.equals | |
* @param {Phaser.Rectangle} a - The first Rectangle object. | |
* @param {Phaser.Rectangle} b - The second Rectangle object. | |
* @return {boolean} A value of true if the two Rectangles have exactly the same values for the x, y, width and height properties; otherwise false. | |
*/ | |
Phaser.Rectangle.equals = function (a, b) { | |
return (a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height); | |
}; | |
/** | |
* Determines if the two objects (either Rectangles or Rectangle-like) have the same width and height values under strict equality. | |
* @method Phaser.Rectangle.sameDimensions | |
* @param {Rectangle-like} a - The first Rectangle object. | |
* @param {Rectangle-like} b - The second Rectangle object. | |
* @return {boolean} True if the object have equivalent values for the width and height properties. | |
*/ | |
Phaser.Rectangle.sameDimensions = function (a, b) { | |
return (a.width === b.width && a.height === b.height); | |
}; | |
/** | |
* If the Rectangle object specified in the toIntersect parameter intersects with this Rectangle object, returns the area of intersection as a Rectangle object. If the Rectangles do not intersect, this method returns an empty Rectangle object with its properties set to 0. | |
* @method Phaser.Rectangle.intersection | |
* @param {Phaser.Rectangle} a - The first Rectangle object. | |
* @param {Phaser.Rectangle} b - The second Rectangle object. | |
* @param {Phaser.Rectangle} [output] - Optional Rectangle object. If given the intersection values will be set into this object, otherwise a brand new Rectangle object will be created and returned. | |
* @return {Phaser.Rectangle} A Rectangle object that equals the area of intersection. If the Rectangles do not intersect, this method returns an empty Rectangle object; that is, a Rectangle with its x, y, width, and height properties set to 0. | |
*/ | |
Phaser.Rectangle.intersection = function (a, b, output) { | |
if (output === undefined) | |
{ | |
output = new Phaser.Rectangle(); | |
} | |
if (Phaser.Rectangle.intersects(a, b)) | |
{ | |
output.x = Math.max(a.x, b.x); | |
output.y = Math.max(a.y, b.y); | |
output.width = Math.min(a.right, b.right) - output.x; | |
output.height = Math.min(a.bottom, b.bottom) - output.y; | |
} | |
return output; | |
}; | |
/** | |
* Determines whether the two Rectangles intersect with each other. | |
* This method checks the x, y, width, and height properties of the Rectangles. | |
* @method Phaser.Rectangle.intersects | |
* @param {Phaser.Rectangle} a - The first Rectangle object. | |
* @param {Phaser.Rectangle} b - The second Rectangle object. | |
* @return {boolean} A value of true if the specified object intersects with this Rectangle object; otherwise false. | |
*/ | |
Phaser.Rectangle.intersects = function (a, b) { | |
if (a.width <= 0 || a.height <= 0 || b.width <= 0 || b.height <= 0) | |
{ | |
return false; | |
} | |
return !(a.right < b.x || a.bottom < b.y || a.x > b.right || a.y > b.bottom); | |
}; | |
/** | |
* Determines whether the object specified intersects (overlaps) with the given values. | |
* @method Phaser.Rectangle.intersectsRaw | |
* @param {number} left - The x coordinate of the left of the area. | |
* @param {number} right - The right coordinate of the area. | |
* @param {number} top - The y coordinate of the area. | |
* @param {number} bottom - The bottom coordinate of the area. | |
* @param {number} tolerance - A tolerance value to allow for an intersection test with padding, default to 0 | |
* @return {boolean} A value of true if the specified object intersects with the Rectangle; otherwise false. | |
*/ | |
Phaser.Rectangle.intersectsRaw = function (a, left, right, top, bottom, tolerance) { | |
if (tolerance === undefined) { tolerance = 0; } | |
return !(left > a.right + tolerance || right < a.left - tolerance || top > a.bottom + tolerance || bottom < a.top - tolerance); | |
}; | |
/** | |
* Adds two Rectangles together to create a new Rectangle object, by filling in the horizontal and vertical space between the two Rectangles. | |
* @method Phaser.Rectangle.union | |
* @param {Phaser.Rectangle} a - The first Rectangle object. | |
* @param {Phaser.Rectangle} b - The second Rectangle object. | |
* @param {Phaser.Rectangle} [output] - Optional Rectangle object. If given the new values will be set into this object, otherwise a brand new Rectangle object will be created and returned. | |
* @return {Phaser.Rectangle} A Rectangle object that is the union of the two Rectangles. | |
*/ | |
Phaser.Rectangle.union = function (a, b, output) { | |
if (output === undefined) | |
{ | |
output = new Phaser.Rectangle(); | |
} | |
return output.setTo(Math.min(a.x, b.x), Math.min(a.y, b.y), Math.max(a.right, b.right) - Math.min(a.left, b.left), Math.max(a.bottom, b.bottom) - Math.min(a.top, b.top)); | |
}; | |
/** | |
* Calculates the Axis Aligned Bounding Box (or aabb) from an array of points. | |
* | |
* @method Phaser.Rectangle#aabb | |
* @param {Phaser.Point[]} points - The array of one or more points. | |
* @param {Phaser.Rectangle} [out] - Optional Rectangle to store the value in, if not supplied a new Rectangle object will be created. | |
* @return {Phaser.Rectangle} The new Rectangle object. | |
* @static | |
*/ | |
Phaser.Rectangle.aabb = function(points, out) { | |
if (out === undefined) { | |
out = new Phaser.Rectangle(); | |
} | |
var xMax = Number.NEGATIVE_INFINITY, | |
xMin = Number.POSITIVE_INFINITY, | |
yMax = Number.NEGATIVE_INFINITY, | |
yMin = Number.POSITIVE_INFINITY; | |
points.forEach(function(point) { | |
if (point.x > xMax) { | |
xMax = point.x; | |
} | |
if (point.x < xMin) { | |
xMin = point.x; | |
} | |
if (point.y > yMax) { | |
yMax = point.y; | |
} | |
if (point.y < yMin) { | |
yMin = point.y; | |
} | |
}); | |
out.setTo(xMin, yMin, xMax - xMin, yMax - yMin); | |
return out; | |
}; | |
// Because PIXI uses its own Rectangle, we'll replace it with ours to avoid duplicating code or confusion. | |
PIXI.Rectangle = Phaser.Rectangle; | |
PIXI.EmptyRectangle = new Phaser.Rectangle(0, 0, 0, 0); | |
/** | |
* @author Mat Groves http://matgroves.com/ | |
* @author Richard Davey <[email protected]> | |
* @copyright 2016 Photon Storm Ltd. | |
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} | |
*/ | |
/** | |
* The Rounded Rectangle object is an area defined by its position and has nice rounded corners, | |
* as indicated by its top-left corner point (x, y) and by its width and its height. | |
* | |
* @class Phaser.RoundedRectangle | |
* @constructor | |
* @param {number} [x=0] - The x coordinate of the top-left corner of the Rectangle. | |
* @param {number} [y=0] - The y coordinate of the top-left corner of the Rectangle. | |
* @param {number} [width=0] - The width of the Rectangle. Should always be either zero or a positive value. | |
* @param {number} [height=0] - The height of the Rectangle. Should always be either zero or a positive value. | |
* @param {number} [radius=20] - Controls the radius of the rounded corners. | |
*/ | |
Phaser.RoundedRectangle = function(x, y, width, height, radius) | |
{ | |
if (x === undefined) { x = 0; } | |
if (y === undefined) { y = 0; } | |
if (width === undefined) { width = 0; } | |
if (height === undefined) { height = 0; } | |
if (radius === undefined) { radius = 20; } | |
/** | |
* @property {number} x - The x coordinate of the top-left corner of the Rectangle. | |
*/ | |
this.x = x; | |
/** | |
* @property {number} y - The y coordinate of the top-left corner of the Rectangle. | |
*/ | |
this.y = y; | |
/** | |
* @property {number} width - The width of the Rectangle. This value should never be set to a negative. | |
*/ | |
this.width = width; | |
/** | |
* @property {number} height - The height of the Rectangle. This value should never be set to a negative. | |
*/ | |
this.height = height; | |
/** | |
* @property {number} radius - The radius of the rounded corners. | |
*/ | |
this.radius = radius || 20; | |
/** | |
* @property {number} type - The const type of this object. | |
* @readonly | |
*/ | |
this.type = Phaser.ROUNDEDRECTANGLE; | |
}; | |
Phaser.RoundedRectangle.prototype = { | |
/** | |
* Returns a new RoundedRectangle object with the same values for the x, y, width, height and | |
* radius properties as this RoundedRectangle object. | |
* | |
* @method Phaser.RoundedRectangle#clone | |
* @return {Phaser.RoundedRectangle} | |
*/ | |
clone: function () { | |
return new Phaser.RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); | |
}, | |
/** | |
* Determines whether the specified coordinates are contained within the region defined by this Rounded Rectangle object. | |
* | |
* @method Phaser.RoundedRectangle#contains | |
* @param {number} x - The x coordinate of the point to test. | |
* @param {number} y - The y coordinate of the point to test. | |
* @return {boolean} A value of true if the RoundedRectangle Rectangle object contains the specified point; otherwise false. | |
*/ | |
contains: function (x, y) { | |
if (this.width <= 0 || this.height <= 0) | |
{ | |
return false; | |
} | |
var x1 = this.x; | |
if (x >= x1 && x <= x1 + this.width) | |
{ | |
var y1 = this.y; | |
if (y >= y1 && y <= y1 + this.height) | |
{ | |
return true; | |
} | |
} | |
return false; | |
} | |
}; | |
Phaser.RoundedRectangle.prototype.constructor = Phaser.RoundedRectangle; | |
// Because PIXI uses its own type, we'll replace it with ours to avoid duplicating code or confusion. | |
PIXI.RoundedRectangle = Phaser.RoundedRectangle; | |
/** | |
* @author Richard Davey <[email protected]> | |
* @copyright 2016 Photon Storm Ltd. | |
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} | |
*/ | |
/** | |
* A Camera is your view into the game world. It has a position and size and renders only those objects within its field of view. | |
* The game automatically creates a single Stage sized camera on boot. Move the camera around the world with Phaser.Camera.x/y | |
* | |
* @class Phaser.Camera | |
* @constructor | |
* @param {Phaser.Game} game - Game reference to the currently running game. | |
* @param {number} id - Not being used at the moment, will be when Phaser supports multiple camera | |
* @param {number} x - Position of the camera on the X axis | |
* @param {number} y - Position of the camera on the Y axis | |
* @param {number} width - The width of the view rectangle | |
* @param {number} height - The height of the view rectangle | |
*/ | |
Phaser.Camera = function (game, id, x, y, width, height) { | |
/** | |
* @property {Phaser.Game} game - A reference to the currently running Game. | |
*/ | |
this.game = game; | |
/** | |
* @property {Phaser.World} world - A reference to the game world. | |
*/ | |
this.world = game.world; | |
/** | |
* @property {number} id - Reserved for future multiple camera set-ups. | |
* @default | |
*/ | |
this.id = 0; | |
/** | |
* Camera view. | |
* The view into the world we wish to render (by default the game dimensions). | |
* The x/y values are in world coordinates, not screen coordinates, the width/height is how many pixels to render. | |
* Sprites outside of this view are not rendered if Sprite.autoCull is set to `true`. Otherwise they are always rendered. | |
* @property {Phaser.Rectangle} view | |
*/ | |
this.view = new Phaser.Rectangle(x, y, width, height); | |
/** | |
* The Camera is bound to this Rectangle and cannot move outside of it. By default it is enabled and set to the size of the World. | |
* The Rectangle can be located anywhere in the world and updated as often as you like. If you don't wish the Camera to be bound | |
* at all then set this to null. The values can be anything and are in World coordinates, with 0,0 being the top-left of the world. | |
* | |
* @property {Phaser.Rectangle} bounds - The Rectangle in which the Camera is bounded. Set to null to allow for movement anywhere. | |
*/ | |
this.bounds = new Phaser.Rectangle(x, y, width, height); | |
/** | |
* @property {Phaser.Rectangle} deadzone - Moving inside this Rectangle will not cause the camera to move. | |
*/ | |
this.deadzone = null; | |
/** | |
* @property {boolean} visible - Whether this camera is visible or not. | |
* @default | |
*/ | |
this.visible = true; | |
/** | |
* @property {boolean} roundPx - If a Camera has roundPx set to `true` it will call `view.floor` as part of its update loop, keeping its boundary to integer values. Set this to `false` to disable this from happening. | |
* @default | |
*/ | |
this.roundPx = true; | |
/** | |
* @property {boolean} atLimit - Whether this camera is flush with the World Bounds or not. | |
*/ | |
this.atLimit = { x: false, y: false }; | |
/** | |
* @property {Phaser.Sprite} target - If the camera is tracking a Sprite, this is a reference to it, otherwise null. | |
* @default | |
*/ | |
this.target = null; | |
/** | |
* @property {PIXI.DisplayObject} displayObject - The display object to which all game objects are added. Set by World.boot. | |
*/ | |
this.displayObject = null; | |
/** | |
* @property {Phaser.Point} scale - The scale of the display object to which all game objects are added. Set by World.boot. | |
*/ | |
this.scale = null; | |
/** | |
* @property {number} totalInView - The total number of Sprites with `autoCull` set to `true` that are visible by this Camera. | |
* @readonly | |
*/ | |
this.totalInView = 0; | |
/** | |
* The linear interpolation value to use when following a target. | |
* The default values of 1 means the camera will instantly snap to the target coordinates. | |
* A lower value, such as 0.1 means the camera will more slowly track the target, giving | |
* a smooth transition. You can set the horizontal and vertical values independently, and also | |
* adjust this value in real-time during your game. | |
* @property {Phaser.Point} lerp | |
* @default | |
*/ | |
this.lerp = new Phaser.Point(1, 1); | |
/** | |
* @property {Phaser.Signal} onShakeComplete - This signal is dispatched when the camera shake effect completes. | |
*/ | |
this.onShakeComplete = new Phaser.Signal(); | |
/** | |
* @property {Phaser.Signal} onFlashComplete - This signal is dispatched when the camera flash effect completes. | |
*/ | |
this.onFlashComplete = new Phaser.Signal(); | |
/** | |
* This signal is dispatched when the camera fade effect completes. | |
* When the fade effect completes you will be left with the screen black (or whatever | |
* color you faded to). In order to reset this call `Camera.resetFX`. This is called | |
* automatically when you change State. | |
* @property {Phaser.Signal} onFadeComplete | |
*/ | |
this.onFadeComplete = new Phaser.Signal(); | |
/** | |
* The Graphics object used to handle camera fx such as fade and flash. | |
* @property {Phaser.Graphics} fx | |
* @protected | |
*/ | |
this.fx = null; | |
/** | |
* @property {Phaser.Point} _targetPosition - Internal point used to calculate target position. | |
* @private | |
*/ | |
this._targetPosition = new Phaser.Point(); | |
/** | |
* @property {number} edge - Edge property. | |
* @private | |
* @default | |
*/ | |
this._edge = 0; | |
/** | |
* @property {Phaser.Point} position - Current position of the camera in world. | |
* @private | |
* @default | |
*/ | |
this._position = new Phaser.Point(); | |
/** | |
* @property {Object} _shake - The shake effect container. | |
* @private | |
*/ | |
this._shake = { | |
intensity: 0, | |
duration: 0, | |
horizontal: false, | |
vertical: false, | |
shakeBounds: true, | |
x: 0, | |
y: 0 | |
}; | |
/** | |
* @property {number} _fxDuration - FX duration timer. | |
* @private | |
*/ | |
this._fxDuration = 0; | |
/** | |
* @property {number} _fxType - The FX type running. | |
* @private | |
*/ | |
this._fxType = 0; | |
}; | |
/** | |
* @constant | |
* @type {number} | |
*/ | |
Phaser.Camera.FOLLOW_LOCKON = 0; | |
/** | |
* @constant | |
* @type {number} | |
*/ | |
Phaser.Camera.FOLLOW_PLATFORMER = 1; | |
/** | |
* @constant | |
* @type {number} | |
*/ | |
Phaser.Camera.FOLLOW_TOPDOWN = 2; | |
/** | |
* @constant | |
* @type {number} | |
*/ | |
Phaser.Camera.FOLLOW_TOPDOWN_TIGHT = 3; | |
/** | |
* @constant | |
* @type {number} | |
*/ | |
Phaser.Camera.SHAKE_BOTH = 4; | |
/** | |
* @constant | |
* @type {number} | |
*/ | |
Phaser.Camera.SHAKE_HORIZONTAL = 5; | |
/** | |
* @constant | |
* @type {number} | |
*/ | |
Phaser.Camera.SHAKE_VERTICAL = 6; | |
/** | |
* @constant | |
* @type {boolean} | |
*/ | |
Phaser.Camera.ENABLE_FX = true; | |
Phaser.Camera.prototype = { | |
/** | |
* Called automatically by Phaser.World. | |
* | |
* @method Phaser.Camera#boot | |
* @private | |
*/ | |
boot: function () { | |
this.displayObject = this.game.world; | |
this.scale = this.game.world.scale; | |
this.game.camera = this; | |
if (Phaser.Graphics && Phaser.Camera.ENABLE_FX) | |
{ | |
this.fx = new Phaser.Graphics(this.game); | |
this.game.stage.addChild(this.fx); | |
} | |
}, | |
/** | |
* Camera preUpdate. Sets the total view counter to zero. | |
* | |
* @method Phaser.Camera#preUpdate | |
*/ | |
preUpdate: function () { | |
this.totalInView = 0; | |
}, | |
/** | |
* Tell the camera which sprite to follow. | |
* | |
* You can set the follow type and a linear interpolation value. | |
* Use low lerp values (such as 0.1) to automatically smooth the camera motion. | |
* | |
* If you find you're getting a slight "jitter" effect when following a Sprite it's probably to do with sub-pixel rendering of the Sprite position. | |
* This can be disabled by setting `game.renderer.renderSession.roundPixels = true` to force full pixel rendering. | |
* | |
* @method Phaser.Camera#follow | |
* @param {Phaser.Sprite|Phaser.Image|Phaser.Text} target - The object you want the camera to track. Set to null to not follow anything. | |
* @param {number} [style] - Leverage one of the existing "deadzone" presets. If you use a custom deadzone, ignore this parameter and manually specify the deadzone after calling follow(). | |
* @param {float} [lerpX=1] - A value between 0 and 1. This value specifies the amount of linear interpolation to use when horizontally tracking the target. The closer the value to 1, the faster the camera will track. | |
* @param {float} [lerpY=1] - A value between 0 and 1. This value specifies the amount of linear interpolation to use when vertically tracking the target. The closer the value to 1, the faster the camera will track. | |
*/ | |
follow: function (target, style, lerpX, lerpY) { | |
if (style === undefined) { style = Phaser.Camera.FOLLOW_LOCKON; } | |
if (lerpX === undefined) { lerpX = 1; } | |
if (lerpY === undefined) { lerpY = 1; } | |
this.target = target; | |
this.lerp.set(lerpX, lerpY); | |
var helper; | |
switch (style) { | |
case Phaser.Camera.FOLLOW_PLATFORMER: | |
var w = this.width / 8; | |
var h = this.height / 3; | |
this.deadzone = new Phaser.Rectangle((this.width - w) / 2, (this.height - h) / 2 - h * 0.25, w, h); | |
break; | |
case Phaser.Camera.FOLLOW_TOPDOWN: | |
helper = Math.max(this.width, this.height) / 4; | |
this.deadzone = new Phaser.Rectangle((this.width - helper) / 2, (this.height - helper) / 2, helper, helper); | |
break; | |
case Phaser.Camera.FOLLOW_TOPDOWN_TIGHT: | |
helper = Math.max(this.width, this.height) / 8; | |
this.deadzone = new Phaser.Rectangle((this.width - helper) / 2, (this.height - helper) / 2, helper, helper); | |
break; | |
case Phaser.Camera.FOLLOW_LOCKON: | |
this.deadzone = null; | |
break; | |
default: | |
this.deadzone = null; | |
break; | |
} | |
}, | |
/** | |
* Sets the Camera follow target to null, stopping it from following an object if it's doing so. | |
* | |
* @method Phaser.Camera#unfollow | |
*/ | |
unfollow: function () { | |
this.target = null; | |
}, | |
/** | |
* Move the camera focus on a display object instantly. | |
* @method Phaser.Camera#focusOn | |
* @param {any} displayObject - The display object to focus the camera on. Must have visible x/y properties. | |
*/ | |
focusOn: function (displayObject) { | |
this.setPosition(Math.round(displayObject.x - this.view.halfWidth), Math.round(displayObject.y - this.view.halfHeight)); | |
}, | |
/** | |
* Move the camera focus on a location instantly. | |
* @method Phaser.Camera#focusOnXY | |
* @param {number} x - X position. | |
* @param {number} y - Y position. | |
*/ | |
focusOnXY: function (x, y) { | |
this.setPosition(Math.round(x - this.view.halfWidth), Math.round(y - this.view.halfHeight)); | |
}, | |
/** | |
* This creates a camera shake effect. It works by applying a random amount of additional | |
* spacing on the x and y axis each frame. You can control the intensity and duration | |
* of the effect, and if it should effect both axis or just one. | |
* | |
* When the shake effect ends the signal Camera.onShakeComplete is dispatched. | |
* | |
* @method Phaser.Camera#shake | |
* @param {float} [intensity=0.05] - The intensity of the camera shake. Given as a percentage of the camera size representing the maximum distance that the camera can move while shaking. | |
* @param {number} [duration=500] - The duration of the shake effect in milliseconds. | |
* @param {boolean} [force=true] - If a camera shake effect is already running and force is true it will replace the previous effect, resetting the duration. | |
* @param {number} [direction=Phaser.Camera.SHAKE_BOTH] - The directions in which the camera can shake. Either Phaser.Camera.SHAKE_BOTH, Phaser.Camera.SHAKE_HORIZONTAL or Phaser.Camera.SHAKE_VERTICAL. | |
* @param {boolean} [shakeBounds=true] - Is the effect allowed to shake the camera beyond its bounds (if set?). | |
* @return {boolean} True if the shake effect was started, otherwise false. | |
*/ | |
shake: function (intensity, duration, force, direction, shakeBounds) { | |
if (intensity === undefined) { intensity = 0.05; } | |
if (duration === undefined) { duration = 500; } | |
if (force === undefined) { force = true; } | |
if (direction === undefined) { direction = Phaser.Camera.SHAKE_BOTH; } | |
if (shakeBounds === undefined) { shakeBounds = true; } | |
if (!force && this._shake.duration > 0) | |
{ | |
// Can't reset an already running shake | |
return false; | |
} | |
this._shake.intensity = intensity; | |
this._shake.duration = duration; | |
this._shake.shakeBounds = shakeBounds; | |
this._shake.x = 0; | |
this._shake.y = 0; | |
this._shake.horizontal = (direction === Phaser.Camera.SHAKE_BOTH || direction === Phaser.Camera.SHAKE_HORIZONTAL); | |
this._shake.vertical = (direction === Phaser.Camera.SHAKE_BOTH || direction === Phaser.Camera.SHAKE_VERTICAL); | |
return true; | |
}, | |
/** | |
* This creates a camera flash effect. It works by filling the game with the solid fill | |
* color specified, and then fading it away to alpha 0 over the duration given. | |
* | |
* You can use this for things such as hit feedback effects. | |
* | |
* When the effect ends the signal Camera.onFlashComplete is dispatched. | |
* | |
* @method Phaser.Camera#flash | |
* @param {numer} [color=0xffffff] - The color of the flash effect. I.e. 0xffffff for white, 0xff0000 for red, etc. | |
* @param {number} [duration=500] - The duration of the flash effect in milliseconds. | |
* @param {boolean} [force=false] - If a camera flash or fade effect is already running and force is true it will replace the previous effect, resetting the duration. | |
* @return {boolean} True if the effect was started, otherwise false. | |
*/ | |
flash: function (color, duration, force) { | |
if (color === undefined) { color = 0xffffff; } | |
if (duration === undefined) { duration = 500; } | |
if (force === undefined) { force = false; } | |
if (!this.fx || (!force && this._fxDuration > 0)) | |
{ | |
return false; | |
} | |
this.fx.clear(); | |
this.fx.beginFill(color); | |
this.fx.drawRect(0, 0, this.width, this.height); | |
this.fx.endFill(); | |
this.fx.alpha = 1; | |
this._fxDuration = duration; | |
this._fxType = 0; | |
return true; | |
}, | |
/** | |
* This creates a camera fade effect. It works by filling the game with the | |
* color specified, over the duration given, ending with a solid fill. | |
* | |
* You can use this for things such as transitioning to a new scene. | |
* | |
* The game will be left 'filled' at the end of this effect, likely obscuring | |
* everything. In order to reset it you can call `Camera.resetFX` and it will clear the | |
* fade. Or you can call `Camera.flash` with the same color as the fade, and it will | |
* reverse the process, bringing the game back into view again. | |
* | |
* When the effect ends the signal Camera.onFadeComplete is dispatched. | |
* | |
* @method Phaser.Camera#fade | |
* @param {numer} [color=0x000000] - The color the game will fade to. I.e. 0x000000 for black, 0xff0000 for red, etc. | |
* @param {number} [duration=500] - The duration of the fade in milliseconds. | |
* @param {boolean} [force=false] - If a camera flash or fade effect is already running and force is true it will replace the previous effect, resetting the duration. | |
* @return {boolean} True if the effect was started, otherwise false. | |
*/ | |
fade: function (color, duration, force) { | |
if (color === undefined) { color = 0x000000; } | |
if (duration === undefined) { duration = 500; } | |
if (force === undefined) { force = false; } | |
if (!this.fx || (!force && this._fxDuration > 0)) | |
{ | |
return false; | |
} | |
this.fx.clear(); | |
this.fx.beginFill(color); | |
this.fx.drawRect(0, 0, this.width, this.height); | |
this.fx.endFill(); | |
this.fx.alpha = 0; | |
this._fxDuration = duration; | |
this._fxType = 1; | |
return true; | |
}, | |
/** | |
* The camera update loop. This is called automatically by the core game loop. | |
* | |
* @method Phaser.Camera#update | |
* @protected | |
*/ | |
update: function () { | |
if (this._fxDuration > 0) | |
{ | |
this.updateFX(); | |
} | |
if (this._shake.duration > 0) | |
{ | |
this.updateShake(); | |
} | |
if (this.bounds) | |
{ | |
this.checkBounds(); | |
} | |
if (this.roundPx) | |
{ | |
this.view.floor(); | |
this._shake.x = Math.floor(this._shake.x); | |
this._shake.y = Math.floor(this._shake.y); | |
} | |
this.displayObject.position.x = -this.view.x; | |
this.displayObject.position.y = -this.view.y; | |
}, | |
/** | |
* Update the camera flash and fade effects. | |
* | |
* @method Phaser.Camera#updateFX | |
* @private | |
*/ | |
updateFX: function () { | |
if (this._fxType === 0) | |
{ | |
// flash | |
this.fx.alpha -= this.game.time.elapsedMS / this._fxDuration; | |
if (this.fx.alpha <= 0) | |
{ | |
this._fxDuration = 0; | |
this.fx.alpha = 0; | |
this.onFlashComplete.dispatch(); | |
} | |
} | |
else | |
{ | |
// fade | |
this.fx.alpha += this.game.time.elapsedMS / this._fxDuration; | |
if (this.fx.alpha >= 1) | |
{ | |
this._fxDuration = 0; | |
this.fx.alpha = 1; | |
this.onFadeComplete.dispatch(); | |
} | |
} | |
}, | |
/** | |
* Update the camera shake effect. | |
* | |
* @method Phaser.Camera#updateShake | |
* @private | |
*/ | |
updateShake: function () { | |
this._shake.duration -= this.game.time.elapsedMS; | |
if (this._shake.duration <= 0) | |
{ | |
this.onShakeComplete.dispatch(); | |
this._shake.x = 0; | |
this._shake.y = 0; | |
} | |
else | |
{ | |
if (this._shake.horizontal) | |
{ | |
this._shake.x = this.game.rnd.frac() * this._shake.intensity * this.view.width * 2 - this._shake.intensity * this.view.width; | |
} | |
if (this._shake.vertical) | |
{ | |
this._shake.y = this.game.rnd.frac() * this._shake.intensity * this.view.height * 2 - this._shake.intensity * this.view.height; | |
} | |
} | |
}, | |
/** | |
* Internal method that handles tracking a sprite. | |
* | |
* @method Phaser.Camera#updateTarget | |
* @private | |
*/ | |
updateTarget: function () { | |
this._targetPosition.x = this.view.x + this.target.worldPosition.x; | |
this._targetPosition.y = this.view.y + this.target.worldPosition.y; | |
if (this.deadzone) | |
{ | |
this._edge = this._targetPosition.x - this.view.x; | |
if (this._edge < this.deadzone.left) | |
{ | |
this.view.x = this.game.math.linear(this.view.x, this._targetPosition.x - this.deadzone.left, this.lerp.x); | |
} | |
else if (this._edge > this.deadzone.right) | |
{ | |
this.view.x = this.game.math.linear(this.view.x, this._targetPosition.x - this.deadzone.right, this.lerp.x); | |
} | |
this._edge = this._targetPosition.y - this.view.y; | |
if (this._edge < this.deadzone.top) | |
{ | |
this.view.y = this.game.math.linear(this.view.y, this._targetPosition.y - this.deadzone.top, this.lerp.y); | |
} | |
else if (this._edge > this.deadzone.bottom) | |
{ | |
this.view.y = this.game.math.linear(this.view.y, this._targetPosition.y - this.deadzone.bottom, this.lerp.y); | |
} | |
} | |
else | |
{ | |
this.view.x = this.game.math.linear(this.view.x, this._targetPosition.x - this.view.halfWidth, this.lerp.x); | |
this.view.y = this.game.math.linear(this.view.y, this._targetPosition.y - this.view.halfHeight, this.lerp.y); | |
} | |
if (this.bounds) | |
{ | |
this.checkBounds(); | |
} | |
if (this.roundPx) | |
{ | |
this.view.floor(); | |
} | |
this.displayObject.position.x = -this.view.x; | |
this.displayObject.position.y = -this.view.y; | |
}, | |
/** | |
* Update the Camera bounds to match the game world. | |
* | |
* @method Phaser.Camera#setBoundsToWorld | |
*/ | |
setBoundsToWorld: function () { | |
if (this.bounds) | |
{ | |
this.bounds.copyFrom(this.game.world.bounds); | |
} | |
}, | |
/** | |
* Method called to ensure the camera doesn't venture outside of the game world. | |
* Called automatically by Camera.update. | |
* | |
* @method Phaser.Camera#checkBounds | |
* @protected | |
*/ | |
checkBounds: function () { | |
this.atLimit.x = false; | |
this.atLimit.y = false; | |
var vx = this.view.x + this._shake.x; | |
var vw = this.view.right + this._shake.x; | |
var vy = this.view.y + this._shake.y; | |
var vh = this.view.bottom + this._shake.y; | |
// Make sure we didn't go outside the cameras bounds | |
if (vx <= this.bounds.x * this.scale.x) | |
{ | |
this.atLimit.x = true; | |
this.view.x = this.bounds.x * this.scale.x; | |
if (!this._shake.shakeBounds) | |
{ | |
// The camera is up against the bounds, so reset the shake | |
this._shake.x = 0; | |
} | |
} | |
if (vw >= this.bounds.right * this.scale.x) | |
{ | |
this.atLimit.x = true; | |
this.view.x = (this.bounds.right * this.scale.x) - this.width; | |
if (!this._shake.shakeBounds) | |
{ | |
// The camera is up against the bounds, so reset the shake | |
this._shake.x = 0; | |
} | |
} | |
if (vy <= this.bounds.top * this.scale.y) | |
{ | |
this.atLimit.y = true; | |
this.view.y = this.bounds.top * this.scale.y; | |
if (!this._shake.shakeBounds) | |
{ | |
// The camera is up against the bounds, so reset the shake | |
this._shake.y = 0; | |
} | |
} | |
if (vh >= this.bounds.bottom * this.scale.y) | |
{ | |
this.atLimit.y = true; | |
this.view.y = (this.bounds.bottom * this.scale.y) - this.height; | |
if (!this._shake.shakeBounds) | |
{ | |
// The camera is up against the bounds, so reset the shake | |
this._shake.y = 0; | |
} | |
} | |
}, | |
/** | |
* A helper function to set both the X and Y properties of the camera at once | |
* without having to use game.camera.x and game.camera.y. | |
* | |
* @method Phaser.Camera#setPosition | |
* @param {number} x - X position. | |
* @param {number} y - Y position. | |
*/ | |
setPosition: function (x, y) { | |
this.view.x = x; | |
this.view.y = y; | |
if (this.bounds) | |
{ | |
this.checkBounds(); | |
} | |
}, | |
/** | |
* Sets the size of the view rectangle given the width and height in parameters. | |
* | |
* @method Phaser.Camera#setSize | |
* @param {number} width - The desired width. | |
* @param {number} height - The desired height. | |
*/ | |
setSize: function (width, height) { | |
this.view.width = width; | |
this.view.height = height; | |
}, | |
/** | |
* Resets the camera back to 0,0 and un-follows any object it may have been tracking. | |
* Also immediately resets any camera effects that may have been running such as | |
* shake, flash or fade. | |
* | |
* @method Phaser.Camera#reset | |
*/ | |
reset: function () { | |
this.target = null; | |
this.view.x = 0; | |
this.view.y = 0; | |
this._shake.duration = 0; | |
this.resetFX(); | |
}, | |
/** | |
* Resets any active FX, such as a fade or flash and immediately clears it. | |
* Useful to calling after a fade in order to remove the fade from the Stage. | |
* | |
* @method Phaser.Camera#resetFX | |
*/ | |
resetFX: function () { | |
this.fx.clear(); | |
this.fx.alpha = 0; | |
this._fxDuration = 0; | |
} | |
}; | |
Phaser.Camera.prototype.constructor = Phaser.Camera; | |
/** | |
* The Cameras x coordinate. This value is automatically clamped if it falls outside of the World bounds. | |
* @name Phaser.Camera#x | |
* @property {number} x - Gets or sets the cameras x position. | |
*/ | |
Object.defineProperty(Phaser.Camera.prototype, "x", { | |
get: function () { | |
return this.view.x; | |
}, | |
set: function (value) { | |
this.view.x = value; | |
if (this.bounds) | |
{ | |
this.checkBounds(); | |
} | |
} | |
}); | |
/** | |
* The Cameras y coordinate. This value is automatically clamped if it falls outside of the World bounds. | |
* @name Phaser.Camera#y | |
* @property {number} y - Gets or sets the cameras y position. | |
*/ | |
Object.defineProperty(Phaser.Camera.prototype, "y", { | |
get: function () { | |
return this.view.y; | |
}, | |
set: function (value) { | |
this.view.y = value; | |
if (this.bounds) | |
{ | |
this.checkBounds(); | |
} | |
} | |
}); | |
/** | |
* The Cameras position. This value is automatically clamped if it falls outside of the World bounds. | |
* @name Phaser.Camera#position | |
* @property {Phaser.Point} position - Gets or sets the cameras xy position using Phaser.Point object. | |
*/ | |
Object.defineProperty(Phaser.Camera.prototype, "position", { | |
get: function () { | |
this._position.set(this.view.x, this.view.y); | |
return this._position; | |
}, | |
set: function (value) { | |
if (typeof value.x !== "undefined") { this.view.x = value.x; } | |
if (typeof value.y !== "undefined") { this.view.y = value.y; } | |
if (this.bounds) | |
{ | |
this.checkBounds(); | |
} | |
} | |
}); | |
/** | |
* The Cameras width. By default this is the same as the Game size and should not be adjusted for now. | |
* @name Phaser.Camera#width | |
* @property {number} width - Gets or sets the cameras width. | |
*/ | |
Object.defineProperty(Phaser.Camera.prototype, "width", { | |
get: function () { | |
return this.view.width; | |
}, | |
set: function (value) { | |
this.view.width = value; | |
} | |
}); | |
/** | |
* The Cameras height. By default this is the same as the Game size and should not be adjusted for now. | |
* @name Phaser.Camera#height | |
* @property {number} height - Gets or sets the cameras height. | |
*/ | |
Object.defineProperty(Phaser.Camera.prototype, "height", { | |
get: function () { | |
return this.view.height; | |
}, | |
set: function (value) { | |
this.view.height = value; | |
} | |
}); | |
/** | |
* The Cameras shake intensity. | |
* @name Phaser.Camera#shakeIntensity | |
* @property {number} shakeIntensity - Gets or sets the cameras shake intensity. | |
*/ | |
Object.defineProperty(Phaser.Camera.prototype, "shakeIntensity", { | |
get: function () { | |
return this._shake.intensity; | |
}, | |
set: function (value) { | |
this._shake.intensity = value; | |
} | |
}); | |
/** | |
* @author Richard Davey <[email protected]> | |
* @copyright 2016 Photon Storm Ltd. | |
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} | |
*/ | |
/** | |
* This is a base State class which can be extended if you are creating your own game. | |
* It provides quick access to common functions such as the camera, cache, input, match, sound and more. | |
* | |
* @class Phaser.State | |
* @constructor | |
*/ | |
Phaser.State = function () { | |
/** | |
* @property {Phaser.Game} game - This is a reference to the currently running Game. | |
*/ | |
this.game = null; | |
/** | |
* @property {string} key - The string based identifier given to the State when added into the State Manager. | |
*/ | |
this.key = ''; | |
/** | |
* @property {Phaser.GameObjectFactory} add - A reference to the GameObjectFactory which can be used to add new objects to the World. | |
*/ | |
this.add = null; | |
/** | |
* @property {Phaser.GameObjectCreator} make - A reference to the GameObjectCreator which can be used to make new objects. | |
*/ | |
this.make = null; | |
/** | |
* @property {Phaser.Camera} camera - A handy reference to World.camera. | |
*/ | |
this.camera = null; | |
/** | |
* @property {Phaser.Cache} cache - A reference to the game cache which contains any loaded or generated assets, such as images, sound and more. | |
*/ | |
this.cache = null; | |
/** | |
* @property {Phaser.Input} input - A reference to the Input Manager. | |
*/ | |
this.input = null; | |
/** | |
* @property {Phaser.Loader} load - A reference to the Loader, which you mostly use in the preload method of your state to load external assets. | |
*/ | |
this.load = null; | |
/** | |
* @property {Phaser.Math} math - A reference to Math class with lots of helpful functions. | |
*/ | |
this.math = null; | |
/** | |
* @property {Phaser.SoundManager} sound - A reference to the Sound Manager which can create, play and stop sounds, as well as adjust global volume. | |
*/ | |
this.sound = null; | |
/** | |
* @property {Phaser.ScaleManager} scale - A reference to the Scale Manager which controls the way the game scales on different displays. | |
*/ | |
this.scale = null; | |
/** | |
* @property {Phaser.Stage} stage - A reference to the Stage. | |
*/ | |
this.stage = null; | |
/** | |
* @property {Phaser.StateManager} stage - A reference to the State Manager, which controls state changes. | |
*/ | |
this.state = null; | |
/** | |
* @property {Phaser.Time} time - A reference to the game clock and timed events system. | |
*/ | |
this.time = null; | |
/** | |
* @property {Phaser.TweenManager} tweens - A reference to the tween manager. | |
*/ | |
this.tweens = null; | |
/** | |
* @property {Phaser.World} world - A reference to the game world. All objects live in the Game World and its size is not bound by the display resolution. | |
*/ | |
this.world = null; | |
/** | |
* @property {Phaser.Particles} particles - The Particle Manager. It is called during the core gameloop and updates any Particle Emitters it has created. | |
*/ | |
this.particles = null; | |
/** | |
* @property {Phaser.Physics} physics - A reference to the physics manager which looks after the different physics systems available within Phaser. | |
*/ | |
this.physics = null; | |
/** | |
* @property {Phaser.RandomDataGenerator} rnd - A reference to the seeded and repeatable random data generator. | |
*/ | |
this.rnd = null; | |
}; | |
Phaser.State.prototype = { | |
/** | |
* init is the very first function called when your State starts up. It's called before preload, create or anything else. | |
* If you need to route the game away to another State you could do so here, or if you need to prepare a set of variables | |
* or objects before the preloading starts. | |
* | |
* @method Phaser.State#init | |
*/ | |
init: function () { | |
}, | |
/** | |
* preload is called first. Normally you'd use this to load your game assets (or those needed for the current State) | |
* You shouldn't create any objects in this method that require assets that you're also loading in this method, as | |
* they won't yet be available. | |
* | |
* @method Phaser.State#preload | |
*/ | |
preload: function () { | |
}, | |
/** | |
* loadUpdate is called during the Loader process. This only happens if you've set one or more assets to load in the preload method. | |
* | |
* @method Phaser.State#loadUpdate | |
*/ | |
loadUpdate: function () { | |
}, | |
/** | |
* loadRender is called during the Loader process. This only happens if you've set one or more assets to load in the preload method. | |
* The difference between loadRender and render is that any objects you render in this method you must be sure their assets exist. | |
* | |
* @method Phaser.State#loadRender | |
*/ | |
loadRender: function () { | |
}, | |
/** | |
* create is called once preload has completed, this includes the loading of any assets from the Loader. | |
* If you don't have a preload method then create is the first method called in your State. | |
* | |
* @method Phaser.State#create | |
*/ | |
create: function () { | |
}, | |
/** | |
* The update method is left empty for your own use. | |
* It is called during the core game loop AFTER debug, physics, plugins and the Stage have had their preUpdate methods called. | |
* It is called BEFORE Stage, Tweens, Sounds, Input, Physics, Particles and Plugins have had their postUpdate methods called. | |
* | |
* @method Phaser.State#update | |
*/ | |
update: function () { | |
}, | |
/** | |
* The preRender method is called after all Game Objects have been updated, but before any rendering takes place. | |
* | |
* @method Phaser.State#preRender | |
*/ | |
preRender: function () { | |
}, | |
/** | |
* Nearly all display objects in Phaser render automatically, you don't need to tell them to render. | |
* However the render method is called AFTER the game renderer and plugins have rendered, so you're able to do any | |
* final post-processing style effects here. Note that this happens before plugins postRender takes place. | |
* | |
* @method Phaser.State#render | |
*/ | |
render: function () { | |
}, | |
/** | |
* If your game is set to Scalemode RESIZE then each time the browser resizes it will call this function, passing in the new width and height. | |
* | |
* @method Phaser.State#resize | |
*/ | |
resize: function () { | |
}, | |
/** | |
* This method will be called if the core game loop is paused. | |
* | |
* @method Phaser.State#paused | |
*/ | |
paused: function () { | |
}, | |
/** | |
* This method will be called when the core game loop resumes from a paused state. | |
* | |
* @method Phaser.State#resumed | |
*/ | |
resumed: function () { | |
}, | |
/** | |
* pauseUpdate is called while the game is paused instead of preUpdate, update and postUpdate. | |
* | |
* @method Phaser.State#pauseUpdate | |
*/ | |
pauseUpdate: function () { | |
}, | |
/** | |
* This method will be called when the State is shutdown (i.e. you switch to another state from this one). | |
* | |
* @method Phaser.State#shutdown | |
*/ | |
shutdown: function () { | |
} | |
}; | |
Phaser.State.prototype.constructor = Phaser.State; | |
/* jshint newcap: false */ | |
/** | |
* @author Richard Davey <[email protected]> | |
* @copyright 2016 Photon Storm Ltd. | |
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} | |
*/ | |
/** | |
* The State Manager is responsible for loading, setting up and switching game states. | |
* | |
* @class Phaser.StateManager | |
* @constructor | |
* @param {Phaser.Game} game - A reference to the currently running game. | |
* @param {Phaser.State|Object} [pendingState=null] - A State object to seed the manager with. | |
*/ | |
Phaser.StateManager = function (game, pendingState) { | |
/** | |
* @property {Phaser.Game} game - A reference to the currently running game. | |
*/ | |
this.game = game; | |
/** | |
* @property {object} states - The object containing Phaser.States. | |
*/ | |
this.states = {}; | |
/** | |
* @property {Phaser.State} _pendingState - The state to be switched to in the next frame. | |
* @private | |
*/ | |
this._pendingState = null; | |
if (typeof pendingState !== 'undefined' && pendingState !== null) | |
{ | |
this._pendingState = pendingState; | |
} | |
/** | |
* @property {boolean} _clearWorld - Clear the world when we switch state? | |
* @private | |
*/ | |
this._clearWorld = false; | |
/** | |
* @property {boolean} _clearCache - Clear the cache when we switch state? | |
* @private | |
*/ | |
this._clearCache = false; | |
/** | |
* @property {boolean} _created - Flag that sets if the State has been created or not. | |
* @private | |
*/ | |
this._created = false; | |
/** | |
* @property {any[]} _args - Temporary container when you pass vars from one State to another. | |
* @private | |
*/ | |
this._args = []; | |
/** | |
* @property {string} current - The current active State object. | |
* @default | |
*/ | |
this.current = ''; | |
/** | |
* onStateChange is a Phaser.Signal that is dispatched whenever the game changes state. | |
* | |
* It is dispatched only when the new state is started, which isn't usually at the same time as StateManager.start | |
* is called because state swapping is done in sync with the game loop. It is dispatched *before* any of the new states | |
* methods (such as preload and create) are called, and *after* the previous states shutdown method has been run. | |
* | |
* The callback you specify is sent two parameters: the string based key of the new state, | |
* and the second parameter is the string based key of the old / previous state. | |
* | |
* @property {Phaser.Signal} onStateChange | |
*/ | |
this.onStateChange = new Phaser.Signal(); | |
/** | |
* @property {function} onInitCallback - This is called when the state is set as the active state. | |
* @default | |
*/ | |
this.onInitCallback = null; | |
/** | |
* @property {function} onPreloadCallback - This is called when the state starts to load assets. | |
* @default | |
*/ | |
this.onPreloadCallback = null; | |
/** | |
* @property {function} onCreateCallback - This is called when the state preload has finished and creation begins. | |
* @default | |
*/ | |
this.onCreateCallback = null; | |
/** | |
* @property {function} onUpdateCallback - This is called when the state is updated, every game loop. It doesn't happen during preload (@see onLoadUpdateCallback). | |
* @default | |
*/ | |
this.onUpdateCallback = null; | |
/** | |
* @property {function} onRenderCallback - This is called post-render. It doesn't happen during preload (see onLoadRenderCallback). | |
* @default | |
*/ | |
this.onRenderCallback = null; | |
/** | |
* @property {function} onResizeCallback - This is called if ScaleManager.scalemode is RESIZE and a resize event occurs. It's passed the new width and height. | |
* @default | |
*/ | |
this.onResizeCallback = null; | |
/** | |
* @property {function} onPreRenderCallback - This is called before the state is rendered and before the stage is cleared but after all game objects have had their final properties adjusted. | |
* @default | |
*/ | |
this.onPreRenderCallback = null; | |
/** | |
* @property {function} onLoadUpdateCallback - This is called when the State is updated during the preload phase. | |
* @default | |
*/ | |
this.onLoadUpdateCallback = null; | |
/** | |
* @property {function} onLoadRenderCallback - This is called when the State is rendered during the preload phase. | |
* @default | |
*/ | |
this.onLoadRenderCallback = null; | |
/** | |
* @property {function} onPausedCallback - This is called when the game is paused. | |
* @default | |
*/ | |
this.onPausedCallback = null; | |
/** | |
* @property {function} onResumedCallback - This is called when the game is resumed from a paused state. | |
* @default | |
*/ | |
this.onResumedCallback = null; | |
/** | |
* @property {function} onPauseUpdateCallback - This is called every frame while the game is paused. | |
* @default | |
*/ | |
this.onPauseUpdateCallback = null; | |
/** | |
* @property {function} onShutDownCallback - This is called when the state is shut down (i.e. swapped to another state). | |
* @default | |
*/ | |
this.onShutDownCallback = null; | |
}; | |
Phaser.StateManager.prototype = { | |
/** | |
* The Boot handler is called by Phaser.Game when it first starts up. | |
* @method Phaser.StateManager#boot | |
* @private | |
*/ | |
boot: function () { | |
this.game.onPause.add(this.pause, this); | |
this.game.onResume.add(this.resume, this); | |
if (this._pendingState !== null && typeof this._pendingState !== 'string') | |
{ | |
this.add('default', this._pendingState, true); | |
} | |
}, | |
/** | |
* Adds a new State into the StateManager. You must give each State a unique key by which you'll identify it. | |
* The State can be either a Phaser.State object (or an object that extends it), a plain JavaScript object or a function. | |
* If a function is given a new state object will be created by calling it. | |
* | |
* @method Phaser.StateManager#add | |
* @param {string} key - A unique key you use to reference this state, i.e. "MainMenu", "Level1". | |
* @param {Phaser.State|object|function} state - The state you want to switch to. | |
* @param {boolean} [autoStart=false] - If true the State will be started immediately after adding it. | |
*/ | |
add: function (key, state, autoStart) { | |
if (autoStart === undefined) { autoStart = false; } | |
var newState; | |
if (state instanceof Phaser.State) | |
{ | |
newState = state; | |
} | |
else if (typeof state === 'object') | |
{ | |
newState = state; | |
newState.game = this.game; | |
} | |
else if (typeof state === 'function') | |
{ | |
newState = new state(this.game); | |
} | |
this.states[key] = newState; | |
if (autoStart) | |
{ | |
if (this.game.isBooted) | |
{ | |
this.start(key); | |
} | |
else | |
{ | |
this._pendingState = key; | |
} | |
} | |
return newState; | |
}, | |
/** | |
* Delete the given state. | |
* @method Phaser.StateManager#remove | |
* @param {string} key - A unique key you use to reference this state, i.e. "MainMenu", "Level1". | |
*/ | |
remove: function (key) { | |
if (this.current === key) | |
{ | |
this.callbackContext = null; | |
this.onInitCallback = null; | |
this.onShutDownCallback = null; | |
this.onPreloadCallback = null; | |
this.onLoadRenderCallback = null; | |
this.onLoadUpdateCallback = null; | |
this.onCreateCallback = null; | |
this.onUpdateCallback = null; | |
this.onPreRenderCallback = null; | |
this.onRenderCallback = null; | |
this.onResizeCallback = null; | |
this.onPausedCallback = null; | |
this.onResumedCallback = null; | |
this.onPauseUpdateCallback = null; | |
} | |
delete this.states[key]; | |
}, | |
/** | |
* Start the given State. If a State is already running then State.shutDown will be called (if it exists) before switching to the new State. | |
* | |
* @method Phaser.StateManager#start | |
* @param {string} key - The key of the state you want to start. | |
* @param {boolean} [clearWorld=true] - Clear everything in the world? This clears the World display list fully (but not the Stage, so if you've added your own objects to the Stage they will need managing directly) | |
* @param {boolean} [clearCache=false] - Clear the Game.Cache? This purges out all loaded assets. The default is false and you must have clearWorld=true if you want to clearCache as well. | |
* @param {...*} parameter - Additional parameters that will be passed to the State.init function (if it has one). | |
*/ | |
start: function (key, clearWorld, clearCache) { | |
if (clearWorld === undefined) { clearWorld = true; } | |
if (clearCache === undefined) { clearCache = false; } | |
if (this.checkState(key)) | |
{ | |
// Place the state in the queue. It will be started the next time the game loop begins. | |
this._pendingState = key; | |
this._clearWorld = clearWorld; | |
this._clearCache = clearCache; | |
if (arguments.length > 3) | |
{ | |
this._args = Array.prototype.splice.call(arguments, 3); | |
} | |
} | |
}, | |
/** | |
* Restarts the current State. State.shutDown will be called (if it exists) before the State is restarted. | |
* | |
* @method Phaser.StateManager#restart | |
* @param {boolean} [clearWorld=true] - Clear everything in the world? This clears the World display list fully (but not the Stage, so if you've added your own objects to the Stage they will need managing directly) | |
* @param {boolean} [clearCache=false] - Clear the Game.Cache? This purges out all loaded assets. The default is false and you must have clearWorld=true if you want to clearCache as well. | |
* @param {...*} parameter - Additional parameters that will be passed to the State.init function if it has one. | |
*/ | |
restart: function (clearWorld, clearCache) { | |
if (clearWorld === undefined) { clearWorld = true; } | |
if (clearCache === undefined) { clearCache = false; } | |
// Place the state in the queue. It will be started the next time the game loop starts. | |
this._pendingState = this.current; | |
this._clearWorld = clearWorld; | |
this._clearCache = clearCache; | |
if (arguments.length > 2) | |
{ | |
this._args = Array.prototype.slice.call(arguments, 2); | |
} | |
}, | |
/** | |
* Used by onInit and onShutdown when those functions don't exist on the state | |
* @method Phaser.StateManager#dummy | |
* @private | |
*/ | |
dummy: function () { | |
}, | |
/** | |
* preUpdate is called right at the start of the game loop. It is responsible for changing to a new state that was requested previously. | |
* | |
* @method Phaser.StateManager#preUpdate | |
*/ | |
preUpdate: function () { | |
if (this._pendingState && this.game.isBooted) | |
{ | |
var previousStateKey = this.current; | |
// Already got a state running? | |
this.clearCurrentState(); | |
this.setCurrentState(this._pendingState); | |
this.onStateChange.dispatch(this.current, previousStateKey); | |
if (this.current !== this._pendingState) | |
{ | |
return; | |
} | |
else | |
{ | |
this._pendingState = null; | |
} | |
// If StateManager.start has been called from the init of a State that ALSO has a preload, then | |
// onPreloadCallback will be set, but must be ignored | |
if (this.onPreloadCallback) | |
{ | |
this.game.load.reset(true); | |
this.onPreloadCallback.call(this.callbackContext, this.game); | |
// Is the loader empty? | |
if (this.game.load.totalQueuedFiles() === 0 && this.game.load.totalQueuedPacks() === 0) | |
{ | |
this.loadComplete(); | |
} | |
else | |
{ | |
// Start the loader going as we have something in the queue | |
this.game.load.start(); | |
} | |
} | |
else | |
{ | |
// No init? Then there was nothing to load either | |
this.loadComplete(); | |
} | |
} | |
}, | |
/** | |
* This method clears the current State, calling its shutdown callback. The process also removes any active tweens, | |
* resets the camera, resets input, clears physics, removes timers and if set clears the world and cache too. | |
* | |
* @method Phaser.StateManager#clearCurrentState | |
*/ | |
clearCurrentState: function () { | |
if (this.current) | |
{ | |
if (this.onShutDownCallback) | |
{ | |
this.onShutDownCallback.call(this.callbackContext, this.game); | |
} | |
this.game.tweens.removeAll(); | |
this.game.camera.reset(); | |
this.game.input.reset(true); | |
this.game.physics.clear(); | |
this.game.time.removeAll(); | |
this.game.scale.reset(this._clearWorld); | |
if (this.game.debug) | |
{ | |
this.game.debug.reset(); | |
} | |
if (this._clearWorld) | |
{ | |
this.game.world.shutdown(); | |
if (this._clearCache) | |
{ | |
this.game.cache.destroy(); | |
} | |
} | |
} | |
}, | |
/** | |
* Checks if a given phaser state is valid. A State is considered valid if it has at least one of the core functions: preload, create, update or render. | |
* | |
* @method Phaser.StateManager#checkState | |
* @param {string} key - The key of the state you want to check. | |
* @return {boolean} true if the State has the required functions, otherwise false. | |
*/ | |
checkState: function (key) { | |
if (this.states[key]) | |
{ | |
if (this.states[key]['preload'] || this.states[key]['create'] || this.states[key]['update'] || this.states[key]['render']) | |
{ | |
return true; | |
} | |
else | |
{ | |
console.warn("Invalid Phaser State object given. Must contain at least a one of the required functions: preload, create, update or render"); | |
return false; | |
} | |
} | |
else | |
{ | |
console.warn("Phaser.StateManager - No state found with the key: " + key); | |
return false; | |
} | |
}, | |
/** | |
* Links game properties to the State given by the key. | |
* | |
* @method Phaser.StateManager#link | |
* @param {string} key - State key. | |
* @protected | |
*/ | |
link: function (key) { | |
this.states[key].game = this.game; | |
this.states[key].add = this.game.add; | |
this.states[key].make = this.game.make; | |
this.states[key].camera = this.game.camera; | |
this.states[key].cache = this.game.cache; | |
this.states[key].input = this.game.input; | |
this.states[key].load = this.game.load; | |
this.states[key].math = this.game.math; | |
this.states[key].sound = this.game.sound; | |
this.states[key].scale = this.game.scale; | |
this.states[key].state = this; | |
this.states[key].stage = this.game.stage; | |
this.states[key].time = this.game.time; | |
this.states[key].tweens = this.game.tweens; | |
this.states[key].world = this.game.world; | |
this.states[key].particles = this.game.particles; | |
this.states[key].rnd = this.game.rnd; | |
this.states[key].physics = this.game.physics; | |
this.states[key].key = key; | |
}, | |
/** | |
* Nulls all State level Phaser properties, including a reference to Game. | |
* | |
* @method Phaser.StateManager#unlink | |
* @param {string} key - State key. | |
* @protected | |
*/ | |
unlink: function (key) { | |
if (this.states[key]) | |
{ | |
this.states[key].game = null; | |
this.states[key].add = null; | |
this.states[key].make = null; | |
this.states[key].camera = null; | |
this.states[key].cache = null; | |
this.states[key].input = null; | |
this.states[key].load = null; | |
this.states[key].math = null; | |
this.states[key].sound = null; | |
this.states[key].scale = null; | |
this.states[key].state = null; | |
this.states[key].stage = null; | |
this.states[key].time = null; | |
this.states[key].tweens = null; | |
this.states[key].world = null; | |
this.states[key].particles = null; | |
this.states[key].rnd = null; | |
this.states[key].physics = null; | |
} | |
}, | |
/** | |
* Sets the current State. Should not be called directly (use StateManager.start) | |
* | |
* @method Phaser.StateManager#setCurrentState | |
* @param {string} key - State key. | |
* @private | |
*/ | |
setCurrentState: function (key) { | |
this.callbackContext = this.states[key]; | |
this.link(key); | |
// Used when the state is set as being the current active state | |
this.onInitCallback = this.states[key]['init'] || this.dummy; | |
this.onPreloadCallback = this.states[key]['preload'] || null; | |
this.onLoadRenderCallback = this.states[key]['loadRender'] || null; | |
this.onLoadUpdateCallback = this.states[key]['loadUpdate'] || null; | |
this.onCreateCallback = this.states[key]['create'] || null; | |
this.onUpdateCallback = this.states[key]['update'] || null; | |
this.onPreRenderCallback = this.states[key]['preRender'] || null; | |
this.onRenderCallback = this.states[key]['render'] || null; | |
this.onResizeCallback = this.states[key]['resize'] || null; | |
this.onPausedCallback = this.states[key]['paused'] || null; | |
this.onResumedCallback = this.states[key]['resumed'] || null; | |
this.onPauseUpdateCallback = this.states[key]['pauseUpdate'] || null; | |
// Used when the state is no longer the current active state | |
this.onShutDownCallback = this.states[key]['shutdown'] || this.dummy; | |
// Reset the physics system, but not on the first state start | |
if (this.current !== '') | |
{ | |
this.game.physics.reset(); | |
} | |
this.current = key; | |
this._created = false; | |
// At this point key and pendingState should equal each other | |
this.onInitCallback.apply(this.callbackContext, this._args); | |
// If they no longer do then the init callback hit StateManager.start | |
if (key === this._pendingState) | |
{ | |
this._args = []; | |
} | |
this.game._kickstart = true; | |
}, | |
/** | |
* Gets the current State. | |
* | |
* @method Phaser.StateManager#getCurrentState | |
* @return {Phaser.State} | |
* @public | |
*/ | |
getCurrentState: function() { | |
return this.states[this.current]; | |
}, | |
/** | |
* @method Phaser.StateManager#loadComplete | |
* @protected | |
*/ | |
loadComplete: function () { | |
// Make sure to do load-update one last time before state is set to _created | |
if (this._created === false && this.onLoadUpdateCallback) | |
{ | |
this.onLoadUpdateCallback.call(this.callbackContext, this.game); | |
} | |
if (this._created === false && this.onCreateCallback) | |
{ | |
this._created = true; | |
this.onCreateCallback.call(this.callbackContext, this.game); | |
} | |
else | |
{ | |
this._created = true; | |
} | |
}, | |
/** | |
* @method Phaser.StateManager#pause | |
* @protected | |
*/ | |
pause: function () { | |
if (this._created && this.onPausedCallback) | |
{ | |
this.onPausedCallback.call(this.callbackContext, this.game); | |
} | |
}, | |
/** | |
* @method Phaser.StateManager#resume | |
* @protected | |
*/ | |
resume: function () { | |
if (this._created && this.onResumedCallback) | |
{ | |
this.onResumedCallback.call(this.callbackContext, this.game); | |
} | |
}, | |
/** | |
* @method Phaser.StateManager#update | |
* @protected | |
*/ | |
update: function () { | |
if (this._created) | |
{ | |
if (this.onUpdateCallback) | |
{ | |
this.onUpdateCallback.call(this.callbackContext, this.game); | |
} | |
} | |
else | |
{ | |
if (this.onLoadUpdateCallback) | |
{ | |
this.onLoadUpdateCallback.call(this.callbackContext, this.game); | |
} | |
} | |
}, | |
/** | |
* @method Phaser.StateManager#pauseUpdate | |
* @protected | |
*/ | |
pauseUpdate: function () { | |
if (this._created) | |
{ | |
if (this.onPauseUpdateCallback) | |
{ | |
this.onPauseUpdateCallback.call(this.callbackContext, this.game); | |
} | |
} | |
else | |
{ | |
if (this.onLoadUpdateCallback) | |
{ | |
this.onLoadUpdateCallback.call(this.callbackContext, this.game); | |
} | |
} | |
}, | |
/** | |
* @method Phaser.StateManager#preRender | |
* @protected | |
* @param {number} elapsedTime - The time elapsed since the last update. | |
*/ | |
preRender: function (elapsedTime) { | |
if (this._created && this.onPreRenderCallback) | |
{ | |
this.onPreRenderCallback.call(this.callbackContext, this.game, elapsedTime); | |
} | |
}, | |
/** | |
* @method Phaser.StateManager#resize | |
* @protected | |
*/ | |
resize: function (width, height) { | |
if (this.onResizeCallback) | |
{ | |
this.onResizeCallback.call(this.callbackContext, width, height); | |
} | |
}, | |
/** | |
* @method Phaser.StateManager#render | |
* @protected | |
*/ | |
render: function () { | |
if (this._created) | |
{ | |
if (this.onRenderCallback) | |
{ | |
if (this.game.renderType === Phaser.CANVAS) | |
{ | |
this.game.context.save(); | |
this.game.context.setTransform(1, 0, 0, 1, 0, 0); | |
this.onRenderCallback.call(this.callbackContext, this.game); | |
this.game.context.restore(); | |
} | |
else | |
{ | |
this.onRenderCallback.call(this.callbackContext, this.game); | |
} | |
} | |
} | |
else | |
{ | |
if (this.onLoadRenderCallback) | |
{ | |
this.onLoadRenderCallback.call(this.callbackContext, this.game); | |
} | |
} | |
}, | |
/** | |
* Removes all StateManager callback references to the State object, nulls the game reference and clears the States object. | |
* You don't recover from this without rebuilding the Phaser instance again. | |
* @method Phaser.StateManager#destroy | |
*/ | |
destroy: function () { | |
this._clearWorld = true; | |
this._clearCache = true; | |
this.clearCurrentState(); | |
this.callbackContext = null; | |
this.onInitCallback = null; | |
this.onShutDownCallback = null; | |
this.onPreloadCallback = null; | |
this.onLoadRenderCallback = null; | |
this.onLoadUpdateCallback = null; | |
this.onCreateCallback = null; | |
this.onUpdateCallback = null; | |
this.onRenderCallback = null; | |
this.onPausedCallback = null; | |
this.onResumedCallback = null; | |
this.onPauseUpdateCallback = null; | |
this.game = null; | |
this.states = {}; | |
this._pendingState = null; | |
this.current = ''; | |
} | |
}; | |
Phaser.StateManager.prototype.constructor = Phaser.StateManager; | |
/** | |
* @name Phaser.StateManager#created | |
* @property {boolean} created - True if the current state has had its `create` method run (if it has one, if not this is true by default). | |
* @readOnly | |
*/ | |
Object.defineProperty(Phaser.StateManager.prototype, "created", { | |
get: function () { | |
return this._created; | |
} | |
}); | |
/** | |
* "It's like nailing jelly to a kitten" - Gary Penn | |
*/ | |
/** | |
* @author Miller Medeiros http://millermedeiros.github.com/js-signals/ | |
* @author Richard Davey <[email protected]> | |
* @copyright 2016 Photon Storm Ltd. | |
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} | |
*/ | |
/** | |
* Signals are what Phaser uses to handle events and event dispatching. | |
* You can listen for a Signal by binding a callback / function to it. | |
* This is done by using either `Signal.add` or `Signal.addOnce`. | |
* | |
* For example you can listen for a touch or click event from the Input Manager | |
* by using its `onDown` Signal: | |
* | |
* `game.input.onDown.add(function() { ... });` | |
* | |
* Rather than inline your function, you can pass a reference: | |
* | |
* `game.input.onDown.add(clicked, this);` | |
* `function clicked () { ... }` | |
* | |
* In this case the second argument (`this`) is the context in which your function should be called. | |
* | |
* Now every time the InputManager dispatches the `onDown` signal (or event), your function | |
* will be called. | |
* | |
* Very often a Signal will send arguments to your function. | |
* This is specific to the Signal itself. | |
* If you're unsure then check the documentation, or failing that simply do: | |
* | |
* `Signal.add(function() { console.log(arguments); })` | |
* | |
* and it will log all of the arguments your function received from the Signal. | |
* | |
* Sprites have lots of default signals you can listen to in their Events class, such as: | |
* | |
* `sprite.events.onKilled` | |
* | |
* Which is called automatically whenever the Sprite is killed. | |
* There are lots of other events, see the Events component for a list. | |
* | |
* As well as listening to pre-defined Signals you can also create your own: | |
* | |
* `var mySignal = new Phaser.Signal();` | |
* | |
* This creates a new Signal. You can bind a callback to it: | |
* | |
* `mySignal.add(myCallback, this);` | |
* | |
* and then finally when ready you can dispatch the Signal: | |
* | |
* `mySignal.dispatch(your arguments);` | |
* | |
* And your callback will be invoked. See the dispatch method for more details. | |
* | |
* @class Phaser.Signal | |
* @constructor | |
*/ | |
Phaser.Signal = function () {}; | |
Phaser.Signal.prototype = { | |
/** | |
* @property {?Array.<Phaser.SignalBinding>} _bindings - Internal variable. | |
* @private | |
*/ | |
_bindings: null, | |
/** | |
* @property {any} _prevParams - Internal variable. | |
* @private | |
*/ | |
_prevParams: null, | |
/** | |
* Memorize the previously dispatched event? | |
* | |
* If an event has been memorized it is automatically dispatched when a new listener is added with {@link #add} or {@link #addOnce}. | |
* Use {@link #forget} to clear any currently memorized event. | |
* | |
* @property {boolean} memorize | |
*/ | |
memorize: false, | |
/** | |
* @property {boolean} _shouldPropagate | |
* @private | |
*/ | |
_shouldPropagate: true, | |
/** | |
* Is the Signal active? Only active signals will broadcast dispatched events. | |
* | |
* Setting this property during a dispatch will only affect the next dispatch. To stop the propagation of a signal from a listener use {@link #halt}. | |
* | |
* @property {boolean} active | |
* @default | |
*/ | |
active: true, | |
/** | |
* @property {function} _boundDispatch - The bound dispatch function, if any. | |
* @private | |
*/ | |
_boundDispatch: false, | |
/** | |
* @method Phaser.Signal#validateListener | |
* @param {function} listener - Signal handler function. | |
* @param {string} fnName - Function name. | |
* @private | |
*/ | |
validateListener: function (listener, fnName) { | |
if (typeof listener !== 'function') | |
{ | |
throw new Error('Phaser.Signal: listener is a required param of {fn}() and should be a Function.'.replace('{fn}', fnName)); | |
} | |
}, | |
/** | |
* @method Phaser.Signal#_registerListener | |
* @private | |
* @param {function} listener - Signal handler function. | |
* @param {boolean} isOnce - Should the listener only be called once? | |
* @param {object} [listenerContext] - The context under which the listener is invoked. | |
* @param {number} [priority] - The priority level of the event listener. Listeners with higher priority will be executed before listeners with lower priority. Listeners with same priority level will be executed at the same order as they were added. (default = 0). | |
* @return {Phaser.SignalBinding} An Object representing the binding between the Signal and listener. | |
*/ | |
_registerListener: function (listener, isOnce, listenerContext, priority, args) { | |
var prevIndex = this._indexOfListener(listener, listenerContext); | |
var binding; | |
if (prevIndex !== -1) | |
{ | |
binding = this._bindings[prevIndex]; | |
if (binding.isOnce() !== isOnce) | |
{ | |
throw new Error('You cannot add' + (isOnce ? '' : 'Once') + '() then add' + (!isOnce ? '' : 'Once') + '() the same listener without removing the relationship first.'); | |
} | |
} | |
else | |
{ | |
binding = new Phaser.SignalBinding(this, listener, isOnce, listenerContext, priority, args); | |
this._addBinding(binding); | |
} | |
if (this.memorize && this._prevParams) | |
{ | |
binding.execute(this._prevParams); | |
} | |
return binding; | |
}, | |
/** | |
* @method Phaser.Signal#_addBinding | |
* @private | |
* @param {Phaser.SignalBinding} binding - An Object representing the binding between the Signal and listener. | |
*/ | |
_addBinding: function (binding) { | |
if (!this._bindings) | |
{ | |
this._bindings = []; | |
} | |
// Simplified insertion sort | |
var n = this._bindings.length; | |
do { | |
n--; | |
} | |
while (this._bindings[n] && binding._priority <= this._bindings[n]._priority); | |
this._bindings.splice(n + 1, 0, binding); | |
}, | |
/** | |
* @method Phaser.Signal#_indexOfListener | |
* @private | |
* @param {function} listener - Signal handler function. | |
* @param {object} [context=null] - Signal handler function. | |
* @return {number} The index of the listener within the private bindings array. | |
*/ | |
_indexOfListener: function (listener, context) { | |
if (!this._bindings) | |
{ | |
return -1; | |
} | |
if (context === undefined) { context = null; } | |
var n = this._bindings.length; | |
var cur; | |
while (n--) | |
{ | |
cur = this._bindings[n]; | |
if (cur._listener === listener && cur.context === context) | |
{ | |
return n; | |
} | |
} | |
return -1; | |
}, | |
/** | |
* Check if a specific listener is attached. | |
* | |
* @method Phaser.Signal#has | |
* @param {function} listener - Signal handler function. | |
* @param {object} [context] - Context on which listener will be executed (object that should represent the `this` variable inside listener function). | |
* @return {boolean} If Signal has the specified listener. | |
*/ | |
has: function (listener, context) { | |
return this._indexOfListener(listener, context) !== -1; | |
}, | |
/** | |
* Add an event listener for this signal. | |
* | |
* An event listener is a callback with a related context and priority. | |
* | |
* You can optionally provide extra arguments which will be passed to the callback after any internal parameters. | |
* | |
* For example: `Phaser.Key.onDown` when dispatched will send the Phaser.Key object that caused the signal as the first parameter. | |
* Any arguments you've specified after `priority` will be sent as well: | |
* | |
* `fireButton.onDown.add(shoot, this, 0, 'lazer', 100);` | |
* | |
* When onDown dispatches it will call the `shoot` callback passing it: `Phaser.Key, 'lazer', 100`. | |
* | |
* Where the first parameter is the one that Key.onDown dispatches internally and 'lazer', | |
* and the value 100 were the custom arguments given in the call to 'add'. | |
* | |
* @method Phaser.Signal#add | |
* @param {function} listener - The function to call when this Signal is dispatched. | |
* @param {object} [listenerContext] - The context under which the listener will be executed (i.e. the object that should represent the `this` variable). | |
* @param {number} [priority] - The priority level of the event listener. Listeners with higher priority will be executed before listeners with lower priority. Listeners with same priority level will be executed at the same order as they were added (default = 0) | |
* @param {...any} [args=(none)] - Additional arguments to pass to the callback (listener) function. They will be appended after any arguments usually dispatched. | |
* @return {Phaser.SignalBinding} An Object representing the binding between the Signal and listener. | |
*/ | |
add: function (listener, listenerContext, priority) { | |
this.validateListener(listener, 'add'); | |
var args = []; | |
if (arguments.length > 3) | |
{ | |
for (var i = 3; i < arguments.length; i++) | |
{ | |
args.push(arguments[i]); | |
} | |
} | |
return this._registerListener(listener, false, listenerContext, priority, args); | |
}, | |
/** | |
* Add a one-time listener - the listener is automatically removed after the first execution. | |
* | |
* If there is as {@link Phaser.Signal#memorize memorized} event then it will be dispatched and | |
* the listener will be removed immediately. | |
* | |
* @method Phaser.Signal#addOnce | |
* @param {function} listener - The function to call when this Signal is dispatched. | |
* @param {object} [listenerContext] - The context under which the listener will be executed (i.e. the object that should represent the `this` variable). | |
* @param {number} [priority] - The priority level of the event listener. Listeners with higher priority will be executed before listeners with lower priority. Listeners with same priority level will be executed at the same order as they were added (default = 0) | |
* @param {...any} [args=(none)] - Additional arguments to pass to the callback (listener) function. They will be appended after any arguments usually dispatched. | |
* @return {Phaser.SignalBinding} An Object representing the binding between the Signal and listener. | |
*/ | |
addOnce: function (listener, listenerContext, priority) { | |
this.validateListener(listener, 'addOnce'); | |
var args = []; | |
if (arguments.length > 3) | |
{ | |
for (var i = 3; i < arguments.length; i++) | |
{ | |
args.push(arguments[i]); | |
} | |
} | |
return this._registerListener(listener, true, listenerContext, priority, args); | |
}, | |
/** | |
* Remove a single event listener. | |
* | |
* @method Phaser.Signal#remove | |
* @param {function} listener - Handler function that should be removed. | |
* @param {object} [context=null] - Execution context (since you can add the same handler multiple times if executing in a different context). | |
* @return {function} Listener handler function. | |
*/ | |
remove: function (listener, context) { | |
this.validateListener(listener, 'remove'); | |
var i = this._indexOfListener(listener, context); | |
if (i !== -1) | |
{ | |
this._bindings[i]._destroy(); //no reason to a Phaser.SignalBinding exist if it isn't attached to a signal | |
this._bindings.splice(i, 1); | |
} | |
return listener; | |
}, | |
/** | |
* Remove all event listeners. | |
* | |
* @method Phaser.Signal#removeAll | |
* @param {object} [context=null] - If specified only listeners for the given context will be removed. | |
*/ | |
removeAll: function (context) { | |
if (context === undefined) { context = null; } | |
if (!this._bindings) | |
{ | |
return; | |
} | |
var n = this._bindings.length; | |
while (n--) | |
{ | |
if (context) | |
{ | |
if (this._bindings[n].context === context) | |
{ | |
this._bindings[n]._destroy(); | |
this._bindings.splice(n, 1); | |
} | |
} | |
else | |
{ | |
this._bindings[n]._destroy(); | |
} | |
} | |
if (!context) | |
{ | |
this._bindings.length = 0; | |
} | |
}, | |
/** | |
* Gets the total number of listeners attached to this Signal. | |
* | |
* @method Phaser.Signal#getNumListeners | |
* @return {integer} Number of listeners attached to the Signal. | |
*/ | |
getNumListeners: function () { | |
return this._bindings ? this._bindings.length : 0; | |
}, | |
/** | |
* Stop propagation of the event, blocking the dispatch to next listener on the queue. | |
* | |
* This should be called only during event dispatch as calling it before/after dispatch won't affect another broadcast. | |
* See {@link #active} to enable/disable the signal entirely. | |
* | |
* @method Phaser.Signal#halt | |
*/ | |
halt: function () { | |
this._shouldPropagate = false; | |
}, | |
/** | |
* Dispatch / broadcast the event to all listeners. | |
* | |
* To create an instance-bound dispatch for this Signal, use {@link #boundDispatch}. | |
* | |
* @method Phaser.Signal#dispatch | |
* @param {any} [params] - Parameters that should be passed to each handler. | |
*/ | |
dispatch: function () { | |
if (!this.active || !this._bindings) | |
{ | |
return; | |
} | |
var paramsArr = Array.prototype.slice.call(arguments); | |
var n = this._bindings.length; | |
var bindings; | |
if (this.memorize) | |
{ | |
this._prevParams = paramsArr; | |
} | |
if (!n) | |
{ | |
// Should come after memorize | |
return; | |
} | |
bindings = this._bindings.slice(); //clone array in case add/remove items during dispatch | |
this._shouldPropagate = true; //in case `halt` was called before dispatch or during the previous dispatch. | |
//execute all callbacks until end of the list or until a callback returns `false` or stops propagation | |
//reverse loop since listeners with higher priority will be added at the end of the list | |
do { | |
n--; | |
} | |
while (bindings[n] && this._shouldPropagate && bindings[n].execute(paramsArr) !== false); | |
}, | |
/** | |
* Forget the currently {@link Phaser.Signal#memorize memorized} event, if any. | |
* | |
* @method Phaser.Signal#forget | |
*/ | |
forget: function() { | |
if (this._prevParams) | |
{ | |
this._prevParams = null; | |
} | |
}, | |
/** | |
* Dispose the signal - no more events can be dispatched. | |
* | |
* This removes all event listeners and clears references to external objects. | |
* Calling methods on a disposed objects results in undefined behavior. | |
* | |
* @method Phaser.Signal#dispose | |
*/ | |
dispose: function () { | |
this.removeAll(); | |
this._bindings = null; | |
if (this._prevParams) | |
{ | |
this._prevParams = null; | |
} | |
}, | |
/** | |
* A string representation of the object. | |
* | |
* @method Phaser.Signal#toString | |
* @return {string} String representation of the object. | |
*/ | |
toString: function () { | |
return '[Phaser.Signal active:'+ this.active +' numListeners:'+ this.getNumListeners() +']'; | |
} | |
}; | |
/** | |
* Create a `dispatch` function that maintains a binding to the original Signal context. | |
* | |
* Use the resulting value if the dispatch function needs to be passed somewhere | |
* or called independently of the Signal object. | |
* | |
* @memberof Phaser.Signal | |
* @property {function} boundDispatch | |
*/ | |
Object.defineProperty(Phaser.Signal.prototype, "boundDispatch", { | |
get: function () { | |
var _this = this; | |
return this._boundDispatch || (this._boundDispatch = function () { | |
return _this.dispatch.apply(_this, arguments); | |
}); | |
} | |
}); | |
Phaser.Signal.prototype.constructor = Phaser.Signal; | |
/** | |
* @author Miller Medeiros http://millermedeiros.github.com/js-signals/ | |
* @author Richard Davey <[email protected]> | |
* @copyright 2016 Photon Storm Ltd. | |
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} | |
*/ | |
/** | |
* Object that represents a binding between a Signal and a listener function. | |
* This is an internal constructor and shouldn't be created directly. | |
* Inspired by Joa Ebert AS3 SignalBinding and Robert Penner's Slot classes. | |
* | |
* @class Phaser.SignalBinding | |
* @constructor | |
* @param {Phaser.Signal} signal - Reference to Signal object that listener is currently bound to. | |
* @param {function} listener - Handler function bound to the signal. | |
* @param {boolean} isOnce - If binding should be executed just once. | |
* @param {object} [listenerContext=null] - Context on which listener will be executed (object that should represent the `this` variable inside listener function). | |
* @param {number} [priority] - The priority level of the event listener. (default = 0). | |
* @param {...any} [args=(none)] - Additional arguments to pass to the callback (listener) function. They will be appended after any arguments usually dispatched. | |
*/ | |
Phaser.SignalBinding = function (signal, listener, isOnce, listenerContext, priority, args) { | |
/** | |
* @property {Phaser.Game} _listener - Handler function bound to the signal. | |
* @private | |
*/ | |
this._listener = listener; | |
if (isOnce) | |
{ | |
this._isOnce = true; | |
} | |
if (listenerContext != null) /* not null/undefined */ | |
{ | |
this.context = listenerContext; | |
} | |
/** | |
* @property {Phaser.Signal} _signal - Reference to Signal object that listener is currently bound to. | |
* @private | |
*/ | |
this._signal = signal; | |
if (priority) | |
{ | |
this._priority = priority; | |
} | |
if (args && args.length) | |
{ | |
this._args = args; | |
} | |
}; | |
Phaser.SignalBinding.prototype = { | |
/** | |
* @property {?object} context - Context on which listener will be executed (object that should represent the `this` variable inside listener function). | |
*/ | |
context: null, | |
/** | |
* @property {boolean} _isOnce - If binding should be executed just once. | |
* @private | |
*/ | |
_isOnce: false, | |
/** | |
* @property {number} _priority - Listener priority. | |
* @private | |
*/ | |
_priority: 0, | |
/** | |
* @property {array} _args - Listener arguments. | |
* @private | |
*/ | |
_args: null, | |
/** | |
* @property {number} callCount - The number of times the handler function has been called. | |
*/ | |
callCount: 0, | |
/** | |
* If binding is active and should be executed. | |
* @property {boolean} active | |
* @default | |
*/ | |
active: true, | |
/** | |
* Default parameters passed to listener during `Signal.dispatch` and `SignalBinding.execute` (curried parameters). | |
* @property {array|null} params | |
* @default | |
*/ | |
params: null, | |
/** | |
* Call listener passing arbitrary parameters. | |
* If binding was added using `Signal.addOnce()` it will be automatically removed from signal dispatch queue, this method is used internally for the signal dispatch. | |
* @method Phaser.SignalBinding#execute | |
* @param {any[]} [paramsArr] - Array of parameters that should be passed to the listener. | |
* @return {any} Value returned by the listener. | |
*/ | |
execute: function(paramsArr) { | |
var handlerReturn, params; | |
if (this.active && !!this._listener) | |
{ | |
params = this.params ? this.params.concat(paramsArr) : paramsArr; | |
if (this._args) | |
{ | |
params = params.concat(this._args); | |
} | |
handlerReturn = this._listener.apply(this.context, params); | |
this.callCount++; | |
if (this._isOnce) | |
{ | |
this.detach(); | |
} | |
} | |
return handlerReturn; | |
}, | |
/** | |
* Detach binding from signal. | |
* alias to: @see mySignal.remove(myBinding.getListener()); | |
* @method Phaser.SignalBinding#detach | |
* @return {function|null} Handler function bound to the signal or `null` if binding was previously detached. | |
*/ | |
detach: function () { | |
return this.isBound() ? this._signal.remove(this._listener, this.context) : null; | |
}, | |
/** | |
* @method Phaser.SignalBinding#isBound | |
* @return {boolean} True if binding is still bound to the signal and has a listener. | |
*/ | |
isBound: function () { | |
return (!!this._signal && !!this._listener); | |
}, | |
/** | |
* @method Phaser.SignalBinding#isOnce | |
* @return {boolean} If SignalBinding will only be executed once. | |
*/ | |
isOnce: function () { | |
return this._isOnce; | |
}, | |
/** | |
* @method Phaser.SignalBinding#getListener | |
* @return {function} Handler function bound to the signal. | |
*/ | |
getListener: function () { | |
return this._listener; | |
}, | |
/** | |
* @method Phaser.SignalBinding#getSignal | |
* @return {Phaser.Signal} Signal that listener is currently bound to. | |
*/ | |
getSignal: function () { | |
return this._signal; | |
}, | |
/** | |
* Delete instance properties | |
* @method Phaser.SignalBinding#_destroy | |
* @private | |
*/ | |
_destroy: function () { | |
delete this._signal; | |
delete this._listener; | |
delete this.context; | |
}, | |
/** | |
* @method Phaser.SignalBinding#toString | |
* @return {string} String representation of the object. | |
*/ | |
toString: function () { | |
return '[Phaser.SignalBinding isOnce:' + this._isOnce +', isBound:'+ this.isBound() +', active:' + this.active + ']'; | |
} | |
}; | |
Phaser.SignalBinding.prototype.constructor = Phaser.SignalBinding; | |
/** | |
* @author Richard Davey <[email protected]> | |
* @copyright 2016 Photon Storm Ltd. | |
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} | |
*/ | |
/** | |
* This is a base Filter class to use for any Phaser filter development. | |
* | |
* The vast majority of filters (including all of those that ship with Phaser) use fragment shaders, and | |
* therefore only work in WebGL and are not supported by Canvas at all. | |
* | |
* @class Phaser.Filter | |
* @constructor | |
* @param {Phaser.Game} game - A reference to the currently running game. | |
* @param {object} uniforms - Uniform mappings object | |
* @param {Array|string} fragmentSrc - The fragment shader code. Either an array, one element per line of code, or a string. | |
*/ | |
Phaser.Filter = function (game, uniforms, fragmentSrc) { | |
/** | |
* @property {Phaser.Game} game - A reference to the currently running game. | |
*/ | |
this.game = game; | |
/** | |
* @property {number} type - The const type of this object, either Phaser.WEBGL_FILTER or Phaser.CANVAS_FILTER. | |
* @default | |
*/ | |
this.type = Phaser.WEBGL_FILTER; | |
/** | |
* An array of passes - some filters contain a few steps this array simply stores the steps in a linear fashion. | |
* For example the blur filter has two passes blurX and blurY. | |
* @property {array} passes - An array of filter objects. | |
* @private | |
*/ | |
this.passes = [this]; | |
/** | |
* @property {array} shaders - Array an array of shaders. | |
* @private | |
*/ | |
this.shaders = []; | |
/** | |
* @property {boolean} dirty - Internal PIXI var. | |
* @default | |
*/ | |
this.dirty = true; | |
/** | |
* @property {number} padding - Internal PIXI var. | |
* @default | |
*/ | |
this.padding = 0; | |
/** | |
* @property {Phaser.Point} prevPoint - The previous position of the pointer (we don't update the uniform if the same) | |
*/ | |
this.prevPoint = new Phaser.Point(); | |
/* | |
* The supported types are: 1f, 1fv, 1i, 2f, 2fv, 2i, 2iv, 3f, 3fv, 3i, 3iv, 4f, 4fv, 4i, 4iv, mat2, mat3, mat4 and sampler2D. | |
*/ | |
var d = new Date(); | |
/** | |
* @property {object} uniforms - Default uniform mappings. Compatible with ShaderToy and GLSLSandbox. | |
*/ | |
this.uniforms = { | |
resolution: { type: '2f', value: { x: 256, y: 256 }}, | |
time: { type: '1f', value: 0 }, | |
mouse: { type: '2f', value: { x: 0.0, y: 0.0 } }, | |
date: { type: '4fv', value: [ d.getFullYear(), d.getMonth(), d.getDate(), d.getHours() *60 * 60 + d.getMinutes() * 60 + d.getSeconds() ] }, | |
sampleRate: { type: '1f', value: 44100.0 }, | |
iChannel0: { type: 'sampler2D', value: null, textureData: { repeat: true } }, | |
iChannel1: { type: 'sampler2D', value: null, textureData: { repeat: true } }, | |
iChannel2: { type: 'sampler2D', value: null, textureData: { repeat: true } }, | |
iChannel3: { type: 'sampler2D', value: null, textureData: { repeat: true } } | |
}; | |
// Copy over/replace any passed in the constructor | |
if (uniforms) | |
{ | |
for (var key in uniforms) | |
{ | |
this.uniforms[key] = uniforms[key]; | |
} | |
} | |
/** | |
* @property {array|string} fragmentSrc - The fragment shader code. | |
*/ | |
this.fragmentSrc = fragmentSrc || ''; | |
}; | |
Phaser.Filter.prototype = { | |
/** | |
* Should be over-ridden. | |
* @method Phaser.Filter#init | |
*/ | |
init: function () { | |
// This should be over-ridden. Will receive a variable number of arguments. | |
}, | |
/** | |
* Set the resolution uniforms on the filter. | |
* @method Phaser.Filter#setResolution | |
* @param {number} width - The width of the display. | |
* @param {number} height - The height of the display. | |
*/ | |
setResolution: function (width, height) { | |
this.uniforms.resolution.value.x = width; | |
this.uniforms.resolution.value.y = height; | |
}, | |
/** | |
* Updates the filter. | |
* @method Phaser.Filter#update | |
* @param {Phaser.Pointer} [pointer] - A Pointer object to use for the filter. The coordinates are mapped to the mouse uniform. | |
*/ | |
update: function (pointer) { | |
if (typeof pointer !== 'undefined') | |
{ | |
var x = pointer.x / this.game.width; | |
var y = 1 - pointer.y / this.game.height; | |
if (x !== this.prevPoint.x || y !== this.prevPoint.y) | |
{ | |
this.uniforms.mouse.value.x = x.toFixed(2); | |
this.uniforms.mouse.value.y = y.toFixed(2); | |
this.prevPoint.set(x, y); | |
} | |
} | |
this.uniforms.time.value = this.game.time.totalElapsedSeconds(); | |
}, | |
/** | |
* Creates a new Phaser.Image object using a blank texture and assigns | |
* this Filter to it. The image is then added to the world. | |
* | |
* If you don't provide width and height values then Filter.width and Filter.height are used. | |
* | |
* If you do provide width and height values then this filter will be resized to match those | |
* values. | |
* | |
* @method Phaser.Filter#addToWorld | |
* @param {number} [x=0] - The x coordinate to place the Image at. | |
* @param {number} [y=0] - The y coordinate to place the Image at. | |
* @param {number} [width] - The width of the Image. If not specified (or null) it will use Filter.width. If specified Filter.width will be set to this value. | |
* @param {number} [height] - The height of the Image. If not specified (or null) it will use Filter.height. If specified Filter.height will be set to this value. | |
* @param {number} [anchorX=0] - Set the x anchor point of the Image. A value between 0 and 1, where 0 is the top-left and 1 is bottom-right. | |
* @param {number} [anchorY=0] - Set the y anchor point of the Image. A value between 0 and 1, where 0 is the top-left and 1 is bottom-right. | |
* @return {Phaser.Image} The newly added Image object. | |
*/ | |
addToWorld: function (x, y, width, height, anchorX, anchorY) { | |
if (anchorX === undefined) { anchorX = 0; } | |
if (anchorY === undefined) { anchorY = 0; } | |
if (width !== undefined && width !== null) | |
{ | |
this.width = width; | |
} | |
else | |
{ | |
width = this.width; | |
} | |
if (height !== undefined && height !== null) | |
{ | |
this.height = height; | |
} | |
else | |
{ | |
height = this.height; | |
} | |
var image = this.game.add.image(x, y, '__default'); | |
image.width = width; | |
image.height = height; | |
image.anchor.set(anchorX, anchorY); | |
image.filters = [ this ]; | |
return image; | |
}, | |
/** | |
* Clear down this Filter and null out references | |
* @method Phaser.Filter#destroy | |
*/ | |
destroy: function () { | |
this.game = null; | |
} | |
}; | |
Phaser.Filter.prototype.constructor = Phaser.Filter; | |
/** | |
* @name Phaser.Filter#width | |
* @property {number} width - The width (resolution uniform) | |
*/ | |
Object.defineProperty(Phaser.Filter.prototype, 'width', { | |
get: function() { | |
return this.uniforms.resolution.value.x; | |
}, | |
set: function(value) { | |
this.uniforms.resolution.value.x = value; | |
} | |
}); | |
/** | |
* @name Phaser.Filter#height | |
* @property {number} height - The height (resolution uniform) | |
*/ | |
Object.defineProperty(Phaser.Filter.prototype, 'height', { | |
get: function() { | |
return this.uniforms.resolution.value.y; | |
}, | |
set: function(value) { | |
this.uniforms.resolution.value.y = value; | |
} | |
}); | |
/** | |
* @author Richard Davey <[email protected]> | |
* @copyright 2016 Photon Storm Ltd. | |
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} | |
*/ | |
/** | |
* This is a base Plugin template to use for any Phaser plugin development. | |
* | |
* @class Phaser.Plugin | |
* @constructor | |
* @param {Phaser.Game} game - A reference to the currently running game. | |
* @param {any} parent - The object that owns this plugin, usually Phaser.PluginManager. | |
*/ | |
Phaser.Plugin = function (game, parent) { | |
if (parent === undefined) { parent = null; } | |
/** | |
* @property {Phaser.Game} game - A reference to the currently running game. | |
*/ | |
this.game = game; | |
/** | |
* @property {any} parent - The parent of this plugin. If added to the PluginManager the parent will be set to that, otherwise it will be null. | |
*/ | |
this.parent = parent; | |
/** | |
* @property {boolean} active - A Plugin with active=true has its preUpdate and update methods called by the parent, otherwise they are skipped. | |
* @default | |
*/ | |
this.active = false; | |
/** | |
* @property {boolean} visible - A Plugin with visible=true has its render and postRender methods called by the parent, otherwise they are skipped. | |
* @default | |
*/ | |
this.visible = false; | |
/** | |
* @property {boolean} hasPreUpdate - A flag to indicate if this plugin has a preUpdate method. | |
* @default | |
*/ | |
this.hasPreUpdate = false; | |
/** | |
* @property {boolean} hasUpdate - A flag to indicate if this plugin has an update method. | |
* @default | |
*/ | |
this.hasUpdate = false; | |
/** | |
* @property {boolean} hasPostUpdate - A flag to indicate if this plugin has a postUpdate method. | |
* @default | |
*/ | |
this.hasPostUpdate = false; | |
/** | |
* @property {boolean} hasRender - A flag to indicate if this plugin has a render method. | |
* @default | |
*/ | |
this.hasRender = false; | |
/** | |
* @property {boolean} hasPostRender - A flag to indicate if this plugin has a postRender method. | |
* @default | |
*/ | |
this.hasPostRender = false; | |
}; | |
Phaser.Plugin.prototype = { | |
/** | |
* Pre-update is called at the very start of the update cycle, before any other subsystems have been updated (including Physics). | |
* It is only called if active is set to true. | |
* @method Phaser.Plugin#preUpdate | |
*/ | |
preUpdate: function () { | |
}, | |
/** | |
* Update is called after all the core subsystems (Input, Tweens, Sound, etc) and the State have updated, but before the render. | |
* It is only called if active is set to true. | |
* @method Phaser.Plugin#update | |
*/ | |
update: function () { | |
}, | |
/** | |
* Render is called right after the Game Renderer completes, but before the State.render. | |
* It is only called if visible is set to true. | |
* @method Phaser.Plugin#render | |
*/ | |
render: function () { | |
}, | |
/** | |
* Post-render is called after the Game Renderer and State.render have run. | |
* It is only called if visible is set to true. | |
* @method Phaser.Plugin#postRender | |
*/ | |
postRender: function () { | |
}, | |
/** | |
* Clear down this Plugin and null out references | |
* @method Phaser.Plugin#destroy | |
*/ | |
destroy: function () { | |
this.game = null; | |
this.parent = null; | |
this.active = false; | |
this.visible = false; | |
} | |
}; | |
Phaser.Plugin.prototype.constructor = Phaser.Plugin; | |
/* jshint newcap: false */ | |
/** | |
* @author Richard Davey <[email protected]> | |
* @copyright 2016 Photon Storm Ltd. | |
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} | |
*/ | |
/** | |
* The Plugin Manager is responsible for the loading, running and unloading of Phaser Plugins. | |
* | |
* @class Phaser.PluginManager | |
* @constructor | |
* @param {Phaser.Game} game - A reference to the currently running game. | |
*/ | |
Phaser.PluginManager = function(game) { | |
/** | |
* @property {Phaser.Game} game - A reference to the currently running game. | |
*/ | |
this.game = game; | |
/** | |
* @property {Phaser.Plugin[]} plugins - An array of all the plugins being managed by this PluginManager. | |
*/ | |
this.plugins = []; | |
/** | |
* @property {number} _len - Internal cache var. | |
* @private | |
*/ | |
this._len = 0; | |
/** | |
* @property {number} _i - Internal cache var. | |
* @private | |
*/ | |
this._i = 0; | |
}; | |
Phaser.PluginManager.prototype = { | |
/** | |
* Add a new Plugin into the PluginManager. | |
* The Plugin must have 2 properties: game and parent. Plugin.game is set to the game reference the PluginManager uses, and parent is set to the PluginManager. | |
* | |
* @method Phaser.PluginManager#add | |
* @param {object|Phaser.Plugin} plugin - The Plugin to add into the PluginManager. This can be a function or an existing object. | |
* @param {...*} parameter - Additional arguments that will be passed to the Plugin.init method. | |
* @return {Phaser.Plugin} The Plugin that was added to the manager. | |
*/ | |
add: function (plugin) { | |
var args = Array.prototype.slice.call(arguments, 1); | |
var result = false; | |
// Prototype? | |
if (typeof plugin === 'function') | |
{ | |
plugin = new plugin(this.game, this); | |
} | |
else | |
{ | |
plugin.game = this.game; | |
plugin.parent = this; | |
} | |
// Check for methods now to avoid having to do this every loop | |
if (typeof plugin['preUpdate'] === 'function') | |
{ | |
plugin.hasPreUpdate = true; | |
result = true; | |
} | |
if (typeof plugin['update'] === 'function') | |
{ | |
plugin.hasUpdate = true; | |
result = true; | |
} | |
if (typeof plugin['postUpdate'] === 'function') | |
{ | |
plugin.hasPostUpdate = true; | |
result = true; | |
} | |
if (typeof plugin['render'] === 'function') | |
{ | |
plugin.hasRender = true; | |
result = true; | |
} | |
if (typeof plugin['postRender'] === 'function') | |
{ | |
plugin.hasPostRender = true; | |
result = true; | |
} | |
// The plugin must have at least one of the above functions to be added to the PluginManager. | |
if (result) | |
{ | |
if (plugin.hasPreUpdate || plugin.hasUpdate || plugin.hasPostUpdate) | |
{ | |
plugin.active = true; | |
} | |
if (plugin.hasRender || plugin.hasPostRender) | |
{ | |
plugin.visible = true; | |
} | |
this._len = this.plugins.push(plugin); | |
// Allows plugins to run potentially destructive code outside of the constructor, and only if being added to the PluginManager | |
if (typeof plugin['init'] === 'function') | |
{ | |
plugin.init.apply(plugin, args); | |
} | |
return plugin; | |
} | |
else | |
{ | |
return null; | |
} | |
}, | |
/** | |
* Remove a Plugin from the PluginManager. It calls Plugin.destroy on the plugin before removing it from the manager. | |
* | |
* @method Phaser.PluginManager#remove | |
* @param {Phaser.Plugin} plugin - The plugin to be removed. | |
* @param {boolean} [destroy=true] - Call destroy on the plugin that is removed? | |
*/ | |
remove: function (plugin, destroy) { | |
if (destroy === undefined) { destroy = true; } | |
this._i = this._len; | |
while (this._i--) | |
{ | |
if (this.plugins[this._i] === plugin) | |
{ | |
if (destroy) | |
{ | |
plugin.destroy(); | |
} | |
this.plugins.splice(this._i, 1); | |
this._len--; | |
return; | |
} | |
} | |
}, | |
/** | |
* Remove all Plugins from the PluginManager. It calls Plugin.destroy on every plugin before removing it from the manager. | |
* | |
* @method Phaser.PluginManager#removeAll | |
*/ | |
removeAll: function() { | |
this._i = this._len; | |
while (this._i--) | |
{ | |
this.plugins[this._i].destroy(); | |
} | |
this.plugins.length = 0; | |
this._len = 0; | |
}, | |
/** | |
* Pre-update is called at the very start of the update cycle, before any other subsystems have been updated (including Physics). | |
* It only calls plugins who have active=true. | |
* | |
* @method Phaser.PluginManager#preUpdate | |
*/ | |
preUpdate: function () { | |
this._i = this._len; | |
while (this._i--) | |
{ | |
if (this.plugins[this._i].active && this.plugins[this._i].hasPreUpdate) | |
{ | |
this.plugins[this._i].preUpdate(); | |
} | |
} | |
}, | |
/** | |
* Update is called after all the core subsystems (Input, Tweens, Sound, etc) and the State have updated, but before the render. | |
* It only calls plugins who have active=true. | |
* | |
* @method Phaser.PluginManager#update | |
*/ | |
update: function () { | |
this._i = this._len; | |
while (this._i--) | |
{ | |
if (this.plugins[this._i].active && this.plugins[this._i].hasUpdate) | |
{ | |
this.plugins[this._i].update(); | |
} | |
} | |
}, | |
/** | |
* PostUpdate is the last thing to be called before the world render. | |
* In particular, it is called after the world postUpdate, which means the camera has been adjusted. | |
* It only calls plugins who have active=true. | |
* | |
* @method Phaser.PluginManager#postUpdate | |
*/ | |
postUpdate: function () { | |
this._i = this._len; | |
while (this._i--) | |
{ | |
if (this.plugins[this._i].active && this.plugins[this._i].hasPostUpdate) | |
{ | |
this.plugins[this._i].postUpdate(); | |
} | |
} | |
}, | |
/** | |
* Render is called right after the Game Renderer completes, but before the State.render. | |
* It only calls plugins who have visible=true. | |
* | |
* @method Phaser.PluginManager#render | |
*/ | |
render: function () { | |
this._i = this._len; | |
while (this._i--) | |
{ | |
if (this.plugins[this._i].visible && this.plugins[this._i].hasRender) | |
{ | |
this.plugins[this._i].render(); | |
} | |
} | |
}, | |
/** | |
* Post-render is called after the Game Renderer and State.render have run. | |
* It only calls plugins who have visible=true. | |
* | |
* @method Phaser.PluginManager#postRender | |
*/ | |
postRender: function () { | |
this._i = this._len; | |
while (this._i--) | |
{ | |
if (this.plugins[this._i].visible && this.plugins[this._i].hasPostRender) | |
{ | |
this.plugins[this._i].postRender(); | |
} | |
} | |
}, | |
/** | |
* Clear down this PluginManager, calls destroy on every plugin and nulls out references. | |
* | |
* @method Phaser.PluginManager#destroy | |
*/ | |
destroy: function () { | |
this.removeAll(); | |
this.game = null; | |
} | |
}; | |
Phaser.PluginManager.prototype.constructor = Phaser.PluginManager; | |
/** | |
* @author Richard Davey <[email protected]> | |
* @copyright 2016 Photon Storm Ltd. | |
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} | |
*/ | |
/** | |
* The Stage controls root level display objects upon which everything is displayed. | |
* It also handles browser visibility handling and the pausing due to loss of focus. | |
* | |
* @class Phaser.Stage | |
* @extends PIXI.DisplayObjectContainer | |
* @constructor | |
* @param {Phaser.Game} game - Game reference to the currently running game. | |
*/ | |
Phaser.Stage = function (game) { | |
/** | |
* @property {Phaser.Game} game - A reference to the currently running Game. | |
*/ | |
this.game = game; | |
PIXI.DisplayObjectContainer.call(this); | |
/** | |
* @property {string} name - The name of this object. | |
* @default | |
*/ | |
this.name = '_stage_root'; | |
/** | |
* By default if the browser tab loses focus the game will pause. | |
* You can stop that behavior by setting this property to true. | |
* Note that the browser can still elect to pause your game if it wishes to do so, | |
* for example swapping to another browser tab. This will cause the RAF callback to halt, | |
* effectively pausing your game, even though no in-game pause event is triggered if you enable this property. | |
* @property {boolean} disableVisibilityChange | |
* @default | |
*/ | |
this.disableVisibilityChange = false; | |
/** | |
* @property {boolean} exists - If exists is true the Stage and all children are updated, otherwise it is skipped. | |
* @default | |
*/ | |
this.exists = true; | |
/** | |
* @property {PIXI.Matrix} worldTransform - Current transform of the object based on world (parent) factors | |
* @private | |
* @readOnly | |
*/ | |
this.worldTransform = new PIXI.Matrix(); | |
/** | |
* @property {Phaser.Stage} stage - The stage reference (the Stage is its own stage) | |
* @private | |
* @readOnly | |
*/ | |
this.stage = this; | |
/** | |
* @property {number} currentRenderOrderID - Reset each frame, keeps a count of the total number of objects updated. | |
*/ | |
this.currentRenderOrderID = 0; | |
/** | |
* @property {string} hiddenVar - The page visibility API event name. | |
* @private | |
*/ | |
this._hiddenVar = 'hidden'; | |
/** | |
* @property {function} _onChange - The blur/focus event handler. | |
* @private | |
*/ | |
this._onChange = null; | |
/** | |
* @property {number} _bgColor - Stage background color object. Populated by setBackgroundColor. | |
* @private | |
*/ | |
this._bgColor = { r: 0, g: 0, b: 0, a: 0, color: 0, rgba: '#000000' }; | |
if (!this.game.transparent) | |
{ | |
// transparent = 0,0,0,0 - otherwise r,g,b,1 | |
this._bgColor.a = 1; | |
} | |
if (game.config) | |
{ | |
this.parseConfig(game.config); | |
} | |
}; | |
Phaser.Stage.prototype = Object.create(PIXI.DisplayObjectContainer.prototype); | |
Phaser.Stage.prototype.constructor = Phaser.Stage; | |
/** | |
* Parses a Game configuration object. | |
* | |
* @method Phaser.Stage#parseConfig | |
* @protected | |
* @param {object} config -The configuration object to parse. | |
*/ | |
Phaser.Stage.prototype.parseConfig = function (config) { | |
if (config['disableVisibilityChange']) | |
{ | |
this.disableVisibilityChange = config['disableVisibilityChange']; | |
} | |
if (config['backgroundColor']) | |
{ | |
this.setBackgroundColor(config['backgroundColor']); | |
} | |
}; | |
/** | |
* Initialises the stage and adds the event listeners. | |
* @method Phaser.Stage#boot | |
* @private | |
*/ | |
Phaser.Stage.prototype.boot = function () { | |
Phaser.DOM.getOffset(this.game.canvas, this.offset); | |
Phaser.Canvas.setUserSelect(this.game.canvas, 'none'); | |
Phaser.Canvas.setTouchAction(this.game.canvas, 'none'); | |
this.checkVisibility(); | |
}; | |
/** | |
* This is called automatically after the plugins preUpdate and before the State.update. | |
* Most objects have preUpdate methods and it's where initial movement and positioning is done. | |
* | |
* @method Phaser.Stage#preUpdate | |
*/ | |
Phaser.Stage.prototype.preUpdate = function () { | |
this.currentRenderOrderID = 0; | |
// This can't loop in reverse, we need the renderOrderID to be in sequence | |
for (var i = 0; i < this.children.length; i++) | |
{ | |
this.children[i].preUpdate(); | |
} | |
}; | |
/** | |
* This is called automatically after the State.update, but before particles or plugins update. | |
* | |
* @method Phaser.Stage#update | |
*/ | |
Phaser.Stage.prototype.update = function () { | |
// Goes in reverse, because it's highly likely the child will destroy itself in `update` | |
var i = this.children.length; | |
while (i--) | |
{ | |
this.children[i].update(); | |
} | |
}; | |
/** | |
* This is called automatically before the renderer runs and after the plugins have updated. | |
* In postUpdate this is where all the final physics calculations and object positioning happens. | |
* The objects are processed in the order of the display list. | |
* | |
* @method Phaser.Stage#postUpdate | |
*/ | |
Phaser.Stage.prototype.postUpdate = function () { | |
// Apply the camera shake, fade, bounds, etc | |
this.game.camera.update(); | |
// Camera target first? | |
if (this.game.camera.target) | |
{ | |
this.game.camera.target.postUpdate(); | |
this.updateTransform(); | |
this.game.camera.updateTarget(); | |
} | |
for (var i = 0; i < this.children.length; i++) | |
{ | |
this.children[i].postUpdate(); | |
} | |
this.updateTransform(); | |
}; | |
/** | |
* Updates the transforms for all objects on the display list. | |
* This overrides the Pixi default as we don't need the interactionManager, but do need the game property check. | |
* | |
* @method Phaser.Stage#updateTransform | |
*/ | |
Phaser.Stage.prototype.updateTransform = function () { | |
this.worldAlpha = 1; | |
for (var i = 0; i < this.children.length; i++) | |
{ | |
this.children[i].updateTransform(); | |
} | |
}; | |
/** | |
* Starts a page visibility event listener running, or window.onpagehide/onpageshow if not supported by the browser. | |
* Also listens for window.onblur and window.onfocus. | |
* | |
* @method Phaser.Stage#checkVisibility | |
*/ | |
Phaser.Stage.prototype.checkVisibility = function () { | |
if (document.hidden !== undefined) | |
{ | |
this._hiddenVar = 'visibilitychange'; | |
} | |
else if (document.webkitHidden !== undefined) | |
{ | |
this._hiddenVar = 'webkitvisibilitychange'; | |
} | |
else if (document.mozHidden !== undefined) | |
{ | |
this._hiddenVar = 'mozvisibilitychange'; | |
} | |
else if (document.msHidden !== undefined) | |
{ | |
this._hiddenVar = 'msvisibilitychange'; | |
} | |
else | |
{ | |
this._hiddenVar = null; | |
} | |
var _this = this; | |
this._onChange = function (event) { | |
return _this.visibilityChange(event); | |
}; | |
// Does browser support it? If not (like in IE9 or old Android) we need to fall back to blur/focus | |
if (this._hiddenVar) | |
{ | |
document.addEventListener(this._hiddenVar, this._onChange, false); | |
} | |
window.onblur = this._onChange; | |
window.onfocus = this._onChange; | |
window.onpagehide = this._onChange; | |
window.onpageshow = this._onChange; | |
if (this.game.device.cocoonJSApp) | |
{ | |
CocoonJS.App.onSuspended.addEventListener(function () { | |
Phaser.Stage.prototype.visibilityChange.call(_this, { type: "pause" }); | |
}); | |
CocoonJS.App.onActivated.addEventListener(function () { | |
Phaser.Stage.prototype.visibilityChange.call(_this, { type: "resume" }); | |
}); | |
} | |
}; | |
/** | |
* This method is called when the document visibility is changed. | |
* | |
* @method Phaser.Stage#visibilityChange | |
* @param {Event} event - Its type will be used to decide whether the game should be paused or not. | |
*/ | |
Phaser.Stage.prototype.visibilityChange = function (event) { | |
if (event.type === 'pagehide' || event.type === 'blur' || event.type === 'pageshow' || event.type === 'focus') | |
{ | |
if (event.type === 'pagehide' || event.type === 'blur') | |
{ | |
this.game.focusLoss(event); | |
} | |
else if (event.type === 'pageshow' || event.type === 'focus') | |
{ | |
this.game.focusGain(event); | |
} | |
return; | |
} | |
if (this.disableVisibilityChange) | |
{ | |
return; | |
} | |
if (document.hidden || document.mozHidden || document.msHidden || document.webkitHidden || event.type === "pause") | |
{ | |
this.game.gamePaused(event); | |
} | |
else | |
{ | |
this.game.gameResumed(event); | |
} | |
}; | |
/** | |
* Sets the background color for the Stage. | |
* | |
* The color can be given as a hex string (`'#RRGGBB'`), a CSS color string (`'rgb(r,g,b)'`), or a numeric value (`0xRRGGBB`). | |
* | |
* An alpha channel is _not_ supported and will be ignored. | |
* | |
* If you've set your game to be transparent then calls to setBackgroundColor are ignored. | |
* | |
* @method Phaser.Stage#setBackgroundColor | |
* @param {number|string} color - The color of the background. | |
*/ | |
Phaser.Stage.prototype.setBackgroundColor = function (color) { | |
if (this.game.transparent) { return; } | |
Phaser.Color.valueToColor(color, this._bgColor); | |
Phaser.Color.updateColor(this._bgColor); | |
// For gl.clearColor (canvas uses _bgColor.rgba) | |
this._bgColor.r /= 255; | |
this._bgColor.g /= 255; | |
this._bgColor.b /= 255; | |
this._bgColor.a = 1; | |
}; | |
/** | |
* Destroys the Stage and removes event listeners. | |
* | |
* @method Phaser.Stage#destroy | |
*/ | |
Phaser.Stage.prototype.destroy = function () { | |
if (this._hiddenVar) | |
{ | |
document.removeEventListener(this._hiddenVar, this._onChange, false); | |
} | |
window.onpagehide = null; | |
window.onpageshow = null; | |
window.onblur = null; | |
window.onfocus = null; | |
}; | |
/** | |
* @name Phaser.Stage#backgroundColor | |
* @property {number|string} backgroundColor - Gets and sets the background color of the stage. The color can be given as a number: 0xff0000 or a hex string: '#ff0000' | |
*/ | |
Object.defineProperty(Phaser.Stage.prototype, "backgroundColor", { | |
get: function () { | |
return this._bgColor.color; | |
}, | |
set: function (color) { | |
this.setBackgroundColor(color); | |
} | |
}); | |
/** | |
* Enable or disable texture smoothing for all objects on this Stage. Only works for bitmap/image textures. Smoothing is enabled by default. | |
* | |
* @name Phaser.Stage#smoothed | |
* @property {boolean} smoothed - Set to true to smooth all sprites rendered on this Stage, or false to disable smoothing (great for pixel art) | |
*/ | |
Object.defineProperty(Phaser.Stage.prototype, "smoothed", { | |
get: function () { | |
return PIXI.scaleModes.DEFAULT === PIXI.scaleModes.LINEAR; | |
}, | |
set: function (value) { | |
if (value) | |
{ | |
PIXI.scaleModes.DEFAULT = PIXI.scaleModes.LINEAR; | |
} | |
else | |
{ | |
PIXI.scaleModes.DEFAULT = PIXI.scaleModes.NEAREST; | |
} | |
} | |
}); | |
/** | |
* @author Richard Davey <[email protected]> | |
* @copyright 2016 Photon Storm Ltd. | |
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} | |
*/ | |
/** | |
* A Group is a container for {@link DisplayObject display objects} including {@link Phaser.Sprite Sprites} and {@link Phaser.Image Images}. | |
* | |
* Groups form the logical tree structure of the display/scene graph where local transformations are applied to children. | |
* For instance, all children are also moved/rotated/scaled when the group is moved/rotated/scaled. | |
* | |
* In addition, Groups provides support for fast pooling and object recycling. | |
* | |
* Groups are also display objects and can be nested as children within other Groups. | |
* | |
* @class Phaser.Group | |
* @extends PIXI.DisplayObjectContainer | |
* @param {Phaser.Game} game - A reference to the currently running game. | |
* @param {DisplayObject|null} [parent=(game world)] - The parent Group (or other {@link DisplayObject}) that this group will be added to. | |
* If undefined/unspecified the Group will be added to the {@link Phaser.Game#world Game World}; if null the Group will not be added to any parent. | |
* @param {string} [name='group'] - A name for this group. Not used internally but useful for debugging. | |
* @param {boolean} [addToStage=false] - If true this group will be added directly to the Game.Stage instead of Game.World. | |
* @param {boolean} [enableBody=false] - If true all Sprites created with {@link #create} or {@link #createMulitple} will have a physics body created on them. Change the body type with {@link #physicsBodyType}. | |
* @param {integer} [physicsBodyType=0] - The physics body type to use when physics bodies are automatically added. See {@link #physicsBodyType} for values. | |
*/ | |
Phaser.Group = function (game, parent, name, addToStage, enableBody, physicsBodyType) { | |
if (addToStage === undefined) { addToStage = false; } | |
if (enableBody === undefined) { enableBody = false; } | |
if (physicsBodyType === undefined) { physicsBodyType = Phaser.Physics.ARCADE; } | |
/** | |
* A reference to the currently running Game. | |
* @property {Phaser.Game} game | |
* @protected | |
*/ | |
this.game = game; | |
if (parent === undefined) | |
{ | |
parent = game.world; | |
} | |
/** | |
* A name for this group. Not used internally but useful for debugging. | |
* @property {string} name | |
*/ | |
this.name = name || 'group'; | |
/** | |
* The z-depth value of this object within its parent container/Group - the World is a Group as well. | |
* This value must be unique for each child in a Group. | |
* @property {integer} z | |
* @readOnly | |
*/ | |
this.z = 0; | |
PIXI.DisplayObjectContainer.call(this); | |
if (addToStage) | |
{ | |
this.game.stage.addChild(this); | |
this.z = this.game.stage.children.length; | |
} | |
else | |
{ | |
if (parent) | |
{ | |
parent.addChild(this); | |
this.z = parent.children.length; | |
} | |
} | |
/** | |
* Internal Phaser Type value. | |
* @property {integer} type | |
* @protected | |
*/ | |
this.type = Phaser.GROUP; | |
/** | |
* @property {number} physicsType - The const physics body type of this object. | |
* @readonly | |
*/ | |
this.physicsType = Phaser.GROUP; | |
/** | |
* The alive property is useful for Groups that are children of other Groups and need to be included/excluded in checks like forEachAlive. | |
* @property {boolean} alive | |
* @default | |
*/ | |
this.alive = true; | |
/** | |
* If exists is true the group is updated, otherwise it is skipped. | |
* @property {boolean} exists | |
* @default | |
*/ | |
this.exists = true; | |
/** | |
* A group with `ignoreDestroy` set to `true` ignores all calls to its `destroy` method. | |
* @property {boolean} ignoreDestroy | |
* @default | |
*/ | |
this.ignoreDestroy = false; | |
/** | |
* A Group is that has `pendingDestroy` set to `true` is flagged to have its destroy method | |
* called on the next logic update. | |
* You can set it directly to flag the Group to be destroyed on its next update. | |
* | |
* This is extremely useful if you wish to destroy a Group from within one of its own callbacks | |
* or a callback of one of its children. | |
* | |
* @property {boolean} pendingDestroy | |
*/ | |
this.pendingDestroy = false; | |
/** | |
* The type of objects that will be created when using {@link #create} or {@link #createMultiple}. | |
* | |
* Any object may be used but it should extend either Sprite or Image and accept the same constructor arguments: | |
* when a new object is created it is passed the following parameters to its constructor: `(game, x, y, key, frame)`. | |
* | |
* @property {object} classType | |
* @default {@link Phaser.Sprite} | |
*/ | |
this.classType = Phaser.Sprite; | |
/** | |
* The current display object that the group cursor is pointing to, if any. (Can be set manually.) | |
* | |
* The cursor is a way to iterate through the children in a Group using {@link #next} and {@link #previous}. | |
* @property {?DisplayObject} cursor | |
*/ | |
this.cursor = null; | |
/** | |
* A Group with `inputEnableChildren` set to `true` will automatically call `inputEnabled = true` | |
* on any children _added_ to, or _created by_, this Group. | |
* | |
* If there are children already in the Group at the time you set this property, they are not changed. | |
* | |
* @property {boolean} inputEnableChildren | |
* @default | |
*/ | |
this.inputEnableChildren = false; | |
/** | |
* This Signal is dispatched whenever a child of this Group emits an onInputDown signal as a result | |
* of having been interacted with by a Pointer. You can bind functions to this Signal instead of to | |
* every child Sprite. | |
* | |
* This Signal is sent 2 arguments: A reference to the Sprite that triggered the signal, and | |
* a reference to the Pointer that caused it. | |
* | |
* @property {Phaser.Signal} onChildInputDown | |
*/ | |
this.onChildInputDown = new Phaser.Signal(); | |
/** | |
* This Signal is dispatched whenever a child of this Group emits an onInputUp signal as a result | |
* of having been interacted with by a Pointer. You can bind functions to this Signal instead of to | |
* every child Sprite. | |
* | |
* This Signal is sent 3 arguments: A reference to the Sprite that triggered the signal, | |
* a reference to the Pointer that caused it, and a boolean value `isOver` that tells you if the Pointer | |
* is still over the Sprite or not. | |
* | |
* @property {Phaser.Signal} onChildInputUp | |
*/ | |
this.onChildInputUp = new Phaser.Signal(); | |
/** | |
* This Signal is dispatched whenever a child of this Group emits an onInputOver signal as a result | |
* of having been interacted with by a Pointer. You can bind functions to this Signal instead of to | |
* every child Sprite. | |
* | |
* This Signal is sent 2 arguments: A reference to the Sprite that triggered the signal, and | |
* a reference to the Pointer that caused it. | |
* | |
* @property {Phaser.Signal} onChildInputOver | |
*/ | |
this.onChildInputOver = new Phaser.Signal(); | |
/** | |
* This Signal is dispatched whenever a child of this Group emits an onInputOut signal as a result | |
* of having been interacted with by a Pointer. You can bind functions to this Signal instead of to | |
* every child Sprite. | |
* | |
* This Signal is sent 2 arguments: A reference to the Sprite that triggered the signal, and | |
* a reference to the Pointer that caused it. | |
* | |
* @property {Phaser.Signal} onChildInputOut | |
*/ | |
this.onChildInputOut = new Phaser.Signal(); | |
/** | |
* If true all Sprites created by, or added to this group, will have a physics body enabled on them. | |
* | |
* If there are children already in the Group at the time you set this property, they are not changed. | |
* | |
* The default body type is controlled with {@link #physicsBodyType}. | |
* @property {boolean} enableBody | |
*/ | |
this.enableBody = enableBody; | |
/** | |
* If true when a physics body is created (via {@link #enableBody}) it will create a physics debug object as well. | |
* | |
* This only works for P2 bodies. | |
* @property {boolean} enableBodyDebug | |
* @default | |
*/ | |
this.enableBodyDebug = false; | |
/** | |
* If {@link #enableBody} is true this is the type of physics body that is created on new Sprites. | |
* | |
* The valid values are {@link Phaser.Physics.ARCADE}, {@link Phaser.Physics.P2JS}, {@link Phaser.Physics.NINJA}, etc. | |
* @property {integer} physicsBodyType | |
*/ | |
this.physicsBodyType = physicsBodyType; | |
/** | |
* If this Group contains Arcade Physics Sprites you can set a custom sort direction via this property. | |
* | |
* It should be set to one of the Phaser.Physics.Arcade sort direction constants: | |
* | |
* Phaser.Physics.Arcade.SORT_NONE | |
* Phaser.Physics.Arcade.LEFT_RIGHT | |
* Phaser.Physics.Arcade.RIGHT_LEFT | |
* Phaser.Physics.Arcade.TOP_BOTTOM | |
* Phaser.Physics.Arcade.BOTTOM_TOP | |
* | |
* If set to `null` the Group will use whatever Phaser.Physics.Arcade.sortDirection is set to. This is the default behavior. | |
* | |
* @property {integer} physicsSortDirection | |
* @default | |
*/ | |
this.physicsSortDirection = null; | |
/** | |
* This signal is dispatched when the group is destroyed. | |
* @property {Phaser.Signal} onDestroy | |
*/ | |
this.onDestroy = new Phaser.Signal(); | |
/** | |
* @property {integer} cursorIndex - The current index of the Group cursor. Advance it with Group.next. | |
* @readOnly | |
*/ | |
this.cursorIndex = 0; | |
/** | |
* A Group that is fixed to the camera uses its x/y coordinates as offsets from the top left of the camera. These are stored in Group.cameraOffset. | |
* | |
* Note that the cameraOffset values are in addition to any parent in the display list. | |
* So if this Group was in a Group that has x: 200, then this will be added to the cameraOffset.x | |
* | |
* @property {boolean} fixedToCamera | |
*/ | |
this.fixedToCamera = false; | |
/** | |
* If this object is {@link #fixedToCamera} then this stores the x/y position offset relative to the top-left of the camera view. | |
* If the parent of this Group is also `fixedToCamera` then the offset here is in addition to that and should typically be disabled. | |
* @property {Phaser.Point} cameraOffset | |
*/ | |
this.cameraOffset = new Phaser.Point(); | |
/** | |
* The hash array is an array belonging to this Group into which you can add any of its children via Group.addToHash and Group.removeFromHash. | |
* | |
* Only children of this Group can be added to and removed from the hash. | |
* | |
* This hash is used automatically by Phaser Arcade Physics in order to perform non z-index based destructive sorting. | |
* However if you don't use Arcade Physics, or this isn't a physics enabled Group, then you can use the hash to perform your own | |
* sorting and filtering of Group children without touching their z-index (and therefore display draw order) | |
* | |
* @property {array} hash | |
*/ | |
this.hash = []; | |
/** | |
* The property on which children are sorted. | |
* @property {string} _sortProperty | |
* @private | |
*/ | |
this._sortProperty = 'z'; | |
}; | |
Phaser.Group.prototype = Object.create(PIXI.DisplayObjectContainer.prototype); | |
Phaser.Group.prototype.constructor = Phaser.Group; | |
/** | |
* A returnType value, as specified in {@link #iterate} eg. | |
* @constant | |
* @type {integer} | |
*/ | |
Phaser.Group.RETURN_NONE = 0; | |
/** | |
* A returnType value, as specified in {@link #iterate} eg. | |
* @constant | |
* @type {integer} | |
*/ | |
Phaser.Group.RETURN_TOTAL = 1; | |
/** | |
* A returnType value, as specified in {@link #iterate} eg. | |
* @constant | |
* @type {integer} | |
*/ | |
Phaser.Group.RETURN_CHILD = 2; | |
/** | |
* A returnType value, as specified in {@link #iterate} eg. | |
* @constant | |
* @type {integer} | |
*/ | |
Phaser.Group.RETURN_ALL = 3; | |
/** | |
* A sort ordering value, as specified in {@link #sort} eg. | |
* @constant | |
* @type {integer} | |
*/ | |
Phaser.Group.SORT_ASCENDING = -1; | |
/** | |
* A sort ordering value, as specified in {@link #sort} eg. | |
* @constant | |
* @type {integer} | |
*/ | |
Phaser.Group.SORT_DESCENDING = 1; | |
/** | |
* Adds an existing object as the top child in this group. | |
* | |
* The child is automatically added to the top of the group, and is displayed above every previous child. | |
* | |
* Or if the _optional_ index is specified, the child is added at the location specified by the index value, | |
* this allows you to control child ordering. | |
* | |
* If the child was already in this Group, it is simply returned, and nothing else happens to it. | |
* | |
* If `Group.enableBody` is set, then a physics body will be created on the object, so long as one does not already exist. | |
* | |
* If `Group.inputEnableChildren` is set, then an Input Handler will be created on the object, so long as one does not already exist. | |
* | |
* Use {@link #addAt} to control where a child is added. Use {@link #create} to create and add a new child. | |
* | |
* @method Phaser.Group#add | |
* @param {DisplayObject} child - The display object to add as a child. | |
* @param {boolean} [silent=false] - If true the child will not dispatch the `onAddedToGroup` event. | |
* @param {integer} [index] - The index within the group to insert the child to. Where 0 is the bottom of the Group. | |
* @return {DisplayObject} The child that was added to the group. | |
*/ | |
Phaser.Group.prototype.add = function (child, silent, index) { | |
if (silent === undefined) { silent = false; } | |
if (child.parent === this) | |
{ | |
return child; | |
} | |
if (child.body && child.parent && child.parent.hash) | |
{ | |
child.parent.removeFromHash(child); | |
} | |
if (index === undefined) | |
{ | |
child.z = this.children.length; | |
this.addChild(child); | |
} | |
else | |
{ | |
this.addChildAt(child, index); | |
this.updateZ(); | |
} | |
if (this.enableBody && child.hasOwnProperty('body') && child.body === null) | |
{ | |
this.game.physics.enable(child, this.physicsBodyType); | |
} | |
else if (child.body) | |
{ | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment