2D виявлення зіткнення


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 Вибачте, але через 9 годин після публікації я повинен припустити, що інші, можливо, почали працювати над викликом, і зміна специфікації (крім виправлення неполадок) не здається мені гарною ідеєю. Залежно від того, як це зробити, паралельні відрізки ліній повинні бути єдиним крайовим випадком, про який потрібно турбуватися при зіткненнях ліній.
Мартін Ендер

Звичайно, я не очікував, що ти зміниш це. Мене було просто трохи засмутити, що обробка різних варіантів сегментів колінеарних ліній подвоїла довжину мого коду до цих пір. :) (До речі, чудовий виклик!)
Еміль

Чи не підпадають колінеарні точки під "не стикається на 10 ^ -10"?
TwiNight

@TwiNight Не якщо два рядки колінеарні, але не перетинаються. Напр.[0,[-2,0],[-1,0]], [0,[1,0],[2,0]]
Мартін Ендер

Відповіді:


6

АПЛ, 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. Я думаю, що останній раз це було, але я навіть не впевнений, що це було так складно.

Формат вводу
В основному такий же, як і ОП, за винятком 0порожнини, 1для диска та 2для лінійного сегмента.

Основне оновлення

Мені вдалося багато разів пограти в гольф, використовуючи інший алгоритм. Більше немає gбиків ** т !!

Основна функція fподіляється на випадки:


2 2≡x: Сегмент-сегмент

У цьому випадку обчисліть вектор від кінцевих точок кожного рядка та вирішіть систему лінійних рівнянь, щоб перевірити, чи міститься перетин всередині векторів.

Застереження:

  • Кінцева точка вектора не розглядається як частина вектора (в той час як його походження). Однак, якщо лише інша частина вектора знаходиться на іншому, вхід недійсний відповідно до специфікації.
  • Невироджені паралельні сегменти завжди повертаються помилковими, незалежно від колінеарності.
  • Якщо один із сегментів вироджений, завжди повертайте помилковий. Якщо обидва сегменти вироджені, завжди повертайте істину.

Приклади: (Зверніть увагу на застереження 1 на малюнку справа)


2≡2⌷x: Сегмент-інше

У цьому випадку інший об’єкт - круговий. Перевірте, чи кінцеві точки відрізка знаходяться в колі за допомогою перевірки відстані.

У випадку диска також побудуйте відрізок лінії діаметром, перпендикулярним даному відрізку. Перевірте, чи стикаються сегменти шляхом рекурсії.
У випадку з порожниною підкрадайтеся до "разів 0" у конструкції зазначеного сегмента, щоб він вироджувався. (Дивіться, чому я зараз використовую 0для порожнини та 1для диска?) Оскільки даний сегмент не є виродженим, виявлення зіткнення сегментів-сегментів завжди повертається помилково.

Нарешті поєднайте результати перевірки відстані та виявлення зіткнення. У випадку з порожниною спочатку заперечуйте результати перевірок на відстані. Тоді (в обох випадках) АБО 3 результати разом.

Щодо застережень на сегменти сегменту, цифри 3 розглядаються (і використовуються). Число 2 не є проблемою, оскільки ми перетинаємо тут перпендикулярні відрізки, які ніколи не є паралельними, якщо не вироджуються. Число 1 набуває чинності лише у випадку диска, коли одна із заданих кінцевих точок знаходиться на побудованому діаметрі. Якщо кінцева точка знаходиться всередині кола, перевірка відстані переконалася б про це. Якщо кінцева точка знаходиться на колі, оскільки побудований діаметр паралельний заданому відрізку, останній повинен бути дотичним до кола, лише одна точка торкається диска, що не є дійсним введенням.

Приклади:


Випадок за замовчуванням: Інше-інше

Обчисліть відстань між центрами. Зіткнення дискових дисків відбувається лише тоді, коли відстань менша за суму радіусів. Зіткнення дискової порожнини відбувається лише тоді, коли відстань більша за різницю радіусів.

Щоб подбати про випадок порожнини-порожнини, заперечуйте результат перевірки відстані І з кожним із ідентифікуючих цілих чисел, а потім АБО їх разом. Використовуючи деяку логіку, можна показати, що цей процес повертає істину, якщо і лише якщо обидва ідентифікуючих цілих числа є хибними (випадок порожнини порожнини) або якщо перевірка відстані повертається істиною


Я розумію, що якщо ваша програма написана з використанням символів, що охоплюють Unicode, а не ASCII, кількість байтів повинна визнати природу Unicode 2 байти на символ.
COTO

@COTO Я не вказав Unicode. Наскільки мені відомо, набір символів APL дійсно вписується в байт, і є однобайтові кодові сторінки, які містять усі символи APL, тому, використовуючи це кодування, кількість байтів добре. Підрахунок у байтах здебільшого актуальний для людей, які кодують матеріал у багатобайтові рядки "звичайними" мовами або користуються ярликами Unicode Mathematica.
Мартін Ендер

@ MartinBüttner: Отже, ви говорите, що хоча ніхто не може розумно або практично представляти версію рядка на 1 байт-за-char в текстовому редакторі, окрім явно розробленої для APL, вона вважається 1-байтовою на-char тому що в мовній специфікації є 256 або менше дозволених символів?
COTO

@COTO Ну і тому, що існують кодування , згідно з якими однобайтовий кодований файл може бути інтерпретований. Я не думаю, що я хотів би піти по цьому маршруту, якби людям довелося зробити кодування. Інакше будь-яка програма, яка використовує менше 257 різних символів, може стверджувати, що (це, на мій погляд, майже будь-яка відповідь на PPCG). Я просто думаю, що ми не повинні штрафувати APL за те, що випереджає Unicoding декількома десятиліттями - тоді було доцільно тлумачити байти, які ви мали, як дивні прикольні персонажі, які працюють як мнемоніка.
Мартін Ендер

1
@COTO Там є J, який заснований на APL і використовує лише символи ASCII. Зазвичай вони забивають аналогічно, так що, ймовірно, ви биєте і вас, навіть якщо забиває Unicode. І слід додати, що жодна мова не була розроблена для гольфу, і обидва AFAIK фактично використовуються професійно. І проблеми з гольфу тут полягають не в тому, щоб отримати зелену галочку, а більше про те, щоб видалити кожен останній байт із вашої програми засобами вашої мови та побоїти всіх у тій же «ваговій категорії», що, як правило, заробляє більше грошей ніж використовувати лаконічну мову. ;)
Мартін Ендер

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яка приймає необхідні аргументи і повертає необхідне значення
  • Формат вводу ідентичний формату в ОП, за винятком того, що код цілого типу для кожного примітиву є окремим від кортежу. Наприклад, F( 0,[[0,0],[2,2]], 0,[[1,0],[2,4]] )або F( 1,[[3,0],1], 2,[[0,0],1] ).
  • код підтверджений у всіх тестових випадках, що поставляються в ОП
  • має обробляти всі крайові та кутові корпуси, включаючи відрізки ліній нульової довжини та кола з нульовим радіусом

Ах, дякую, що згадали про ці два крайні корпуси. Круги з нульовим радіусом не потрібно обробляти (пост повідомляє про позитивний радіус), і я також уточню, що кінці відрізка лінії будуть чіткими.
Мартін Ендер

4

Пітона, 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)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.