mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2024-12-28 17:02:24 -05:00
First implementation of sweep and prune (#1740)
This commit is contained in:
parent
871531b46a
commit
1f39b1df98
5 changed files with 436 additions and 70 deletions
|
@ -42,6 +42,7 @@ var paper = function(self, undefined) {
|
|||
/*#*/ include('core/PaperScope.js');
|
||||
/*#*/ include('core/PaperScopeItem.js');
|
||||
|
||||
/*#*/ include('util/CollisionDetection.js');
|
||||
/*#*/ include('util/Formatter.js');
|
||||
/*#*/ include('util/Numerical.js');
|
||||
/*#*/ include('util/UID.js');
|
||||
|
|
|
@ -2103,52 +2103,55 @@ new function() { // Scope for bezier intersection using fat-line clipping
|
|||
}
|
||||
|
||||
function getIntersections(curves1, curves2, include, matrix1, matrix2,
|
||||
_returnFirst) {
|
||||
_returnFirst) {
|
||||
var epsilon = Numerical.GEOMETRIC_EPSILON;
|
||||
var self = !curves2;
|
||||
if (self)
|
||||
curves2 = curves1;
|
||||
var length1 = curves1.length,
|
||||
length2 = curves2.length,
|
||||
values2 = [],
|
||||
arrays = [],
|
||||
locations,
|
||||
current;
|
||||
// Cache values for curves2 as we re-iterate them for each in curves1.
|
||||
for (var i = 0; i < length2; i++)
|
||||
values2[i] = curves2[i].getValues(matrix2);
|
||||
values1 = new Array(length1),
|
||||
values2 = self ? values1 : new Array(length2),
|
||||
locations = [];
|
||||
|
||||
for (var i = 0; i < length1; i++) {
|
||||
var curve1 = curves1[i],
|
||||
values1 = self ? values2[i] : curve1.getValues(matrix1),
|
||||
path1 = curve1.getPath();
|
||||
// NOTE: Due to the nature of getCurveIntersections(), we use
|
||||
// separate location arrays per path1, to make sure the circularity
|
||||
// checks are not getting confused by locations on separate paths.
|
||||
// The separate arrays are then flattened in the end.
|
||||
if (path1 !== current) {
|
||||
current = path1;
|
||||
locations = [];
|
||||
arrays.push(locations);
|
||||
var v = curves1[i].getValues(matrix1);
|
||||
values1[i] = v;
|
||||
}
|
||||
if (!self) {
|
||||
for (var i = 0; i < length2; i++) {
|
||||
var v = curves2[i].getValues(matrix2);
|
||||
values2[i] = v;
|
||||
}
|
||||
}
|
||||
var boundsCollisions = CollisionDetection.findCurveBoundsCollisions(
|
||||
values1, self ? null : values2, epsilon);
|
||||
for (var index1 = 0; index1 < length1; index1++) {
|
||||
var curve1 = curves1[index1],
|
||||
v1 = values1[index1];
|
||||
if (self) {
|
||||
// First check for self-intersections within the same curve.
|
||||
getSelfIntersection(values1, curve1, locations, include);
|
||||
getSelfIntersection(v1, curve1, locations, include);
|
||||
}
|
||||
// Check for intersections with other curves.
|
||||
// For self-intersection, we can start at i + 1 instead of 0.
|
||||
for (var j = self ? i + 1 : 0; j < length2; j++) {
|
||||
// There might be already one location from the above
|
||||
// self-intersection check:
|
||||
if (_returnFirst && locations.length)
|
||||
return locations;
|
||||
getCurveIntersections(values1, values2[j], curve1, curves2[j],
|
||||
locations, include);
|
||||
// Check for intersections with potentially intersecting curves.
|
||||
var collisions1 = boundsCollisions[index1];
|
||||
if (collisions1) {
|
||||
for (var j = 0; j < collisions1.length; j++) {
|
||||
// There might be already one location from the above
|
||||
// self-intersection check:
|
||||
if (_returnFirst && locations.length)
|
||||
return locations;
|
||||
var index2 = collisions1[j];
|
||||
if (!self || index2 > index1) {
|
||||
var curve2 = curves2[index2],
|
||||
v2 = values2[index2];
|
||||
getCurveIntersections(
|
||||
v1, v2, curve1, curve2, locations, include
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Flatten the list of location arrays to one array and return it.
|
||||
locations = [];
|
||||
for (var i = 0, l = arrays.length; i < l; i++) {
|
||||
Base.push(locations, arrays[i]);
|
||||
}
|
||||
}
|
||||
return locations;
|
||||
}
|
||||
|
||||
|
|
|
@ -156,19 +156,61 @@ PathItem.inject(new function() {
|
|||
collect(paths1);
|
||||
if (paths2)
|
||||
collect(paths2);
|
||||
|
||||
var curvesValues = new Array(curves.length);
|
||||
for (var i = 0, l = curves.length; i < l; i++) {
|
||||
curvesValues[i] = curves[i].getValues();
|
||||
}
|
||||
var horCurveCollisions =
|
||||
CollisionDetection.findCurveBoundsCollisions(
|
||||
curvesValues, curvesValues, 0, false, true);
|
||||
var horCurvesMap = {};
|
||||
for (var i = 0; i < curves.length; i++) {
|
||||
var curve = curves[i],
|
||||
collidingCurves = [],
|
||||
collisionIndices = horCurveCollisions[i];
|
||||
if (collisionIndices) {
|
||||
for (var j = 0; j < collisionIndices.length; j++) {
|
||||
collidingCurves.push(curves[collisionIndices[j]]);
|
||||
}
|
||||
}
|
||||
var pathId = curve.getPath().getId();
|
||||
horCurvesMap[pathId] = horCurvesMap[pathId] || {};
|
||||
horCurvesMap[pathId][curve.getIndex()] = collidingCurves;
|
||||
}
|
||||
|
||||
var vertCurveCollisions =
|
||||
CollisionDetection.findCurveBoundsCollisions(
|
||||
curvesValues, curvesValues, 0, true, true);
|
||||
var vertCurvesMap = {};
|
||||
for (var i = 0; i < curves.length; i++) {
|
||||
var curve = curves[i],
|
||||
collidingCurves = [],
|
||||
collisionIndices = vertCurveCollisions[i];
|
||||
if (collisionIndices) {
|
||||
for (var j = 0; j < collisionIndices.length; j++) {
|
||||
collidingCurves.push(curves[collisionIndices[j]]);
|
||||
}
|
||||
}
|
||||
var pathId = curve.getPath().getId();
|
||||
vertCurvesMap[pathId] = vertCurvesMap[pathId] || {};
|
||||
vertCurvesMap[pathId][curve.getIndex()] = collidingCurves;
|
||||
}
|
||||
|
||||
// Propagate the winding contribution. Winding contribution of
|
||||
// curves does not change between two crossings.
|
||||
// First, propagate winding contributions for curve chains starting
|
||||
// in all crossings:
|
||||
for (var i = 0, l = crossings.length; i < l; i++) {
|
||||
propagateWinding(crossings[i]._segment, _path1, _path2, curves,
|
||||
operator);
|
||||
propagateWinding(crossings[i]._segment, _path1, _path2,
|
||||
horCurvesMap, vertCurvesMap, operator);
|
||||
}
|
||||
for (var i = 0, l = segments.length; i < l; i++) {
|
||||
var segment = segments[i],
|
||||
inter = segment._intersection;
|
||||
if (!segment._winding) {
|
||||
propagateWinding(segment, _path1, _path2, curves, operator);
|
||||
propagateWinding(segment, _path1, _path2,
|
||||
horCurvesMap, vertCurvesMap, operator);
|
||||
}
|
||||
// See if all encountered segments in a path are overlaps.
|
||||
if (!(inter && inter._overlap))
|
||||
|
@ -186,7 +228,6 @@ PathItem.inject(new function() {
|
|||
return !!operator[w];
|
||||
});
|
||||
}
|
||||
|
||||
return createResult(paths, true, path1, path2, options);
|
||||
}
|
||||
|
||||
|
@ -300,29 +341,39 @@ PathItem.inject(new function() {
|
|||
// Get reference to the first, largest path and insert it
|
||||
// already.
|
||||
first = sorted[0];
|
||||
// create lookup containing potentially overlapping path bounds
|
||||
var collisions = CollisionDetection.findItemBoundsCollisions(sorted,
|
||||
null, Numerical.GEOMETRIC_EPSILON);
|
||||
if (clockwise == null)
|
||||
clockwise = first.isClockwise();
|
||||
// Now determine the winding for each path, from large to small.
|
||||
for (var i = 0; i < length; i++) {
|
||||
var path1 = sorted[i],
|
||||
entry1 = lookup[path1._id],
|
||||
point = path1.getInteriorPoint(),
|
||||
containerWinding = 0;
|
||||
for (var j = i - 1; j >= 0; j--) {
|
||||
var path2 = sorted[j];
|
||||
// As we run through the paths from largest to smallest, for
|
||||
// any current path, all potentially containing paths have
|
||||
// already been processed and their orientation fixed.
|
||||
// To achieve correct orientation of contained paths based
|
||||
// on winding, we have to find one containing path with
|
||||
// different "insideness" and set opposite orientation.
|
||||
if (path2.contains(point)) {
|
||||
var entry2 = lookup[path2._id];
|
||||
containerWinding = entry2.winding;
|
||||
entry1.winding += containerWinding;
|
||||
entry1.container = entry2.exclude ? entry2.container
|
||||
: path2;
|
||||
break;
|
||||
indicesI = collisions[i];
|
||||
if (indicesI) {
|
||||
var entry1 = lookup[path1._id],
|
||||
point = null; // interior point, only get it if required
|
||||
containerWinding = 0;
|
||||
for (var j = indicesI.length - 1; j >= 0; j--) {
|
||||
if (indicesI[j] < i) {
|
||||
point = point || path1.getInteriorPoint();
|
||||
var path2 = sorted[indicesI[j]];
|
||||
// As we run through the paths from largest to
|
||||
// smallest, for any current path, all potentially
|
||||
// containing paths have already been processed and
|
||||
// their orientation fixed. To achieve correct
|
||||
// orientation of contained paths based on winding,
|
||||
// we have to find one containing path with
|
||||
// different "insideness" and set opposite orientation.
|
||||
if (path2.contains(point)) {
|
||||
var entry2 = lookup[path2._id];
|
||||
containerWinding = entry2.winding;
|
||||
entry1.winding += containerWinding;
|
||||
entry1.container = entry2.exclude ?
|
||||
entry2.container : path2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Only keep paths if the "insideness" changes when crossing the
|
||||
|
@ -483,9 +534,16 @@ PathItem.inject(new function() {
|
|||
*
|
||||
* @param {Point} point the location for which to determine the winding
|
||||
* contribution
|
||||
* @param {Curve[]} curves the curves that describe the shape against which
|
||||
* @param {Curve[]} curvesH The curves that describe the shape against which
|
||||
* to check, as returned by {@link Path#curves} or
|
||||
* {@link CompoundPath#curves}
|
||||
* {@link CompoundPath#curves}. This only has to contain those curves
|
||||
* that can be crossed by a horizontal line through the point to be
|
||||
* checked.
|
||||
* @param {Curve[]} curvesV The curves that describe the shape against which
|
||||
* to check, as returned by {@link Path#curves} or
|
||||
* {@link CompoundPath#curves}. This only has to contain those curves
|
||||
* that can be crossed by a vertical line through the point to be
|
||||
* checked.
|
||||
* @param {Boolean} [dir=false] the direction in which to determine the
|
||||
* winding contribution, `false`: in x-direction, `true`: in y-direction
|
||||
* @param {Boolean} [closed=false] determines how areas should be closed
|
||||
|
@ -498,7 +556,8 @@ PathItem.inject(new function() {
|
|||
* well as an indication whether the point was situated on the contour
|
||||
* @private
|
||||
*/
|
||||
function getWinding(point, curves, dir, closed, dontFlip) {
|
||||
function getWinding(point, curvesH, curvesV, dir, closed, dontFlip) {
|
||||
var curves = !dir ? curvesV : curvesH;
|
||||
// Determine the index of the abscissa and ordinate values in the curve
|
||||
// values arrays, based on the direction:
|
||||
var ia = dir ? 1 : 0, // the abscissa index
|
||||
|
@ -613,7 +672,7 @@ PathItem.inject(new function() {
|
|||
// again with flipped direction and return that result instead.
|
||||
return !dontFlip && a > paL && a < paR
|
||||
&& Curve.getTangent(v, t)[dir ? 'x' : 'y'] === 0
|
||||
&& getWinding(point, curves, !dir, closed, true);
|
||||
&& getWinding(point, curvesH, curvesV, !dir, closed, true);
|
||||
}
|
||||
|
||||
function handleCurve(v) {
|
||||
|
@ -734,7 +793,8 @@ PathItem.inject(new function() {
|
|||
};
|
||||
}
|
||||
|
||||
function propagateWinding(segment, path1, path2, curves, operator) {
|
||||
function propagateWinding(segment, path1, path2, horCurveCollisionsMap,
|
||||
vertCurveCollisionsMap, operator) {
|
||||
// Here we try to determine the most likely winding number contribution
|
||||
// for the curve-chain starting with this segment. Once we have enough
|
||||
// confidence in the winding contribution, we can propagate it until the
|
||||
|
@ -801,7 +861,12 @@ PathItem.inject(new function() {
|
|||
}
|
||||
}
|
||||
}
|
||||
wind = wind || getWinding(pt, curves, dir, true);
|
||||
var pathId = path.getId();
|
||||
var curveIndex = curve.getIndex();
|
||||
var hCollisions = horCurveCollisionsMap[pathId][curveIndex];
|
||||
var vCollisions = vertCurveCollisionsMap[pathId][curveIndex];
|
||||
wind = wind ||
|
||||
getWinding(pt, hCollisions, vCollisions, dir, true);
|
||||
if (wind.quality > winding.quality)
|
||||
winding = wind;
|
||||
break;
|
||||
|
@ -1077,7 +1142,8 @@ PathItem.inject(new function() {
|
|||
* @return {Number} the winding number
|
||||
*/
|
||||
_getWinding: function(point, dir, closed) {
|
||||
return getWinding(point, this.getCurves(), dir, closed);
|
||||
let curves = this.getCurves();
|
||||
return getWinding(point, curves, curves, dir, closed);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -722,16 +722,20 @@ var PathItem = Item.extend(/** @lends PathItem# */{
|
|||
matched = [],
|
||||
count = 0;
|
||||
ok = true;
|
||||
var boundsOverlaps = CollisionDetection.findBoundsOverlaps(paths1, paths2, Numerical.GEOMETRIC_EPSILON);
|
||||
for (var i1 = length1 - 1; i1 >= 0 && ok; i1--) {
|
||||
var path1 = paths1[i1];
|
||||
ok = false;
|
||||
for (var i2 = length2 - 1; i2 >= 0 && !ok; i2--) {
|
||||
if (path1.compare(paths2[i2])) {
|
||||
if (!matched[i2]) {
|
||||
matched[i2] = true;
|
||||
count++;
|
||||
var pathBoundsOverlaps = boundsOverlaps[i1];
|
||||
if (pathBoundsOverlaps) {
|
||||
for (var i2 = pathBoundsOverlaps.length - 1; i2 >= 0 && !ok; i2--) {
|
||||
if (path1.compare(paths2[pathBoundsOverlaps[i2]])) {
|
||||
if (!matched[pathBoundsOverlaps[i2]]) {
|
||||
matched[pathBoundsOverlaps[i2]] = true;
|
||||
count++;
|
||||
}
|
||||
ok = true;
|
||||
}
|
||||
ok = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
292
src/util/CollisionDetection.js
Normal file
292
src/util/CollisionDetection.js
Normal file
|
@ -0,0 +1,292 @@
|
|||
/*
|
||||
* Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
|
||||
* http://paperjs.org/
|
||||
*
|
||||
* Copyright (c) 2011 - 2019, Juerg Lehni & Jonathan Puckey
|
||||
* http://scratchdisk.com/ & https://puckey.studio/
|
||||
*
|
||||
* Distributed under the MIT license. See LICENSE file for details.
|
||||
*
|
||||
* All rights reserved.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @name CollisionDetection
|
||||
* @namespace
|
||||
* @private
|
||||
*/
|
||||
var CollisionDetection = /** @lends CollisionDetection */{
|
||||
|
||||
/**
|
||||
* Finds collisions between axis aligned bounding boxes of items.
|
||||
*
|
||||
* This function takes the bounds of all items in the items1 and items2
|
||||
* arrays and calls findBoundsCollisions().
|
||||
*
|
||||
* @param {Array} itemsA Array of curve values for which collisions should
|
||||
* be found.
|
||||
* @param {Array} [itemsA] Array of curve values that the first array should
|
||||
* be compared with. If not provided, collisions between items within
|
||||
* the first arrray will be returned.
|
||||
* @param {Number} [tolerance] If provided, the tolerance will be added to
|
||||
* all sides of each bounds when checking for collisions.
|
||||
* @param {Boolean} [sweepVertical] If set to true, the sweep is done
|
||||
* along the y axis.
|
||||
* @param {Boolean} [onlySweepAxisCollisionss] If set to true, no collision
|
||||
* checks will be done on the secondary axis.
|
||||
* @returns {Array} Array containing for the bounds at thes same index in
|
||||
* itemsA an array of the indexes of colliding bounds in itemsB
|
||||
*
|
||||
* @author Jan Boesenberg <jan.boesenberg@gmail.com>
|
||||
*/
|
||||
findItemBoundsCollisions: function(itemsA, itemsB, tolerance,
|
||||
sweepVertical, onlySweepAxisCollisions) {
|
||||
var boundsArr1 = new Array(itemsA.length),
|
||||
boundsArr2;
|
||||
for (var i = 0; i < boundsArr1.length; i++) {
|
||||
var bounds = itemsA[i].bounds;
|
||||
boundsArr1[i] = [bounds.left, bounds.top, bounds.right,
|
||||
bounds.bottom];
|
||||
}
|
||||
if (itemsB) {
|
||||
if (itemsB === itemsA) {
|
||||
boundsArr2 = boundsArr1;
|
||||
} else {
|
||||
boundsArr2 = new Array(itemsB.length);
|
||||
for (var i = 0; i < boundsArr2.length; i++) {
|
||||
var bounds = itemsB[i].bounds;
|
||||
boundsArr2[i] = [bounds.left, bounds.top, bounds.right,
|
||||
bounds.bottom];
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.findBoundsCollisions(boundsArr1, boundsArr2, tolerance || 0,
|
||||
sweepVertical, onlySweepAxisCollisions);
|
||||
},
|
||||
|
||||
/**
|
||||
* Finds collisions between curves bounds. For performance reasons this
|
||||
* uses broad bounds of the curve, which can be calculated much faster than
|
||||
* the actual bounds. Broad bounds guarantee to contain the full curve,
|
||||
* but they are usually larger than the actual bounds of a curve.
|
||||
*
|
||||
* This function takes the broad bounds of all curve values in the
|
||||
* curveValues1 and curveValues2 arrays and calls findBoundsCollisions().
|
||||
*
|
||||
* @param {Array} curvesValues1 Array of curve values for which collisions
|
||||
* should be found.
|
||||
* @param {Array} [curvesValues2] Array of curve values that the first
|
||||
* array should be compared with. If not provided, collisions between
|
||||
* curve bounds within the first arrray will be returned.
|
||||
* @param {Number} [tolerance] If provided, the tolerance will be added to
|
||||
* all sides of each bounds when checking for collisions.
|
||||
* @param {Boolean} [sweepVertical] If set to true, the sweep is done
|
||||
* along the y axis.
|
||||
* @param {Boolean} [onlySweepAxisCollisionss] If set to true, no collision
|
||||
* checks will be done on the secondary axis.
|
||||
* @returns {Array} Array containing for the bounds at thes same index in
|
||||
* curveValuesA an array of the indexes of colliding bounds in
|
||||
* curveValuesB
|
||||
*
|
||||
* @author Jan Boesenberg <jan.boesenberg@gmail.com>
|
||||
*/
|
||||
findCurveBoundsCollisions: function(curvesValues1, curvesValues2,
|
||||
tolerance, sweepVertical, onlySweepAxisCollisions) {
|
||||
var min = Math.min,
|
||||
max = Math.max,
|
||||
boundsArr1 = new Array(curvesValues1.length),
|
||||
boundsArr2;
|
||||
for (var i = 0; i < boundsArr1.length; i++) {
|
||||
var v1 = curvesValues1[i];
|
||||
boundsArr1[i] = [
|
||||
min(v1[0], v1[2], v1[4], v1[6]),
|
||||
min(v1[1], v1[3], v1[5], v1[7]),
|
||||
max(v1[0], v1[2], v1[4], v1[6]),
|
||||
max(v1[1], v1[3], v1[5], v1[7])
|
||||
];
|
||||
}
|
||||
if (curvesValues2) {
|
||||
if (curvesValues2 === curvesValues1) {
|
||||
boundsArr2 = boundsArr1;
|
||||
} else {
|
||||
boundsArr2 = new Array(curvesValues2.length);
|
||||
for (var i = 0; i < boundsArr2.length; i++) {
|
||||
var v2 = curvesValues2[i];
|
||||
boundsArr2[i] = [
|
||||
min(v2[0], v2[2], v2[4], v2[6]),
|
||||
min(v2[1], v2[3], v2[5], v2[7]),
|
||||
max(v2[0], v2[2], v2[4], v2[6]),
|
||||
max(v2[1], v2[3], v2[5], v2[7])
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.findBoundsCollisions(boundsArr1, boundsArr2,
|
||||
tolerance || 0, sweepVertical, onlySweepAxisCollisions);
|
||||
},
|
||||
|
||||
/**
|
||||
* Finds collisions between two sets of bounding rectangles.
|
||||
*
|
||||
* The collision detection is implemented as a sweep and prune algorithm
|
||||
* with sweep either along the x or y axis (primary axis) and immediate
|
||||
* check on secondary axis for potential pairs.
|
||||
*
|
||||
* Each entry in the bounds arrays must be an array of length 4 with
|
||||
* x0, y0, x1, and y1 as the array elements.
|
||||
*
|
||||
* The returned array has the same length as boundsArr1. Each entry
|
||||
* contains an array with all indices of overlapping bounds of
|
||||
* boundsArr2 (or boundsArr1 if boundsArr2 is not provided) sorted
|
||||
* in ascending order.
|
||||
*
|
||||
* If the second bounds array parameter is null, collisions between bounds
|
||||
* within the first bounds array will be found. In this case the indexed
|
||||
* returned for each bounds will not contain the bounds' own index.
|
||||
*
|
||||
*
|
||||
* @param {Array} boundsArr1 Array of bounds objects for which collisions
|
||||
* should be found.
|
||||
* @param {Array} [boundsArr2] Array of bounds that the first array should
|
||||
* be compared with. If not provided, collisions between bounds within
|
||||
* the first arrray will be returned.
|
||||
* @param {Number} [tolerance] If provided, the tolerance will be added to
|
||||
* all sides of each bounds when checking for collisions.
|
||||
* @param {Boolean} [sweepVertical] If set to true, the sweep is done
|
||||
* along the y axis.
|
||||
* @param {Boolean} [onlySweepAxisCollisionss] If set to true, no collision
|
||||
* checks will be done on the secondary axis.
|
||||
* @returns {Array} Array containing for the bounds at thes same index in
|
||||
* boundsA an array of the indexes of colliding bounds in boundsB
|
||||
*
|
||||
* @author Jan Boesenberg <jan.boesenberg@gmail.com>
|
||||
*/
|
||||
findBoundsCollisions: function(boundsA, boundsB, tolerance,
|
||||
sweepVertical, onlySweepAxisCollisions) {
|
||||
// Binary search utility function.
|
||||
// For multiple same entries, this returns the rightmost entry.
|
||||
// https://en.wikipedia.org/wiki/Binary_search_algorithm#Procedure_for_finding_the_rightmost_element
|
||||
var lo, hi;
|
||||
var binarySearch = function(indices, coordinateValue, coordinate) {
|
||||
lo = 0;
|
||||
hi = indices.length;
|
||||
while (lo < hi) {
|
||||
var mid = (hi + lo) >>> 1; // same as Math.floor((hi+lo)/2)
|
||||
if (allBounds[indices[mid]][coordinate] < coordinateValue) {
|
||||
lo = mid + 1;
|
||||
} else {
|
||||
hi = mid;
|
||||
}
|
||||
}
|
||||
return lo - 1;
|
||||
};
|
||||
|
||||
//
|
||||
var self = !boundsB || boundsA === boundsB,
|
||||
allBounds = self ? boundsA : boundsA.concat(boundsB),
|
||||
countA = boundsA.length,
|
||||
countAll = allBounds.length;
|
||||
// Set coordinates for primary and secondary axis depending on sweep
|
||||
// direction. By default we sweep in horizontal direction, which
|
||||
// means x is the primary axis.
|
||||
var coordP0 = sweepVertical ? 1 : 0,
|
||||
coordP1 = coordP0 + 2,
|
||||
coordS0 = sweepVertical ? 0 : 1,
|
||||
coordS1 = coordS0 + 2;
|
||||
// Create array with all indices sorted by lower boundary on primary
|
||||
// axis.
|
||||
var allIndicesByP0 = new Array(countAll);
|
||||
for (var i = 0; i < countAll; i++) {
|
||||
allIndicesByP0[i] = i;
|
||||
}
|
||||
allIndicesByP0.sort(function(i1, i2) {
|
||||
return allBounds[i1][coordP0] - allBounds[i2][coordP0];
|
||||
});
|
||||
// Sweep along primary axis. Indices of active bounds are kept in an
|
||||
// array sorted by higher boundary on primary axis.
|
||||
var activeIndicesByP1 = [],
|
||||
allCollisions = new Array(countA);
|
||||
for (var i = 0; i < countAll; i++) {
|
||||
var currentIndex = allIndicesByP0[i],
|
||||
currentBounds = allBounds[currentIndex];
|
||||
currentOriginalIndex = self ? currentIndex
|
||||
: currentIndex - countA, // index in boundsA or boundsB array
|
||||
isCurrentA = currentIndex < countA,
|
||||
isCurrentB = self || currentIndex >= countA,
|
||||
currentCollisions = isCurrentA ? [] : null;
|
||||
if (activeIndicesByP1.length) {
|
||||
// remove (prune) indices that are no longer active
|
||||
var pruneCount = binarySearch(activeIndicesByP1,
|
||||
currentBounds[coordP0] - tolerance, coordP1) + 1;
|
||||
activeIndicesByP1.splice(0, pruneCount);
|
||||
// add collisions for current index
|
||||
if (self && onlySweepAxisCollisions) {
|
||||
// All active indexes can be added, no further checks needed
|
||||
currentCollisions = currentCollisions.concat(
|
||||
activeIndicesByP1.slice());
|
||||
// Add current index to collisions of all active indexes
|
||||
for (var j = 0; j < activeIndicesByP1.length; j++) {
|
||||
var activeIndex = activeIndicesByP1[j];
|
||||
allCollisions[activeIndex].push(currentOriginalIndex);
|
||||
}
|
||||
} else {
|
||||
var currentS1 = currentBounds[coordS1],
|
||||
currentS0 = currentBounds[coordS0];
|
||||
for (var j = 0; j < activeIndicesByP1.length; j++) {
|
||||
var activeIndex = activeIndicesByP1[j],
|
||||
isActiveA = activeIndex < countA,
|
||||
isActiveB = self || activeIndex >= countA;
|
||||
// Check secondary axis bounds if necessary
|
||||
if (onlySweepAxisCollisions ||
|
||||
(((isCurrentA && isActiveB) ||
|
||||
(isCurrentB && isActiveA)) &&
|
||||
currentS1 >=
|
||||
allBounds[activeIndex][coordS0] -
|
||||
tolerance &&
|
||||
currentS0 <=
|
||||
allBounds[activeIndex][coordS1] +
|
||||
tolerance)) {
|
||||
// Add current index to collisions of active
|
||||
// indices and vice versa.
|
||||
if (isCurrentA && isActiveB) {
|
||||
currentCollisions.push(
|
||||
self ? activeIndex : activeIndex - countA);
|
||||
}
|
||||
if (isCurrentB && isActiveA) {
|
||||
allCollisions[activeIndex].push(
|
||||
currentOriginalIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isCurrentA) {
|
||||
if (boundsA === boundsB) {
|
||||
// if both arrays are the same, add self collision
|
||||
currentCollisions.push(currentIndex);
|
||||
}
|
||||
// add collisions for current index
|
||||
allCollisions[currentIndex] = currentCollisions;
|
||||
}
|
||||
// add current index to active indices. Keep array sorted by
|
||||
// their higher boundary on the primary axis
|
||||
if (activeIndicesByP1.length) {
|
||||
var currentP1 = currentBounds[coordP1],
|
||||
insertIndex =
|
||||
binarySearch(activeIndicesByP1, currentP1, coordP1) + 1;
|
||||
activeIndicesByP1.splice(insertIndex, 0, currentIndex);
|
||||
} else {
|
||||
activeIndicesByP1.push(currentIndex);
|
||||
}
|
||||
}
|
||||
// Sort collision indioes in ascending order
|
||||
for (var i = 0; i < allCollisions.length; i++) {
|
||||
if (allCollisions[i]) {
|
||||
allCollisions[i].sort(function(i1, i2) {
|
||||
return i1 - i2;
|
||||
});
|
||||
}
|
||||
}
|
||||
return allCollisions;
|
||||
}
|
||||
};
|
Loading…
Reference in a new issue