// Version 0.2 - Copyright 2013 - Jim Riecken <jimr@jimr.ca> // // Released under the MIT License - https://github.com/jriecken/sat-js // // A simple library for determining intersections of circles and // polygons using the Separating Axis Theorem. /** @preserve SAT.js - Version 0.2 - Copyright 2013 - Jim Riecken <jimr@jimr.ca> - released under the MIT License. https://github.com/jriecken/sat-js */ /*global define: false, module: false*/ /*jshint shadow:true, sub:true, forin:true, noarg:true, noempty:true, eqeqeq:true, bitwise:true, strict:true, undef:true, curly:true, browser:true */ // Create a UMD wrapper for SAT. Works in: // // - Plain browser via global SAT variable // - AMD loader (like require.js) // - Node.js // // The quoted properties all over the place are used so that the Closure Compiler // does not mangle the exposed API in advanced mode. /** * @param {*} root - The global scope * @param {Function} factory - Factory that creates SAT module */ (function (root, factory) { "use strict"; if (typeof define === 'function' && define['amd']) { define(factory); } else if (typeof exports === 'object') { module['exports'] = factory(); } else { root['SAT'] = factory(); } }(this, function () { "use strict"; var SAT = {}; // // ## Vector // // Represents a vector in two dimensions with `x` and `y` properties. // Create a new Vector, optionally passing in the `x` and `y` coordinates. If // a coordinate is not specified, it will be set to `0` /** * @param {?number=} x The x position. * @param {?number=} y The y position. * @constructor */ function Vector(x, y) { this['x'] = x || 0; this['y'] = y || 0; } SAT['Vector'] = Vector; // Alias `Vector` as `V` SAT['V'] = Vector; // Copy the values of another Vector into this one. /** * @param {Vector} other The other Vector. * @return {Vector} This for chaining. */ Vector.prototype['copy'] = Vector.prototype.copy = function(other) { this['x'] = other['x']; this['y'] = other['y']; return this; }; // Change this vector to be perpendicular to what it was before. (Effectively // roatates it 90 degrees in a clockwise direction) /** * @return {Vector} This for chaining. */ Vector.prototype['perp'] = Vector.prototype.perp = function() { var x = this['x']; this['x'] = this['y']; this['y'] = -x; return this; }; // Rotate this vector (counter-clockwise) by the specified angle (in radians). /** * @param {number} angle The angle to rotate (in radians) * @return {Vector} This for chaining. */ Vector.prototype['rotate'] = Vector.prototype.rotate = function (angle) { var x = this['x']; var y = this['y']; this['x'] = x * Math.cos(angle) - y * Math.sin(angle); this['y'] = x * Math.sin(angle) + y * Math.cos(angle); return this; }; // Rotate this vector (counter-clockwise) by the specified angle (in radians) which has already been calculated into sin and cos. /** * @param {number} sin - The Math.sin(angle) * @param {number} cos - The Math.cos(angle) * @return {Vector} This for chaining. */ Vector.prototype['rotatePrecalc'] = Vector.prototype.rotatePrecalc = function (sin, cos) { var x = this['x']; var y = this['y']; this['x'] = x * cos - y * sin; this['y'] = x * sin + y * cos; return this; }; // Reverse this vector. /** * @return {Vector} This for chaining. */ Vector.prototype['reverse'] = Vector.prototype.reverse = function() { this['x'] = -this['x']; this['y'] = -this['y']; return this; }; // Normalize this vector. (make it have length of `1`) /** * @return {Vector} This for chaining. */ Vector.prototype['normalize'] = Vector.prototype.normalize = function() { var d = this.len(); if(d > 0) { this['x'] = this['x'] / d; this['y'] = this['y'] / d; } return this; }; // Add another vector to this one. /** * @param {Vector} other The other Vector. * @return {Vector} This for chaining. */ Vector.prototype['add'] = Vector.prototype.add = function(other) { this['x'] += other['x']; this['y'] += other['y']; return this; }; // Subtract another vector from this one. /** * @param {Vector} other The other Vector. * @return {Vector} This for chaiing. */ Vector.prototype['sub'] = Vector.prototype.sub = function(other) { this['x'] -= other['x']; this['y'] -= other['y']; return this; }; // Scale this vector. An independant scaling factor can be provided // for each axis, or a single scaling factor that will scale both `x` and `y`. /** * @param {number} x The scaling factor in the x direction. * @param {?number=} y The scaling factor in the y direction. If this * is not specified, the x scaling factor will be used. * @return {Vector} This for chaining. */ Vector.prototype['scale'] = Vector.prototype.scale = function(x,y) { this['x'] *= x; this['y'] *= y || x; return this; }; // Project this vector on to another vector. /** * @param {Vector} other The vector to project onto. * @return {Vector} This for chaining. */ Vector.prototype['project'] = Vector.prototype.project = function(other) { var amt = this.dot(other) / other.len2(); this['x'] = amt * other['x']; this['y'] = amt * other['y']; return this; }; // Project this vector onto a vector of unit length. This is slightly more efficient // than `project` when dealing with unit vectors. /** * @param {Vector} other The unit vector to project onto. * @return {Vector} This for chaining. */ Vector.prototype['projectN'] = Vector.prototype.projectN = function(other) { var amt = this.dot(other); this['x'] = amt * other['x']; this['y'] = amt * other['y']; return this; }; // Reflect this vector on an arbitrary axis. /** * @param {Vector} axis The vector representing the axis. * @return {Vector} This for chaining. */ Vector.prototype['reflect'] = Vector.prototype.reflect = function(axis) { var x = this['x']; var y = this['y']; this.project(axis).scale(2); this['x'] -= x; this['y'] -= y; return this; }; // Reflect this vector on an arbitrary axis (represented by a unit vector). This is // slightly more efficient than `reflect` when dealing with an axis that is a unit vector. /** * @param {Vector} axis The unit vector representing the axis. * @return {Vector} This for chaining. */ Vector.prototype['reflectN'] = Vector.prototype.reflectN = function(axis) { var x = this['x']; var y = this['y']; this.projectN(axis).scale(2); this['x'] -= x; this['y'] -= y; return this; }; // Get the dot product of this vector and another. /** * @param {Vector} other The vector to dot this one against. * @return {number} The dot product. */ Vector.prototype['dot'] = Vector.prototype.dot = function(other) { return this['x'] * other['x'] + this['y'] * other['y']; }; // Get the squared length of this vector. /** * @return {number} The length^2 of this vector. */ Vector.prototype['len2'] = Vector.prototype.len2 = function() { return this.dot(this); }; // Get the length of this vector. /** * @return {number} The length of this vector. */ Vector.prototype['len'] = Vector.prototype.len = function() { return Math.sqrt(this.len2()); }; // ## Circle // // Represents a circle with a position and a radius. // Create a new circle, optionally passing in a position and/or radius. If no position // is given, the circle will be at `(0,0)`. If no radius is provided, the circle will // have a radius of `0`. /** * @param {Vector=} pos A vector representing the position of the center of the circle * @param {?number=} r The radius of the circle * @constructor */ function Circle(pos, r) { this['pos'] = pos || new Vector(); this['r'] = r || 0; } SAT['Circle'] = Circle; // ## Polygon // // Represents a *convex* polygon with any number of points (specified in counter-clockwise order) // // The edges/normals of the polygon will be calculated on creation and stored in the // `edges` and `normals` properties. If you change the polygon's points, you will need // to call `recalc` to recalculate the edges/normals. // Create a new polygon, passing in a position vector, and an array of points (represented // by vectors relative to the position vector). If no position is passed in, the position // of the polygon will be `(0,0)`. /** * @param {Vector=} pos A vector representing the origin of the polygon. (all other * points are relative to this one) * @param {Array.<Vector>=} points An array of vectors representing the points in the polygon, * in counter-clockwise order. * @constructor */ function Polygon(pos, points) { this['pos'] = pos || new Vector(); this['points'] = points || []; this.recalc(); } SAT['Polygon'] = Polygon; // Recalculates the edges and normals of the polygon. This **must** be called // if the `points` array is modified at all and the edges or normals are to be // accessed. /** * @return {Polygon} This for chaining. */ Polygon.prototype['recalc'] = Polygon.prototype.recalc = function() { // The edges here are the direction of the `n`th edge of the polygon, relative to // the `n`th point. If you want to draw a given edge from the edge value, you must // first translate to the position of the starting point. this['edges'] = []; // The normals here are the direction of the normal for the `n`th edge of the polygon, relative // to the position of the `n`th point. If you want to draw an edge normal, you must first // translate to the position of the starting point. this['normals'] = []; var points = this['points']; var len = points.length; for (var i = 0; i < len; i++) { var p1 = points[i]; var p2 = i < len - 1 ? points[i + 1] : points[0]; var e = new Vector().copy(p2).sub(p1); var n = new Vector().copy(e).perp().normalize(); this['edges'].push(e); this['normals'].push(n); } return this; }; // Rotates this polygon counter-clockwise around the origin of *its local coordinate system* (i.e. `pos`). // // Note: You do **not** need to call `recalc` after rotation. /** * @param {number} angle The angle to rotate (in radians) * @return {Polygon} This for chaining. */ Polygon.prototype['rotate'] = Polygon.prototype.rotate = function(angle) { var i; var points = this['points']; var edges = this['edges']; var normals = this['normals']; var len = points.length; // Calc it just the once, rather than 4 times per array element var cos = Math.cos(angle); var sin = Math.sin(angle); for (i = 0; i < len; i++) { points[i].rotatePrecalc(sin, cos); edges[i].rotatePrecalc(sin, cos); normals[i].rotatePrecalc(sin, cos); } return this; }; // Rotates this polygon counter-clockwise around the origin of *its local coordinate system* (i.e. `pos`). // // Note: You do **not** need to call `recalc` after rotation. /** * @param {number} angle The angle to rotate (in radians) * @return {Polygon} This for chaining. */ Polygon.prototype['scale'] = Polygon.prototype.scale = function(x, y) { var i; var points = this['points']; var edges = this['edges']; var normals = this['normals']; var len = points.length; for (i = 0; i < len; i++) { points[i].scale(x,y); edges[i].scale(x,y); normals[i].scale(x,y); } return this; }; // Translates the points of this polygon by a specified amount relative to the origin of *its own coordinate // system* (i.e. `pos`). // // This is most useful to change the "center point" of a polygon. // // Note: You do **not** need to call `recalc` after translation. /** * @param {number} x The horizontal amount to translate. * @param {number} y The vertical amount to translate. * @return {Polygon} This for chaining. */ Polygon.prototype['translate'] = Polygon.prototype.translate = function (x, y) { var i; var points = this['points']; var len = points.length; for (i = 0; i < len; i++) { points[i].x += x; points[i].y += y; } return this; }; // ## Box // // Represents an axis-aligned box, with a width and height. // Create a new box, with the specified position, width, and height. If no position // is given, the position will be `(0,0)`. If no width or height are given, they will // be set to `0`. /** * @param {Vector=} pos A vector representing the top-left of the box. * @param {?number=} w The width of the box. * @param {?number=} h The height of the box. * @constructor */ function Box(pos, w, h) { this['pos'] = pos || new Vector(); this['w'] = w || 0; this['h'] = h || 0; } SAT['Box'] = Box; // Returns a polygon whose edges are the same as this box. /** * @return {Polygon} A new Polygon that represents this box. */ Box.prototype['toPolygon'] = Box.prototype.toPolygon = function() { var pos = this['pos']; var w = this['w']; var h = this['h']; return new Polygon(new Vector(pos['x'], pos['y']), [ new Vector(), new Vector(w, 0), new Vector(w,h), new Vector(0,h) ]); }; // ## Response // // An object representing the result of an intersection. Contains: // - The two objects participating in the intersection // - The vector representing the minimum change necessary to extract the first object // from the second one (as well as a unit vector in that direction and the magnitude // of the overlap) // - Whether the first object is entirely inside the second, and vice versa. /** * @constructor */ function Response() { this['a'] = null; this['b'] = null; this['overlapN'] = new Vector(); this['overlapV'] = new Vector(); this.clear(); } SAT['Response'] = Response; // Set some values of the response back to their defaults. Call this between tests if // you are going to reuse a single Response object for multiple intersection tests (recommented // as it will avoid allcating extra memory) /** * @return {Response} This for chaining */ Response.prototype['clear'] = Response.prototype.clear = function() { this['aInB'] = true; this['bInA'] = true; this['overlap'] = Number.MAX_VALUE; return this; }; // ## Object Pools // A pool of `Vector` objects that are used in calculations to avoid // allocating memory. /** * @type {Array.<Vector>} */ var T_VECTORS = []; for (var i = 0; i < 10; i++) { T_VECTORS.push(new Vector()); } // A pool of arrays of numbers used in calculations to avoid allocating // memory. /** * @type {Array.<Array.<number>>} */ var T_ARRAYS = []; for (var i = 0; i < 5; i++) { T_ARRAYS.push([]); } // ## Helper Functions // Flattens the specified array of points onto a unit vector axis, // resulting in a one dimensional range of the minimum and // maximum value on that axis. /** * @param {Array.<Vector>} points The points to flatten. * @param {Vector} normal The unit vector axis to flatten on. * @param {Array.<number>} result An array. After calling this function, * result[0] will be the minimum value, * result[1] will be the maximum value. */ function flattenPointsOn(points, normal, result) { var min = Number.MAX_VALUE; var max = -Number.MAX_VALUE; var len = points.length; for (var i = 0; i < len; i++ ) { // The magnitude of the projection of the point onto the normal var dot = points[i].dot(normal); if (dot < min) { min = dot; } if (dot > max) { max = dot; } } result[0] = min; result[1] = max; } // Check whether two convex polygons are separated by the specified // axis (must be a unit vector). /** * @param {Vector} aPos The position of the first polygon. * @param {Vector} bPos The position of the second polygon. * @param {Array.<Vector>} aPoints The points in the first polygon. * @param {Array.<Vector>} bPoints The points in the second polygon. * @param {Vector} axis The axis (unit sized) to test against. The points of both polygons * will be projected onto this axis. * @param {Response=} response A Response object (optional) which will be populated * if the axis is not a separating axis. * @return {boolean} true if it is a separating axis, false otherwise. If false, * and a response is passed in, information about how much overlap and * the direction of the overlap will be populated. */ function isSeparatingAxis(aPos, bPos, aPoints, bPoints, axis, response) { var rangeA = T_ARRAYS.pop(); var rangeB = T_ARRAYS.pop(); // The magnitude of the offset between the two polygons var offsetV = T_VECTORS.pop().copy(bPos).sub(aPos); var projectedOffset = offsetV.dot(axis); // Project the polygons onto the axis. flattenPointsOn(aPoints, axis, rangeA); flattenPointsOn(bPoints, axis, rangeB); // Move B's range to its position relative to A. rangeB[0] += projectedOffset; rangeB[1] += projectedOffset; // Check if there is a gap. If there is, this is a separating axis and we can stop if (rangeA[0] > rangeB[1] || rangeB[0] > rangeA[1]) { T_VECTORS.push(offsetV); T_ARRAYS.push(rangeA); T_ARRAYS.push(rangeB); return true; } // This is not a separating axis. If we're calculating a response, calculate the overlap. if (response) { var overlap = 0; // A starts further left than B if (rangeA[0] < rangeB[0]) { response['aInB'] = false; // A ends before B does. We have to pull A out of B if (rangeA[1] < rangeB[1]) { overlap = rangeA[1] - rangeB[0]; response['bInA'] = false; // B is fully inside A. Pick the shortest way out. } else { var option1 = rangeA[1] - rangeB[0]; var option2 = rangeB[1] - rangeA[0]; overlap = option1 < option2 ? option1 : -option2; } // B starts further left than A } else { response['bInA'] = false; // B ends before A ends. We have to push A out of B if (rangeA[1] > rangeB[1]) { overlap = rangeA[0] - rangeB[1]; response['aInB'] = false; // A is fully inside B. Pick the shortest way out. } else { var option1 = rangeA[1] - rangeB[0]; var option2 = rangeB[1] - rangeA[0]; overlap = option1 < option2 ? option1 : -option2; } } // If this is the smallest amount of overlap we've seen so far, set it as the minimum overlap. var absOverlap = Math.abs(overlap); if (absOverlap < response['overlap']) { response['overlap'] = absOverlap; response['overlapN'].copy(axis); if (overlap < 0) { response['overlapN'].reverse(); } } } T_VECTORS.push(offsetV); T_ARRAYS.push(rangeA); T_ARRAYS.push(rangeB); return false; } // Calculates which Vornoi region a point is on a line segment. // It is assumed that both the line and the point are relative to `(0,0)` // // | (0) | // (-1) [S]--------------[E] (1) // | (0) | /** * @param {Vector} line The line segment. * @param {Vector} point The point. * @return {number} LEFT_VORNOI_REGION (-1) if it is the left region, * MIDDLE_VORNOI_REGION (0) if it is the middle region, * RIGHT_VORNOI_REGION (1) if it is the right region. */ function vornoiRegion(line, point) { var len2 = line.len2(); var dp = point.dot(line); // If the point is beyond the start of the line, it is in the // left vornoi region. if (dp < 0) { return LEFT_VORNOI_REGION; } // If the point is beyond the end of the line, it is in the // right vornoi region. else if (dp > len2) { return RIGHT_VORNOI_REGION; } // Otherwise, it's in the middle one. else { return MIDDLE_VORNOI_REGION; } } // Constants for Vornoi regions /** * @const */ var LEFT_VORNOI_REGION = -1; /** * @const */ var MIDDLE_VORNOI_REGION = 0; /** * @const */ var RIGHT_VORNOI_REGION = 1; // ## Collision Tests // Check if two circles collide. /** * @param {Circle} a The first circle. * @param {Circle} b The second circle. * @param {Response=} response Response object (optional) that will be populated if * the circles intersect. * @return {boolean} true if the circles intersect, false if they don't. */ function testCircleCircle(a, b, response) { // Check if the distance between the centers of the two // circles is greater than their combined radius. var differenceV = T_VECTORS.pop().copy(b['pos']).sub(a['pos']); var totalRadius = a['r'] + b['r']; var totalRadiusSq = totalRadius * totalRadius; var distanceSq = differenceV.len2(); // If the distance is bigger than the combined radius, they don't intersect. if (distanceSq > totalRadiusSq) { T_VECTORS.push(differenceV); return false; } // They intersect. If we're calculating a response, calculate the overlap. if (response) { var dist = Math.sqrt(distanceSq); response['a'] = a; response['b'] = b; response['overlap'] = totalRadius - dist; response['overlapN'].copy(differenceV.normalize()); response['overlapV'].copy(differenceV).scale(response['overlap']); response['aInB']= a['r'] <= b['r'] && dist <= b['r'] - a['r']; response['bInA'] = b['r'] <= a['r'] && dist <= a['r'] - b['r']; } T_VECTORS.push(differenceV); return true; } SAT['testCircleCircle'] = testCircleCircle; // Check if a polygon and a circle collide. /** * @param {Polygon} polygon The polygon. * @param {Circle} circle The circle. * @param {Response=} response Response object (optional) that will be populated if * they interset. * @return {boolean} true if they intersect, false if they don't. */ function testPolygonCircle(polygon, circle, response) { // Get the position of the circle relative to the polygon. var circlePos = T_VECTORS.pop().copy(circle['pos']).sub(polygon['pos']); var radius = circle['r']; var radius2 = radius * radius; var points = polygon['points']; var len = points.length; var edge = T_VECTORS.pop(); var point = T_VECTORS.pop(); // For each edge in the polygon: for (var i = 0; i < len; i++) { var next = i === len - 1 ? 0 : i + 1; var prev = i === 0 ? len - 1 : i - 1; var overlap = 0; var overlapN = null; // Get the edge. edge.copy(polygon['edges'][i]); // Calculate the center of the circle relative to the starting point of the edge. point.copy(circlePos).sub(points[i]); // If the distance between the center of the circle and the point // is bigger than the radius, the polygon is definitely not fully in // the circle. if (response && point.len2() > radius2) { response['aInB'] = false; } // Calculate which Vornoi region the center of the circle is in. var region = vornoiRegion(edge, point); // If it's the left region: if (region === LEFT_VORNOI_REGION) { // We need to make sure we're in the RIGHT_VORNOI_REGION of the previous edge. edge.copy(polygon['edges'][prev]); // Calculate the center of the circle relative the starting point of the previous edge var point2 = T_VECTORS.pop().copy(circlePos).sub(points[prev]); region = vornoiRegion(edge, point2); if (region === RIGHT_VORNOI_REGION) { // It's in the region we want. Check if the circle intersects the point. var dist = point.len(); if (dist > radius) { // No intersection T_VECTORS.push(circlePos); T_VECTORS.push(edge); T_VECTORS.push(point); T_VECTORS.push(point2); return false; } else if (response) { // It intersects, calculate the overlap. response['bInA'] = false; overlapN = point.normalize(); overlap = radius - dist; } } T_VECTORS.push(point2); // If it's the right region: } else if (region === RIGHT_VORNOI_REGION) { // We need to make sure we're in the left region on the next edge edge.copy(polygon['edges'][next]); // Calculate the center of the circle relative to the starting point of the next edge. point.copy(circlePos).sub(points[next]); region = vornoiRegion(edge, point); if (region === LEFT_VORNOI_REGION) { // It's in the region we want. Check if the circle intersects the point. var dist = point.len(); if (dist > radius) { // No intersection T_VECTORS.push(circlePos); T_VECTORS.push(edge); T_VECTORS.push(point); return false; } else if (response) { // It intersects, calculate the overlap. response['bInA'] = false; overlapN = point.normalize(); overlap = radius - dist; } } // Otherwise, it's the middle region: } else { // Need to check if the circle is intersecting the edge, // Change the edge into its "edge normal". var normal = edge.perp().normalize(); // Find the perpendicular distance between the center of the // circle and the edge. var dist = point.dot(normal); var distAbs = Math.abs(dist); // If the circle is on the outside of the edge, there is no intersection. if (dist > 0 && distAbs > radius) { // No intersection T_VECTORS.push(circlePos); T_VECTORS.push(normal); T_VECTORS.push(point); return false; } else if (response) { // It intersects, calculate the overlap. overlapN = normal; overlap = radius - dist; // If the center of the circle is on the outside of the edge, or part of the // circle is on the outside, the circle is not fully inside the polygon. if (dist >= 0 || overlap < 2 * radius) { response['bInA'] = false; } } } // If this is the smallest overlap we've seen, keep it. // (overlapN may be null if the circle was in the wrong Vornoi region). if (overlapN && response && Math.abs(overlap) < Math.abs(response['overlap'])) { response['overlap'] = overlap; response['overlapN'].copy(overlapN); } } // Calculate the final overlap vector - based on the smallest overlap. if (response) { response['a'] = polygon; response['b'] = circle; response['overlapV'].copy(response['overlapN']).scale(response['overlap']); } T_VECTORS.push(circlePos); T_VECTORS.push(edge); T_VECTORS.push(point); return true; } SAT['testPolygonCircle'] = testPolygonCircle; // Check if a circle and a polygon collide. // // **NOTE:** This is slightly less efficient than polygonCircle as it just // runs polygonCircle and reverses everything at the end. /** * @param {Circle} circle The circle. * @param {Polygon} polygon The polygon. * @param {Response=} response Response object (optional) that will be populated if * they interset. * @return {boolean} true if they intersect, false if they don't. */ function testCirclePolygon(circle, polygon, response) { // Test the polygon against the circle. var result = testPolygonCircle(polygon, circle, response); if (result && response) { // Swap A and B in the response. var a = response['a']; var aInB = response['aInB']; response['overlapN'].reverse(); response['overlapV'].reverse(); response['a'] = response['b']; response['b'] = a; response['aInB'] = response['bInA']; response['bInA'] = aInB; } return result; } SAT['testCirclePolygon'] = testCirclePolygon; // Checks whether polygons collide. /** * @param {Polygon} a The first polygon. * @param {Polygon} b The second polygon. * @param {Response=} response Response object (optional) that will be populated if * they interset. * @return {boolean} true if they intersect, false if they don't. */ function testPolygonPolygon(a, b, response) { var aPoints = a['points']; var aLen = aPoints.length; var bPoints = b['points']; var bLen = bPoints.length; // If any of the edge normals of A is a separating axis, no intersection. for (var i = 0; i < aLen; i++) { if (isSeparatingAxis(a['pos'], b['pos'], aPoints, bPoints, a['normals'][i], response)) { return false; } } // If any of the edge normals of B is a separating axis, no intersection. for (var i = 0;i < bLen; i++) { if (isSeparatingAxis(a['pos'], b['pos'], aPoints, bPoints, b['normals'][i], response)) { return false; } } // Since none of the edge normals of A or B are a separating axis, there is an intersection // and we've already calculated the smallest overlap (in isSeparatingAxis). Calculate the // final overlap vector. if (response) { response['a'] = a; response['b'] = b; response['overlapV'].copy(response['overlapN']).scale(response['overlap']); } return true; } SAT['testPolygonPolygon'] = testPolygonPolygon; return SAT; }));