Rilevamento di collisioni 2D


21

Questa sfida si basa sull'effettivo rilevamento delle collisioni che ho dovuto scrivere di recente per un semplice gioco.

Scrivi un programma o una funzione che, dati due oggetti, restituisce un valore di verità o falsa a seconda che i due oggetti siano in collisione (cioè si intersecano) o meno.

Devi supportare tre tipi di oggetti:

  • Segmenti di linea : rappresentati da 4 float, che indicano i due punti finali, ovvero (x 1 , y 1 ) e (x 2 , y 2 ) . Si può presumere che gli endpoint non siano identici (quindi il segmento di linea non è degenerato).
  • Dischi : cioè cerchi pieni, rappresentati da 3 galleggianti, due per il centro (x, y) e uno (positivo) per il raggio r .
  • Cavità : questi sono complementi di un disco. Cioè, una cavità riempie tutto lo spazio 2D, tranne una regione circolare, specificata da un centro e un raggio.

Il tuo programma o funzione riceverà due di questi oggetti sotto forma di un numero intero identificativo (a tua scelta) e dei loro 3 o 4 float. È possibile accettare input tramite STDIN, ARGV o argomento della funzione. È possibile rappresentare l'input in qualsiasi forma conveniente che non sia preelaborata, ad esempio da 8 a 10 numeri singoli, due elenchi di valori separati da virgole o due elenchi. Il risultato può essere restituito o scritto su STDOUT.

Si può presumere che gli oggetti siano distanti almeno 10-10 unità di lunghezza o si intersecino di così tanto, quindi non è necessario preoccuparsi dei limiti dei tipi in virgola mobile.

Questo è il golf del codice, quindi vince la risposta più breve (in byte).

Casi test

Rappresentando segmenti di linea con 0, dischi con 1e cavità con 2, utilizzando un formato di input basato su elenco, tutti i seguenti dovrebbero produrre un output veritiero:

[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

mentre quanto segue dovrebbe comportare un output errato

[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

All'inizio più complicato di quanto pensassi. A partire dal caso line / line, mi imbatto in un numero sorprendente di casi limite. Non potresti non consentire segmenti collineari? Renderebbe le cose molto più facili. ;)
Emil,

@Emil Siamo spiacenti, ma 9 ore dopo la pubblicazione, dovrò presumere che altri potrebbero aver iniziato a lavorare sulla sfida e cambiare le specifiche (a parte risolvere i problemi di rottura) non mi sembra una buona idea. A seconda di come lo fai, penso che i segmenti di linea parallela dovrebbero essere l'unico caso limite di cui devi preoccuparti per le collisioni di linea.
Martin Ender,

Certo, non mi aspettavo davvero che lo cambiassi. Ero solo un po 'frustrato dal fatto che la gestione delle diverse varianti dei segmenti di linea collineari ha raddoppiato la lunghezza del mio codice finora. :) (A proposito, una grande sfida!)
Emil

I punti collineari non rientrano nel campo "non si scontrano per 10 ^ -10"?
TwiNight,

@TwiNight Non se le due linee sono collineari ma non si sovrappongono. Ad esempio[0,[-2,0],[-1,0]], [0,[1,0],[2,0]]
Martin Ender,

Risposte:


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}

Le interruzioni di riga nella funzione fsono per chiarezza. Dovrebbero essere sostituiti con il separatore di istruzioni

È passato così tanto tempo dall'ultima volta che ho realizzato un programma APL così complesso. Penso che l'ultima volta sia stato questo, ma non sono nemmeno sicuro che fosse così complesso.

Formato di input
Fondamentalmente uguale all'OP, tranne che 0per cavità, 1disco e 2segmento di linea.

Aggiornamento importante

Sono riuscito a golf un sacco di caratteri usando un algoritmo diverso. Niente più gtori ** t !!

La funzione principale fè divisa in casi:


2 2≡x: Segmento di segmento

In questo caso, calcolare il vettore dai punti finali di ogni linea e risolvere un sistema di equazioni lineari per verificare se l'intersezione è contenuta nei vettori.

Avvertenze:

  • Il punto finale di un vettore non è considerato come parte del vettore (mentre la sua origine è). Tuttavia, se solo la punta di un vettore si trova sull'altro, l'input non è valido secondo le specifiche.
  • I segmenti paralleli non degeneri restituiscono sempre false, indipendentemente dalla collinearità.
  • Se uno dei segmenti è degenerato, restituisce sempre false. Se entrambi i segmenti sono degeneri, restituisce sempre true.

Esempi: (nota l'avvertenza 1 in azione nella figura a destra)


2≡2⌷x: Segmento-altro

In questo caso, l'altro oggetto è circolare. Controlla se i punti finali del segmento si trovano all'interno del cerchio usando il controllo della distanza.

Nel caso del disco, costruisci anche un segmento di linea del diametro perpendicolare al segmento dato. Controlla se i segmenti si scontrano per ricorsione.
Nel caso della cavità, intrufolarsi in uno "volte 0" nella costruzione di detto segmento per renderlo degenerato. (Vedi perché uso adesso 0per cavità e 1per disco?) Dato che il segmento dato non è degenerato, il rilevamento delle collisioni segmento segmento restituisce sempre falso.

Combina infine i risultati dei controlli di distanza e il rilevamento delle collisioni. Nel caso della cavità, annullare prima i risultati dei controlli della distanza. Quindi (in entrambi i casi) OPPURE i 3 risultati insieme.

Per quanto riguarda gli avvertimenti segmento-segmento, i numeri 3 sono indirizzati (e sfruttati). Il numero 2 non è un problema poiché qui intersechiamo segmenti perpendicolari, che non sono mai paralleli se non sono degenerati. Il numero 1 ha effetto solo nel caso del disco, quando uno dei punti finali indicati si trova sul diametro costruito. Se il punto finale si trova all'interno del cerchio, i controlli della distanza si sarebbero occupati di esso. Se il punto finale si trova sul cerchio, poiché il diametro costruito è parallelo al segmento dato, quest'ultimo deve essere tangente al cerchio con un solo punto a contatto con il disco, che non è un input valido.

Esempi:


Caso predefinito: Altro-altro

Calcola la distanza tra i centri. La collisione disco-disco si verifica se e solo se la distanza è inferiore alla somma dei raggi. La collisione della cavità del disco si verifica se e solo se la distanza è maggiore della differenza di raggio.

Per occuparsi del caso cavità-cavità, annulla il risultato del controllo della distanza, E con ciascuno degli interi identificativi e quindi OR insieme. Usando un po 'di logica, si può dimostrare che questo processo ritorna vero se e solo se entrambi gli interi identificativi sono falsi (caso cavità-cavità) o se il controllo della distanza è tornato vero


La mia comprensione è che se il tuo programma è scritto usando caratteri che si estendono su Unicode anziché ASCII, il conteggio dei byte deve riconoscere la natura a 2 byte per carattere di Unicode.
COTO

@COTO Non ho specificato Unicode. Per quanto ne so, il set di caratteri APL si adatta a un byte e ci sono pagine di codice a byte singolo che contengono tutti i caratteri APL, quindi usando quella codifica, il conteggio dei byte va bene. Il conteggio dei byte è principalmente rilevante per le persone che codificano roba in stringhe multi-byte in linguaggi "normali" o che usano le scorciatoie Unicode di Mathematica.
Martin Ender,

@ MartinBüttner: Quindi stai dicendo che anche se nessuno poteva ragionevolmente o praticamente rappresentare la versione a 1 byte per carattere della stringa in qualsiasi editor di testo oltre a uno progettato esplicitamente per APL, conta come 1 byte per carattere perché ci sono 256 o meno caratteri consentiti nelle specifiche della lingua?
COTO

@COTO Bene e perché esistono codifiche in base alle quali un file codificato a byte singolo potrebbe essere interpretato. Non penso che sarei disposto a percorrere questa strada se le persone dovessero inventare la loro codifica. Altrimenti qualsiasi programma che usa meno di 257 caratteri distinti potrebbe affermarlo (che sarebbe quasi una risposta su PPCG, credo). Penso solo che non dovremmo penalizzare APL per aver preceduto Unicode di diversi decenni - allora aveva senso interpretare i byte che avevi come strani personaggi funky che funzionano come mnemonici.
Martin Ender,

1
@COTO C'è J, che si basa su APL e usa solo caratteri ASCII. Di solito segnano in modo simile, quindi probabilmente ti batterebbe anche se segnato da Unicode. E dovrei aggiungere che nessuno dei due linguaggi è stato progettato per il golf e che AFAIK sono entrambi utilizzati in modo professionale. E le sfide del golf qui non sono tanto quella di ottenere il segno di spunta verde, ma piuttosto di spremere ogni ultimo piccolo byte dal tuo programma con i mezzi della tua lingua e battere tutti nella stessa "categoria di peso", che di solito ti farà guadagnare più voti che usare comunque una lingua concisa. ;)
Martin Ender,

5

Javascript - 393 byte

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

Allargato:

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

Gli appunti:

  • definisce la funzione Fche accetta gli argomenti richiesti e restituisce il valore richiesto
  • il formato di input è identico al formato nell'OP, con l'eccezione che il codice del tipo intero per ciascuna primitiva è separato dalla tupla. Ad esempio, F( 0,[[0,0],[2,2]], 0,[[1,0],[2,4]] )oppure F( 1,[[3,0],1], 2,[[0,0],1] ).
  • codice validato su tutti i casi di test forniti nel PO
  • dovrebbe gestire tutti i casi di bordi e angoli, inclusi segmenti di linea a lunghezza zero e cerchi a raggio zero

Ah, grazie per aver menzionato quei due casi limite. I cerchi a raggio zero non devono essere gestiti (il post dice raggio positivo) e chiarirò anche che le estremità del segmento di linea saranno distinte.
Martin Ender,

4

Python, 284

Sto usando un algoritmo piuttosto spazzatura rispetto a tutti questi trucchi geometrici, ma ottiene le risposte giuste anche se ci vuole più di un minuto per superare i casi di test. Il grande vantaggio è che devo solo scrivere le tre funzioni di punteggio e l'arrampicata si occupa di tutti i casi limite.

golfed:

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

E infine, uno script di prova nel caso in cui qualcun altro voglia provare questo in 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)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.