web-dev-qa-db-ja.com

2本の線が交差するかどうかをテストする-JavaScript関数

2つの行が互いに交差するかどうかを検出するjavascript関数を検索してみました。

この関数は、各ラインの両方の始点と終点のx、y値を取ります(これらをラインAおよびラインBと呼びます)。

交差する場合はtrue、そうでない場合はfalseを返します。

関数の例。答えが代わりにベクトルオブジェクトを使用する場合、私はうれしいです。

Function isIntersect (lineAp1x, lineAp1y, lineAp2x, lineAp2y, lineBp1x, lineBp1y, lineBp2x, lineBp2y) 
{

    // JavaScript line intersecting test here. 

}

背景情報:このコードは、html5キャンバスで作成しようとしているゲーム用であり、衝突検出の一部です。

26
Jarrod
// returns true iff the line from (a,b)->(c,d) intersects with (p,q)->(r,s)
function intersects(a,b,c,d,p,q,r,s) {
  var det, gamma, lambda;
  det = (c - a) * (s - q) - (r - p) * (d - b);
  if (det === 0) {
    return false;
  } else {
    lambda = ((s - q) * (r - a) + (p - r) * (s - b)) / det;
    gamma = ((b - d) * (r - a) + (c - a) * (s - b)) / det;
    return (0 < lambda && lambda < 1) && (0 < gamma && gamma < 1);
  }
};

説明:(ベクトル、行列、生意気な行列式)

線は、初期ベクトルvと方向ベクトルdで記述できます。

r = v + lambda*d 

1つのポイントを使用します(a,b)初期ベクトルとして、およびそれらの差(c-a,d-b)方向ベクトルとして。 2行目も同様です。

2本の線が交差する場合、最初の線に沿って距離lambdaを移動することにより到達可能で、2番目の線に沿ってガンマ単位を移動することにより到達可能な点Xがなければなりません。これにより、Xの座標について2つの連立方程式が得られます。

X = v1 + lambda*d1 
X = v2 + gamma *d2

これらの方程式は、行列形式で表すことができます。交点Xが存在するかどうかを確認するために、行列式がゼロでないことを確認します。

交差点がある場合は、交差点が実際に両方のポイントセットの間にあることを確認する必要があります。 lambdaが1より大きい場合、交差点は2番目のポイントを超えています。 lambdaが0より小さい場合、交差点は最初のポイントの前にあります。

したがって、0<lambda<1 && 0<gamma<1は、2本の線が交差することを示します!

36
Dan Fox
function lineIntersect(x1,y1,x2,y2, x3,y3,x4,y4) {
    var x=((x1*y2-y1*x2)*(x3-x4)-(x1-x2)*(x3*y4-y3*x4))/((x1-x2)*(y3-y4)-(y1-y2)*(x3-x4));
    var y=((x1*y2-y1*x2)*(y3-y4)-(y1-y2)*(x3*y4-y3*x4))/((x1-x2)*(y3-y4)-(y1-y2)*(x3-x4));
    if (isNaN(x)||isNaN(y)) {
        return false;
    } else {
        if (x1>=x2) {
            if (!(x2<=x&&x<=x1)) {return false;}
        } else {
            if (!(x1<=x&&x<=x2)) {return false;}
        }
        if (y1>=y2) {
            if (!(y2<=y&&y<=y1)) {return false;}
        } else {
            if (!(y1<=y&&y<=y2)) {return false;}
        }
        if (x3>=x4) {
            if (!(x4<=x&&x<=x3)) {return false;}
        } else {
            if (!(x3<=x&&x<=x4)) {return false;}
        }
        if (y3>=y4) {
            if (!(y4<=y&&y<=y3)) {return false;}
        } else {
            if (!(y3<=y&&y<=y4)) {return false;}
        }
    }
    return true;
}

wiki 私が答えを見つけたページ。

31
Alex Malcolm

Peter Woneの答えは素晴らしい解決策ですが、説明がありません。私は最後の1時間ほどかけて、それがどのように機能するかを理解し、それを説明するのに十分理解できると思います。詳細については、彼の答えを参照してください: https://stackoverflow.com/a/16725715/697477

また、以下のコードに同一直線上のソリューションを含めました。

回転方向を使用して交差点を確認する

答えを説明するために、2本の線のすべての交差点に共通するものを見てみましょう。以下の図を考えると、P1to[〜#〜] ip [〜#〜]からP4は反時計回りに回転します。相補的な側面が時計回りに回転していることがわかります。現在、交差するかどうかはわからないため、交差点はわかりません。ただし、P1toP2toP4も反時計回りに回転します。さらに、P1toP2toP3は時計回りに回転します。この知識を使用して、2本の線が交差するかどうかを判断できます。

Stretching the face

交差点の例

Line intersectionLine Intersection

交差する線は、反対方向を指す4つの面を作成します。それらは反対方向を向いているため、Pの方向1toP2toP3Pとは異なる方向に回転します1toP2toP4。また、P1toP3toP4Pとは異なる方向に回転します2toP3toP4

交差しない例

Line No IntersectionLine No Intersection

この例では、交差テストの同じパターンに従って、2つの面が同じ方向に回転することに注意してください。それらは同じ方向を向いているため、交差していないことがわかります。

コードサンプル

したがって、これをPeter Woneが提供する元のコードに実装できます。

// Check the direction these three points rotate
function RotationDirection(p1x, p1y, p2x, p2y, p3x, p3y) {
  if (((p3y - p1y) * (p2x - p1x)) > ((p2y - p1y) * (p3x - p1x)))
    return 1;
  else if (((p3y - p1y) * (p2x - p1x)) == ((p2y - p1y) * (p3x - p1x)))
    return 0;
  
  return -1;
}

function containsSegment(x1, y1, x2, y2, sx, sy) {
  if (x1 < x2 && x1 < sx && sx < x2) return true;
  else if (x2 < x1 && x2 < sx && sx < x1) return true;
  else if (y1 < y2 && y1 < sy && sy < y2) return true;
  else if (y2 < y1 && y2 < sy && sy < y1) return true;
  else if (x1 == sx && y1 == sy || x2 == sx && y2 == sy) return true;
  return false;
}

function hasIntersection(x1, y1, x2, y2, x3, y3, x4, y4) {
  var f1 = RotationDirection(x1, y1, x2, y2, x4, y4);
  var f2 = RotationDirection(x1, y1, x2, y2, x3, y3);
  var f3 = RotationDirection(x1, y1, x3, y3, x4, y4);
  var f4 = RotationDirection(x2, y2, x3, y3, x4, y4);
  
  // If the faces rotate opposite directions, they intersect.
  var intersect = f1 != f2 && f3 != f4;
  
  // If the segments are on the same line, we have to check for overlap.
  if (f1 == 0 && f2 == 0 && f3 == 0 && f4 == 0) {
    intersect = containsSegment(x1, y1, x2, y2, x3, y3) || containsSegment(x1, y1, x2, y2, x4, y4) ||
    containsSegment(x3, y3, x4, y4, x1, y1) || containsSegment(x3, y3, x4, y4, x2, y2);
  }
  
  return intersect;
}

// Main call for checking intersection. Particularly verbose for explanation purposes.
function checkIntersection() {
  // Grab the values
  var x1 = parseInt($('#p1x').val());
  var y1 = parseInt($('#p1y').val());
  var x2 = parseInt($('#p2x').val());
  var y2 = parseInt($('#p2y').val());
  var x3 = parseInt($('#p3x').val());
  var y3 = parseInt($('#p3y').val());
  var x4 = parseInt($('#p4x').val());
  var y4 = parseInt($('#p4y').val());

  // Determine the direction they rotate. (You can combine this all into one step.)
  var face1CounterClockwise = RotationDirection(x1, y1, x2, y2, x4, y4);
  var face2CounterClockwise = RotationDirection(x1, y1, x2, y2, x3, y3);
  var face3CounterClockwise = RotationDirection(x1, y1, x3, y3, x4, y4);
  var face4CounterClockwise = RotationDirection(x2, y2, x3, y3, x4, y4);

  // If face 1 and face 2 rotate different directions and face 3 and face 4 rotate different directions, 
  // then the lines intersect.
  var intersect = hasIntersection(x1, y1, x2, y2, x3, y3, x4, y4);

  // Output the results.
  var output = "Face 1 (P1, P2, P4) Rotates: " + ((face1CounterClockwise > 0) ? "counterClockWise" : ((face1CounterClockwise == 0) ? "Linear" : "clockwise")) + "<br />";
  var output = output + "Face 2 (P1, P2, P3) Rotates: " + ((face2CounterClockwise > 0) ? "counterClockWise" : ((face2CounterClockwise == 0) ? "Linear" : "clockwise")) + "<br />";
  var output = output + "Face 3 (P1, P3, P4) Rotates: " + ((face3CounterClockwise > 0) ? "counterClockWise" : ((face3CounterClockwise == 0) ? "Linear" : "clockwise")) + "<br />";
  var output = output + "Face 4 (P2, P3, P4) Rotates: " + ((face4CounterClockwise > 0) ? "counterClockWise" : ((face4CounterClockwise == 0) ? "Linear" : "clockwise")) + "<br />";
  var output = output + "Intersection: " + ((intersect) ? "Yes" : "No") + "<br />";
  $('#result').html(output);


  // Draw the lines.
  var canvas = $("#canvas");
  var context = canvas.get(0).getContext('2d');
  context.clearRect(0, 0, canvas.get(0).width, canvas.get(0).height);
  context.beginPath();
  context.moveTo(x1, y1);
  context.lineTo(x2, y2);
  context.moveTo(x3, y3);
  context.lineTo(x4, y4);
  context.stroke();
}

checkIntersection();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<canvas id="canvas" width="200" height="200" style="border: 2px solid #000000; float: right;"></canvas>
<div style="float: left;">
  <div style="float: left;">
    <b>Line 1:</b>
    <br />P1 x:
    <input type="number" min="0" max="200" id="p1x" style="width: 40px;" onChange="checkIntersection();" value="0">y:
    <input type="number" min="0" max="200" id="p1y" style="width: 40px;" onChange="checkIntersection();" value="20">
    <br />P2 x:
    <input type="number" min="0" max="200" id="p2x" style="width: 40px;" onChange="checkIntersection();" value="100">y:
    <input type="number" min="0" max="200" id="p2y" style="width: 40px;" onChange="checkIntersection();" value="20">
    <br />
  </div>
  <div style="float: left;">
    <b>Line 2:</b>
    <br />P3 x:
    <input type="number" min="0" max="200" id="p3x" style="width: 40px;" onChange="checkIntersection();" value="150">y:
    <input type="number" min="0" max="200" id="p3y" style="width: 40px;" onChange="checkIntersection();" value="100">
    <br />P4 x:
    <input type="number" min="0" max="200" id="p4x" style="width: 40px;" onChange="checkIntersection();" value="0">y:
    <input type="number" min="0" max="200" id="p4y" style="width: 40px;" onChange="checkIntersection();" value="0">
    <br />
  </div>
  <br style="clear: both;" />
  <br />
  <div style="float: left; border: 1px solid #EEEEEE; padding: 2px;" id="result"></div>
</div>
25
teynon

交点を見つけることができると便利ですが、whether line segment intersectionのテストは、ポリゴンヒットテストに最もよく使用されます。 高速。したがって、減算、乗算、比較、およびANDのみを使用して、このようにすることをお勧めします。 Turnは、3つの点で表される2つのエッジ間の勾配の変化の方向を計算します。1は反時計回り、0は回転なし、-1は時計回りを意味します。

このコードは、GLatLngオブジェクトとして表現されたポイントを想定していますが、他の表現システムに簡単に書き直すことができます。勾配の比較は ε 浮動小数点誤差の減衰に対する耐性に正規化されています。

function Turn(p1, p2, p3) {
  a = p1.lng(); b = p1.lat(); 
  c = p2.lng(); d = p2.lat();
  e = p3.lng(); f = p3.lat();
  A = (f - b) * (c - a);
  B = (d - b) * (e - a);
  return (A > B + Number.EPSILON) ? 1 : (A + Number.EPSILON < B) ? -1 : 0;
}

function isIntersect(p1, p2, p3, p4) {
  return (Turn(p1, p3, p4) != Turn(p2, p3, p4)) && (Turn(p1, p2, p3) != Turn(p1, p2, p4));
}
19
Peter Wone

Lat()/ long()の代わりにx/yを使用して、1つの関数に対するPeter Woneの答えを書き直しました。

function isIntersecting(p1, p2, p3, p4) {
    function CCW(p1, p2, p3) {
        return (p3.y - p1.y) * (p2.x - p1.x) > (p2.y - p1.y) * (p3.x - p1.x);
    }
    return (CCW(p1, p3, p4) != CCW(p2, p3, p4)) && (CCW(p1, p2, p3) != CCW(p1, p2, p4));
}
7
Jonathan

this Gist に基づいたバージョンと、より簡潔な変数名、およびいくつかのCoffeeがあります。

JavaScriptバージョン

var lineSegmentsIntersect = (x1, y1, x2, y2, x3, y3, x4, y4)=> {
    var a_dx = x2 - x1;
    var a_dy = y2 - y1;
    var b_dx = x4 - x3;
    var b_dy = y4 - y3;
    var s = (-a_dy * (x1 - x3) + a_dx * (y1 - y3)) / (-b_dx * a_dy + a_dx * b_dy);
    var t = (+b_dx * (y1 - y3) - b_dy * (x1 - x3)) / (-b_dx * a_dy + a_dx * b_dy);
    return (s >= 0 && s <= 1 && t >= 0 && t <= 1);
}

CoffeeScriptバージョン

lineSegmentsIntersect = (x1, y1, x2, y2, x3, y3, x4, y4)->
    a_dx = x2 - x1
    a_dy = y2 - y1
    b_dx = x4 - x3
    b_dy = y4 - y3
    s = (-a_dy * (x1 - x3) + a_dx * (y1 - y3)) / (-b_dx * a_dy + a_dx * b_dy)
    t = (+b_dx * (y1 - y3) - b_dy * (x1 - x3)) / (-b_dx * a_dy + a_dx * b_dy)
    (0 <= s <= 1 and 0 <= t <= 1)
3
1j01

まず、交差点の座標を見つけます-ここで詳細を説明します: http://www.mathopenref.com/coordintersection.html

次に、交差のx座標がいずれかの線のx範囲内にあるかどうかを確認します(または、必要に応じてy座標でも同じことを行います)。つまり、xIntersectionがlineAp1xとlineAp2xの間にある場合は交差します。

2
egres

コールドフュージョンの準備ができているソリューションを持ちたいすべての人々のために、ここに私が適応したものがあります http://grepcode.com/file/repository.grepcode.com/Java/root/jdk/openjdk/7- b147/Java/awt/geom/Line2D.Java#Line2D.linesIntersect%28double%2Cdouble%2Cdouble%2Cdouble%2Cdouble%2Cdouble%2Cdouble%2Cdouble%29

重要な関数はccwとJava.awt.geom.Line2DのlinesIntersectであり、私はそれらをcoldfusionに記述したので、ここに行きます。

<cffunction name="relativeCCW" description="schnittpunkt der vier punkte (2 geraden) berechnen">
<!---
Returns an indicator of where the specified point (px,py) lies with respect to this line segment. See the method comments of relativeCCW(double,double,double,double,double,double) to interpret the return value.
Parameters:
px the X coordinate of the specified point to be compared with this Line2D
py the Y coordinate of the specified point to be compared with this Line2D
Returns:
an integer that indicates the position of the specified coordinates with respect to this Line2D
--->
<cfargument name="x1" type="numeric" required="yes" >
<cfargument name="y1" type="numeric" required="yes">
<cfargument name="x2" type="numeric" required="yes" >
<cfargument name="y2" type="numeric" required="yes">
<cfargument name="px" type="numeric" required="yes" >
<cfargument name="py" type="numeric" required="yes">
    <cfscript>
    x2 = x2 - x1;
    y2 = y2 - y1;
    px = px - x1;
    py = py - y1;
    ccw = (px * y2) - (py * x2);
    if (ccw EQ 0) {
        // The point is colinear, classify based on which side of
        // the segment the point falls on.  We can calculate a
        // relative value using the projection of px,py onto the
        // segment - a negative value indicates the point projects
        // outside of the segment in the direction of the particular
        // endpoint used as the Origin for the projection.
        ccw = (px * x2) + (py * y2);
        if (ccw GT 0) {
            // Reverse the projection to be relative to the original x2,y2
            // x2 and y2 are simply negated.
            // px and py need to have (x2 - x1) or (y2 - y1) subtracted
            //    from them (based on the original values)
            // Since we really want to get a positive answer when the
             //    point is "beyond (x2,y2)", then we want to calculate
            //    the inverse anyway - thus we leave x2 & y2 negated.       
            px = px - x2;
            py = py - y2;
            ccw = (px * x2) + (py * y2);
            if (ccw LT 0) {
                ccw = 0;
                }
        }
    }
    if (ccw LT 0) {
        ret = -1;
    }
    else if (ccw GT 0) {
        ret = 1;
    }
    else {
        ret = 0;
    }   
    </cfscript> 
    <cfreturn ret>
</cffunction>


<cffunction name="linesIntersect" description="schnittpunkt der vier punkte (2 geraden) berechnen">
<cfargument name="x1" type="numeric" required="yes" >
<cfargument name="y1" type="numeric" required="yes">
<cfargument name="x2" type="numeric" required="yes" >
<cfargument name="y2" type="numeric" required="yes">
<cfargument name="x3" type="numeric" required="yes" >
<cfargument name="y3" type="numeric" required="yes">
<cfargument name="x4" type="numeric" required="yes" >
<cfargument name="y4" type="numeric" required="yes">
    <cfscript>
    a1 = relativeCCW(x1, y1, x2, y2, x3, y3);
    a2 = relativeCCW(x1, y1, x2, y2, x4, y4);
    a3 = relativeCCW(x3, y3, x4, y4, x1, y1);
    a4 = relativeCCW(x3, y3, x4, y4, x2, y2);
    aa = ((relativeCCW(x1, y1, x2, y2, x3, y3) * relativeCCW(x1, y1, x2, y2, x4, y4) LTE 0)
            && (relativeCCW(x3, y3, x4, y4, x1, y1) * relativeCCW(x3, y3, x4, y4, x2, y2) LTE 0));
    </cfscript>
 <cfreturn aa>
</cffunction>

これが他の言語への適応に役立つことを願っていますか?

0
Raffael Meier