14

I am writing a music display program and need to draw a 'slur' between two notes. A slur is a curved line linking two notes - just to be clear.

enter image description here

I know the note positions and calculate where the start and end points of the curve should be - Start point A and End point B.

I now need to obtain the offset C, given the distance required, for use within a quadratic curve. This is where my, very, limited knowledge and understanding of maths formulae comes in.

I have indeed looked here in SO for my answer, but the solutions proposed either do not work or I am too limited to code them correctly.

Can someone help me with the calculation, in a NON mathematical form ?

legends2k
  • 31,634
  • 25
  • 118
  • 222
Simon
  • 2,208
  • 4
  • 32
  • 47

3 Answers3

28

Given the line segment AB, you can find the midpoint, say M, using the famous midpoint formula (A + B)/2. Now calculate the vector from B to A:

p = <p.x, p.y> = AB

Rotate it about the origin by 90° counter-clockwise to get the perpendicular vector

n = <n.x, n.y> = < ‒ p.y, p.x >

Normalise it:

n = <n.x, n.y> / ‖n‖ where ‖n‖ = √(n.x² + n.y²) is the Euclidean Norm or length

C = L(t) = M + t n

Using this equation -- parametric form of a line -- you can find any number of points along the perpendicular line (in the direction of n). t is the distance of the obtained point, C, from M. When t = 0, you get M back, when t = 1, you get a point 1 unit away from M along n and so on. This also works for negative values of t, where the points obtained will be on the opposite side of AB i.e. towards the note. Since t can be a decimal number, you can play with it by changing its values to get the desired distance and direction of the obtained point from M.

Code, since you said you're not interested in the math jargon ;)

vec2d calculate_perp_point(vec2d A, vec2d B, float distance)
{
   vec2d M = (A + B) / 2;
   vec2d p = A - B;
   vec2d n = (-p.y, p.x);
   int norm_length = sqrt((n.x * n.x) + (n.y * n.y));
   n.x /= norm_length;
   n.y /= norm_length;
   return (M + (distance * n));
}

This is just pseudo code, since I'm not sure of the vector math library you are using for your project.

Boldface variables above are 2-d vectors; uppercase letters denote points and lowercase ones are vectors with no position

legends2k
  • 31,634
  • 25
  • 118
  • 222
  • Thankyou for your time @legends2k. If I have understood correctly, each operation done on your vec2d object applies to the x and y values equally ? – Simon Jun 19 '13 at 15:54
  • @Simon: Yes, when you see `p = B - A`, what it really means is `(p.x, p.y) = (B.x - A.x, B.y - A.y)` and the operation of a scalar and a vector like `distance * n` is really `(distance * n.x, distance * n.y)`. Happy to help :) – legends2k Jun 19 '13 at 15:56
  • @Simon: If it'd helped you, I wouldn't mind if you accept the answer :) – legends2k Jun 19 '13 at 16:03
  • thank you. I've been testing after having to restart my system. – Simon Jun 19 '13 at 18:45
  • one further question, can you tell me how I could adapt this so as to have two control points for a cubic bezier ? Should I open another question ? – Simon Jun 19 '13 at 18:48
  • @Simon Not required. `C = L(t) = M + t n` is nothing but a point M is pushed by the vector `n` which is `t` units long. So instead of M you can use any point along the line to push it to the required distance. – legends2k Jun 19 '13 at 19:15
  • @Simon: So `C1` can be `M1 + t n` and `C2` can be `M2 + t n`; instead of `M = (A + B) / 2` which is halfway between A and B, M1 and M2 may be quarter and three-quarters between A and B i.e. `M1 = .75 B + .25 A` and `M2 = .25B + .75A`. In general any point along A and B can be obtained using the formula `M = (1 - t) B + tA` where t is the distance from B. – legends2k Jun 19 '13 at 19:21
  • @Simon: I've changed the calculation of p from `B - A` to `A - B` which is appropriate since a vector from B to A would be given by the latter and not the former. – legends2k Jun 19 '13 at 19:23
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/32091/discussion-between-simon-and-legends2k) – Simon Jun 20 '13 at 16:46
3

I took legends2k excellent answer and converted to Java on Android. This might help someone save some time.

private PointF getPerpendicularPoint(int startX, int startY, int stopX, int stopY, float distance)
{
    PointF M = new PointF((startX + stopX) / 2, (startY + stopY) / 2);
    PointF p = new PointF(startX - stopX, startY - stopY);
    PointF n = new PointF(-p.y, p.x);
    int norm_length = (int) Math.sqrt((n.x * n.x) + (n.y * n.y));
    n.x /= norm_length;
    n.y /= norm_length;
    return new PointF(M.x + (distance * n.x), M.y + (distance * n.y));
}
David Boyd
  • 6,501
  • 3
  • 21
  • 13
2

And here’s a Swift version:

func pointNormalToLine(startPoint: CGPoint, endPoint: CGPoint, distance: CGFloat) -> CGPoint {

    let midpoint = CGPoint(x: (startPoint.x + endPoint.x) / 2, y: (startPoint.y + endPoint.y) / 2)
    let p = CGPoint(x: startPoint.x - endPoint.x, y: startPoint.y - endPoint.y)
    var n = CGPoint(x: -p.y, y: p.x)
    let norm_length = sqrt((n.x * n.x) + (n.y * n.y))
    n.x /= norm_length
    n.y /= norm_length
    return CGPoint(x: midpoint.x + (distance * n.x), y: midpoint.y + (distance * n.y))
}
John Cromie
  • 181
  • 2
  • 4