Quanti e quali assi utilizzare per la collisione OBB 3D con SAT


29

Ho implementato il SAT in base a:

A pagina 7, nella tabella, fa riferimento ai 15 assi da testare in modo che possiamo trovare una collisione, ma con solo Ax, Ay e Az, sto già ottenendo collisioni.

Perché devo testare tutti gli altri casi? Esiste una situazione in cui solo Ax, Ay e Az non sono sufficienti?

Risposte:


56

Potresti ottenere falsi positivi. Collisioni rilevate ma non realmente in collisione.

Viene il numero 15

  • 3 assi dall'oggetto A (facce normali)
  • 3 assi dall'oggetto B (faccia normali)
  • 9 assi da tutte le coppie di bordi di A e bordi di B (3x3)
  • = 15 in totale

I 9 assi sono costituiti da prodotti incrociati dei bordi di A e dei bordi di B

  1. Ae1 x Be1 (bordo 1 di A bordo trasversale 1 di B)
  2. Ae1 x Be2
  3. Ae1 x Be3
  4. Ae2 x Be1
  5. ... e così via

I primi 6 assi (dalle normali alla faccia) sono usati per verificare se lì un angolo di un oggetto interseca una faccia dell'altro oggetto. (o più correttamente per eliminare questo tipo di collisioni)

L'insieme di 9 assi formato dai prodotti trasversali dei bordi viene utilizzato per considerare il rilevamento delle collisioni da bordo a bordo, dove non vi è un vertice che penetri nell'altro oggetto. Come la "quasi" collisione nella foto qui sotto. Supponiamo per il resto di questa risposta che le due caselle nella foto non si stanno effettivamente scontrando, ma sono separate da una minuscola distanza.

inserisci qui la descrizione dell'immagine

Vediamo cosa succede se usiamo solo le 6 normali del viso per SAT. La prima immagine in basso mostra un asse dalla casella blu e 2 assi dalla casella gialla. Se proiettiamo entrambi gli oggetti su questi assi, avremo una sovrapposizione su tutti e tre. La seconda immagine in basso mostra i due assi rimanenti della casella blu e l'asse rimanente della casella gialla. Ancora una volta la proiezione su questi assi mostrerà sovrapposizioni su tutti e 3.

Quindi, solo il controllo delle 6 facce normali mostrerà sovrapposizioni su tutti e 6 gli assi, il che, secondo il SAT, significa che gli oggetti si scontrano, perché non siamo stati in grado di trovare una separazione. Ma ovviamente, questi oggetti non si scontrano. Il motivo per cui non abbiamo trovato una separazione è perché non abbiamo guardato abbastanza duramente!

inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine

Quindi, come possiamo trovare questo divario? L'immagine seguente mostra un asse su cui la proiezione di entrambi gli oggetti rivelerà una separazione.

inserisci qui la descrizione dell'immagine

Da dove prendiamo questo asse?

Se immagini di far scivolare un pezzo di carta rigida nella fessura, quella carta farà parte del piano di separazione. Se proiettiamo sulla normale di quel piano (freccia nera nella figura sopra), vedremo la separazione. Sappiamo cos'è quel piano perché abbiamo due vettori che giacciono su quel piano) Un vettore è allineato con il bordo del blu e l'altro vettore è allineato con il bordo del giallo e come tutti sappiamo che il normale di un piano è semplicemente il prodotto incrociato di due vettori disteso sull'aereo.

Quindi per gli OOBB dobbiamo controllare ogni combinazione (9 di essi) di prodotti incrociati dei bordi dei due oggetti per assicurarci che non ci manchi alcuna separazione dei bordi.


2
Spiegazione fantastica! E grazie per le foto. Come osserva @Acegikmo, è un po 'confuso quando si dice "9 assi sono costituiti da prodotti incrociati di bordi di A e bordi di B", dal momento che possiamo usare solo le normali, piuttosto che i bordi. Grazie ancora :)

5
@joeRocc Per i cuboidi hai ragione, usa solo le normali, perché le normali e i bordi sono allineati, ma per altre forme (es. tetraedri, altri poliedri) le normali non sono allineate con i bordi.
Ken,

Grazie mille amico! Stavo leggendo questo bel libro intitolato "Sviluppo del motore di fisica del gioco" e ho riscontrato questo problema. Non avevo idea del perché stiamo usando 15 assi. Molte grazie. Ora sono abbastanza fiducioso da vantarmene. ; D
Ankit singh kushwah,

11

Note sulla risposta di Ken :

I 9 assi sono costituiti da prodotti incrociati dei bordi di A e dei bordi di B

È un po 'confuso fare riferimento ai bordi, poiché ci sono 12 bordi rispetto a 6 normali, quando si potrebbero anche usare le tre normali principali per lo stesso output: i bordi sono tutti allineati con le normali, quindi consiglio invece di usarli !

Si noti inoltre che le normali che puntano lungo lo stesso asse, ma in una direzione diversa, vengono ignorate e quindi restano tre assi univoci.

Un'altra cosa che vorrei aggiungere è che puoi ottimizzare questo calcolo uscendo presto se trovi un asse di separazione, prima di calcolare tutti gli assi che vuoi testare. Quindi, no, non è necessario testare tutti gli assi in ogni caso, ma è necessario essere pronti a testarli tutti :)

Ecco un elenco completo di assi da testare, dati due OBB, A e B, dove x, y e z si riferiscono ai vettori di base / tre normali univoci. 0 = asse x, 1 = asse y, 2 = asse z

  1. a0
  2. a1
  3. a2
  4. b0
  5. b1
  6. b2
  7. croce (a0, b0)
  8. croce (a0, b1)
  9. croce (a0, b2)
  10. croce (a1, b0)
  11. croce (a1, b1)
  12. croce (a1, b2)
  13. croce (a2, b0)
  14. croce (a2, b1)
  15. croce (a2, b2)

C'è anche un piccolo avvertimento, che dovresti conoscere.

Il prodotto incrociato ti darà un vettore zero {0,0,0} quando due assi tra gli oggetti puntano nella stessa direzione.

Inoltre, poiché questa parte è stata lasciata fuori, ecco la mia implementazione per verificare se la proiezione si sovrappone o meno. Probabilmente c'è un modo migliore, ma questo ha funzionato per me! (Utilizzando Unity e la sua API C #)

// aCorn and bCorn are arrays containing all corners (vertices) of the two OBBs
private static bool IntersectsWhenProjected( Vector3[] aCorn, Vector3[] bCorn, Vector3 axis ) {

    // Handles the cross product = {0,0,0} case
    if( axis == Vector3.zero ) 
        return true;

    float aMin = float.MaxValue;
    float aMax = float.MinValue;
    float bMin = float.MaxValue;
    float bMax = float.MinValue;

    // Define two intervals, a and b. Calculate their min and max values
    for( int i = 0; i < 8; i++ ) {
        float aDist = Vector3.Dot( aCorn[i], axis );
        aMin = ( aDist < aMin ) ? aDist : aMin;
        aMax = ( aDist > aMax ) ? aDist : aMax;
        float bDist = Vector3.Dot( bCorn[i], axis );
        bMin = ( bDist < bMin ) ? bDist : bMin;
        bMax = ( bDist > bMax ) ? bDist : bMax;
    }

    // One-dimensional intersection test between a and b
    float longSpan = Mathf.Max( aMax, bMax ) - Mathf.Min( aMin, bMin );
    float sumSpan = aMax - aMin + bMax - bMin;
    return longSpan < sumSpan; // Change this to <= if you want the case were they are touching but not overlapping, to count as an intersection
}

1
Benvenuti nel sito. Controlla il centro assistenza e in particolare notare che questo sito non è un forum e che "rispondere" ad altre risposte non è una buona idea (perché la tua "risposta" potrebbe non apparire prima del post a cui stai rispondendo). È meglio scrivere le risposte in modo indipendente e utilizzare i commenti se si desidera chiarire in modo specifico un post esistente.
Josh,

Grazie per il chiarimento Acegikmo! Ero un po 'confuso anche dal riferimento ai bordi. @Josh Petrie dovresti mettere le emoticon alla fine dei tuoi commenti, così i neofiti sanno che non li

vedi il mio commento sopra re edge vs normals
Ken

2

esempio c # funzionante basato sulla risposta di Acegikmo (usando alcune API di unità):

using UnityEngine;

public class ObbTest : MonoBehaviour
{
 public Transform A;
 public Transform B;

 void Start()
 {
      Debug.Log(Intersects(ToObb(A), ToObb(B)));
 }

 static Obb ToObb(Transform t)
 {
      return new Obb(t.position, t.localScale, t.rotation);
 }

 class Obb
 {
      public readonly Vector3[] Vertices;
      public readonly Vector3 Right;
      public readonly Vector3 Up;
      public readonly Vector3 Forward;

      public Obb(Vector3 center, Vector3 size, Quaternion rotation)
      {
           var max = size / 2;
           var min = -max;

           Vertices = new[]
           {
                center + rotation * min,
                center + rotation * new Vector3(max.x, min.y, min.z),
                center + rotation * new Vector3(min.x, max.y, min.z),
                center + rotation * new Vector3(max.x, max.y, min.z),
                center + rotation * new Vector3(min.x, min.y, max.z),
                center + rotation * new Vector3(max.x, min.y, max.z),
                center + rotation * new Vector3(min.x, max.y, max.z),
                center + rotation * max,
           };

           Right = rotation * Vector3.right;
           Up = rotation * Vector3.up;
           Forward = rotation * Vector3.forward;
      }
 }

 static bool Intersects(Obb a, Obb b)
 {
      if (Separated(a.Vertices, b.Vertices, a.Right))
           return false;
      if (Separated(a.Vertices, b.Vertices, a.Up))
           return false;
      if (Separated(a.Vertices, b.Vertices, a.Forward))
           return false;

      if (Separated(a.Vertices, b.Vertices, b.Right))
           return false;
      if (Separated(a.Vertices, b.Vertices, b.Up))
           return false;
      if (Separated(a.Vertices, b.Vertices, b.Forward))
           return false;

      if (Separated(a.Vertices, b.Vertices, Vector3.Cross(a.Right, b.Right)))
           return false;
      if (Separated(a.Vertices, b.Vertices, Vector3.Cross(a.Right, b.Up)))
           return false;
      if (Separated(a.Vertices, b.Vertices, Vector3.Cross(a.Right, b.Forward)))
           return false;

      if (Separated(a.Vertices, b.Vertices, Vector3.Cross(a.Up, b.Right)))
           return false;
      if (Separated(a.Vertices, b.Vertices, Vector3.Cross(a.Up, b.Up)))
           return false;
      if (Separated(a.Vertices, b.Vertices, Vector3.Cross(a.Up, b.Forward)))
           return false;

      if (Separated(a.Vertices, b.Vertices, Vector3.Cross(a.Forward, b.Right)))
           return false;
      if (Separated(a.Vertices, b.Vertices, Vector3.Cross(a.Forward, b.Up)))
           return false;
      if (Separated(a.Vertices, b.Vertices, Vector3.Cross(a.Forward, b.Forward)))
           return false;

      return true;
 }

 static bool Separated(Vector3[] vertsA, Vector3[] vertsB, Vector3 axis)
 {
      // Handles the cross product = {0,0,0} case
      if (axis == Vector3.zero)
           return false;

      var aMin = float.MaxValue;
      var aMax = float.MinValue;
      var bMin = float.MaxValue;
      var bMax = float.MinValue;

      // Define two intervals, a and b. Calculate their min and max values
      for (var i = 0; i < 8; i++)
      {
           var aDist = Vector3.Dot(vertsA[i], axis);
           aMin = aDist < aMin ? aDist : aMin;
           aMax = aDist > aMax ? aDist : aMax;
           var bDist = Vector3.Dot(vertsB[i], axis);
           bMin = bDist < bMin ? bDist : bMin;
           bMax = bDist > bMax ? bDist : bMax;
      }

      // One-dimensional intersection test between a and b
      var longSpan = Mathf.Max(aMax, bMax) - Mathf.Min(aMin, bMin);
      var sumSpan = aMax - aMin + bMax - bMin;
      return longSpan >= sumSpan; // > to treat touching as intersection
 }
}
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.