Manuals
Manuals




This translation is community contributed and may not be up to date. We only maintain the English version of the documentation. Read this manual in English

Rozwiązywanie kolizji kinematycznych

Korzystanie z kinematycznych obiektów kolizji (kinematic collision objects) wymaga się od Ciebie ręcznego rozwiązywania kolizji i przemieszczania obiektów jako reakcji na nie. Proste podejście do rozdzielenia dwóch kolidujących obiektów wygląda następująco:

function on_message(self, message_id, message, sender)
  -- Handle collision
  if message_id == hash("contact_point_response") then
    local newpos = go.get_position() + message.normal * message.distance
    go.set_position(newpos)
  end
end

Ten kod rozdzieli Twój obiekt kinematyczny od innych obiektów fizycznych, które penetruje, ale rozdzielenie często jest zbyt duże, co prowadzi do drgań w wielu przypadkach. Aby lepiej zrozumieć problem, rozważmy następujący przypadek, w którym postać gracza koliduje z dwoma obiektami, A i B:

Kolzja fizyczna

Silnik fizyki wyśle wiele wiadomości "contact_point_response", jedną dla obiektu A i jedną dla obiektu B w klatce, w której wystąpiła kolizja. Jeśli przesuniesz postać w odpowiedzi na każde przeniknięcie, jak w prostym kodzie powyżej, to rozdzielenie będzie się prezentować następująco:

  • Przesuń postać poza obiekt A zgodnie z jego odległością penetracji (czarna strzałka).
  • Przesuń postać poza obiekt B zgodnie z jego odległością penetracji (czarna strzałka).

Kolejność tych działań jest dowolna, ale wynik jest taki sam w obu przypadkach: całkowite rozdzielenie, które stanowi sumę wektorów penetracji poszczególnych obiektów:

Proste rozdzielenie

Aby właściwie rozdzielić postać od obiektów A i B, musisz obsłużyć odległość penetracji punktów kontaktowych i sprawdzić, czy jakiekolwiek wcześniejsze rozdzielenia już całkowicie lub częściowo rozwiązały problem rozdzielenia.

Załóżmy, że pierwsza wiadomość punktu kontaktowego pochodzi od obiektu A i przesuwasz postać poza obiekt A zgodnie z wektorem penetracji A:

Rozdzielenie krok 1

Wtedy postać zostaje już częściowo rozdzielona od obiektu B. Ostateczna korekta konieczna do pełnego rozdzielenia od obiektu B jest wskazana czarną strzałką powyżej. Długość wektora kompensacji (compensation vector) można obliczyć, projektując wektor penetracji A na wektor penetracji B:

Projekcja

l = vmath.project(A, B) * vmath.length(B)

Wektor kompensacji można znaleźć, zmniejszając długość B o l. Aby obliczyć to dla dowolnej liczby penetracji, można akumulować niezbędną korektę w wektorze, który zaczyna się od wektora kompensacji o zerowej długości:

  1. Zweryfikuj projekcję bieżącej korekcji na wektor penetracji kontaktu.
  2. Oblicz, ile kompensacji pozostało z wektora penetracji (zgodnie z powyższym wzorem).
  3. Przesuń obiekt o wektor kompensacji.
  4. Dodaj kompensację do akumulowanej korekty.

Pełna implementacja wygląda tak:

function init(self)
  -- wektor korekcji (kompensacji)
  self.correction = vmath.vector3()
end

function update(self, dt)
  -- zresetuj wektor korekcji
  self.correction = vmath.vector3()
end

function on_message(self, message_id, message, sender)
  -- Obsługa kolizji
  if message_id == hash("contact_point_response") then
    -- Pobierz informacje potrzebne do rozdzielenia kolizji. Możemy
    -- otrzymać wiele punktów kontaktowych i musimy obliczyć,
    -- jak się z nich wycofać, akumulując korekcję
    -- na tę klatkę:
    if message.distance > 0 then
      -- Po pierwsze, zweryfikuj projektowaną korekcję na
      -- wektor penetracji akumulowany
      local proj = vmath.project(self.correction, message.normal * message.distance)
      if proj < 1 then
        -- Weź pod uwagę tylko projekcje, które nie przestrzeliły.
        local comp = (message.distance - message.distance * proj) * message.normal
        -- Dodaj kompensację
        go.set_position(go.get_position() + comp)
        -- Akumuluj korekcję
        self.correction = self.correction + comp
      end
    end
  end
end