Moduł 04 · Podstawy

Wykrywanie kolizji

GJK, EPA, MPR, SAT, BVH/AABB/OBB/k-DOP, CCD, distance queries i gradient SDF, GPU collision, self-collision Pandy.

TL;DR

Każde zapytanie „czy konfiguracja qq jest bezpieczna?" w końcu sprowadza się do testowania kolizji między kawałkami geometrii (linki robota ↔ przeszkody ↔ inne linki). Ten moduł kataloguje algorytmy:

  • Narrow-phase (kolizja para-na-parę): GJK, EPA, MPR, SAT.
  • Broad-phase (które pary w ogóle sprawdzać): BVH (AABB-tree, OBB-tree, k-DOP), sweep-and-prune, spatial hashing.
  • CCD (Continuous Collision Detection): wykrywanie kolizji między dyskretnymi klatkami symulacji.
  • Distance queries + gradient SDF — dla CHOMP / CBF / fabrics, gdzie potrzeba „odpychającej" siły.
  • Self-collision — szczególny przypadek dla manipulatora; reduktor: Allowed Collision Matrix.

Reguła kciuka dla planowania manipulatorów: ~80% czasu sampling-based planera (RRT*, BIT*) spędza w collision checking. Optymalizacja tej warstwy jest kluczowa dla performance.

GJK — Gilbert-Johnson-Keerthi

Idea: dwa wypukłe zbiory A,BA, B kolidują wtedy i tylko wtedy, gdy ich różnica Minkowskiego AB={ab:aA,bB}A \ominus B = \{a - b : a \in A, b \in B\} zawiera origin:

AB    0ABA \cap B \neq \emptyset \iff 0 \in A \ominus B

GJK nie buduje ABA \ominus B jawnie (drogie). Zamiast tego iteracyjnie konstruuje simplex (1-, 2- lub 3-punktowy w 2D; do 4 w 3D) wewnątrz ABA \ominus B, próbując „objąć" origin. W każdej iteracji dodaje support point s(d)s(d) w aktualnym kierunku dd ku origin.

Kluczowy lemmat: jeśli istnieje plane separujący AA i BB (czyli AB=A \cap B = \emptyset), to w pewnej iteracji wystąpi:

s,d<0    NO COLLISION\langle s, d \rangle < 0 \implies \text{NO COLLISION}

GJK zwraca odpowiedź w średnio 4-8 iteracjach dla 3D wielokątów. Złożoność O(m+n)O(m + n) per iteracja gdzie m,nm, n = liczby wierzchołków A,BA, B.

GJK — krok 1 / 3

⟳ iterating
Original space: wielokąty A, B
AB
Minkowski space: A ⊖ B + simplex
0new
krok:Init: dowolny kierunek startowy (1, 0), pierwszy support point dodany do simplex'u.
ABA ⊖ B (Minkowski diff)simplexoriginkierunek d

Pokrewne i rozszerzenia

  • EPA (Expanding Polytope Algorithm) — gdy GJK wykryje kolizję, EPA znajduje penetration depth i kierunek separacji (potrzebne dla rozwiązania kontaktu).
  • MPR (Minkowski Portal Refinement) — alternatywa GJK + EPA z prostszym kodem, podobny performance.
  • SAT(Separating Axis Theorem) — dla wielokątów: sprawdza projekcje na osie krawędzi. Szybsze dla małej liczby wierzchołków (boxes), wolniejsze dla wielokątów > 100 vertices.
  • libccd, FCL, Bullet — biblioteki C++ implementujące GJK+EPA z optymalizacjami (caching simplex między klatkami).

BVH — Bounding Volume Hierarchy

GJK testuje jedną parę w ms. Ale w scenie z setkami obiektów (manipulator + półki + obiekty) liczba par toO(n2)O(n^2) — niedopuszczalne. Stąd potrzebny broad-phase: szybkie odrzucanie par, które na pewno nie kolidują.

BVH organizuje obiekt w drzewo bounding volumes (zwykle AABB lub OBB). Korzeń = AABB całego obiektu. Każdy węzeł wewnętrzny ma dwoje dzieci z mniejszymi AABB. Liście = pojedyncze trójkąty.

Test kolizji dwóch obiektów = jednoczesny obchód obu drzew:

function intersect(nodeA, nodeB):
  if not AABB_intersect(nodeA.aabb, nodeB.aabb):
    return false
  if both are leaves:
    return triangle_test(nodeA.tri, nodeB.tri)
  recursively test children

W typowych scenach koszt zapytania spada z O(n1n2)O(n_1 n_2) do O(logn1+logn2)O(\log n_1 + \log n_2) + linear cost na liście.

Bounding Volume Hierarchy — poziom 2 (4 AABB)

poziom BVH2 / 5
Jak czytać: Poziom 0 = korzeń (jedna „obejmująca" AABB całego obiektu). Każdy kolejny poziom rozdziela na pół (median split po dłuższej osi). Końcowe AABB to liście — pojedyncze trójkąty.
Po co: sprawdzając kolizję obiekt-obiekt, najpierw pytamy: czy ich korzenie się przecinają? Jeśli nie — koniec (O(1)). Jeśli tak — rekursywnie schodzimy po dzieciach. Dla dwóch obiektów po n trójkątów dostajemy O(n log n) zamiast O(n²) naiwnego sprawdzania.

Warianty bounding volumes

  • AABB (Axis-Aligned BB) — 6 floats (xMin..zMax). Najtańszy test, ale luźne dla obrotowych obiektów.
  • OBB (Oriented BB) — z 3D rotacją. Ciasne, ale test droższy (15 osi separacji per parze).
  • k-DOP (Discrete Oriented Polytope) — k par normal-aligned half-spaces. Kompromis AABB/OBB; popularne k = 14, 18, 26.
  • Sphere — 4 floats. Mega tani test, bardzo luźny (zwykle ostatni resort).

Convex decomposition: dla nie-wypukłych mesh'y (np. linków Pandy) BVH + GJK nie wystarcza — narzędzia jak V-HACD dekomponują geometrię na sumę wypukłych części, każda z osobnym narrow-phase.

CCD — Continuous Collision Detection

Dyskretny collision check sprawdza kolizję tylko w chwilach tk=kΔtt_k = k \cdot \Delta t. Jeśli obiekt porusza się szybko, między tkt_k a tk+1t_{k+1} może przeskoczyć przez przeszkodę — kolizja niewykryta.

CCD wykrywa time-of-impact t[tk,tk+1]t^* \in [t_k, t_{k+1}] — pierwszą chwilę, w której obiekty stykają się. Najprostsza forma: swept volume — obejmujemy całą trajektorię obiektu między klatkami jednym objętym AABB, testujemy z geometrią statycznej sceny.

W manipulatorze CCD jest kluczowe podczas wykonania zaplanowanej trajektorii — bez niego szybkie ruchy (q̇ ~ 2 rad/s) mogą wjechać w przeszkodę między cyklami sterownika (1 kHz).

CCD vs Discrete — efekt tunelowania

startwall
prędkość pocisku120 px/s
Ustaw prędkość > 500 px/s i odpal bez CCD: pocisk przemieści się o więcej niż szerokość ściany w jednej klatce (dt = 0.06s). Discrete collision check zobaczy go „przed" i „za" ścianą, ale nigdy „w" — kolizja niewykryta (efekt tunelowania). Włącz CCD i obserwuj: time-of-impact wykrywany, pocisk zatrzymuje się dokładnie przy ścianie. Pomarańczowy obszar = sweep aktualnej klatki.

Algorytmy CCD w praktyce

  • Swept-AABB vs static obstacle — najprostsze, wystarcza dla pocisków / mobile robotów.
  • Continuous GJK (Bergen 2003) — sweep'owy GJK z parametryzacją czasem. Złożone, ale dokładne.
  • Conservative advancement — iteracyjnie advance'uj symulację o czas mniejszy niż TOI, aż osiągniesz kontakt z toleracją.
  • FCL implementuje conservative advancement z GJK podstawy.

Distance queries i gradient

Często potrzebujemy więcej niż „jest/brak kolizji" — chcemy odległość do najbliższej przeszkody i kierunek, w którym się oddalać. To podstawa:

  • CHOMP / TrajOpt (moduł 07): gradient SDF wokół przeszkód działa jak siła wypychająca trajektorię.
  • CBF-QP (moduł 12): bezpieczeństwo formalne — ograniczenie Φ(q(t))ϵsafe\Phi(q(t)) \geq \epsilon_{\text{safe}}.
  • Reactive control (moduł 09): pola potencjałowe, fabrics.

GJK rozszerzony o zwracanie najbliższych punktów daje:

d(A,B)=minaA,bBab,d=babad(A, B) = \min_{a \in A, b \in B} \|a - b\|, \quad \nabla d = \frac{b^* - a^*}{\|b^* - a^*\|}

gdzie (a,b)(a^*, b^*) to najbliższe punkty na powierzchni każdego obiektu. Dla SDF (moduł 03) gradient jest natywny i ciągły.

Praktyczna kombinacja w cuRobo / NVIDIA Warp: SDF voxel grid przeszkód (na GPU) + analityczne SDF kapsuł dla ogniw Pandy (sferi + walce) → tysiące jednoczesnych queries dla batched MPC.

GPU collision — krótki przegląd

CPU collision check w pętli rolloutów MPPI (K = 1000 trajektorii × H = 30 kroków = 30 000 queries per iteracja, przy 50 Hz = 1.5M/s) szybko staje się wąskim gardłem. GPU pozwala na batched parallel queries:

  • cuRobo(NVIDIA, 2023) — pełen kinodynamic stack na CUDA. Collision check oparty na sphere SDF dla ogniw + voxel SDF przeszkód. Tysiące rolloutów w < 10 ms.
  • NVIDIA Warp — Python ↔ CUDA framework dla physics. Implementacja BVH + GJK na GPU dostępna w warp.sim.
  • Isaac Sim / Isaac Lab — pełna fizyka GPU dla RL, używa PhysX-based collision.
  • Flex / FleX — granular / soft body GPU collision.

Aplikacja webowa (ten projekt) używa CPU collision — JS nie ma dostępu do CUDA. Dla rzeczywistego deployment na manipulatorze z GPU rekomendacja: cuRobo (open-source, integrowany z ROS2).

Self-collision Pandy

Manipulator 7-DOF (i więcej) może sam się ze sobą zderzyć — przedramię z barkiem, gripper z bazą. Bez self-collision checking planer może zwrócić „idealną" trajektorię, która w praktyce zniszczy robota.

Naiwne podejście: dla wszystkich par ogniw (i,j)(i, j) wykonaj collision check. Dla Pandy: (82)=28\binom{8}{2} = 28 par. Większość jest jednak trywialna:

  • Sąsiednie ogniwa (dzielące staw) zawsze się stykają — bez sensu sprawdzać.
  • Pary fizycznie nieosiągalne (baza ↔ link 1) — ignored.
  • Pary o znikomym prawdopodobieństwie kolizji można też wyłączyć dla performance.

Allowed Collision Matrix (ACM)zapisuje, które pary są aktywnie sprawdzane. MoveIt2 generuje ACM automatycznie przez próbkowanie 10 000 random configs i wyłączenie par, które kolidują > 95% przypadków (zawsze stykające) lub < 0.1% (nigdy). Reszta to faktycznie ryzykowne kombinacje.

Macierz par self-collision (8 ogniw, 27/28 aktywne)

link0link1link2link3link4link5link6link7hand
link0
link1
link2
link3
link4
link5
link6
link7
hand
sprawdzanepominięte (klik włącza)sąsiednie (na stałe ignored)
Dlaczego ACM: Pełna macierz 8×8 = 28 par. Sąsiednie ogniwa (i, i+1) dzielą staw — zawsze stykają się, więc check zawsze zwraca true; bezsensowne sprawdzanie. Pary niemożliwe (np. baza ↔ link bezpośrednio nad nią) też mogą być wyłączone. MoveIt2 generuje ACM automatycznie próbkując 10 000 konfiguracji i wyłączając pary z < 0.1% kolizji lub > 95% (zawsze-stykające).

Ściąga

Hierarchia testów (broad → narrow → continuous)

  1. Broad-phase: BVH / sweep-and-prune — wybierz pary mogące kolidować.
  2. Narrow-phase: GJK / EPA / SAT — dokładny test kolizji + penetration depth.
  3. Continuous: CCD — wykrywa tunneling przy szybkim ruchu.
  4. Distance / SDF: dla planerów z gradientem (CHOMP, TrajOpt, CBF).

GJK w jednym akapicie

AB    0ABA \cap B \neq \emptyset \iff 0 \in A \ominus B. Iteracyjnie buduj simplex (≤3 wierzchołki w 2D, ≤4 w 3D) wewnątrz ABA \ominus B. W każdej iteracji dodaj support point w kierunku dd ku origin. Jeśli s,d<0\langle s, d \rangle < 0 — origin poza ABA \ominus B ⇒ brak kolizji. Jeśli simplex zawiera origin ⇒ kolizja.

ACM dla Pandy

Wyłącz pary sąsiednie i fizycznie nieosiągalne. Z 28 par typowo zostaje 10-15 aktywnych. Generowane automatycznie w MoveIt2 przez sampling configs.

Reguła kciuka

~80% czasu sampling-based planera (RRT*, BIT*) idzie w collision checking. Optymalizacja tej warstwy ma największy wpływ na czas planowania.

Referencje do dalszej lektury

  • Gilbert, Johnson & Keerthi, „A Fast Procedure for Computing the Distance Between Complex Objects in Three-Dimensional Space" (IEEE J. Robotics & Automation, 1988) — oryginalny paper GJK.
  • van den Bergen, Collision Detection in Interactive 3D Environments (Morgan Kaufmann, 2003) — kanoniczny podręcznik (GJK, EPA, CCD, BVH).
  • Pan, Chitta & Manocha, „FCL: A General Purpose Library for Collision and Proximity Queries" (ICRA 2012). FCL GitHub.
  • Coumans, Bullet Physics — implementacja referencyjna GJK + EPA + CCD w C++. pybullet.org.
  • Cohen, Lin, Manocha & Ponamgi, „I-COLLIDE: An Interactive and Exact Collision Detection System for Large-Scale Environments" (SIGGRAPH I3D 1995) — sweep-and-prune.
  • Sundaralingam et al., „cuRobo: Parallelized Collision-Free Robot Motion Generation" (ICRA 2023). curobo.org.
  • MoveIt2 docs — Allowed Collision Matrix generation. collision_checking.