2D衝突検出


21

この挑戦は、私が最近簡単なゲームのために書かなければならなかった実際の衝突検出に基づいています。

2つのオブジェクトが与えられたときに、2つのオブジェクトが衝突(交差)しているかどうかに応じて、真偽値を返すプログラムまたは関数を作成します。

次の3種類のオブジェクトをサポートする必要があります。

  • 線分:2つの端点、つまり(x 1、y 1(x 2、y 2)を示す4つの浮動小数点で表されます。エンドポイントが同一ではないと想定することができます(したがって、ラインセグメントは縮退していません)。
  • ディスク:塗りつぶされた円。3つのフロートで表され、中心(x、y)に2つ、半径rに 1(正)で表されます。
  • キャビティ:これらはディスクを補完するものです。つまり、空洞は、中心と半径で指定された円形の領域を除き、すべての2D空間を満たします。

プログラムまたは関数は、2つのそのようなオブジェクトを(選択した)識別整数の形式で受け取り、それらの3つまたは4つのフロートを受け取ります。STDIN、ARGV、または関数引数を介して入力を取得できます。入力は、前処理されていない便利な形式で表すことができます。たとえば、8〜10個の個別の数値、2つのコンマ区切りの値リスト、2つのリストなどです。結果を返すか、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

最初は思っていたよりトリッキー。ライン/ラインケースから始めて、驚くべき数のエッジケースに出会います。共線セグメントを禁止できませんでしたか?物事がはるかに簡単になります。;)
エミール14年

@Emil申し訳ありませんが、投稿してから9時間後には、他の人がチャレンジに取り組み始めた可能性があると推測する必要があります。ただし、その方法によっては、線と線の衝突について心配する必要がある唯一のエッジケースが平行線セグメントになるはずです。
マーティンエンダー14年

確かに、私はあなたがそれを変えると本当に期待していませんでした。これまでのところ、同一直線上の線セグメントのさまざまなバリアントを処理すると、コードの長さが2倍になったことに少しイライラしていました。:)(ところで、大きな挑戦!)
エミール14年

共線点は「10 ^ -10衝突しない」に該当しませんか?
TwiNight 14年

@TwiNight 2本の線が同一線上にあるが、重なっていない場合。例えば[0,[-2,0],[-1,0]], [0,[1,0],[2,0]]
マーティン・エンダー

回答:


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:セグメント-セグメント

この場合、各線の終点からベクトルを計算し、線形方程式系を解いて、交差がベクトル内に含まれているかどうかを確認します。

警告:

  • ベクトルの終点は、ベクトルの一部とは見なされません(ただし、その原点は)。ただし、ベクトルの先端だけが他の先端にある場合、仕様に従って入力は無効です。
  • 非縮退並列セグメントは、共線性に関係なく常にfalseを返します。
  • セグメントの1つが縮退している場合、常にfalseを返します。両方のセグメントが縮退している場合、常にtrueを返します。

例:(右側の図の動作中の警告1に注意してください)


2≡2⌷x:セグメント-その他

この場合、他のオブジェクトは円形オブジェクトです。距離チェックを使用して、セグメントの終点が円内にあるかどうかを確認します。

ディスクの場合、指定されたセグメントに垂直な直径の線セグメントも作成します。セグメントが再帰によって衝突するかどうかを確認します。
空洞の場合、前記セグメントの構成で「タイム0」を忍び込ませて、セグメントを縮退させます。(現在0、キャビティと1ディスクに使用している理由をご覧ください。)指定されたセグメントは縮退していないため、セグメントとセグメントの衝突検出は常にfalseを返します。

最後に、距離チェックと衝突検出の結果を組み合わせます。空洞の場合、最初に距離チェックの結果を無効にします。次に(両方の場合)、3つの結果をORで結合します。

セグメント間の注意事項については、3番が対処されています(そして悪用されています)。番号2は、ここでは垂直なセグメントと交差しているため問題ではありません。これらのセグメントは、縮退していない場合は決して平行になりません。番号1は、指定されたエンドポイントの1つが構築された直径上にある場合にのみ、ディスクケースで有効になります。終点が円の内側にある場合、距離チェックがそれを処理します。終点が円上にある場合、構築された直径は指定されたセグメントに平行であるため、セグメントは円に接している必要があり、1つの点のみがディスクに接触します。これは有効な入力ではありません。

例:


デフォルトの場合:その他

中心間の距離を計算します。ディスクとディスクの衝突は、距離が半径の合計よりも小さい場合にのみ発生します。ディスクとキャビティの衝突は、距離が半径の差よりも大きい場合にのみ発生します。

空洞-空洞のケースを処理するには、距離チェックの結果を否定し、識別整数のそれぞれとANDし、それらを一緒にORします。何らかのロジックを使用すると、両方の識別整数が偽である場合(キャビティキャビティの場合)、または距離チェックがtrueを返した場合にのみ、このプロセスがtrueを返すことを示すことができます。


私の理解では、プログラムがASCIIではなくUnicodeにまたがる文字を使用して記述されている場合、バイトカウントはUnicodeの1文字あたり2バイトの性質を認識する必要があるということです。
COTO 14年

@COTO Unicodeを指定しませんでした。これまで私が承知しているとしてとして、APL文字セットはバイトにフィットしていて、そこにあるすべてのAPL文字を含む単一バイト・コード・ページは、そのエンコーディングを使用するので、バイト数で結構です。バイトによるカウントは、「通常の」言語でマルチバイト文字列の内容をエンコードする人、またはMathematicaのUnicodeショートカットを使用する人に最も関連があります。
マーティンエンダー

@MartinBüttner:つまり、APL用に明示的に設計されたもの以外のテキストエディターでは、1文字あたり1バイトバージョンの文字列を合理的または実用的に表すことができなかったとしても、1文字あたり1バイトとしてカウントされます言語仕様に許可される文字が256個以下であるためですか?
COTO 14年

@COTOさて、エンコードシングルバイトのエンコードされたファイルを解釈できるように存在するためです。人々がエンコードを作成しなければならなかった場合、私はそのルートを進んで下るつもりはないと思います。そうでなければ、257個未満の異なる文字を使用するプログラムは、それを主張することができます(これは、PPCGのほとんどすべての答えになると思います)。ユニコーディングを数十年前に犯したことでAPLにペナルティを課すべきではないと思います-その当時、あなたが持っていたバイトをニーモニックとして機能する奇妙なファンキーなキャラクターとして解釈することは意味がありました。
マーティンエンダー

1
@COTO APLに基づいており、ASCII文字のみを使用するJがあります。彼らは通常、同様に得点するので、Unicodeで得点されたとしても、それはおそらくあなたにも勝るでしょう。そして、どちらの言語もゴルフ用に設計されたものではなく、AFAIKはどちらも実際に専門的に使用されています。そして、ここでのゴルフの課題は、緑色のチェックマークを取得することではなく、言語の手段でプログラムの最後の小さなバイトを絞り、同じ「重みカテゴリ」の全員を打つことです。とにかく簡潔な言語を使用するよりも。;)
マーティン・エンダー14年

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で提供されるすべてのテストケースで検証されたコード
  • 長さゼロの線分と半径ゼロの円を含む、すべてのエッジとコーナーのケースを処理する必要があります

ああ、これら2つのエッジケースについて言及してくれてありがとう。ゼロ半径の円を処理する必要はありません(投稿では正の半径を示しています)。また、線分セグメントの端が明確になることも明確にします。
マーティンエンダー14年

4

Python、284

私はこれらのすべての幾何学的なトリックと比較してかなりガーベッジアルゴリズムを使用していますが、テストケースを通過するのに1分以上かかるにもかかわらず、正しい答えが得られます。大きな利点は、3つのスコアリング関数を記述するだけでよいことです。また、ヒルクライミングがすべてのエッジケースを処理します。

ゴルフ:

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)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.