Détection de collision 2D

21

Ce défi est basé sur la détection de collision réelle que j'ai dû écrire récemment pour un jeu simple.

Écrivez un programme ou une fonction qui, compte tenu de deux objets, renvoie une valeur vraie ou fausse selon que les deux objets sont en collision (c'est-à-dire se croisent) ou non.

Vous devez prendre en charge trois types d'objets:

  • Segments de ligne : représentés par 4 flottants, indiquant les deux extrémités, à savoir (x 1 , y 1 ) et (x 2 , y 2 ) . Vous pouvez supposer que les points d'extrémité ne sont pas identiques (donc le segment de ligne n'est pas dégénéré).
  • Disques : c'est-à-dire des cercles remplis, représentés par 3 flotteurs, deux pour le centre (x, y) et un (positif) pour le rayon r .
  • Cavités : ce sont le complément d'un disque. C'est-à-dire qu'une cavité remplit tout l'espace 2D, à l' exception d' une région circulaire, spécifiée par un centre et un rayon.

Votre programme ou fonction recevra deux de ces objets sous la forme d'un entier identifiant (de votre choix) et leurs 3 ou 4 flottants. Vous pouvez prendre des entrées via STDIN, ARGV ou un argument de fonction. Vous pouvez représenter l'entrée sous n'importe quelle forme pratique qui n'est pas prétraitée, par exemple 8 à 10 nombres individuels, deux listes de valeurs séparées par des virgules ou deux listes. Le résultat peut être retourné ou écrit dans STDOUT.

Vous pouvez supposer que les objets sont séparés d' au moins 10 à 10 unités de longueur ou se croisent d'autant, vous n'avez donc pas à vous soucier des limitations des types à virgule flottante.

Il s'agit du code golf, donc la réponse la plus courte (en octets) l'emporte.

Cas de test

Représentant des segments de ligne avec 0, des disques avec 1et des cavités avec 2, en utilisant un format d'entrée basé sur une liste, les éléments suivants devraient tous produire une sortie véridique:

[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

tandis que les éléments suivants devraient tous entraîner une sortie fausse

[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
Martin Ender
la source
Plus délicat que je ne le pensais au début. En commençant par le cas ligne / ligne, je tombe sur un nombre surprenant de cas de bord. Ne pourriez-vous pas interdire les segments colinéaires? Rendrait les choses beaucoup plus faciles. ;)
Emil
@Emil Désolé, mais 9 heures après la publication, je dois supposer que d'autres ont peut-être commencé à travailler sur le défi, et changer les spécifications (en plus de résoudre les problèmes de rupture) ne me semble pas être une bonne idée. Selon la façon dont vous le faites, les segments de ligne parallèles devraient être le seul cas de bord dont vous devez vous soucier pour les collisions ligne-ligne, je pense.
Martin Ender
Bien sûr, je ne m'attendais pas vraiment à ce que vous le changiez. J'étais juste un peu frustré que la gestion des différentes variantes de segments de ligne colinéaires ait jusqu'à présent doublé la longueur de mon code. :) (Un grand défi, au fait!)
Emil
Les points colinéaires ne tombent-ils pas sous "n'entre pas en collision par 10 ^ -10"?
TwiNight
@TwiNight Pas si les deux lignes sont colinéaires mais ne se chevauchent pas. Par exemple[0,[-2,0],[-1,0]], [0,[1,0],[2,0]]
Martin Ender

Réponses:

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}

Les sauts de ligne dans la fonction fsont pour plus de clarté. Ils doivent être remplacés par le séparateur d'instructions

Cela fait si longtemps que je n'ai pas créé un programme APL aussi complexe. Je pense que la dernière fois ce mais je ne suis même pas sûr que c'était aussi complexe.

Format d'entrée
Fondamentalement identique à l'OP, sauf 0pour la cavité, le 1disque et le 2segment de ligne.

Mise à jour majeure

J'ai réussi à jouer beaucoup de caractères en utilisant un algorithme différent. Plus de gtaureaux ** t !!

La fonction principale fest divisée en cas:


2 2≡x: Segment-segment

Dans ce cas, calculez le vecteur à partir des points d'extrémité de chaque ligne et résolvez un système d'équations linéaires pour vérifier si l'intersection est contenue dans les vecteurs.

Mises en garde:

  • Le point final d'un vecteur n'est pas considéré comme faisant partie du vecteur (alors que son origine l'est). Cependant, si seule la pointe d'un vecteur se trouve sur l'autre, l'entrée n'est pas valide selon les spécifications.
  • Les segments parallèles non dégénérés retournent toujours faux, quelle que soit la colinéarité.
  • Si l'un des segments est dégénéré, retournez toujours false. Si les deux segments sont dégénérés, retournez toujours vrai.

Exemples: (Notez la mise en garde 1 en action sur la figure de droite)


2≡2⌷x: Segment-autre

Dans ce cas, l'autre objet est circulaire. Vérifiez si les points d'extrémité du segment se trouvent dans le cercle à l'aide de la vérification de distance.

Dans le cas du disque, construisez également un segment de ligne de diamètre perpendiculaire au segment donné. Vérifiez si les segments entrent en collision par récursivité.
Dans le cas de la cavité, se faufiler dans un "temps 0" dans la construction dudit segment pour le faire dégénérer. (Voir pourquoi j'utilise 0pour la cavité et 1pour le disque maintenant?) Comme le segment donné n'est pas dégénéré, la détection de collision segment-segment renvoie toujours false.

Enfin, combinez les résultats des contrôles de distance et de la détection de collision. Pour le cas de la cavité, annulez d'abord les résultats des contrôles de distance. Ensuite (dans les deux cas) OU les 3 résultats ensemble.

Concernant les mises en garde segment-segment, le numéro 3 est adressé (et exploité). Le numéro 2 n'est pas un problème car nous croisons ici des segments perpendiculaires, qui ne sont jamais parallèles s'ils ne sont pas dégénérés. Le numéro 1 ne prend effet que dans le boîtier du disque, lorsque l'un des points d'extrémité donnés se trouve sur le diamètre construit. Si le point final est bien à l'intérieur du cercle, les vérifications de distance auraient pris soin de lui. Si le point final est sur le cercle, puisque le diamètre construit est parallèle au segment donné, ce dernier doit être tangent au cercle avec un seul point touchant le disque, ce qui n'est pas une entrée valide.

Exemples:


Cas par défaut: Autre-autre

Calculez la distance entre les centres. La collision disque-disque se produit si et seulement si la distance est inférieure à la somme des rayons. La collision disque-cavité se produit si et seulement si la distance est supérieure à la différence de rayons.

Pour prendre soin du cas cavité-cavité, annulez le résultat du contrôle de distance, ET avec chacun des entiers d'identification, puis OU ensemble. En utilisant une certaine logique, on peut montrer que ce processus retourne vrai si et seulement si les deux entiers identifiants sont faux (cas Cavité-cavité), ou si le contrôle de distance est retourné vrai

TwiNight
la source
Ma compréhension est que si votre programme est écrit en utilisant des caractères qui s'étendent sur Unicode plutôt que ASCII, le nombre d'octets doit reconnaître la nature de 2 octets par caractère d'Unicode.
COTO
@COTO Je n'ai pas spécifié Unicode. Pour autant que je sache, le jeu de caractères APL tient dans un octet, et il y a des pages de codes à un octet qui contiennent tous les caractères APL, donc en utilisant cet encodage, le nombre d'octets est correct. Le comptage par octets est principalement pertinent pour les personnes qui encodent des choses dans des chaînes multi-octets dans des langues "normales", ou qui utilisent les raccourcis Unicode de Mathematica.
Martin Ender
@ MartinBüttner: Vous dites donc que même si personne ne peut raisonnablement ou pratiquement représenter la version 1 octet par caractère de la chaîne dans n'importe quel éditeur de texte en plus d'une version explicitement conçue pour APL, cela compte comme 1 octet par caractère parce qu'il y a 256 ou moins de caractères autorisés dans la spécification de langue?
COTO
@COTO Eh bien, et parce qu'il existe des codages selon lesquels un fichier codé sur un octet pourrait être interprété. Je ne pense pas que je serais prêt à emprunter cette voie si les gens devaient inventer leur encodage. Sinon, tout programme qui utilise moins de 257 caractères distincts pourrait prétendre cela (ce qui serait presque n'importe quelle réponse sur PPCG, je pense). Je pense juste que nous ne devrions pas pénaliser APL pour avoir précédé Unicoding de plusieurs décennies - à l'époque, il était logique d'interpréter les octets que vous aviez comme des personnages funky étranges qui fonctionnent comme des mnémoniques.
Martin Ender
1
@COTO Il y a J, qui est basé sur APL et utilise uniquement des caractères ASCII. Ils marquent généralement de la même manière, ce qui vous battrait probablement aussi, même s'il était marqué par Unicode. Et je dois ajouter qu'aucune des deux langues n'a été conçue pour le golf, et AFAIK sont tous les deux utilisés de manière professionnelle. Et les défis de golf ici ne consistent pas tant à obtenir la coche verte, mais plutôt à extraire chaque dernier octet de votre programme avec les moyens de votre langue et à battre tout le monde dans la même "catégorie de poids", ce qui vous rapportera généralement plus de votes positifs. que d'utiliser un langage laconique de toute façon. ;)
Martin Ender
5

Javascript - 393 octets

Minifié:

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

Étendu:

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

Remarques:

  • définit la fonction Fqui accepte les arguments requis et renvoie la valeur requise
  • le format d'entrée est identique au format de l'OP, à l'exception du fait que le code de type entier pour chaque primitive est distinct du tuple. Par exemple, F( 0,[[0,0],[2,2]], 0,[[1,0],[2,4]] )ou F( 1,[[3,0],1], 2,[[0,0],1] ).
  • code validé sur tous les cas de test fournis dans l'OP
  • devrait gérer tous les cas de bord et d'angle, y compris les segments de ligne de longueur nulle et les cercles de rayon nul
COTO
la source
Ah, merci d'avoir mentionné ces deux cas de bord. Les cercles à rayon zéro ne doivent pas être traités (le message indique un rayon positif), et je préciserai également que les extrémités du segment de ligne seront distinctes.
Martin Ender
4

Python, 284

J'utilise un algorithme assez ordonné par rapport à toutes ces astuces géométriques, mais il obtient les bonnes réponses même s'il faut plus d'une minute pour parcourir les cas de test. Le gros avantage est que je n'ai qu'à écrire les trois fonctions de notation, et l'escalade s'occupe de tous les cas de bord.

Golfé:

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

Non golfé:

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

Et enfin, un script de test au cas où quelqu'un d'autre voudrait essayer cela en 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)
QuadmasterXLII
la source