ขั้นตอนวิธีการตรวจจับการชนกันของส่วนวงกลม


197

ฉันมีเส้นจาก A ถึง B และวงกลมวางตำแหน่งที่ C กับรัศมี R

ภาพ

อัลกอริทึมที่ดีที่ใช้ในการตรวจสอบว่าเส้นตัดกันวงกลมหรือไม่ และมันเกิดขึ้นที่พิกัดใดตามขอบวงกลม?


4
อืมมม คำถามหนึ่ง: คุณกำลังพูดถึงเส้นไม่สิ้นสุดผ่าน A และ B หรือส่วนของเส้น จำกัด จาก A ถึง B?
เจสัน S

2
ในกรณีนี้ส่วนของเส้นที่ จำกัด "เส้น" เรียกว่าอย่างอื่นขึ้นอยู่กับว่ามันแน่นอนหรือไม่มีที่สิ้นสุด?
Mizipzor

1
มีข้อกำหนดด้านประสิทธิภาพหรือไม่ ควรเป็นวิธีที่รวดเร็วหรือไม่
chmike

ณ จุดนี้ไม่อัลกอริทึมทั้งหมดที่ฉันพยายามไม่ได้ทำให้แอปพลิเคชันช้าลงอย่างเห็นได้ชัด
Mizipzor

13
@Mizipzor ใช่พวกเขาจะเรียกว่าอย่างอื่น: สายส่วน หากคุณเพียงแค่พูดว่า "บรรทัด" มันก็ส่อถึงอนันต์
MestreLion

คำตอบ:


200

สละ

  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 ที่นี่วิธีการแก้ปัญหาที่เรานำมาใช้ในรูปแบบ 3 มิติ

ที่จะได้รับ:

  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
    (e x + td x ) 2 - 2 (e x + td x ) h + h 2 + (e y + td y ) 2 - 2 (e y + td y ) k + k 2 - r 2 = 0
  3. ระเบิด
    อีx 2 + 2e x td x + T 2 d x 2 - 2e x H - 2TD x H + H 2 + E ปี2 + 2e Y td Y + T 2 d Y 2 - 2e Y k - 2TD Y k + k 2 - r 2 = 0
  4. กลุ่ม
    t 2 (d x 2 + d y 2 ) + 2t (e x d x + e y d 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 และ * คือ ผลิตภัณฑ์ dot *
  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
ดังนั้นการแก้สมการกำลังสอง:

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 คืออะไร? k เป็นค่าคงที่ที่เส้น / เรย์เพิ่มบน y ส่วน x หรือไม่ และ t คืออะไร? ดูรหัสดูเหมือนว่าคุณคิดว่ามันเป็น 1 (ดังนั้นมันเพิ่ง "ลบ") สูตรเหล่านี้มีชื่อหรืออะไร? บางทีฉันสามารถค้นหารายละเอียดเกี่ยวกับ Wolfram ได้
Mizipzor

3
h และ k เป็นศูนย์กลางของวงกลมที่คุณตัดกัน t คือพารามิเตอร์ของสมการเส้นตรง ในรหัส t1 และ t2 เป็นคำตอบ t1 และ t2 บอกคุณว่า "ไกลแค่ไหนไปตามแนวรัศมี" จุดตัดเกิดขึ้น
bobobobo

1
ตกลงเข้าใจแล้ว ผลิตภัณฑ์ดอทนั้นคำนวณได้ง่ายบนเวกเตอร์สามองค์ประกอบ (x, y, z) ฉันจะเปลี่ยนรหัสเป็นอัลกอริทึมนี้
chmike

21
P = E + t * dคือtอะไร
Derek 朕會功夫

3
ไม่แน่ใจว่าทำไม แต่รหัสไม่ทำงานสำหรับกรณี Impale มันจะทำอย่างไรเมื่อฉันเพิ่มถ้า t1 <= 0 && t1> = -1 && t2 <= 0 && t2> = -1 เป็นเงื่อนไขจริง แต่จากนั้นมันก็ให้ค่าเป็นบวกเท็จที่ด้านหนึ่งของเส้น จำกัด เมื่อวงกลม อยู่ในส่วน "อนันต์" ฉันยังไม่เข้าใจคณิตศาสตร์ แต่คัดลอก / paster ระวัง
Nicolas Mommaerts

142

ดูเหมือนจะไม่มีใครพิจารณาฉายภาพเลย

โครงการเวกเตอร์บน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. Axe และ Ay เป็นองค์ประกอบ x และ y ของจุด A เช่นเดียวกับ B และ C สเกลาร์ 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

ขอบคุณ ฉันชอบความคิดเห็น pgm นี้เป็นคำอธิบายเพราะไม่มีการใช้คำว่า "ผลิตภัณฑ์ดอท" เนื่องจากคณิตศาสตร์ของฉันเป็นสนิม อย่างไรก็ตาม t และ dt ไม่อยู่ระหว่าง 0..1 ดังนั้นในขณะที่เปลี่ยนสิ่งนี้เป็น python ฉันเปลี่ยน t เพื่อหารด้วย LAB ** 2 ความเข้าใจของฉันคือส่วนแรกโดย LAB คือการฉายจุดศูนย์กลางของวงกลมไปยังบรรทัด AB และส่วนที่สองโดย LAB คือการทำให้มาตรฐานเป็นแนวปกติในช่วง 0..1 นอกจากนี้ dt จะต้องถูกหารด้วย LAB ดังนั้นจึงเป็นมาตรฐาน ดังนั้น "if (t-dt> = 0.0)" มีจุดตัดแรกอยู่ "หาก (t + dt <= 1.0)" มีจุดตัดที่สองอยู่ สิ่งนี้ใช้ได้กับการทดสอบ
punchcard

2
เพราะจุดตัดกับวงกลมอยู่ที่ "ระยะทาง" t+dtและt-dtบนเส้น tคือจุดบนบรรทัดที่อยู่ใกล้กับศูนย์กลางของวงกลมมากที่สุด tจุดตัดกับวงกลมที่มีความสมมาตรระยะทางจาก จุดแยกอยู่ที่ "ระยะทาง" และt-dt t+dtฉันอ้างระยะทางเพราะมันไม่ใช่ระยะทางยูคลิด เพื่อให้ได้ระยะทางแบบยุคลิดจากAที่คุณจะต้องคูณค่าโดยt=0 LAB
chmike

1
@Matt W คุณหมายถึง "วิธีการตรวจสอบว่าจุดตัดเกิดขึ้นนอกจุดสิ้นสุดของส่วนบรรทัด AB"? แค่คิดเกี่ยวกับ t เป็นการวัดระยะทางตามแนวเส้น จุด A t=0อยู่ที่ จุด B t=LABที่ เมื่อจุดตัดทั้งสอง ( t1=t-tdและt2=t+td) มีค่าลบมากกว่าจุดตัดอยู่นอกส่วน (หลังจุด A มองจากด้านส่วนของจุด) เมื่อ t1 และ t2 ใหญ่กว่า LAB แล้วพวกเขาก็อยู่ข้างนอกด้วย (คราวนี้อยู่หลังจุด B) Intersection t1 (หรือ t2) เกิดขึ้นระหว่าง A และ B เฉพาะเมื่อ t1 (หรือ t2) มันอยู่ระหว่าง 0 และ LAB
Marconius

20

ตกลงฉันจะไม่ให้รหัส แต่เนื่องจากคุณติดแท็กนี้ ฉันไม่คิดว่าจะมีความสำคัญกับคุณ ก่อนอื่นคุณต้องได้เวกเตอร์ตั้งฉากกับเส้นตรง

คุณจะมีตัวแปรที่ไม่รู้จักในy = ax + c ( cจะไม่ทราบ)
เพื่อแก้ปัญหานั้นให้คำนวณค่าของมันเมื่อบรรทัดผ่านไปยังกึ่งกลางของวงกลม

นั่นคือเสียบสถานที่ตั้งของศูนย์วงกลมสมการเส้นและแก้ปัญหาสำหรับ
จากนั้นคำนวณจุดตัดของเส้นเดิมและเส้นปกติ c

สิ่งนี้จะให้จุดที่ใกล้ที่สุดในบรรทัดไปยังวงกลม
คำนวณระยะห่างระหว่างจุดนี้กับจุดศูนย์กลางวงกลม (โดยใช้ขนาดของเวกเตอร์)
หากนี่น้อยกว่ารัศมีของวงกลม - voila เรามีทางแยก!


2
นั่นคือสิ่งที่ฉันต้องการ ฉันต้องการทฤษฏีการค้นหา google ของอัลกอริธึมการชนกันของเส้นวงกลมจะเปลี่ยนเป็นรหัสเท่าที่ฉันเห็น
Mizipzor

ตกลง c ไม่เป็นที่รู้จักในสมการของคุณ แต่ "a" คืออะไร? คำตอบอื่น ๆ ดูเหมือนจะอ้างถึงตัวแปรนั้นว่า "alpha" และ "t" แม้ว่านี่เป็นเพียงฟังก์ชันเชิงเส้น (y = kx + m) ซึ่งเป็นคณิตศาสตร์พื้นฐานค่อนข้างมากดังนั้นฉันจึงรู้สึกว่าเป็นสนิมเล็กน้อย ยังไม่ทราบหรือไม่ หรือคุณหมายความว่าเราสามารถสันนิษฐานได้ m = 0 และแก้ k? ถ้างั้น m (นั่นคือ c) จะเป็นศูนย์เสมอสำหรับ k ที่เราแก้แล้ว?
Mizipzor

1
โอ้ขอโทษ - ฉันใช้สมการง่าย ๆ ของเส้นที่มีการไล่ระดับสีและออฟเซ็ต (สมการคาร์ทีเซียน) ฉันสันนิษฐานว่าคุณกำลังจัดเก็บเส้นเป็นสมการดังกล่าวซึ่งในกรณีนี้คุณใช้ค่าลบของการไล่ระดับสีสำหรับ k หากคุณไม่มีเส้นที่ถูกจัดเก็บไว้เช่นนี้คุณสามารถคำนวณ k เป็น (y2-y1) / (x2-x1)
a_m0d

1
เราไม่คิดว่า 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:

คุณสามารถปรับรหัสให้เหมาะสมโดยใช้การคำนวณรากที่สองของ Inverse ที่อธิบายไว้ที่นี่เพื่อรับการประมาณ 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 แตกต่างจาก B และความยาวของเซกเมนต์ไม่เป็นโมฆะหากสิ่งนี้อาจเกิดขึ้นในแอปพลิเคชันของคุณ


2
ฉันชอบความเรียบง่ายในวิธีนี้ บางทีฉันสามารถปรับรหัสรอบข้างบางตัวเพื่อไม่ต้องการจุดชนจริงได้เองดูว่าเกิดอะไรขึ้นถ้าฉันใช้ A หรือ B แทนที่จะเป็นจุดที่คำนวณได้
Mizipzor

1
t = Dx * (Cx-Axe) + Dy * (Cy-Axe) ควรอ่าน t = Dx * (Cx-Axe) + Dy * (Cy-Ay)
Stonetip

ถูกต้องแล้ว ขอบคุณสำหรับการชี้ให้เห็น ฉันแก้ไขมันในโพสต์
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's และลบyของแล้ววางมันลงในจุดใหม่ในกรณีที่ไม่มีใครรู้

อย่างไรก็ตามตอนนี้ฉันแก้สมการของเส้นด้วยp3และ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

ดังนั้นฉันจึงใส่มันลงในสูตรกำลังสอง:

(-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 

จากนั้นเพียงแค่เสียบผลมาจากทั้งสองสมการเหล่านั้นเข้ามาในx 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เสริม ')'
Jeevan

4

คุณสามารถหาจุดบนเส้นอนันต์ที่ใกล้กับจุดศูนย์กลางของวงกลมโดยฉายเวกเตอร์ AC ลงบนเวกเตอร์ AB คำนวณระยะห่างระหว่างจุดนั้นกับจุดศูนย์กลางวงกลม ถ้ายิ่งใหญ่กว่า R ก็จะไม่มีทางแยก ถ้าระยะทางเท่ากับ R เส้นนั้นจะแทนเจนต์ของวงกลมและจุดที่ใกล้กับศูนย์กลางของวงกลมจริงๆแล้วคือจุดตัดกัน ถ้าระยะทางน้อยกว่า R นั้นจะมีจุดตัด 2 จุด พวกมันอยู่ในระยะเดียวกันจากจุดที่ใกล้ที่สุดถึงศูนย์กลางวงกลม สามารถคำนวณระยะทางได้อย่างง่ายดายโดยใช้ทฤษฎีบทพีทาโกรัส นี่คืออัลกอริทึมใน pseudocode:

{
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

หมายเหตุ: วิธีนี้ใช้งานได้ดีกับสาย แต่ไม่สามารถใช้กับส่วนของเส้นได้
ไมค์

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

หากคุณพบระยะห่างระหว่างจุดศูนย์กลางของทรงกลม (เนื่องจากเป็นแบบ 3 มิติฉันถือว่าคุณหมายถึงทรงกลมและไม่ใช่วงกลม) และเส้นจากนั้นตรวจสอบว่าระยะทางนั้นน้อยกว่ารัศมีที่จะทำการหลอกลวง

จุดปะทะกันนั้นเป็นจุดที่อยู่ใกล้ที่สุดระหว่างเส้นกับทรงกลม (ซึ่งจะถูกคำนวณเมื่อคุณคำนวณระยะห่างระหว่างทรงกลมกับเส้น)

ระยะทางระหว่างจุดหนึ่งถึงหนึ่งเส้น:
http://mathworld.wolfram.com/Point-LineDistance3-Dimensional.html


1
มันอยู่ใน 2D ไม่ใช่ 3D; อย่างที่คุณพูดสิ่งนี้ไม่สำคัญเลย
Martijn

ฉันไม่ใช่นักคณิตศาสตร์ดังนั้นฉันคิดว่าฉันควรจัดทำวิธีการทั่วไปให้ดีขึ้นและปล่อยให้ผู้อื่นคิดคณิตศาสตร์เฉพาะ (แม้ว่าฉันจะดูค่อนข้างไม่สำคัญ)
Martin

2
+1 ด้วยการโหวตที่แข็งแกร่ง (แม้ว่าฉันจะมีการเชื่อมโยงไปยังเว็บไซต์อื่นเว็บไซต์ pbourke ดูสับสน) คำตอบอื่น ๆ ทั้งหมดจะซับซ้อนเกินไป แม้ว่าความคิดเห็นของคุณ "จุดนั้นก็เป็นจุดตัดบนบรรทัด" ไม่ถูกต้อง แต่ไม่มีจุดที่ถูกสร้างขึ้นในกระบวนการคำนวณ
เจสัน S

2
mathworld.wolfram.com/Point-LineDistance3-Dimensional.htmlและmathworld.wolfram.com/Point-LineDistance2-Dimensional.htmlจะดีกว่าและจากเว็บไซต์ที่มีชื่อเสียงมากขึ้น
เจสัน S

ผมอธิบายเล็ก ๆ น้อย ๆ ที่ดีเกี่ยวกับจุดที่ใกล้ที่สุดและเชื่อมโยงกับแม ธ เวิลด์แทน pbourke :)
มาร์ติน

3

นี่คือการใช้งานใน Javascript วิธีการของฉันคือการแปลงส่วนของเส้นให้เป็นเส้นที่ไม่มีที่สิ้นสุดก่อนแล้วจึงหาจุดตัดกัน จากนั้นฉันจะตรวจสอบว่าพบจุดที่อยู่ในส่วนของเส้น รหัสมีเอกสารที่ดีคุณควรจะทำตาม

คุณสามารถลองใช้รหัสที่นี่ในการสาธิตสดนี้ รหัสถูกนำมาจากฉันขั้นตอนวิธีการซื้อคืน

ป้อนคำอธิบายรูปภาพที่นี่

// 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

ในการชนกันของเส้นวงกลมโพสต์นี้จะถูกตรวจสอบโดยการตรวจสอบระยะห่างระหว่างจุดศูนย์กลางวงกลมและจุดบนส่วนของเส้นตรง (Ipoint) ที่แสดงถึงจุดตัดระหว่าง N (รูปภาพ 2) ปกติจากจุดศูนย์กลางวงกลมไปยังส่วนของเส้น

( 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 ถูกฉายบน Vector D โดย "dot product" ของเวกเตอร์ E และเวกเตอร์หน่วย D, ผลลัพธ์ของ dot product คือ scalar Xp ที่แสดงระยะทางระหว่างจุดเริ่มต้นของบรรทัดและจุดตัด (Ipoint) ของ เวกเตอร์ N และเวกเตอร์ D. เวกเตอร์ X ถัดไปพบได้โดยการคูณเวกเตอร์หน่วย D และสเกลาร์ Xp

ตอนนี้เราต้องหาเวกเตอร์ Z (เวกเตอร์ถึง Ipoint) มันง่ายที่จะเพิ่มเวกเตอร์ A อย่างง่ายของเวกเตอร์ A (จุดเริ่มต้นบนบรรทัด) และเวกเตอร์ X ถัดไปเราต้องจัดการกับกรณีพิเศษที่เราต้องตรวจสอบว่า ไม่ใช่ว่าเราจะต้องหาว่ามันเหลืออยู่หรือทางขวาเราจะใช้เวกเตอร์ที่ใกล้เคียงที่สุดเพื่อกำหนดว่าจุดใดที่อยู่ใกล้วงกลมมากที่สุด

( https://i.stack.imgur.com/p9WIr.png )ภาพที่ 3. หาจุดที่ใกล้เคียงที่สุด

เมื่อโปรเจค Xp เป็นลบ Ipoint จะเหลือส่วนของเส้นตรงเวกเตอร์ที่ใกล้เคียงที่สุดจะเท่ากับเวกเตอร์ของจุดเริ่มต้นของเส้นเมื่อโปรเจค Xp มีค่ามากกว่าขนาดของเวกเตอร์ D แล้ว Ipoint เป็นด้านขวาของส่วนของเส้น จุดในกรณีอื่นใดเวกเตอร์ที่ใกล้เคียงที่สุดเท่ากับเวกเตอร์ Z

ตอนนี้เมื่อเรามีเวกเตอร์ที่ใกล้เคียงที่สุดเราต้องหาเวกเตอร์จากวงกลมตรงกลางถึง Ipoint (dist เวกเตอร์) มันง่ายที่เราแค่ต้องลบเวกเตอร์ที่ใกล้เคียงที่สุดจากเวกเตอร์ตรงกลาง จากนั้นตรวจสอบว่าเวกเตอร์ขนาดขยายน้อยกว่ารัศมีวงถ้ามันชนกันหรือไม่ถ้ามันไม่มีชนกัน

( https://i.stack.imgur.com/QJ63q.png )รูปที่ 4. การตรวจสอบการชน

ในตอนท้ายเราสามารถคืนค่าบางค่าสำหรับการแก้ไขการชนวิธีที่ง่ายที่สุดคือการคืนค่าการทับซ้อนของการชนกัน (ลบรัศมีจากขนาดเวกเตอร์) และแกนส่งคืนของการชนเวกเตอร์ D ของมันด้วย


2

หากพิกัดของเส้นคือ Axe, Ay และ Bx, By และศูนย์กลางของวงกลมคือ Cx, Cy สูตรนั้นจะเป็น:

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

y = Ay * t + โดย * (1 - t)

โดยที่ 0 <= t <= 1

และวงกลมก็คือ

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

ถ้าคุณแทนสูตร x และ y ของบรรทัดลงในสูตรวงกลมคุณจะได้สมการอันดับสองของ t และวิธีแก้ปัญหาคือจุดตัด (ถ้ามี) หากคุณไปถึงจุดที่เล็กกว่า 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 # (คลาส Circle บางส่วน) ผ่านการทดสอบและใช้งานได้อย่างมีเสน่ห์

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

นี่คือทางออกที่ดีใน 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;
}


2

Circle เป็นคนเลวจริงๆ :) ดังนั้นวิธีที่ดีคือหลีกเลี่ยงการใช้แวดวงจริงถ้าทำได้ หากคุณกำลังตรวจสอบการชนกันของเกมคุณสามารถใช้การทำให้เข้าใจง่ายและมีผลิตภัณฑ์แบบ 3 จุดและมีการเปรียบเทียบกันเล็กน้อย

ฉันเรียกสิ่งนี้ว่า "fat point" หรือ "thin circle" ชนิดของวงรีที่มีรัศมีเป็นศูนย์ในทิศทางขนานกับเซกเมนต์ แต่รัศมีเต็มในทิศทางตั้งฉากกับส่วน

ก่อนอื่นฉันจะพิจารณาเปลี่ยนชื่อและสลับระบบพิกัดเพื่อหลีกเลี่ยงข้อมูลมากเกินไป:

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

ประการที่สองดัชนี h ใน hvec2f หมายถึงเวกเตอร์ต้องสนับสนุนการปฏิบัติการ horisontal เช่น dot () / det () ซึ่งหมายความว่าส่วนประกอบของมันจะต้องอยู่ใน xmm register แยกกันเพื่อหลีกเลี่ยงการสับ / hadd'ing / hsub'ing และที่นี่เราไปด้วยการตรวจจับการชนที่ง่ายที่สุดสำหรับเกม 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;
}

ฉันสงสัยว่าคุณสามารถเพิ่มประสิทธิภาพได้อีก ฉันใช้มันสำหรับการตรวจจับการชนกันของรถแข่งที่ขับเคลื่อนด้วยโครงข่ายประสาทเทียมเพื่อประมวลผลซ้ำหลายล้านขั้นตอน


หากส่วนของเส้นตัดกับวงกลม แต่มีเพียงเล็กน้อยเพื่อให้ไม่ผ่านจุดศูนย์กลางฟังก์ชันนี้จะไม่ส่งคืนค่าเท็จเมื่อมันควรกลับเป็นจริง ค่า t อาจอยู่นอกช่วง 0..1
Chris

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

นี่คือโซลูชันของฉันใน TypeScript ตามแนวคิดที่ @Mizipzor แนะนำ (โดยใช้การฉายภาพ):

/**
 * 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 กล่าว ดังนั้นฉันจึงเพิ่มการเปรียบเทียบบางอย่างเพื่อดูว่าส่วนของเส้นตรงแตะที่วงกลมหรือไม่ฉันเขียนไว้ใน Python

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. แสดงส่วนของเส้นตรงเป็นฟังก์ชัน parametrized ของ t สำหรับพิกัด x และ y ถ้า t เป็น 0 ค่าของฟังก์ชันคือจุดสิ้นสุดจุดหนึ่งของส่วนและถ้า t คือ 1 ค่าของฟังก์ชันจะเป็นจุดสิ้นสุดอีกจุดหนึ่ง
  3. แก้ไขถ้าเป็นไปได้สมการกำลังสองที่เกิดจากค่าที่ จำกัด ของ t ที่สร้าง x, y พิกัดกับระยะทางจากจุดกำเนิดเท่ากับรัศมีของวงกลม
  4. ทิ้งโซลูชันที่ t คือ <0 หรือ> 1 (<= 0 หรือ> = 1 สำหรับเซกเมนต์เปิด) จุดเหล่านั้นไม่ได้อยู่ในส่วน
  5. แปลกลับเป็นพิกัดดั้งเดิม

ค่าสำหรับ 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] ฉันสมมติว่า z = 0 เพื่อแก้ปัญหาบนระนาบ XY

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

วิธีแก้ปัญหาใน python โดยอิงจาก @Joe Skeen

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.