Zapomnij o sprężynach – użyj ograniczeń
Oczywiście, można symulować linę jako łańcuch mas połączonych sztywnymi sprężynami: siła sprężystości zmusza sąsiednie punkty do powrotu wzdół od odległości spoczynnej. W praktyce to kiepska metoda. Aby sprężyna wydawała się nieciągnieciająca się liną, musiałaby mieć bardzo dużą sztywność, co z kolei wymagałoby bardzo małego kroku czasowego, aby pozostać numerycznie stabilna, a dowolny rozsądny budżet ramowy sprawiłby, że lina byłaby widocznie podskakująca lub eksplodowałaby całkowicie. Alternatywa, którą wykorzystują wszystkie nowoczesne silniki, polega na traktowaniu połączeń jako twardych geometrii ograniczeń – "te dwa punkty muszą być dokładnie d apart" i bezpośrednio egzekwuje się to na pozycji, bez jakiejkolwiek siły lub parametru sztywności.
Integracja Verlet: pozycja pamięta o prędkości za darmo
Każdy węzeł liny jest przechowywany jako tylko aktualna pozycja i poprzednia pozycja, bez zmiennej prędkości. Integracja Verlet z pozycji (Position Verlet) porusza punkt za pomocą tych dwóch pozycji oraz przyspieszenia, a implikowana prędkość to po prostu ich różnica podzielona przez krok czasowy. To właściwość sprawia, że solver ograniczeń jest prosty: aby spełnić ograniczenie dystansu, wystarczy przesunąć aktualną pozycję - "prędkość" automatycznie się aktualizuje w następnym kroku, bez konieczności prowadzenia oddzielnej ewidencji.
// per node, per frame newPos = pos + (pos - oldPos) * damping + acceleration * dt * dt oldPos = pos pos = newPos
Rozwiazanie Jakobsona: zaspokoj kazdy element, raz po raz
Metoda Thomasa Jakobsona z 2001 roku (uzywana w oryginalnym silniku Hitmana) rozwiązuje caly szereg ograniczeń z brutalną prostotą: iteruj przez kazdy element i dla kazdego z nich przesuwaj bezpośrednio połączone punkty do przodu lub do tyłu, aż znajdą się dokładnie na odleglosci d od siebie, dzieląc korektę pomiędzy nie proporcjonalnie do ich odwrotnej masy. Robi sie to dla kazdego elementu, a następnie powtarza caly przebieg kilka razy na klatke. Kazda iteracja tylko częściowo rozplata skomplikowany zbiór nakładajacych się ograniczeń, ale kilkadziesiat powtoreń wystarcza, aby szybko uzyskać wrazenie całkowitej sztywnosci w czasie rzeczywistym.
dla iteracji od 1 do N: // np. N = 8-15 iteracji na klatke dla kazdego elementu (a, b, restLen): delta = b.pozycja - a.pozycja odleglosc = dlugosc(delta) roznica = (odleglosc - restLen) / odleglosc a.pozycja += delta * 0.5 * roznica * a.odwrotnaMasa / (a.odwrotnaMasa + b.odwrotnaMasa) b.pozycja -= delta * 0.5 * roznica * b.odwrotnaMasa / (a.odwrotnaMasa + b.odwrotnaMasa) Zablokowanie jednego konca lina jest trivialne w tym frameworku: przypisz temu punktowi odwrotną masę równą zero, a kazda korekta, która by go przesunęła, zostanie całkowicie przekierowana na sąsiadującego element. Ta sama sztuczka zablokowuje linę przy ruchomej rce, owija ją wokół trzciny poprzez dodanie dodatkowych ograniczeń przeciwko powierzchni rki, i zamienia ten sam kod w tkaninę po prostu przez dodanie siatki 2D elementów zamiast pojedynczej liny.
for iteration in 1..N: // e.g. N = 8-15 passes per frame
for each link (a, b, restLen):
delta = b.pos - a.pos
dist = length(delta)
diff = (dist - restLen) / dist
a.pos += delta * 0.5 * diff * a.invMass / (a.invMass + b.invMass)
b.pos -= delta * 0.5 * diff * b.invMass / (a.invMass + b.invMass)
Katenoid: jak wygląda lina w spoczynku
Lewa strona wie suspendowana między dwoma ustalonymi punktami, prawdziwa lina lub łańcuch osadza się w określonej krzywej zwanej katenoidem (z łac. 'łańcuch'): y = a·cosh(x/a), gdzie a jest ustawiany przez wagę liny na jednostkę długości i poziom naprężenia poziomego. Wygląda jak parabola, ale nie jest nią – różnica polega na tym, że ciężar łańcucha rozkłada się na jednostkę własnej długości łuku, podczas gdy parabola jest prawidłową formą tylko wtedy, gdy obciążenie rozkładane jest na jednostkę osi poziomej, jak w przypadku płyty mostu zawieszonego opierającej się na kablach pionowych. Dobrze dostrojona symulacja liny naturalnie generuje kształt katenoidu, po prostu z powodu działania grawitacji na cząstki plus ograniczenia dystansów – nie trzeba ręcznie kodować wzoru cosh.
Frequently asked questions
Dlaczego symulowana lina rozciąga się zamiast pozostać sztywna?
Wynika to z tego, że ograniczenia odległości są rozwiązywane przy ograniczonym liczbie iteracji relaksacyjnych, a nie dokładnie. Każda iteracja tylko częściowo koryguje błąd między dwoma powiązanymi punktami, więc przy zbyt małej liczbie iteracji lina zachowuje się jak sztywna sprężyna zamiast nieodkształcalnej linki. Zwiększenie liczby iteracji (lub stosowanie tej samej iteracji wielokrotnie na klatkę) sprawia, że zbliża się ona do prawdziwej sztywności, kosztem większego obciążenia obliczeniowego.
Czy wiszący łańcuch ma tę samą formę co parabola?
Nie, chociaż wyglądają podobnie i łatwo je pomieszać. Łańcuch wiszący pod własnym ciężarem ustala się w kształt katenary, y = a·cosh(x/a), ponieważ naprężenie w każdym punkcie musi zrównoważyć obciążenie, które samo zależy od długości łuku. Parabola jest prawidłowym kształtem dla kabla mostu wiszącego, ponieważ obciążenie (konstrukcja) jest rozłożone równomiernie na jednostkę długości poziomej zamiast jednostki długości liny.
Dlaczego używa się integracji Verlet zamiast śledzenia prędkości bezpośrednio?
Przechowywanie tylko bieżącej i poprzedniej pozycji (z implikowanym tempem jako ich różnicą) sprawia, że egzekwowanie ograniczeń jest proste: wystarczy przesunąć punkt, aby zaspokoić to ograniczenie, a implikowane tempo automatycznie aktualizuje się w następnym kroku. W przypadku zmiennej prędkości jawnej każda korekta ograniczenia musiałaby również oddzielnie naprawić prędkość, co jest dokładnie dodatkowym księgowaniem, którego są zaprojektowane do unikania solvery oparte na Verlet.
Wypróbuj na żywo
Wszystko powyżej działa bezpośrednio w Twojej przeglądarce — otwórz Rope and Chain Physics i zmieniaj parametry podczas działania. Nic nie jest instalowane ani przesyłane na serwer, cały model działa w jednej karcie.
▶ Otwórz symulację Rope and Chain Physics