Kinematyka odwrotna na przykładzie węża

Odpowiedz Nowy wątek
2019-05-03 21:33
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...

edytowany 2x, ostatnio: furious programming, 2019-05-03 23:12
Dorzuć źródła do załączników posta, tak aby nie trzeba było ich szukać nie wiadomo gdzie. - furious programming 2019-05-04 01:04

Pozostało 580 znaków

2019-05-03 23:39
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. :]


Pozostało 580 znaków

2019-05-04 10:01
1

Załącznik przesłany

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

Pozostało 580 znaków

2019-05-04 23:46
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.


edytowany 2x, ostatnio: furious programming, 2019-05-05 00:26

Pozostało 580 znaków

2019-05-05 11:13
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,

Pozostało 580 znaków

2019-05-05 14:25
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. :]


edytowany 2x, ostatnio: furious programming, 2019-05-05 14:27

Pozostało 580 znaków

2019-05-05 19:06
0

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

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

Pozostało 580 znaków

2019-05-07 09:41
0

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


Pozostało 580 znaków

2019-05-07 10:17
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" ;)

Pozostało 580 znaków

2019-05-07 11:34
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. :]


edytowany 2x, ostatnio: furious programming, 2019-05-07 11:34

Pozostało 580 znaków

2019-05-11 22:51
1

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

edytowany 1x, ostatnio: furious programming, 2019-05-11 23:41

Pozostało 580 znaków

Odpowiedz
Liczba odpowiedzi na stronę

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