From b9e7d9dd8ba7bfb80efab84d837d0571fa2d3c28 Mon Sep 17 00:00:00 2001 From: liabru Date: Sun, 12 Dec 2021 10:45:50 +0000 Subject: [PATCH] replaced Matter.SAT with Matter.Collision --- src/collision/Collision.js | 378 ++++++++++++++++++++++++++++++++++++- src/collision/Query.js | 8 +- src/collision/SAT.js | 279 ++------------------------- src/module/main.js | 1 + test/ExampleWorker.js | 21 ++- 5 files changed, 408 insertions(+), 279 deletions(-) diff --git a/src/collision/Collision.js b/src/collision/Collision.js index ef53a7d..40b6cdf 100644 --- a/src/collision/Collision.js +++ b/src/collision/Collision.js @@ -1,5 +1,9 @@ /** -* The `Matter.Collision` module contains methods for managing collision records. +* The `Matter.Collision` module contains methods for detecting collisions between a given pair of bodies. +* +* For efficient detection between a list of bodies, see `Matter.Detector` and `Matter.Query`. +* +* See `Matter.Engine` for collision events. * * @class Collision */ @@ -8,13 +12,27 @@ var Collision = {}; module.exports = Collision; +var Vertices = require('../geometry/Vertices'); +var Pair = require('./Pair'); + (function() { + var _supports = []; + + var _overlapAB = { + overlap: 0, + axis: null + }; + + var _overlapBA = { + overlap: 0, + axis: null + }; /** * Creates a new collision record. * @method create - * @param {body} bodyA - * @param {body} bodyB + * @param {body} bodyA The first body part represented by the collision record + * @param {body} bodyB The second body part represented by the collision record * @return {collision} A new collision record */ Collision.create = function(bodyA, bodyB) { @@ -33,4 +51,358 @@ module.exports = Collision; }; }; + /** + * Detect collision between two bodies. + * @method collides + * @param {body} bodyA + * @param {body} bodyB + * @param {pairs} [pairs] Optionally reuse collision records from existing pairs. + * @return {collision|null} A collision record if detected, otherwise null + */ + Collision.collides = function(bodyA, bodyB, pairs) { + Collision._overlapAxes(_overlapAB, bodyA.vertices, bodyB.vertices, bodyA.axes); + + if (_overlapAB.overlap <= 0) { + return null; + } + + Collision._overlapAxes(_overlapBA, bodyB.vertices, bodyA.vertices, bodyB.axes); + + if (_overlapBA.overlap <= 0) { + return null; + } + + // reuse collision records for gc efficiency + var pair = pairs && pairs.table[Pair.id(bodyA, bodyB)], + collision; + + if (!pair) { + collision = Collision.create(bodyA, bodyB); + collision.collided = true; + collision.bodyA = bodyA.id < bodyB.id ? bodyA : bodyB; + collision.bodyB = bodyA.id < bodyB.id ? bodyB : bodyA; + collision.parentA = collision.bodyA.parent; + collision.parentB = collision.bodyB.parent; + } else { + collision = pair.collision; + } + + bodyA = collision.bodyA; + bodyB = collision.bodyB; + + var minOverlap; + + if (_overlapAB.overlap < _overlapBA.overlap) { + minOverlap = _overlapAB; + } else { + minOverlap = _overlapBA; + } + + var normal = collision.normal, + supports = collision.supports, + minAxis = minOverlap.axis, + minAxisX = minAxis.x, + minAxisY = minAxis.y; + + // ensure normal is facing away from bodyA + if (minAxisX * (bodyB.position.x - bodyA.position.x) + minAxisY * (bodyB.position.y - bodyA.position.y) < 0) { + normal.x = minAxisX; + normal.y = minAxisY; + } else { + normal.x = -minAxisX; + normal.y = -minAxisY; + } + + collision.tangent.x = -normal.y; + collision.tangent.y = normal.x; + + collision.depth = minOverlap.overlap; + + collision.penetration.x = normal.x * collision.depth; + collision.penetration.y = normal.y * collision.depth; + + // find support points, there is always either exactly one or two + var supportsB = Collision._findSupports(bodyA, bodyB, normal, 1), + supportCount = 0; + + // find the supports from bodyB that are inside bodyA + if (Vertices.contains(bodyA.vertices, supportsB[0])) { + supports[supportCount++] = supportsB[0]; + } + + if (Vertices.contains(bodyA.vertices, supportsB[1])) { + supports[supportCount++] = supportsB[1]; + } + + // find the supports from bodyA that are inside bodyB + if (supportCount < 2) { + var supportsA = Collision._findSupports(bodyB, bodyA, normal, -1); + + if (Vertices.contains(bodyB.vertices, supportsA[0])) { + supports[supportCount++] = supportsA[0]; + } + + if (supportCount < 2 && Vertices.contains(bodyB.vertices, supportsA[1])) { + supports[supportCount++] = supportsA[1]; + } + } + + // account for the edge case of overlapping but no vertex containment + if (supportCount === 0) { + supports[supportCount++] = supportsB[0]; + } + + // update supports array size + supports.length = supportCount; + + return collision; + }; + + /** + * Find the overlap between two sets of vertices. + * @method _overlapAxes + * @private + * @param {object} result + * @param {vertices} verticesA + * @param {vertices} verticesB + * @param {axes} axes + */ + Collision._overlapAxes = function(result, verticesA, verticesB, axes) { + var verticesALength = verticesA.length, + verticesBLength = verticesB.length, + verticesAX = verticesA[0].x, + verticesAY = verticesA[0].y, + verticesBX = verticesB[0].x, + verticesBY = verticesB[0].y, + axesLength = axes.length, + overlapMin = Number.MAX_VALUE, + overlapAxisNumber = 0, + overlap, + overlapAB, + overlapBA, + dot, + i, + j; + + for (i = 0; i < axesLength; i++) { + var axis = axes[i], + axisX = axis.x, + axisY = axis.y, + minA = verticesAX * axisX + verticesAY * axisY, + minB = verticesBX * axisX + verticesBY * axisY, + maxA = minA, + maxB = minB; + + for (j = 1; j < verticesALength; j += 1) { + dot = verticesA[j].x * axisX + verticesA[j].y * axisY; + + if (dot > maxA) { + maxA = dot; + } else if (dot < minA) { + minA = dot; + } + } + + for (j = 1; j < verticesBLength; j += 1) { + dot = verticesB[j].x * axisX + verticesB[j].y * axisY; + + if (dot > maxB) { + maxB = dot; + } else if (dot < minB) { + minB = dot; + } + } + + overlapAB = maxA - minB; + overlapBA = maxB - minA; + overlap = overlapAB < overlapBA ? overlapAB : overlapBA; + + if (overlap < overlapMin) { + overlapMin = overlap; + overlapAxisNumber = i; + + if (overlap <= 0) { + // can not be intersecting + break; + } + } + } + + result.axis = axes[overlapAxisNumber]; + result.overlap = overlapMin; + }; + + /** + * Projects vertices on an axis and returns an interval. + * @method _projectToAxis + * @private + * @param {} projection + * @param {} vertices + * @param {} axis + */ + Collision._projectToAxis = function(projection, vertices, axis) { + var min = vertices[0].x * axis.x + vertices[0].y * axis.y, + max = min; + + for (var i = 1; i < vertices.length; i += 1) { + var dot = vertices[i].x * axis.x + vertices[i].y * axis.y; + + 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 {body} bodyA + * @param {body} bodyB + * @param {vector} normal + * @param {number} direction + * @return [vector] + */ + Collision._findSupports = function(bodyA, bodyB, normal, direction) { + var vertices = bodyB.vertices, + verticesLength = vertices.length, + bodyAPositionX = bodyA.position.x, + bodyAPositionY = bodyA.position.y, + normalX = normal.x * direction, + normalY = normal.y * direction, + nearestDistance = Number.MAX_VALUE, + vertexA, + vertexB, + vertexC, + distance, + j; + + // find deepest vertex relative to the axis + for (j = 0; j < verticesLength; j += 1) { + vertexB = vertices[j]; + distance = normalX * (bodyAPositionX - vertexB.x) + normalY * (bodyAPositionY - vertexB.y); + + // convex hill-climbing + if (distance < nearestDistance) { + nearestDistance = distance; + vertexA = vertexB; + } + } + + // measure next vertex + vertexC = vertices[(verticesLength + vertexA.index - 1) % verticesLength]; + nearestDistance = normalX * (bodyAPositionX - vertexC.x) + normalY * (bodyAPositionY - vertexC.y); + + // compare with previous vertex + vertexB = vertices[(vertexA.index + 1) % verticesLength]; + if (normalX * (bodyAPositionX - vertexB.x) + normalY * (bodyAPositionY - vertexB.y) < nearestDistance) { + _supports[0] = vertexA; + _supports[1] = vertexB; + + return _supports; + } + + _supports[0] = vertexA; + _supports[1] = vertexC; + + return _supports; + }; + + /* + * + * Properties Documentation + * + */ + + /** + * A reference to the pair using this collision record, if there is one. + * + * @property pair + * @type {pair|null} + * @default null + */ + + /** + * A flag that indicates if the bodies were colliding when the collision was last updated. + * + * @property collided + * @type boolean + * @default false + */ + + /** + * The first body part represented by the collision (see also `collision.parentA`). + * + * @property bodyA + * @type body + */ + + /** + * The second body part represented by the collision (see also `collision.parentB`). + * + * @property bodyB + * @type body + */ + + /** + * The first body represented by the collision (i.e. `collision.bodyA.parent`). + * + * @property parentA + * @type body + */ + + /** + * The second body represented by the collision (i.e. `collision.bodyB.parent`). + * + * @property parentB + * @type body + */ + + /** + * A `Number` that represents the minimum separating distance between the bodies along the collision normal. + * + * @readOnly + * @property depth + * @type number + * @default 0 + */ + + /** + * A normalised `Vector` that represents the direction between the bodies that provides the minimum separating distance. + * + * @property normal + * @type vector + * @default { x: 0, y: 0 } + */ + + /** + * A normalised `Vector` that is the tangent direction to the collision normal. + * + * @property tangent + * @type vector + * @default { x: 0, y: 0 } + */ + + /** + * A `Vector` that represents the direction and depth of the collision. + * + * @property penetration + * @type vector + * @default { x: 0, y: 0 } + */ + + /** + * An array of body vertices that represent the support points in the collision. + * These are the deepest vertices (along the collision normal) of each body that are contained by the other body's vertices. + * + * @property supports + * @type vector[] + * @default [] + */ + })(); diff --git a/src/collision/Query.js b/src/collision/Query.js index cfc6877..4f45afb 100644 --- a/src/collision/Query.js +++ b/src/collision/Query.js @@ -11,7 +11,7 @@ var Query = {}; module.exports = Query; var Vector = require('../geometry/Vector'); -var SAT = require('./SAT'); +var Collision = require('./Collision'); var Bounds = require('../geometry/Bounds'); var Bodies = require('../factory/Bodies'); var Vertices = require('../geometry/Vertices'); @@ -23,13 +23,13 @@ var Vertices = require('../geometry/Vertices'); * @method collides * @param {body} body * @param {body[]} bodies - * @return {object[]} Collisions + * @return {collision[]} Collisions */ Query.collides = function(body, bodies) { var collisions = [], bodiesLength = bodies.length, bounds = body.bounds, - collides = SAT.collides, + collides = Collision.collides, overlaps = Bounds.overlaps; for (var i = 0; i < bodiesLength; i++) { @@ -63,7 +63,7 @@ var Vertices = require('../geometry/Vertices'); * @param {vector} startPoint * @param {vector} endPoint * @param {number} [rayWidth] - * @return {object[]} Collisions + * @return {collision[]} Collisions */ Query.ray = function(bodies, startPoint, endPoint, rayWidth) { rayWidth = rayWidth || 1e-100; diff --git a/src/collision/SAT.js b/src/collision/SAT.js index 3a3a611..cc0e8c5 100644 --- a/src/collision/SAT.js +++ b/src/collision/SAT.js @@ -1,290 +1,37 @@ /** +* This module has now been replaced by `Matter.Collision`. +* +* All usage should be migrated to `Matter.Collision`. +* For back-compatibility purposes this module will remain for a short term and then later removed in a future release. +* * The `Matter.SAT` module contains methods for detecting collisions using the Separating Axis Theorem. * * @class SAT +* @deprecated */ var SAT = {}; module.exports = SAT; -var Vertices = require('../geometry/Vertices'); -var Pair = require('../collision/Pair'); var Collision = require('./Collision'); +var Common = require('../core/Common'); +var deprecated = Common.deprecated; (function() { - var _supports = []; - - var _overlapAB = { - overlap: 0, - axis: null - }; - - var _overlapBA = { - overlap: 0, - axis: null - }; /** * Detect collision between two bodies using the Separating Axis Theorem. + * @deprecated replaced by Collision.collides * @method collides * @param {body} bodyA * @param {body} bodyB - * @param {pairs} [pairs] Optionally reuse collision objects from existing pairs. - * @return {collision|null} A collision object if found, otherwise null + * @return {collision} collision */ - SAT.collides = function(bodyA, bodyB, pairs) { - SAT._overlapAxes(_overlapAB, bodyA.vertices, bodyB.vertices, bodyA.axes); - - if (_overlapAB.overlap <= 0) { - return null; - } - - SAT._overlapAxes(_overlapBA, bodyB.vertices, bodyA.vertices, bodyB.axes); - - if (_overlapBA.overlap <= 0) { - return null; - } - - // reuse collision objects for gc efficiency - var pair = pairs && pairs.table[Pair.id(bodyA, bodyB)], - collision; - - if (!pair) { - collision = Collision.create(bodyA, bodyB); - collision.collided = true; - collision.bodyA = bodyA.id < bodyB.id ? bodyA : bodyB; - collision.bodyB = bodyA.id < bodyB.id ? bodyB : bodyA; - collision.parentA = collision.bodyA.parent; - collision.parentB = collision.bodyB.parent; - } else { - collision = pair.collision; - } - - bodyA = collision.bodyA; - bodyB = collision.bodyB; - - var minOverlap; - - if (_overlapAB.overlap < _overlapBA.overlap) { - minOverlap = _overlapAB; - } else { - minOverlap = _overlapBA; - } - - var normal = collision.normal, - supports = collision.supports, - minAxis = minOverlap.axis, - minAxisX = minAxis.x, - minAxisY = minAxis.y; - - // ensure normal is facing away from bodyA - if (minAxisX * (bodyB.position.x - bodyA.position.x) + minAxisY * (bodyB.position.y - bodyA.position.y) < 0) { - normal.x = minAxisX; - normal.y = minAxisY; - } else { - normal.x = -minAxisX; - normal.y = -minAxisY; - } - - collision.tangent.x = -normal.y; - collision.tangent.y = normal.x; - - collision.depth = minOverlap.overlap; - - collision.penetration.x = normal.x * collision.depth; - collision.penetration.y = normal.y * collision.depth; - - // find support points, there is always either exactly one or two - var supportsB = SAT._findSupports(bodyA, bodyB, normal, 1), - supportCount = 0; - - // find the supports from bodyB that are inside bodyA - if (Vertices.contains(bodyA.vertices, supportsB[0])) { - supports[supportCount++] = supportsB[0]; - } - - if (Vertices.contains(bodyA.vertices, supportsB[1])) { - supports[supportCount++] = supportsB[1]; - } - - // find the supports from bodyA that are inside bodyB - if (supportCount < 2) { - var supportsA = SAT._findSupports(bodyB, bodyA, normal, -1); - - if (Vertices.contains(bodyB.vertices, supportsA[0])) { - supports[supportCount++] = supportsA[0]; - } - - if (supportCount < 2 && Vertices.contains(bodyB.vertices, supportsA[1])) { - supports[supportCount++] = supportsA[1]; - } - } - - // account for the edge case of overlapping but no vertex containment - if (supportCount === 0) { - supports[supportCount++] = supportsB[0]; - } - - // update supports array size - supports.length = supportCount; - - return collision; + SAT.collides = function(bodyA, bodyB) { + return Collision.collides(bodyA, bodyB); }; - /** - * Find the overlap between two sets of vertices. - * @method _overlapAxes - * @private - * @param {object} result - * @param {vertices} verticesA - * @param {vertices} verticesB - * @param {axes} axes - */ - SAT._overlapAxes = function(result, verticesA, verticesB, axes) { - var verticesALength = verticesA.length, - verticesBLength = verticesB.length, - verticesAX = verticesA[0].x, - verticesAY = verticesA[0].y, - verticesBX = verticesB[0].x, - verticesBY = verticesB[0].y, - axesLength = axes.length, - overlapMin = Number.MAX_VALUE, - overlapAxisNumber = 0, - overlap, - overlapAB, - overlapBA, - dot, - i, - j; - - for (i = 0; i < axesLength; i++) { - var axis = axes[i], - axisX = axis.x, - axisY = axis.y, - minA = verticesAX * axisX + verticesAY * axisY, - minB = verticesBX * axisX + verticesBY * axisY, - maxA = minA, - maxB = minB; - - for (j = 1; j < verticesALength; j += 1) { - dot = verticesA[j].x * axisX + verticesA[j].y * axisY; - - if (dot > maxA) { - maxA = dot; - } else if (dot < minA) { - minA = dot; - } - } - - for (j = 1; j < verticesBLength; j += 1) { - dot = verticesB[j].x * axisX + verticesB[j].y * axisY; - - if (dot > maxB) { - maxB = dot; - } else if (dot < minB) { - minB = dot; - } - } - - overlapAB = maxA - minB; - overlapBA = maxB - minA; - overlap = overlapAB < overlapBA ? overlapAB : overlapBA; - - if (overlap < overlapMin) { - overlapMin = overlap; - overlapAxisNumber = i; - - if (overlap <= 0) { - // can not be intersecting - break; - } - } - } - - result.axis = axes[overlapAxisNumber]; - result.overlap = overlapMin; - }; - - /** - * Projects vertices on an axis and returns an interval. - * @method _projectToAxis - * @private - * @param {} projection - * @param {} vertices - * @param {} axis - */ - SAT._projectToAxis = function(projection, vertices, axis) { - var min = vertices[0].x * axis.x + vertices[0].y * axis.y, - max = min; - - for (var i = 1; i < vertices.length; i += 1) { - var dot = vertices[i].x * axis.x + vertices[i].y * axis.y; - - 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 {body} bodyA - * @param {body} bodyB - * @param {vector} normal - * @param {number} direction - * @return [vector] - */ - SAT._findSupports = function(bodyA, bodyB, normal, direction) { - var vertices = bodyB.vertices, - verticesLength = vertices.length, - bodyAPositionX = bodyA.position.x, - bodyAPositionY = bodyA.position.y, - normalX = normal.x * direction, - normalY = normal.y * direction, - nearestDistance = Number.MAX_VALUE, - vertexA, - vertexB, - vertexC, - distance, - j; - - // find deepest vertex relative to the axis - for (j = 0; j < verticesLength; j += 1) { - vertexB = vertices[j]; - distance = normalX * (bodyAPositionX - vertexB.x) + normalY * (bodyAPositionY - vertexB.y); - - // convex hill-climbing - if (distance < nearestDistance) { - nearestDistance = distance; - vertexA = vertexB; - } - } - - // measure next vertex - vertexC = vertices[(verticesLength + vertexA.index - 1) % verticesLength]; - nearestDistance = normalX * (bodyAPositionX - vertexC.x) + normalY * (bodyAPositionY - vertexC.y); - - // compare with previous vertex - vertexB = vertices[(vertexA.index + 1) % verticesLength]; - if (normalX * (bodyAPositionX - vertexB.x) + normalY * (bodyAPositionY - vertexB.y) < nearestDistance) { - _supports[0] = vertexA; - _supports[1] = vertexB; - - return _supports; - } - - _supports[0] = vertexA; - _supports[1] = vertexC; - - return _supports; - }; + deprecated(SAT, 'collides', 'SAT.collides ➤ replaced by Collision.collides'); })(); diff --git a/src/module/main.js b/src/module/main.js index 869ce38..fa0b57c 100644 --- a/src/module/main.js +++ b/src/module/main.js @@ -4,6 +4,7 @@ Matter.Axes = require('../geometry/Axes'); Matter.Bodies = require('../factory/Bodies'); Matter.Body = require('../body/Body'); Matter.Bounds = require('../geometry/Bounds'); +Matter.Collision = require('../collision/Collision'); Matter.Common = require('../core/Common'); Matter.Composite = require('../body/Composite'); Matter.Composites = require('../factory/Composites'); diff --git a/test/ExampleWorker.js b/test/ExampleWorker.js index 81b25eb..7b8b43d 100644 --- a/test/ExampleWorker.js +++ b/test/ExampleWorker.js @@ -79,12 +79,21 @@ const prepareMatter = (options) => { Matter.Common.info = Matter.Common.warn = Matter.Common.log; if (options.stableSort) { - const MatterSATCollides = Matter.SAT.collides; - Matter.SAT.collides = function(bodyA, bodyB, previousCollision, pairActive) { - const _bodyA = bodyA.id < bodyB.id ? bodyA : bodyB; - const _bodyB = bodyA.id < bodyB.id ? bodyB : bodyA; - return MatterSATCollides(_bodyA, _bodyB, previousCollision, pairActive); - }; + if (Matter.Collision) { + const MatterCollisionCollides = Matter.Collision.collides; + Matter.Collision.collides = function(bodyA, bodyB, pairs) { + const _bodyA = bodyA.id < bodyB.id ? bodyA : bodyB; + const _bodyB = bodyA.id < bodyB.id ? bodyB : bodyA; + return MatterCollisionCollides(_bodyA, _bodyB, pairs); + }; + } else { + const MatterSATCollides = Matter.SAT.collides; + Matter.SAT.collides = function(bodyA, bodyB, previousCollision, pairActive) { + const _bodyA = bodyA.id < bodyB.id ? bodyA : bodyB; + const _bodyB = bodyA.id < bodyB.id ? bodyB : bodyA; + return MatterSATCollides(_bodyA, _bodyB, previousCollision, pairActive); + }; + } Matter.after('Detector.collisions', function() { this.sort(collisionCompareId); }); Matter.after('Composite.allBodies', function() { sortById(this); });