Implement simpler strategy to iteratively find nearest points on paths.

Based on method described on http://pomax.github.io/bezierinfo/
This commit is contained in:
Jürg Lehni 2013-05-05 22:41:06 -07:00
parent fa34ea5e5b
commit f2cd893f45
3 changed files with 132 additions and 0 deletions

View file

@ -0,0 +1,76 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>NearestPoint</title>
<link rel="stylesheet" href="../css/style.css">
<script type="text/javascript" src="../../dist/paper.js"></script>
<script type="text/paperscript" canvas="canvas">
window.performance = window.performance || {};
performance.now = (function() {
return performance.now ||
performance.mozNow ||
performance.msNow ||
performance.oNow ||
performance.webkitNow ||
function() { return new Date().getTime(); };
})();
var path = project.importSVG(document.getElementById('svg')).firstChild,
total = 0,
slower = 0;
function getNearest(point) {
var t1 = performance.now();
var loc1 = path.getNearestLocation(point);
t1 = performance.now() - t1;
var t2 = performance.now();
var loc2 = path._getNearestLocation(point);
t2 = performance.now() - t2;
total++;
if (t2 > t1) {
slower++;
console.log('slower', slower / total, t1, t2, point)
}
if (loc2.distance > loc1.distance) {
console.log('not precise');
}
return [loc1.point, loc2.point];
}
for (var i = 0; i < 1000; i++) {
getNearest(new Point(1028, 286));
}
/*
*/
function onMouseMove(event) {
var res = getNearest(event.point);
console.log(res.join());
var circle = new Path.Circle({
center: res[0],
radius: 2,
strokeColor: 'red'
}).removeOnMove();
var line = new Path.Line({
from: res[0],
to: event.point,
strokeColor: 'red'
}).removeOnMove();
var circle = new Path.Circle({
center: res[1],
radius: 4,
strokeColor: 'green'
}).removeOnMove();
}
</script>
</head>
<body>
<canvas id="canvas" resize></canvas>
<svg id="svg" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" xml:space="preserve" style="display: none;">
<path fill="none" stroke="#000000" stroke-miterlimit="10" d="M167,265c33.321,23.568,53,38,72.886,109.353
c22.516,80.792-52.284,163.541-74.631,92.135C95,242,393.045,246.871,399.326,431.434C402,510,286.311,474.239,289,369
c1.961-76.747,78.663-125.069,162.393-91.026C540,314,562.299,442.872,476.159,390.783C424.547,359.573,382.365,317.41,383,221"/>
</svg>
</body>
</html>

View file

@ -1246,6 +1246,48 @@ new function() { // Scope for methods that require numerical integration
Math.sqrt(minDist));
},
_getNearestLocation: function(point) {
var values = this.getValues(),
step = 1 / 100,
minDist = Infinity,
minT = 0;
for (var t = 0; t <= 1; t += step) {
var pt = Curve.evaluate(values, t, true, 0),
dist = point.getDistance(pt, true);
if (dist < minDist) {
minDist = dist;
minT = t;
}
}
function refine(t, dist, precision) {
if(precision < 0.0000001) return t;
// refinement
// smaller distances?
var t1 = t - precision;
if (t1 >= 0) {
var dist1 = point.getDistance(
Curve.evaluate(values, t1, true, 0), true);
if (dist1 < dist)
return refine(t1, dist1, precision);
}
var t2 = t + precision;
if (t2 <= 1) {
var dist2 = point.getDistance(
Curve.evaluate(values, t2, true, 0), true);
if (dist2 < dist)
return refine(t2, dist2, precision);
}
// larger distances
return refine(t, dist, precision / 2);
}
minT = refine(minT, minDist, step);
var pt = Curve.evaluate(values, minT, true, 0);
return new CurveLocation(this, minT, pt, null, point.getDistance(pt));
},
getNearestPoint: function(point) {
return this.getNearestLocation(point).getPoint();
}

View file

@ -1552,6 +1552,20 @@ var Path = this.Path = PathItem.extend(/** @lends Path# */{
return minLoc;
},
_getNearestLocation: function(point) {
var curves = this.getCurves(),
minDist = Infinity,
minLoc = null;
for (var i = 0, l = curves.length; i < l; i++) {
var loc = curves[i]._getNearestLocation(point);
if (loc._distance < minDist) {
minDist = loc._distance;
minLoc = loc;
}
}
return minLoc;
},
/**
* Returns the nearest point on the path to the specified point.
*