mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2024-12-29 09:22:22 -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/PaperScope.js');
|
||||||
/*#*/ include('core/PaperScopeItem.js');
|
/*#*/ include('core/PaperScopeItem.js');
|
||||||
|
|
||||||
|
/*#*/ include('util/CollisionDetection.js');
|
||||||
/*#*/ include('util/Formatter.js');
|
/*#*/ include('util/Formatter.js');
|
||||||
/*#*/ include('util/Numerical.js');
|
/*#*/ include('util/Numerical.js');
|
||||||
/*#*/ include('util/UID.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,
|
function getIntersections(curves1, curves2, include, matrix1, matrix2,
|
||||||
_returnFirst) {
|
_returnFirst) {
|
||||||
|
var epsilon = Numerical.GEOMETRIC_EPSILON;
|
||||||
var self = !curves2;
|
var self = !curves2;
|
||||||
if (self)
|
if (self)
|
||||||
curves2 = curves1;
|
curves2 = curves1;
|
||||||
var length1 = curves1.length,
|
var length1 = curves1.length,
|
||||||
length2 = curves2.length,
|
length2 = curves2.length,
|
||||||
values2 = [],
|
values1 = new Array(length1),
|
||||||
arrays = [],
|
values2 = self ? values1 : new Array(length2),
|
||||||
locations,
|
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);
|
|
||||||
for (var i = 0; i < length1; i++) {
|
for (var i = 0; i < length1; i++) {
|
||||||
var curve1 = curves1[i],
|
var v = curves1[i].getValues(matrix1);
|
||||||
values1 = self ? values2[i] : curve1.getValues(matrix1),
|
values1[i] = v;
|
||||||
path1 = curve1.getPath();
|
}
|
||||||
// NOTE: Due to the nature of getCurveIntersections(), we use
|
if (!self) {
|
||||||
// separate location arrays per path1, to make sure the circularity
|
for (var i = 0; i < length2; i++) {
|
||||||
// checks are not getting confused by locations on separate paths.
|
var v = curves2[i].getValues(matrix2);
|
||||||
// The separate arrays are then flattened in the end.
|
values2[i] = v;
|
||||||
if (path1 !== current) {
|
|
||||||
current = path1;
|
|
||||||
locations = [];
|
|
||||||
arrays.push(locations);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
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) {
|
if (self) {
|
||||||
// First check for self-intersections within the same curve.
|
// 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.
|
// Check for intersections with potentially intersecting curves.
|
||||||
// For self-intersection, we can start at i + 1 instead of 0.
|
var collisions1 = boundsCollisions[index1];
|
||||||
for (var j = self ? i + 1 : 0; j < length2; j++) {
|
if (collisions1) {
|
||||||
// There might be already one location from the above
|
for (var j = 0; j < collisions1.length; j++) {
|
||||||
// self-intersection check:
|
// There might be already one location from the above
|
||||||
if (_returnFirst && locations.length)
|
// self-intersection check:
|
||||||
return locations;
|
if (_returnFirst && locations.length)
|
||||||
getCurveIntersections(values1, values2[j], curve1, curves2[j],
|
return locations;
|
||||||
locations, include);
|
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;
|
return locations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -156,19 +156,61 @@ PathItem.inject(new function() {
|
||||||
collect(paths1);
|
collect(paths1);
|
||||||
if (paths2)
|
if (paths2)
|
||||||
collect(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
|
// Propagate the winding contribution. Winding contribution of
|
||||||
// curves does not change between two crossings.
|
// curves does not change between two crossings.
|
||||||
// First, propagate winding contributions for curve chains starting
|
// First, propagate winding contributions for curve chains starting
|
||||||
// in all crossings:
|
// in all crossings:
|
||||||
for (var i = 0, l = crossings.length; i < l; i++) {
|
for (var i = 0, l = crossings.length; i < l; i++) {
|
||||||
propagateWinding(crossings[i]._segment, _path1, _path2, curves,
|
propagateWinding(crossings[i]._segment, _path1, _path2,
|
||||||
operator);
|
horCurvesMap, vertCurvesMap, operator);
|
||||||
}
|
}
|
||||||
for (var i = 0, l = segments.length; i < l; i++) {
|
for (var i = 0, l = segments.length; i < l; i++) {
|
||||||
var segment = segments[i],
|
var segment = segments[i],
|
||||||
inter = segment._intersection;
|
inter = segment._intersection;
|
||||||
if (!segment._winding) {
|
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.
|
// See if all encountered segments in a path are overlaps.
|
||||||
if (!(inter && inter._overlap))
|
if (!(inter && inter._overlap))
|
||||||
|
@ -186,7 +228,6 @@ PathItem.inject(new function() {
|
||||||
return !!operator[w];
|
return !!operator[w];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return createResult(paths, true, path1, path2, options);
|
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
|
// Get reference to the first, largest path and insert it
|
||||||
// already.
|
// already.
|
||||||
first = sorted[0];
|
first = sorted[0];
|
||||||
|
// create lookup containing potentially overlapping path bounds
|
||||||
|
var collisions = CollisionDetection.findItemBoundsCollisions(sorted,
|
||||||
|
null, Numerical.GEOMETRIC_EPSILON);
|
||||||
if (clockwise == null)
|
if (clockwise == null)
|
||||||
clockwise = first.isClockwise();
|
clockwise = first.isClockwise();
|
||||||
// Now determine the winding for each path, from large to small.
|
// Now determine the winding for each path, from large to small.
|
||||||
for (var i = 0; i < length; i++) {
|
for (var i = 0; i < length; i++) {
|
||||||
var path1 = sorted[i],
|
var path1 = sorted[i],
|
||||||
entry1 = lookup[path1._id],
|
indicesI = collisions[i];
|
||||||
point = path1.getInteriorPoint(),
|
if (indicesI) {
|
||||||
containerWinding = 0;
|
var entry1 = lookup[path1._id],
|
||||||
for (var j = i - 1; j >= 0; j--) {
|
point = null; // interior point, only get it if required
|
||||||
var path2 = sorted[j];
|
containerWinding = 0;
|
||||||
// As we run through the paths from largest to smallest, for
|
for (var j = indicesI.length - 1; j >= 0; j--) {
|
||||||
// any current path, all potentially containing paths have
|
if (indicesI[j] < i) {
|
||||||
// already been processed and their orientation fixed.
|
point = point || path1.getInteriorPoint();
|
||||||
// To achieve correct orientation of contained paths based
|
var path2 = sorted[indicesI[j]];
|
||||||
// on winding, we have to find one containing path with
|
// As we run through the paths from largest to
|
||||||
// different "insideness" and set opposite orientation.
|
// smallest, for any current path, all potentially
|
||||||
if (path2.contains(point)) {
|
// containing paths have already been processed and
|
||||||
var entry2 = lookup[path2._id];
|
// their orientation fixed. To achieve correct
|
||||||
containerWinding = entry2.winding;
|
// orientation of contained paths based on winding,
|
||||||
entry1.winding += containerWinding;
|
// we have to find one containing path with
|
||||||
entry1.container = entry2.exclude ? entry2.container
|
// different "insideness" and set opposite orientation.
|
||||||
: path2;
|
if (path2.contains(point)) {
|
||||||
break;
|
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
|
// 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
|
* @param {Point} point the location for which to determine the winding
|
||||||
* contribution
|
* 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
|
* 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
|
* @param {Boolean} [dir=false] the direction in which to determine the
|
||||||
* winding contribution, `false`: in x-direction, `true`: in y-direction
|
* winding contribution, `false`: in x-direction, `true`: in y-direction
|
||||||
* @param {Boolean} [closed=false] determines how areas should be closed
|
* @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
|
* well as an indication whether the point was situated on the contour
|
||||||
* @private
|
* @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
|
// Determine the index of the abscissa and ordinate values in the curve
|
||||||
// values arrays, based on the direction:
|
// values arrays, based on the direction:
|
||||||
var ia = dir ? 1 : 0, // the abscissa index
|
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.
|
// again with flipped direction and return that result instead.
|
||||||
return !dontFlip && a > paL && a < paR
|
return !dontFlip && a > paL && a < paR
|
||||||
&& Curve.getTangent(v, t)[dir ? 'x' : 'y'] === 0
|
&& Curve.getTangent(v, t)[dir ? 'x' : 'y'] === 0
|
||||||
&& getWinding(point, curves, !dir, closed, true);
|
&& getWinding(point, curvesH, curvesV, !dir, closed, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCurve(v) {
|
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
|
// Here we try to determine the most likely winding number contribution
|
||||||
// for the curve-chain starting with this segment. Once we have enough
|
// for the curve-chain starting with this segment. Once we have enough
|
||||||
// confidence in the winding contribution, we can propagate it until the
|
// 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)
|
if (wind.quality > winding.quality)
|
||||||
winding = wind;
|
winding = wind;
|
||||||
break;
|
break;
|
||||||
|
@ -1077,7 +1142,8 @@ PathItem.inject(new function() {
|
||||||
* @return {Number} the winding number
|
* @return {Number} the winding number
|
||||||
*/
|
*/
|
||||||
_getWinding: function(point, dir, closed) {
|
_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 = [],
|
matched = [],
|
||||||
count = 0;
|
count = 0;
|
||||||
ok = true;
|
ok = true;
|
||||||
|
var boundsOverlaps = CollisionDetection.findBoundsOverlaps(paths1, paths2, Numerical.GEOMETRIC_EPSILON);
|
||||||
for (var i1 = length1 - 1; i1 >= 0 && ok; i1--) {
|
for (var i1 = length1 - 1; i1 >= 0 && ok; i1--) {
|
||||||
var path1 = paths1[i1];
|
var path1 = paths1[i1];
|
||||||
ok = false;
|
ok = false;
|
||||||
for (var i2 = length2 - 1; i2 >= 0 && !ok; i2--) {
|
var pathBoundsOverlaps = boundsOverlaps[i1];
|
||||||
if (path1.compare(paths2[i2])) {
|
if (pathBoundsOverlaps) {
|
||||||
if (!matched[i2]) {
|
for (var i2 = pathBoundsOverlaps.length - 1; i2 >= 0 && !ok; i2--) {
|
||||||
matched[i2] = true;
|
if (path1.compare(paths2[pathBoundsOverlaps[i2]])) {
|
||||||
count++;
|
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