二维碰撞检测


21

这个挑战基于我最近不得不为一个简单的游戏编写的实际碰撞检测。

编写一个程序或函数,给定两个对象,该程序或函数将根据两个对象是否发生碰撞(即相交)来返回真实值或虚假值

您需要支持三种类型的对象:

  • 线段:由4个浮点数表示,指示两个端点,即(x 1,y 1(x 2,y 2。您可以假定端点不相同(因此线段不会退化)。
  • 圆盘:即实心圆,以3个浮点表示,两个为中心(x,y),一个为(正)半径r
  • :这些是光盘的补充。也就是说,除了由中心和半径指定的圆形区域之外,空腔会填充所有2D空间。

您的程序或函数将以标识整数(您选择的形式)及其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

比起初我想的要棘手。从行/行案例开始,我遇到了数量惊人的边缘案例。您不能禁止共线段吗?会使事情变得容易得多。;)
Emil

@Emil对不起,但是发布后9个小时,我不得不假设其他人可能已经开始应对挑战,并且更改规范(除了解决重大问题之外)对我来说不是一个好主意。不过,我认为,取决于您的操作方式,平行线段应该是您需要担心的线-线碰撞的唯一边缘情况。
马丁·恩德

当然,我没想到您会更改它。到目前为止,处理共线线段的不同变体使我的代码长度翻了一番,对此我感到有点沮丧。:)(顺便说一下,这是一个巨大的挑战!)
Emil,2014年

共线点是否不落在“不会碰撞10 ^ -10”之下?
TwiNight

@TwiNight如果两行是共线的,但不重叠,则不行。EG[0,[-2,0],[-1,0]], [0,[1,0],[2,0]]
马丁安德

Answers:


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公牛**!

主要功能f分为几种情况:


2 2≡x:细分市场

在这种情况下,请从每条线的端点计算向量,并求解线性方程组,以检查向量中是否包含交点。

注意事项:

  • 向量的终点不视为向量的一部分(尽管其起点是)。但是,如果仅矢量的尖端在另一个上,则根据规范,输入无效。
  • 不论共线性,非退化并行段始终返回false。
  • 如果其中一个段退化,则始终返回false。如果两个段均退化,则始终返回true。

示例:(请注意右图中的警告1)


2≡2⌷x:细分-其他

在这种情况下,另一个对象是圆形对象。使用距离检查来检查线段的端点是否在圆内。

在光盘的情况下,还应构造一个直径垂直于给定线段的线段。通过递归检查线段是否碰撞。
在空腔情况下,在所述段的构造中潜入“乘以0”以使其退化。(请参阅为什么我要使用0腔体和1光盘?)由于给定的段不退化,因此段段碰撞检测始终返回false。

最后结合距离检查和碰撞检测的结果。对于腔体情况,请先取消距离检查的结果。然后(在两种情况下)将3个结果相加或。

关于段段警告,编号3已解决(并已被利用)。2号不是问题,因为我们在这里相交垂直段,如果它们不退化,则它们永远不会平行。当给定端点之一位于构造直径上时,数字1仅在圆盘情况下有效。如果终点在圆内,则距离检查会解决这个问题。如果端点在圆上,则由于构造的直径与给定的线段平行,因此后者必须与圆相切,并且只有一个点接触圆盘,这是无效的输入。

例子:


默认情况:其他

计算中心之间的距离。当且仅当距离小于半径之和时,才会发生光盘冲突。当且仅当距离大于半径差时,才会发生圆盘腔碰撞。

要处理腔体情况,请对距离检查的结果取反,然后将其与每个标识整数进行“与”运算,然后对它们进行“或”运算。使用某种逻辑,当且仅当两个标识整数均是假的(空腔情况),或者距离检查返回true时,才可以表明该过程返回true。


我的理解是,如果您的程序是使用跨越Unicode而不是ASCII的字符编写的,则字节数需要确认Unicode的每字符2个字节的性质。
COTO 2014年

@COTO我未指定Unicode。据我所知,在APL字符集倒是可以为一个字节,并且有包含所有的APL字符的单字节代码页,所以使用该编码,字节数是好的。对于以“正常”语言在多字节字符串中编码内容的人,或者使用Mathematica的Unicode快捷方式的人,按字节计数最重要。
Martin Ender 2014年

@MartinBüttner:所以你说的是,即使没有一个文本编辑器为APL专门设计一个,每个人都无法合理地或实际地表示该字符串的每字节1字节版本,但它算作每字符1字节。因为语言规范中允许的字符数少于256个?
COTO 2014年

@COTO好吧,因为确实存在编码根据该编码可以解释单字节编码的文件。如果人们不得不进行编码,我认为我不愿意走那条路。否则,任何使用少于257个不同字符的程序都可以声明(我认为这几乎是PPCG的任何答案)。我只是认为我们不应该因为APL早于几十年而对APL进行惩罚-那时,将您拥有的字节解释为充当助记符的怪异时髦字符是有意义的。
Martin Ender 2014年

1
@COTO有一个J,它基于APL,仅使用ASCII字符。它们通常得分相似,因此即使使用Unicode得分也可能会打败您。而且我要补充一点,这两种语言都不是专为打高尔夫球而设计的,而AFAIK两者实际上都是专业人士使用的。而且这里打高尔夫球的挑战不仅仅在于获得绿色的勾号,还在于更多地用语言的手段从程序中挤出最后一个小字节并击败相同“体重类别”中的每个人,这通常会使您获得更多的赞誉而不是使用简洁的语言。;)
Martin Ender 2014年

5

Javascript-393个字节

缩小:

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中的格式相同,不同之处在于每个基元的整数类型代码与元组分开。例如,F( 0,[[0,0],[2,2]], 0,[[1,0],[2,4]] )F( 1,[[3,0],1], 2,[[0,0],1] )
  • 在OP中提供的所有测试用例上验证的代码
  • 应该处理所有的边角情况,包括零长度线段和零半径圆

啊,谢谢你提到这两个极端情况。不必处理零半径的圆(该帖子说的是正半径),并且我还将阐明线段的两端将是不同的。
马丁·恩德

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

取消高尔夫:

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

最后,一个测试脚本,以防其他人想在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)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.