Kinematyka odwrotna na przykładzie węża

2

Cześć,

przeklejam swój wątek https://forum.lazarus.freepascal.org/index.php/topic,45282.0.html z forum Lazarusa. Może się przydać piszącym gry, chociaż napisałem to w celu testowym do obliczania kąta dla poszczególnych segmentów. Można ten projekt nawet potraktować jako wyjściowy etap do prostej gierki. Co kto lubi...

0

Przydatna rzecz – ma wiele zastosowań.

Jak Ci się nudzi to możesz dla testu spróbować zaimplementować proste drzewo składające się z podobnych segmentów i dyndające na wietrze. Prosty mechanizm, nic skomplikowanego. :]

1

Załącznik przesłany

No też mi to drzewo przyszło na myśl, muszę czekać na kolejne natchnienie ;)

1

No dobra, przyjrzałem się bliżej kodowi tego projektu (bom był ciekaw jak to działa) i mam kilka(naście) uwag. :]


Pierwsza rzecz – kinematyka odwrotna po angielsku to inverse kinematics, a nie invert kinematiks.

Kod piszesz bardzo niedbale – nie stosujesz jednolitych wcięć, nie korzystasz z przyjętej konwencji nazewnictwa, chętnie stosujesz jednoliterowe, a tym samym nic nie mówiące o swoim przeznaczeniu identyfikatory, nie robisz odstępów pomiędzy operatorami, warunki (nawet złożone) piszesz w jednej linijce, co sprawia, że są one kompletnie nieczytelne. Do tego pozostawiasz w kodzie nieużywane zmienne i moduły, a także zakomentowane fragmenty kodu, które nie wiadomo dlaczego są zakomentowane (czy są one pozostałością po testach, czy może alternatywnym rozwiązaniem danego problemu).

Poza tym jeśli już publikujesz źródła projektu, to nie dołączaj do nich plików binarnych czy backupów źródeł, a także nie pozostawiaj w ustawieniach projektu hardkodowanych ścieżek do jakichś bibliotek (w dodatku nieużywanych w projekcie).

Trochę niedobrze, że projekt w takiej postaci publikujesz na forach. Brzydki i niedbały kod źle świadczy o jego autorze – nawet jeśli działa prawidłowo, przedstawia to co ma przedstawiać i nie generuje wycieków pamięci. Nieważne, że to tylko test – skoro testowe aplikacje piszesz byle jak to znaczy, że brak Ci nawyku pisania ładnego i czytelnego kodu, więc najprawdopodobniej właściwy projekt też będzie posiadał brzydki kod. :/


Jeśli chodzi o część merytoryczną, to też jest sporo do poprawienia. Po pierwsze, do przechowywania płaskiej listy jakichś elementów używaj list (np. generycznych), bo o wiele łatwiej się ich używa – łatwiej się wypełnia danymi i łatwo się te dane później modyfikuje, dzięki czemu kod będzie krótki i czytelny. Ty używasz zwykłej macierzy z zarezerwowaną ostatnią komórką, która jest pomijana podczas renderowania węża. To samo można uzyskać korzystając z list.

Druga sprawa to nieprawidłowe renderowanie węża. Nie chodzi o to, że wygląd węża nie jest zgodny z danymi o jego segmentach, a o to, że przed jego namalowaniem zamalowujesz całe płótno okna danym kolorem, a następnie renderujesz węża. Nie jest to jakiś bardzo głupi pomysł, ale ma jedną wadę – podczas przesuwania węża ekran miga. Im szybciej przesywamy węża (a tym samym częściej odmalowywane jest płótno okna), tym bardziej owe miganie jest widoczne.

Problem z miganiem ekranu jest łatwy do rozwiązania – w ogóle nie wypełniaj tła okna. Jeśli włączone jest podwójne buforowanie, to za czyszczenie płótna odpowiadają wewnętrzne mechanizmy. W takim wypadku wystarczy tylko malować to co potrzebujemy (czyli węża). To kiedy i jak malować zależy od kontekstu – nie ma jednego sposobu pokrywającego wszystkie przypadki, bo zestaw koniecznych czynności do wykonania jest inny dla renderowania bezpośrednio na płótnie okna i inny dla renderowania na pomocniczych buforach. Dużo by odpowiadać.


Przerobiłem kod Twojego projektu i teraz wąż reprezentowany jest przez generyczną listę. Dodałem trochę rzeczy, dzięki którym tworzenie węża jest łatwiejsze i daje trochę więcej możliwości. Zmieniłem też sposób jego renderowania, dzięki czemu obraz nie miga podczas ruchu, a także dodałem funkcję wyświetlania krzyżyków w miejscach połączeń jego segmentów.

Inverse Kinematics.png

Liczbę segmentów, długość i grubość każdego z nich oraz kolor węża ustala się w konstruktorze formularza.

Jeśli chodzi o sterowanie wężem to są dwa tryby. Pierwszy to podążanie – nie trzeba klikać aby przesuwać węża bo sam chodzi za kursorem. Drugi tryb to przesuwanie manualne, w którym aby wąż chodził, trzeba trzymać lewy przycisk i przesuwać myszę. Tryb przełącza się prawym klawiszem myszy. Tryb manualnego przesuwania węża wykorzystuje różnicę współrzędnych jego głowy oraz kursora, dzięki czemu głowa nie jest ciągle przylepiona do kursora (w trybie podążania jest).


W załączniku dorzucam w pełni obiektowy kod, jako rozwinięcie projektu przedstawionego przez OP. Jeśli ktoś chciałby się pobawić wężem to plik wykonywalny znajduje się w załączonym archiwum. A Ty @machinebyte4 pobierz sobie to archiwum i zobacz jak powinien wyglądać czytelny kod źródłowy.

0
furious programming napisał(a):

No dobra, przyjrzałem się bliżej kodowi tego projektu (bom był ciekaw jak to działa) i mam kilka(naście) uwag. :]


Pierwsza rzecz – kinematyka odwrotna po angielsku to inverse kinematics, a nie invert kinematiks.

Racja, błąd

Kod piszesz bardzo niedbale – nie stosujesz jednolitych wcięć, nie korzystasz z przyjętej konwencji nazewnictwa, chętnie stosujesz jednoliterowe, a tym samym nic nie mówiące o swoim przeznaczeniu identyfikatory, nie robisz odstępów pomiędzy operatorami, warunki (nawet złożone) piszesz w jednej linijce, co sprawia, że są one kompletnie nieczytelne. Do tego pozostawiasz w kodzie nieużywane zmienne i moduły, a także zakomentowane fragmenty kodu, które nie wiadomo dlaczego są zakomentowane (czy są one pozostałością po testach, czy może alternatywnym rozwiązaniem danego problemu).

Często spotykam się z zakomentowanymi całymi fragmentami kodu ale rozumiem Twój punkt widzenia.

Poza tym jeśli już publikujesz źródła projektu, to nie dołączaj do nich plików binarnych czy backupów źródeł, a także nie pozostawiaj w ustawieniach projektu hardkodowanych ścieżek do jakichś bibliotek (w dodatku nieużywanych w projekcie).

Ach ta wygoda zaznaczenia katalogu do archiwum..

Jeśli chodzi o część merytoryczną, to też jest sporo do poprawienia. Po pierwsze, do przechowywania płaskiej listy jakichś elementów używaj list (np. generycznych), bo o wiele łatwiej się ich używa – łatwiej się wypełnia danymi i łatwo się te dane później modyfikuje, dzięki czemu kod będzie krótki i czytelny. Ty używasz zwykłej macierzy z zarezerwowaną ostatnią komórką, która jest pomijana podczas renderowania węża. To samo można uzyskać korzystając z list.

Tutaj skupiasz się na elementach w odniesieniu do grafiki. Zauważ, że dla mnie ważniejszy jest sam algorytm działania węża.

btw, drzewo zrobię w przyszłym tygodniu. Publikując kod wezmę sobie niektóre Twoje uwagi pod rozwagę ;)
Pozdrawiam,

0
machinebyte4 napisał(a):

Często spotykam się z zakomentowanymi całymi fragmentami kodu ale rozumiem Twój punkt widzenia.

Tu nie chodzi o punkt widzenia – zbędny kod zawsze usuwa się z projektu.

Natomiast jeśli już z jakiegoś powodu decydujemy się na pozostawienie zaremowanych fragmentów lub nawet całych metod, to wypada w komentarzu napisać o co chodzi, tak aby inny użytkownik wiedział czy to backup, czy inny sposób implementacji czy jeszcze coś innego.

Ach ta wygoda zaznaczenia katalogu do archiwum..

Lazarus ma proste narzędzie do ”publikowania” kodu – menu Project, pozycja Publish project.

Tutaj skupiasz się na elementach w odniesieniu do grafiki.

Tak, dlatego że Twój projekt będzie w przyszłości pobierany przez wiele osób i każdy będzie się zastanawiał dlaczego mu ekran tak miga podczas zabawy wężem. Jeśli nie zamierzasz w przyszłości zajmować się grafiką dla okienkowego UI (np. tworzeniem komponentów) to z tych uwag mogą skorzystać odwiedzający ten wątek.

Zauważ, że dla mnie ważniejszy jest sam algorytm działania węża.

Tutaj też mam uwagę – spróbuj implementować tego typu listy w taki sposób, aby wszystkie elementy takiej listy były używane. Obecnie masz to zrobione w ten sposób, że ostatni element listy nie jest w ogóle wyświetlany.

Nie wpadłbym na to, gdybym nie spróbował przeportować tego kodu, wymieniając macierz na generyczną listę. Kod przepisałem i ciągle dziwiłem się dlaczego głowa węża (ostatni segment) jest przyklejona do lewego górnego rogu okna, czyli jeden z punktów ostatniego segmentu cały czas posiada współrzędne 0,0. Sądziłem, że popełniłem błąd podczas przepisywania kodu i coś pominąłem. A tu okazało się, że wszystko jest w porządku oprócz renderowania – użyłem pętli for in i renderowałem wszystkie segmenty.

Całe zamieszanie wzięło się z pokrętnej deklaracji macierzy przechowującej segmenty:

const maxParts = 10;

var
  arm : array[0..maxParts+1] of TArm;

Jeśli już używasz stałej określającej rozmiar macierzy, to powinna ona przyjmować wartość faktycznie zgodną z liczbą elementów. Twoja stała informuje, że macierz zawiera 10 elementów, tymczasem zawiera ich 12. Nie chodzi tu o tę jedną zarezerwowaną komórkę (pomijaną podczas renderowania), a o indeksację macierzy od 0. Jeśliby nie używać tej dodatkowej komórki, to w dalszym ciągu macierz zawierałaby o jedną komórkę za dużo.

Tymczasem schemat jest prosty – stała powinna określać rzeczywisty rozmiar macierzy. Przykład:

const
  SEGMENTS_COUNT = 10;

var
  Segments: array [0 .. SEGMENTS_COUNT - 1] of TSegment;

Jeśli macierz ma zawierać ten jeden dodatkowy element na końcu (zarezerwowany) to w ten sposób:

var
  Segments: array [0 .. SEGMENTS_COUNT] of TSegment;

Aby móc wygodnie odwoływać się do dwóch specjalnych komórek (ostatniej używanej oraz zarezerwowanej) warto zadeklarować sobie dodatkowe stałe z ich indeksami:

const
  SEGMENTS_COUNT = 10;

const
  SEGMENT_RESERVED = SEGMENTS_COUNT;
  SEGMENT_LAST     = SEGMENT_RESERVED - 1;

var
  Segments: array [0 .. SEGMENTS_COUNT] of TSegment;

Niby to takie pierdoły, ale w szerszej perspektywie właściwa deklaracja zmiennych pozwoli zwiększyć elastyczność kodu i oszczędzić czas. Jeśli trzeba będzie zmienić rozmiar macierzy to wystarczy zmienić wartość jednej stałej, a reszta kodu sama się dostosuje do zmian.

btw, drzewo zrobię w przyszłym tygodniu.

Z tym drzewem to akurat tak się złożyło, że w piątek byłem na małej wycieczce i gapiąc się godzinę za okno samochodu zastanawiałem się nad narzędziem do proceduralnego generowania piksel-artowej roślinności podatnej na odkształcanie (głównie w wyniku działania wirtualnego wiatru).

No i akurat tak się składa, że tego typu drzewka powinny być reprezentowane przez drzewiastą strukturę połączonych ze sobą segmentów, a wyginanie całych gałęzi byłoby niczym innym jak specyficzną implementacją właśnie algorytmu kinematyki odwrotnej. :]

0

Lazarus ma proste narzędzie do ”publikowania” kodu – menu Project, pozycja Publish project.

Ha, nie zwróciłem na to nigdy uwagi.

0

@machinebyte4: jak będziesz miał coś ciekawego to podrzuć – z chęcią się zapoznam z kodem i pobawię. :]

2

wczoraj zrobiłem "łodygę" lub jak kto woli pień drzewa. Klasyczna kinematyka prosta (forward kinematics). Jeszcze nie posyłam bo myślę nad koncepcją gałęzi, czy na sztywno w kodzie tworzyć gałęzie czy jakoś inaczej..
Jak będzie większy postęp to wyślę Ci to na pewno do "przeróbki" ;)

0
machinebyte4 napisał(a):

Jeszcze nie posyłam bo myślę nad koncepcją gałęzi, czy na sztywno w kodzie tworzyć gałęzie czy jakoś inaczej..

Jeśli dane na temat segmentu pnia (lub pni – drzewa bywają fikuśne) oraz segmentów wszystkich odnóg (gałęzi) opakujesz sobie w prostą drzewiastą strukturę, to metoda generująca losowe drzewko będzie bardzo prosta – za sprawą rekurencji rzecz jasna. Sugeruję prostą klasę opisującą pojedynczy segment z osadzoną listą segmentów-dzieci – łatwo będzie nią zarządzać, modyfikować i iterować po segmentach.

Jak będzie większy postęp to wyślę Ci to na pewno do "przeróbki" ;)

Spoko – zawsze to dla mnie coś nowego, bo kinematyką jako tako nigdy się nie zajmowałem. :]

1

Kod zapodam jak zrobię kilka ulepszeń do obliczeń.

0

Wyszło całkiem nieźle, choć z tym wiatrem to miałem na myśli jedynie oś X. :]

No nic, czekam w takim razie na kod źródłowy.

1

prościej chyba tego się nie da napisać (na pewno bardziej elegancko).. ;)

0

@machinebyte4: działać działa, ale ten wiatr wygląda słabo i dość nierealistycznie wygina to drzewko. Wszystko dlatego, że owy wiatr w Twoim kodzie nie jest parametrem wychylania gałęzi, a przyciągania ich końcówek do określonego punktu. Czyli działa to tak samo jak w przypadku węża, z tą różnicą, że teraz jest wiele węży połączonych ze sobą.

Poza tym nadal kod nie wygląda zbyt dobrze. Stosujesz złe praktyki, jeśli chodzi o nazewnictwo elementów (zmiennych, stałych, parametrów itd.), formatowanie kodu (głównie instrukcje warunkowe), nadal używasz zmiennych globalnych do przechowywania elementów, które globalnymi być nie powinny. No i nadal pitolisz się z gołymi macierzami, zamiast skorzystać z wygodniejszych i prostszych w użyciu kontenerów (np. z listy generycznej) oraz opakować drzewko w wygodną klasę, tak aby łatwo było zarządzać całą zawartością (czyli wszystkimi gałęziami).


Jeśli chcesz się jeszcze pobawić tym drzewkiem to popraw to o czym napisałem w powyższym paragrafie. Natomiast jeśli chodzi o sposób wyginania gałęzi, to zabierz się za to z d**y strony. Dosłownie – nie modyfikuj pozycji i kąta segmentów od czubków końcowych gałęzi, a idąc od pnia drzewa aż do końcowych segmentów. Czyli najpierw przechyl pień i wszystko wyżej, następnie przechyl gałęzie wychodzące z pnia i wszystko ponad nimi itd. itd., aż do samego końca (użyj rekurencji).

Jeśli chodzi o wiatr, to użyj tylko jednej liczby, która określi kierunek i siłę wiatru w osi X (skoro i tak korzystasz z rzutu bocznego). Im wyższa wartość, tym silniejszy przechył. Natomiast przechylenie gałęzi możesz zrealizować bazując na okręgu i zwiększeniu kąta. Wierzchołek danego segmentu bliższy pnia potraktuj jako środek okręgu, a długość segmentu jako promień. W takim przypadku przechylenie segmentu oznaczać będzie obliczenie punktu na tym okręgu według bieżącego konta plus (lub minus) siła wiatru razy mnożnik. Mnożnikiem może być numer segmentu licząc od pnia – im dalej od niego, tym wyższa wartość mnożnika, a więc mocniejszy przechył (à la im dalej od pnia, tym cieńsze gałęzie i bardziej podatne na odkształcanie).

Potem możesz dodać kolejne parametry, np. wyginanie się gałęzi (rysowane jako łuki) i pulsujący wiatr. ;)

2

Te dwa proste programy które tutaj zamieściłem (tak, wiem, że na tablicach) są testem-wstępem do tego projektu:

Powolutku może zacznę się ogarniać i z kodem i ze sprzętem, bo doprowadzenie tego do postaci wyżej zaprezentowanej było dość męczące ;)

Przy okazji, czy ma ktoś z Was doświadczenie z jakąkolwiek bezpłatną biblioteką do obsługi obrazu, czytaj śledzenia obiektu w czasie rzeczywistym? Taką którą mogę stosować z Lazarusem na RPi.

1
machinebyte4 napisał(a):

...
Przy okazji, czy ma ktoś z Was doświadczenie z jakąkolwiek bezpłatną biblioteką do obsługi obrazu, czytaj śledzenia obiektu w czasie rzeczywistym? Taką którą mogę stosować z Lazarusem na RPi.

Nie mam w tym doświadczenia, ale może to się przyda: https://forum.lazarus.freepascal.org/index.php/topic,38121.0.html

1 użytkowników online, w tym zalogowanych: 0, gości: 1