4

I'm using JQuery.path to move an object along a bezier curve. When the item is clicked, I can determine the start and end points. How do I calculate the angle and length to make the element move from point A to point B on an arc that's 1/4 of a circle intersecting the start and end point?

I essentially want it to move along a curve that never dips lower than the starting y position and never to the left of the end x position.

    var path = {
        start: {
            x: currentLeft,
            y: currentTop,
            angle: ????, //Don't know how to calculate this
            length: ???? //Don't know how to calculate this
        },
        end: {
            x: endLeft,
            y: endTop,
            angle: ????, //Don't know how to calculate this
            length: ???? //Don't know how to calculate this
        }
    };

    jQuery(myElement).animate(
        {
            path: new jQuery.path.bezier(path)
        }
    );

Approx. what I want: enter image description here

Approx what I'm getting (they're dipping too low): enter image description here

Don Rhummy
  • 24,730
  • 42
  • 175
  • 330
  • [relevant if not quite a duplicate](http://stackoverflow.com/a/27863181/497418). – zzzzBov Jul 20 '15 at 23:14
  • @zzzzBov That is relevant but not the same question or an answer. I still need to know how to calculate the angle. Please see my attached image. – Don Rhummy Jul 20 '15 at 23:24
  • This probably requires some [math](http://math.stackexchange.com/questions/830413/calculating-the-arc-length-of-a-circle-segment) so you should specify the radius of the circle. – aug Jul 20 '15 at 23:27
  • @aug Knowing the start and end points, how do I calculate the radius? – Don Rhummy Jul 20 '15 at 23:30
  • @DonRhummy What do angle and length mean? I looked in the documentation but it's not useful at all. – Derek 朕會功夫 Jul 20 '15 at 23:39
  • @Derek朕會功夫 `angle` is the angle of the control point from the line joining the start and end. `length` is the distance from the point to it’s control point as a ratio of the distance from start to end. https://foxparker.wordpress.com/2009/09/22/bezier-curves-and-arcs-in-jquery/ – Don Rhummy Jul 20 '15 at 23:46
  • @DonRhummy That's the exact same wording in the documentation. Anyway, wouldn't it be easier by [doing it this way](http://jsfiddle.net/94pj2pgk/)? – Derek 朕會功夫 Jul 20 '15 at 23:48

2 Answers2

3

A generalised solution is slightly tricky because it must handle diagonal movements in each of four diagonal directions, and horizontal, and vertical.

First, you need a couple of utility functions :

function r2d(x) {
    /* radians to degrees */
    return x * 180 / Math.PI;
}
function smaller(x, y) {
    /* returns the closer of x|y to zero */
    var x_ = Math.abs(x);
    var y_ = Math.abs(y);
    return (Math.min(x_, y_) === x_) ? x : y;
}

Now a main function, anim, accepts a jQuery object (containing the element of interest) and an end object (with properties .left and .top ).

function anim($el, end) {
    var current = $el.position();

    var slope1 = (end.top - current.top) / (end.left - current.left);
    var slope2 = 1 / slope1;
    var endAngle = r2d(Math.atan(smaller(slope1, slope2)));
    var startAngle = -endAngle;
    var length = 1/3; //Vary between 0 and 1 to affect the path's curvature. Also, try >1 for an interesting effect.

    //For debugging
    $("#endAngle").text(endAngle);
    $("#startAngle").text(startAngle);
    $("#length").text(length);

    var path = {
        start: {
            x: current.left,
            y: current.top,
            angle: startAngle,
            length: length
        },
        end: {
            x: end.left,
            y: end.top,
            angle: endAngle,
            length: length
        }
    };

    $el.animate({ path: new jQuery.path.bezier(path) });
}

The calculation of endAngle is pretty simple for each individual case (the four diagonals, horizontal and vertical) but slightly tricky for a generalised solution. It took me a while to develop something that worked in all cases.

DEMO

Roamer-1888
  • 19,138
  • 5
  • 33
  • 44
1

If the "what you want" is really what you need, i.e. 90 degree departure and arrivals, then we can solve this problem pretty much instantly:

p_start = { X:..., Y:... }
p_end = { X:..., Y:... }
dx = p_end.X - p_start.X
dy = p_end.Y - p_start.Y
control_1 = { X: p_start.X, Y: p_start.Y + 0.55228 * dy }
control_2 = { X: p_end.X - 0.55228 * dx, Y: p_end.Y }

And done. What we've basically done is pretend that the start and end points lie on a circle, and computer the control points such that the resulting Bezier curve has minimal error wrt the quarter circular arc.

In terms of angles: The departure from start is always at angle π/2, and the arrival at the end points is always at angle 0.

Mike 'Pomax' Kamermans
  • 49,297
  • 16
  • 112
  • 153
  • And how would I use that with jQuery animation? – Don Rhummy Jul 20 '15 at 23:54
  • 1
    there are *lots* of jQuery animation libraries, and *lots* of ones that act on purely CSS (which are generally faster). I've added the departure and arrival angles, but without a jsbin from your end for me to change the values in, it'll be hard to say in any definitive way. – Mike 'Pomax' Kamermans Jul 20 '15 at 23:55
  • What I mean is, since you use different names (control, dx/dy), which of those are the start angle/end angle and what would I put for length? (Assuming I'm using the original JQuery.path) – Don Rhummy Jul 21 '15 at 00:04
  • `control_1` and `control_2` are the actual Bezier curve coordinates, which define the curve's length. If we just want angles and a length, then let's exploit basic geometry: we're approximating a quarter circle, and the length of a quarter circle is `π·radius/2` (because it's the circumference divided by 4, which is 2·π·radius/4, which is π·radius/2), so our curve length is somewhere between `π·min(dx,dy)/2` and `π·max(dx,dy)/2`. But we've now turned it into a fairly straight forward geometry problem, with lots of explanations on the internet. – Mike 'Pomax' Kamermans Jul 21 '15 at 01:36
  • @Mike'Pomax'Kamermans, Bezier splines definitely affect the curve's length but not directly, if I understand correctly, in the way you suggest above. Also, jQuery.path expects the angles of its splines to be defined relative to the straight line joining start and end points, not relative to the X-Y axes. Both points make a big impact on how to code a working solution. – Roamer-1888 Jul 23 '15 at 16:04
  • They don't "affect" the length, they *define* the curve length. Bezier curves cannot represent circular arcs (there will always be an error) so you can guess the length by creating the best arc approximation (which uses the 0.55228 value) and then using the real circle's length, but it'll be wrong a few decimal places in, at best. As both author of http://pomax.github.io/bezierinfo and as professional webdev, my recommendation would be "stop using this plugin and get a better one instead". Requiring someone to supply a Bezier curve's length is a horrible solution. – Mike 'Pomax' Kamermans Jul 23 '15 at 16:29
  • Mike, I totally agree, requiring the curve length to be supplied would indeed be a horrible solution. Fortunately, the plugin doesn't require that. The two `length` params define the distances of two "control points" (direction handles) from the start/end points. This way of defining a curve is the programatic equivalent of the [pen tool in Illustrator/Photoshop/Flash and others](https://www.youtube.com/watch?v=umoK473vTAY). I expect that people coming from the computer graphics world find jQuery.path.bezier more intuitive than those from the math world. – Roamer-1888 Jul 24 '15 at 09:52
  • @Roamer-1888 now I'm confused: those lengths were already in the original answer, because those are the values that are used to define the Bezier control points. Control point 1 is `0.55228 * dy` down from the start, and control point 2 is `0.55228 * dx` to the left of the end. – Mike 'Pomax' Kamermans Jul 25 '15 at 00:24
  • Mike, the plugin requires the control points to be defined as vectors from the start/end points, not (x,y) coordinates. `(.length,.angle)` is equivalent to `(r,θ)` in mathematics. As far as I can tell, what you have done in your answer is very clever but it doesn't deliver the parameters in the form the plugin expects. – Roamer-1888 Jul 25 '15 at 11:56
  • And the plugin's `(.x,.y)` are the start/end points themselves, ie the origins from which the control points are vectored. – Roamer-1888 Jul 25 '15 at 12:03
  • right, so what's the problem? If you have two coordinates as *points* converting that to a *vector* is trivial. The angle is `atan2(dy,dx)` and the length is `sqrt(dx*dx + dy*dy)`, this is explained in seconds with a simple google search. If what matters is the shape of the curve, then first you figure out what the coordinates should be, because *those define the curve*, and then you trivially convert the coordinate pairs you found to whatever form you need. – Mike 'Pomax' Kamermans Jul 25 '15 at 17:27
  • Yes, but we're not writing the plugin, we're calling one that's already written! – Roamer-1888 Jul 26 '15 at 08:31
  • ... yes, and you're saying you need the start and end, and the length from start-to-c1, and c2-to-end. Those values are *fully described* by the coordinates I gave. – Mike 'Pomax' Kamermans Jul 26 '15 at 16:44