Related
I want to create a function that allows user to draw rectangle from 3 points (blue points):
The first two points will form an edge (width).
The third point will determine the height.
I need this custom draw function in leaflet, however leaflet's default rectangle is created with 2 diagonal points: https://leafletjs.com/reference-1.5.0.html#rectangle.
I need to calculate one of the green points but my small brain can't seem to figure it out :P
PS/EDIT: The rectangle might be angled, this is what makes it challenging
Leaflet's default rectangle is created with 2 diagonal points
[...]
The rectangle might be angled, this is what makes it challenging
Be aware that Leaflet's L.Rectangle is created from a L.LatLngBounds, a bounding box in which the edges are implicitly aligned to the coordinate grid. Don't use bounding boxes, and rely on L.Polygon instead, providing all four points.
Let A and B be the points of the base of the rectangle, and C be the point on the top. Assuming all points are Javascript structures of the form {x: Number, y: Number}, and assuming that you're working in an euclidean plane (i.e. not on the surface of a geoid),
First, calculate the distance from a point to a line defined by the other two points, i.e. the distance from C to the line defined by AB. Let that be distance (note that it's equal to "height" in your diagram):
var distance = Math.abs(
(A.y - B.y) * C.x - (A.x - B-x) * C.y + B.x * A.y - B.y * A.x )
) / Math.sqrt(
Math.pow(B.y - A.y, 2) + Math.pow(B.x - A.x, 2)
);
Then, let AB be the vector from A to B
var AB = { x: B.x - A.x, y: B.y - A.y };
(Note that the length of AB is equal to "width" in your diagram)
Calculate a unit vector perpendicular to AB:
var perpendicular = {x: -AB.y, y: AB.x}
var perpendicularSize = Math.sqrt(AB.x * AB.x + AB.y * AB.y);
var unit = {x: perpendicular.x / perpendicularSize, y: perpendicular.y / perpendicularSize};
Multiply that unit vector by the distance from C to AB to get the vectors for the "sides" of your rectangle:
var sideVector = { x: unit.x * distance, y: unit.y * distance };
...and create new points D and E by offsetting A and B by the vector for the sides of the rectangle:
var D = { x: A.x + sideVector.x, y: A.y + sideVector.y };
var E = { x: B.x + sideVector.x, y: B.y + sideVector.y };
...And your rectangle is now defined by the points ABDE. Note that C is in the line defined by points DE.
For Python:
#Given three points fit a rectangle
import math
a,b,c=[1,4],[3,4],[3,10] #Sample numbers
#Distance from dot C to line AB (https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line#Line_defined_by_two_points)
distance= abs((b[1]-a[1])*c[0] - (b[0]-a[0])*c[1] + b[0]*a[1] - b[1]*a[0] ) / math.sqrt((b[1]-a[1])**2 + (b[0]-a[0])**2)
print(distance)
#let AB be the vector from A to B
ab=[b[0]-a[0],b[1]-a[1]]
#unit vector perpendicular to AB (https://en.wikipedia.org/wiki/Unit_vector)
perpendicularSize = math.sqrt(ab[0]**2+ab[1]**2)
unit = [-ab[1]/perpendicularSize ,ab[0]/perpendicularSize]
#Multiply that unit vector by the distance from C to AB to get the vectors for the "sides" of your rectangle
sideVector = [unit[0]*distance,unit[1]*distance]
#create new points D and E by offsetting A and B by the vector for the sides of the rectangle
d=[a[0]+sideVector[0],a[1]+sideVector[1]]
e=[b[0]+sideVector[0],b[1]+sideVector[1]]
print(e,d) #[3.0, 10.0] [1.0, 10.0]
Assuming coordinates of those 3 dots are provided,
- 2, 5 (first point)
- 5, 5 (second point)
- x, 8 (third point)
the first green will take x from one of the first two point let's say from the first one => 2, and y from the third point => 8
so the first green point is at 2,8
the second will take x from the second point => 5, and y from the third point => 8
so the second green point is at 5,8
I'm not sure if I understand the answer correctly tho.
assuming edge1 = [x1,y1]
, edge2 = [x2,y2]
def calculate_edges (edge1,edge2,height)
edge3 [0] = edge1[0] //x3
edge3 [1] = edge1[1] + height //y3
edge4 [0] = edge2[0] //x4
edge4 [1] = edge2[1] + height //y4
return edge3,edge4
I'm trying to obtain a 4x4 projection matrix that transforms a point in the world to the display coordinates.
Having a pixel (x, y) and the corresponding z-value (from the zbuffer), I obtain its 3D world coordinates with vtkWorldPointPicker class. Let's denote the result by x.
According to documentation, I can compute the view coordinates of the world point by applying the matrix GetCompositeProjectionTransformMatrix to x. Next, I'm using the transformation from the view to the initial display coordinates by using the code found in vtkViewport::ViewToDisplay (*):
dx = (v[0] + 1.0) * (sizex*(v[2]-v[0])) / 2.0 + sizex*v[0];
dy = (v[1] + 1.0) * (sizey*(v[3]-v[1])) / 2.0 + sizey*v[1];
where sizex and sizey are the width and height of the image in pixels, and v are the computed view coordinates.
Unfortunately, the values I get back do not match the original:
display [0, 0, 0.716656] // x,y-pixel coordinates and the zbuffer
x = [0.0255492, -0.0392383, 0.00854707] // world coordinates (using vtkWorldPointPicker)
// camera->GetCompositeProjectionTransformMatrix
P = [
-1.84177 0 0 0
0 1.20317 1.39445 0
0 -757.134 653.275 -9.9991
0 -0.757126 0.653268 0 ]
v = [-0.0470559, -0.0352919, 25.2931, 0.0352919] // P*x
a = [7697.18, -0.597848] // using (*)
Is this approach (in general) correct, or is there a more conventional way to do this? Thanks for any help.
Edit: the provided snippet from vtkViewport::ViewToDisplay is incorrect. It should read:
dx = (v[0] + 1.0) * (sizex*(vp[2]-vp[0])) / 2.0 + sizex*vp[0];
dy = (v[1] + 1.0) * (sizey*(vp[3]-vp[1])) / 2.0 + sizey*vp[1];
Note, that v refers to the normalised view coordinates, vp is the viewport (by default, vp := [0, 0, 1, 1])!
The conversion is indeed valid, although there might be built-in ways to obtain the final matrix.
Assuming only one (default) viewport is used, the matrix converting the view into display coordinates is:
M = [X/2, 0, 0, X/2,
0, Y/2, 0, Y/2,
0, 0, 1, 0,
0, 0, 0, 1]
where X and Y is the width and height of the image in pixels.
Hence, given a point x in the world coordinates, the display coordinates in homogeneous form are:
c = M * P * x;
where P is the CompositeProjectionTransformMatrix. After normalising (c[i] /= c[3], i = 0,1,2) we arrive at the original pixel values.
I have to find max number of rectangles from a given set of coordinates.
Consider the following coordinates are given in an X Y coordinate system
3 10,
3 8,
3 6,
3 4,
3 0,
6 0,
6 4,
6 8,
6 10,
How can I find if the following coordinates form a rectangle (3,0) (3,4) (6,4) (6,0)
Running time constraint: 0.1 sec
Thank you
Separate your points in lists of 'y' coordinate, grouped by 'x' coordinate. In your case you would have two sorted lists:
3: [0,4,6,8,10]
6: [0,4,8,10]
Doing the intersection of both lists you get: [0,4,8,10]
Any two of those would form a rectangle:
[0,4] => (3,0), (3,4), (6,0), (6,4)
[0,8] => (3,0), (3,8), (6,0), (6,8)
[4,8] => (3,4), (3,8), (6,4), (6,8)
...
This solution only works for orthogonal rectangles, this is, with sides parallel to x,y axis.
For every pair of points, say (x1, y1) and (x2, y2) consider it to be the diagonal of some rectangle. If there exist points (x1, y2) and (x2, y1) in the initial set then we have found our rectangle. It should be noted that there will exist 2 diagonals which will represent the same rectangle so we divide the final answer by 2.
This will work only for rectangles parallel to x-axis or y-axis.
PseudoCode C++:
answer = 0;
set<pair<int, int>> points;
for(auto i=points.begin(); i!=std::prev(points.end()); i++)
{
for(auto j=i+1; j!=points.end(); j++)
{
pair<int, int> p1 = *i;
pair<int, int> p2 = *j;
if(p1.first == p2.first || p1.second == p2.second)
continue;
pair<int, int> p3 = make_pair(p1.first, p2.second);
pair<int, int> p4 = make_pair(p2.first, p1.second);
if(points.find(p3) != points.end() && points.find(p4) != points.end())
++answer;
}
}
return answer/2;
To check if 4 points form a rectangle:
for every two points calculate the distance. store all in array of floats.
sort the array.
you will have a[0] = a[1], a[2] = a[3], a[4] = a[5]
How can I find if the following coordinates form a rectangle
Check whether the difference vectors are orthogonal, i.e. have dot product zero.
This does not check whether these coordinates are included in your list. It also does not check whether the rectangle is aligned with the coordinate axes, which would be a far simpler problem.
If you want to find all rectangles in your input, you could do the above check for all quadruples. If that is inacceptable for performance reasons, then you should update your question, indicating what kind of problem size and performance constrainst you are facing.
My humble submission
I assume number of optimizations is possible.
My approach is to
Traverse over every point
Check which all points are there just above this point and then store Y coordinates of that points which form a line
Next time when I find again the same Y coordinate that means we have found 1 rectangle
Keep traversing all other points again doing the same thing.
My solution runs in O(n^2) but this will only be rectangle which are parallel to X or Y axis.
Here is my code for above approach:
def getRectangleCount(coordinate):
n = len(coordinate)
y_count = dict()
ans = 0
for i in range(n):
x, y = coordinate[i]
for j in range(n):
dx = coordinate[j][0]
dy = coordinate[j][1]
if y < dy and x == dx:
ans += y_count.get((y, dy), 0)
y_count[(y, dy)] = y_count.get((y, dy), 0) + 1
return ans
coordinate = [[3, 10], [3, 8], [3, 6], [3, 4], [3, 0], [6, 0], [6, 4], [6, 8], [6, 10]]
print(getRectangleCount(coordinate))
Here's a solution that finds all unique rectangles (not only those parallel to the x or y-axis) in a given list of coordinate points in O(n^4) time.
Pseudocode:
// checks if two floating point numbers are equal within a given
// error to avoid rounding issues
bool is_equal(double a, double b, double e) {
return abs(a - b) < e;
}
// computes the dot product of the vectors ab and ac
double dot_product(Point a, Point b, Point c) {
return (b.x - a.x) * (c.x - a.x) + (b.y - a.y) * (c.y - a.y);
}
// find all rectangles in a given set of coordinate points
List<Rectangle> find_rectangles(List<Point> points) {
List<Rectangle> rectangles;
// sort points in ascending order by first comparing x than y value
sort(points);
for (int a = 0; a < points.size(); ++a)
for (int b = a + 1; a < points.size(); ++b)
for (int c = b + 1; c < points.size(); ++c)
for (int d = c + 1; d < points.size(); ++d)
// check all angles
if (is_equal(dot_product(points[a], points[b], points[c]), 0.0, 1e-7) &&
is_equal(dot_product(points[b], points[a], points[d]), 0.0, 1e-7) &&
is_equal(dot_product(points[d], points[b], points[c]), 0.0, 1e-7) &&
is_equal(dot_product(points[c], points[a], points[d]), 0.0, 1e-7))
// found rectangle
rectangles.add(new Rectangle(points[a], points[c], points[d], points[b]));
return rectangles;
}
Explanation:
For a given set of points A, B, C, D to define a rectangle we can check if all angles are 90° meaning all non-parallel sides are orthogonal.
Since we can check this property just by the dot product being 0 this is the most efficient way (instead of having to do square-root calculations for computing side lengths).
Sorting the points first avoids checking the same set of points multiple times due to permutations.
I have linear systems of inequalities in 3 variables and I'd like to plot these regions.
Ideally, I'd like something that looks like objects in PolyhedronData. I tried RegionPlot3D, but the results are visually poor and too polygon-heavy to rotate in real time
Here's what I mean, the code below generates 10 sets of linear constraints and plots them
randomCons := Module[{},
hadamard = KroneckerProduct ## Table[{{1, 1}, {1, -1}}, {3}];
invHad = Inverse[hadamard];
vs = Range[8];
m = mm /# vs;
sectionAnchors = Subsets[vs, {1, 7}];
randomSection :=
Mean[hadamard[[#]] & /# #] & /#
Prepend[RandomChoice[sectionAnchors, 3], vs]; {p0, p1, p2, p3} =
randomSection;
section =
Thread[m ->
p0 + {x, y, z}.Orthogonalize[{p1 - p0, p2 - p0, p3 - p0}]];
And ## Thread[invHad.m >= 0 /. section]
];
Table[RegionPlot3D ## {randomCons, {x, -3, 3}, {y, -3, 3}, {z, -3,
3}}, {10}]
Any suggestions?
Update: Incorporating suggestions below, here's the version I ended up using to plot feasible region of a system of linear inequalities
(* Plots feasible region of a linear program in 3 variables, \
specified as cons[[1]]>=0,cons[[2]]>=0,...
Each element of cons must \
be an expression of variables x,y,z only *)
plotFeasible3D[cons_] :=
Module[{maxVerts = 20, vcons, vertCons, polyCons},
(* find intersections of all triples of planes and get rid of \
intersections that aren't points *)
vcons = Thread[# == 0] & /# Subsets[cons, {3}];
vcons = Select[vcons, Length[Reduce[#]] == 3 &];
(* Combine vertex constraints with inequality constraints and find \
up to maxVerts feasible points *)
vertCons = Or ## (And ### vcons);
polyCons = And ## Thread[cons >= 0];
verts = {x, y, z} /.
FindInstance[polyCons && vertCons, {x, y, z}, maxVerts];
ComputationalGeometry`Methods`ConvexHull3D[verts,
Graphics`Mesh`FlatFaces -> False]
]
Code for testing
randomCons := Module[{},
hadamard = KroneckerProduct ## Table[{{1, 1}, {1, -1}}, {3}];
invHad = Inverse[hadamard];
vs = Range[8];
m = mm /# vs;
sectionAnchors = Subsets[vs, {1, 7}];
randomSection :=
Mean[hadamard[[#]] & /# #] & /#
Prepend[RandomChoice[sectionAnchors, 3], vs]; {p0, p1, p2, p3} =
randomSection;
section =
Thread[m ->
p0 + {x, y, z}.Orthogonalize[{p1 - p0, p2 - p0, p3 - p0}]];
And ## Thread[invHad.m >= 0 /. section]
];
Table[plotFeasible3D[List ## randomCons[[All, 1]]], {50}];
Here is a small program that seems to do what you want:
rstatic = randomCons; (* Call your function *)
randeq = rstatic /. x_ >= y_ -> x == y; (* make a set of plane equations
replacing the inequalities by == *)
eqset = Subsets[randeq, {3}]; (* Make all possible subsets of 3 planes *)
(* Now find the vertex candidates
Solving the sets of three equations *)
vertexcandidates =
Flatten[Table[Solve[eqset[[i]]], {i, Length[eqset]}], 1];
(* Now select those candidates
satisfying all the original equations *)
vertex = Union[Select[vertexcandidates, rstatic /. # &]];
(* Now use an UNDOCUMENTED Mathematica
function to plot the surface *)
gr1 = ComputationalGeometry`Methods`ConvexHull3D[{x, y, z} /. vertex];
(* Your plot follows *)
gr2 = RegionPlot3D[rstatic,
{x, -3, 3}, {y, -3, 3}, {z, -3, 3},
PerformanceGoal -> "Quality", PlotPoints -> 50]
Show[gr1,gr2] (*Show both Graphs superposed *)
The result is:
Downside: the undocumented function is not perfect. When the face is not a triangle, it will show a triangulation:
Edit
There is an option to get rid of the foul triangulation
ComputationalGeometry`Methods`ConvexHull3D[{x, y, z} /. vertex,
Graphics`Mesh`FlatFaces -> False]
does the magic. Sample:
Edit 2
As I mentioned in a comment, here are two sets of degenerate vertex generated by your randomCons
{{x -> Sqrt[3/5]},
{x -> -Sqrt[(5/3)] + Sqrt[2/3] y},
{x -> -Sqrt[(5/3)], y -> 0},
{y -> -Sqrt[(2/5)], x -> Sqrt[3/5]},
{y -> 4 Sqrt[2/5], x -> Sqrt[3/5]}
}
and
{{x -> -Sqrt[(5/3)] + (2 z)/Sqrt[11]},
{x -> Sqrt[3/5], z -> 0},
{x -> -Sqrt[(5/3)], z -> 0},
{x -> -(13/Sqrt[15]), z -> -4 Sqrt[11/15]},
{x -> -(1/Sqrt[15]), z -> 2 Sqrt[11/15]},
{x -> 17/(3 Sqrt[15]), z -> -((4 Sqrt[11/15])/3)}
}
Still trying to see how to cope gently with those ...
Edit 3
This code is not general enough for the full problem, but eliminates the cylinder degenerancy problem for your sample data generator. I used the fact that the pathogenic cases are always cylinders with their axis paralell to one of the coordinate axis, and then used RegionPlot3D to plot them.
I'm not sure if this will be useful for your general case :(.
For[i = 1, i <= 160, i++,
rstatic = randomCons;
r[i] = rstatic;
s1 = Reduce[r[i], {x, y, z}] /. {x -> var1, y -> var2, z -> var3};
s2 = Union[StringCases[ToString[FullForm[s1]], "var" ~~ DigitCharacter]];
If [Dimensions#s2 == {3},
(randeq = rstatic /. x_ >= y_ -> x == y;
eqset = Subsets[randeq, {3}];
vertexcandidates = Flatten[Table[Solve[eqset[[i]]], {i, Length[eqset]}], 1];
vertex = Union[Select[vertexcandidates, rstatic /. # &]];
a[i] = ComputationalGeometry`Methods`ConvexHull3D[{x, y, z} /. vertex,
Graphics`Mesh`FlatFaces -> False, Axes -> False, PlotLabel -> i])
,
a[i] = RegionPlot3D[s1, {var1, -2, 2}, {var2, -2, 2}, {var3, -2, 2},
Axes -> False, PerformanceGoal -> "Quality", PlotPoints -> 50,
PlotLabel -> i, PlotStyle -> Directive[Yellow, Opacity[0.5]],
Mesh -> None]
];
]
GraphicsGrid[Table[{a[i], a[i + 1], a[i + 2]}, {i, 1, 160, 4}]]
Here you can find an image of the generated output, the degenerated cases (all cylinders) are in transparent yellow
HTH!
A triplet chosen from your set of inequalities will generally determine a point obtained by solving the corresponding triplet of equations. I believe that you want the convex hull of this set of points. You can generate this like so.
cons = randomCons; (* Your function *)
eqs = Apply[Equal, List ### Subsets[cons, {3}], {2}];
sols = Flatten[{x, y, z} /. Table[Solve[eq, {x, y, z}], {eq, eqs}], 1];
pts = Select[sols, And ## (NumericQ /# #) &];
ComputationalGeometry`Methods`ConvexHull3D[pts]
Of course, some triplets might actually be underdetermined and lead to lines or evan a whole plane. Thus the code will issue a complaint in those cases.
This appeared to work in the few random cases that I tried but, as Yaro points out, it doesn't work in all. The following picture will illustrate exactly why:
{p0, p1, p2,
p3} = {{1, 0, 0, 0, 0, 0, 0, 0}, {1, 1/2, -(1/2), 0, -(1/2), 0,
0, -(1/2)}, {1, 0, 1/2, 1/2, 0, 0, -(1/2), 1/2}, {1, -(1/2), 1/2,
0, -(1/2), 0, 0, -(1/2)}};
hadamard = KroneckerProduct ## Table[{{1, 1}, {1, -1}}, {3}];
invHad = Inverse[hadamard];
vs = Range[8];
m = mm /# vs;
section =
Thread[m ->
p0 + {x, y, z}.Orthogonalize[{p1 - p0, p2 - p0, p3 - p0}]];
cons = And ## Thread[invHad.m >= 0 /. section];
eqs = Apply[Equal, List ### Subsets[cons, {3}], {2}];
sols = Flatten[{x, y, z} /. Table[Solve[eq, {x, y, z}], {eq, eqs}],
1]; // Quiet
pts = Select[sols, And ## (NumericQ /# #) &];
ptPic = Graphics3D[{PointSize[Large], Point[pts]}];
regionPic =
RegionPlot3D[cons, {x, -2, 2}, {y, -2, 2}, {z, -2, 2},
PlotPoints -> 40];
Show[{regionPic, ptPic}]
Thus, there are points that are ultimately cut off by the plane defined by some other constraint. Here's one (I'm sure terribly inefficient) way to find the ones you want.
regionPts = regionPic[[1, 1]];
nf = Nearest[regionPts];
trimmedPts = Select[pts, Norm[# - nf[#][[1]]] < 0.2 &];
trimmedPtPic = Graphics3D[{PointSize[Large], Point[trimmedPts]}];
Show[{regionPic, trimmedPtPic}]
Thus, you could use the convex hull of trimmedPts. This ultimately depends on the result of RegionPlot and you might need to ramp of the value of PlotPoints to make it more reliable.
Googling about a bit reveals the concept of a feasibility region in linear programming. This seems to be exactly what you're after.
Mark
Seeing all the previous answers; what is wrong with using the build-in function
RegionPlot3D, e.g.
RegionPlot3D[ 2*y+3*z <= 5 && x+y+2*z <= 4 && x+2*y+3*z <= 7 &&
x >= 0 && y >= 0 && z >= 0,
{x, 0, 4}, {y, 0, 5/2}, {z, 0, 5/3} ]
How can I find the point B(t) along a cubic Bezier curve that is closest to an arbitrary point P in the plane?
I've written some quick-and-dirty code that estimates this for Bézier curves of any degree. (Note: this is pseudo-brute force, not a closed-form solution.)
Demo: http://phrogz.net/svg/closest-point-on-bezier.html
/** Find the ~closest point on a Bézier curve to a point you supply.
* out : A vector to modify to be the point on the curve
* curve : Array of vectors representing control points for a Bézier curve
* pt : The point (vector) you want to find out to be near
* tmps : Array of temporary vectors (reduces memory allocations)
* returns: The parameter t representing the location of `out`
*/
function closestPoint(out, curve, pt, tmps) {
let mindex, scans=25; // More scans -> better chance of being correct
const vec=vmath['w' in curve[0]?'vec4':'z' in curve[0]?'vec3':'vec2'];
for (let min=Infinity, i=scans+1;i--;) {
let d2 = vec.squaredDistance(pt, bézierPoint(out, curve, i/scans, tmps));
if (d2<min) { min=d2; mindex=i }
}
let t0 = Math.max((mindex-1)/scans,0);
let t1 = Math.min((mindex+1)/scans,1);
let d2ForT = t => vec.squaredDistance(pt, bézierPoint(out,curve,t,tmps));
return localMinimum(t0, t1, d2ForT, 1e-4);
}
/** Find a minimum point for a bounded function. May be a local minimum.
* minX : the smallest input value
* maxX : the largest input value
* ƒ : a function that returns a value `y` given an `x`
* ε : how close in `x` the bounds must be before returning
* returns: the `x` value that produces the smallest `y`
*/
function localMinimum(minX, maxX, ƒ, ε) {
if (ε===undefined) ε=1e-10;
let m=minX, n=maxX, k;
while ((n-m)>ε) {
k = (n+m)/2;
if (ƒ(k-ε)<ƒ(k+ε)) n=k;
else m=k;
}
return k;
}
/** Calculate a point along a Bézier segment for a given parameter.
* out : A vector to modify to be the point on the curve
* curve : Array of vectors representing control points for a Bézier curve
* t : Parameter [0,1] for how far along the curve the point should be
* tmps : Array of temporary vectors (reduces memory allocations)
* returns: out (the vector that was modified)
*/
function bézierPoint(out, curve, t, tmps) {
if (curve.length<2) console.error('At least 2 control points are required');
const vec=vmath['w' in curve[0]?'vec4':'z' in curve[0]?'vec3':'vec2'];
if (!tmps) tmps = curve.map( pt=>vec.clone(pt) );
else tmps.forEach( (pt,i)=>{ vec.copy(pt,curve[i]) } );
for (var degree=curve.length-1;degree--;) {
for (var i=0;i<=degree;++i) vec.lerp(tmps[i],tmps[i],tmps[i+1],t);
}
return vec.copy(out,tmps[0]);
}
The code above uses the vmath library to efficiently lerp between vectors (in 2D, 3D, or 4D), but it would be trivial to replace the lerp() call in bézierPoint() with your own code.
Tuning the Algorithm
The closestPoint() function works in two phases:
First, calculate points all along the curve (uniformly-spaced values of the t parameter). Record which value of t has the smallest distance to the point.
Then, use the localMinimum() function to hunt the region around the smallest distance, using a binary search to find the t and point that produces the true smallest distance.
The value of scans in closestPoint() determines how many samples to use in the first pass. Fewer scans is faster, but increases the chances of missing the true minimum point.
The ε limit passed to the localMinimum() function controls how long it continues to hunt for the best value. A value of 1e-2 quantizes the curve into ~100 points, and thus you can see the points returned from closestPoint() popping along the line. Each additional decimal point of precision—1e-3, 1e-4, …—costs about 6-8 additional calls to bézierPoint().
After lots of searching I found a paper that discusses a method for finding the closest point on a Bezier curve to a given point:
Improved Algebraic Algorithm On Point
Projection For Bezier Curves, by
Xiao-Diao Chen, Yin Zhou, Zhenyu Shu,
Hua Su, and Jean-Claude Paul.
Furthermore, I found Wikipedia and MathWorld's descriptions of Sturm sequences useful in understanding the first part of the algoritm, as the paper itself isn't very clear in its own description.
Seeing as the other methods on this page seem to be approximation, this answer will provide a simple numerical solution. It is a python implementation depending on the numpy library to supply Bezier class. In my tests, this approach performed about three times better than my brute-force implementation (using samples and subdivision).
Look at the interactive example here.
Click to enlarge.
I used basic algebra to solve this minimal problem.
Start with the bezier curve equation.
B(t) = (1 - t)^3 * p0 + 3 * (1 - t)^2 * t * p1 + 3 * (1 - t) * t^2 * p2 + t^3 * p3
Since I'm using numpy, my points are represented as numpy vectors (matrices). This means that p0 is a one-dimensional, e.g. (1, 4.2). If you are handling two floating point variables you probably need mutliple equations (for x and y): Bx(t) = (1-t)^3*px_0 + ...
Convert it to a standard form with four coefficients.
You can write the four coefficients by expanding the original equation.
The distance from a point p to the curve B(t) can be calculated using the pythagorean theorem.
Here a and b are the two dimensions of our points x and y. This means that the squared distance D(t) is:
I'm not calculating a square root just now, because it is enough if we compare relative squared distances. All following equation will refer to the squared distance.
This function D(t) describes the distance between the graph and the points. We are interested in the minima in the range of t in [0, 1]. To find them, we have to derive the function twice. The first derivative of the distance function is a 5 order polynomial:
The second derivative is:
A desmos graph let's us examine the different functions.
D(t) has its local minima where d'(t) = 0 and d''(t) >= 0. This means, that we have to find the t for d'(t) = 0'.
black: the bezier curve, green: d(t), purple: d'(t), red:d''(t)
Find the roots of d'(t). I use the numpy library, which takes the coefficients of a polynomial.
dcoeffs = np.stack([da, db, dc, dd, de, df])
roots = np.roots(dcoeffs)
Remove the imaginary roots (keep only the real roots) and remove any roots which are < 0 or > 1. With a cubic bezier, there will probably be about 0-3 roots left.
Next, check the distances of each |B(t) - pt| for each t in roots. Also check the distances for B(0) and B(1) since start and end of the Bezier curve could be the closest points (although they aren't local minima of the distance function).
Return the closest point.
I am attaching the class for the Bezier in python. Check the github link for a usage example.
import numpy as np
# Bezier Class representing a CUBIC bezier defined by four
# control points.
#
# at(t): gets a point on the curve at t
# distance2(pt) returns the closest distance^2 of
# pt and the curve
# closest(pt) returns the point on the curve
# which is closest to pt
# maxes(pt) plots the curve using matplotlib
class Bezier(object):
exp3 = np.array([[3, 3], [2, 2], [1, 1], [0, 0]], dtype=np.float32)
exp3_1 = np.array([[[3, 3], [2, 2], [1, 1], [0, 0]]], dtype=np.float32)
exp4 = np.array([[4], [3], [2], [1], [0]], dtype=np.float32)
boundaries = np.array([0, 1], dtype=np.float32)
# Initialize the curve by assigning the control points.
# Then create the coefficients.
def __init__(self, points):
assert isinstance(points, np.ndarray)
assert points.dtype == np.float32
self.points = points
self.create_coefficients()
# Create the coefficients of the bezier equation, bringing
# the bezier in the form:
# f(t) = a * t^3 + b * t^2 + c * t^1 + d
#
# The coefficients have the same dimensions as the control
# points.
def create_coefficients(self):
points = self.points
a = - points[0] + 3*points[1] - 3*points[2] + points[3]
b = 3*points[0] - 6*points[1] + 3*points[2]
c = -3*points[0] + 3*points[1]
d = points[0]
self.coeffs = np.stack([a, b, c, d]).reshape(-1, 4, 2)
# Return a point on the curve at the parameter t.
def at(self, t):
if type(t) != np.ndarray:
t = np.array(t)
pts = self.coeffs * np.power(t, self.exp3_1)
return np.sum(pts, axis = 1)
# Return the closest DISTANCE (squared) between the point pt
# and the curve.
def distance2(self, pt):
points, distances, index = self.measure_distance(pt)
return distances[index]
# Return the closest POINT between the point pt
# and the curve.
def closest(self, pt):
points, distances, index = self.measure_distance(pt)
return points[index]
# Measure the distance^2 and closest point on the curve of
# the point pt and the curve. This is done in a few steps:
# 1 Define the distance^2 depending on the pt. I am
# using the squared distance because it is sufficient
# for comparing distances and doesn't have the over-
# head of an additional root operation.
# D(t) = (f(t) - pt)^2
# 2 Get the roots of D'(t). These are the extremes of
# D(t) and contain the closest points on the unclipped
# curve. Only keep the minima by checking if
# D''(roots) > 0 and discard imaginary roots.
# 3 Calculate the distances of the pt to the minima as
# well as the start and end of the curve and return
# the index of the shortest distance.
#
# This desmos graph is a helpful visualization.
# https://www.desmos.com/calculator/ktglugn1ya
def measure_distance(self, pt):
coeffs = self.coeffs
# These are the coefficients of the derivatives d/dx and d/(d/dx).
da = 6*np.sum(coeffs[0][0]*coeffs[0][0])
db = 10*np.sum(coeffs[0][0]*coeffs[0][1])
dc = 4*(np.sum(coeffs[0][1]*coeffs[0][1]) + 2*np.sum(coeffs[0][0]*coeffs[0][2]))
dd = 6*(np.sum(coeffs[0][0]*(coeffs[0][3]-pt)) + np.sum(coeffs[0][1]*coeffs[0][2]))
de = 2*(np.sum(coeffs[0][2]*coeffs[0][2])) + 4*np.sum(coeffs[0][1]*(coeffs[0][3]-pt))
df = 2*np.sum(coeffs[0][2]*(coeffs[0][3]-pt))
dda = 5*da
ddb = 4*db
ddc = 3*dc
ddd = 2*dd
dde = de
dcoeffs = np.stack([da, db, dc, dd, de, df])
ddcoeffs = np.stack([dda, ddb, ddc, ddd, dde]).reshape(-1, 1)
# Calculate the real extremes, by getting the roots of the first
# derivativ of the distance function.
extrema = np_real_roots(dcoeffs)
# Remove the roots which are out of bounds of the clipped range [0, 1].
# [future reference] https://stackoverflow.com/questions/47100903/deleting-every-3rd-element-of-a-tensor-in-tensorflow
dd_clip = (np.sum(ddcoeffs * np.power(extrema, self.exp4)) >= 0) & (extrema > 0) & (extrema < 1)
minima = extrema[dd_clip]
# Add the start and end position as possible positions.
potentials = np.concatenate((minima, self.boundaries))
# Calculate the points at the possible parameters t and
# get the index of the closest
points = self.at(potentials.reshape(-1, 1, 1))
distances = np.sum(np.square(points - pt), axis = 1)
index = np.argmin(distances)
return points, distances, index
# Point the curve to a matplotlib figure.
# maxes ... the axes of a matplotlib figure
def plot(self, maxes):
import matplotlib.path as mpath
import matplotlib.patches as mpatches
Path = mpath.Path
pp1 = mpatches.PathPatch(
Path(self.points, [Path.MOVETO, Path.CURVE4, Path.CURVE4, Path.CURVE4]),
fc="none")#, transform=ax.transData)
pp1.set_alpha(1)
pp1.set_color('#00cc00')
pp1.set_fill(False)
pp2 = mpatches.PathPatch(
Path(self.points, [Path.MOVETO, Path.LINETO , Path.LINETO , Path.LINETO]),
fc="none")#, transform=ax.transData)
pp2.set_alpha(0.2)
pp2.set_color('#666666')
pp2.set_fill(False)
maxes.scatter(*zip(*self.points), s=4, c=((0, 0.8, 1, 1), (0, 1, 0.5, 0.8), (0, 1, 0.5, 0.8),
(0, 0.8, 1, 1)))
maxes.add_patch(pp2)
maxes.add_patch(pp1)
# Wrapper around np.roots, but only returning real
# roots and ignoring imaginary results.
def np_real_roots(coefficients, EPSILON=1e-6):
r = np.roots(coefficients)
return r.real[abs(r.imag) < EPSILON]
Depending on your tolerances. Brute force and being accepting of error. This algorithm could be wrong for some rare cases. But, in the majority of them it will find a point very close to the right answer and the results will improve the higher you set the slices. It just tries each point along the curve at regular intervals and returns the best one it found.
public double getClosestPointToCubicBezier(double fx, double fy, int slices, double x0, double y0, double x1, double y1, double x2, double y2, double x3, double y3) {
double tick = 1d / (double) slices;
double x;
double y;
double t;
double best = 0;
double bestDistance = Double.POSITIVE_INFINITY;
double currentDistance;
for (int i = 0; i <= slices; i++) {
t = i * tick;
//B(t) = (1-t)**3 p0 + 3(1 - t)**2 t P1 + 3(1-t)t**2 P2 + t**3 P3
x = (1 - t) * (1 - t) * (1 - t) * x0 + 3 * (1 - t) * (1 - t) * t * x1 + 3 * (1 - t) * t * t * x2 + t * t * t * x3;
y = (1 - t) * (1 - t) * (1 - t) * y0 + 3 * (1 - t) * (1 - t) * t * y1 + 3 * (1 - t) * t * t * y2 + t * t * t * y3;
currentDistance = Point.distanceSq(x,y,fx,fy);
if (currentDistance < bestDistance) {
bestDistance = currentDistance;
best = t;
}
}
return best;
}
You can get a lot better and faster by simply finding the nearest point and recursing around that point.
public double getClosestPointToCubicBezier(double fx, double fy, int slices, int iterations, double x0, double y0, double x1, double y1, double x2, double y2, double x3, double y3) {
return getClosestPointToCubicBezier(iterations, fx, fy, 0, 1d, slices, x0, y0, x1, y1, x2, y2, x3, y3);
}
private double getClosestPointToCubicBezier(int iterations, double fx, double fy, double start, double end, int slices, double x0, double y0, double x1, double y1, double x2, double y2, double x3, double y3) {
if (iterations <= 0) return (start + end) / 2;
double tick = (end - start) / (double) slices;
double x, y, dx, dy;
double best = 0;
double bestDistance = Double.POSITIVE_INFINITY;
double currentDistance;
double t = start;
while (t <= end) {
//B(t) = (1-t)**3 p0 + 3(1 - t)**2 t P1 + 3(1-t)t**2 P2 + t**3 P3
x = (1 - t) * (1 - t) * (1 - t) * x0 + 3 * (1 - t) * (1 - t) * t * x1 + 3 * (1 - t) * t * t * x2 + t * t * t * x3;
y = (1 - t) * (1 - t) * (1 - t) * y0 + 3 * (1 - t) * (1 - t) * t * y1 + 3 * (1 - t) * t * t * y2 + t * t * t * y3;
dx = x - fx;
dy = y - fy;
dx *= dx;
dy *= dy;
currentDistance = dx + dy;
if (currentDistance < bestDistance) {
bestDistance = currentDistance;
best = t;
}
t += tick;
}
return getClosestPointToCubicBezier(iterations - 1, fx, fy, Math.max(best - tick, 0d), Math.min(best + tick, 1d), slices, x0, y0, x1, y1, x2, y2, x3, y3);
}
In both cases you can do the quad just as easily:
x = (1 - t) * (1 - t) * x0 + 2 * (1 - t) * t * x1 + t * t * x2; //quad.
y = (1 - t) * (1 - t) * y0 + 2 * (1 - t) * t * y1 + t * t * y2; //quad.
By switching out the equation there.
While the accepted answer is right, and you really can figure out the roots and compare that stuff. If you really just need to find the nearest point on the curve, this will do it.
In regard to Ben in the comments. You cannot short hand the formula in the many hundreds of control point range, like I did for cubic and quad forms. Because the amount demanded by each new addition of a bezier curve means that you build a Pythagorean pyramids for them, and we're basically dealing with even more and more massive strings of numbers. For quad you go 1, 2, 1, for cubic you go 1, 3, 3, 1. You end up building bigger and bigger pyramids, and end up breaking it down with Casteljau's algorithm, (I wrote this for solid speed):
/**
* Performs deCasteljau's algorithm for a bezier curve defined by the given control points.
*
* A cubic for example requires four points. So it should get at least an array of 8 values
*
* #param controlpoints (x,y) coord list of the Bezier curve.
* #param returnArray Array to store the solved points. (can be null)
* #param t Amount through the curve we are looking at.
* #return returnArray
*/
public static float[] deCasteljau(float[] controlpoints, float[] returnArray, float t) {
int m = controlpoints.length;
int sizeRequired = (m/2) * ((m/2) + 1);
if (returnArray == null) returnArray = new float[sizeRequired];
if (sizeRequired > returnArray.length) returnArray = Arrays.copyOf(controlpoints, sizeRequired); //insure capacity
else System.arraycopy(controlpoints,0,returnArray,0,controlpoints.length);
int index = m; //start after the control points.
int skip = m-2; //skip if first compare is the last control point.
for (int i = 0, s = returnArray.length - 2; i < s; i+=2) {
if (i == skip) {
m = m - 2;
skip += m;
continue;
}
returnArray[index++] = (t * (returnArray[i + 2] - returnArray[i])) + returnArray[i];
returnArray[index++] = (t * (returnArray[i + 3] - returnArray[i + 1])) + returnArray[i + 1];
}
return returnArray;
}
You basically need to use the algorithm directly, not just for the calculation of the x,y which occur on the curve itself, but you also need it to perform actual and proper Bezier subdivision algorithm (there are others but that is what I'd recommend), to calculate not just an approximation as I give by dividing it into line segments, but of the actual curves. Or rather the polygon hull that is certain to contain the curve.
You do this by using the above algorithm to subdivide the curves at the given t. So T=0.5 to cut the curves in half (note 0.2 would cut it 20% 80% through the curve). Then you index the various points at the side of the pyramid and the other side of the pyramid as built from the base. So for example in cubic:
9
7 8
4 5 6
0 1 2 3
You would feed the algorithm 0 1 2 3 as control points, then you would index the two perfectly subdivided curves at 0, 4, 7, 9 and 9, 8, 6, 3. Take special note to see that these curves start and end at the same point. and the final index 9 which is the point on the curve is used as the other new anchor point. Given this you can perfectly subdivide a bezier curve.
Then to find the closest point you'd want to keep subdividing the curve into different parts noting that it is the case that the entire curve of a bezier curve is contained within the hull of the control points. Which is to say if we turn points 0, 1, 2, 3 into a closed path connecting 0,3 that curve must fall completely within that polygon hull. So what we do is define our given point P, then we continue to subdivide curves until such time as we know that the farthest point of one curve is closer than the closest point of another curve. We simply compare this point P to all the control and anchor points of the curves. And discard any curve from our active list whose closest point (whether anchor or control) is further away than the farthest point of another curve. Then we subdivide all the active curves and do this again. Eventually we will have very subdivided curves discarding about half each step (meaning it should be O(n log n)) until our error is basically negligible. At this point we call our active curves the closest point to that point (there could be more than one), and note that the error in that highly subdivided bit of curve is basically equal to a point. Or simply decide the issue by saying whichever of the two anchor point is closest is the closest point to our point P. And we know the error to a very specific degree.
This, though, requires that we actually have a robust solution and do a certainly correct algorithm and correctly find the tiny fraction of curve that will certainly be the closest point to our point. And it should be relatively fast still.
There is also DOM SVG specific implementations of the closest point algorithms from Mike Bostock:
https://bl.ocks.org/mbostock/8027637
https://bl.ocks.org/mbostock/8027835
A solution to this problem would be to get all the possible points on the bezier curve and compare each distance. The number of points can be controlled by the detail variable.
Here is a implementation made in Unity (C#):
public Vector2 FindNearestPointOnBezier(Bezier bezier, Vector2 point)
{
float detail = 100;
List<Vector2> points = new List<Vector2>();
for (float t = 0; t < 1f; t += 1f / detail)
{
// this function can be exchanged for any bezier curve
points.Add(Functions.CalculateBezier(bezier.a, bezier.b, bezier.c, bezier.d, t));
}
Vector2 closest = Vector2.zero;
float minDist = Mathf.Infinity;
foreach (Vector2 p in points)
{
// use sqrMagnitude as it is faster
float dist = (p - point).sqrMagnitude;
if (dist < minDist)
{
minDist = dist;
closest = p;
}
}
return closest;
}
Note that the Bezier class just holds 4 points.
Probably not the best way as it can become very slow depending on the detail.