คุณจะตรวจสอบได้อย่างไรว่าส่วนของเส้นสองเส้นตัดกันอย่างไร [ปิด]


518

ฉันจะตรวจสอบได้อย่างไรว่าเส้นสองเส้นตัดกันหรือไม่และถ้าเป็นเช่นนั้นจะทำอะไรที่ x, y point


มันอาจช่วยให้คิดถึงขอบของสี่เหลี่ยมเป็นเส้นแยกแทนที่จะเป็นรูปหลายเหลี่ยมที่สมบูรณ์
Ryan Graham

ผู้ดำเนินรายการหมายเหตุ : การอภิปรายว่าโพสต์นี้อยู่ในหัวข้ออยู่ในMeta Stack Overflowหรือไม่ความคิดเห็นเพิ่มเติมเกี่ยวกับที่นี่จะถูกลบทิ้ง
Martijn Pieters

คำตอบ:


659

มีวิธีการที่ดีในการแก้ไขปัญหานี้ซึ่งใช้ผลิตภัณฑ์ครอสเวกเตอร์ กําหนด 2 มิติเวกเตอร์สินค้าข้ามวี  ×  Wจะเป็นวีx  W Y  -  วีY  W x

สมมติว่าทั้งสองกลุ่มสายวิ่งจากหน้าไปP  +  Rและจากคิวที่จะQ  +  s จากนั้นจุดใด ๆ บนบรรทัดแรกจะสามารถแทนได้เป็นp  +  t  r (สำหรับพารามิเตอร์ scalar  t ) และจุดใด ๆ บนบรรทัดที่สองเป็นq  +  u  s (สำหรับพารามิเตอร์ scalar  u )

Two line segments intersecting

เส้นสองเส้นตัดกันถ้าเราหาtและu ได้ว่า:

p + t  r = q + u  s

Formulae for the point of intersection

ข้ามทั้งสองข้างด้วยsรับ

( p + t  r ) × s = ( q + u  s ) × s

และเนื่องจากs  ×  s = 0 นี่หมายถึง

t  ( r × s ) = ( q - p ) × s

ดังนั้นการหาค่าt :

t = ( q - p ) × s / ( r × s )

ในทำนองเดียวกันเราสามารถแก้ปัญหาให้คุณได้ :

( p + t  r ) × r = ( q + u  s ) × r

คุณ  ( s × r ) = ( p - q ) × r

u = ( p - q ) × r / ( s × r )

หากต้องการลดจำนวนขั้นตอนการคำนวณคุณสามารถเขียนสิ่งต่อไปนี้ใหม่ได้ง่าย ๆ (จำได้ว่าs  ×  r = -  r  ×  s ):

u = ( q - p ) × r / ( r × s )

ขณะนี้มีสี่กรณี:

  1. ถ้าr  ×  s  = 0 และ ( q  -  p ) ×  r  = 0 ดังนั้นทั้งสองเส้นคือ collinear

    ในกรณีนี้แสดงจุดสิ้นสุดของส่วนที่สอง ( qและq  +  s ) ในแง่ของสมการของส่วนของบรรทัดแรก ( p + t r ):

    t 0 = ( q - p ) ·  r / ( r  ·  r )

    t 1 = ( q + s - p ) ·  r / ( r  ·  r ) = t 0 + s  ·  r / ( r  ·  r )

    หากช่วงเวลาระหว่างt 0และt 1ตัดช่วงเวลา [0, 1] ดังนั้นส่วนของเส้นตรงนั้นเป็น collinear และทับซ้อนกัน มิฉะนั้นพวกเขาจะ collinear และ disjoint

    โปรดทราบว่าถ้าsและrชี้ไปในทิศทางตรงกันข้ามดังนั้นs  ·  r <0 และดังนั้นช่วงเวลาที่จะตรวจสอบคือ [ t 1 , t 0 ] มากกว่า [ t 0 , t 1 ]

  2. ถ้าr  ×  s  = 0 และ ( q  -  p ) ×  r  ≠ 0 ทั้งสองเส้นนั้นขนานกันและไม่ตัดกัน

  3. ถ้าR  ×  s  ≠ 0 คนและ 0 ≤  T  ≤ 1 และ 0 ≤  ยู  ≤ 1 ทั้งสองกลุ่มสายพบกันที่จุดP + T  R = Q + U  s

  4. มิฉะนั้นส่วนของเส้นสองเส้นนั้นไม่ขนานกัน แต่อย่าตัดกัน

เครดิต: วิธีการนี้เป็นความเชี่ยวชาญแบบสองมิติของอัลกอริทึมการแยกบรรทัด 3 มิติจากบทความ "จุดตัดของสองบรรทัดในสามช่องว่าง" โดย Ronald Goldman เผยแพร่ในGraphics Gemsหน้า 304 ในสามมิติกรณีปกติคือ เส้นเอียง (ไม่ขนานหรือตัดกัน) ซึ่งในกรณีนี้วิธีการให้คะแนนของวิธีการที่ใกล้ที่สุดของทั้งสองสาย


5
@myrkos: ไม่เซ็กเมนต์บรรทัดแรกจะเรียกใช้ "จาก p ถึง p + r" ดังนั้นเมื่อมันแสดงด้วยเงื่อนไขแบบพารามิเตอร์เป็น "p + tr" ดังนั้นเซ็กเมนต์นั้นจะสอดคล้องกับ 0 ≤ t ≤ 1 ในทำนองเดียวกันสำหรับเซ็กเมนต์อื่น
Gareth Rees

7
แกเร็ ธ ฉันรู้สึกว่าฉันต้องคิดถึงบางสิ่งบางอย่าง แต่คุณจะแบ่งเวกเตอร์ด้วยเวกเตอร์ได้อย่างไร คำตอบของคุณสำหรับtและuลงท้ายด้วย/ (r × s), แต่(r × s)เวกเตอร์, จริงไหม? (0, 0, rx * sy - ry * sx)เวกเตอร์ และด้านซ้ายมือก็มีเวกเตอร์ขนานกับแกน z งั้น ... ฉันจะแบ่งองค์ประกอบ z ด้วยองค์ประกอบ z ตัวอื่นหรือไม่? สูตรสำหรับ t เป็นจริง|(q − p) × s| / |(r × s)|หรือไม่?
LarsH

7
@ LarsH: ดูย่อหน้าแรก
Gareth Rees

35
สำหรับผู้ที่สนใจนี่คือการติดตั้ง C # แบบง่ายโดยใช้จุดเริ่มต้นและจุดสิ้นสุดของพิกัดสำหรับสายที่ดูเหมือนจะใช้งานได้: ideone.com/PnPJgb
Matt

24
ฉันรวบรวมการใช้งานจาวาสคริปต์ตามด้วย @Matt ฉันแก้ไขข้อผิดพลาดที่ชี้ให้เห็นโดย Tekito
pgkelley

230

FWIW ฟังก์ชั่นต่อไปนี้ (ใน C) ตรวจจับจุดตัดของเส้นและกำหนดจุดตัด มันขึ้นอยู่กับอัลกอริทึมใน " เคล็ดลับการเขียนโปรแกรมเกม Windows ของ Andre LeMothe " ไม่ใช่ความแตกต่างของอัลกอริทึมบางคำตอบอื่น ๆ (เช่นของ Gareth) LeMothe ใช้กฎของ Cramer (ไม่ต้องถามฉัน) เพื่อแก้สมการด้วยตนเอง

ฉันสามารถยืนยันได้ว่ามันทำงานในโคลนดาวเคราะห์น้อยที่อ่อนแอของฉันและดูเหมือนว่าจะจัดการอย่างถูกต้องกับกรณีขอบที่อธิบายไว้ในคำตอบอื่น ๆ โดย Elemental, Dan และ Wodzu มันอาจเร็วกว่าโค้ดที่โพสต์โดย KingNestor เพราะมันเป็นการคูณและการหารไม่มีรากที่สอง!

ฉันเดาว่ามีความเป็นไปได้ที่จะหารด้วยศูนย์แม้ว่ามันจะไม่เป็นปัญหาในกรณีของฉัน ง่ายพอที่จะแก้ไขเพื่อหลีกเลี่ยงความผิดพลาดอยู่ดี

// Returns 1 if the lines intersect, otherwise 0. In addition, if the lines 
// intersect the intersection point may be stored in the floats i_x and i_y.
char get_line_intersection(float p0_x, float p0_y, float p1_x, float p1_y, 
    float p2_x, float p2_y, float p3_x, float p3_y, float *i_x, float *i_y)
{
    float s1_x, s1_y, s2_x, s2_y;
    s1_x = p1_x - p0_x;     s1_y = p1_y - p0_y;
    s2_x = p3_x - p2_x;     s2_y = p3_y - p2_y;

    float s, t;
    s = (-s1_y * (p0_x - p2_x) + s1_x * (p0_y - p2_y)) / (-s2_x * s1_y + s1_x * s2_y);
    t = ( s2_x * (p0_y - p2_y) - s2_y * (p0_x - p2_x)) / (-s2_x * s1_y + s1_x * s2_y);

    if (s >= 0 && s <= 1 && t >= 0 && t <= 1)
    {
        // Collision detected
        if (i_x != NULL)
            *i_x = p0_x + (t * s1_x);
        if (i_y != NULL)
            *i_y = p0_y + (t * s1_y);
        return 1;
    }

    return 0; // No collision
}

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

(4 * (4 - 1) + 12 * (7 - 1)) / (17 * 4 + 12 * 10)

= 844 / 0.88

= 0.44

ว่าฉันสับสนสำหรับชั่วโมง :(


9
ฟังก์ชัน getLineIntersection (p0_x, p0_y, p1_x, p2_x, p2_y, p3_x, p3_y) {var s1_x, s1_y, s2_x, s2_y; s1_x = p1_x - p0_x; s1_y = p1_y - p0_y; s2_x = p3_x - p2_x; s2_y = p3_y - p2_y; var s, t; s = (-s1_y * (p0_x - p2_x) + s1_x * (p0_y - p2_y)) / (-s2_x * s1_y + s1_x * s2_y); t = (s2_x * (p0_y - p2_y) - s2_y * (p0_x - p2_x)) / (-s2_x * s1_y + s1_x * s2_y);
cortijon

5
หาก (s> = 0 && s <= 1 && t> = 0 && t <= 1) {// ตรวจพบการชนกันของ var intX = p0_x + (t * s1_x); var intY = p0_y + (t * s1_y); คืน [intX, intY]; } คืนค่า null; // ไม่มีการปะทะ}
cortijon

13
อัลกอริธึมที่ดีอย่างไรก็ตาม fyi ไม่รองรับกรณีที่ดีเทอร์มีแนนต์เป็น 0 (-s2_x * s1_y + s1_x * s2_y ด้านบน) ถ้าเป็น 0 (หรือใกล้ 0) เส้นนั้นขนานหรือ collinear หากเป็นเส้นตรงสีทางแยกอาจเป็นส่วนของเส้นตรงอีกเส้น
seand

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

18
ฉันทำการทดสอบประสิทธิภาพของอัลกอริธึมทั้งหมดที่โพสต์ที่นี่และอันนี้เร็วอย่างน้อยสองเท่าของโพรโทคอลอื่น ๆ ขอบคุณสำหรับการโพสต์!
lajos

63

ปัญหาลดลงสำหรับคำถามนี้: ทำสองบรรทัดจาก A ถึง B และจาก C ถึง D ตัดกันหรือไม่ จากนั้นคุณสามารถถามได้สี่ครั้ง (ระหว่างบรรทัดและแต่ละด้านของสี่เหลี่ยมสี่มุม)

นี่คือคณิตศาสตร์เวกเตอร์สำหรับทำมัน ฉันสมมติว่าเส้นจาก A ถึง B คือเส้นคำถามและเส้นจาก C ถึง D เป็นหนึ่งในเส้นสี่เหลี่ยมผืนผ้า สัญกรณ์ของฉันคือนั่นAxคือ "พิกัด x ของ A" และCyคือ "พิกัด y ของ C. " และ " *" A*B = Ax*Bx + Ay*Byหมายถึงดอทผลิตภัณฑ์เพื่อให้เช่น

E = B-A = ( Bx-Ax, By-Ay )
F = D-C = ( Dx-Cx, Dy-Cy ) 
P = ( -Ey, Ex )
h = ( (A-C) * P ) / ( F * P )

hหมายเลขนี้เป็นกุญแจสำคัญ ถ้าhอยู่ระหว่าง0และ1เส้นตัดมิฉะนั้นพวกเขาไม่ได้ ถ้าF*Pเป็นศูนย์แน่นอนว่าคุณไม่สามารถคำนวณได้ แต่ในกรณีนี้เส้นนั้นขนานกันดังนั้นจึงตัดกันในกรณีที่เห็นได้ชัดเท่านั้น

C + F*hจุดที่แน่นอนของสี่แยกเป็น

สนุกมาก:

ถ้าhเป็นเส้นตรง 0หรือ1สัมผัสที่จุดสิ้นสุด คุณสามารถพิจารณาสิ่งนี้ว่า "สี่แยก" หรือไม่ก็ได้ตามที่เห็นสมควร

โดยเฉพาะhคือคุณต้องคูณความยาวของเส้นเพื่อสัมผัสเส้นอื่น ๆ อย่างแน่นอน

ดังนั้นถ้าh<0มันหมายความว่าเส้นสี่เหลี่ยมผืนผ้าเป็น "เบื้องหลัง" บรรทัดที่กำหนด (โดยมี "ทิศทาง" เป็น "จาก A ถึง B") และถ้าh>1เส้นสี่เหลี่ยมผืนผ้านั้นเป็น "ด้านหน้า" ของบรรทัดที่กำหนด

ที่มา:

A และ C เป็นเวกเตอร์ที่ชี้ไปที่จุดเริ่มต้นของบรรทัด; E และ F เป็นเวกเตอร์จากปลาย A และ C ที่ประกอบเป็นเส้น

สำหรับเส้นที่ไม่ขนานกันสองเส้นในระนาบจะต้องมีสเกลาร์หนึ่งคู่gและhแน่นอนว่าสมการนี้ถือ:

A + E*g = C + F*h

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

( ตอนแรกดูเหมือนว่าเป็นสมการเดียวที่มีสองนิรนาม! แต่มันไม่ใช่เมื่อคุณคิดว่านี่คือสมการเชิงเวกเตอร์สองมิติซึ่งหมายความว่านี่เป็นสมการคู่ในxและyจริง ๆ )

เราต้องกำจัดหนึ่งในตัวแปรเหล่านี้ วิธีที่ง่ายคือการทำให้Eคำนั้นเป็นศูนย์ ในการทำเช่นนั้นให้หาจุดผลคูณของทั้งสองข้างของสมการโดยใช้เวกเตอร์ที่จะจุดเป็นศูนย์ด้วย E เวกเตอร์นั้นที่ผมเรียกPข้างบนและผมทำการแปลงของ E อย่างชัดเจน

ตอนนี้คุณมี:

A*P = C*P + F*P*h
(A-C)*P = (F*P)*h
( (A-C)*P ) / (F*P) = h

29
อัลกอริทึมนี้ดี แต่มีรูอยู่ในนั้นตามที่ Dan @ stackoverflow.com/questions/563198/ … & Elemental @ stackoverflow.com/questions/563198/ ......มันจะเจ๋งถ้าคุณจะอัปเดตคำตอบสำหรับการอ้างอิงในอนาคต ขอบคุณ
Chantz

2
อัลกอริทึมนี้มีเสถียรภาพเชิงตัวเลขหรือไม่? ฉันลอง aproach similliar และมันกลับกลายเป็นว่าผลลัพธ์ที่แปลกเมื่อทำงานกับลอย
milosz

3
ดูเหมือนว่าจะมีปัญหาอื่นกับอัลกอริทึมนี้ เมื่อป้อนคะแนน A = {1, 0} B = {2, 0} C = {0, 0} D = {1,0}, แม้ว่าส่วนของเส้นตรงจะสัมผัสที่ส่วนท้าย, F P (และ E Q ซึ่งสอดคล้องกับการแก้ไขของผู้ใช้ด้านล่าง) เป็นทั้ง 0 จึงทำให้การหารด้วย 0 เพื่อหา h และ g ยังคงทำงานกับวิธีแก้ปัญหาสำหรับสิ่งนี้ แต่ฉันคิดว่าปัญหาน่าจะชี้ให้เห็น
candrews

12
คำตอบนี้ไม่ถูกต้อง ลอง A = {0,0}, B = {0,1}, C = {0,2} D = {2,0}
Tim Cooper

6
A + E*g = C + F*hเส้นสองเส้นตัดกันถ้าหากสมการของสมการนั้น (สมมติว่าพวกมันไม่ขนานกัน) จะมีทั้งคู่gและhระหว่าง 0 และ 1 (เป็น - หรือพิเศษขึ้นอยู่กับว่าคุณนับการสัมผัสที่จุดสิ้นสุด) หรือไม่
Daniel Fischer

46

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

ตัวอย่างเช่นพิจารณาคะแนน A (10,10) B (20,20) C (10,1) D (1,10) ให้ h = .5 และยังเป็นที่ชัดเจนโดยการตรวจสอบว่าส่วนเหล่านี้ไม่ได้อยู่ใกล้กัน อื่น ๆ

การทำกราฟทำให้ชัดเจนว่าเกณฑ์ 0 <h <1 ระบุว่าจุดตัดสัญญาณจะวางอยู่บนแผ่นซีดีหากมีอยู่ แต่ไม่บอกจุดใดจุดหนึ่งที่อยู่บน AB เพื่อให้แน่ใจว่ามีจุดไขว้คุณต้องทำการคำนวณสมมาตรสำหรับตัวแปร g และข้อกำหนดสำหรับการสกัดกั้นคือ: 0 <g <1 และ 0 <h <1


2
ฉันดึงผมออกมาแล้วพยายามคิดออกว่าทำไมคำตอบที่ยอมรับไม่ได้ผลกับฉัน ขอบคุณมาก!
แมตต์บริดจ์

1
ยังน่าสังเกตว่าเงื่อนไขขอบเขตทำงานในกรณีนี้ (เช่นสำหรับ h = 0 หรือ h = 1 หรือ g = 0 หรือ g = 1 สัมผัส 'เพียงแค่' บรรทัด '
Elemental

สำหรับคนที่มีปัญหาในการมองเห็นผลลัพธ์ฉันได้ใช้สิ่งนี้ใน Javascript: jsfiddle.net/ferrybig/eokwL9mp
Ferrybig

45

นี่คือการปรับปรุงคำตอบของ Gavin วิธีการแก้ปัญหาของ Marcp ก็คล้ายกัน แต่ก็ไม่เลื่อนการแบ่งออกไป

อันที่จริงแล้วนี่เป็นการใช้งานจริงของคำตอบของ Gareth Rees เช่นกันเพราะการเปรียบเทียบข้ามผลิตภัณฑ์ใน 2D เป็น perp-dot-product ซึ่งเป็นสิ่งที่รหัสนี้ใช้สาม การสลับเป็น 3D และใช้ผลิตภัณฑ์ Cross-Interpolating ทั้ง s และ t ในตอนท้ายผลลัพธ์ในสองจุดที่ใกล้เคียงที่สุดระหว่างบรรทัดใน 3D อย่างไรก็ตามโซลูชัน 2D:

int get_line_intersection(float p0_x, float p0_y, float p1_x, float p1_y, 
    float p2_x, float p2_y, float p3_x, float p3_y, float *i_x, float *i_y)
{
    float s02_x, s02_y, s10_x, s10_y, s32_x, s32_y, s_numer, t_numer, denom, t;
    s10_x = p1_x - p0_x;
    s10_y = p1_y - p0_y;
    s32_x = p3_x - p2_x;
    s32_y = p3_y - p2_y;

    denom = s10_x * s32_y - s32_x * s10_y;
    if (denom == 0)
        return 0; // Collinear
    bool denomPositive = denom > 0;

    s02_x = p0_x - p2_x;
    s02_y = p0_y - p2_y;
    s_numer = s10_x * s02_y - s10_y * s02_x;
    if ((s_numer < 0) == denomPositive)
        return 0; // No collision

    t_numer = s32_x * s02_y - s32_y * s02_x;
    if ((t_numer < 0) == denomPositive)
        return 0; // No collision

    if (((s_numer > denom) == denomPositive) || ((t_numer > denom) == denomPositive))
        return 0; // No collision
    // Collision detected
    t = t_numer / denom;
    if (i_x != NULL)
        *i_x = p0_x + (t * s10_x);
    if (i_y != NULL)
        *i_y = p0_y + (t * s10_y);

    return 1;
}

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

คุณอาจต้องการลองใช้การทดสอบ epsilon แทนการเปรียบเทียบกับศูนย์ เส้นที่อยู่ใกล้กับขนานมากสามารถให้ผลลัพธ์ที่ปิดได้เล็กน้อย นี่ไม่ใช่ข้อผิดพลาดมันเป็นข้อ จำกัด กับคณิตศาสตร์จุดลอย


1
ล้มเหลวหากบางจุดมีค่า 0 .. ที่ไม่ควรเกิดขึ้นใช่ไหม
hfossli

1
ฉันได้ทำการแก้ไขข้อผิดพลาดที่แนะนำเมื่อชะลอการหาร t อาจเป็นค่าบวกเมื่อตัวเลขและ denom ทั้งคู่เป็นลบ
iMalc

2
ไม่ทำงานหาก p0-p1 เป็นแนวตั้งและ p2-p3 เป็นแนวนอนและทั้งสองส่วนไขว้กัน (การดำเนินการส่งคืนแรก)
Fabio Dalla Libera

กรณี coolinear มีสอง possibilites: ไม่ใช่ onverlapping และทับซ้อนกัน shoul ตัวแรกคืนค่าเท็จตัวที่สองให้เป็นจริง ในรหัสของคุณนี้ไม่ได้ทดสอบ มันจะส่งกลับค่า false เป็นคำตอบที่นี่มากที่สุดเสมอ มันเป็นความอัปยศที่ดูเหมือนว่าจะไม่มีวิธีแก้ปัญหา
AlexWien

3
คุณช่วยสอนฉันได้ไหมว่าทำไมสิ่งเหล่านี้ใช้ชื่อตัวแปรที่คลุมเครือs32_yแทนสิ่งที่อธิบายว่ามันเป็นpoint2YDifferenceอย่างไร
Supuhstar

40

คำถาม C: คุณตรวจพบได้อย่างไรว่าส่วนของเส้นสองเส้นตัดกันหรือไม่

ฉันค้นหาหัวข้อเดียวกันและฉันไม่พอใจกับคำตอบ ดังนั้นฉันจึงได้เขียนบทความที่อธิบายรายละเอียดอย่างมากว่าจะตรวจสอบว่าส่วนของเส้นสองเส้นตัดกันกับภาพจำนวนมากหรือไม่ มี Java-code ที่สมบูรณ์ (และทดสอบ)

นี่คือบทความที่ถูกตัดส่วนที่สำคัญที่สุด:

อัลกอริทึมที่ตรวจสอบว่าส่วนของเส้นตรงตัดกันกับส่วนของเส้นขมีลักษณะดังนี้:

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

กล่อง จำกัด ขอบเขตคืออะไร นี่คือกล่องสองขอบเขตของสองส่วนบรรทัด:

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

ถ้ากล่องขอบทั้งสองมีจุดตัดคุณย้ายส่วนของเส้น a เพื่อให้จุดหนึ่งอยู่ที่ (0 | 0) ตอนนี้คุณมีเส้นผ่านจุดกำเนิดที่กำหนดโดย ตอนนี้ย้ายส่วนของเส้นขด้วยวิธีเดียวกันและตรวจสอบว่าจุดใหม่ของส่วนของเส้นขอยู่ในด้านต่าง ๆ ของเส้น a หากเป็นกรณีนี้ให้ตรวจสอบวิธีอื่น หากเป็นเช่นนั้นส่วนของเส้นจะตัดกัน ถ้าไม่พวกเขาจะไม่ตัดกัน

คำถาม A: ส่วนของเส้นตรงสองเส้นตัดกันที่ไหน

คุณรู้ว่าสองส่วนของเส้นตรง a และ b ตัดกัน หากคุณไม่ทราบให้ตรวจสอบด้วยเครื่องมือที่ฉันให้ไว้ใน "คำถาม C"

ตอนนี้คุณสามารถผ่านบางกรณีและรับการแก้ปัญหาด้วยคณิตศาสตร์ชั้นประถมศึกษาปีที่ 7 (ดูรหัสและตัวอย่างการโต้ตอบ )

คำถาม B: คุณตรวจพบได้อย่างไรว่าเส้นสองเส้นตัดกันหรือไม่

สมมติว่าจุดของคุณA = (x1, y1), จุดB = (x2, y2), ,C = (x_3, y_3) D = (x_4, y_4)บรรทัดแรกของคุณถูกกำหนดโดย AB (ด้วย A! = B) และบรรทัดที่สองของคุณโดย CD (พร้อม C! = D)

function doLinesIntersect(AB, CD) {
    if (x1 == x2) {
        return !(x3 == x4 && x1 != x3);
    } else if (x3 == x4) {
        return true;
    } else {
        // Both lines are not parallel to the y-axis
        m1 = (y1-y2)/(x1-x2);
        m2 = (y3-y4)/(x3-x4);
        return m1 != m2;
    }
}

คำถาม D: เส้นสองเส้นตัดกันที่ไหน

ตรวจสอบกับคำถามขหากพวกเขาตัดกันเลย

บรรทัด a และ b ถูกกำหนดโดยสองจุดสำหรับแต่ละบรรทัด โดยทั่วไปคุณสามารถใช้ตรรกะเดียวกันกับที่ใช้ในคำถาม A


15
เพื่อความชัดเจนคำถาม B ในคำตอบนี้เป็นจริงเกี่ยวกับการตัดกันสองบรรทัดไม่ใช่ส่วนของเส้น ฉันไม่ได้บ่น มันไม่ถูกต้อง แค่ไม่ต้องการให้ใครเข้าใจผิด
264 phord

1
ไม่มี "คำถาม C" และคำถาม D เพียงแค่ตีกลับกลับไปที่คำถาม A.
Konrad Viltersten

21

คำตอบที่ได้รับเมื่อยอมรับที่นี่นั้นไม่ถูกต้อง (มันไม่ได้รับการยอมรับ, ดังนั้นไชโย!) ไม่สามารถกำจัดจุดตัดที่ไม่ใช่ทางแยกทั้งหมดได้อย่างถูกต้อง อาจดูเหมือนว่าทำงานได้ แต่อาจล้มเหลวโดยเฉพาะอย่างยิ่งในกรณีที่ 0 และ 1 ถือว่าถูกต้องสำหรับ h

พิจารณากรณีต่อไปนี้:

เส้นที่ (4,1) - (5,1) และ (0,0) - (0,2)

นี่คือเส้นตั้งฉากซึ่งชัดเจนไม่ทับซ้อนกัน

A = (4,1)
B = (5,1)
C = (0,0)
D = (0,2)
E = (5,1) - (4,1) = (- 1,0)
F = (0,2) - (0,0) = (0, -2)
P = (0,1)
h = ((4,1) - (0,0)) จุด (0,1) / (0 , -2) จุด (0,1)) = 0

จากคำตอบข้างต้นกลุ่มบรรทัดทั้งสองนี้จะพบกันที่จุดปลาย (ค่า 0 และ 1) จุดสิ้นสุดนั้นจะเป็น:

(0,0) + (0, -2) * 0 = (0,0)

ดังนั้นเห็นได้ชัดว่าทั้งสองส่วนของเส้นตรงพบที่ (0,0) ซึ่งอยู่ในสายซีดี แต่ไม่ใช่ในสาย AB แล้วจะเกิดอะไรขึ้น? คำตอบคือค่า 0 และ 1 ไม่ถูกต้องและบางครั้งก็เกิดขึ้นเพื่อคาดการณ์จุดตัดกันของจุดสิ้นสุดอย่างถูกต้อง เมื่อส่วนขยายของหนึ่งบรรทัด (แต่ไม่ใช่อื่น ๆ ) จะตรงกับส่วนของเส้นตรงอัลกอริทึมจะคาดการณ์จุดตัดของส่วนของบรรทัด แต่สิ่งนี้ไม่ถูกต้อง ฉันจินตนาการว่าโดยการทดสอบเริ่มต้นด้วย AB vs CD แล้วทดสอบด้วย CD vs AB ปัญหานี้จะถูกกำจัด เฉพาะในกรณีที่ทั้งคู่อยู่ระหว่าง 0 ถึง 1 จะสามารถกล่าวได้ว่าตัดกัน

ฉันขอแนะนำให้ใช้วิธีการข้ามแบบเวกเตอร์หากคุณต้องทำนายจุดสิ้นสุด

-Dan


4
คำตอบ "ยอมรับ" สามารถเปลี่ยนแปลงได้ดังนั้นคุณควรเรียกชื่ออย่างอื่น (อันที่จริงฉันคิดว่ามันเปลี่ยนไปจากความคิดเห็นของคุณ)
โยฮันเนสฮอฟฟ์

14

คำตอบของ iMalc รุ่น Python:

def find_intersection( p0, p1, p2, p3 ) :

    s10_x = p1[0] - p0[0]
    s10_y = p1[1] - p0[1]
    s32_x = p3[0] - p2[0]
    s32_y = p3[1] - p2[1]

    denom = s10_x * s32_y - s32_x * s10_y

    if denom == 0 : return None # collinear

    denom_is_positive = denom > 0

    s02_x = p0[0] - p2[0]
    s02_y = p0[1] - p2[1]

    s_numer = s10_x * s02_y - s10_y * s02_x

    if (s_numer < 0) == denom_is_positive : return None # no collision

    t_numer = s32_x * s02_y - s32_y * s02_x

    if (t_numer < 0) == denom_is_positive : return None # no collision

    if (s_numer > denom) == denom_is_positive or (t_numer > denom) == denom_is_positive : return None # no collision


    # collision detected

    t = t_numer / denom

    intersection_point = [ p0[0] + (t * s10_x), p0[1] + (t * s10_y) ]


    return intersection_point

จำไว้ว่าคุณต้องทำให้ตัวเลขของคุณลอยหรือเปลี่ยนบรรทัดที่ 8 เพื่อใช้งานdenom = float(...)
Jonno_FTW

11

การหาจุดตัดที่ถูกต้องของส่วนของเส้นตรงสองเส้นเป็นงานที่ไม่สำคัญกับกรณีขอบจำนวนมาก นี่คือเอกสารที่ใช้งานได้ดีและผ่านการทดสอบใน Java

ในสาระสำคัญมีสามสิ่งที่สามารถเกิดขึ้นได้เมื่อค้นหาจุดตัดของสองส่วนบรรทัด:

  1. ส่วนจะไม่ตัดกัน

  2. มีจุดตัดที่ไม่เหมือนใคร

  3. จุดตัดเป็นอีกส่วนหนึ่ง

หมายเหตุ : ในรหัสฉันคิดว่าส่วนของเส้น (x1, y1), (x2, y2) ที่มี x1 = x2 และ y1 = y2 เป็นส่วนของเส้นที่ถูกต้อง การพูดทางคณิตศาสตร์ส่วนของเส้นตรงประกอบด้วยจุดที่แตกต่างกัน แต่ฉันอนุญาตให้ส่วนต่าง ๆ เป็นจุดในการดำเนินการนี้เพื่อความสมบูรณ์

รหัสถูกนำมาจากrepo GitHubของฉัน

/**
 * This snippet finds the intersection of two line segments.
 * The intersection may either be empty, a single point or the
 * intersection is a subsegment there's an overlap.
 */

import static java.lang.Math.abs;
import static java.lang.Math.max;
import static java.lang.Math.min;

import java.util.ArrayList;
import java.util.List;

public class LineSegmentLineSegmentIntersection {

  // Small epsilon used for double value comparison.
  private static final double EPS = 1e-5;

  // 2D Point class.
  public static class Pt {
    double x, y;
    public Pt(double x, double y) {
      this.x = x; 
      this.y = y;
    }
    public boolean equals(Pt pt) {
      return abs(x - pt.x) < EPS && abs(y - pt.y) < EPS;
    }
  }

  // Finds the orientation of point 'c' relative to the line segment (a, b)
  // Returns  0 if all three points are collinear.
  // Returns -1 if 'c' is clockwise to segment (a, b), i.e right of line formed by the segment.
  // Returns +1 if 'c' is counter clockwise to segment (a, b), i.e left of line
  // formed by the segment.
  public static int orientation(Pt a, Pt b, Pt c) {
    double value = (b.y - a.y) * (c.x - b.x) - 
                   (b.x - a.x) * (c.y - b.y);
    if (abs(value) < EPS) return 0;
    return (value > 0) ? -1 : +1;
  }

  // Tests whether point 'c' is on the line segment (a, b).
  // Ensure first that point c is collinear to segment (a, b) and
  // then check whether c is within the rectangle formed by (a, b)
  public static boolean pointOnLine(Pt a, Pt b, Pt c) {
    return orientation(a, b, c) == 0 && 
           min(a.x, b.x) <= c.x && c.x <= max(a.x, b.x) && 
           min(a.y, b.y) <= c.y && c.y <= max(a.y, b.y);
  }

  // Determines whether two segments intersect.
  public static boolean segmentsIntersect(Pt p1, Pt p2, Pt p3, Pt p4) {

    // Get the orientation of points p3 and p4 in relation
    // to the line segment (p1, p2)
    int o1 = orientation(p1, p2, p3);
    int o2 = orientation(p1, p2, p4);
    int o3 = orientation(p3, p4, p1);
    int o4 = orientation(p3, p4, p2);

    // If the points p1, p2 are on opposite sides of the infinite
    // line formed by (p3, p4) and conversly p3, p4 are on opposite
    // sides of the infinite line formed by (p1, p2) then there is
    // an intersection.
    if (o1 != o2 && o3 != o4) return true;

    // Collinear special cases (perhaps these if checks can be simplified?)
    if (o1 == 0 && pointOnLine(p1, p2, p3)) return true;
    if (o2 == 0 && pointOnLine(p1, p2, p4)) return true;
    if (o3 == 0 && pointOnLine(p3, p4, p1)) return true;
    if (o4 == 0 && pointOnLine(p3, p4, p2)) return true;

    return false;
  }

  public static List<Pt> getCommonEndpoints(Pt p1, Pt p2, Pt p3, Pt p4) {

    List<Pt> points = new ArrayList<>();

    if (p1.equals(p3)) {
      points.add(p1);
      if (p2.equals(p4)) points.add(p2);

    } else if (p1.equals(p4)) {
      points.add(p1);
      if (p2.equals(p3)) points.add(p2);

    } else if (p2.equals(p3)) {
      points.add(p2);
      if (p1.equals(p4)) points.add(p1);

    } else if (p2.equals(p4)) {
      points.add(p2);
      if (p1.equals(p3)) points.add(p1);
    }

    return points;
  }

  // Finds the intersection point(s) of two line segments. Unlike regular line 
  // segments, segments which are points (x1 = x2 and y1 = y2) are allowed.
  public static Pt[] lineSegmentLineSegmentIntersection(Pt p1, Pt p2, Pt p3, Pt p4) {

    // No intersection.
    if (!segmentsIntersect(p1, p2, p3, p4)) return new Pt[]{};

    // Both segments are a single point.
    if (p1.equals(p2) && p2.equals(p3) && p3.equals(p4))
      return new Pt[]{p1};

    List<Pt> endpoints = getCommonEndpoints(p1, p2, p3, p4);
    int n = endpoints.size();

    // One of the line segments is an intersecting single point.
    // NOTE: checking only n == 1 is insufficient to return early
    // because the solution might be a sub segment.
    boolean singleton = p1.equals(p2) || p3.equals(p4);
    if (n == 1 && singleton) return new Pt[]{endpoints.get(0)};

    // Segments are equal.
    if (n == 2) return new Pt[]{endpoints.get(0), endpoints.get(1)};

    boolean collinearSegments = (orientation(p1, p2, p3) == 0) && 
                                (orientation(p1, p2, p4) == 0);

    // The intersection will be a sub-segment of the two
    // segments since they overlap each other.
    if (collinearSegments) {

      // Segment #2 is enclosed in segment #1
      if (pointOnLine(p1, p2, p3) && pointOnLine(p1, p2, p4))
        return new Pt[]{p3, p4};

      // Segment #1 is enclosed in segment #2
      if (pointOnLine(p3, p4, p1) && pointOnLine(p3, p4, p2))
        return new Pt[]{p1, p2};

      // The subsegment is part of segment #1 and part of segment #2.
      // Find the middle points which correspond to this segment.
      Pt midPoint1 = pointOnLine(p1, p2, p3) ? p3 : p4;
      Pt midPoint2 = pointOnLine(p3, p4, p1) ? p1 : p2;

      // There is actually only one middle point!
      if (midPoint1.equals(midPoint2)) return new Pt[]{midPoint1};

      return new Pt[]{midPoint1, midPoint2};
    }

    /* Beyond this point there is a unique intersection point. */

    // Segment #1 is a vertical line.
    if (abs(p1.x - p2.x) < EPS) {
      double m = (p4.y - p3.y) / (p4.x - p3.x);
      double b = p3.y - m * p3.x;
      return new Pt[]{new Pt(p1.x, m * p1.x + b)};
    }

    // Segment #2 is a vertical line.
    if (abs(p3.x - p4.x) < EPS) {
      double m = (p2.y - p1.y) / (p2.x - p1.x);
      double b = p1.y - m * p1.x;
      return new Pt[]{new Pt(p3.x, m * p3.x + b)};
    }

    double m1 = (p2.y - p1.y) / (p2.x - p1.x);
    double m2 = (p4.y - p3.y) / (p4.x - p3.x);
    double b1 = p1.y - m1 * p1.x;
    double b2 = p3.y - m2 * p3.x;
    double x = (b2 - b1) / (m1 - m2);
    double y = (m1 * b2 - m2 * b1) / (m1 - m2);

    return new Pt[]{new Pt(x, y)};
  }

}

นี่คือตัวอย่างการใช้งานที่ง่าย:

  public static void main(String[] args) {

    // Segment #1 is (p1, p2), segment #2 is (p3, p4)
    Pt p1, p2, p3, p4;

    p1 = new Pt(-2, 4); p2 = new Pt(3, 3);
    p3 = new Pt(0, 0);  p4 = new Pt(2, 4);
    Pt[] points = lineSegmentLineSegmentIntersection(p1, p2, p3, p4);
    Pt point = points[0];

    // Prints: (1.636, 3.273)
    System.out.printf("(%.3f, %.3f)\n", point.x, point.y);

    p1 = new Pt(-10, 0); p2 = new Pt(+10, 0);
    p3 = new Pt(-5, 0);  p4 = new Pt(+5, 0);
    points = lineSegmentLineSegmentIntersection(p1, p2, p3, p4);
    Pt point1 = points[0], point2 = points[1];

    // Prints: (-5.000, 0.000) (5.000, 0.000)
    System.out.printf("(%.3f, %.3f) (%.3f, %.3f)\n", point1.x, point1.y, point2.x, point2.y);
  }

มันใช้งานได้กับระบบพิกัดทางภูมิศาสตร์ของฉัน! ขอบคุณ! แต่สำหรับการตัดกันแบบไม่มีที่สิ้นสุดและฉันกำลังมองหาจุดตัดที่มี จำกัด มากขึ้น
M. Usman Khan

8

แค่อยากจะพูดถึงว่าคำอธิบายที่ดีและการแก้ปัญหาที่ชัดเจนสามารถพบได้ในซีรีส์ตัวเลขสูตร ฉันได้รับรุ่นที่ 3 และคำตอบอยู่ในหน้า 1117 หัวข้อ 21.4 ทางออกก็มีศัพท์ที่แตกต่างกันสามารถพบได้ในกระดาษโดย Marina Gavrilova สายที่เชื่อถือได้มาตราแยกการทดสอบ วิธีแก้ปัญหาของเธอก็ง่ายกว่าเล็กน้อย

การใช้งานของฉันอยู่ด้านล่าง:

bool NuGeometry::IsBetween(const double& x0, const double& x, const double& x1){
   return (x >= x0) && (x <= x1);
}

bool NuGeometry::FindIntersection(const double& x0, const double& y0, 
     const double& x1, const double& y1,
     const double& a0, const double& b0, 
     const double& a1, const double& b1, 
     double& xy, double& ab) {
   // four endpoints are x0, y0 & x1,y1 & a0,b0 & a1,b1
   // returned values xy and ab are the fractional distance along xy and ab
   // and are only defined when the result is true

   bool partial = false;
   double denom = (b0 - b1) * (x0 - x1) - (y0 - y1) * (a0 - a1);
   if (denom == 0) {
      xy = -1;
      ab = -1;
   } else {
      xy = (a0 * (y1 - b1) + a1 * (b0 - y1) + x1 * (b1 - b0)) / denom;
      partial = NuGeometry::IsBetween(0, xy, 1);
      if (partial) {
         // no point calculating this unless xy is between 0 & 1
         ab = (y1 * (x0 - a1) + b1 * (x1 - x0) + y0 * (a1 - x1)) / denom; 
      }
   }
   if ( partial && NuGeometry::IsBetween(0, ab, 1)) {
      ab = 1-ab;
      xy = 1-xy;
      return true;
   }  else return false;
}

ไม่ทำงานสำหรับ p1 = (0,0), p2 = (10,0), p3 = (9,0), p4 = (20,0)
padmalcom

ขึ้นอยู่กับคำจำกัดความของคุณของ "ไม่ทำงาน" ฉันเดา Denom คือ 0 ดังนั้นมันจะคืนค่าเท็จซึ่งดูเหมือนว่าถูกต้องสำหรับฉันเพราะพวกมันไม่ตัดกัน Colinear นั้นไม่เหมือนกับการตัดกัน
marcp

8

มีโซลูชันให้เลือกมากมาย แต่ฉันคิดว่าโซลูชันด้านล่างนั้นค่อนข้างเรียบง่ายและเข้าใจง่าย

สองส่วน Vector AB และ Vector CD ตัดกันถ้าหากเฉพาะ

  1. จุดสิ้นสุด a และ b อยู่ฝั่งตรงข้ามของซีดีเซ็กเมนต์
  2. จุดสิ้นสุด c และ d อยู่ฝั่งตรงข้ามของส่วน AB

โดยเฉพาะอย่างยิ่ง a และ b อยู่ฝั่งตรงข้ามของเซ็กเมนต์ซีดีถ้าหากว่าหนึ่งในสองสามอย่าง a, c, d และ b, c, d อยู่ในลำดับทวนเข็มนาฬิกา

Intersect(a, b, c, d)
 if CCW(a, c, d) == CCW(b, c, d)
    return false;
 else if CCW(a, b, c) == CCW(a, b, d)
    return false;
 else
    return true;

นี่ CCW แทนทวนเข็มนาฬิกาซึ่งผลตอบแทนจริง / เท็จขึ้นอยู่กับการวางแนวของจุด

แหล่งข้อมูล: http://compgeom.cs.uiuc.edu/~jeffe/teaching/373/notes/x06-sweepline.pdf หน้า 2


2
ฉันคิดว่าคุณควรเจาะจงมากกว่านี้เล็กน้อย: การCCWทดสอบมีการกำหนดอย่างไร ด้วยสัญลักษณ์ของผลิตภัณฑ์ด้านนอก?
ocramz

ขอบคุณ; โค้ดหลอกนี้อนุญาตให้นำไปปฏิบัติได้อย่างง่ายดายมากใน Scratch ดูโครงการนี้: scratch.mit.edu/projects/129319027
Ruud Helderman

8

C และวัตถุประสงค์ -C

ตามคำตอบของ Gareth Rees

const AGKLine AGKLineZero = (AGKLine){(CGPoint){0.0, 0.0}, (CGPoint){0.0, 0.0}};

AGKLine AGKLineMake(CGPoint start, CGPoint end)
{
    return (AGKLine){start, end};
}

double AGKLineLength(AGKLine l)
{
    return CGPointLengthBetween_AGK(l.start, l.end);
}

BOOL AGKLineIntersection(AGKLine l1, AGKLine l2, CGPoint *out_pointOfIntersection)
{
    // http://stackoverflow.com/a/565282/202451

    CGPoint p = l1.start;
    CGPoint q = l2.start;
    CGPoint r = CGPointSubtract_AGK(l1.end, l1.start);
    CGPoint s = CGPointSubtract_AGK(l2.end, l2.start);

    double s_r_crossProduct = CGPointCrossProductZComponent_AGK(r, s);
    double t = CGPointCrossProductZComponent_AGK(CGPointSubtract_AGK(q, p), s) / s_r_crossProduct;
    double u = CGPointCrossProductZComponent_AGK(CGPointSubtract_AGK(q, p), r) / s_r_crossProduct;

    if(t < 0 || t > 1.0 || u < 0 || u > 1.0)
    {
        if(out_pointOfIntersection != NULL)
        {
            *out_pointOfIntersection = CGPointZero;
        }
        return NO;
    }
    else
    {
        if(out_pointOfIntersection != NULL)
        {
            CGPoint i = CGPointAdd_AGK(p, CGPointMultiply_AGK(r, t));
            *out_pointOfIntersection = i;
        }
        return YES;
    }
}

CGFloat CGPointCrossProductZComponent_AGK(CGPoint v1, CGPoint v2)
{
    return v1.x * v2.y - v1.y * v2.x;
}

CGPoint CGPointSubtract_AGK(CGPoint p1, CGPoint p2)
{
    return (CGPoint){p1.x - p2.x, p1.y - p2.y};
}

CGPoint CGPointAdd_AGK(CGPoint p1, CGPoint p2)
{
    return (CGPoint){p1.x + p2.x, p1.y + p2.y};
}

CGFloat CGPointCrossProductZComponent_AGK(CGPoint v1, CGPoint v2)
{
    return v1.x * v2.y - v1.y * v2.x;
}

CGPoint CGPointMultiply_AGK(CGPoint p1, CGFloat factor)
{
    return (CGPoint){p1.x * factor, p1.y * factor};
}

ฟังก์ชั่นและโครงสร้างหลายอย่างเป็นแบบส่วนตัว แต่คุณน่าจะรู้ได้ว่าเกิดอะไรขึ้น นี่เป็นสาธารณะใน repo นี้https://github.com/hfossli/AGGeometryKit/


AGPointZero มาจากไหนในรหัสนี้?
seanicus

1
ตัวอย่าง @seanicus ที่อัปเดตแล้วเพื่อใช้ CGPoint แทน
hfossli

6

มันทำงานได้ดีสำหรับฉัน ที่นำมาจากที่นี่

 // calculates intersection and checks for parallel lines.  
 // also checks that the intersection point is actually on  
 // the line segment p1-p2  
 Point findIntersection(Point p1,Point p2,  
   Point p3,Point p4) {  
   float xD1,yD1,xD2,yD2,xD3,yD3;  
   float dot,deg,len1,len2;  
   float segmentLen1,segmentLen2;  
   float ua,ub,div;  

   // calculate differences  
   xD1=p2.x-p1.x;  
   xD2=p4.x-p3.x;  
   yD1=p2.y-p1.y;  
   yD2=p4.y-p3.y;  
   xD3=p1.x-p3.x;  
   yD3=p1.y-p3.y;    

   // calculate the lengths of the two lines  
   len1=sqrt(xD1*xD1+yD1*yD1);  
   len2=sqrt(xD2*xD2+yD2*yD2);  

   // calculate angle between the two lines.  
   dot=(xD1*xD2+yD1*yD2); // dot product  
   deg=dot/(len1*len2);  

   // if abs(angle)==1 then the lines are parallell,  
   // so no intersection is possible  
   if(abs(deg)==1) return null;  

   // find intersection Pt between two lines  
   Point pt=new Point(0,0);  
   div=yD2*xD1-xD2*yD1;  
   ua=(xD2*yD3-yD2*xD3)/div;  
   ub=(xD1*yD3-yD1*xD3)/div;  
   pt.x=p1.x+ua*xD1;  
   pt.y=p1.y+ua*yD1;  

   // calculate the combined length of the two segments  
   // between Pt-p1 and Pt-p2  
   xD1=pt.x-p1.x;  
   xD2=pt.x-p2.x;  
   yD1=pt.y-p1.y;  
   yD2=pt.y-p2.y;  
   segmentLen1=sqrt(xD1*xD1+yD1*yD1)+sqrt(xD2*xD2+yD2*yD2);  

   // calculate the combined length of the two segments  
   // between Pt-p3 and Pt-p4  
   xD1=pt.x-p3.x;  
   xD2=pt.x-p4.x;  
   yD1=pt.y-p3.y;  
   yD2=pt.y-p4.y;  
   segmentLen2=sqrt(xD1*xD1+yD1*yD1)+sqrt(xD2*xD2+yD2*yD2);  

   // if the lengths of both sets of segments are the same as  
   // the lenghts of the two lines the point is actually  
   // on the line segment.  

   // if the point isn’t on the line, return null  
   if(abs(len1-segmentLen1)>0.01 || abs(len2-segmentLen2)>0.01)  
     return null;  

   // return the valid intersection  
   return pt;  
 }  

 class Point{  
   float x,y;  
   Point(float x, float y){  
     this.x = x;  
     this.y = y;  
   }  

   void set(float x, float y){  
     this.x = x;  
     this.y = y;  
   }  
 }  

8
มีปัญหาหลายประการกับรหัสนี้ มันสามารถยกข้อยกเว้นเนื่องจากการหารด้วยศูนย์; มันช้าเพราะใช้รากที่สอง และบางครั้งมันก็ส่งคืนผลบวกปลอมเพราะใช้ปัจจัยเหลวไหล คุณทำได้ดีกว่านี้!
Gareth Rees

เอาล่ะเป็นวิธีการแก้ปัญหา แต่ที่ได้รับจากเจสันเป็นมั่นเหมาะคอมพิวเตอร์ได้เร็วขึ้นและหลีกเลี่ยงปัญหามากที่มีการแก้ปัญหานี้
ธาตุ

6

ฉันลองคำตอบเหล่านี้แล้ว แต่พวกเขาก็ไม่ได้ผลสำหรับฉัน (คนที่ขอโทษ); หลังจากค้นหาสุทธิฉันพบสิ่งนี้อีก

ด้วยการปรับเปลี่ยนเล็กน้อยในรหัสของเขาตอนนี้ฉันมีฟังก์ชั่นนี้ที่จะคืนค่าจุดตัดหรือไม่พบจุดตัดก็จะคืนค่า -1, -1

    Public Function intercetion(ByVal ax As Integer, ByVal ay As Integer, ByVal bx As Integer, ByVal by As Integer, ByVal cx As Integer, ByVal cy As Integer, ByVal dx As Integer, ByVal dy As Integer) As Point
    '//  Determines the intersection point of the line segment defined by points A and B
    '//  with the line segment defined by points C and D.
    '//
    '//  Returns YES if the intersection point was found, and stores that point in X,Y.
    '//  Returns NO if there is no determinable intersection point, in which case X,Y will
    '//  be unmodified.

    Dim distAB, theCos, theSin, newX, ABpos As Double

    '//  Fail if either line segment is zero-length.
    If ax = bx And ay = by Or cx = dx And cy = dy Then Return New Point(-1, -1)

    '//  Fail if the segments share an end-point.
    If ax = cx And ay = cy Or bx = cx And by = cy Or ax = dx And ay = dy Or bx = dx And by = dy Then Return New Point(-1, -1)

    '//  (1) Translate the system so that point A is on the origin.
    bx -= ax
    by -= ay
    cx -= ax
    cy -= ay
    dx -= ax
    dy -= ay

    '//  Discover the length of segment A-B.
    distAB = Math.Sqrt(bx * bx + by * by)

    '//  (2) Rotate the system so that point B is on the positive X axis.
    theCos = bx / distAB
    theSin = by / distAB
    newX = cx * theCos + cy * theSin
    cy = cy * theCos - cx * theSin
    cx = newX
    newX = dx * theCos + dy * theSin
    dy = dy * theCos - dx * theSin
    dx = newX

    '//  Fail if segment C-D doesn't cross line A-B.
    If cy < 0 And dy < 0 Or cy >= 0 And dy >= 0 Then Return New Point(-1, -1)

    '//  (3) Discover the position of the intersection point along line A-B.
    ABpos = dx + (cx - dx) * dy / (dy - cy)

    '//  Fail if segment C-D crosses line A-B outside of segment A-B.
    If ABpos < 0 Or ABpos > distAB Then Return New Point(-1, -1)

    '//  (4) Apply the discovered position to line A-B in the original coordinate system.
    '*X=Ax+ABpos*theCos
    '*Y=Ay+ABpos*theSin

    '//  Success.
    Return New Point(ax + ABpos * theCos, ay + ABpos * theSin)
End Function

6

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

อัลกอริทึมที่ iMalc จัดทำผ่านทางคำตอบของ Gavin เป็นสิ่งที่ฉันใช้ในโปรเจ็กต์จาวาสคริปต์และฉันแค่ต้องการจัดทำเวอร์ชันล้างข้อมูลที่นี่ถ้ามันอาจช่วยใครก็ได้

// Some variables for reuse, others may do this differently
var p0x, p1x, p2x, p3x, ix,
    p0y, p1y, p2y, p3y, iy,
    collisionDetected;

// do stuff, call other functions, set endpoints...

// note: for my purpose I use |t| < |d| as opposed to
// |t| <= |d| which is equivalent to 0 <= t < 1 rather than
// 0 <= t <= 1 as in Gavin's answer - results may vary

var lineSegmentIntersection = function(){
    var d, dx1, dx2, dx3, dy1, dy2, dy3, s, t;

    dx1 = p1x - p0x;      dy1 = p1y - p0y;
    dx2 = p3x - p2x;      dy2 = p3y - p2y;
    dx3 = p0x - p2x;      dy3 = p0y - p2y;

    collisionDetected = 0;

    d = dx1 * dy2 - dx2 * dy1;

    if(d !== 0){
        s = dx1 * dy3 - dx3 * dy1;
        if((s <= 0 && d < 0 && s >= d) || (s >= 0 && d > 0 && s <= d)){
            t = dx2 * dy3 - dx3 * dy2;
            if((t <= 0 && d < 0 && t > d) || (t >= 0 && d > 0 && t < d)){
                t = t / d;
                collisionDetected = 1;
                ix = p0x + t * dx1;
                iy = p0y + t * dy1;
            }
        }
    }
};

ฉันไม่เข้าใจวิธีการที่คุณสามารถเข้าใจสิ่งที่เกิดขึ้นกับสายการชอบt = dx2 * dy3 - dx3 * dy2;...
Supuhstar

@Supuhstar เกี่ยวข้องกับคณิตศาสตร์เวกเตอร์และคำจำกัดความของ dot product และ cross product ตัวอย่างเช่นรหัสที่คุณโพสต์แสดงถึงการทำงานข้ามผลิตภัณฑ์ มันเป็นวิธีการฉายเซกเมนต์หนึ่งบรรทัดไปยังอีกเซ็กเมนต์เพื่อกำหนดว่ามันจะตรงกับที่ใดในเซกเมนต์อื่น ๆ ก่อนที่จุดเริ่มต้นที่อยู่ตรงกลางหรือหลังบรรทัด t คือค่าปกติ ถ้าอยู่ระหว่าง 0 ถึง 1 ทั้งสองส่วนจะตัดกัน ถ้ามันน้อยกว่า 0 หรือมากกว่าหนึ่งพวกเขาก็ทำไม่ได้
Nolo

@Supuhstar โปรดทราบว่าเพื่อให้การฉายภาพเพื่อหาจุดที่เกิดขึ้นจริงจะต้องมีการปรับขนาดผลลัพธ์ นั่นคือที่t/dมา
Nolo

1
ฉันหมายถึงคุณเข้าใจได้อย่างไรว่าเกิดอะไรขึ้นกับชื่อตัวแปรแบบนั้น? ทำไมไม่ชอบcrossProduct = (line1XDifference * line2YDifference) - (line2XDifference * line1YDifference)และscaledResult = crossProduct / dotProduct?
Supuhstar

1
@Supuhstar อ่าฉันเห็นว่าคุณหมายถึงอะไร เอ่อฉันคิดว่ามันไม่มีเหตุผลที่ดีที่จะพูดถึงเรื่องการครอบงำเกินประสิทธิภาพ แต่นั่นก็ไม่ใช่เหตุผลที่ดีในตัวเองเพราะคอมไพเลอร์ทำงานได้ดีมากในการใช้โค้ดส่วนใหญ่ที่คุณให้พวกเขาและทำให้มันมีประสิทธิภาพ เป็นไปได้ในขณะที่ไม่เปลี่ยนสิ่งที่ควรคำนวณ ในทางกลับกันชื่อp1x, p1yและอื่น ๆ มีไว้เพื่ออธิบายคะแนนด้วยค่า x และ y ของพวกเขาดังนั้นp1xตัวย่อสำหรับpoint1xเช่นเดียวกันd1xในใจของฉันก็คือตัวย่อสำหรับตัวอักษรกรีกdeltaXหรือคุณอาจพูดdifferenceInXได้ (เพิ่มเติม)
Nolo

5

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

แก้ไข:

นี่คือวิธีคำนวณจุดตัด (ฉันไม่รู้อีกต่อไปแล้วว่าฉันพบข้อมูลโค้ดนี้ที่ใด)

Point3D

มาจาก

System.Windows.Media.Media3D

public static Point3D? Intersection(Point3D start1, Point3D end1, Point3D start2, Point3D end2) {

        double a1 = end1.Y - start1.Y;
        double b1 = start1.X - end1.X;
        double c1 = a1 * start1.X + b1 * start1.Y;

        double a2 = end2.Y - start2.Y;
        double b2 = start2.X - end2.X;
        double c2 = a2 * start2.X + b2 * start2.Y;

        double det = a1 * b2 - a2 * b1;
        if (det == 0) { // lines are parallel
            return null;
        }

        double x = (b2 * c1 - b1 * c2) / det;
        double y = (a1 * c2 - a2 * c1) / det;

        return new Point3D(x, y, 0.0);
    }

และนี่คือของฉัน (ง่ายสำหรับวัตถุประสงค์ของคำตอบ) ระดับ BoundingBox:

public class BoundingBox {
    private Point3D min = new Point3D();
    private Point3D max = new Point3D();

    public BoundingBox(Point3D point) {
        min = point;
        max = point;
    }

    public Point3D Min {
        get { return min; }
        set { min = value; }
    }

    public Point3D Max {
        get { return max; }
        set { max = value; }
    }

    public bool Contains(BoundingBox box) {
        bool contains =
            min.X <= box.min.X && max.X >= box.max.X &&
            min.Y <= box.min.Y && max.Y >= box.max.Y &&
            min.Z <= box.min.Z && max.Z >= box.max.Z;
        return contains;
    }

    public bool Contains(Point3D point) {
        return Contains(new BoundingBox(point));
    }

}

3

วิธีนี้อาจช่วยได้

public static float GetLineYIntesept(PointF p, float slope)
    {
        return p.Y - slope * p.X;
    }

    public static PointF FindIntersection(PointF line1Start, PointF line1End, PointF line2Start, PointF line2End)
    {

        float slope1 = (line1End.Y - line1Start.Y) / (line1End.X - line1Start.X);
        float slope2 = (line2End.Y - line2Start.Y) / (line2End.X - line2Start.X);

        float yinter1 = GetLineYIntesept(line1Start, slope1);
        float yinter2 = GetLineYIntesept(line2Start, slope2);

        if (slope1 == slope2 && yinter1 != yinter2)
            return PointF.Empty;

        float x = (yinter2 - yinter1) / (slope1 - slope2);

        float y = slope1 * x + yinter1;

        return new PointF(x, y);
    }

3

ฉันตอบคำถามของ Kris ข้างต้นกับ JavaScript แล้ว หลังจากลองคำตอบต่าง ๆ นานาเขาให้คะแนนที่ถูกต้อง ฉันคิดว่าฉันกำลังบ้าที่ฉันไม่ได้รับคะแนนที่ฉันต้องการ

function getLineLineCollision(p0, p1, p2, p3) {
    var s1, s2;
    s1 = {x: p1.x - p0.x, y: p1.y - p0.y};
    s2 = {x: p3.x - p2.x, y: p3.y - p2.y};

    var s10_x = p1.x - p0.x;
    var s10_y = p1.y - p0.y;
    var s32_x = p3.x - p2.x;
    var s32_y = p3.y - p2.y;

    var denom = s10_x * s32_y - s32_x * s10_y;

    if(denom == 0) {
        return false;
    }

    var denom_positive = denom > 0;

    var s02_x = p0.x - p2.x;
    var s02_y = p0.y - p2.y;

    var s_numer = s10_x * s02_y - s10_y * s02_x;

    if((s_numer < 0) == denom_positive) {
        return false;
    }

    var t_numer = s32_x * s02_y - s32_y * s02_x;

    if((t_numer < 0) == denom_positive) {
        return false;
    }

    if((s_numer > denom) == denom_positive || (t_numer > denom) == denom_positive) {
        return false;
    }

    var t = t_numer / denom;

    var p = {x: p0.x + (t * s10_x), y: p0.y + (t * s10_y)};
    return p;
}

2

ฉันลองหลายวิธีแล้วจึงตัดสินใจเขียนของตัวเอง ดังนั้นนี่คือ:

bool IsBetween (float x, float b1, float b2)
{
   return ( ((x >= (b1 - 0.1f)) && 
        (x <= (b2 + 0.1f))) || 
        ((x >= (b2 - 0.1f)) &&
        (x <= (b1 + 0.1f))));
}

bool IsSegmentsColliding(   POINTFLOAT lineA,
                POINTFLOAT lineB,
                POINTFLOAT line2A,
                POINTFLOAT line2B)
{
    float deltaX1 = lineB.x - lineA.x;
    float deltaX2 = line2B.x - line2A.x;
    float deltaY1 = lineB.y - lineA.y;
    float deltaY2 = line2B.y - line2A.y;

    if (abs(deltaX1) < 0.01f && 
        abs(deltaX2) < 0.01f) // Both are vertical lines
        return false;
    if (abs((deltaY1 / deltaX1) -
        (deltaY2 / deltaX2)) < 0.001f) // Two parallel line
        return false;

    float xCol = (  (   (deltaX1 * deltaX2) * 
                        (line2A.y - lineA.y)) - 
                    (line2A.x * deltaY2 * deltaX1) + 
                    (lineA.x * deltaY1 * deltaX2)) / 
                 ((deltaY1 * deltaX2) - (deltaY2 * deltaX1));
    float yCol = 0;
    if (deltaX1 < 0.01f) // L1 is a vertical line
        yCol = ((xCol * deltaY2) + 
                (line2A.y * deltaX2) - 
                (line2A.x * deltaY2)) / deltaX2;
    else // L1 is acceptable
        yCol = ((xCol * deltaY1) +
                (lineA.y * deltaX1) -
                (lineA.x * deltaY1)) / deltaX1;

    bool isCol =    IsBetween(xCol, lineA.x, lineB.x) &&
            IsBetween(yCol, lineA.y, lineB.y) &&
            IsBetween(xCol, line2A.x, line2B.x) &&
            IsBetween(yCol, line2A.y, line2B.y);
    return isCol;
}

อิงจากสองสูตรนี้: (ฉันทำให้มันง่ายขึ้นจากสมการของเส้นและสูตรอื่น ๆ )

สูตรสำหรับ x

สูตรสำหรับ y


ใช้งานได้ แต่พยายามป้อนพิกัดนี้ (หากเป็นสี / ทับซ้อนแล้วมันจะส่งกลับผลลัพธ์ที่ผิด): PointA1 = (0,0) PointA2 = (0,2) และ PointB1 = (0,1) PointB2 = (0,5)
dns

@dns นั่นเป็นเพราะรหัสคืนค่าเท็จสำหรับเส้นคู่ขนาน ฉันเห็นปัญหาอย่างไรก็ตามฉันยังไม่ทราบว่าฟังก์ชันควรกลับมาอย่างไรเนื่องจากมีคำตอบจำนวนนับไม่ถ้วน
Soroush Falahati

2

สิ่งนี้ขึ้นอยู่กับคำตอบของ Gareth Ree นอกจากนี้ยังส่งคืนการเหลื่อมกันของส่วนของเส้นตรงหากทำเช่นนั้น รหัสใน C ++, V เป็นคลาสเวกเตอร์ง่าย ๆ เมื่อผลคูณของสองเวกเตอร์ใน 2D ส่งคืนสเกลาร์เดี่ยว มันได้รับการทดสอบและผ่านระบบการทดสอบอัตโนมัติของโรงเรียนของฉัน

//Required input point must be colinear with the line
bool on_segment(const V& p, const LineSegment& l)
{
    //If a point is on the line, the sum of the vectors formed by the point to the line endpoints must be equal
    V va = p - l.pa;
    V vb = p - l.pb;
    R ma = va.magnitude();
    R mb = vb.magnitude();
    R ml = (l.pb - l.pa).magnitude();
    R s = ma + mb;
    bool r = s <= ml + epsilon;
    return r;
}

//Compute using vector math
// Returns 0 points if the lines do not intersect or overlap
// Returns 1 point if the lines intersect
//  Returns 2 points if the lines overlap, contain the points where overlapping start starts and stop
std::vector<V> intersect(const LineSegment& la, const LineSegment& lb)
{
    std::vector<V> r;

    //http://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect
    V oa, ob, da, db; //Origin and direction vectors
    R sa, sb; //Scalar values
    oa = la.pa;
    da = la.pb - la.pa;
    ob = lb.pa;
    db = lb.pb - lb.pa;

    if (da.cross(db) == 0 && (ob - oa).cross(da) == 0) //If colinear
    {
        if (on_segment(lb.pa, la) && on_segment(lb.pb, la))
        {
            r.push_back(lb.pa);
            r.push_back(lb.pb);
            dprintf("colinear, overlapping\n");
            return r;
        }

        if (on_segment(la.pa, lb) && on_segment(la.pb, lb))
        {
            r.push_back(la.pa);
            r.push_back(la.pb);
            dprintf("colinear, overlapping\n");
            return r;
        }

        if (on_segment(la.pa, lb))
            r.push_back(la.pa);

        if (on_segment(la.pb, lb))
            r.push_back(la.pb);

        if (on_segment(lb.pa, la))
            r.push_back(lb.pa);

        if (on_segment(lb.pb, la))
            r.push_back(lb.pb);

        if (r.size() == 0)
            dprintf("colinear, non-overlapping\n");
        else
            dprintf("colinear, overlapping\n");

        return r;
    }

    if (da.cross(db) == 0 && (ob - oa).cross(da) != 0)
    {
        dprintf("parallel non-intersecting\n");
        return r;
    }

    //Math trick db cross db == 0, which is a single scalar in 2D.
    //Crossing both sides with vector db gives:
    sa = (ob - oa).cross(db) / da.cross(db);

    //Crossing both sides with vector da gives
    sb = (oa - ob).cross(da) / db.cross(da);

    if (0 <= sa && sa <= 1 && 0 <= sb && sb <= 1)
    {
        dprintf("intersecting\n");
        r.push_back(oa + da * sa);
        return r;
    }

    dprintf("non-intersecting, non-parallel, non-colinear, non-overlapping\n");
    return r;
}

2

นี่คือการใช้งานพื้นฐานของส่วนของเส้นใน C # พร้อมกับรหัสการตรวจจับแยกที่สอดคล้องกัน มันต้องมีโครงสร้างเวกเตอร์ / จุด 2D ที่เรียกว่าVector2fแม้ว่าคุณจะสามารถแทนที่สิ่งนี้ด้วยประเภทอื่น ๆ ที่มีคุณสมบัติ X / Y คุณสามารถแทนที่floatด้วยdoubleหากเหมาะสมกับความต้องการของคุณดีขึ้น

รหัสนี้ถูกนำมาใช้ในห้องสมุดฟิสิกส์ของฉัน NET, โบอิ้ง

public struct LineSegment2f
{
    public Vector2f From { get; }
    public Vector2f To { get; }

    public LineSegment2f(Vector2f @from, Vector2f to)
    {
        From = @from;
        To = to;
    }

    public Vector2f Delta => new Vector2f(To.X - From.X, To.Y - From.Y);

    /// <summary>
    /// Attempt to intersect two line segments.
    /// </summary>
    /// <remarks>
    /// Even if the line segments do not intersect, <paramref name="t"/> and <paramref name="u"/> will be set.
    /// If the lines are parallel, <paramref name="t"/> and <paramref name="u"/> are set to <see cref="float.NaN"/>.
    /// </remarks>
    /// <param name="other">The line to attempt intersection of this line with.</param>
    /// <param name="intersectionPoint">The point of intersection if within the line segments, or empty..</param>
    /// <param name="t">The distance along this line at which intersection would occur, or NaN if lines are collinear/parallel.</param>
    /// <param name="u">The distance along the other line at which intersection would occur, or NaN if lines are collinear/parallel.</param>
    /// <returns><c>true</c> if the line segments intersect, otherwise <c>false</c>.</returns>
    public bool TryIntersect(LineSegment2f other, out Vector2f intersectionPoint, out float t, out float u)
    {
        var p = From;
        var q = other.From;
        var r = Delta;
        var s = other.Delta;

        // t = (q − p) × s / (r × s)
        // u = (q − p) × r / (r × s)

        var denom = Fake2DCross(r, s);

        if (denom == 0)
        {
            // lines are collinear or parallel
            t = float.NaN;
            u = float.NaN;
            intersectionPoint = default(Vector2f);
            return false;
        }

        var tNumer = Fake2DCross(q - p, s);
        var uNumer = Fake2DCross(q - p, r);

        t = tNumer / denom;
        u = uNumer / denom;

        if (t < 0 || t > 1 || u < 0 || u > 1)
        {
            // line segments do not intersect within their ranges
            intersectionPoint = default(Vector2f);
            return false;
        }

        intersectionPoint = p + r * t;
        return true;
    }

    private static float Fake2DCross(Vector2f a, Vector2f b)
    {
        return a.X * b.Y - a.Y * b.X;
    }
}

1

โปรแกรม C ++ เพื่อตรวจสอบว่ามีส่วนของเส้นตรงสองเส้นตัดกันหรือไม่

#include <iostream>
using namespace std;

struct Point
{
    int x;
    int y;
};

// Given three colinear points p, q, r, the function checks if
// point q lies on line segment 'pr'
bool onSegment(Point p, Point q, Point r)
{
    if (q.x <= max(p.x, r.x) && q.x >= min(p.x, r.x) &&
        q.y <= max(p.y, r.y) && q.y >= min(p.y, r.y))
       return true;

    return false;
}

// To find orientation of ordered triplet (p, q, r).
// The function returns following values
// 0 --> p, q and r are colinear
// 1 --> Clockwise
// 2 --> Counterclockwise
int orientation(Point p, Point q, Point r)
{
    // See 10th slides from following link for derivation of the formula
    // http://www.dcs.gla.ac.uk/~pat/52233/slides/Geometry1x1.pdf
    int val = (q.y - p.y) * (r.x - q.x) -
              (q.x - p.x) * (r.y - q.y);

    if (val == 0) return 0;  // colinear

    return (val > 0)? 1: 2; // clock or counterclock wise
}

// The main function that returns true if line segment 'p1q1'
// and 'p2q2' intersect.
bool doIntersect(Point p1, Point q1, Point p2, Point q2)
{
    // Find the four orientations needed for general and
    // special cases
    int o1 = orientation(p1, q1, p2);
    int o2 = orientation(p1, q1, q2);
    int o3 = orientation(p2, q2, p1);
    int o4 = orientation(p2, q2, q1);

    // General case
    if (o1 != o2 && o3 != o4)
        return true;

    // Special Cases
    // p1, q1 and p2 are colinear and p2 lies on segment p1q1
    if (o1 == 0 && onSegment(p1, p2, q1)) return true;

    // p1, q1 and p2 are colinear and q2 lies on segment p1q1
    if (o2 == 0 && onSegment(p1, q2, q1)) return true;

    // p2, q2 and p1 are colinear and p1 lies on segment p2q2
    if (o3 == 0 && onSegment(p2, p1, q2)) return true;

     // p2, q2 and q1 are colinear and q1 lies on segment p2q2
    if (o4 == 0 && onSegment(p2, q1, q2)) return true;

    return false; // Doesn't fall in any of the above cases
}

// Driver program to test above functions
int main()
{
    struct Point p1 = {1, 1}, q1 = {10, 1};
    struct Point p2 = {1, 2}, q2 = {10, 2};

    doIntersect(p1, q1, p2, q2)? cout << "Yes\n": cout << "No\n";

    p1 = {10, 0}, q1 = {0, 10};
    p2 = {0, 0}, q2 = {10, 10};
    doIntersect(p1, q1, p2, q2)? cout << "Yes\n": cout << "No\n";

    p1 = {-5, -5}, q1 = {0, 0};
    p2 = {1, 1}, q2 = {10, 10};
    doIntersect(p1, q1, p2, q2)? cout << "Yes\n": cout << "No\n";

    return 0;
}

1

จาก @Gareth Rees คำตอบเวอร์ชันสำหรับ Python:

import numpy as np

def np_perp( a ) :
    b = np.empty_like(a)
    b[0] = a[1]
    b[1] = -a[0]
    return b

def np_cross_product(a, b):
    return np.dot(a, np_perp(b))

def np_seg_intersect(a, b, considerCollinearOverlapAsIntersect = False):
    # /programming/563198/how-do-you-detect-where-two-line-segments-intersect/565282#565282
    # http://www.codeproject.com/Tips/862988/Find-the-intersection-point-of-two-line-segments
    r = a[1] - a[0]
    s = b[1] - b[0]
    v = b[0] - a[0]
    num = np_cross_product(v, r)
    denom = np_cross_product(r, s)
    # If r x s = 0 and (q - p) x r = 0, then the two lines are collinear.
    if np.isclose(denom, 0) and np.isclose(num, 0):
        # 1. If either  0 <= (q - p) * r <= r * r or 0 <= (p - q) * s <= * s
        # then the two lines are overlapping,
        if(considerCollinearOverlapAsIntersect):
            vDotR = np.dot(v, r)
            aDotS = np.dot(-v, s)
            if (0 <= vDotR  and vDotR <= np.dot(r,r)) or (0 <= aDotS  and aDotS <= np.dot(s,s)):
                return True
        # 2. If neither 0 <= (q - p) * r = r * r nor 0 <= (p - q) * s <= s * s
        # then the two lines are collinear but disjoint.
        # No need to implement this expression, as it follows from the expression above.
        return None
    if np.isclose(denom, 0) and not np.isclose(num, 0):
        # Parallel and non intersecting
        return None
    u = num / denom
    t = np_cross_product(v, s) / denom
    if u >= 0 and u <= 1 and t >= 0 and t <= 1:
        res = b[0] + (s*u)
        return res
    # Otherwise, the two line segments are not parallel but do not intersect.
    return None

0

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


3
โปรดทราบว่านี่เป็นคำตอบที่สมเหตุสมผลสำหรับคำถามตามกรอบเดิม แต่ตอนนี้คำถามได้รับการแก้ไขอย่างหนักแล้วมันไม่ได้สมเหตุสมผลนัก
GS - ขอโทษ Monica

0

ตามคำตอบของ t3chb0t:

int intersezione_linee(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4, int& p_x, int& p_y)
{
   //L1: estremi (x1,y1)(x2,y2) L2: estremi (x3,y3)(x3,y3)
   int d;
   d = (x1-x2)*(y3-y4) - (y1-y2)*(x3-x4);
   if(!d)
       return 0;
   p_x = ((x1*y2-y1*x2)*(x3-x4) - (x1-x2)*(x3*y4-y3*x4))/d;
   p_y = ((x1*y2-y1*x2)*(y3-y4) - (y1-y2)*(x3*y4-y3*x4))/d;
   return 1;
}

int in_bounding_box(int x1, int y1, int x2, int y2, int p_x, int p_y)
{
    return p_x>=x1 && p_x<=x2 && p_y>=y1 && p_y<=y2;

}

int intersezione_segmenti(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4, int& p_x, int& p_y)
{
    if (!intersezione_linee(x1,y1,x2,y2,x3,y3,x4,y4,p_x,p_y))
        return 0;

    return in_bounding_box(x1,y1,x2,y2,p_x,p_y) && in_bounding_box(x3,y3,x4,y4,p_x,p_y);
}

0

ฉันอ่านอัลกอริทึมเหล่านี้จากหนังสือ "เรขาคณิตหลายมุมมอง"

ข้อความต่อไปนี้โดยใช้

'เป็นสัญญาณการย้าย

* เป็นผลิตภัณฑ์ dot

x เป็น cross cross เมื่อใช้เป็น operator

1. คำจำกัดความของบรรทัด

จุด x_vec = (x, y) 'อยู่บนเส้นขวาน + โดย + c = 0

เราแสดงว่า L = (a, b, c) ', จุดที่ (x, y, 1)' เป็นพิกัดที่เป็นเนื้อเดียวกัน

สมการบรรทัดสามารถเขียนเป็น

(x, y, 1) (a, b, c) '= 0 หรือ x' * L = 0

2. จุดตัดของเส้น

เรามีสองบรรทัด L1 = (a1, b1, c1) ', L2 = (a2, b2, c2)'

สมมติว่า x คือจุดหนึ่งเวกเตอร์และ x = L1 x L2 (L1 ข้ามผลิตภัณฑ์ L2)

ระวัง x เป็นจุด 2 มิติเสมอโปรดอ่านพิกัดที่เป็นเนื้อเดียวกันหากคุณสับสน (L1xL2) เป็นเวกเตอร์สามองค์ประกอบและ x เป็นพิกัด 2 มิติ

ตามผลิตภัณฑ์สามเรารู้ว่า

L1 * (L1 x L2) = 0, และ L2 * (L1 x L2) = 0, เนื่องจาก L1, L2 co-plane

เราแทน (L1xL2) ด้วย vector x จากนั้นเรามี L1 * x = 0, L2 * x = 0 ซึ่งหมายความว่า x นอนทั้ง L1 และ L2, x คือจุดตัด

ระวังนี่คือพิกัด x ที่เป็นเนื้อเดียวกันหากองค์ประกอบสุดท้ายของ x เป็นศูนย์หมายความว่า L1 และ L2 ขนานกัน


0

คำตอบจำนวนมากได้รวมการคำนวณทั้งหมดไว้ในฟังก์ชั่นเดียว หากคุณต้องการคำนวณความชันของเส้น, จุดตัดแกน y หรือจุดตัดแกน x เพื่อใช้ที่อื่นในรหัสของคุณคุณจะต้องทำการคำนวณซ้ำซ้อน ฉันได้แยกฟังก์ชั่นที่เกี่ยวข้องใช้ชื่อตัวแปรที่ชัดเจนและใส่รหัสของฉันเพื่อให้ง่ายต่อการติดตาม ฉันต้องการทราบว่าเส้นตัดกันเกินกว่าจุดสิ้นสุดหรือไม่ดังนั้นใน JavaScript:

http://jsfiddle.net/skibulk/evmqq00u/

var point_a = {x:0, y:10},
    point_b = {x:12, y:12},
    point_c = {x:10, y:0},
    point_d = {x:0, y:0},
    slope_ab = slope(point_a, point_b),
    slope_bc = slope(point_b, point_c),
    slope_cd = slope(point_c, point_d),
    slope_da = slope(point_d, point_a),
    yint_ab = y_intercept(point_a, slope_ab),
    yint_bc = y_intercept(point_b, slope_bc),
    yint_cd = y_intercept(point_c, slope_cd),
    yint_da = y_intercept(point_d, slope_da),
    xint_ab = x_intercept(point_a, slope_ab, yint_ab),
    xint_bc = x_intercept(point_b, slope_bc, yint_bc),
    xint_cd = x_intercept(point_c, slope_cd, yint_cd),
    xint_da = x_intercept(point_d, slope_da, yint_da),
    point_aa = intersect(slope_da, yint_da, xint_da, slope_ab, yint_ab, xint_ab),
    point_bb = intersect(slope_ab, yint_ab, xint_ab, slope_bc, yint_bc, xint_bc),
    point_cc = intersect(slope_bc, yint_bc, xint_bc, slope_cd, yint_cd, xint_cd),
    point_dd = intersect(slope_cd, yint_cd, xint_cd, slope_da, yint_da, xint_da);

console.log(point_a, point_b, point_c, point_d);
console.log(slope_ab, slope_bc, slope_cd, slope_da);
console.log(yint_ab, yint_bc, yint_cd, yint_da);
console.log(xint_ab, xint_bc, xint_cd, xint_da);
console.log(point_aa, point_bb, point_cc, point_dd);

function slope(point_a, point_b) {
  var i = (point_b.y - point_a.y) / (point_b.x - point_a.x);
  if (i === -Infinity) return Infinity;
  if (i === -0) return 0;
  return i;
}

function y_intercept(point, slope) {
    // Horizontal Line
    if (slope == 0) return point.y;
  // Vertical Line
    if (slope == Infinity)
  {
    // THE Y-Axis
    if (point.x == 0) return Infinity;
    // No Intercept
    return null;
  }
  // Angled Line
  return point.y - (slope * point.x);
}

function x_intercept(point, slope, yint) {
    // Vertical Line
    if (slope == Infinity) return point.x;
  // Horizontal Line
    if (slope == 0)
  {
    // THE X-Axis
    if (point.y == 0) return Infinity;
    // No Intercept
    return null;
  }
  // Angled Line
  return -yint / slope;
}

// Intersection of two infinite lines
function intersect(slope_a, yint_a, xint_a, slope_b, yint_b, xint_b) {
  if (slope_a == slope_b)
  {
    // Equal Lines
    if (yint_a == yint_b && xint_a == xint_b) return Infinity;
    // Parallel Lines
    return null;
  }
  // First Line Vertical
    if (slope_a == Infinity)
  {
    return {
        x: xint_a,
      y: (slope_b * xint_a) + yint_b
    };
  }
  // Second Line Vertical
    if (slope_b == Infinity)
  {
    return {
        x: xint_b,
      y: (slope_a * xint_b) + yint_a
    };
  }
  // Not Equal, Not Parallel, Not Vertical
  var i = (yint_b - yint_a) / (slope_a - slope_b);
  return {
    x: i,
    y: (slope_a * i) + yint_a
  };
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.