UNPKG

phaser

Version:

A fast, free and fun HTML5 Game Framework for Desktop and Mobile web browsers.

271 lines (220 loc) 9.03 kB
/** * The `Matter.SAT` module contains methods for detecting collisions using the Separating Axis Theorem. * * @class SAT */ // TODO: true circles and curves var SAT = {}; module.exports = SAT; var Vertices = require('../geometry/Vertices'); var Vector = require('../geometry/Vector'); (function() { /** * Detect collision between two bodies using the Separating Axis Theorem. * @method collides * @param {body} bodyA * @param {body} bodyB * @param {collision} previousCollision * @return {collision} collision */ SAT.collides = function(bodyA, bodyB, previousCollision) { var overlapAB, overlapBA, minOverlap, collision, canReusePrevCol = false; if (previousCollision) { // estimate total motion var parentA = bodyA.parent, parentB = bodyB.parent, motion = parentA.speed * parentA.speed + parentA.angularSpeed * parentA.angularSpeed + parentB.speed * parentB.speed + parentB.angularSpeed * parentB.angularSpeed; // we may be able to (partially) reuse collision result // but only safe if collision was resting canReusePrevCol = previousCollision && previousCollision.collided && motion < 0.2; // reuse collision object collision = previousCollision; } else { collision = { collided: false, bodyA: bodyA, bodyB: bodyB }; } if (previousCollision && canReusePrevCol) { // if we can reuse the collision result // we only need to test the previously found axis var axisBodyA = collision.axisBody, axisBodyB = axisBodyA === bodyA ? bodyB : bodyA, axes = [axisBodyA.axes[previousCollision.axisNumber]]; minOverlap = _overlapAxes(axisBodyA.vertices, axisBodyB.vertices, axes); collision.reused = true; if (minOverlap.overlap <= 0) { collision.collided = false; return collision; } } else { // if we can't reuse a result, perform a full SAT test overlapAB = _overlapAxes(bodyA.vertices, bodyB.vertices, bodyA.axes); if (overlapAB.overlap <= 0) { collision.collided = false; return collision; } overlapBA = _overlapAxes(bodyB.vertices, bodyA.vertices, bodyB.axes); if (overlapBA.overlap <= 0) { collision.collided = false; return collision; } if (overlapAB.overlap < overlapBA.overlap) { minOverlap = overlapAB; collision.axisBody = bodyA; } else { minOverlap = overlapBA; collision.axisBody = bodyB; } // important for reuse later collision.axisNumber = minOverlap.axisNumber; } collision.bodyA = bodyA.id < bodyB.id ? bodyA : bodyB; collision.bodyB = bodyA.id < bodyB.id ? bodyB : bodyA; collision.collided = true; collision.depth = minOverlap.overlap; collision.parentA = collision.bodyA.parent; collision.parentB = collision.bodyB.parent; bodyA = collision.bodyA; bodyB = collision.bodyB; // ensure normal is facing away from bodyA if (Vector.dot(minOverlap.axis, Vector.sub(bodyB.position, bodyA.position)) < 0) { collision.normal = { x: minOverlap.axis.x, y: minOverlap.axis.y }; } else { collision.normal = { x: -minOverlap.axis.x, y: -minOverlap.axis.y }; } collision.tangent = Vector.perp(collision.normal); collision.penetration = collision.penetration || {}; collision.penetration.x = collision.normal.x * collision.depth; collision.penetration.y = collision.normal.y * collision.depth; // find support points, there is always either exactly one or two var verticesB = _findSupports(bodyA, bodyB, collision.normal), supports = []; // find the supports from bodyB that are inside bodyA if (Vertices.contains(bodyA.vertices, verticesB[0])) supports.push(verticesB[0]); if (Vertices.contains(bodyA.vertices, verticesB[1])) supports.push(verticesB[1]); // find the supports from bodyA that are inside bodyB if (supports.length < 2) { var verticesA = _findSupports(bodyB, bodyA, Vector.neg(collision.normal)); if (Vertices.contains(bodyB.vertices, verticesA[0])) supports.push(verticesA[0]); if (supports.length < 2 && Vertices.contains(bodyB.vertices, verticesA[1])) supports.push(verticesA[1]); } // account for the edge case of overlapping but no vertex containment if (supports.length < 1) supports = [verticesB[0]]; collision.supports = supports; return collision; }; /** * Find the overlap between two sets of vertices. * @method _overlapAxes * @private * @param {} verticesA * @param {} verticesB * @param {} axes * @return result */ var _overlapAxes = function(verticesA, verticesB, axes) { var projectionA = Vector._temp[0], projectionB = Vector._temp[1], result = { overlap: Number.MAX_VALUE }, overlap, axis; for (var i = 0; i < axes.length; i++) { axis = axes[i]; _projectToAxis(projectionA, verticesA, axis); _projectToAxis(projectionB, verticesB, axis); overlap = Math.min(projectionA.max - projectionB.min, projectionB.max - projectionA.min); if (overlap <= 0) { result.overlap = overlap; return result; } if (overlap < result.overlap) { result.overlap = overlap; result.axis = axis; result.axisNumber = i; } } return result; }; /** * Projects vertices on an axis and returns an interval. * @method _projectToAxis * @private * @param {} projection * @param {} vertices * @param {} axis */ var _projectToAxis = function(projection, vertices, axis) { var min = Vector.dot(vertices[0], axis), max = min; for (var i = 1; i < vertices.length; i += 1) { var dot = Vector.dot(vertices[i], axis); if (dot > max) { max = dot; } else if (dot < min) { min = dot; } } projection.min = min; projection.max = max; }; /** * Finds supporting vertices given two bodies along a given direction using hill-climbing. * @method _findSupports * @private * @param {} bodyA * @param {} bodyB * @param {} normal * @return [vector] */ var _findSupports = function(bodyA, bodyB, normal) { var nearestDistance = Number.MAX_VALUE, vertexToBody = Vector._temp[0], vertices = bodyB.vertices, bodyAPosition = bodyA.position, distance, vertex, vertexA, vertexB; // find closest vertex on bodyB for (var i = 0; i < vertices.length; i++) { vertex = vertices[i]; vertexToBody.x = vertex.x - bodyAPosition.x; vertexToBody.y = vertex.y - bodyAPosition.y; distance = -Vector.dot(normal, vertexToBody); if (distance < nearestDistance) { nearestDistance = distance; vertexA = vertex; } } // find next closest vertex using the two connected to it var prevIndex = vertexA.index - 1 >= 0 ? vertexA.index - 1 : vertices.length - 1; vertex = vertices[prevIndex]; vertexToBody.x = vertex.x - bodyAPosition.x; vertexToBody.y = vertex.y - bodyAPosition.y; nearestDistance = -Vector.dot(normal, vertexToBody); vertexB = vertex; var nextIndex = (vertexA.index + 1) % vertices.length; vertex = vertices[nextIndex]; vertexToBody.x = vertex.x - bodyAPosition.x; vertexToBody.y = vertex.y - bodyAPosition.y; distance = -Vector.dot(normal, vertexToBody); if (distance < nearestDistance) { vertexB = vertex; } return [vertexA, vertexB]; }; })();