21

I have dynamically generated lines that animate and I want to detect when a lines hits another. I'm trying to implement some basic linear algebra to obtain the equation of the lines and then solving for x,y, but the results are erratic. At this point I'm testing only with two lines which means I should be getting one point of intersection, but I get two. I just want to make sure my math is ok and that I should be looking elsewhere for the problem.

function collision(boid1, boid2) {
  var x1 = boid1.initialX, y1 = boid1.initialY, x2 = boid1.x, y2 = boid1.y, x3 = boid2.initialX, y3 = boid2.initialY, x4 = boid2.x, y4 = boid2.y;
  slope1 = (y1 - y2)/(x1 - x2);
  slope2 = (y3 - y4)/(x3- x4);

  if(slope1 != slope2){
    var b1 = getB(slope1,x1,y1);
    var b2 = getB(slope2,x3,y3);

    if(slope2 >= 0){
      u = slope1 - slope2;
    }else{
      u = slope1 + slope2;
    }

    if(b1 >= 0){
      z = b2 - b1;
    }else{
      z = b2 + b1;
    }
    pointX = z / u;
    pointY = (slope1*pointX)+b1;
    pointYOther = (slope2*pointX)+b2;
    console.log("pointx:"+pointX+" pointy:"+pointY+" othery:"+pointYOther);
    context.beginPath();
    context.arc(pointX, pointY, 2, 0, 2 * Math.PI, false);
    context.fillStyle = 'green';
    context.fill();
    context.lineWidth = 1;
    context.strokeStyle = '#003300';
    context.stroke();
  }
  return false;
}

function getB(slope,x,y){
  var y = y, x = x, m = slope;
  a = m*x;
  if(a>=0){
    b = y - a;
  }else{
    b = y + a;
  }
  return b;
}

The problem is that I'm getting two different values for the point of intersection. There should only be one, which leads me to believe my calculations are wrong. Yes, x2,y2,x4,y4 are all moving, but they have a set angle and the consistent slopes confirm that.

ashleedawg
  • 20,365
  • 9
  • 72
  • 105
Adam
  • 8,849
  • 16
  • 67
  • 131
  • Please add a [short self-contained example](http://sscce.org), which shows the problem. At the moment your post is both lacking a problem and a question, it should contain at least one of them. (Note: you told us that the result is erratic, but you didn't tell us the exact nature of the problem). – Zeta Dec 18 '12 at 16:56
  • The fact that you keep checking whether things are positive is a red flag. This should not be necessary. Also, you are definitely not taking care of the case where one of the lines is vertical (i.e. `slope = Infinity`). – Aaron Dufour Dec 18 '12 at 17:02
  • Aaron, my approach was to do it as if a human were trying to solve for the variables. What would be a more efficient method? – Adam Dec 18 '12 at 17:21

5 Answers5

34

I found a great solution by Paul Bourke. Here it is, implemented in JavaScript:

function line_intersect(x1, y1, x2, y2, x3, y3, x4, y4)
{
    var ua, ub, denom = (y4 - y3)*(x2 - x1) - (x4 - x3)*(y2 - y1);
    if (denom == 0) {
        return null;
    }
    ua = ((x4 - x3)*(y1 - y3) - (y4 - y3)*(x1 - x3))/denom;
    ub = ((x2 - x1)*(y1 - y3) - (y2 - y1)*(x1 - x3))/denom;
    return {
        x: x1 + ua * (x2 - x1),
        y: y1 + ua * (y2 - y1),
        seg1: ua >= 0 && ua <= 1,
        seg2: ub >= 0 && ub <= 1
    };
}
vbarbarosh
  • 3,502
  • 4
  • 33
  • 43
  • 1
    Note there is a typo in this solution: The calculation of seg2 says "ub >= 0 && ua <= 1" when it should say "ub >= 0 && ub <= 1". – Dr. Pain Nov 17 '16 at 16:16
  • 10
    I had better luck with the javascript implementation from Paul's site: http://paulbourke.net/geometry/pointlineplane/javascript.txt – David Figatner Jun 09 '18 at 01:44
  • 2
    I don't know why, but in this solution, the intersection point always has a y-offset. The link above works perfectly. – musemind Oct 23 '18 at 10:51
  • @musemind Can you provide an example when it does not work for you? – vbarbarosh Oct 23 '18 at 14:45
  • 2
    I also got the y-offset. Note that in the answer above (as it is now) the y position is calculated using "y1 + ub * (y2 - y1)", while Paul Bourke uses "y1 + ua * (y2 - y1)" on his site (note the difference on the ua vs ub). Paul uses ua for both x and y. If I use the lines {x1:100, y1:0, x2:100, y2:218, x3:0, y3:100, x4:200, y4:100} the above answer gives 100X109, while Bourke's code gives 100X100 (which is correct) – Karl Erik Steinbakk Dec 17 '18 at 20:44
15

For line segment-line segment intersections, use Paul Bourke's solution:

// line intercept math by Paul Bourke http://paulbourke.net/geometry/pointlineplane/
// Determine the intersection point of two line segments
// Return FALSE if the lines don't intersect
function intersect(x1, y1, x2, y2, x3, y3, x4, y4) {

  // Check if none of the lines are of length 0
    if ((x1 === x2 && y1 === y2) || (x3 === x4 && y3 === y4)) {
        return false
    }

    denominator = ((y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1))

  // Lines are parallel
    if (denominator === 0) {
        return false
    }

    let ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denominator
    let ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / denominator

  // is the intersection along the segments
    if (ua < 0 || ua > 1 || ub < 0 || ub > 1) {
        return false
    }

  // Return a object with the x and y coordinates of the intersection
    let x = x1 + ua * (x2 - x1)
    let y = y1 + ua * (y2 - y1)

    return {x, y}
}

For infinite line intersections, use Justin C. Round's algorithm:

function checkLineIntersection(line1StartX, line1StartY, line1EndX, line1EndY, line2StartX, line2StartY, line2EndX, line2EndY) {
    // if the lines intersect, the result contains the x and y of the intersection (treating the lines as infinite) and booleans for whether line segment 1 or line segment 2 contain the point
    var denominator, a, b, numerator1, numerator2, result = {
        x: null,
        y: null,
        onLine1: false,
        onLine2: false
    };
    denominator = ((line2EndY - line2StartY) * (line1EndX - line1StartX)) - ((line2EndX - line2StartX) * (line1EndY - line1StartY));
    if (denominator == 0) {
        return result;
    }
    a = line1StartY - line2StartY;
    b = line1StartX - line2StartX;
    numerator1 = ((line2EndX - line2StartX) * a) - ((line2EndY - line2StartY) * b);
    numerator2 = ((line1EndX - line1StartX) * a) - ((line1EndY - line1StartY) * b);
    a = numerator1 / denominator;
    b = numerator2 / denominator;

    // if we cast these lines infinitely in both directions, they intersect here:
    result.x = line1StartX + (a * (line1EndX - line1StartX));
    result.y = line1StartY + (a * (line1EndY - line1StartY));

    // if line1 is a segment and line2 is infinite, they intersect if:
    if (a > 0 && a < 1) {
        result.onLine1 = true;
    }
    // if line2 is a segment and line1 is infinite, they intersect if:
    if (b > 0 && b < 1) {
        result.onLine2 = true;
    }
    // if line1 and line2 are segments, they intersect if both of the above are true
    return result;
};
ashleedawg
  • 20,365
  • 9
  • 72
  • 105
David Figatner
  • 771
  • 8
  • 18
6

You don't need to alternate between adding/subtracting y-intersects when plugging 'found-x' back into one of the equations:

(function () {
    window.linear = {
        slope: function (x1, y1, x2, y2) {
            if (x1 == x2) return false;
            return (y1 - y2) / (x1 - x2);
        },
        yInt: function (x1, y1, x2, y2) {
            if (x1 === x2) return y1 === 0 ? 0 : false;
            if (y1 === y2) return y1;
            return y1 - this.slope(x1, y1, x2, y2) * x1 ;
        },
        getXInt: function (x1, y1, x2, y2) {
            var slope;
            if (y1 === y2) return x1 == 0 ? 0 : false;
            if (x1 === x2) return x1;
            return (-1 * ((slope = this.slope(x1, y1, x2, y2)) * x1 - y1)) / slope;
        },
        getIntersection: function (x11, y11, x12, y12, x21, y21, x22, y22) {
            var slope1, slope2, yint1, yint2, intx, inty;
            if (x11 == x21 && y11 == y21) return [x11, y11];
            if (x12 == x22 && y12 == y22) return [x12, y22];

            slope1 = this.slope(x11, y11, x12, y12);
            slope2 = this.slope(x21, y21, x22, y22);
            if (slope1 === slope2) return false;

            yint1 = this.yInt(x11, y11, x12, y12);
            yint2 = this.yInt(x21, y21, x22, y22);
            if (yint1 === yint2) return yint1 === false ? false : [0, yint1];

            if (slope1 === false) return [y21, slope2 * y21 + yint2];
            if (slope2 === false) return [y11, slope1 * y11 + yint1];
            intx = (slope1 * x11 + yint1 - yint2)/ slope2;
            return [intx, slope1 * intx + yint1];
        }
    }
}());
SReject
  • 3,774
  • 1
  • 25
  • 41
  • Thanks. Here at "line2.yint = line2.slope * line2.x2 - line1.y1;" do you mean "line2.yint = line2.slope * line2.x2 - line2.y2;"? – Adam Dec 18 '12 at 18:30
  • @Adam, fixed it, I meant `line2.x1` – SReject Dec 18 '12 at 18:46
  • To solve don't we need any point that lies on the particular line? Can you explain why you can use line1.y1 to solve for the y-int of line 2? – Adam Dec 18 '12 at 18:54
  • derped again. I do infact, need `line2.x1` and `line2.y1`to get line2's yint. Fixed. – SReject Dec 18 '12 at 19:05
  • Does it calculate the right y-int? I think it gets the correct number but switches it to the opposite; positive becomes negative, negative becomes positive. – Adam Dec 18 '12 at 19:15
  • ah doing y - slope*x; does it! – Adam Dec 18 '12 at 19:19
  • all my derps by fixed; I believe. Find another and Ill edit it aswell – SReject Dec 18 '12 at 19:29
  • is our math inherently wrong? I double checked the calculations after running this script and the point of intersection is clearly wrong. – Adam Dec 18 '12 at 20:51
  • ah great. Could you include the old code as well? Where you calculate intX should have been (line2.yint - line1.yint) / (line1.slope - line2.slope); – Adam Dec 18 '12 at 21:43
  • That was actually wrong. Here's a fiddle for the new code: http://jsfiddle.net/SReject/ydt8V/ – SReject Dec 18 '12 at 22:15
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/21350/discussion-between-sreject-and-adam) – SReject Dec 18 '12 at 22:26
  • There are still errors in this code. Both the one posted and in the fiddle. I've used it to calculate intersection point of the horizontal lines with the left edge of the document in this svg: https://jsfiddle.net/rzymek/deruagx2/. I've switched to `line-intersect` npm module, which worked fine. – rzymek Apr 23 '17 at 16:56
2

You may do as follows;

function lineIntersect(a,b){
  a.m = (a[0].y-a[1].y)/(a[0].x-a[1].x);  // slope of line 1
  b.m = (b[0].y-b[1].y)/(b[0].x-b[1].x);  // slope of line 2
  return a.m - b.m < Number.EPSILON ? undefined
                                    : { x: (a.m * a[0].x - b.m*b[0].x + b[0].y - a[0].y) / (a.m - b.m),
                                        y: (a.m*b.m*(b[0].x-a[0].x) + b.m*a[0].y - a.m*b[0].y) / (b.m - a.m)};
}

var line1 = [{x:3, y:3},{x:17, y:8}],
    line2 = [{x:7, y:10},{x:11, y:2}];
console.log(lineIntersect(line1, line2));
Redu
  • 25,060
  • 6
  • 56
  • 76
  • 1
    This is nice and concise answer, but because it's treating the lines as (slope) equations, it is missing the ability to determine whether the intersection happens within the line segments, or outside of it. – Daniel Feb 13 '17 at 21:18
  • For a quick fix, I've tested first whether it intersects using function from here http://stackoverflow.com/a/28866825/197546 and only on true calculating the actual location, but I"m sure there's a more preformant solution – Daniel Feb 13 '17 at 21:29
  • 1
    @Daniel Hmm.. Yes you are right but once you know their intersection point, it's fairly trivial to test if the intersection point is within the Epsilon proximity of one of the line segments. But yes there is possibly something more efficient. As there is always. – Redu Feb 13 '17 at 22:22
  • var line1 = [{x:0, y:0},{x:100, y:0}], line2 = [{x:50, y:-50},{x:50, y:50}]; This will give wrong result – Fei Sun Jul 07 '22 at 08:22
1

There is an npm module that does just that: line-intersect.

Install it using

npm install --save line-intersect

ES6 usage:

import { checkIntersection } from "line-intersect";

const result = lineIntersect.checkIntersection(
  line1.start.x, line1.start.y, line1.end.x, line1.end.y,
  line2.start.x, line2.start.y, line2.end.x, line2.end.y
);

result.type  // any of "none", "parallel", "colinear", "intersecting"
result.point // only exists when result.type == 'intersecting'

If you're using typescript, here are the typings:

declare module "line-intersect" {
  export function checkIntersection(
    x1: number, y1: number,
    x2: number, y2: number,
    x3: number, y3: number,
    x4: number, y4: number): {
        type: string,
        point: {x:number, y:number}
    }; 
}

Put it in a file and reference if in tsconfig.json's "files" section.

rzymek
  • 9,064
  • 2
  • 45
  • 59