서클 라인 세그먼트 충돌 감지 알고리즘?


195

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

영상

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


4
흠. 한 가지 질문 : A와 B를 통한 무한 선 또는 A에서 B까지의 유한 선분에 대해 이야기하고 있습니까?
Jason S

2
이 경우 유한 선분입니다. "줄"이 유한한지 아닌지에 따라 다른 것으로 불리는가?
Mizipzor

1
성능 요구 사항이 있습니까? 빠른 방법이어야합니까?
chmike

이 시점에서 아니요. 여기서 시도한 모든 알고리즘으로 인해 응용 프로그램이 눈에 띄게 느려지지는 않습니다.
Mizipzor

13
@Mizipzor 그렇습니다. 그것들은 다른 것입니다 : line segment . "라인"이라고하면 무한한 의미가됩니다.
MestreLion

답변:


200

취득

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

계산 :
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 = e 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 ;
}

1
직접 복사하여 붙여 넣으면 작동하는 것 같지만 이해하려고합니다. (xh) ^ 2 + (yk) ^ 2 = r ^ 2에서 h와 k는 무엇입니까? x에 비해 y에서 라인 / 레이가 증가하는 k는 일정합니까? 그리고 t는 무엇입니까? 코드를 보면 1을 가정 한 것 같습니다 (따라서 "제거 된"것입니다). 이 공식에는 이름이나 무언가가 있습니까? Wolfram에서 자세히 살펴볼 수도 있습니다.
Mizipzor

3
h와 k는 교차하는 원의 중심입니다. t는 선 방정식의 매개 변수입니다. 코드에서 t1과 t2가 해결책입니다. t1과 t2는 교차점이 발생한 "선을 따라 얼마나 멀리"있는지 알려줍니다.
bobobobo

1
알았어 내적은 단순히 세 요소 (x, y, z) 벡터에 대해 계산됩니다. 코드를이 알고리즘으로 전환하겠습니다.
chmike

21
P = E + t * d무엇입니까 t?
데릭 朕 會 功夫

3
이유는 확실하지 않지만 Impale 사례에서는 코드가 작동하지 않는 것 같습니다. t1 <= 0 && t1> = -1 && t2 <= 0 && t2> = -1을 참 조건으로 추가하면 원이 될 때 유한 선의 한쪽에 위양성을 제공합니다. "무한한"부분에 있습니다. 아직 수학을 이해하지 못하지만 복사 / 붙여 넣기를주의하십시오.
Nicolas Mommaerts

141

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

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

이처럼 :
SchoolBoy의 이미지


9
고려해야 할 많은 세부 사항이 있습니다. D는 AB 사이에 있습니까? 선과의 C 수직 거리가 반지름보다 큽니까? 이들 모두는 벡터의 크기, 즉 제곱근을 포함합니다.
ADB

15
좋은 생각이지만 두 교차점을 어떻게 계산합니까?

4
@ 스파이더 그것은 중요하지 않습니다. 일반적으로 이것은 구형 라인 교차 문제의 변형이므로 Mizipzor의 전략은 완벽하게 유효합니다. CD투영법이며, 정의상 수직입니다.

2
이것은 오래된 질문이지만이 웹 사이트에는이 알고리즘 및 관련 알고리즘에 대한 유용한 자료가 있습니다. paulbourke.net/geometry/pointlineplane
Andrew

1
이 답변에 대한 훌륭한 설명 : scratchapixel.com/lessons/3d-basic-rendering/…
ShawnFeatherly

50

알고리즘을 사용하여 점 (원 중심)과 선 (선 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

원과 교차하지 않는 선과 그 점 p1 및 p2가 원 안에있는 경우 이 경우 알고리즘이 어떻게 작동합니까 ??
Prashant

1
t-dt 및 t + dt를 테스트해야합니다. t-dt <0이면 p1보다 원 안에 있습니다. t + dt> 1 인 경우 p2보다 원 안에 있습니다. 물론 LEC <R 인 경우에도 마찬가지입니다.
chmike

감사. 수학이 녹슨 것이기 때문에 "dot product"라는 단어를 사용하지 않았기 때문에이 pgm 설명을 설명으로 좋아했습니다. 그러나 t와 dt는 0..1 사이가 아니므로 이것을 파이썬으로 변경하는 동안 t를 LAB ** 2로 나누도록 변경했습니다. LAB에 의한 첫 번째 구분은 원의 중심을 선 AB에 투영하는 것이고 LAB에 의한 두 번째 구분은 0..1 범위로 정규화하는 것입니다. 또한 dt는 LAB로 나누어야하므로 정규화됩니다. 따라서 "if (t-dt> = 0.0)"첫 번째 교차가 "if (t + dt <= 1.0)"이면 두 번째 교차가 존재합니다. 이것은 테스트와 함께 작동했습니다.
punchcard

2
원과의 교점이 "거리" t+dtt-dt선에 있기 때문입니다. t원의 중심에 가장 가까운 선의 점입니다. 원과의 교점은에서 대칭 거리에 있습니다 t. 교차점은 "거리" t-dtt+dt입니다. 그것이 유클리드 거리가 아니기 때문에 거리를 인용했습니다. A어디 에서 유클리드 거리를 얻으려면 값에을 t=0곱해야합니다 LAB.
chmike

1
@Matt W : "선 구간 AB의 끝점 밖에서 교차가 발생하는지 확인하는 방법"을 의미합니까? t를 직선을 따르는 거리의 척도로 생각하십시오. A 지점은 t=0입니다. 에서 B를 가리 킵니다 t=LAB. 두 교차점 ( t1=t-tdt2=t+td)이 모두 음수 값을 갖는 경우 교차점이 섹션 외부에 있습니다 (점 A의 단면을 바라본 점 A 뒤). t1과 t2가 LAB보다 크면 외부에도 있습니다 (이번에는 B 지점 뒤). 교차점 t1 (또는 t2)은 t1 (또는 t2)이 0과 LAB 사이 인 경우에만 A와 B 사이에서 발생합니다.
Marconius

20

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

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

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

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


2
그것은 사실 내가 원했던 것입니다. 나는 이론을 원한다. 라인 원 충돌 알고리즘의 Google 검색은 내가 볼 수있는 한 코드 만 나타납니다.
Mizipzor

좋아, c는 방정식에서 알 수 없지만 "a"는 무엇입니까? 다른 답변은 해당 변수를 "알파"와 "t"라고합니다. 비록 이것은 선형 함수 (y = kx + m), 아주 기본적인 수학 일뿐이므로 갑자기 조금 녹슨 느낌이 듭니다. k도 알 수 없습니까? 아니면 우리가 m = 0이라고 가정하고 k를 풀 수 있다는 것을 의미합니까? 그러면 해결 된 k에 대해 m (즉, c)이 항상 0입니까?
Mizipzor

1
죄송합니다. 그래디언트 및 오프셋 (직교 방정식)이있는 간단한 선 방정식을 사용하고 있습니다. 나는 당신이 그런 식으로 선을 저장한다고 가정했습니다.이 경우 k에 대해 음의 기울기를 사용합니다. 이렇게 저장된 행이 없으면 k를 (y2-y1) / (x2-x1)으로
계산할 수 있습니다

1
우리는 m이 0이라고 가정하지 않습니다. 우리는 먼저 그라디언트를 계산합니다 (예를 들어 선의 방정식은 y = 2x + m처럼 보입니다). 그라디언트가 있으면 원의 중심에 y와 x를 꽂아 m을 풀 수 있습니다. .
a_m0d

1
+1 멋진 설명! 그러나 나는 이것이 선 세그먼트가 아니라 선을 가정한다고 생각합니다. 따라서이 선에서 원의 중심까지 가장 가까운 점이 점 A와 B 사이에 있지 않은 경우에도 계산됩니다.
Hassan

12

다른 방법은 삼각형 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와 다르고 세그먼트 길이가 널이 아닌지 확인해야합니다.


2
이 방법의 단순함을 좋아합니다. 어쩌면 실제 충돌 지점 자체가 필요하지 않도록 주변 코드 중 일부를 조정할 수 있습니다. 사이의 계산 된 지점 대신 A 또는 B를 사용하면 어떻게되는지 볼 수 있습니다.
Mizipzor

1
t = Dx * (Cx-Ax) + Dy * (Cy-Ax)는 t = Dx * (Cx-Ax) + Dy * (Cy-Ay)를
읽어야합니다.

맞습니다. 지적 해 주셔서 감사합니다. 게시물에서 수정했습니다.
chmike

방금 편집 됨-첫 번째 줄 은 내적이 아닌 교차 곱을 사용하여 삼각형 영역을 계산 합니다. 여기에 코드로 확인 : stackoverflow.com/questions/2533011/…
ericsoco

4
또한이 답변의 전반부는 선분이 아닌 선과의 교집합을 테스트합니다 (질문에서 요청한대로).
ericsoco

8

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

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/


5

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

취득:

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];
    }
}

이게 도움이 되길 바란다!

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


가능하면 흐름을 빠르게 파악할 수 있도록 샘플 값을 게시하십시오.
Prabindh

underRadical')'추가
byJeevan

4

벡터 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
  }
}

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


선분에 대해 이야기하고 있기 때문에 하나의 사례를 놓쳤습니다.
ADB

@ADB 실제로 내 알고리즘은 선 세그먼트가 아닌 무한 선에만 작동합니다. 선분으로 처리하지 않는 경우가 많습니다.
Juozas Kontvainis

원래 질문은 원 선 교차가 아닌 선 세그먼트에 관한 것이 었습니다.
msumme

4

이상하게도 대답 할 수는 있지만 언급 할 수는 없습니다 ... 나는 원의 중심이 원점에 놓 이도록 모든 것을 이동시키는 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];
    }
}

1
제안 : 먼저, 선분이 수직 인 경우를 처리하십시오 (예 : 무한 경사). 둘째, 실제로 원래 선 세그먼트의 범위 내에 해당하는 점만 반환하도록하십시오. 점이 선 세그먼트 밖에있는 경우에도 무한 선에 속하는 모든 점을 행복하게 반환한다고 생각합니다.
Gino

참고 : 이것은 선에는 적합하지만 선 세그먼트에는 작동하지 않습니다.
Mike

3

여기 수학이 필요합니다.

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의 좌표를 계산하면 교차점이있는 경우 교차점을 얻습니다.


3

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

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

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


1
3D가 아니라 2D입니다.
아시다시피

내가 더 일반적인 접근 방식을 요약하고 (내가보기 오히려 사소한는하지만) 특정 수학에서 그림을 다른 사람에게 떠날 줄 알았는데, 그래서 나는 어떤 수학자는 아니지만
마틴

2
강한 공감대를 가진 +1. (내가 다른 사이트에 링크했을지라도 pbourke 사이트는 혼란스러워 보입니다) 지금까지의 다른 모든 대답은 너무 복잡합니다. "이 점은 선의 교점이기도합니다"라는 설명이 올바르지 않지만 계산 프로세스에서 구성된 점은 없습니다.
Jason S


가장 가까운 점에 대해 조금 더 잘 설명하고 pbourke 대신 mathworld에 연결했습니다. :)
Martin

3

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

라이브 데모 에서 코드를 시험해 볼 수 있습니다 . 코드는 알고리즘 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 [];

}

3

이 포스트에서 원 중심과 선분 사이의 법선 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입니다.


2

선의 좌표가 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 공식을 원 공식으로 대체하면 t의 2 차 방정식이 나오고 그 해는 교점입니다 (있는 경우). 어느 것이 0보다 작거나 1보다 크면 솔루션이 아니지만 선이 원의 방향을 '포인팅'한다는 것을 나타냅니다.


2

이 스레드에 추가 한 것입니다 ... 아래는 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));
    }

C # / XNA에서는 다음을 사용할 수 있습니다Ray.Intersects(BoundingSphere)
bobobobo

2

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

' 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

2

나는 주어진 대답에 따라 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;
}

2

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;
        }

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


2

서클은 정말 나쁜 사람입니다 :) 따라서 좋은 방법은 가능한 경우 진정한 서클을 피하는 것입니다. 게임의 충돌 검사를 수행하는 경우 몇 가지 단순화를 수행하고 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;
}

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


선분이 원과 교차하지만 중심점을 통과하지 않고 약간만 교차하면 true를 반환해야 할 때이 함수가 false를 반환하지 않습니까? t 값이 0..1 범위를 벗어날 수 있습니다.
크리스

1

이 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;
}

1

@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;
}

1

이 스레드가 열린 이후로 오랜 시간이 걸렸습니다. chmike의 답변에서 Aqib Mumtaz가 개선했습니다. Aqib에 따르면 그들은 좋은 대답을하지만 무한한 선에서만 작동합니다. 그래서 선분이 원에 닿는 지 비교하기 위해 비교를 추가하고, 파이썬으로 작성합니다.

def LineIntersectCircle(c, r, p1, p2):
    #p1 is the first line point
    #p2 is the second line point
    #c is the circle's center
    #r is the circle's radius

    p3 = [p1[0]-c[0], p1[1]-c[1]]
    p4 = [p2[0]-c[0], p2[1]-c[1]]

    m = (p4[1] - p3[1]) / (p4[0] - p3[0])
    b = p3[1] - m * p3[0]

    underRadical = math.pow(r,2)*math.pow(m,2) + math.pow(r,2) - math.pow(b,2)

    if (underRadical < 0):
        print("NOT")
    else:
        t1 = (-2*m*b+2*math.sqrt(underRadical)) / (2 * math.pow(m,2) + 2)
        t2 = (-2*m*b-2*math.sqrt(underRadical)) / (2 * math.pow(m,2) + 2)
        i1 = [t1+c[0], m * t1 + b + c[1]]
        i2 = [t2+c[0], m * t2 + b + c[1]]

        if p1[0] > p2[0]:                                           #Si el punto 1 es mayor al 2 en X
            if (i1[0] < p1[0]) and (i1[0] > p2[0]):                 #Si el punto iX esta entre 2 y 1 en X
                if p1[1] > p2[1]:                                   #Si el punto 1 es mayor al 2 en Y
                    if (i1[1] < p1[1]) and (i1[1] > p2[1]):         #Si el punto iy esta entre 2 y 1
                        print("Intersection")
                if p1[1] < p2[1]:                                   #Si el punto 2 es mayo al 2 en Y
                    if (i1[1] > p1[1]) and (i1[1] < p2[1]):         #Si el punto iy esta entre 1 y 2
                        print("Intersection")

        if p1[0] < p2[0]:                                           #Si el punto 2 es mayor al 1 en X
            if (i1[0] > p1[0]) and (i1[0] < p2[0]):                 #Si el punto iX esta entre 1 y 2 en X
                if p1[1] > p2[1]:                                   #Si el punto 1 es mayor al 2 en Y
                    if (i1[1] < p1[1]) and (i1[1] > p2[1]):         #Si el punto iy esta entre 2 y 1
                        print("Intersection")
                if p1[1] < p2[1]:                                   #Si el punto 2 es mayo al 2 en Y
                    if (i1[1] > p1[1]) and (i1[1] < p2[1]):         #Si el punto iy esta entre 1 y 2
                        print("Intersection")

        if p1[0] > p2[0]:                                           #Si el punto 1 es mayor al 2 en X
            if (i2[0] < p1[0]) and (i2[0] > p2[0]):                 #Si el punto iX esta entre 2 y 1 en X
                if p1[1] > p2[1]:                                   #Si el punto 1 es mayor al 2 en Y
                    if (i2[1] < p1[1]) and (i2[1] > p2[1]):         #Si el punto iy esta entre 2 y 1
                        print("Intersection")
                if p1[1] < p2[1]:                                   #Si el punto 2 es mayo al 2 en Y
                    if (i2[1] > p1[1]) and (i2[1] < p2[1]):         #Si el punto iy esta entre 1 y 2
                        print("Intersection")

        if p1[0] < p2[0]:                                           #Si el punto 2 es mayor al 1 en X
            if (i2[0] > p1[0]) and (i2[0] < p2[0]):                 #Si el punto iX esta entre 1 y 2 en X
                if p1[1] > p2[1]:                                   #Si el punto 1 es mayor al 2 en Y
                    if (i2[1] < p1[1]) and (i2[1] > p2[1]):         #Si el punto iy esta entre 2 y 1
                        print("Intersection")
                if p1[1] < p2[1]:                                   #Si el punto 2 es mayo al 2 en Y
                    if (i2[1] > p1[1]) and (i2[1] < p2[1]):         #Si el punto iy esta entre 1 y 2
                        print("Intersection")

0

다음은 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인지 여부 만 테스트하여 한 지점에서 시작하여 다른 지점을 통과하고 무한대로 확장되는 광선의 경우로 쉽게 확장 할 수 있습니다.


0

방금 필요했기 때문에이 솔루션을 생각해 냈습니다. 언어는 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)
)

0

@Joe Skeen 기반의 Python 솔루션

def check_line_segment_circle_intersection(line, point, radious):
    """ Checks whether a point intersects with a line defined by two points.

    A `point` is list with two values: [2, 3]

    A `line` is list with two points: [point1, point2]

    """
    line_distance = distance(line[0], line[1])
    distance_start_to_point = distance(line[0], point)
    distance_end_to_point = distance(line[1], point)

    if (distance_start_to_point <= radious or distance_end_to_point <= radious):
        return True

    # angle between line and point with law of cosines
    numerator = (math.pow(distance_start_to_point, 2)
                 + math.pow(line_distance, 2)
                 - math.pow(distance_end_to_point, 2))
    denominator = 2 * distance_start_to_point * line_distance
    ratio = numerator / denominator
    ratio = ratio if ratio <= 1 else 1  # To account for float errors
    ratio = ratio if ratio >= -1 else -1  # To account for float errors
    angle = math.acos(ratio)

    # distance from the point to the line with sin projection
    distance_line_to_point = math.sin(angle) * distance_start_to_point

    if distance_line_to_point <= radious:
        point_projection_in_line = math.cos(angle) * distance_start_to_point
        # Intersection occurs whent the point projection in the line is less
        # than the line distance and positive
        return point_projection_in_line <= line_distance and point_projection_in_line >= 0
    return False

def distance(point1, point2):
    return math.sqrt(
        math.pow(point1[1] - point2[1], 2) +
        math.pow(point1[0] - point2[0], 2)
    )

0
Function lineCircleCollision(p1,p2,c,r,precision){
Let dx = (p2.x-p1.x)/precision
Let dy = (p2.y-p1.y)/precision
Let collision=false
For(let i = 0;i<precision:i++){
If(Math.sqrt((p1.x+dx*i-c.x)**2+(p1.y+dy*i-c.y)**2).<r {
Collision=true
}
}

선에서 균일 한 간격으로 X 점을 찍을 수 있으며 원 안에 있으면 충돌이 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.