การตรวจจับการชนกันของ 2D


21

ความท้าทายนี้ขึ้นอยู่กับการตรวจจับการชนจริงที่ฉันได้เขียนสำหรับเกมง่ายๆเมื่อเร็ว ๆ นี้

เขียนโปรแกรมหรือฟังก์ชั่นที่ให้วัตถุสองชิ้นคืนค่าจริงหรือค่าเท็จขึ้นอยู่กับว่าวัตถุทั้งสองอยู่ในการชนกัน (เช่นตัดกัน) หรือไม่

คุณต้องรองรับวัตถุสามประเภท:

  • กลุ่มสาย : ตัวแทนจาก 4 ลอยระบุสองจุดสิ้นสุดคือ(x 1 , y 1 )และ(x 2 , y 2 ) คุณอาจสมมติว่าจุดสิ้นสุดไม่เหมือนกัน (ดังนั้นส่วนของเส้นจะไม่ลดลง)
  • แผ่น : คือเต็มไปวงการตัวแทนจาก 3 ลอยสองสำหรับศูนย์(x, y)และหนึ่ง (บวก) สำหรับรัศมีR
  • ฟันผุ : สิ่งเหล่านี้เป็นส่วนประกอบของดิสก์ นั่นคือโพรงเติมเต็มพื้นที่ 2 มิติทั้งหมดยกเว้นพื้นที่วงกลมซึ่งระบุโดยจุดศูนย์กลางและรัศมี

โปรแกรมหรือฟังก์ชั่นของคุณจะได้รับวัตถุสองชนิดนี้ในรูปแบบของจำนวนเต็มที่ระบุ (ที่คุณเลือก) และ 3 หรือ 4 อัน คุณสามารถรับอินพุตผ่าน STDIN, ARGV หรืออาร์กิวเมนต์ของฟังก์ชัน คุณอาจเป็นตัวแทนของอินพุตในรูปแบบที่สะดวกใด ๆ ที่ไม่ได้ประมวลผลล่วงหน้าเช่น 8 ถึง 10 หมายเลขบุคคลรายการค่าที่คั่นด้วยเครื่องหมายจุลภาคสองรายการหรือสองรายการ ผลลัพธ์สามารถส่งคืนหรือเขียนไปยัง STDOUT

คุณอาจสันนิษฐานว่าวัตถุนั้นมีหน่วยความยาวอย่างน้อย 10 -10ชิ้นหรือแยกจากกันมากดังนั้นคุณไม่ต้องกังวลเกี่ยวกับข้อ จำกัด ของชนิดจุดลอย

นี่คือรหัสกอล์ฟดังนั้นคำตอบที่สั้นที่สุด (เป็นไบต์) ชนะ

กรณีทดสอบ

การเป็นตัวแทนของส่วนของเส้นด้วย0แผ่นดิสก์1และช่องว่าง2โดยใช้รูปแบบอินพุตตามรายการต่อไปนี้ควรสร้างเอาต์พุตจริง:

[0,[0,0],[2,2]], [0,[1,0],[2,4]]        # Crossing line segments
[0,[0.5,0],[-0.5,0]], [1,[0,0],1]       # Line contained in a disc
[0,[0.5,0],[1.5,0]], [1,[0,0],1]        # Line partially within disc
[0,[-1.5,0.5],[1.5,0.5]], [1,[0,0],1]   # Line cutting through disc
[0,[0.5,2],[-0.5,2]], [2,[0,0],1]       # Line outside cavity
[0,[0.5,0],[1.5,0]], [2,[0,0],1]        # Line partially outside cavity
[0,[-1.5,0.5],[1.5,0.5]], [2,[0,0],1]   # Line cutting through cavity
[1,[0,0],1], [1,[0,0],2]                # Disc contained within another
[1,[0,0],1.1], [1,[2,0],1.1]            # Intersecting discs
[1,[3,0],1], [2,[0,0],1]                # Disc outside cavity
[1,[1,0],0.1], [2,[0,0],1]              # Disc partially outside cavity
[1,[0,0],2], [2,[0,0],1]                # Disc encircling cavity
[2,[0,0],1], [2,[0,0],1]                # Any two cavities intersect
[2,[-1,0],1], [2,[1,0],1]               # Any two cavities intersect

ในขณะที่ต่อไปนี้ควรส่งผลให้ผลลัพธ์ที่ผิดพลาด

[0,[0,0],[1,0]], [0,[0,1],[1,1]]        # Parallel lines
[0,[-2,0],[-1,0]], [0,[1,0],[2,0]]      # Collinear non-overlapping lines
[0,[0,0],[2,0]], [0,[1,1],[1,2]]        # Intersection outside one segment
[0,[0,0],[1,0]], [0,[2,1],[2,3]]        # Intersection outside both segments
[0,[-1,2],[1,2]], [1,[0,0],1]           # Line passes outside disc
[0,[2,0],[3,0]], [1,[0,0],1]            # Circle lies outside segment
[0,[-0.5,0.5],[0.5,-0.5]], [2,[0,0],1]  # Line inside cavity
[1,[-1,0],1], [1,[1,1],0.5]             # Non-intersecting circles
[1,[0.5,0],0.1], [2,[0,0],1]            # Circle contained within cavity

หลอกลวงกว่าที่ฉันคิดตอนแรก เริ่มจากกรณีที่บรรทัด / บรรทัดฉันมาจำนวนกรณีขอบที่น่าแปลกใจ คุณไม่สามารถไม่อนุญาตกลุ่ม collinear ใช่ไหม จะทำให้สิ่งต่าง ๆ ง่ายขึ้นมาก ;)
Emil

@Emil ขออภัย แต่หลังจาก 9 ชั่วโมงหลังจากโพสต์ฉันจะต้องคิดว่าคนอื่นอาจเริ่มทำงานกับความท้าทายและการเปลี่ยนสเป็ค (นอกเหนือจากการแก้ไขปัญหาแตกหัก) ดูเหมือนจะไม่เป็นความคิดที่ดีสำหรับฉัน ส่วนของเส้นคู่ขนานนั้นควรเป็นกรณีขอบเดียวที่คุณต้องกังวลเกี่ยวกับการชนกันของเส้นบรรทัดทั้งนี้ขึ้นอยู่กับวิธีที่คุณทำ
Martin Ender

แน่นอนฉันไม่ได้คาดหวังว่าคุณจะเปลี่ยนมัน ฉันรู้สึกหงุดหงิดเล็กน้อยที่จัดการคอลลินไลน์ที่แตกต่างกันของส่วนของเส้นคู่ในความยาวโค้ดของฉัน :) (ความท้าทายที่ยิ่งใหญ่โดยวิธี!)
Emil

คะแนน collinear ไม่อยู่ภายใต้ "ไม่ชนด้วย 10 ^ -10" หรือไม่
TwiNight

@TwiNight ไม่ได้หากสองบรรทัดนั้นเป็นแบบเส้นตรง แต่ไม่ทับซ้อนกัน ตัวอย่าง[0,[-2,0],[-1,0]], [0,[1,0],[2,0]]
Martin Ender

คำตอบ:


6

APL, 279 208 206 203

s←1 ¯1
f←{x←⊣/¨z←⍺⍵[⍋⊣/¨⍺⍵]
2 2≡x:∧/0∧.=⌊(2⊃-⌿↑z)⌹⍣(≠.×∘⌽/x)⍉↑x←s×-/2⊢/↑z
2≡2⌷x:∨/((2⊃z)∇2,x[1]×(2⌷⊃z)+,∘-⍨⊂y÷.5*⍨+.×⍨y←⌽s×⊃-/y),x[1]=(×⍨3⊃⊃z)>+.×⍨¨y←(s↓⌽↑z)-2⌷⊃z
~x∨.∧x[1]≠(.5*⍨+.×⍨2⊃-⌿↑z)<-/⊢/¨z×s*1⌷x}

การแบ่งบรรทัดในฟังก์ชันfใช้เพื่อความชัดเจน ควรแทนที่ด้วยตัวคั่นคำสั่ง

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

รูปแบบอินพุต
โดยทั่วไปเหมือนกับ OP ยกเว้นการใช้0สำหรับช่อง1สำหรับแผ่นดิสก์และ2สำหรับส่วนของเส้น

การอัปเดตที่สำคัญ

ฉันจัดการกับตัวอักษรจำนวนมากโดยใช้อัลกอริทึมที่แตกต่างกัน ไม่มีgบูลส์ ** t !!

หน้าที่หลักfแบ่งออกเป็นกรณีต่างๆ:


2 2≡x: ส่วนงาน

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

คำเตือน:

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

ตัวอย่าง: (หมายเหตุข้อสังเกต 1 ที่ใช้งานจริงในรูปด้านขวา)


2≡2⌷x: ส่วนอื่น ๆ

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

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

ในที่สุดก็รวมผลลัพธ์ของการตรวจสอบระยะทางและการตรวจจับการชนกัน สำหรับกรณีช่องให้ลบล้างผลลัพธ์ของการตรวจสอบระยะทางก่อน จากนั้น (ในทั้งสองกรณี) หรือ 3 ผลลัพธ์พร้อมกัน

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

ตัวอย่าง:


กรณีเริ่มต้น: อื่น ๆ อื่น ๆ

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

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


ความเข้าใจของฉันคือว่าถ้าโปรแกรมของคุณเขียนโดยใช้อักขระที่ครอบคลุม Unicode แทนที่จะเป็น ASCII การนับไบต์จำเป็นต้องยอมรับธรรมชาติ 2 ไบต์ต่ออักขระของ Unicode
COTO

@COTO ฉันไม่ได้ระบุ Unicode เท่าที่ผมทราบ, ชุดอักขระ APL ไม่พอดีไบต์และมีมีโค้ดเพจไบต์เดี่ยวที่มีตัวอักษรของทุก APL เพื่อใช้การเข้ารหัสที่นับไบต์จะปรับ การนับไบต์เป็นส่วนใหญ่ที่เกี่ยวข้องสำหรับผู้ที่เข้ารหัสข้อมูลในสตริงหลายไบต์ในภาษา "ปกติ" หรือผู้ที่ใช้ทางลัด Unicode ของ Mathematica
Martin Ender

@ MartinBüttner: ดังนั้นคุณกำลังพูดว่าแม้ว่าจะไม่มีใครสามารถแสดงสตริง 1-byte-per-char รุ่นในตัวแก้ไขข้อความใด ๆ นอกเหนือจากที่ออกแบบมาอย่างชัดเจนสำหรับ APL แต่ก็นับว่าเป็น 1 ไบต์ต่อถ่าน เพราะมีอักขระที่อนุญาต 256 ตัวหรือน้อยกว่าในสเป็คภาษา?
COTO

@COTO ดีและเนื่องจากการเข้ารหัสมีอยู่ตามที่ไฟล์เข้ารหัสไบต์เดียวสามารถตีความได้ ฉันไม่คิดว่าฉันยินดีที่จะลงเส้นทางนั้นถ้าผู้คนต้องทำการเข้ารหัสของพวกเขา มิฉะนั้นโปรแกรมใด ๆ ที่ใช้อักขระที่แตกต่างกันน้อยกว่า 257 ตัวสามารถอ้างได้ว่า (ซึ่งน่าจะเป็นคำตอบสำหรับ PPCG ฉันคิดว่า) ฉันแค่คิดว่าเราไม่ควรลงโทษ APL สำหรับการทำนาย Unicoding หลายทศวรรษ - หลังจากนั้นมันก็สมเหตุสมผลที่จะตีความไบต์ที่คุณมีเหมือนตัวละครขี้ขลาดแปลก ๆ ที่ทำงานเป็นตัวช่วยจำ
Martin Ender

1
@COTO มี J ซึ่งอิงจาก APL และใช้เฉพาะอักขระ ASCII เท่านั้น พวกเขามักจะทำคะแนนคล้ายกันดังนั้นที่อาจจะชนะคุณเช่นกันแม้ว่าคะแนนโดย Unicode และฉันควรเพิ่มว่าไม่มีภาษาใดที่ถูกออกแบบมาสำหรับการเล่นกอล์ฟและ AFAIK ทั้งสองถูกใช้อย่างมืออาชีพ และความท้าทายของการเล่นกอล์ฟที่นี่ไม่ได้เกี่ยวกับการทำเครื่องหมายสีเขียวมากนัก แต่ก็ยิ่งเพิ่มมากขึ้นเกี่ยวกับการบีบไบต์เล็ก ๆ น้อย ๆ ออกจากโปรแกรมของคุณด้วยวิธีการใช้ภาษาของคุณและเอาชนะทุกคนในหมวดหมู่ กว่าการใช้ภาษาสั้น ๆ อยู่ดี ;)
Martin Ender

5

Javascript - 393 ไบต์

minified:

F=(s,a,t,b,e,x)=>(x=e||F(t,b,s,a,1),[A,B]=a,[C,D]=b,r=(p,l)=>([g,h]=l,[f,i]=y(h,g),[j,k]=y(p,g),m=Math.sqrt(f*f+i*i),[(f*j+i*k)/m,(f*k-i*j)/m]),u=(p,c)=>([f,g]=c,[i,j]=y(p,f),i*i+j*j<g*g),y=(p,c)=>[p[0]-c[0],p[1]-c[1]],[n,o]=r(C,a),[q,v]=r(D,a),w=(v*n-o*q)/(v-o),z=r(B,a)[0],Y=u(A,b),Z=u(B,b),[v*o<0&&w*(w-z)<0,Y||Z||o<D&&o>-D&&n*(n-z)<0,!Y||!Z,x,u(A,[C,D+B]),B>D||!u(A,[C,D-B]),x,x,1][s*3+t])

ขยาย:

F = (s,a,t,b,e,x) => (
    x = e || F(t,b,s,a,1),
    [A,B] = a,
    [C,D] = b,
    r = (p,l) => (
        [g,h] = l,
        [f,i] = y(h,g),
        [j,k] = y(p,g),
        m = Math.sqrt( f*f + i*i ),
        [(f*j + i*k)/m, (f*k - i*j)/m] ),
    u = (p,c) => (
        [f,g] = c,
        [i,j] = y(p,f),
        i*i + j*j < g*g ),
    y = (p,c) => [p[0] - c[0], p[1] - c[1]],
    [n,o] = r(C,a),
    [q,v] = r(D,a),
    w = (v*n - o*q)/(v - o),
    z = r(B,a)[0],
    Y = u(A,b), Z = u(B,b),
    [   v*o < 0 && w*(w-z) < 0,
        Y || Z || o < D && o > -D && n*(n-z) < 0,
        !Y || !Z,
        x,
        u(A,[C,D+B]),
        B > D || !u(A,[C,D-B]),
        x,
        x,
        1
    ][s*3+t]);

หมายเหตุ:

  • กำหนดฟังก์ชั่นFที่ยอมรับข้อโต้แย้งที่ต้องการและส่งกลับค่าที่ต้องการ
  • รูปแบบอินพุตเหมือนกับรูปแบบใน OP ยกเว้นว่ารหัสชนิดจำนวนเต็มสำหรับแต่ละดั้งเดิมแยกจาก tuple ยกตัวอย่างเช่นหรือF( 0,[[0,0],[2,2]], 0,[[1,0],[2,4]] )F( 1,[[3,0],1], 2,[[0,0],1] )
  • รหัสตรวจสอบความถูกต้องในทุกกรณีทดสอบที่ให้มาใน OP
  • ควรจัดการกรณีขอบและมุมทั้งหมดรวมถึงส่วนของเส้นศูนย์ความยาวและวงกลมศูนย์รัศมี

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

4

Python, 284

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

แข็งแรงเล่นกอล์ฟ:

import math,random as r
n=lambda(a,c),(b,d):math.sqrt((a-b)**2+(c-d)**2)
x=lambda(t,a,b),p:max(eval(["n(b,p)-n(a,b)+","-b+","b-"][t]+'n(a,p)'),0)
def F(t,j):
q=0,0;w=1e9
 for i in q*9000:
    y=x(t,q)+x(j,q)
    if y<w:p,w=q,y
    q=(r.random()-.5)*w+p[0],(r.random()-.5)*w+p[1]
 return w<.0001

Ungolfed:

import math
import random as r
def norm(a, b):
 return math.sqrt((a[0] - b[0])**2 + (a[1] - b[1])**2)

def lineWeight(a, b, p):
 l1 = norm(a, p)
 l2 = norm(b, p)
 return min(l1, l2, l1 + l2 - norm(a, b))

def circleWeight(a, r, p):
 return max(0, norm(a, p) - r)

def voidWeight(a, r, p):
 return max(0, r - norm(a, p))

def weight(f1, f2, s1, s2, p):
 return f1(s1[1], s1[2], p) + f2(s2[1], s2[2], p)

def checkCollision(s1, s2):
 a = [lineWeight, circleWeight, voidWeight]
 f1 = a[s1[0]]
 f2 = a[s2[0]]
 p = (0.0, 0.0)
 w = 0
 for i in a*1000:
  w = weight(f1, f2, s1, s2, p)
  p2 = ((r.random()-.5)*w + p[0], (r.random()-.5)*w + p[1])
  if(weight(f1, f2, s1, s2, p2) < w):
   p = p2
 if w < .0001:
  return True
 return False

และท้ายที่สุดสคริปต์ทดสอบในกรณีที่คนอื่นต้องการลองใช้งานในไพ ธ อน:

import collisiongolfedbak
reload(collisiongolfedbak)

tests = [
[0,[0,0],[2,2]], [0,[1,0],[2,4]],        # Crossing line segments
[0,[0.5,0],[-0.5,0]], [1,[0,0],1],       # Line contained in a disc
[0,[0.5,0],[1.5,0]], [1,[0,0],1],        # Line partially within disc
[0,[-1.5,0.5],[1.5,0.5]], [1,[0,0],1],   # Line cutting through disc
[0,[0.5,2],[-0.5,2]], [2,[0,0],1],       # Line outside cavity
[0,[0.5,0],[1.5,0]], [2,[0,0],1],        # Line partially outside cavity
[0,[-1.5,0.5],[1.5,0.5]], [2,[0,0],1],   # Line cutting through cavity
[1,[0,0],1], [1,[0,0],2],                # Disc contained within another
[1,[0,0],1.1], [1,[2,0],1.1],            # Intersecting discs
[1,[3,0],1], [2,[0,0],1],                # Disc outside cavity
[1,[1,0],0.1], [2,[0,0],1],              # Disc partially outside cavity
[1,[0,0],2], [2,[0,0],1],                # Disc encircling cavity
[2,[0,0],1], [2,[0,0],1] ,               # Any two cavities intersect
[2,[-1,0],1], [2,[1,0],1] ,              # Any two cavities intersect
[0,[0,0],[1,0]], [0,[0,1],[1,1]] ,       # Parallel lines
[0,[-2,0],[-1,0]], [0,[1,0],[2,0]],      # Collinear non-overlapping lines
[0,[0,0],[2,0]], [0,[1,1],[1,2]],        # Intersection outside one segment
[0,[0,0],[1,0]], [0,[2,1],[2,3]],        # Intersection outside both segments
[0,[-1,2],[1,2]], [1,[0,0],1],           # Line passes outside disc
[0,[2,0],[3,0]], [1,[0,0],1],            # Circle lies outside segment
[0,[-0.5,0.5],[0.5,-0.5]], [2,[0,0],1],  # Line inside cavity
[1,[-1,0],1], [1,[1,1],0.5],             # Non-intersecting circles
[1,[0.5,0],0.1], [2,[0,0],1]            # Circle contained within cavity
]

for a, b in zip(tests[0::2], tests[1::2]):
 print collisiongolfedbak.F(a,b)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.