Phát hiện va chạm 2D


21

Thử thách này dựa trên phát hiện va chạm thực tế mà tôi đã phải viết cho một trò chơi đơn giản gần đây.

Viết chương trình hoặc hàm, cho hai đối tượng, trả về giá trị trung thực hoặc sai lệch tùy thuộc vào việc hai đối tượng có va chạm (tức là giao nhau) hay không.

Bạn cần hỗ trợ ba loại đối tượng:

  • Phân đoạn dòng : được biểu thị bằng 4 số float, biểu thị hai điểm cuối, nghĩa là (x 1 , y 1 )(x 2 , y 2 ) . Bạn có thể giả sử rằng các điểm cuối không giống nhau (vì vậy đoạn đường không bị suy biến).
  • Đĩa : tức là các vòng tròn được lấp đầy, được biểu thị bằng 3 số float, hai cho tâm (x, y) và một (dương) cho bán kính r .
  • Sâu răng : đây là một bổ sung của đĩa. Đó là, một khoang lấp đầy tất cả không gian 2D, ngoại trừ một vùng hình tròn, được chỉ định bởi một trung tâm và bán kính.

Chương trình hoặc hàm của bạn sẽ nhận được hai đối tượng như vậy dưới dạng một số nguyên xác định (bạn chọn) và 3 hoặc 4 số float của chúng. Bạn có thể nhận đầu vào thông qua STDIN, ARGV hoặc đối số hàm. Bạn có thể biểu thị đầu vào dưới bất kỳ hình thức thuận tiện nào chưa được xử lý trước, ví dụ 8 đến 10 số riêng lẻ, hai danh sách giá trị được phân tách bằng dấu phẩy hoặc hai danh sách. Kết quả có thể được trả lại hoặc ghi vào STDOUT.

Bạn có thể giả định rằng các đối tượng cách nhau ít nhất 10 -10 đơn vị hoặc giao nhau với nhau, vì vậy bạn không cần phải lo lắng về các giới hạn của các loại dấu phẩy động.

Đây là mã golf, vì vậy câu trả lời ngắn nhất (tính bằng byte) sẽ thắng.

Các trường hợp thử nghiệm

Đại diện cho các phân đoạn dòng với 0, đĩa có 1và khoang với 2, sử dụng định dạng đầu vào dựa trên danh sách, tất cả những điều sau đây sẽ tạo ra một đầu ra trung thực:

[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

trong khi sau đây tất cả sẽ dẫn đến một đầu ra giả

[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

Lừa hơn tôi nghĩ lúc đầu. Bắt đầu với trường hợp dòng / dòng, tôi gặp một số trường hợp cạnh đáng ngạc nhiên. Bạn không thể không cho phép các phân khúc collinear? Sẽ làm cho mọi thứ dễ dàng hơn nhiều. ;)
Emil

@Emil Xin lỗi, nhưng 9 giờ sau khi đăng bài, tôi sẽ phải cho rằng những người khác có thể đã bắt đầu thực hiện thử thách và thay đổi thông số kỹ thuật (ngoài việc khắc phục sự cố vi phạm) có vẻ không phải là ý tưởng hay đối với tôi. Tuy nhiên, tùy thuộc vào cách bạn thực hiện, các phân đoạn đường song song sẽ là trường hợp cạnh duy nhất bạn cần lo lắng về các va chạm dòng-đường, tuy nhiên, tôi nghĩ vậy.
Martin Ender

Chắc chắn, tôi đã không thực sự mong đợi bạn thay đổi nó. Tôi chỉ hơi thất vọng một chút khi xử lý các biến thể khác nhau của các đoạn đường thẳng cộng hưởng gấp đôi chiều dài mã của tôi cho đến nay. :) (Một thử thách lớn, nhân tiện!)
Emil

Các điểm cộng tuyến không thuộc "không va chạm với 10 ^ -10"?
TwiNight

@TwiNight Không phải nếu hai dòng này thẳng hàng nhưng không trùng nhau. Ví dụ:[0,[-2,0],[-1,0]], [0,[1,0],[2,0]]
Martin Ender

Câu trả lời:


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}

Ngắt dòng trong chức năng flà cho rõ ràng. Chúng nên được thay thế bằng dấu phân cách câu lệnh

Đã quá lâu kể từ lần cuối tôi thực hiện một chương trình APL phức tạp như vậy. Tôi nghĩ lần trước là thế này nhưng tôi thậm chí không chắc nó phức tạp đến thế.

Định dạng đầu vào
Về cơ bản giống như OP, ngoại trừ sử dụng 0cho khoang, 1cho đĩa và 2cho phân đoạn dòng.

Cập nhật lớn

Tôi đã quản lý để chơi golf rất nhiều ký tự bằng một thuật toán khác. Không còn gbò đực nữa ** t !!

Các chức năng chính fđược chia thành các trường hợp:


2 2≡x: Phân khúc phân khúc

Trong trường hợp này, tính toán vectơ từ các điểm cuối của mỗi dòng và giải hệ phương trình tuyến tính để kiểm tra xem giao điểm có được chứa trong các vectơ không.

Hãy cẩn thận:

  • Điểm cuối của một vectơ không được coi là một phần của vectơ (trong khi gốc của nó là). Tuy nhiên, nếu chỉ có đầu của một vectơ nằm trên một vectơ khác, đầu vào không hợp lệ theo thông số kỹ thuật.
  • Các đoạn song song không suy biến luôn trả về false, bất kể cộng tuyến.
  • Nếu một trong các phân đoạn bị suy biến, luôn luôn trả về false. Nếu cả hai phân đoạn đều suy biến, luôn luôn trả về đúng.

Ví dụ: (Lưu ý cảnh báo 1 trong hành động trong hình bên phải)


2≡2⌷x: Phân đoạn khác

Trong trường hợp này, đối tượng khác là một hình tròn. Kiểm tra xem các điểm cuối của đoạn có nằm trong vòng tròn hay không bằng cách sử dụng kiểm tra khoảng cách.

Trong trường hợp đĩa, cũng xây dựng một đoạn đường thẳng có đường kính vuông góc với đoạn đã cho. Kiểm tra nếu các phân đoạn va chạm bởi đệ quy.
Trong trường hợp khoang, lẻn vào "lần 0" trong việc xây dựng đoạn nói trên để làm cho nó thoái hóa. (Xem lý do tại sao tôi sử dụng 0cho khoang và 1cho đĩa ngay bây giờ?) Vì phân đoạn đã cho không suy biến, phát hiện va chạm phân đoạn luôn trả về sai.

Cuối cùng kết hợp các kết quả kiểm tra khoảng cách và phát hiện va chạm. Đối với trường hợp khoang, trước tiên hãy phủ nhận kết quả kiểm tra khoảng cách. Sau đó (trong cả hai trường hợp) HOẶC 3 kết quả cùng nhau.

Về các cảnh báo phân khúc phân khúc, số 3 được đề cập (và khai thác). Số 2 không phải là vấn đề vì chúng ta đang giao nhau giữa các đoạn vuông góc ở đây, không bao giờ song song nếu chúng không suy biến. Số 1 chỉ có hiệu lực trong trường hợp đĩa, khi một trong các điểm cuối đã cho nằm trên đường kính được xây dựng. Nếu điểm cuối nằm trong vòng tròn, kiểm tra khoảng cách sẽ quan tâm đến nó. Nếu điểm cuối nằm trên vòng tròn, vì đường kính được xây dựng song song với đoạn đã cho, điểm sau phải tiếp xúc với vòng tròn chỉ có một điểm chạm vào đĩa, không phải là đầu vào hợp lệ.

Ví dụ:


Trường hợp mặc định: Khác khác

Tính khoảng cách giữa các tâm. Va chạm đĩa-đĩa xảy ra khi và chỉ khi khoảng cách nhỏ hơn tổng bán kính. Va chạm khoang đĩa xảy ra khi và chỉ khi khoảng cách lớn hơn chênh lệch bán kính.

Để xử lý trường hợp khoang-khoang, phủ định kết quả kiểm tra khoảng cách, VÀ với từng số nguyên xác định và sau đó HOẶC chúng cùng nhau. Sử dụng một số logic, người ta có thể chỉ ra rằng quá trình này trả về đúng khi và chỉ khi cả hai số nguyên xác định là sai (trường hợp khoang-khoang) hoặc nếu kiểm tra khoảng cách trả về đúng


Tôi hiểu rằng nếu chương trình của bạn được viết bằng các ký tự trải dài Unicode chứ không phải ASCII, thì số byte cần phải thừa nhận bản chất 2 byte mỗi ký tự của Unicode.
COTO

@COTO Tôi không chỉ định Unicode. Theo như tôi biết, các bộ ký tự APL không phù hợp vào một byte, và có single-byte đang trang có chứa tất cả các ký tự APL, vì vậy sử dụng mã hóa đó, số lượng byte là tốt. Việc đếm theo byte hầu hết có liên quan đến những người mã hóa nội dung bằng chuỗi nhiều byte bằng ngôn ngữ "bình thường" hoặc những người sử dụng phím tắt Unicode của Mathicala.
Martin Ender

@ MartinBüttner: Vì vậy, bạn đang nói rằng mặc dù không ai có thể đại diện một cách hợp lý hoặc thực tế phiên bản chuỗi 1 byte cho mỗi trình soạn thảo văn bản ngoài một trình soạn thảo được thiết kế rõ ràng cho APL, nó được tính là 1 byte cho mỗi char bởi vì có 256 hoặc ít hơn các ký tự được phép trong thông số ngôn ngữ?
COTO

@COTO Vâng và bởi vì mã hóa tồn tại theo đó một tệp được mã hóa một byte có thể được diễn giải. Tôi không nghĩ rằng tôi sẵn sàng đi theo con đường đó nếu mọi người phải tạo ra mã hóa của họ. Mặt khác, bất kỳ chương trình nào sử dụng ít hơn 257 ký tự riêng biệt đều có thể yêu cầu điều đó (đó sẽ gần như là bất kỳ câu trả lời nào trên PPCG, tôi nghĩ vậy). Tôi chỉ nghĩ rằng chúng ta không nên phạt APL vì đã dự đoán Unicoding trong vài thập kỷ - hồi đó, thật hợp lý khi diễn giải các byte mà bạn có là những ký tự kỳ quặc kỳ lạ hoạt động như những điều tưởng niệm.
Martin Ender

1
@COTO Có J, dựa trên APL và chỉ sử dụng các ký tự ASCII. Họ thường ghi điểm tương tự, do đó có thể sẽ đánh bại bạn, ngay cả khi được ghi bằng Unicode. Và tôi nên nói thêm rằng không có ngôn ngữ nào được thiết kế để chơi gôn và cả AFAIK đều thực sự được sử dụng một cách chuyên nghiệp. Và những thách thức chơi gôn ở đây không phải là quá nhiều về việc đánh dấu màu xanh lá cây, mà nhiều hơn về việc vắt từng byte nhỏ cuối cùng ra khỏi chương trình của bạn bằng ngôn ngữ của bạn và đánh bại mọi người trong cùng một "hạng cân", thường sẽ giúp bạn kiếm được nhiều tiền hơn hơn là sử dụng một ngôn ngữ ngắn gọn ;)
Martin Ender

5

Javascript - 393 byte

Giảm thiểu:

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])

Mở rộng:

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

Ghi chú:

  • định nghĩa hàm Fchấp nhận các đối số được yêu cầu và trả về giá trị được yêu cầu
  • định dạng đầu vào giống hệt với định dạng trong OP, ngoại trừ mã loại số nguyên cho mỗi nguyên hàm tách biệt với bộ dữ liệu. Ví dụ, F( 0,[[0,0],[2,2]], 0,[[1,0],[2,4]] )hoặc F( 1,[[3,0],1], 2,[[0,0],1] ).
  • mã được xác thực trên tất cả các trường hợp thử nghiệm được cung cấp trong OP
  • nên xử lý tất cả các trường hợp cạnh và góc, bao gồm các đoạn đường có độ dài bằng không và vòng tròn bán kính bằng không

Ah, cảm ơn vì đã đề cập đến hai trường hợp cạnh. Các vòng tròn bán kính bằng không phải được xử lý (bài viết cho biết bán kính dương) và tôi cũng sẽ làm rõ rằng các đầu của đoạn đường sẽ khác biệt.
Martin Ender

4

Con trăn, 284

Tôi đang sử dụng một thuật toán rác khá so với tất cả các thủ thuật hình học này, nhưng nó đã có câu trả lời đúng mặc dù phải mất hơn một phút để vượt qua các trường hợp thử nghiệm. Ưu điểm lớn là tôi chỉ phải viết ba chức năng cho điểm, và việc trả tiền đồi sẽ đảm nhiệm tất cả các trường hợp biên.

Chơi gôn

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

Ung dung:

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

Và cuối cùng, một kịch bản thử nghiệm trong trường hợp bất kỳ ai khác muốn thử điều này trong python:

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)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.