Rozwiązanie za pomocą "Skirt"
Skirt (z ang. spódniczka), służy dodaniu dodatkowej "spódniczki" do każdego z sektorów. Jest to rozwiązanie o tyle dobre, bo nie przy stwarza dużych problemów przy implementacji i w miarę dobrze spełnia swoje zadanie.
Dobra to jak taka spódniczka wygląda ? Na rysunku zaznaczone to jest na czerwono :
Co musimy zrobić by dodać tę dodatkową warstwę sektorów ?
Dla skrajnych wierzchołków sektora (leżących na bokach sektora) można dodać dodatkową warstwę wierzchołków o współrzędnych takich samych jak wierzchołki podstawowe, lecz opuszczone o jakąś konkretną wartość (np o długość boku sektora, chodź w praktyce potrzeba dużo mniejszych wartości).
Gdy dodamy te dodatkowe wierzchołki wystarczy obliczyć jeszcze texCoord' y i odpowiednio połączyć w trójkąty. Tego pomysłu nie mam zaimplementowanego u siebie.
Rozwiązanie za pomocą dodatkowego indeksowania
W tym rozwiązaniu zakłada się, że poziom szczegółowości nie może różnić się więcej niż jeden. Tutaj zamiast jednego zestawu IB tworzy się 16 zestawów index bufferów (tak naprawdę można chyba 8 ale nie będę się zagłębiać dalej). Po co ? Przygotowuje się wszystkie podzbiory krawędzi tak by pasowały do poziomu szczegółowości sąsiada. Lecz by można było zastosować to rozwiązanie musimy lekko zmodyfikować indexowanie trójkątów. Teraz sektor mający wszystkich sąsiadów o takim poziomie szczegółowości będzie wyglądać tak :
(Indeksowanie w kwadracie czerwonym jest identyczne jak było)
Widać jak zmieniło się indeksowanie trójkątów skrajnych (takich, które łączą się z innymi sektorami)
A jak będzie to wyglądać dla sektorów gdzie jakiś sąsiad ma inny poziom szczegółowości ?(pamiętajmy o założeniu, że nie będzie to poziom większy niż jeden). Rysunek poniżej (pokazuje nam sytuację, gdzie każdy sąsiad ma inny poziom szczegółowości) :
Widzimy w tej sytuacji, że indeksowanie tego sektora wpasuje się nam do innego sektora, rozwiązując tym samym nasz problem, mianowicie usunie dziury w geometrii.
Jak to zaimplementować ? Cóż, moja implementacji do najładniejszych nie należy. Nie będę też przedstawiać tu kodu bo aktualny stan silnika będzie można pobrać w linku na samym dole. Chciałbym jedynie tutaj przedstawić zarys mojego rozwiązania. Obliczam sobie krawędź "normalną" tzn taką, która ma sąsiada o tym samym poziomie i krawędź "dodatkową', która ma sąsiada o innym poziomie szczegółowości. Nie muszę obliczać 4 takich krawędzi "normalnych" i "dodatkowych". Wystarczy 2, jedna obliczająca krawędź w poziomie a druga w pionie, a kolejne 2 oblicze z niedużych przekształceń tych 2 obliczonych wcześniej(tak naprawdę wystarczy jedna krawędź, ale wtedy jest więcej kombinowania, jakie wartości dodać, by otrzymać wszystkie cztery wartości). Każda z czterech krawędzie jest tak ponumerowana :
Jak to zaimplementować ? Cóż, moja implementacji do najładniejszych nie należy. Nie będę też przedstawiać tu kodu bo aktualny stan silnika będzie można pobrać w linku na samym dole. Chciałbym jedynie tutaj przedstawić zarys mojego rozwiązania. Obliczam sobie krawędź "normalną" tzn taką, która ma sąsiada o tym samym poziomie i krawędź "dodatkową', która ma sąsiada o innym poziomie szczegółowości. Nie muszę obliczać 4 takich krawędzi "normalnych" i "dodatkowych". Wystarczy 2, jedna obliczająca krawędź w poziomie a druga w pionie, a kolejne 2 oblicze z niedużych przekształceń tych 2 obliczonych wcześniej(tak naprawdę wystarczy jedna krawędź, ale wtedy jest więcej kombinowania, jakie wartości dodać, by otrzymać wszystkie cztery wartości). Każda z czterech krawędzie jest tak ponumerowana :
Teraz wystarczy przejść przez wszystkie podzbiory krawędzi. Jak efektywnie wygenerować podzbiory ?
Przedstawię pomysł, który pokazał mi Pan Marcin Andrychowicz na kółku informatycznym :
for(int i = 0; i < (1 << n) - 1; ++i){
for(int j = 0; j < n; ++j)
if(i & j) cout << j << " ";
cout << j;
}
Jak to działa ? najpierw idzie pętla przez wszystkie podzbiory (lekkie przypomnienie z prawdopodobieństwa jak to policzyć). Ilość podzbiorów równa się 2 ^ n - 1. Potem idziemy przez wszystkie elementy, by sprawdzić, jakie elementy należą do aktualnie liczonego podzbioru. Warunek if(i & j) sprawdza czy element j należy do podzbioru i. Ale dlaczego to działa ? Operator & to zwykłe mnożenie bitowe. W naszym przykładzie warunek jest prawdziwy jeżeli i - ty bit jest równy jeden. Jeżeli nie bardzo rozumiesz rozpisz na kartce to powinieneś zrozumieć o co chodzi. Teraz wiedząc już jak wygenerować podzbiory przechodzimy do obliczania wszystkich IB.
Jak pokazałem wyżej puszczamy pętle przez wszystkie podzbiory (trzeba tylko delikatnie pozmieniać wartości w pętli bo trzeba zauważyć, że może występować taki index buffer, który nie będzie miał żadnej krawędzi "dodatkowej", dzieje się to przy sąsiadach o takim samym poziomie szczegółowości, a podzbiór nie może być przecież zerowy). U nas moc podzbioru oczywiście wynosi 4 bo mamy 4 krawędzie, numerowane :0, 1, 2, 3 jak pokazałem wyżej na rysunku. Wystarczy teraz dla i - tego podzbioru sprawdzić, które krawędzie w sobie zawiera, właśnie ten warunek to robi if(i & j) i dodać odpowiednie index' y i na koniec wygenerować wszystkie IB. No i na koniec renderując dany sektor musimy obliczyć, który zestaw index buffera wybrać. Robię to tak :
int EvoHeightMap::GetGeoDetail(int i, int j){
int result = 0;
if(i > 0 && geoMap[i][j] < geoMap[i - 1][j]) result |= 4;
if(j > 0 && geoMap[i][j] < geoMap[i][j - 1]) result |= 2;
if(i < (numOfSectors - 1) && geoMap[i][j] < geoMap[i + 1][j]) result |= 1;
if(j < (numOfSectors - 1) && geoMap[i][j] < geoMap[i][j + 1]) result |= 8;
return result;
}
int result = 0;
if(i > 0 && geoMap[i][j] < geoMap[i - 1][j]) result |= 4;
if(j > 0 && geoMap[i][j] < geoMap[i][j - 1]) result |= 2;
if(i < (numOfSectors - 1) && geoMap[i][j] < geoMap[i + 1][j]) result |= 1;
if(j < (numOfSectors - 1) && geoMap[i][j] < geoMap[i][j + 1]) result |= 8;
return result;
}
geoMap - to tablica 2 - wymiarowa zawierająca jaki jest poziom geomipmappingu dla sektora [i][j].
Teraz nie ma już żadnych dziur :
Aktualny kod silnika do pobrania TUTAJ.
Pozdrawiam Norbert G.