10

I wish to determine when a point (mouse position) in on, or near a curve defined by a series of B-Spline control points.

The information I will have for the B-Spline is the list of n control points (in x,y coordinates). The list of control points can be of any length (>= 4) and define a B-spline consisting of (n−1)/3 cubic Bezier curves. The Bezier curves are are all cubic. I wish to set a parameter k,(in pixels) of the distance defined to be "near" the curve. If the mouse position is within k pixels of the curve then I need to return true, otherwise false.

Is there an algorithm that gives me this information. Any solution does not need to be precise - I am working to a tolerance of 1 pixel (or coordinate).

I have found the following questions seem to offer some help, but do not answer my exact question. In particular the first reference seems to be a solution only for 4 control points, and does not take into account the nearness factor I wish to define.

Position of a point relative to a Bezier curve

Intersection between bezier curve and a line segment

EDIT: An example curve:

 e, 63.068, 127.26   
    29.124, 284.61   
    25.066, 258.56   
    20.926, 212.47   
        34, 176  
    38.706, 162.87  
    46.556, 149.82  
    54.393, 138.78 

The description of the format is: "Every edge is assigned a pos attribute, which consists of a list of 3n + 1 locations. These are B-spline control points: points p0, p1, p2, p3 are the first Bezier spline, p3, p4, p5, p6 are the second, etc. Points are represented by two integers separated by a comma, representing the X and Y coordinates of the location specified in points (1/72 of an inch). In the pos attribute, the list of control points might be preceded by a start point ps and/or an end point pe. These have the usual position representation with a "s," or "e," prefix, respectively."

EDIT2: Further explanation of the "e" point (and s if present).

In the pos attribute, the list of control points might be preceded by a start point ps and/or an end point pe. These have the usual position representation with a "s," or "e," prefix, respectively. A start point is present if there is an arrow at p0. In this case, the arrow is from p0 to ps, where ps is actually on the node’s boundary. The length and direction of the arrowhead is given by the vector (ps −p0). If there is no arrow, p0 is on the node’s boundary. Similarly, the point pe designates an arrow at the other end of the edge, connecting to the last spline point.

Community
  • 1
  • 1
Chris Walton
  • 2,513
  • 3
  • 25
  • 39
  • How are you working with the bezier curve? What are you writing an app for? Most of the time with UI elements you can define a mouseover or something, are you writing a UI element from scratch? What language? – jcolebrand Nov 25 '10 at 06:47
  • Cubic Bezier curve ALWAYS depends on exactly 4 control points. Maybe you want to make a http://en.wikipedia.org/wiki/Spline_(mathematics) from several curves or something? – skalee Nov 25 '10 at 10:55
  • @drachenstern I am effectively writing a UI element from scratch. I am writing an graphical editor, where the various elements of the editor are visual representations of the underlying objects and need to manipulate them in accordance with the rules governing the objects. – Chris Walton Nov 25 '10 at 15:25
  • @skalee - your answer made me look again at the definition of what I am working with. It is indeed a B-spline with n control points which define (n−1)/3 cubic Bezier curves. – Chris Walton Nov 25 '10 at 15:28
  • @Chris Could you drop a comment explaining the "e" point? – Dr. belisarius Nov 26 '10 at 18:45
  • @Belisarius See second edit above. – Chris Walton Nov 27 '10 at 01:34
  • 1
    @Chris ufff sounds pretty convolved. I'll read it carefully and see what I can get. Thanks. – Dr. belisarius Nov 27 '10 at 01:50
  • @Belisarius Any solution, and indeed my original question, can afford to ignore the e and s points. I suspect this will reduce considerably the convolutions to jump through. – Chris Walton Nov 27 '10 at 01:59

3 Answers3

11

You may do this analitically, but a little math is needed.

A Bezier curve can be expressed in terms of the Bernstein Basis. Here I'll use Mathematica, that provides good support for the math involved.

So if you have the points:

pts = {{0, -1}, {1, 1}, {2, -1}, {3, 1}};  

The eq. for the Bezier curve is:

f[t_] := Sum[pts[[i + 1]] BernsteinBasis[3, i, t], {i, 0, 3}];

Keep in mind that I am using the Bernstein basis for convenience, but ANY parametric representation of the Bezier curve would do.

Which gives:

alt text

Now to find the minimum distance to a point (say {3,-1}, for example) you have to minimize the function:

d[t_] := Norm[{3, -1} - f[t]];  

For doing that you need a minimization algorithm. I have one handy, so:

NMinimize[{d[t], 0 <= t <= 1}, t]  

gives:

 {1.3475, {t -> 0.771653}}  

And that is it.

HTH!

Edit Regarding your edit "B-spline with consisting of (n−1)/3 cubic Bezier curves."

If you constructed a piecewise B-spline representation you should iterate on all segments to find the minima. If you joined the pieces on a continuous parameter, then this same approach will do.

Edit

Solving your curve. I disregard the first point because I really didn't understand what it is.

I solved it using standard Bsplines instead of the mathematica features, for the sake of clarity.

Clear["Global`*"];
(*first define the points *)
pts = {{
        29.124, 284.61}, {
        25.066, 258.56}, {
        20.926, 212.47}, {
        34, 176}, {
        38.706, 162.87}, {
        46.556, 149.82}, {
        54.393, 138.78}};

(*define a bspline template function *)

b[t_, p0_, p1_, p2_, p3_] :=
                  (1-t)^3 p0 + 3 (1-t)^2 t p1 + 3 (1-t) t^2 p2 + t^3 p3;

(* define two bsplines *)
b1[t_] := b[t, pts[[1]], pts[[2]], pts[[3]], pts[[4]]];
b2[t_] := b[t, pts[[4]], pts[[5]], pts[[6]], pts[[7]]];

(* Lets see the curve *)

Show[Graphics[{Red, Point[pts], Green, Line[pts]}, Axes -> True], 
 ParametricPlot[BSplineFunction[pts][t], {t, 0, 1}]]

. ( Rotated ! for screen space saving )

alt text

(*Now define the distance from any point u to a point in our Bezier*)
d[u_, t_] := If[(0 <= t <= 1), Norm[u - b1[t]], Norm[u - b2[t - 1]]];

(*Define a function that find the minimum distance from any point u \
to our curve*)
h[u_] := NMinimize[{d[u, t], 0.0001 <= t <= 1.9999}, t];

(*Lets test it ! *)
Plot3D[h[{x, y}][[1]], {x, 20, 55}, {y, 130, 300}]

This plot is the (minimum) distance from any point in space to our curve (of course the value over the curve is zero):

alt text

Dr. belisarius
  • 60,527
  • 15
  • 115
  • 190
  • Can you at least deign to say whether the NMinimize is in a publicly available library or not? – Eric Nov 25 '10 at 13:20
  • 1
    @Eric Nminimize can be replaced by any math library function able to minimize functions in one variable. There are hordes out there. The OP doesn't specify environment and language, so implementation details are useless. – Dr. belisarius Nov 25 '10 at 13:34
  • 2
    @belisarius: On the contrary, it's always useful to give more details about your answer. It makes it easier for others to try it out. – Eric Nov 25 '10 at 13:54
  • 3
    @Eric Finding function maxima and minima is a problem much studied in CS. Almost any CS curricula in the world covers it. If you feel in the mood to learn more about it just google "function maxima minimma algorithm" or peep at http://mathworld.wolfram.com/topics/MaximaandMinima.html or post a question in SO. This specific question is about Bezier curves and not general math. – Dr. belisarius Nov 25 '10 at 14:21
  • Keep in mind that the subject may not be familiar to everyone reading your answer. Including a short header like "Here is an example using Mathematica" makes it easier for someone to understand it who doesn't have a ready knowledge of software libraries that can minimize functions in one variable. – Eric Nov 25 '10 at 15:32
  • @belisarius. This is the type of answer I was seeking. However, you will see from my comments and edits above that my original description was too imprecise. Is there a comparable function to your d[t_] that describes the B-Spline of Beziers? – Chris Walton Nov 25 '10 at 15:50
  • 1
    @Chris See edit "ANY parametric representation of the Bezier curve would do". I think you only need the minimization library function, you already have the rest – Dr. belisarius Nov 25 '10 at 15:56
  • @Chris If you post a set of B-splines in your representation I may try to show you a solved problem. – Dr. belisarius Nov 25 '10 at 16:01
  • @Chris please paste the thing in your question. I can't distinguish X and Y coordinates for the points – Dr. belisarius Nov 25 '10 at 19:56
  • @Belisarius - Thank you - this is a tremendous answer. Your efforts and completeness of answer are awesome. This will enable me to move forward substantially. Having marked your answer up, accepted it as the answer to my question, I can only repeat my gratitude for your work. – Chris Walton Nov 27 '10 at 07:43
  • @Chris you are most welcome. You just need a nice math library :) – Dr. belisarius Nov 27 '10 at 15:26
2

First, render the curve to a bitmap (black and white) with your favourite algorithm. Then, whenever you need, determine the nearest pixel to the mouse position using information from this question. You can modify the searching function so that it will return distance, so you can easilly compare it with your requirements. This method gives you the distance with tolerance of 1-2 pixels, which will do, I guess.

Community
  • 1
  • 1
atomizer
  • 4,458
  • 1
  • 17
  • 9
  • 1
    doesn't this seem too brute forceish? – fortran Nov 25 '10 at 15:12
  • This is a different way of looking at my problem. I was focussed on an analytic approach, and had not considered this alternative. – Chris Walton Nov 25 '10 at 15:32
  • Isn't this a bit silly? I mean you could draw a circle around each point you found in the curve on that same bitmap and then have no distance calculation at all after that, and you could solve for whether that point is on near the curve in O(1) time. Is this pixel black? – Tatarize Aug 17 '16 at 00:18
1

Definition: distance from a point to a line segment = distance from the original point to the closest point still on the segment.

Assumption: an algo to compute the distance from a point to a segment is known (e.g. compute the intercept with the segment of the normal to the segment passing through the original point. If the intersection is outside the segment, pick the closest end-point of the segment)

  1. use the deCasteljau algo and subdivide your cubics until getting to a good enough daisy-chain of linear segments. Supplementary info the "Bezier curve flattening" section
  2. consider the minimum of the distances between your point and the resulted segments as the distance from your point to the curve. Repeat for all the curves in your set.

Refinement at point 2: don't compute the actual distance, but the square of it, getting the minimum square distance is good enough - saves a sqrt call/segment.

Computation effort: empirically a cubic curve with a maximum extent (i.e. bounding box) of 200-300 results in about 64 line segments when flattened to a maximum tolerance of 0.5 (approx good enough for the naked eye).

  1. Each deCasteljau step requires 12 division-by-2 and 12 additions.
  2. Flatness evaluation - 8 multiplications + 4 additions (if using the TaxiCab distance to evaluate a distance)
  3. the evaluation of point-to-segment distance requires at max 12 multiplications and 11 additions - but this will be a rare case in the context of Bezier flattening, I'd expect an average of 6 multiplications and 9 additions.

So, assuming a very bad case (100 straight segments/cubic), you finish in finding your distance with a cost of approx 2600 multiplications + 2500 additions per considered cubic.

Disclaimers:

  1. don't ask me for a demonstration on the numbers in the computational effort evaluation above, I'll answer with "Use the source-code" (note: Java implementation).
  2. other approaches may be possible and maybe less costly.

Regards,

Adrian Colomitchi

Adrian Colomitchi
  • 3,974
  • 1
  • 14
  • 23