Distance from line segment

Told you mine was untested!

I’d missed an adjustment in the last calculation for when the line segment doesn’t point at the origin. Fixed now.

i mean interactive tween demos?and i wonder which websites or apps

@loopSpace yepp, that is correct now.

@LoopSpace I tried your code along with mine and all calculations are now the same. I wish I could say I understand what yours is doing, but I can’t. I’ll look it over and see if I can understand why it works.

@dave1707 Our code is doing the same thing, almost command-for-command. The key differences are:

  1. I like vectors, I do.
  2. Using vectors mean that I can use the inbuilt vector methods rather than rewriting them.
  3. There are a few steps in your code that aren’t strictly necessary.

@LoopSpace I spent a lot of time looking at dot product information on a lot of web pages, but I haven’t found anything that explaines how using dot products makes your code work. I got out some graph paper and drew a line segment and a point and tried doing the dot calculations, but none of the results really gave me any information I could relate to. The really confusing line was the v:rotate90:normalize(). I understand that it rotates the vector by 90 degrees and reduces the length to 1, but other than that, it didn’t relate to anything. Do you know of something that explaines why these work, because I haven’t found anything.

@dave1707 dot products are the heart of geometry. For this particular problem, the key piece of information is that if you have a unit vector, say u, then the orthogonal projection of a point, say p, onto the line in the direction of u is (p.u) u. If v is a vector which is not necessarily a unit vector, the formula is (p.v) v / (v.v).

So in your code, you first work out the vector along the line (this is the dx = x2 - x1 dy = y2 - y1 part). Then you work out the square of the length of this vector (temp = dx * dx + dy * dy); this is the same as the dot product of (dx,dy) with itself. Next, you work out the projection of (px,py) onto the line in the direction of (dx,dy) but as our line doesn’t pass through the origin, we have to adjust it slightly by projecting (px,py) - (x1,y1) so that everything is relative to one end of the line (this is the a=((px-x1)*dx+(py-y1)*dy)/temp part). So in dot product terms, your variable a contains (p.v) / (v.v). Since we’re dealing with a line segment, we test whether this a is between 0 and 1 and clamp it to that range (I’d probably do a a = math.max(math.min(a,1),0) instead of the conditionals here). Once you’ve clamped, you compute the point (x1,y1) + a (dx,dy) which is the closest point to (px,py) on the line segment. Finally, you return its distance from (px,py).

Mathematically, you compute

||p - Max(Min(p.(b - a)/(b-a).(b-a),1),0)(b-a)||

and now that I see it written like that, I see that it’s possible to make my code even shorter:

function lineDist(p,a,b)
    return p:dist(math.max(math.min(p:dot(b-a)/(b-a):lenSqr()),1),0)*(b-a))
end

Shorter, but probably not clearer!

As a postscript, I note that the formula above is better than the one I gave earlier involving Heaviside functions.

@LoopSpace Thanks for the above explanation. I still don’t understand it all, but I’m still trying to figure it out. I’m going to keep playing with this with graph paper until I can see what’s actually happening. I guess I’m a visual type of person so I need to see how this works.

@loopspace thanks for your additional explanations, but there seems to be an error in your last formula!

It wasn’t tested …

function lineDistB(p,a,b)
    return (p-a):dist(math.max(math.min((p-a):dot(b-a)/(b-a):lenSqr(),1),0)*(b-a))
end

and to get the coordinates of the actual closest point on the line:

    local c = math.max(math.min((p-a):dot(b-a)/(b-a):lenSqr(),1),0)*(b-a)+a

Full code:

-- LineDistance

-- Use this function to perform your initial setup
function setup()
    p = vec2(WIDTH/2,HEIGHT/2)
    a = vec2(100,100)
    b = vec2(400,800)
    parameter.watch("lineDist(p,a,b)")
    parameter.watch("lineDistB(p,a,b)")
end

-- This function gets called once every frame
function draw()
    pushStyle()
    -- This sets a dark background color 
    background(40, 40, 50)

    -- This sets the line thickness
    strokeWidth(5)

    -- Do your drawing here
    line(a.x,a.y,b.x,b.y)
    ellipse(p.x,p.y,5)
    noStroke()
    fill(255, 212, 0, 255)
    local c = math.max(math.min((p-a):dot(b-a)/(b-a):lenSqr(),1),0)*(b-a)+a
    ellipse(c.x,c.y,20)
    popStyle()
end

local touchpt,offset
function touched(t)
    local tp = vec2(t.x,t.y)
    if t.state == BEGAN then
        local dp,da,db = tp:distSqr(p),tp:distSqr(a),tp:distSqr(b)
        if dp < da and dp < db then
            touchpt = "p"
            offset = tp - p
        elseif da < db then
            touchpt = "a"
            offset = tp - a
        else
            touchpt = "b"
            offset = tp - b
        end
    else
        _G[touchpt] = tp - offset
    end
end

-- Returns the distance from the point p to the line segment from a to b
-- p,a,b are all vec2s
function lineDist(p,a,b)
    -- Vector along the direction of the line from a to b
    local v = b - a
    -- Project p onto the vector v and compare with the projections of a and b
    if v:dot(p) < v:dot(a) then
    -- The projection of p is lower than a, so return the distance from p to a
        return p:dist(a)
    elseif v:dot(p) > v:dot(b) then
    -- The projection of p is beyond b, so return the distance from p to b
        return p:dist(b)
    end
    -- The projection of p is between a and b, so we need to take the dot product with the orthogonal vector to v, so we rotate v and normalise it.
    v = v:rotate90():normalize()
    -- This is the distance from v to the line segment.
    return math.abs(v:dot(p)-v:dot(a))
end

function lineDistB(p,a,b)
    return (p-a):dist(math.max(math.min((p-a):dot(b-a)/(b-a):lenSqr(),1),0)*(b-a))
end

could u explain lineDistB?is it the best solution by vectors?

loopspace code copy with 1 code bloc only for my records (nothing new)

-- LineDistance

-- Use this function to perform your initial setup
function setup()
    p = vec2(WIDTH/2,HEIGHT/2)
    a = vec2(100,100)
    b = vec2(400,800)
    parameter.watch("lineDist(p,a,b)")
    parameter.watch("lineDistB(p,a,b)")
end

-- This function gets called once every frame
function draw()
    pushStyle()
    -- This sets a dark background color 
    background(40, 40, 50)

    -- This sets the line thickness
    strokeWidth(5)

    -- Do your drawing here
    line(a.x,a.y,b.x,b.y)
    ellipse(p.x,p.y,5)
    noStroke()
    fill(255, 212, 0, 255)
    local c = math.max(math.min((p-a):dot(b-a)/(b-a):lenSqr(),1),0)*(b-a)+a
    ellipse(c.x,c.y,20)
    popStyle()
end

local touchpt,offset
function touched(t)
    local tp = vec2(t.x,t.y)
    if t.state == BEGAN then
        local dp,da,db = tp:distSqr(p),tp:distSqr(a),tp:distSqr(b)
        if dp < da and dp < db then
            touchpt = "p"
            offset = tp - p
        elseif da < db then
            touchpt = "a"
            offset = tp - a
        else
            touchpt = "b"
            offset = tp - b
        end
    else
        _G[touchpt] = tp - offset
    end
end

-- Returns the distance from the point p to the line segment from a to b
-- p,a,b are all vec2s
function lineDist(p,a,b)
    -- Vector along the direction of the line from a to b
    local v = b - a
    -- Project p onto the vector v and compare with the projections of a and b
    if v:dot(p) < v:dot(a) then
    -- The projection of p is lower than a, so return the distance from p to a
        return p:dist(a)
    elseif v:dot(p) > v:dot(b) then
    -- The projection of p is beyond b, so return the distance from p to b
        return p:dist(b)
    end
    -- The projection of p is between a and b, so we need to take the dot product with the orthogonal vector to v, so we rotate v and normalise it.
    v = v:rotate90():normalize()
    -- This is the distance from v to the line segment.
    return math.abs(v:dot(p)-v:dot(a))
end

function lineDistB(p,a,b)
    return (p-a):dist(math.max(math.min((p-a):dot(b-a)/(b-a):lenSqr(),1),0)*(b-a))
end

@loopspace perfect! i don’t understand what you are doing with touchpt and offset?

@piinthesky it’s to allow you to move the three points around on the screen. When you touch the screen, it works out which point is nearest to the touch and then moves that point as you move your finger on the screen.

@loopspace ah ok i didn’t trigger that the ends of the line coukd be changed. i am not familiar with the G function - its good to know.

@dave1707, @piinthesky - I’ve written a post on this using an explanation from another source

https://coolcodea.wordpress.com/2015/01/19/194-shortest-distance-to-line-explained/

how to serch for the math expression of the function?such as dist?anglebetween etc?

Reference link at top of this page has the Codea functions

if you want formulae, try google