Pokaż wiadomości

Ta sekcja pozwala Ci zobaczyć wszystkie wiadomości wysłane przez tego użytkownika. Zwróć uwagę, że możesz widzieć tylko wiadomości wysłane w działach do których masz aktualnie dostęp.


Wiadomości - ChristopherTaivaus

Strony: [1]
1
Nie wiem co prawda jak wygląda kod Twojego silnika, ale ten pseudo zapis powinien coś Tobie dać:
Position position;
...
if( sprite->type == CHARACTER )
    position.y -= 1;
sprite->Draw( screen, position );
Z poważaniem,
Krzysztof.
Powinien dać do zrozumienia, czego nie powinno się robić. Rozwiązanie, które dopisał OP jest miliard razy lepsze, bo jest ogólne, nie szczególne.
Dziękuję szanownemu koledze Kyroaku'owi, że przypomniał mi jak działa system edukacyjny w Polsce.
Dla wszystkich innych, którzy chcieliby się dostać do gamedev'u lub do korpo na jakieś dobre stanowisko, lub chce podwyższyć swoje kwalfikacje, wytłumacze swoją opinię.
Sprawa pierwsza: umiejętność czytania ze zrozumieniem.
Jeśli ktoś pisze pseudo kod lub przedstawia schemat blokowy istotną rzeczą jest interpretacja, a nie odczyt dosłowny (jeśli ktoś czytający przygotowuje się do matury z informatyki - i chce ją zdać -, lub w firmie, do której się wybiera, dział HR robi test - i chce się dostać do tej firmy - to radzę to zapamietać).
Stosując powyższą zasadę, następujące:
Position position;
...
if( sprite->type == CHARACTER )
    position.y -= 1;
sprite->Draw( screen, position );
powinno być przekształcone mniej więcej w:
Position position;
...
for( int _y_ = ymin; _y_ <= ymax; _y_++ )
{
for( int _x_ = xmin; _x_ <= xmax; _x_++ )
{
position.x = _x_;
position.y = _y_;
tile[ _y_ * tilesw + _x_ ].sprite->Draw( screen, position );
}
}
for( int _character_ = 0; _character_ < maxchars; _charakter_++ )
{
position = character[ _character_ ].pos;
position.y--;
character[ _character_ ].sprite->Draw( screen, position );
}
gdyż:
if( sprite->type == CHARACTER )
    position.y -= 1;
wyraźnie mówi: sprite o typie bohatera ma być potraktowany inaczej!.
Sprawa druga: kompilator i zasada działania procesora.
Po pierwsze kompilator (zależy jak bardzo inteligentny) działania
x = a * b - c;
y = d * e - f;
może przekompilować na następujące instrukcje:
pomnoż:  x = a * b
pomnoż:  y = d * e
odejmij: x = x - c
odejmij: y = y - f
ponieważ w procesorach architektury x86 szybciej wykona się: mnożenie po mnożeniu, niż dodawanie po mnożeniu. Architektura ta ma dwa rodzaje czasu odpowiedzialnego za wykonywanie instrukcji:
 - latencje - rzeczywisty czas wykonania instrukcji w cyklach (może posiadać zakres);
 - throughput - czas w cyklach po którym ta sama instrukcja może rozpoczać wykonanie (np. mnożenie liczb całkowitych po mnożeniu liczb całkowitych), który jest mniejszy od latencji;
więc dwie te same instrukcje arytmetyczne pod rząd mogą się wykonać (w najgorszym wypadku) w czasie maksymalna latencja + throuput, zamiast maksymalna latencja + maksymalna latencja. Nawet w dzisiejszych architekturach, gdzie wykonanie nie nastepuję w koleności (out-of-order), dosłowna kompilacja:
pomnoż:  x = a * b
odejmij: x = x - c
pomnoż:  y = d * e
odejmij: y = y - f
wykona się w koleności: pomnoż, pomnoż, odejmij, odejmij (ponieważ żeby odjąć c i f muszą być znane wyniki mnożeń).
Dostawienie:
- m_surface->h + Globals::tilesizemoże spowodować skompilowanie obliczania wartości rect.y do postaci (zależnie czy kompilator układa obliczenia idąc od końca czy od początku - w lewo lub w prawo):
rect.y = ( position.y * SIZE - cam.y ) - ( surface->h + SIZE );
rect.y = ( position.y * SIZE ) - ( cam.y - surface->h + SIZE );
zależnie od wybranego kompilatora. Pomijam przeniesienie argumentu cam.y które może wystąpić w tym drugim przypadku (przyp. najpierw dodawanie, później odejmowanie). Może to być ważna informacja jeśli kod w przyszłości ma być upubliczniony i ktoś spróbuje skompilować go dość ciekawym kompilatorem.
( 10 * 32 - 64 ) - ( 32 + 32 ) = 192
( 10 * 32 ) - ( 64 - 32 + 32 ) = 320
Sprawa trzecia: optymalizacja.
W profesjonalnych silnikach operacje arytmetyczne na wektorach danych są wykonywane zbiorczo (w C++ poprzez przeciążenie operatora), więc orginalne:
x = a * b - c;
y = d * e - f;
można przekształcić w
p = pos * SIZE - cam;
rect.x = p.x;
rect.y = p.y;
co jest równe zapisowi z poprzedniego punktu, z tą różnicą, że jeśli dany procesor (nie musi to być procesor architektury x86) posiada zestaw instrukcji do operowania na wektorze danych, złożonego z dwóch 32-bitowych liczb całkowitych, kompilator może w tym wypadku ich użyć. Używając stareńkiego już zestawu MMX, procesorów x86, całość możnaby było wykonać w 6-7 instrukcjach (dodatkową instrukcją byłaby kopiująca SIZE jeśli zrobiłby to ktoś nie umiejętnie, gdyż z tą stałą możnaby było stworzyć stały wektor). W normalnych okolicznościach polecałbym zmiane odejmowania na dodawanie ze względu na szybkość wykonania, to dobra praktyka w przypadku liczb zmiennoprzecinkowych, ale w tym przypadku (wektorowym) nie ma to znaczenia. Wspomniana dobra praktyka dodawania po mnożeniu, przy liczbach zmiennoprzecinkowych, odzwierciedlić można przez istniejące pojedyncze instrukcje operujące na wektorach danych wykonujące te dwie operacje. Mnożenie możnaby było zmienić na operacje przesunięcia bitowego w lewo (jest szybsze o 1 cykl od mnożenia, a w przypadku niewektorowym prawie 2 razy wolniejsze od mnożenia). Dekrementacja position.y też ma nikłe benefity względem odejmowania jeśli jest w rejestrze i sytuację odwrotną jeśli jest w pamięci.
Zmieniłbym jeszcze układ klasy Sprite. Ale zapewne ta sugestia i powyższe sztuczki/uwagi to za dużo dla prostej gry. Powtarzam: przedstawiłem tylko mój punkt widzenia, reprezentowany przez kryptyczny zapis, skrytykowany poprzez wcześniej wspomnianego jegomościa. Mam nauczkę na przyszłość: pisać referaty, a nie skróty myśliowe...
Pozdrawiam.
Krzysztof.

2
Nie wiem co prawda jak wygląda kod Twojego silnika, ale ten pseudo zapis powinien coś Tobie dać:
Position position;
...
if( sprite->type == CHARACTER )
    position.y -= 1;
sprite->Draw( screen, position );

Z poważaniem,
Krzysztof.

3
Szanowny Panie to zagadnienie zaawansowane - żeby nie powiedzieć eksperckie!
Po pierwsze chciałbym się dowiedzieć czy to ma być kompilowany c++, czy mechanika działania Unity? (tak tylko... żeby zmiejszyć ścianę tekstu potrzebnego do wyjaśnienia - na przykład.)
Pozdrawiam. Krzysztof.

4
SDL / Odp: SDL2 Obliczenie proporcji tekstury do rozdzielczości okna
« dnia: Lipiec 12, 2017, 00:18:53 »
I ja też witam,
Czytając post zaowocowało w kilka pytań u mnie w głowie:

1. Czy używasz OpenGL'a czy tylko SDL2?
2. Jak zrozumieć 'zeskalować' i 'proporcjonalna wielkość'?
3. Czy tekstura jest częścią UI?

Więc odpowiadam na wszystkie 3:

1. Nie wiem co robi SDL_RenderSetScale (jak poprzednik nie miałem styczności z SDL_RenderSetScale): czy ona tylko skaluje - tworząc pikselową blokowość, czy także interpoluje - jak program graficzny (dokumentacja mówi o 'zależności od renderera', a jakaż to jest: nie wiem). Jeśli to jest problemem (funkcja SDL produkuje pikselozę) rozwiazania są dwa, lub trzy (zależy czy używasz dodatkowo OpenGL'a). Pierwsze: polega na tym, że swój program wzbogacasz o jakąś biblioteczkę do skalowania, która interpoluje bilinearnie czy bikubicznie i używasz jednej z tych opcji (zależnie od gusty). Druga: używasz do tego zewnętrznego programu: tworząc kilka wersji jednej grafiki dla różnych rozdzielczości. Trzecie: pozwalasz na to OpenGL'owi używając mipmap'owania (i może to o tym mówi dokumentacja SDL_RenderSetScale). Jest jeszcze funkcja SDL_RenderSetLogicalSize która ma być lepsza, ale zapewna robi to samo co SDL_RenderSetScale, rzekomo umożliwia obliczanie poprawnego skoku w platwormowkach (tej też nie używałem przepisuje tu z innego tekstu, więc klamać mogę).
2. Obraz w rozdzielczości 1920x1080 to obraz który ma proporcje 16:9, 800x600 za to ma 4:3, więc tekstura będąca kwadratem w 16:9 będzie przeskalowanym wysokim prostokątem w 4:3 kiedy przeskalowana będzie szerokość i wysokość kwadratu, i będzie zajmować proporcjonalnie taką samą powierzchnię na ekranie jak w przypadku 16:9 (a), za to przeskalowana wielkość kwadratu proporcjonalna tylko względem szerokości da kwadrat niższy (b), niż taki przeskalowany przez wielkość proporcjonalną względem wysokości (c), ale oba nie będą miały takiej samej (proporcjonalnie) powierzchni. Jeśli SDL_RenderSetScale działa w sposób a, a chciałbyś np. c to całą logikę skalowania trzeba będzie przepisać.
a:                                          b:                                          c:
float skala_dla_szerokosci = 800 / 1920;    float skala_dla_szerokosci = 800 / 1920;   
float skala_dla_wysokosci  = 600 / 1080;                                                float skala_dla_wysokosci  = 600 / 1080;
obiekt.x = obiekt.x * skala_dla_szerokosci; obiekt.x = obiekt.x * skala_dla_szerokosci; obiekt.x = obiekt.x * skala_dla_wysokosci;
obiekt.y = obiekt.y * skala_dla_wysokosci;  obiekt.y = obiekt.y * skala_dla_szerokosci; obiekt.y = obiekt.y * skala_dla_wysokosci;
obiekt.w = obiekt.w * skala_dla_szerokosci; obiekt.w = obiekt.w * skala_dla_szerokosci; obiekt.w = obiekt.w * skala_dla_wysokosci;
obiekt.h = obiekt.h * skala_dla_wysokosci;  obiekt.h = obiekt.h * skala_dla_szerokosci; obiekt.h = obiekt.h * skala_dla_wysokosci;
16:9 +--------------------+                 +--------------------+                      +--------------------+
     |                    |                 |                    |                      |                    |
     |                    |                 |                    |                      |                    |
     +---+                |                 +---+                |                      +---+                |
     |   |                |                 |   |                |                      |   |                |
     +---+----------------+                 +---+----------------+                      +---+----------------+
4:3  +--------------+                       +--------------+                            +--------------+
     |              |                       |              |                            |              |
     |              |                       |              |                            |              |
     +-+            |                       |              |                            +---+          |
     | |            |                       +-+            |                            |   |          |
     +-+------------+                       +-+------------+                            +---+----------+
3. Jest jeszcze sprawa taka, że tekstura może być częścią interfejsu. Jeśli ten, jak w strategiach, jest fragmentem ekranu: można wtedy powierzchnię dla pola gry wyznaczyć stałą proporcjonalną, a skalować tylko interfejs (co tworzyć będzie pasek o różnej szerokości lub wysokości zależnie od położenia UI) i zmieniać jego układ jak na internetowych stronach responsywnych.
16:9 +--------------------+                 +--------------------+
     |           |        |                 |    pole            |
     |   pole    |        |                 |    gry             |
     |   gry     |        |                 |                    |
     |           |        |                 |--------------------|
     +--------------------+                 +--------------------+
4:3  +--------------+                       +--------------+
     |           |  |                       |    pole      |
     |   pole    |  |                       |    gry       |
     |   gry     |  |                       |--------------|
     |           |  |                       |              |
     +--------------+                       +--------------+

Pozdrawiam.
Krzysztof.

5
Szkółka / Odp: [OpenGL] Wyświetlenie fontów w interkach 4kb
« dnia: Czerwiec 14, 2017, 22:38:38 »
Jak najmniej miejsca? Możesz wykorzystać bitmapy (dosłownie!) i mieć litery tak jak w trybie tekstowym, gdzie glif 8x16 czcionki zajmuje 16 byte'ów (czyli każdy pixel zajmuje bit). Np. 'A':

byte:   grafika:
        01234567
0x00  0 ........
0x00  1 ........
0x00  2 ........
0x38  3 ..###...
0x6C  4 .##.##..
0xC6  5 ##...##.
0xC6  6 ##...##.
0xFE  7 #######.
0xC6  8 ##...##.
0xC6  9 ##...##.
0xC6  a ##...##.
0x00  b ........
0x00  c ........
0x00  d ........
0x00  e ........

Powyższe możesz zredukować do 8 byte'ów: bo można pominąć pierwsze 3 i 4 ostatnie byte'y o wartości 0 i, jak każda inna duża litera i cyfra, 'A' ma taką samą wysokość więc i te w/w w 8 byte'ach się zmieszczą, a małe litery będą potrzebować 10 (bo wysokości są różne), lub można też napisać dodatkowy byte z wartością offsetu górnego i liczby lini do rysowania dla małych liter czy znaków interpunkcji. Np. 'a':

byte dodatkowy:

0x56 (w rzeczywistości: offset 5 lini i 6 byte'ów do rysowania)

0x78  5 .####...
0x0C  6 ....##..
0x7C  7 .#####..
0xCC  8 ##..##..
0xCC  9 ##..##..
0x7E  a .######.

Podsumowując dla dużych liter i cyfr: 8 byte'ów, dla małych: 10, lub: byte-nagłówek i odpowiednia liczba byte'ow na wypełnienie. Następnie wszystkie potrzebne dane znaków (poprzedzone dodatkowym byte'm-nagłówkiem typu: 'A' i dane dla 'A') umieścić w kolejności ASCII (dla szybszego dekodowania symboli) w dużej tabeli byte'owych elementów i na podstawie tego tworzyć teksturę i atlas glifów dopiero podczas działania programu.

Z poważaniem.
Krzysztof.

6
Szkółka / Odp: Tworzenie mapy do RTS'a
« dnia: Marzec 10, 2016, 07:03:32 »
Jeżeli rozdzielisz mesh od grida z definicją rodzaju pola to obojętnie ile kafelka klif wizualnie zajmuje oznaczasz to na gridzie jako klif. Zakładam że na te pół kafelka i tak nie wpuścisz jednostki
Jeśli będzie jak w Warcraft'ie III to się zgodzę, ale jeśli Krystian się zmotywuje to może zrobić jak w Starcraft'ie II. Zresztą w oby dwóch grach w pliku mapy są dodatkowe informacje, o tym gdzie może jednostka chodzić, a gdzie nie, tworzące dodatkowy mesh. W takim przypadku można się wyzbyć specjalnych definicji np. typu klif, o których to będzie wiedział tylko generator/edytor i wykorzystywał do tworzenia map. A informacje na temat po której częsci klifu można chodzić (mesh), można by było dodać do pliku z modelem klifu (dodatkowy element do formatu).
Pozdrawiam,
Krzysztof.

7
Szkółka / Odp: Tworzenie mapy do RTS'a
« dnia: Marzec 09, 2016, 18:19:46 »
Referat!
Tym razem o życiu...
Cytuj
Kurczę, aż mi głupio, że włożyłeś w to tyle wysiłku i z tego co widzę to kawał nieprzespanej nocy...
Eee... z tego o czym mnie poinformowała przeglądarka zajęło mi to 7 godzin i 15 minut... życia! ( Masz szczeście, że nie żyjemy w latach dziewiećdziesiątych! Wtedy było co oglądać w telewizji i zapewne zamiast odpowiedzieć na Twoje pytanie, wolałbym coś oglądnąć.). Miej na uwadze, że pisanie kodu to błachostka, a ten spędzony czas to w większości: pisanie opisów, dokumentowanie i sprawdzanie błędów (kilka stylistycznych pozostało, ale już poprawiłem (na szczęście nie banują, za ortografię i błędy stylistyczne...)).

Cytuj
A jeszcze bardziej niezręcznie mi, bo jednak raczej chciałem najpierw spróbować zrobić to w Unity (...)
Patrz zasada piąta w moim poprzednim poście: nie przejmujemy się kiedy prototypujemy. Nie ważne w czym prototypujesz, w prototypie ważny jest efekt końcowy/mechanika.

Cytuj
(...) nie czuję się na tyle mocny w programowaniu grafiki żeby zabierać się do tego.
Programowanie grafiki, to takie... programowanie mechanik to dopiero hardcore (w stylu: (to zapewne przed Tobą) jak zrobić coś by wykonać to w odpowiednim czasie (warunki zyciężstwa, zabicie potwora i poinformowanie o tym gracza, co prawda można to zakodować na stałe, ale lepiej by było gdyby takie coś było wczytywane z pliku mapy), czy jak zmieścić coś co w rzeczywistości zajmowałoby 16 gibibajtów (jest taka jednostka) w około 1 megabajcie (spoiler!: da się, ale zależne jest to od tego czym są te dane)).

Cytuj
(...)mam nadzieję, że ta gra nie będzie w Unity ani w DirectX3D, tylko w OpenGL i C/C++(...)
Cytuj
(...) zainspirowałeś mnie swoją postawą i jeśli uda mi się stworzyć ten prototyp w gotowym silniku, to nie wykluczone że spróbuję (...)
Mam nadzieję. Choć wiem: że języki programowania są skomplikowane (sam żałuję, że przygody z programowaniem imperatywnym nie zacząłem od assembly): to raz, dwa: dbanie na własną rękę o każdy zasób dodaje komplikacji, no i trzy: walka systemów operacyjnych ze sterownikami też komplikuje (np. na karcie graficznej X coś się rysuje, a na karcie Y już nie, na tym samym komputerze na systemie X program chodzi płynnie, a na systemie Y wolniej, przy czym sprzęt nie jest zmieniany (czytaj: zawsze zbrodnie popełnia służacy), na tych samych kartach i systemach operacyjnych, ale różnych procesorach o tej samej klasie, coś się wyświetla w innym miejscu).

Cytuj
        // Uwaga! Część kodu w OpenGL. (reszta api zapewne też ma taką mechanikę)
Nigdy nie programowałem w Unity3D, ale teraz sprawdziłem w dokumentacji API czy są odpowiedniki glPushMatrix, a także glPopMatrix. I istnieją takie funkcje: GL.PushMatrix, GL.PopMatrix. I co się tyczy:

Cytuj
                // wskaźniki do wierzchołków kontrolnych (...)
Informację o wskaźnikach też sprawdziłem i podobno są w C# (nie programowałem nigdy w C#).

Cytuj
(...) nie wykluczone że spróbuję podciągnąć się w OpenGL'u.
W związku z powyższym (czytaj: moich zaglądnięciem w API Unity3D) OpenGL jest nie unikniony.

Cytuj
Jednak bardziej 'jara mnie' tworzenie samej gry, a nie silnika (co jest ewidentnym przeciwieństwem większości użytkowników warsztatu).
Powiem tak: nie wiem czy kojarzysz grę: 'Warcraft III: Reign of Chaos' i jej dodatek: 'Warcraft III: Frozen Throne' (tak strategia! i jeśli się wczytasz w mój poprzedni post to możesz się skapnąć, że mechanika rysowania mapy jest identyczna jak w tych tytułach), po pograniu w nie, chciałem taką grę zrobić (oczywiście istnieje ich więcej w różnych gatunkach, których chciałbym chociaż zaprototypować). I próbowałem! A efektem prób stało się zagłębianie w coraz to drobniejsze detale. Czyli kogoś będącego pod wpływem kilku gier stałem się (nieświadomie) potworem! Wszystko przed Tobą (technikalia i takie tam).

Cytuj
Niemniej i tak należą Ci się wielkie podziękowania (...)
Może chcesz (mój: żeby nie było, że fragmanty książek nielegalnie rozprowadzam) tekst o 'Wyzwalaczach i skryptach' (może Tobie się to przydać do warunków zwyciężstwa, czy wypisywaniu informacji dla gracza kiedy coś osiągnie)? Jak tak: to przekaż mi swojego maila i w weekend Tobie prześlę (tylko powiedz czy tekst chciałbyś w wersji dla: początkującego, zaawansowanego, czy eksperta, albo wszystkie trzy).

Cytuj
(...) należ[y] Ci się (...) dobre piwo ;)
Dobre tzn. znasz jakąś dobrą recepturę? Przy czym ja sobie to zapamiętam... (Drogi pamiętniczku: dziś dostałem informację, że za moje wysiłki nałeży mi się piwo. Mam nadzieję, że jeśli spotkam kiedyś autora pochwały, to mi je postawi. W przeciwnym razie rzucę na niego klątwę...). Powiedzmy, że jak będę miał coś ciekawego, i będę potrzebował kogoś do przetestowania to się do Ciebie odezwę (jesteś użytkownikiem Windowsa zapewne?).

Dobra wracam do roboty. I jeszcze raz powodzenia.
Pozdrawiam, Krzysztof.

8
Szkółka / Odp: Tworzenie mapy do RTS'a
« dnia: Marzec 09, 2016, 05:55:22 »
Więc...
Woda: Raz jeszcze.
Na rysunku 1.jpg, masz pokazane dwa sposoby jak możesz ugryść problem wody:
  • po lewej masz przykład klifów
  • po prawej zagięcie siatki (blender ma narzędzie do rzeźbienia, możesz go wykorzystać)
Rysunek 2.jpg pokazuje, że woda to dodatkowa warstwa pod siatką, jest ona jednym kwadratem (będącym czymś, jak gdyby poziomem morza), wysokość w tym przypadku to połowa wysokości klifu po niżej terenu po którym można się poruszać, ale możesz definiować jej poziom w pliku mapy jeśli chesz.
Cytuj
Nie mogę skopiować i dodać nowych wierzchołków, bo mam ich określoną ilość na samym początku więc wszystko by mi się rozjechało.
Tak. Masz określoną liczbę wierzchołków... dla siatki, nie kafli! Z resztą i tak dodatkowych nie potrzebujesz dla siatki. Na rysunku 2.jpg (załóżmy, że jest to widok z gry) widzisz mapę składającą się z wody, klifów i zagłębienia co daje 421 wierzchołków, a w rzeczywistości masz siatkę, trzy specjalne kafle klifów (obracane i/lub odbijane w odpowiedni sposób, patrz rysuj_klif który do końca nie mogłeś pojąć: jest poniżej) i jeden dodatkowy kwadrat będący wodą (i też tu nie potrzebujesz pełnego kwadratu tylko samą wysokość (zasada numer cztery: nie zaśmiecaj pamięci zbędnymi danymi; piąte: no chyba, że to prototyp jakiejś funktionalności i nie chce się nad nim myśleć)) razem 321 wierzchołków, tylko tyle potrzebujesz infomarcji podczas rozgrywki, reszta będzie obliczana i wysyłana wczasie rzeczywistym do karty graficznej. A to że widzisz tam więcej wielokątów na klifach ( przybliżenie 4.jpg ), nie znaczy, że musisz je trzymać w pamięci bez przerwy, tak jak wydzisz na obrazku, znaczy nie musisz mieć całej mapy. Obrazek 3.jpg, zawiera 2 kafle:
  • ten po lewej przedstawia coś co może być dnem morza (specjalny kafel, za którego rysowanie odpowiadałaby funkcja rysuj_wode), czerwona ramka ponad nim byłaby równa poziomowi terenu, na poziom wody nie patrz może on być wyżej lub niżej. Takiego kafla można zrobić dwie wersje dla dwóch głębokość, jedną płytką dla jednostek do chodzenia i małych łodzi, i drugą głęboką, po której pływają statki (łodzie też mogą);
  • pozostałe dwa, to warianty klifu, który może być zanurzony lub na powierzchni. Przy czym jak chcesz zastosować klify i mieć możliwość rzeźbienia to różnica pomiędzy górą klifu, a dołem nie może przekroczyć jakiejś ustalonej wartości (na rysunkach jest to 1), bo potrzebowałbyć więcej pamięci na kafle, lub estetycznie nie ładnie by to wyglądało. W takim wypadku przydałby się dedykowany edytor do map, który znałby wszyskie zasady, jak na przykład dno wody nie może być głępsze niż -2, ale napisanie czegoś takiego trochę by zajeło (jak robisz grę sam i chcesz miec edytor, no to do zobaczenia za rok, o ile weźmiesz się ostro do roboty, oczywiście w tym momęcie nie chcę wystraszyć). Mam nadzieję, że jak razie wystarczy sprytny skrypt w Pythonie i Blender, lub Blender i przepisywanie ręcznie wszystkich wierzchołków.
Cytuj
Jednak nie bardzo rozumiem część tworzenia klifu.
Rysowania nie tworzenia!
Cytuj
Myślałem też[, żeby] (...) dodawać wierzchołki innych modeli do mojej siatki, żeby jednocześnie grid zachował swoją strukturę.
Potrzebujesz modelów klifów i specjalnej funkcjonalności do przechowywania wierzchołków kontrolnych lub wskazywania na nie. Możesz utworzyć klasę by tą funkcję spełniała np. ModelKafla (lub ModelKlifu). Wierzchołki które są rogami (wierzchołkami kontrolnymi):
  • albo musisz w pliku składować na początku listy wierzchołków
  • lub musisz zapisać ich indeksy w pliku
(definitywnie bardziej pseudo c++)
// ModelKafla jest specjalną klasą, dedykowaną dla specjalnych kafli, które są
// modelami złożonymi z dużej ilości wierzchołków ( nie są wodą lub terenem ),
// może ona dziedziczyć po klasie Model.

class ModelKafla
{
public:
Wierzcholek3D * wierzcholki;
int liczba_wierzcholkow;

// układ wierzchołków kontrolnych:
// w4--w3
//  |  |
//  |  |
// w1--w2
//

// wskaźniki do wierzchołków kontrolnych ( mam nadzieję,
// że wiesz co to jest wskaźnik, a jak nie, to google'a
// chyba się nie boisz? )

Wierzcholek3D * w1;
Wierzcholek3D * w2;
Wierzcholek3D * w3;
Wierzcholek3D * w4;

// ... mrowie funkcji ...
// między innymi funkcja:

void wczytaj_z_pliku( const char * );
};

// ... mrowie deklaracji ...
// między innymi deklaracja funkcji:

void
ModelKafla::wczytaj_z_pliku( const char * sciezka )
{
FILE * plik;

// ... otwieramy plik ...

if( ( plik = fopen( sciezka, "rb" ) ) == NULL )
return;

// ... wczytujemy nagłówek pliku ...

// gdzieś tam w nim mamy zamieszczoną informację o liczbie wierzchołków:

fread( &liczba_wierzcholkow, 1, sizeof( int ), plik );

// potem wczytujemy wierzchołki

wierzcholki = new Wierzcholek3D[ liczba_wierzcholkow ];
fread( wierzcholki, liczba_wierzcholkow, sizeof( Wierzcholek3D ), plik );

// i teraz pierwsze cztery mogą być tymi czterema od siatki, wtedy:

w1 = & wierzcholki[ 0 ];
w2 = & wierzcholki[ 1 ];
w3 = & wierzcholki[ 2 ];
w4 = & wierzcholki[ 3 ];

// lub możemy specjalnie umieścić ich indeksy w pliku i je wczytać:

int indeksy_wierzcholkow_kontrolnych[ 4 ];
fread( indeksy_wierzcholkow_kontrolnych, 4, sizeof( int ), plik );

w1 = & wierzcholki[ indeksy_wierzcholkow_kontrolnych[ 0 ] ];
w2 = & wierzcholki[ indeksy_wierzcholkow_kontrolnych[ 1 ] ];
w3 = & wierzcholki[ indeksy_wierzcholkow_kontrolnych[ 2 ] ];
w4 = & wierzcholki[ indeksy_wierzcholkow_kontrolnych[ 3 ] ];

// ... wczytujemy resztę pliku ...
// ... i zamykamy plik ...

fclose( f );
}
Cytuj
Myślałem też nad 'sklejaniem' całego mesha z poszczególnych modeli kafelków [...].
Właśnie funkcja rysuj_klif by Tobie 'sklejała', czy raczej: 'wklejała', kafle, nie będące terenem po którym się chodzi lub nie są wodą (kod dla wody takiej głębokiej jak np. dla statków jest poniżej). Przypatrz się obrazkowi 4.jpg. Na nim na czerwono zaznaczyłem wszystkie kopie kafelka znajdującego się na środku rysunku 3.jpg.
(pseudo c/c++)
void
rysuj_klif( int typ, Wierzcholek3D w1, Wierzcholek3D w2,
Wierzcholek3D w3, Wierzcholek3D w4 )
{
// obiekt klasy ModelKafla patrz wyżej

ModelKafla kopia_kafla;

// typ - 2: dlatego, że 0 to teren, a 1 to woda ( !specialna nie rzeźbiona
// np. dla statków lub łodzi ) wynik z działania ( typ - 2 ) / 8 da nam
// który kafel klifu potrzebujemy: róg wklęsły, róg wypukły, ściana,
// wariant ściany, czy wariant któregoś z rogów, przy czym 8 dlatego,
// że mamy tyle orientacji ( patrz niżej ).

kopia_kafla.kopiuj( zestaw_modeli_kafli[ ( typ - 2 ) / 8 ] );

// wynik ( typ - 2 ) % 8 da nam orientację

int orientacja_kafla = ( typ - 2 ) % 8;

// jeśli orientacja wyniesie 0:
// +--+
// |  |
// |ox|
// p--+
//
// legenda do powyższego:
// p w rogu to początek układu współrzędnych
// o i x służy tylko do pokazania zmian orientacji, czy mający pomóc
// przy implementowaniu transformacji kafla ( patrz poniżej ).

if( orientacja_kafla )
{
if( orientacja_kafla < 4 )

// jeśli orientacja wyniesie 1 - 3 obracamy zawartość
// modelu ( tu musimy mnieć na uwadze gdzie występuje
// punkt 'p' ).
// +--+ +--+ +--+
// | x| |xo| |o |
// | o| |  | |x |
// p--+ p--+ p--+
//

kopia_kafla.obroc( orientacja_kafla );
else

// jeśli orientacja wyniesie 4 - 7 najpierw odbijamy
// lustrzanie względem osi x ( lub y: zależy od
// implementacji ) cały kafel ( i tu znowu poświęcamy
// uwagę na punkt 'p' ).
// +--+    +--+
// |  |    |  |
// |ox| -> |xo|
// p--+    p--+
//
// a potem obracamy zawartość modelu jeśli jest taka
// konieczność ( mechanika jak wyżej ).
// +--+ +--+ +--+
// | o| |ox| |x |
// | x| |  | |o |
// p--+ p--+ p--+
//

kopia_kafla.odbij_i_obroc( orientacja_kafla );
}

// punkty kontrolne mogą mieć różną wysokość
// ( lub nie: zależy od implementacji; jeśli nie, pomijamy ten krok,
// i zmieniamy deklarację ( lub/i definicję ) funkcji z:
// void rysuj_klif( int typ, Wierzcholek3D w1, Wierzcholek3D w2,
// Wierzcholek3D w3, Wierzcholek3D w4 );
// na:
// void rysuj_klif( int typ, Wierzcholek3D w1 );
// bo reszty wierzchołków nie potrzebujemy )
// dopiero w tym miejscu, a nie przed transformacją je zmieniamy

kopia_kafla.w1->z = w1.z;
kopia_kafla.w2->z = w2.z;
kopia_kafla.w3->z = w3.z;
kopia_kafla.w4->z = w4.z;

// Uwaga! Część kodu w OpenGL. (reszta api zapewne też ma taką mechanikę)

// możemy kaflowi przypisać matrycę

glPushMatrix( );

// przekształcamy matrycę by kafel był rysowany w odpowiednim miejscu
// przesuniętym o wartość punktu w1 ( dlaczego? patrz wyżej 'układ
// wierzchołków kontrolnych', przy czym ten wierzchołek w1 jest argumentem
// funckji rysuj_klif, nie polem klasy ModelKafla; w1.z jeśli jest
// równe 0 (to ważne!) możemy wpisać jeśli nie wykonujemy
// kroku z nadpisywaniem koordynaty 'z' wierzchołków, w przeciwnym
// razie trzeba wstawić 0 na stałe, zamiast w1.z )

glTranslatef( w1.x, w1.y, w1.z );

// następnie, rysujemy kafel

kopia_kafla.rysuj( );

// i wracamy do poprzedniej matrycy

glPopMatrix( );
}

void
rysuj_wode( int typ, Wierzcholek3D w1, Wierzcholek3D w2,
Wierzcholek3D w3, Wierzcholek3D w4 )
{
// w zasadzie nie potrzebna funkcja ponieważ możemy
// użyć funkcji rysuj_kwadrat jedynie będziemy musieli zmienić,
// rysowaną teksturę z trawy na teksturę dna i wrócić do poprzedniej

zmien_obecnie_wykorzystywana_teksture_siatki( TEKSTURA_DNA );
rysuj_kwadrat( w1, w2, w3, w4 );
zmien_obecnie_wykorzystywana_teksture_siatki( TEKSTURA_TRAWY );
}

void
rysuj_kwadrat( Wierzcholek3D w1, Wierzcholek3D w2,
Wierzcholek3D w3, Wierzcholek3D w4 )
// raczej powinno być rysuj_czterobok, ale będę trzymał się tego błędu...
{
// ... i tu kod rysujący czterobok ...
}
Teraz to jesteś mi winny darmowy dostęp do alfy swojej gry, nawet jeśli skończy jako 'Walka sześcianów w 3D' (Misja 3: Introdukcja: 'O poranku, na wzgorze, wkroczył oddział Generała Sześcianka. Stamtąd dostrzegli dwie bandy Bezpodstawnych: jedną patrolującą brzeg rzeki, a drugą stojącą u jedynego, w okolicy, brodu. Przejdź nie postrzeżenie na drugi brzeg i zaatakuj niczego nie spodziewający się obóz po drugiej stronie rzeki.'). Przy czym mam nadzieję, że ta gra nie będzie w Unity ani w DirectX3D, tylko w OpenGL i C/C++, i zrobisz bild na Linuxa... dla mnie (tylko nie przesyłaj mi kodu: nie chcę dostać zawału, no chybam, że do kodu dołączysz czekoladę mleczną, czy jakiegoś wafelka kokosowego, chociaż... jak tak pomyśleć to dawno piegusków nie jadłem... kurierem chyba da się co takiego przesłać w Polsce?).
Jakieś pytania, jeszcze?
Pozdrawiam, Krzysztof.

9
Szkółka / Odp: Tworzenie mapy do RTS'a
« dnia: Marzec 07, 2016, 19:18:14 »
Witam,
Wygeneruj cały mesh na podstawie danych wysokości, tj: jeśli Twoja mapa ma 256x256 kafelków to potrzebujesz przynajmniej 257x257 wierzchołków, których pozycję 'x' i 'y' możesz obliczyć za pomocą dwóch pętli, a 'z' wczytywać z pliku mapy, z tego pliku także wczytaj informację o typie kafelka (czy jest on terenem, czy klifem, czy wodą). Jeśli chodzi o wodę to zależy jak chcesz ją rozwiązać, możesz stworzyć teren który jest wgłębieniem i przekracza jakąś ustaloną głębokość która byłaby taflą wody, lub mógłbyś stworzyć kilka specjalnych kafelków w blenderze, o dlugośc i szerokośc Twoich kafelków, będących brzegami i narożnikami klifu zagłębiającego się w wodę (i tu też woda to odpowiedni poziom zagłębienia), przy czym rogi takiego kafelka odpowiadałyby wierzchołkom stworzonej wcześniej siatki (byłyby punktami kontrolnymi), siatka byłaby stworzona ze współrzędnych 'x', 'y' prócz wysokości ('z') którą należałoby, jak wyżej napisałem, wczytać z pliku.
(x1,y1) klif   (x2,y1)
|       |      |
V       V      V
+-------+         <--- poziom terenu nad wodą ( z1 )
        |
--------|-------  <---            poziom wody ( z0 )
        |
        +------+  <--- poziom terenu pod wodą ( z-1 )         
Ostrzeżenie! Dalsza częśc może zawierać rozwiązanie, którego szukasz. Jeśli chcesz sam to rowiązać nie czytaj!
Niezależnie którą wersję wybierasz najpierw wygeneruj siatkę np.:
(pseudo c/c++)
float x = 0;
float y = 0;
for(int kafel_y = 0; kafel_y < 256; kafel_y++)
{
for(int kafel_x = 0; kafel_x < 256; kafel_x++)
{
// typy kafli z pliku, np.
// 0: może być płaskim kaflem
// 1: kaflem wodą
// 2: lewym górnym narożnikiem klifu
// 3: prawym górnym narożnikiem klifu, itd.
kafle[ kafel_y * 256 + kafel_x ].typ = typy[ kafel_y * 256 + kafel_x ];
kafle[ kafel_y * 256 + kafel_x ].wierzcholek1 =
Wierzcholek3D( x, y, z[ kafel_y * 257 + kafel_x ] );
kafle[ kafel_y * 256 + kafel_x ].wierzcholek2 =
Wierzcholek3D( x+1, y, z[ kafel_y * 257 + kafel_x +1 ] );
kafle[ kafel_y * 256 + kafel_x ].wierzcholek3 =
Wierzcholek3D( x, y+1, z[ ( kafel_y + 1 ) * 257 + kafel_x ] );
kafle[ kafel_y * 256 + kafel_x ].wierzcholek4 =
Wierzcholek3D( x+1, y+1, z[ ( kafel_y + 1 ) * 257 + kafel_x + 1 ] );
x+=1.f;
}
y+=1.f;
}
Jeśli chcesz mieć kafle klify możesz rysować je w ten sposób:
(pseudo c/c++)
for(int kafel_y = widoczny_y; kafel_y < widoczny_y + widoczny_h; kafel_y++)
{
for(int kafel_x = widoczny_x; kafel_x < widoczny_x + widoczny_w; kafel_x++)
{
// jeśli kafel jest wodą
if( kafle[ kafel_y * 256 + kafel_x ].typ == 1 )
{
// funkcja rysuje animowaną teksturę
rysuj_wode( kafle[ kafel_y * 256 + kafel_x ].typ,
kafle[ kafel_y * 256 + kafel_x ].wierzcholek1,
kafle[ kafel_y * 256 + kafel_x ].wierzcholek2,
kafle[ kafel_y * 256 + kafel_x ].wierzcholek3,
kafle[ kafel_y * 256 + kafel_x ].wierzcholek4 );
}
// jeśli kafel jest klifem
else if( kafle[ kafel_y * 256 + kafel_x ].typ > 1 )
{
// funkcja kopiuje konkretny fragment klifu (np. narożnik) i przekształca jego
// wszystkie wierzchołki dodając do nich wartość wierzchołka 'wierzcholek1',
// a każdemu z wierzchołków 'kontrolnych' (narożnych) nadpisuje wysokość
rysuj_klif( kafle[ kafel_y * 256 + kafel_x ].typ,
kafle[ kafel_y * 256 + kafel_x ].wierzcholek1,
kafle[ kafel_y * 256 + kafel_x ].wierzcholek2,
kafle[ kafel_y * 256 + kafel_x ].wierzcholek3,
kafle[ kafel_y * 256 + kafel_x ].wierzcholek4 );
}
else
{
rysuj_kwadrat( kafle[ kafel_y * 256 + kafel_x ].wierzcholek1,
kafle[ kafel_y * 256 + kafel_x ].wierzcholek2,
kafle[ kafel_y * 256 + kafel_x ].wierzcholek3,
kafle[ kafel_y * 256 + kafel_x ].wierzcholek4 );
}
}
}
Powodzenia.
Pozdrawiam, Krzysztof.

10
Grafika 2D / Odp: Jak wygenerować heightmap z *.jpg ?
« dnia: Marzec 07, 2016, 06:38:19 »
Witam,
Cytuj
Jak wygenerować mapę heightmap.raw z pliku *.jpg?
Chodzi o program, który będzie wykonywał taką opearację?
Na podstawie zamieszczonego obrazka to, ostrzegam, dużo matmy przed Tobą, albo przeszukiwania zasobów internetu, ponieważ:

Jeśli ten zrzut ekranu zapisany jest do JPEG'u, to muszę zasmucić, pewne detale są usuwane podczas kwantyzacji, więc lepiej by było gdyby zrzut zapisany był w formacie BMP lub PPM czy nawet PGM, bo potrzebujesz tylko jednego kanału do obliczeń ( o ile obraz jest czarnobiały, jeśli kolorowy to trzeba zastowować desaturacje ).

Część matematyczna:
Każda wartość szarości obrazu to informacja mówiąca o tym pod jakim kątem pada światło:
  • dla kąta 45 stopni piksel będzie biały, a przy 90 i 0 (choć zapewne mapa nie przedstawia bieguna) będzie szary (wartość bajtu równa 128; w miejscu jakiejś kotliny, czy zasłonięty jakąś skałą: też będzie szary mogąc tworzyć nieścisłości w generowanej heightmapie), czyli wartość rosnąca od 128 do 255 będzie mieściła się pomiędzy 0-45 stopni i 90-45 (nie na odwrót!), załóżmy, że płaszczyzna/równina to wartość 192 czyli 22,5 stopnia (w rzeczywistości na załączonym obrazku może być inaczej sprawdź jaka jest wartość piksela po prawej stronie na czymś co zdaje się być jeziorem), więc wszystko powyżej 192 (wartość bajtu/szarości) będzie rosło/wznosiło się, a poniżej malało/obniżało się (wzgędem źródła światła);
  • więc z tą informacją dla każdego elementu (piksela) można określić czy w danym miejscu stok wgórza/góry się wznosi, lub opada;
  • przy czym trzeba pamiętać, że:
    • dwa sąsiednie piksele o takiej samej wartość szarości, niebędącej wartością 192, dają ten sam interwał wysokości (powiedzmy 10 metrów);
    • a także o tym, że wzniesienie może być strome ( czyli większej niż 45 stopni wartości kąta padania światła ) co może spowodować obliczanie spatku gdy powinno być obliczane wzniesienie;
Do obliczenia heightmapy należy stworzyć bufor o wielkość [szerokość * wysokość * detale], gdzie detale to liczba bajtów na element heightmapy (jeśli program ma możliwość wykorzystania więcej niż jednego bajtu na element tworzonej wizualizacji to lepsze będą generowane detale), w przedstawionym przykładzie interwał ( wysokości pomiędzy dwoma obliczanymi elementami ) dla kąta względem światła 22,5 stopnia to sinus z 0 stopni, a dla kąta 45 to sinus z 45, resztę należy obliczyć poprzez odpowiednią, interpolację.
Na podstawie każdego piksela obrazu i jego różnicy z sąsiednim(i) należałoby ustalić/obliczyć interwał i obliczyć jego wartość względem najniższego lub najwyższego elementu i przemnożyć go przez ułamek detalu, a następnie wpisać w bufor, najprawdopodobniej będzie trzeba to wykonać w dwóch krokach:
  • najpierw wypełnić bufor interwałami i zbierać/obliczać wysokość najwyższego i najniższego elementu, a także punktu początkowego względem najniższego ( lub najwyższego ):
(pseudo c/c++)
// funkcja fn_calc_interval oblicza insterwał na podstawie kąta padania światła
// powinna brać kilka sąsiadujących pikseli do interpolacji
// ale w tym momęcie jesteśmy na pierwszym,
// a wartość '0' względem powyższych założeń nie jest wykorzystywana
float interval_value = fn_calc_interval_in_ref_to_light( buffer_of_pixels[ 0 ], 0 );
buffer_of_intervals[ 0 ] = interval_value;
float cur_height = interval_value;
first_point_height = cur_height;
min_height = cur_height;
max_height = cur_height;
char last_pix_grey_val = buffer_of_pixels[ 0 ];
for( int currpix = 1; currpix < all_the_rest; currpix++ )
{
char cur_pix_grey_val = buffer_of_pixels[ currpix ]
if( cur_pix_grey_val == 192 )
{
buffer_of_intervals[ currpix ] = 0;
if( buffer_of_intervals[ currpix - 1 ] != buffer_of_intervals[ currpix ] )
{
float interval_value = fn_calc_interval_in_ref_to_light
// funkcja oblicza insterwał na podstawie kąta światła
// powinna brać kilka sąsiadujących pikseli do interpolacji
( buffer_of_pixels[ currpix ], buffer_of_pixels[ currpix  - 1 ] );
cur_height += interval_value ;
}
}
else
{
if( cur_pix_grey_val == last_pix_grey_val )
{
// z nadzieją, że jesteśmy w tej samej lini cachu co poprzedni interwał
// (zaoszczędzimi trochę czasu wykonywania)
buffer_of_intervals[ currpix ] = buffer_of_intervals[ currpix - 1 ];
cur_height += buffer_of_intervals[ currpix - 1 ];
}
else
{
float interval_value = fn_calc_interval_in_ref_to_light
( buffer_of_pixels[ currpix ], buffer_of_pixels[ currpix - 1 ] );
buffer_of_intervals[ currpix ] = interval_value;
cur_height += interval_value;
}
}
if( cur_height < min_height )
{
first_point_height += min_height - cur_height;
min_height = cur_height;
}
if( cur_height > max_height )
max_height = cur_height;
last_pix_grey_val = cur_pix_grey_val;
}
  • następnie znając wysokość pierwszego, najwyższego i najniższego z punktów obliczyć pozostałe:
(pseudo c/c++)
// detal o wielkości dwóch bajtów na element ( 0 - 65535 )
// fraction to wspominany powyżej ułamek detalu
float fraction = 65535.0f / ( max_height - min_height );
float height_value = first_point_height - min_height;
unsigned short height_detail = ( unsigned short )( height_value * fraction );
output_buffer[ 0 ] = height_detail;
for( int currpix = 1; currpix < all_the_rest; currpix++ )
{
height_value += buffer_of_intervals[ currpix ];
height_detail = ( unsigned short )( height_value * fraction );
output_buffer[ currpix ] = height_detail;
}
Przy czym kąt padania światła trzeba obliczyć na podstawie 3 wymiarów nie dwóch ( tak jak obliczane są bumpmapy ), jeśli założy się, że na obrazie pozycja wektora światła to po 45 stopni w płaszczyznach 'x' i 'y', czyli promień na nim nadchodzi z równej odlegości z 'zachodu' jak i z 'północy', ale już 'z' ma jakieś 50 stopni, więc trzeba innaczej to obliczać.
Mam nadzięję, że to coś pomoże i napisałem w miarę po polsku, lub ktoś przełorzy moją wypowiedź na polski, zbyt skomplikowanie myślę ( taka przypadłość... )...
Albo też mogę się mylić.
Choć po tych kilku godzinach nie myślenia o tym problemie, to zdaje mi się, że nie potrzebnie tak się rozpisywałem na temat światła (wspominałem już, że światło wpadające do atmosfery ulega załamaniu?)...
Pozdrawiam Krzysztof.

Strony: [1]