Programing

원형 선분 충돌 탐지 알고리즘?

lottogame 2020. 5. 14. 07:54
반응형

원형 선분 충돌 탐지 알고리즘?


A에서 B까지의 선과 반지름이 R 인 C에 원이 있습니다.

영상

선이 원과 교차하는지 확인하는 데 사용하는 좋은 알고리즘은 무엇입니까? 그리고 원 가장자리를 따라 어떤 좌표에서 발생 했습니까?


취득

  1. E 는 광선의 시작점입니다.
  2. L 은 광선의 끝점입니다.
  3. C 는 테스트중인 구의 중심입니다.
  4. r 은 해당 구의 반경입니다

계산 :
d = L-E (시작에서 끝까지 광선의 방향 벡터)
f = E-C (중심에서 광선 시작까지의 벡터)

그런 다음 교점을
찾습니다 . 플러깅 :
P = E + t * d
이것은 파라 메트릭 방정식입니다.
P x = E x + td x
P y = E y + td y

(x-h) 2 + (y- k) 2 = r 2
(h, k) = 원의 중심.

참고 : 여기서 문제를 2D로 단순화했습니다.이 솔루션은 3D에도 적용됩니다.

얻을 :

  1. 확장
    X 2 - 2xh + H 2 + Y 2 - 2yk + K 2 -, R 2 = 0
  2. 플러그
    X = E (X) + TD X
    Y = 전자 Y + TD의 Y
    (즉 , X + TD X ) 2 - 2 (E X + TD X ) H + H 2 + (전자 Y + TD의 Y ) 2 - 2 (전자 Y + td y ) k + k 2 -r 2 = 0
  3. 분해
    E X 2 + 2E X TD X + t (2) (D) X (2) (2E) - X (H) - 2TD X H + H 2 + 전자 Y 2 + 2E Y TD의 Y + t (2) (D) (Y) 2 - 2E의 Y의 K - 2TD Y의 유전율 +를 k 2 -r 2 = 0
  4. 그룹
    t 2 (d X 2 + (D) (Y) 2 ) + 2t (E (X) (D) X + E 의 Y 차원 Y - (D) X (H) - (D) (Y) (K)) + E (X) 2 + E (Y) 2 - 2E X (H) - (2E) (Y) (K)의 + h 2 + k 2 -r 2 = 0
  5. 마지막으로,
    t 2 (_d * _d) + 2t (_e * _d-_d * _c) + _e * _e-2 (_e * _c) + _c * _c-r 2 = 0
    * 여기서 _d는 벡터 d이고 *는 내적. *
  6. 그리고,
    t 2 (_d * _d) + 2t (_d * (_e-_c)) + (_e-_c) * (_e-_c)-r 2 = 0
  7. _f = _e-_c
    t 2 (_d * _d) + 2t (_d * _f) + _f * _f-r 2 = 0

그래서 우리는 다음을 얻습니다 :
t 2 * (d DOT d) + 2t * (f DOT d) + (f DOT f-r 2 ) = 0
따라서 2 차 방정식을 푸십시오.

float a = d.Dot( d ) ;
float b = 2*f.Dot( d ) ;
float c = f.Dot( f ) - r*r ;

float discriminant = b*b-4*a*c;
if( discriminant < 0 )
{
  // no intersection
}
else
{
  // ray didn't totally miss sphere,
  // so there is a solution to
  // the equation.

  discriminant = sqrt( discriminant );

  // either solution may be on or off the ray so need to test both
  // t1 is always the smaller value, because BOTH discriminant and
  // a are nonnegative.
  float t1 = (-b - discriminant)/(2*a);
  float t2 = (-b + discriminant)/(2*a);

  // 3x HIT cases:
  //          -o->             --|-->  |            |  --|->
  // Impale(t1 hit,t2 hit), Poke(t1 hit,t2>1), ExitWound(t1<0, t2 hit), 

  // 3x MISS cases:
  //       ->  o                     o ->              | -> |
  // FallShort (t1>1,t2>1), Past (t1<0,t2<0), CompletelyInside(t1<0, t2>1)

  if( t1 >= 0 && t1 <= 1 )
  {
    // t1 is the intersection, and it's closer than t2
    // (since t1 uses -b - discriminant)
    // Impale, Poke
    return true ;
  }

  // here t1 didn't intersect so we are either started
  // inside the sphere or completely past it
  if( t2 >= 0 && t2 <= 1 )
  {
    // ExitWound
    return true ;
  }

  // no intn: FallShort, Past, CompletelyInside
  return false ;
}

아무도 프로젝션을 고려하지 않는 것 같습니다. 여기서 완전히 벗어난 것입니까?

벡터를 AC에 투영 AB합니다. 투영 된 벡터 AD는 새로운 점을 제공합니다 D.
사이의 거리 가 작거나 같으면 교차점이 있습니다.DCR

이처럼 :
SchoolBoy의 이미지


알고리즘을 사용하여 점 (원 중심)과 선 (선 AB) 사이의 거리를 계산합니다. 그런 다음 원과 선의 교차점을 결정하는 데 사용할 수 있습니다.

A, B, C 점이 있다고 가정 해 봅시다. Ax와 Ay는 A 점의 x와 y 성분입니다. 스칼라 R은 원 반경입니다.

이 알고리즘은 A, B 및 C가 별개의 점이고 R이 0이 아닌 것을 요구합니다.

알고리즘은 다음과 같습니다

// compute the euclidean distance between A and B
LAB = sqrt( (Bx-Ax)²+(By-Ay)² )

// compute the direction vector D from A to B
Dx = (Bx-Ax)/LAB
Dy = (By-Ay)/LAB

// the equation of the line AB is x = Dx*t + Ax, y = Dy*t + Ay with 0 <= t <= LAB.

// compute the distance between the points A and E, where
// E is the point of AB closest the circle center (Cx, Cy)
t = Dx*(Cx-Ax) + Dy*(Cy-Ay)    

// compute the coordinates of the point E
Ex = t*Dx+Ax
Ey = t*Dy+Ay

// compute the euclidean distance between E and C
LEC = sqrt((Ex-Cx)²+(Ey-Cy)²)

// test if the line intersects the circle
if( LEC < R )
{
    // compute distance from t to circle intersection point
    dt = sqrt( R² - LEC²)

    // compute first intersection point
    Fx = (t-dt)*Dx + Ax
    Fy = (t-dt)*Dy + Ay

    // compute second intersection point
    Gx = (t+dt)*Dx + Ax
    Gy = (t+dt)*Dy + Ay
}

// else test if the line is tangent to circle
else if( LEC == R )
    // tangent point to circle is E

else
    // line doesn't touch circle

좋아, 나는 당신에게 코드를주지 않을 것이지만, 당신 이이 을 태그했기 때문에 그것이 당신에게 중요하지 않다고 생각합니다. 먼저 선에 수직 인 벡터를 가져와야합니다.

당신이 알 수없는 변수가됩니다 y = ax + c ( c 알 수있을 것입니다 )
그것을 위해 해결하기를, 계산 그것의 값이 선은 원의 중심을 통과 할 때.

즉,
원 중심의 위치를 ​​선 방정식에 꽂고를 구합니다 c.
그런 다음 원래 선과 그 법선의 교점을 계산하십시오.

그러면 선에 가장 가까운 원이 표시됩니다.
이 점과 원 중심 사이의 거리를 계산하십시오 (벡터의 크기 사용).
이것이 원의 반경보다 작다면-우리는 교차점을 가지고 있습니다!


다른 방법은 삼각형 ABC 영역 공식을 사용합니다. 교차점 테스트는 투영 방법보다 간단하고 효율적이지만 교차점의 좌표를 찾으려면 더 많은 작업이 필요합니다. 최소한 필요한 시점까지 지연됩니다.

삼각형 면적을 계산하는 공식은 다음과 같습니다. area = bh / 2

여기서 b는 기본 길이이고 h는 높이입니다. 우리는 구간 AB를 기준으로 선택하여 h는 원 중심 인 C에서 선까지의 최단 거리입니다.

삼각형 영역은 벡터 내적에 의해 계산 될 수 있으므로 h를 결정할 수 있습니다.

// compute the triangle area times 2 (area = area2/2)
area2 = abs( (Bx-Ax)*(Cy-Ay) - (Cx-Ax)(By-Ay) )

// compute the AB segment length
LAB = sqrt( (Bx-Ax)² + (By-Ay)² )

// compute the triangle height
h = area2/LAB

// if the line intersects the circle
if( h < R )
{
    ...
}        

업데이트 1 :

여기설명 된 빠른 역 제곱근 계산을 사용하여 코드를 최적화하여 1 / LAB의 근사치를 얻을 수 있습니다.

교차점을 계산하는 것은 그리 어렵지 않습니다. 여기 간다

// compute the line AB direction vector components
Dx = (Bx-Ax)/LAB
Dy = (By-Ay)/LAB

// compute the distance from A toward B of closest point to C
t = Dx*(Cx-Ax) + Dy*(Cy-Ay)

// t should be equal to sqrt( (Cx-Ax)² + (Cy-Ay)² - h² )

// compute the intersection point distance from t
dt = sqrt( R² - h² )

// compute first intersection point coordinate
Ex = Ax + (t-dt)*Dx
Ey = Ay + (t-dt)*Dy

// compute second intersection point coordinate
Fx = Ax + (t+dt)*Dx
Fy = Ay + (t+dt)*Dy

h = R 인 경우 선 AB는 원에 접하고 값 dt = 0 및 E = F입니다. 점 좌표는 E 및 F의 좌표입니다.

애플리케이션에서 A가 발생할 수있는 경우 A가 B와 다르고 세그먼트 길이가 널이 아닌지 확인해야합니다.


원의 중심점을 온라인으로 투영하여 교차점을 테스트하는 작은 스크립트를 작성했습니다.

vector distVector = centerPoint - projectedPoint;
if(distVector.length() < circle.radius)
{
    double distance = circle.radius - distVector.length();
    vector moveVector = distVector.normalize() * distance;
    circle.move(moveVector);
}

http://jsfiddle.net/ercang/ornh3594/1/

세그먼트와의 충돌을 확인해야하는 경우 시작점과 끝점까지의 원 중심 거리를 고려해야합니다.

vector distVector = centerPoint - startPoint;
if(distVector.length() < circle.radius)
{
    double distance = circle.radius - distVector.length();
    vector moveVector = distVector.normalize() * distance;
    circle.move(moveVector);
}

https://jsfiddle.net/ercang/menp0991/


내가 찾은이 솔루션은 다른 솔루션 중 일부를 따르는 것이 조금 더 쉬워 보입니다.

취득:

p1 and p2 as the points for the line, and
c as the center point for the circle and r for the radius

기울기 절편 형태의 선 방정식을 풀 것입니다. 그러나 c점으로 어려운 방정식을 다루고 싶지 않았으므로 좌표계를 원으로 이동시킵니다.0,0

p3 = p1 - c
p4 = p2 - c

그건 그렇고, 내가 서로 포인트를 뺄 때마다 누군가를 알지 x못하고을 빼고 y새로운 포인트에 넣습니다.

어쨌든, 이제 p3and와 라인의 방정식을 풀 수 있습니다 p4.

m = (p4_y - p3_y) / (p4_x - p3) (the underscore is an attempt at subscript)
y = mx + b
y - mx = b (just put in a point for x and y, and insert the m we found)

확인. 이제이 방정식을 동일하게 설정해야합니다. 먼저 원의 방정식을 풀어야합니다x

x^2 + y^2 = r^2
y^2 = r^2 - x^2
y = sqrt(r^2 - x^2)

그런 다음 그것들을 동일하게 설정했습니다.

mx + b = sqrt(r^2 - x^2)

이차 방정식 ( 0 = ax^2 + bx + c)을 구합니다 :

(mx + b)^2 = r^2 - x^2
(mx)^2 + 2mbx + b^2 = r^2 - x^2
0 = m^2 * x^2 + x^2 + 2mbx + b^2 - r^2
0 = (m^2 + 1) * x^2 + 2mbx + b^2 - r^2

지금은 내가 a, b하고 c.

a = m^2 + 1
b = 2mb
c = b^2 - r^2

그래서 나는 이것을 2 차 공식에 넣었습니다.

(-b ± sqrt(b^2 - 4ac)) / 2a

그리고 값으로 대체하고 가능한 한 단순화하십시오.

(-2mb ± sqrt(b^2 - 4ac)) / 2a
(-2mb ± sqrt((-2mb)^2 - 4(m^2 + 1)(b^2 - r^2))) / 2(m^2 + 1)
(-2mb ± sqrt(4m^2 * b^2 - 4(m^2 * b^2 - m^2 * r^2 + b^2 - r^2))) / 2m^2 + 2
(-2mb ± sqrt(4 * (m^2 * b^2 - (m^2 * b^2 - m^2 * r^2 + b^2 - r^2))))/ 2m^2 + 2
(-2mb ± sqrt(4 * (m^2 * b^2 - m^2 * b^2 + m^2 * r^2 - b^2 + r^2)))/ 2m^2 + 2
(-2mb ± sqrt(4 * (m^2 * r^2 - b^2 + r^2)))/ 2m^2 + 2
(-2mb ± sqrt(4) * sqrt(m^2 * r^2 - b^2 + r^2))/ 2m^2 + 2
(-2mb ± 2 * sqrt(m^2 * r^2 - b^2 + r^2))/ 2m^2 + 2
(-2mb ± 2 * sqrt(m^2 * r^2 + r^2 - b^2))/ 2m^2 + 2
(-2mb ± 2 * sqrt(r^2 * (m^2 + 1) - b^2))/ 2m^2 + 2

이것은 거의 단순화 될 것입니다. 마지막으로 ±를 사용하여 방정식으로 분리하십시오.

(-2mb + 2 * sqrt(r^2 * (m^2 + 1) - b^2))/ 2m^2 + 2 or     
(-2mb - 2 * sqrt(r^2 * (m^2 + 1) - b^2))/ 2m^2 + 2 

그런 다음 두 방정식의 결과를 xin에 연결하면 됩니다 mx + b. 명확하게하기 위해 이것을 사용하는 방법을 보여주기 위해 JavaScript 코드를 작성했습니다.

function interceptOnCircle(p1,p2,c,r){
    //p1 is the first line point
    //p2 is the second line point
    //c is the circle's center
    //r is the circle's radius

    var p3 = {x:p1.x - c.x, y:p1.y - c.y} //shifted line points
    var p4 = {x:p2.x - c.x, y:p2.y - c.y}

    var m = (p4.y - p3.y) / (p4.x - p3.x); //slope of the line
    var b = p3.y - m * p3.x; //y-intercept of line

    var underRadical = Math.pow((Math.pow(r,2)*(Math.pow(m,2)+1)),2)-Math.pow(b,2)); //the value under the square root sign 

    if (underRadical < 0){
    //line completely missed
        return false;
    } else {
        var t1 = (-2*m*b+2*Math.sqrt(underRadical))/(2 * Math.pow(m,2) + 2); //one of the intercept x's
        var t2 = (-2*m*b-2*Math.sqrt(underRadical))/(2 * Math.pow(m,2) + 2); //other intercept's x
        var i1 = {x:t1,y:m*t1+b} //intercept point 1
        var i2 = {x:t2,y:m*t2+b} //intercept point 2
        return [i1,i2];
    }
}

이게 도움이 되길 바란다!

추신 누군가가 오류를 발견하거나 제안 사항이 있으면 의견을 말하십시오. 나는 매우 새롭고 모든 도움 / 제안을 환영합니다.


벡터 AC를 벡터 AB에 투영하여 원 중심에 가장 가까운 무한 선에서 점을 찾을 수 있습니다. 해당 점과 원 중심 사이의 거리를 계산하십시오. R보다 크면 교차가 없습니다. 거리가 R과 같으면 선은 원의 접선이고 원 중심에 가장 가까운 점은 실제로 교점입니다. 거리가 R보다 작 으면 2 개의 교차점이 있습니다. 원 중심에서 가장 가까운 점에서 같은 거리에 있습니다. 이 거리는 피타고라스 정리를 사용하여 쉽게 계산할 수 있습니다. 의사 코드의 알고리즘은 다음과 같습니다.

{
dX = bX - aX;
dY = bY - aY;
if ((dX == 0) && (dY == 0))
  {
  // A and B are the same points, no way to calculate intersection
  return;
  }

dl = (dX * dX + dY * dY);
t = ((cX - aX) * dX + (cY - aY) * dY) / dl;

// point on a line nearest to circle center
nearestX = aX + t * dX;
nearestY = aY + t * dY;

dist = point_dist(nearestX, nearestY, cX, cY);

if (dist == R)
  {
  // line segment touches circle; one intersection point
  iX = nearestX;
  iY = nearestY;

  if (t < 0 || t > 1)
    {
    // intersection point is not actually within line segment
    }
  }
else if (dist < R)
  {
  // two possible intersection points

  dt = sqrt(R * R - dist * dist) / sqrt(dl);

  // intersection point nearest to A
  t1 = t - dt;
  i1X = aX + t1 * dX;
  i1Y = aY + t1 * dY;
  if (t1 < 0 || t1 > 1)
    {
    // intersection point is not actually within line segment
    }

  // intersection point farthest from A
  t2 = t + dt;
  i2X = aX + t2 * dX;
  i2Y = aY + t2 * dY;
  if (t2 < 0 || t2 > 1)
    {
    // intersection point is not actually within line segment
    }
  }
else
  {
  // no intersection
  }
}

편집 : 발견 된 교차점이 실제로 선 세그먼트 내에 있는지 확인하는 코드가 추가되었습니다.


이상하게도 대답 할 수는 있지만 언급 할 수는 없습니다 ... 나는 원의 중심이 원점에 놓 이도록 모든 것을 이동시키는 Multitaskpro의 접근 방식을 좋아했습니다. 불행히도 그의 코드에는 두 가지 문제가 있습니다. 먼저 제곱근 아래에서 이중 전원을 제거해야합니다. 그래서 :

var underRadical = Math.pow((Math.pow(r,2)*(Math.pow(m,2)+1)),2)-Math.pow(b,2));

그러나:

var underRadical = Math.pow(r,2)*(Math.pow(m,2)+1)) - Math.pow(b,2);

최종 좌표에서 그는 솔루션을 다시 이동하는 것을 잊습니다. 그래서 :

var i1 = {x:t1,y:m*t1+b}

그러나:

var i1 = {x:t1+c.x, y:m*t1+b+c.y};

그러면 전체 기능이 다음과 같이됩니다.

function interceptOnCircle(p1, p2, c, r) {
    //p1 is the first line point
    //p2 is the second line point
    //c is the circle's center
    //r is the circle's radius

    var p3 = {x:p1.x - c.x, y:p1.y - c.y}; //shifted line points
    var p4 = {x:p2.x - c.x, y:p2.y - c.y};

    var m = (p4.y - p3.y) / (p4.x - p3.x); //slope of the line
    var b = p3.y - m * p3.x; //y-intercept of line

    var underRadical = Math.pow(r,2)*Math.pow(m,2) + Math.pow(r,2) - Math.pow(b,2); //the value under the square root sign 

    if (underRadical < 0) {
        //line completely missed
        return false;
    } else {
        var t1 = (-m*b + Math.sqrt(underRadical))/(Math.pow(m,2) + 1); //one of the intercept x's
        var t2 = (-m*b - Math.sqrt(underRadical))/(Math.pow(m,2) + 1); //other intercept's x
        var i1 = {x:t1+c.x, y:m*t1+b+c.y}; //intercept point 1
        var i2 = {x:t2+c.x, y:m*t2+b+c.y}; //intercept point 2
        return [i1, i2];
    }
}

여기 수학이 필요합니다.

A = (Xa, Ya), B = (Xb, Yb) 및 C = (Xc, Yc)라고 가정하십시오. A에서 B까지의 선의 모든 점에는 좌표 (alpha * Xa + (1-alpha) Xb, alpha Ya + (1-alpha) * Yb) = P

점 P의 거리가 R에서 C까지 인 경우 원에 있어야합니다. 당신이 원하는 것은 해결하는 것입니다

distance(P, C) = R

그건

(alpha*Xa + (1-alpha)*Xb)^2 + (alpha*Ya + (1-alpha)*Yb)^2 = R^2
alpha^2*Xa^2 + alpha^2*Xb^2 - 2*alpha*Xb^2 + Xb^2 + alpha^2*Ya^2 + alpha^2*Yb^2 - 2*alpha*Yb^2 + Yb^2=R^2
(Xa^2 + Xb^2 + Ya^2 + Yb^2)*alpha^2 - 2*(Xb^2 + Yb^2)*alpha + (Xb^2 + Yb^2 - R^2) = 0

ABC 방정식을이 방정식에 적용하여 알파에 대한 해를 구하고 알파에 대한 해를 사용하여 P의 좌표를 계산하면 교차점이있는 경우 교차점을 얻습니다.


구의 중심 (3D이므로 원이 아닌 구를 의미한다고 가정)과 선 사이의 거리를 찾으면 그 거리가 트릭을 수행하는 반경보다 작은 지 확인하십시오.

충돌 지점은 선과 구 사이의 가장 가까운 지점입니다 (구와 선 사이의 거리를 계산할 때 계산 됨)

점과 선 사이의 거리 :
http://mathworld.wolfram.com/Point-LineDistance3-Dimensional.html


다음은 자바 스크립트로 구현 한 것입니다. 내 접근 방식은 먼저 선 세그먼트를 무한 선으로 변환 한 다음 교차점을 찾는 것입니다. 거기에서 발견 된 점이 선 세그먼트에 있는지 확인합니다. 코드가 잘 문서화되어 있으므로 따라갈 수 있어야합니다.

라이브 데모 에서 코드를 시험해 볼 수 있습니다 . 코드는 알고리즘 repo 에서 가져 왔습니다 .

여기에 이미지 설명을 입력하십시오

// Small epsilon value
var EPS = 0.0000001;

// point (x, y)
function Point(x, y) {
  this.x = x;
  this.y = y;
}

// Circle with center at (x,y) and radius r
function Circle(x, y, r) {
  this.x = x;
  this.y = y;
  this.r = r;
}

// A line segment (x1, y1), (x2, y2)
function LineSegment(x1, y1, x2, y2) {
  var d = Math.sqrt( (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2) );
  if (d < EPS) throw 'A point is not a line segment';
  this.x1 = x1; this.y1 = y1;
  this.x2 = x2; this.y2 = y2;
}

// An infinite line defined as: ax + by = c
function Line(a, b, c) {
  this.a = a; this.b = b; this.c = c;
  // Normalize line for good measure
  if (Math.abs(b) < EPS) {
    c /= a; a = 1; b = 0;
  } else { 
    a = (Math.abs(a) < EPS) ? 0 : a / b;
    c /= b; b = 1; 
  }
}

// Given a line in standard form: ax + by = c and a circle with 
// a center at (x,y) with radius r this method finds the intersection
// of the line and the circle (if any). 
function circleLineIntersection(circle, line) {

  var a = line.a, b = line.b, c = line.c;
  var x = circle.x, y = circle.y, r = circle.r;

  // Solve for the variable x with the formulas: ax + by = c (equation of line)
  // and (x-X)^2 + (y-Y)^2 = r^2 (equation of circle where X,Y are known) and expand to obtain quadratic:
  // (a^2 + b^2)x^2 + (2abY - 2ac + - 2b^2X)x + (b^2X^2 + b^2Y^2 - 2bcY + c^2 - b^2r^2) = 0
  // Then use quadratic formula X = (-b +- sqrt(a^2 - 4ac))/2a to find the 
  // roots of the equation (if they exist) and this will tell us the intersection points

  // In general a quadratic is written as: Ax^2 + Bx + C = 0
  // (a^2 + b^2)x^2 + (2abY - 2ac + - 2b^2X)x + (b^2X^2 + b^2Y^2 - 2bcY + c^2 - b^2r^2) = 0
  var A = a*a + b*b;
  var B = 2*a*b*y - 2*a*c - 2*b*b*x;
  var C = b*b*x*x + b*b*y*y - 2*b*c*y + c*c - b*b*r*r;

  // Use quadratic formula x = (-b +- sqrt(a^2 - 4ac))/2a to find the 
  // roots of the equation (if they exist).

  var D = B*B - 4*A*C;
  var x1,y1,x2,y2;

  // Handle vertical line case with b = 0
  if (Math.abs(b) < EPS) {

    // Line equation is ax + by = c, but b = 0, so x = c/a
    x1 = c/a;

    // No intersection
    if (Math.abs(x-x1) > r) return [];

    // Vertical line is tangent to circle
    if (Math.abs((x1-r)-x) < EPS || Math.abs((x1+r)-x) < EPS)
      return [new Point(x1, y)];

    var dx = Math.abs(x1 - x);
    var dy = Math.sqrt(r*r-dx*dx);

    // Vertical line cuts through circle
    return [
      new Point(x1,y+dy),
      new Point(x1,y-dy)
    ];

  // Line is tangent to circle
  } else if (Math.abs(D) < EPS) {

    x1 = -B/(2*A);
    y1 = (c - a*x1)/b;

    return [new Point(x1,y1)];

  // No intersection
  } else if (D < 0) {

    return [];

  } else {

    D = Math.sqrt(D);

    x1 = (-B+D)/(2*A);
    y1 = (c - a*x1)/b;

    x2 = (-B-D)/(2*A);
    y2 = (c - a*x2)/b;

    return [
      new Point(x1, y1),
      new Point(x2, y2)
    ];

  }

}

// Converts a line segment to a line in general form
function segmentToGeneralForm(x1,y1,x2,y2) {
  var a = y1 - y2;
  var b = x2 - x1;
  var c = x2*y1 - x1*y2;
  return new Line(a,b,c);
}

// Checks if a point 'pt' is inside the rect defined by (x1,y1), (x2,y2)
function pointInRectangle(pt,x1,y1,x2,y2) {
  var x = Math.min(x1,x2), X = Math.max(x1,x2);
  var y = Math.min(y1,y2), Y = Math.max(y1,y2);
  return x - EPS <= pt.x && pt.x <= X + EPS &&
         y - EPS <= pt.y && pt.y <= Y + EPS;
}

// Finds the intersection(s) of a line segment and a circle
function lineSegmentCircleIntersection(segment, circle) {

  var x1 = segment.x1, y1 = segment.y1, x2 = segment.x2, y2 = segment.y2;
  var line = segmentToGeneralForm(x1,y1,x2,y2);
  var pts = circleLineIntersection(circle, line);

  // No intersection
  if (pts.length === 0) return [];

  var pt1 = pts[0];
  var includePt1 = pointInRectangle(pt1,x1,y1,x2,y2);

  // Check for unique intersection
  if (pts.length === 1) {
    if (includePt1) return [pt1];
    return [];
  }

  var pt2 = pts[1];
  var includePt2 = pointInRectangle(pt2,x1,y1,x2,y2);

  // Check for remaining intersections
  if (includePt1 && includePt2) return [pt1, pt2];
  if (includePt1) return [pt1];
  if (includePt2) return [pt2];
  return [];

}

이 포스트에서 원 중심과 선분 사이의 법선 N (이미지 2) 사이의 교점을 나타내는 원 중심과 선상의 점 (Ipoint) 사이의 거리를 확인하여 원 선 충돌을 검사합니다.

( https://i.stack.imgur.com/3o6do.png )이미지 1. 벡터 E와 D 찾기

이미지 1에는 하나의 원과 하나의 선이 표시됩니다. 벡터 A 점 대 선 시작점, 벡터 B 점 대 선 끝점, 벡터 C 점 대 원 중심. 이제 벡터 E (선 시작점에서 원 중심으로)와 벡터 D (선 시작점에서 선으로 끝점)를 찾아야합니다.이 계산은 이미지 1에 표시되어 있습니다.

( https://i.stack.imgur.com/7098a.png )이미지 2. 벡터 X 찾기

이미지 2에서 벡터 E는 벡터 E와 단위 벡터 D의 "점 곱"에 의해 벡터 D에 투영됨을 알 수 있습니다. 점 곱의 결과는 스칼라 Xp입니다. 벡터 N 및 벡터 D. 다음 벡터 X는 단위 벡터 D와 스칼라 Xp를 곱하여 구합니다.

이제 우리는 벡터 A (라인에서 시작점)와 벡터 X의 간단한 벡터 추가 인 벡터 Z (벡터에서 I 포인트로)를 찾아야합니다. 다음으로 특별한 경우를 다루어야합니다. 왼쪽 또는 오른쪽인지 확인해야하는 것은 아닙니다. 가장 가까운 벡터를 사용하여 원에 가장 가까운 점을 결정합니다.

( https://i.stack.imgur.com/p9WIr.png )이미지 3. 가장 가까운 지점 찾기

투영 Xp가 음수 인 경우 Ipoint는 선분의 ​​왼쪽이고, 가장 가까운 벡터는 선 시작점의 벡터와 같고, 투영 Xp가 벡터 D의 크기보다 크면 Ipoint는 선분의 ​​오른쪽이며 가장 가까운 벡터는 선 끝의 벡터와 같습니다 다른 경우에 가장 가까운 벡터는 벡터 Z와 같습니다.

이제 가장 가까운 vector가있을 때 원 중심에서 Ipoint (dist vector)까지의 벡터를 찾아야합니다. 간단하게 가운데 벡터에서 가장 가까운 벡터를 빼면됩니다. 그런 다음 벡터 거리 크기가 원 반경보다 작은 지 확인하고 충돌이 없으면 충돌하지 않으면 충돌합니다.

( https://i.stack.imgur.com/QJ63q.png )그림 4. 충돌 확인

끝으로 충돌을 해결하기위한 몇 가지 값을 반환 할 수 있습니다. 가장 쉬운 방법은 충돌 겹침 (벡터 거리 크기에서 반지름 빼기)과 충돌 축, 벡터 D를 반환하는 것입니다. 또한 필요한 경우 교차점은 벡터 Z입니다.


선의 좌표가 Ax, Ay 및 Bx, By이고 원 중심이 Cx, Cy 인 경우 선 공식은 다음과 같습니다.

x = Ax * t + Bx * (1-t)

y = Ay * t + By * (1-t)

여기서 0 <= t <= 1

그리고 원은

(Cx-x) ^ 2 + (Cy-y) ^ 2 = R ^ 2

선의 x 및 y 공식을 circle 공식으로 대체하면 t의 2 차 방정식이 나오고 그 해는 교점입니다 (있는 경우). 어느 것이 0보다 작거나 1보다 크면 솔루션이 아니지만 선이 원의 방향을 '포인팅'한다는 것을 나타냅니다.


이 스레드에 추가 한 것입니다 ... 아래는 pahlevan이 게시 한 코드 버전이지만 C # / XNA 용으로 약간 정리되어 있습니다.

    /// <summary>
    /// Intersects a line and a circle.
    /// </summary>
    /// <param name="location">the location of the circle</param>
    /// <param name="radius">the radius of the circle</param>
    /// <param name="lineFrom">the starting point of the line</param>
    /// <param name="lineTo">the ending point of the line</param>
    /// <returns>true if the line and circle intersect each other</returns>
    public static bool IntersectLineCircle(Vector2 location, float radius, Vector2 lineFrom, Vector2 lineTo)
    {
        float ab2, acab, h2;
        Vector2 ac = location - lineFrom;
        Vector2 ab = lineTo - lineFrom;
        Vector2.Dot(ref ab, ref ab, out ab2);
        Vector2.Dot(ref ac, ref ab, out acab);
        float t = acab / ab2;

        if (t < 0)
            t = 0;
        else if (t > 1)
            t = 1;

        Vector2 h = ((ab * t) + lineFrom) - location;
        Vector2.Dot(ref h, ref h, out h2);

        return (h2 <= (radius * radius));
    }

여기에 이미지 설명을 입력하십시오

' VB.NET - Code

Function CheckLineSegmentCircleIntersection(x1 As Double, y1 As Double, x2 As Double, y2 As Double, xc As Double, yc As Double, r As Double) As Boolean
    Static xd As Double = 0.0F
    Static yd As Double = 0.0F
    Static t As Double = 0.0F
    Static d As Double = 0.0F
    Static dx_2_1 As Double = 0.0F
    Static dy_2_1 As Double = 0.0F

    dx_2_1 = x2 - x1
    dy_2_1 = y2 - y1

    t = ((yc - y1) * dy_2_1 + (xc - x1) * dx_2_1) / (dy_2_1 * dy_2_1 + dx_2_1 * dx_2_1)

    If 0 <= t And t <= 1 Then
        xd = x1 + t * dx_2_1
        yd = y1 + t * dy_2_1

        d = Math.Sqrt((xd - xc) * (xd - xc) + (yd - yc) * (yd - yc))
        Return d <= r
    Else
        d = Math.Sqrt((xc - x1) * (xc - x1) + (yc - y1) * (yc - y1))
        If d <= r Then
            Return True
        Else
            d = Math.Sqrt((xc - x2) * (xc - x2) + (yc - y2) * (yc - y2))
            If d <= r Then
                Return True
            Else
                Return False
            End If
        End If
    End If
End Function

나는 주어진 대답에 따라 iOS 용이 기능을 만들었습니다. chmike

+ (NSArray *)intersectionPointsOfCircleWithCenter:(CGPoint)center withRadius:(float)radius toLinePoint1:(CGPoint)p1 andLinePoint2:(CGPoint)p2
{
    NSMutableArray *intersectionPoints = [NSMutableArray array];

    float Ax = p1.x;
    float Ay = p1.y;
    float Bx = p2.x;
    float By = p2.y;
    float Cx = center.x;
    float Cy = center.y;
    float R = radius;


    // compute the euclidean distance between A and B
    float LAB = sqrt( pow(Bx-Ax, 2)+pow(By-Ay, 2) );

    // compute the direction vector D from A to B
    float Dx = (Bx-Ax)/LAB;
    float Dy = (By-Ay)/LAB;

    // Now the line equation is x = Dx*t + Ax, y = Dy*t + Ay with 0 <= t <= 1.

    // compute the value t of the closest point to the circle center (Cx, Cy)
    float t = Dx*(Cx-Ax) + Dy*(Cy-Ay);

    // This is the projection of C on the line from A to B.

    // compute the coordinates of the point E on line and closest to C
    float Ex = t*Dx+Ax;
    float Ey = t*Dy+Ay;

    // compute the euclidean distance from E to C
    float LEC = sqrt( pow(Ex-Cx, 2)+ pow(Ey-Cy, 2) );

    // test if the line intersects the circle
    if( LEC < R )
    {
        // compute distance from t to circle intersection point
        float dt = sqrt( pow(R, 2) - pow(LEC,2) );

        // compute first intersection point
        float Fx = (t-dt)*Dx + Ax;
        float Fy = (t-dt)*Dy + Ay;

        // compute second intersection point
        float Gx = (t+dt)*Dx + Ax;
        float Gy = (t+dt)*Dy + Ay;

        [intersectionPoints addObject:[NSValue valueWithCGPoint:CGPointMake(Fx, Fy)]];
        [intersectionPoints addObject:[NSValue valueWithCGPoint:CGPointMake(Gx, Gy)]];
    }

    // else test if the line is tangent to circle
    else if( LEC == R ) {
        // tangent point to circle is E
        [intersectionPoints addObject:[NSValue valueWithCGPoint:CGPointMake(Ex, Ey)]];
    }
    else {
        // line doesn't touch circle
    }

    return intersectionPoints;
}

서클은 정말 나쁜 사람입니다 :) 따라서 좋은 방법은 가능한 경우 진정한 서클을 피하는 것입니다. 게임의 충돌 검사를 수행하는 경우 몇 가지 단순화를 수행하고 3 개의 도트 제품과 몇 가지 비교 만 할 수 있습니다.

나는 이것을 "뚱뚱한 지점"또는 "가는 원"이라고 부릅니다. 세그먼트와 평행 한 방향으로 반지름이 0 인 타원. 세그먼트에 수직 인 방향으로 전체 반경

먼저 과도한 데이터를 피하기 위해 좌표 시스템의 이름을 바꾸고 좌표계를 전환하는 것을 고려할 것입니다.

s0s1 = B-A;
s0qp = C-A;
rSqr = r*r;

둘째, hvec2f의 인덱스 h는 vector보다 dot () / det ()와 같은 수평 연산을 선호해야 함을 의미합니다. 즉, 셔플 링 / 하드 링 / 하우싱을 피하기 위해 구성 요소를 별도의 xmm 레지스터에 배치해야합니다. 그리고 우리는 2D 게임을위한 가장 간단한 충돌 감지 기능을 가지고 있습니다 :

bool fat_point_collides_segment(const hvec2f& s0qp, const hvec2f& s0s1, const float& rSqr) {
    auto a = dot(s0s1, s0s1);
    //if( a != 0 ) // if you haven't zero-length segments omit this, as it would save you 1 _mm_comineq_ss() instruction and 1 memory fetch
    {
        auto b = dot(s0s1, s0qp);
        auto t = b / a; // length of projection of s0qp onto s0s1
        //std::cout << "t = " << t << "\n";
        if ((t >= 0) && (t <= 1)) // 
        {
            auto c = dot(s0qp, s0qp);
            auto r2 = c - a * t * t;
            return (r2 <= rSqr); // true if collides
        }
    }   
    return false;
}

더 이상 최적화 할 수없는 것 같습니다. 수백만 개의 반복 단계를 처리하기 위해 신경망 구동 자동차 경주 충돌 감지에 사용하고 있습니다.


이 Java 함수는 DVec2 객체를 반환합니다. 그것은 소요 DVec2 원의 중심, 원의 반지름, 그리고 라인을.

public static DVec2 CircLine(DVec2 C, double r, Line line)
{
    DVec2 A = line.p1;
    DVec2 B = line.p2;
    DVec2 P;
    DVec2 AC = new DVec2( C );
    AC.sub(A);
    DVec2 AB = new DVec2( B );
    AB.sub(A);
    double ab2 = AB.dot(AB);
    double acab = AC.dot(AB);
    double t = acab / ab2;

    if (t < 0.0) 
        t = 0.0;
    else if (t > 1.0) 
        t = 1.0;

    //P = A + t * AB;
    P = new DVec2( AB );
    P.mul( t );
    P.add( A );

    DVec2 H = new DVec2( P );
    H.sub( C );
    double h2 = H.dot(H);
    double r2 = r * r;

    if(h2 > r2) 
        return null;
    else
        return P;
}

C # (부분 서클 클래스)의 또 다른 하나. 테스트하고 매력처럼 작동합니다.

public class Circle : IEquatable<Circle>
{
    // ******************************************************************
    // The center of a circle
    private Point _center;
    // The radius of a circle
    private double _radius;

   // ******************************************************************
    /// <summary>
    /// Find all intersections (0, 1, 2) of the circle with a line defined by its 2 points.
    /// Using: http://math.stackexchange.com/questions/228841/how-do-i-calculate-the-intersections-of-a-straight-line-and-a-circle
    /// Note: p is the Center.X and q is Center.Y
    /// </summary>
    /// <param name="linePoint1"></param>
    /// <param name="linePoint2"></param>
    /// <returns></returns>
    public List<Point> GetIntersections(Point linePoint1, Point linePoint2)
    {
        List<Point> intersections = new List<Point>();

        double dx = linePoint2.X - linePoint1.X;

        if (dx.AboutEquals(0)) // Straight vertical line
        {
            if (linePoint1.X.AboutEquals(Center.X - Radius) || linePoint1.X.AboutEquals(Center.X + Radius))
            {
                Point pt = new Point(linePoint1.X, Center.Y);
                intersections.Add(pt);
            }
            else if (linePoint1.X > Center.X - Radius && linePoint1.X < Center.X + Radius)
            {
                double x = linePoint1.X - Center.X;

                Point pt = new Point(linePoint1.X, Center.Y + Math.Sqrt(Radius * Radius - (x * x)));
                intersections.Add(pt);

                pt = new Point(linePoint1.X, Center.Y - Math.Sqrt(Radius * Radius - (x * x)));
                intersections.Add(pt);
            }

            return intersections;
        }

        // Line function (y = mx + b)
        double dy = linePoint2.Y - linePoint1.Y;
        double m = dy / dx;
        double b = linePoint1.Y - m * linePoint1.X;

        double A = m * m + 1;
        double B = 2 * (m * b - m * _center.Y - Center.X);
        double C = Center.X * Center.X + Center.Y * Center.Y - Radius * Radius - 2 * b * Center.Y + b * b;

        double discriminant = B * B - 4 * A * C;

        if (discriminant < 0)
        {
            return intersections; // there is no intersections
        }

        if (discriminant.AboutEquals(0)) // Tangeante (touch on 1 point only)
        {
            double x = -B / (2 * A);
            double y = m * x + b;

            intersections.Add(new Point(x, y));
        }
        else // Secant (touch on 2 points)
        {
            double x = (-B + Math.Sqrt(discriminant)) / (2 * A);
            double y = m * x + b;
            intersections.Add(new Point(x, y));

            x = (-B - Math.Sqrt(discriminant)) / (2 * A);
            y = m * x + b;
            intersections.Add(new Point(x, y));
        }

        return intersections;
    }

    // ******************************************************************
    // Get the center
    [XmlElement("Center")]
    public Point Center
    {
        get { return _center; }
        set
        {
            _center = value;
        }
    }

    // ******************************************************************
    // Get the radius
    [XmlElement]
    public double Radius
    {
        get { return _radius; }
        set { _radius = value; }
    }

    //// ******************************************************************
    //[XmlArrayItemAttribute("DoublePoint")]
    //public List<Point> Coordinates
    //{
    //    get { return _coordinates; }
    //}

    // ******************************************************************
    // Construct a circle without any specification
    public Circle()
    {
        _center.X = 0;
        _center.Y = 0;
        _radius = 0;
    }

    // ******************************************************************
    // Construct a circle without any specification
    public Circle(double radius)
    {
        _center.X = 0;
        _center.Y = 0;
        _radius = radius;
    }

    // ******************************************************************
    // Construct a circle with the specified circle
    public Circle(Circle circle)
    {
        _center = circle._center;
        _radius = circle._radius;
    }

    // ******************************************************************
    // Construct a circle with the specified center and radius
    public Circle(Point center, double radius)
    {
        _center = center;
        _radius = radius;
    }

    // ******************************************************************
    // Construct a circle based on one point
    public Circle(Point center)
    {
        _center = center;
        _radius = 0;
    }

    // ******************************************************************
    // Construct a circle based on two points
    public Circle(Point p1, Point p2)
    {
        Circle2Points(p1, p2);
    }

필수 :

using System;

namespace Mathematic
{
    public static class DoubleExtension
    {
        // ******************************************************************
        // Base on Hans Passant Answer on:
        // http://stackoverflow.com/questions/2411392/double-epsilon-for-equality-greater-than-less-than-less-than-or-equal-to-gre

        /// <summary>
        /// Compare two double taking in account the double precision potential error.
        /// Take care: truncation errors accumulate on calculation. More you do, more you should increase the epsilon.
        public static bool AboutEquals(this double value1, double value2)
        {
            if (double.IsPositiveInfinity(value1))
                return double.IsPositiveInfinity(value2);

            if (double.IsNegativeInfinity(value1))
                return double.IsNegativeInfinity(value2);

            if (double.IsNaN(value1))
                return double.IsNaN(value2);

            double epsilon = Math.Max(Math.Abs(value1), Math.Abs(value2)) * 1E-15;
            return Math.Abs(value1 - value2) <= epsilon;
        }

        // ******************************************************************
        // Base on Hans Passant Answer on:
        // http://stackoverflow.com/questions/2411392/double-epsilon-for-equality-greater-than-less-than-less-than-or-equal-to-gre

        /// <summary>
        /// Compare two double taking in account the double precision potential error.
        /// Take care: truncation errors accumulate on calculation. More you do, more you should increase the epsilon.
        /// You get really better performance when you can determine the contextual epsilon first.
        /// </summary>
        /// <param name="value1"></param>
        /// <param name="value2"></param>
        /// <param name="precalculatedContextualEpsilon"></param>
        /// <returns></returns>
        public static bool AboutEquals(this double value1, double value2, double precalculatedContextualEpsilon)
        {
            if (double.IsPositiveInfinity(value1))
                return double.IsPositiveInfinity(value2);

            if (double.IsNegativeInfinity(value1))
                return double.IsNegativeInfinity(value2);

            if (double.IsNaN(value1))
                return double.IsNaN(value2);

            return Math.Abs(value1 - value2) <= precalculatedContextualEpsilon;
        }

        // ******************************************************************
        public static double GetContextualEpsilon(this double biggestPossibleContextualValue)
        {
            return biggestPossibleContextualValue * 1E-15;
        }

        // ******************************************************************
        /// <summary>
        /// Mathlab equivalent
        /// </summary>
        /// <param name="dividend"></param>
        /// <param name="divisor"></param>
        /// <returns></returns>
        public static double Mod(this double dividend, double divisor)
        {
            return dividend - System.Math.Floor(dividend / divisor) * divisor;
        }

        // ******************************************************************
    }
}

다음은 JavaScript에서 유용한 솔루션입니다 (모든 필수 수학 및 라이브 일러스트레이션 포함) https://bl.ocks.org/milkbread/11000965

is_on해당 솔루션의 기능을 수정해야 하지만 :

function is_on(a, b, c) {
    return Math.abs(distance(a,c) + distance(c,b) - distance(a,b))<0.000001;
}


@Mizipzor가 제안한 (투영 사용) 아이디어에 따라 TypeScript의 솔루션은 다음과 같습니다.

/**
 * Determines whether a line segment defined by a start and end point intersects with a sphere defined by a center point and a radius
 * @param a the start point of the line segment
 * @param b the end point of the line segment
 * @param c the center point of the sphere
 * @param r the radius of the sphere
 */
export function lineSphereIntersects(
  a: IPoint,
  b: IPoint,
  c: IPoint,
  r: number
): boolean {
  // find the three sides of the triangle formed by the three points
  const ab: number = distance(a, b);
  const ac: number = distance(a, c);
  const bc: number = distance(b, c);

  // check to see if either ends of the line segment are inside of the sphere
  if (ac < r || bc < r) {
    return true;
  }

  // find the angle between the line segment and the center of the sphere
  const numerator: number = Math.pow(ac, 2) + Math.pow(ab, 2) - Math.pow(bc, 2);
  const denominator: number = 2 * ac * ab;
  const cab: number = Math.acos(numerator / denominator);

  // find the distance from the center of the sphere and the line segment
  const cd: number = Math.sin(cab) * ac;

  // if the radius is at least as long as the distance between the center and the line
  if (r >= cd) {
    // find the distance between the line start and the point on the line closest to
    // the center of the sphere
    const ad: number = Math.cos(cab) * ac;
    // intersection occurs when the point on the line closest to the sphere center is
    // no further away than the end of the line
    return ad <= ab;
  }
  return false;
}

export function distance(a: IPoint, b: IPoint): number {
  return Math.sqrt(
    Math.pow(b.z - a.z, 2) + Math.pow(b.y - a.y, 2) + Math.pow(b.x - a.x, 2)
  );
}

export interface IPoint {
  x: number;
  y: number;
  z: number;
}

다음은 golang으로 작성된 솔루션입니다. 이 방법은 여기에 게시 된 다른 답변과 비슷하지만 완전히 동일하지는 않습니다. 구현하기 쉽고 테스트를 거쳤습니다. 단계는 다음과 같습니다.

  1. 원이 원점이되도록 좌표를 변환합니다.
  2. x 및 y 좌표 모두에 대해 선분을 t의 매개 변수화 된 함수로 표현합니다. t가 0이면 함수 값은 세그먼트의 한 끝 점이고 t가 1이면 함수 값은 다른 끝점입니다.
  3. 가능한 경우, x, y를 생성하는 t의 제한 값으로 인한 2 차 방정식을 원의 반지름과 동일한 원점으로부터의 거리로 해결합니다.
  4. t가 <0 또는> 1 (개방 세그먼트의 경우 <= 0 또는> = 1) 인 솔루션을 버리십시오. 해당 포인트는 세그먼트에 포함되지 않습니다.
  5. 원래 좌표로 다시 변환합니다.

2 차에 대한 A, B 및 C의 값은 여기에서 도출되며, 여기서 (n-et) 및 (m-dt)는 각각 선의 x 및 y 좌표에 대한 방정식입니다. r은 원의 반지름입니다.

(n-et)(n-et) + (m-dt)(m-dt) = rr
nn - 2etn + etet + mm - 2mdt + dtdt = rr
(ee+dd)tt - 2(en + dm)t + nn + mm - rr = 0

따라서 A = ee + dd, B =-2 (en + dm) 및 C = nn + mm-rr입니다.

함수의 golang 코드는 다음과 같습니다.

package geom

import (
    "math"
)

// SegmentCircleIntersection return points of intersection between a circle and
// a line segment. The Boolean intersects returns true if one or
// more solutions exist. If only one solution exists, 
// x1 == x2 and y1 == y2.
// s1x and s1y are coordinates for one end point of the segment, and
// s2x and s2y are coordinates for the other end of the segment.
// cx and cy are the coordinates of the center of the circle and
// r is the radius of the circle.
func SegmentCircleIntersection(s1x, s1y, s2x, s2y, cx, cy, r float64) (x1, y1, x2, y2 float64, intersects bool) {
    // (n-et) and (m-dt) are expressions for the x and y coordinates
    // of a parameterized line in coordinates whose origin is the
    // center of the circle.
    // When t = 0, (n-et) == s1x - cx and (m-dt) == s1y - cy
    // When t = 1, (n-et) == s2x - cx and (m-dt) == s2y - cy.
    n := s2x - cx
    m := s2y - cy

    e := s2x - s1x
    d := s2y - s1y

    // lineFunc checks if the  t parameter is in the segment and if so
    // calculates the line point in the unshifted coordinates (adds back
    // cx and cy.
    lineFunc := func(t float64) (x, y float64, inBounds bool) {
        inBounds = t >= 0 && t <= 1 // Check bounds on closed segment
        // To check bounds for an open segment use t > 0 && t < 1
        if inBounds { // Calc coords for point in segment
            x = n - e*t + cx
            y = m - d*t + cy
        }
        return
    }

    // Since we want the points on the line distance r from the origin,
    // (n-et)(n-et) + (m-dt)(m-dt) = rr.
    // Expanding and collecting terms yeilds the following quadratic equation:
    A, B, C := e*e+d*d, -2*(e*n+m*d), n*n+m*m-r*r

    D := B*B - 4*A*C // discriminant of quadratic
    if D < 0 {
        return // No solution
    }
    D = math.Sqrt(D)

    var p1In, p2In bool
    x1, y1, p1In = lineFunc((-B + D) / (2 * A)) // First root
    if D == 0.0 {
        intersects = p1In
        x2, y2 = x1, y1
        return // Only possible solution, quadratic has one root.
    }

    x2, y2, p2In = lineFunc((-B - D) / (2 * A)) // Second root

    intersects = p1In || p2In
    if p1In == false { // Only x2, y2 may be valid solutions
        x1, y1 = x2, y2
    } else if p2In == false { // Only x1, y1 are valid solutions
        x2, y2 = x1, y1
    }
    return
}

솔루션 포인트가 선 세그먼트와 원 안에 있음을 확인하는이 기능으로 테스트했습니다. 테스트 세그먼트를 만들고 지정된 원 주위로 스윕합니다.

package geom_test

import (
    "testing"

    . "**put your package path here**"
)

func CheckEpsilon(t *testing.T, v, epsilon float64, message string) {
    if v > epsilon || v < -epsilon {
        t.Error(message, v, epsilon)
        t.FailNow()
    }
}

func TestSegmentCircleIntersection(t *testing.T) {
    epsilon := 1e-10      // Something smallish
    x1, y1 := 5.0, 2.0    // segment end point 1
    x2, y2 := 50.0, 30.0  // segment end point 2
    cx, cy := 100.0, 90.0 // center of circle
    r := 80.0

    segx, segy := x2-x1, y2-y1

    testCntr, solutionCntr := 0, 0

    for i := -100; i < 100; i++ {
        for j := -100; j < 100; j++ {
            testCntr++
            s1x, s2x := x1+float64(i), x2+float64(i)
            s1y, s2y := y1+float64(j), y2+float64(j)

            sc1x, sc1y := s1x-cx, s1y-cy
            seg1Inside := sc1x*sc1x+sc1y*sc1y < r*r
            sc2x, sc2y := s2x-cx, s2y-cy
            seg2Inside := sc2x*sc2x+sc2y*sc2y < r*r

            p1x, p1y, p2x, p2y, intersects := SegmentCircleIntersection(s1x, s1y, s2x, s2y, cx, cy, r)

            if intersects {
                solutionCntr++
                //Check if points are on circle
                c1x, c1y := p1x-cx, p1y-cy
                deltaLen1 := (c1x*c1x + c1y*c1y) - r*r
                CheckEpsilon(t, deltaLen1, epsilon, "p1 not on circle")

                c2x, c2y := p2x-cx, p2y-cy
                deltaLen2 := (c2x*c2x + c2y*c2y) - r*r
                CheckEpsilon(t, deltaLen2, epsilon, "p2 not on circle")

                // Check if points are on the line through the line segment
                // "cross product" of vector from a segment point to the point
                // and the vector for the segment should be near zero
                vp1x, vp1y := p1x-s1x, p1y-s1y
                crossProd1 := vp1x*segy - vp1y*segx
                CheckEpsilon(t, crossProd1, epsilon, "p1 not on line ")

                vp2x, vp2y := p2x-s1x, p2y-s1y
                crossProd2 := vp2x*segy - vp2y*segx
                CheckEpsilon(t, crossProd2, epsilon, "p2 not on line ")

                // Check if point is between points s1 and s2 on line
                // This means the sign of the dot prod of the segment vector
                // and point to segment end point vectors are opposite for
                // either end.
                wp1x, wp1y := p1x-s2x, p1y-s2y
                dp1v := vp1x*segx + vp1y*segy
                dp1w := wp1x*segx + wp1y*segy
                if (dp1v < 0 && dp1w < 0) || (dp1v > 0 && dp1w > 0) {
                    t.Error("point not contained in segment ", dp1v, dp1w)
                    t.FailNow()
                }

                wp2x, wp2y := p2x-s2x, p2y-s2y
                dp2v := vp2x*segx + vp2y*segy
                dp2w := wp2x*segx + wp2y*segy
                if (dp2v < 0 && dp2w < 0) || (dp2v > 0 && dp2w > 0) {
                    t.Error("point not contained in segment ", dp2v, dp2w)
                    t.FailNow()
                }

                if s1x == s2x && s2y == s1y { //Only one solution
                    // Test that one end of the segment is withing the radius of the circle
                    // and one is not
                    if seg1Inside && seg2Inside {
                        t.Error("Only one solution but both line segment ends inside")
                        t.FailNow()
                    }
                    if !seg1Inside && !seg2Inside {
                        t.Error("Only one solution but both line segment ends outside")
                        t.FailNow()
                    }

                }
            } else { // No intersection, check if both points outside or inside
                if (seg1Inside && !seg2Inside) || (!seg1Inside && seg2Inside) {
                    t.Error("No solution but only one point in radius of circle")
                    t.FailNow()
                }
            }
        }
    }
    t.Log("Tested ", testCntr, " examples and found ", solutionCntr, " solutions.")
}

테스트 결과는 다음과 같습니다.

=== RUN   TestSegmentCircleIntersection
--- PASS: TestSegmentCircleIntersection (0.00s)
    geom_test.go:105: Tested  40000  examples and found  7343  solutions.

마지막으로, 방법은 t> 0 또는 t <1인지 여부 만 테스트하여 한 지점에서 시작하여 다른 지점을 통과하고 무한대로 확장되는 광선의 경우로 쉽게 확장 할 수 있습니다.


방금 필요했기 때문에이 솔루션을 생각해 냈습니다. 언어는 maxscript이지만 다른 언어로 쉽게 번역해야합니다. sideA, sideB 및 CircleRadius는 스칼라이고 나머지 변수는 [x, y, z]와 같은 점입니다. 비행기 XY에서 해결하기 위해 z = 0이라고 가정합니다.

fn projectPoint p1 p2 p3 = --project  p1 perpendicular to the line p2-p3
(
    local v= normalize (p3-p2)
    local p= (p1-p2)
    p2+((dot v p)*v)
)
fn findIntersectionLineCircle CircleCenter CircleRadius LineP1 LineP2=
(
    pp=projectPoint CircleCenter LineP1 LineP2
    sideA=distance pp CircleCenter
    --use pythagoras to solve the third side
    sideB=sqrt(CircleRadius^2-sideA^2) -- this will return NaN if they don't intersect
    IntersectV=normalize (pp-CircleCenter)
    perpV=[IntersectV.y,-IntersectV.x,IntersectV.z]
    --project the point to both sides to find the solutions
    solution1=pp+(sideB*perpV)
    solution2=pp-(sideB*perpV)
    return #(solution1,solution2)
)

참고 URL : https://stackoverflow.com/questions/1073336/circle-line-segment-collision-detection-algorithm

반응형