Skocz do zawartości

Processing. Obiekty trójwymiarowe dla średniozaawansowanych - #4


Adder

Pomocna odpowiedź

Część Czwarta

W poprzedniej części omówione zostało wypełnianie obiektów trójwymiarowych teksturą oraz przedstawione wybrane funkcje światła. W ostatniej już, czwartej części, program uzupełnimy o funkcje umożliwiające obejrzenie całego modelu ze wszystkich stron przy wykorzystaniu komputerowej myszki. 

Obrotu widoku sceny można dokonać na 2 podstawowe sposoby:

1.     bezpośrednie przekształcenia obiektów w przestrzeni trójwymiarowej;

2.     zmianę położenia obserwatora (kamery) patrzącego na nieruchome obiekty.

Do programu zaimplementowane zostały obydwa sposoby. Przy czym, transformacja obiektów została wprowadzona w wersji: (1) - „dla wymagających”. Natomiast, przesunięcia kamery w wersji (2) - „dla leniwych”, przy wykorzystaniu biblioteki zewnętrznej. Bez względu na wybraną metodę końcowy efekt będzie podobny, ale nie identyczny.

PMatrix3D, PVector

W pierwszej kolejności zajmiemy się „wersją dla wymagających”. Naszym celem jest wyposażenie programu w możliwość jednoczesnego obracania wszystkich elementów znajdujących się na scenie wokół dowolnej osi i o dowolny kąt za pośrednictwem myszki. Innymi słowy, chcemy dokonywać transformacji położenia wszystkich obiektów razem, względem nieruchomego obserwatora zależnie od położenia kursora myszki. 

Rozwiązania intuicyjne tego zadania nie zawsze prowadzą do oczekiwanych wyników. Najczęściej, o ile nie zostaną zastosowane restrykcyjne reguły transformacji, układ Ziemia-Słońce będzie zmieniał swoje położenie z trudnymi do okiełznania aberracjami… 

Na szczęście teorię przekształceń w przestrzeni 3D można znaleźć w każdym akademickim podręczniku geometrii lub ją sobie wymyślić i tym samym nie ma potrzeby jej tutaj przytaczać.

Bardziej zainteresowanych tematem z przyjemnością odsyłam do artykułu:  3D Rotations in Processing (Vectors, Matrices, Quaternions) , można w nim znaleźć dość przejrzyście wyjaśnioną teorię stosowaną do przesunięć w przestrzeni 3D, wraz z propozycjami jej implementacji. 

Tutaj zajmiemy się wyłącznie zastosowaniami praktycznymi. Krótki, prosty i co najważniejsze efektywny algorytm obrotu przestrzeni 3D o zadany wektor jest następujący:

Krok 1 Zainicjowanie macierzy współrzędnych jednorodnych (MATRIX):

Inicjalizacja macierzy będzie polegała na przypisaniu macierzy współrzędnej  czterowymiarowej macierzy jednostkowej:

PMatrix3D MATRIX=new PMatrix3D    // Inicjalizacja obiektu MATRIX klasy PMatrix3D (macierz przestrzeni 3D w ujęciu jednorodnym)
 (1,0,0,0,
  0,1,0,0,
  0,0,1,0,
  0,0,0,1);    

Wykorzystana została do tego nieudokumentowana klasa PMatrix3D (próżno, szukać więcej informacji o niej na stronie processing.org) lakoniczny opis znajdziemy pod adresem: Class PMatrix3D

Funkcja new w Processing służy do tworzenia nowego obiektu zadanej klasy. Utworzoną macierz można rozumieć jako macierz przekształceń przestrzeni. Przekształcenia te wkrótce nastąpią. 

Krok 2 Zainicjowanie wektora obrotu (rotationVector)

Wektor będzie przechowywał informacje o przesunięciach wskaźnika myszki w płaszczyźnie ekranu X, Y. Świadomie pomijamy tu wartość Z. Nie będzie ona na razie potrzebna i przypisujemy jej na stałe wartość 0.  

PVector rotationVector=new PVector (0,0,0);                       // Inicjalizacja obiektu rotationVector klasy PVector (wektor obrotu)

PVector jest klasą opisującą dwu lub trójwymiarowy wektor geometryczny znany ze szkoły podstawowej posiadający moduł i kierunek. Podobnie jak obiekt klasy PMatrix3D należy go utworzyć przy wykorzystaniu funkcji new. 

Krok 3 Aktualizacja wektora obrotu(rotationVector)

 

Wartość X wektora zastąpiona zostaje zmianą położenia myszki wzdłuż osi X

Wartość Y wektora zastąpiona zostaje zmianą położenia myszki wzdłuż osi Y

Do aktualizacji wektora rotationVector posłuży nam przerwanie obsługujące przeciągnięcie myszki wywoływane funkcją mouseDragged(). Processing przechowuje dwie pomocne zmienne systemowe mouseX oraz pmouseX reprezentujące odpowiednio bieżące oraz poprzednie położenie wskaźnika myszki. Podobnie dzieje się w przypadku współrzędnej Y gdzie zmienne przyjmują oznaczenie mouseY i pmouseY.

W naszym przypadku funkcja ma następującą postać:

void mouseDragged() {                  // Funkcja obsługująca przerwanie wywołane przeciągnięciem myszki z wciśniętym przyciskiem  
  rotationVector.x=(pmouseY-mouseY);   // Przypisanie współrzędnej X wektora obrotu wartości przesunięcia myszki wzdłuż osi X
  rotationVector.y=(mouseX-pmouseX);   // Przypisanie współrzędnej X wektora obrotu wartości przesunięcia myszki wzdłuż osi Y 
}

Krok 4 Transpozycja macierzy (MATRIX)

Zamiana wierszy na kolumny i kolumn na wiersze metodą transpose() klasy PMatrix3D:

MATRIX.transpose();                                               // Transpozycja macierzy 3D

Krok 5 Przemnożenie macierzy (MATRIX) przez wektor obrotu (rotationVector)

W wyniku mnożenia otrzymujemy nowy, zmodyfikowany wektor obrotu (rotationVector). 

MATRIX.mult(rotationVector,rotationVector);  // Aktualizacja wartości wektora obrotu poprzez przemnożenie go przez macierz przestrzeni

Krok 6 Ponowna transpozycja macierzy (MATRIX)

Przywrócenie macierzy do pierwotnej formy. Ponieważ pierwsza transpozycja służyła nam wyłącznie do wykonania mnożenia macierzy przez wektor jeszcze raz zamieniamy jej kolumny z wierszami i wiersze z kolumnami. 

Krok 7 Obrót macierzy współrzędnych (MATRIX) o zadany kąt wokół wektora powstałego ze współrzędnych X,Y,Z wektora obrotu (rotationVector)

 

Kąt ustalony jest arbitralnie (np. 2PI/360*5).  Determinuje skalę obrotu w reakcji na ruch myszką. W wyniku obrotu zawartość MATRIX aktualizuje się o nowe wartości.

MATRIX.rotate(TWO_PI/360*6,rotationVector.x,rotationVector.y,rotationVector.z);      // Obrót macierzy o kąt 6 stopni wokół zmodyfikowanego wektora x,y,z

Kąt obrotu będzie decydował o „czułości” myszki. Im większe będzie jej przesunięcie, tym większa będzie jednorazowa skala obrotu obiektu. 

Krok 8 Transformacja bieżącego układu współrzędnych macierzą MATRIX

Wykonujemy mnożenie bieżącego układu współrzędnych (bieżącej macierzy) przez macierz transformacji (MATRIX). Jak ostrzega Processing funkcja jest bardzo powolna, ponieważ próbuje obliczyć odwrotność transformacji. Dla naszego programu jest jednak wystarczająco szybka, teoretycznie można ją też sobie samemu napisać, ale oczywiście zajmie to kolejne linijki kodu.  

applyMatrix(MATRIX);                            // Zastąpienie bieżącej przestrzeni 3D przestrzenią przekształconą 3D

Krok 9 Reset wektora obrotu

Przywrócenie wektora do pierwotnej postaci 0,0,0:

rotationVector.set(0,0,0);                                        // Reset wektora obrotu

Na tym kończy się algorytm. Oczywiście istnieje wiele różnych implementacji teorii obrotów w praktyce programowania. Ta akurat jest własną metodą autorską.  Proponowane, dostępne w sieci, implementacje są zawsze(nie do końca wiadomo dlaczego) dużo bardziej skomplikowane i często nieczytelne. 

Po zastosowaniu obrotów program przyjmuje następującą formę:

PMatrix3D MATRIX=new PMatrix3D                                    // Inicjalizacja obiektu MATRIX klasy PMatrix3D (macierz przestrzeni 3D w ujęciu jednorodnym)
 (1,0,0,0,
  0,1,0,0,
  0,0,1,0,
  0,0,0,1);                                                         
 
 PVector rotationVector= new PVector (0,0,0);                       // Inicjalizacja obiektu rotationVector klasy PVector (wektor obrotu)
 
 int EARTH_RADIUS=150;                                              // Promień Ziemi
 int SUN_RADIUS=10;                                                 // Promień Słońca
 int SUN_ORBIT=300;                                                 // Promień orbity Słońca;
 int CELESTIAL_SPHERE_RADIUS=4000;                                  // Promień Sfery Niebieskiej
 
 float xSol=0;                                                      // Chwilowa współrzędna x środka Śłońca 
 float ySol=0;                                                      // Chwilowa współrzędna y środka Śłońca
 float zSol=0;                                                      // Chwilowa współrzędna z środka Śłońca
 float tSol=0;                                                      // Chwilowy azymut Słońca liczony od południka 0 w radianach  
 
 PShape EARTH;                                                      // Deklaracja kształtu przechowującego informacje o kształcie Ziemi
 PImage EARTH_TEXTURE;                                              // Deklaracja obrazu przechowującego informacje o teksturze Ziemi

 PShape SUN;                                                        // Deklaracja kształtu przechowującego informacje o kształcie Słońca
 PImage SUN_TEXTURE;                                                // Deklaracja obrazu przechowującego informacje o teksturze Słońca
 
 PShape CELESTIAL_SPHERE;                                           // Deklaracja kształtu przechowującego informacje o kształcie Sfery Niebieskiej
 PImage CELESTIAL_SPHERE_TEXTURE;                                   // Deklaracja obrazu przechowującego informacje o teksturze Sfery Niebieskiej
 
void setup() {
   
  size(1800,1000,P3D);                                              // Rozmiar okna 1800/1000 pixeli. Rendering trójwymiarowy
  frameRate(30);                                                    // Ilość klatek na sekundę(ftp/s)
  background(22);                                                   // Tło
  smooth(4);                                                        // Ustawienie poziomu anty-aliasingu
 
  sphereDetail(120);                                                // Szczegółowość rysowania sfer
 
  EARTH=createShape(SPHERE,EARTH_RADIUS);
  EARTH_TEXTURE=loadImage("1_earth_8k.jpg");
  //EARTH_TEXTURE=loadImage("http://shadedrelief.com/natural3/ne3_data/8192/textures/1_earth_8k.jpg");
   
  SUN=createShape(SPHERE, SUN_RADIUS);
  SUN_TEXTURE=loadImage("320px-Map_of_the_full_sun.jpg");
  //SUN_TEXTURE=loadImage("https://upload.wikimedia.org/wikipedia/commons/thumb/9/99/Map_of_the_full_sun.jpg/320px-Map_of_the_full_sun.jpg");
  
  CELESTIAL_SPHERE=createShape(SPHERE, CELESTIAL_SPHERE_RADIUS);
  CELESTIAL_SPHERE_TEXTURE=loadImage("eso0932a.jpg");
  //CELESTIAL_SPHERE_TEXTURE=loadImage("https://cdn.eso.org/images/large/eso0932a.jpg");
  
  EARTH.setStroke(false);                                           // Brak wyświetlania obrysu
  EARTH.setTexture(EARTH_TEXTURE);                                  // Przypisanie tekstury kształtowi Ziemi
  
  SUN.setStroke(false);                                             // Brak wyświetlania obrysu
  SUN.setTexture(SUN_TEXTURE);                                      // Przypisanie tekstury kształtowi Słońca
  
  CELESTIAL_SPHERE.setStroke(false);                                // Brak wyświetlania obrysu   
  CELESTIAL_SPHERE.setTexture(CELESTIAL_SPHERE_TEXTURE);            // Przypisanie tekstury kształtowi Sfery Niebieskiej 

}

void draw() {
  
  background(22);
 
  translate(width/2,height/2,0);                                    // Przesunięcie środka układu współrzędnych do centrum ekranu/okna programu
 
  MATRIX.transpose();                                               // Transpozycja macierzy przestrzeni 3D
  MATRIX.mult(rotationVector,rotationVector);                       // Aktualizacja wartości wektora obrotu poprzez przemnożenie go przez macierz przestrzeni
  MATRIX.transpose();                                               // Ponowna transpozycja macierzy
  MATRIX.rotate(TWO_PI/360*6,rotationVector.x,rotationVector.y,rotationVector.z);      // Obrót macierzy o kąt 6 stopni wokół zmodyfikowanego wektora x,y,z
  applyMatrix(MATRIX);                                              // Zastąpienie bieżącej przestrzeni 3D przestrzenią przekształconą 3D
  
  rotationVector.set(0,0,0);                                        // Reset wektora obrotu 
  
  rotateX(TWO_PI/4);                                                // Obrót środka układu współrzędnych o 90 stopni wokół osi X
  rotateZ(TWO_PI/4);                                                // Obrót środka układu współrzędnych o 90 stopni wokół osi Z
  pushMatrix();
  rotateY(TWO_PI/12);
  rotateZ(TWO_PI/6);
  shape(CELESTIAL_SPHERE);
  popMatrix();
 
  directionalLight(255, 255, 210, -xSol, -ySol, -zSol);             // Oświetlenie Ziemi światłem jasnożółtym padającym z kierunku Słońca
   
  pushMatrix();                                                     // Wepchnięcie macierzy współrzędnych
  rotateX(-TWO_PI/4);                                               // Obrót środka układu współrzędnych o -90 stopni wokół osi X
  rotateY(PI);                                                      // Obrót środka układu współrzędnych o 180 stopni wokół osi Y
  ambientLight(28, 28, 28);                                         // Przyciemnienie Ziemi
  shape(EARTH);                                                     // Wyświetlenie obrazu Ziemi 
  lights();                                                         // Reset ustawień funkcji światła
  popMatrix();                                                      // Wypchnięcie macierzy współrzędnych 
  
  xSol=SUN_ORBIT*cos(tSol);                                         // Określenie pozycji X środka Słońca
  ySol=SUN_ORBIT*sin(tSol);                                         // Określenie pozycji Y środka Słońca
   
  tSol+=TWO_PI/360;                                                 // Ustalenie azymutu Słońca
  if (tSol>TWO_PI) tSol=0;                                          // Reset azymutu po zakreśleniu przez słońce pełnej orbity
  
  pushMatrix();                                                     // Wepchnięcie macierzy współrzędnych
  translate(xSol,ySol,zSol);                                        // Przesunięcie środka układu współrzędnych do miejsca położenia Słońca
  ambientLight(255, 255, 210);                                      // Rozświetlenie Słońca
  shape(SUN);                                                       // Wyświetlenie obrazu Słońca      
  lights();                                                         // Reset ustawień funkcji światła
  popMatrix();                                                      // Wypchnięcie macierzy współrzędnych 

}

void mouseDragged() {                                               // Funkcja obsługująca przerwanie wywołane przeciągnięciem myszki z wciśniętym klawiszem 
  
  rotationVector.x=(pmouseY-mouseY);                                // Przypisanie współrzędnej x wektora obrotu wartości przesunięcia myszki wzdłuż osi X
  rotationVector.y=(pmouseX-mouseX);                                // Przypisanie współrzędnej y wektora obrotu wartości przesunięcia myszki wzdłuż osi Y
  
}

void mouseClicked(MouseEvent event) {                                  // Funkcja obsługująca przerwanie wywołane kliknięciem myszki 
  if (event.getCount() >= 2) MATRIX.reset();                           // Przywrócenie Ziemi pierwotnego położenia po dwukrotnym kliknięciu myszką
}

Na końcu skryptu umieszczona została dodatkowo funkcja obsługująca kliknięcie myszką. Za jej pomocą można, po drobnej modyfikacji, dokonać resetu macierzy układu  w wyniku dwukrotnego kliknięcia myszką.  

Efekt przedstawia się następująco:

Do pełni efektu pozornie brakuje jeszcze obsługi ruchu układu względem osi Z np. przy wykorzystaniu ruchu kółka myszki. Tutaj jednak stajemy przed dylematem: czy obrót kółka ma dokonywać obrotu układu względem osi o kąt odpowiadający obrotowi kółka, czy też ma powodować ruch obserwatora wzdłuż osi Z. W zasadzie trzymając się założenia, że operujemy układem, a nie obserwatorem, dylemat brzmi czy ruch kółka powinien obracać układ, czy też powodować jego „puchnięcie” lub „kurczenie”. Kwestia ta pozostawiona została Czytelnikowi do samodzielnego rozstrzygnięcia i ew. implementacji. Można do tego wykorzystać funkcję obsługi przerwania: mouseWheel(). W naszym programie kółko myszki zostanie wykorzystane do innych celów, a ruch obiektu względem osi Z nie zostanie oprogramowany jako, akurat w tym przypadku, zupełnie niepotrzebny.

peasycam ver302

 

Jak zostało podkreślone na wstępie efekt ruchu (obrotu) układu w zależności od ruchu myszki można wywołać poruszając samym układem lub poruszając obserwatorem. W naszym programie zastosujemy na raz obydwie metody, czyli możliwe będzie zarówno poruszanie obiektami jak i przesuwanie obserwującej je kamerą.  

Zaczynamy od drobnej modyfikacji funkcji przeciągania myszki ograniczając obroty obiektów wyłącznie do prawego klawisza. W ten sposób pozostawiamy lewy klawisz i kółko do swobodnego wykorzystania. Możemy ich użyć na przykład do obrotów kamerą i przesuwania wzdłuż osi X. Oczywiście nie jest to rozwiązanie idealne. Obracanie obiektami prawym przyciskiem, a kamerą lewym przyciskiem może spowodować pogubienie się obserwatora w aktualnej pozycji obiektów na ekranie. Ruch kamery powinien powodować więc korekty mechaniki przesuwania obiektów, aby cały system nie stracił na intuicyjności, sporo do życzenia pozostawia także płynność odwzorowania ruchu myszki na ruch obiektu. To prawda, ale … cyzelować obroty można prawie w nieskończoność, więc w tym przypadku, z miłosierdzia dla Czytelnika, darujemy sobie dalsze, głębsze zmiany programu. 

void mouseDragged() {                    // Funkcja obsługująca przerwanie wywołane przeciągnięciem myszki z wciśniętym klawiszem 
  if (mouseButton==RIGHT){               // Ograniczenie obrotów obiektów do prawego klawisza 
  rotationVector.x=(pmouseY-mouseY);     // Przypisanie współrzędnej x wektora obrotu wartości przesunięcia myszki wzdłuż osi X
  rotationVector.y=(pmouseX-mouseX);     // Przypisanie współrzędnej y wektora obrotu wartości przesunięcia myszki wzdłuż osi Y
  }  
}

Teraz już (oddając cząstkę naszej wolności, w zamian za wygodę) możemy zaimplementować do skryptu zewnętrzną bibliotekę peaesycam. Jak twierdzi jej twórca Jonathan Feinberg biblioteka oferuje „a dead-simple” 😂 rozwiązanie problemu ruchu kamery za pomocą myszki. Mimo swojej prostoty jest bardzo użytecznym narzędziem, a co więcej nie musimy znać jego konstrukcji, aby z niego skorzystać. 

Aby użyć funkcji oferowanych przez bibliotekę dołączamy ją do skryptu, deklarujemy obiekt (camera) klasy PeasyCam:

import peasy.*;
PeasyCam camera;

, a następnie w sekcji setup()  tworzymy obiekt kamery umiejscowionej w odległości 1000 jednostek od środka układu:

camera = new PeasyCam(this, 1000);

Gotowe. Teraz już można użyć metod zdefiniowanych dla obiektów tej klasy. Ich lista jest dostępna tutaj: peasycam v302

camera.setMinimumDistance(245);
camera.setMaximumDistance(1000);
camera.lookAt(width/2, height/2, 0, 500);
camera.setRightDragHandler(null);
camera.setResetOnDoubleClick(false);

Wszystkie ustawienia kamery znajdują się w sekcji setup() skryptu. Nie ma potrzeby umieszczać żadnych komend w sekcji draw().

Ponieważ autor biblioteki zadbał o to, żeby wszystkie nazwy metod były równie śmiertelnie proste, co sama biblioteka, chyba zbędne jest ich omawianie. W skrócie, ustawione zostały kolejno: minimalna i maksymalna odległość kamery od obiektu, punkt na który „patrzy” kamera (pamiętajmy o translacji układu!), wyłączenie prawego przycisku myszy (używany jest do obrotów obiektu, wyłączenie resetu kamery (dwukrotne kliknięcie, jak pamiętamy ma resetować obrót obiektu, a nie kamery).

Ostatnią rzeczą jaką musimy zrobić to dodać w funkcji obsługującej dwukrotne kliknięcie myszką reset kamery i korektę współrzędnych obiektu, na który patrzy. 

camera.reset();
camera.lookAt(width/2, height/2, 0, 500);

To wszystko. Zbudowaliśmy trójwymiarowy, geocentryczny model układu Ziemia-Słońce. Nadaliśmy mu tekstury, tło i światło. Wprawiliśmy układ w ruch, a następnie umożliwiliśmy jego obserwację pod dowolnym kątem na dwa niezależnie działające sposoby.  

Całkiem sporo jak na 100 linijek kodu. 

Działający skończony program prezentuje się następująco:

Dla zwiększenia czytelności położenia obiektów dodane zostały elipsy i odcinki pozwalające na lepszą orientację w położeniu Ziemi oraz opisy którym przyciskiem myszy przeciągany jest obraz. Samodzielne dodanie tych elementów nie powinno już w tym miejscu nikomu sprawić trudności. Służą do tego komendy ellipse() i line().

Kod programu:

import peasy.*;
PeasyCam camera;
 
 PMatrix3D MATRIX=new PMatrix3D                                    // Inicjalizacja obiektu MATRIX klasy PMatrix3D (macierz przestrzeni 3D w ujęciu jednorodnym)
 (1,0,0,0,
  0,1,0,0,
  0,0,1,0,
  0,0,0,1);                                                         
 
 PVector rotationVector= new PVector (0,0,0);                       // Inicjalizacja obiektu rotationVector klasy PVector (wektor obrotu)
 
 int EARTH_RADIUS=150;                                              // Promień Ziemi
 int SUN_RADIUS=10;                                                 // Promień Słońca
 int SUN_ORBIT=300;                                                 // Promień orbity Słońca;
 int CELESTIAL_SPHERE_RADIUS=4000;                                  // Promień Sfery Niebieskiej
 
 float xSol=0;                                                      // Chwilowa współrzędna x środka Śłońca 
 float ySol=0;                                                      // Chwilowa współrzędna y środka Śłońca
 float zSol=0;                                                      // Chwilowa współrzędna z środka Śłońca
 float tSol=0;                                                      // Chwilowy azymut Słońca liczony od południka 0 w radianach  
 
 PShape EARTH;                                                      // Deklaracja kształtu przechowującego informacje o kształcie Ziemi
 PImage EARTH_TEXTURE;                                              // Deklaracja obrazu przechowującego informacje o teksturze Ziemi

 PShape SUN;                                                        // Deklaracja kształtu przechowującego informacje o kształcie Słońca
 PImage SUN_TEXTURE;                                                // Deklaracja obrazu przechowującego informacje o teksturze Słońca
 
 PShape CELESTIAL_SPHERE;                                           // Deklaracja kształtu przechowującego informacje o kształcie Sfery Niebieskiej
 PImage CELESTIAL_SPHERE_TEXTURE;                                   // Deklaracja obrazu przechowującego informacje o teksturze Sfery Niebieskiej
 
void setup() {
   
  size(1800,1000,P3D);                                              // Rozmiar okna 1800/1000 pixeli. Rendering trójwymiarowy
  frameRate(30);                                                    // Ilość klatek na sekundę(ftp/s)
  background(22);                                                   // Tło
  smooth(4);                                                        // Ustawienie poziomu anty-aliasingu
 
  sphereDetail(120);                                                // Szczegółowość rysowania sfer
 
  EARTH=createShape(SPHERE,EARTH_RADIUS);
  EARTH_TEXTURE=loadImage("1_earth_8k.jpg");
  //EARTH_TEXTURE=loadImage("http://shadedrelief.com/natural3/ne3_data/8192/textures/1_earth_8k.jpg");
   
  SUN=createShape(SPHERE, SUN_RADIUS);
  SUN_TEXTURE=loadImage("320px-Map_of_the_full_sun.jpg");
  //SUN_TEXTURE=loadImage("https://upload.wikimedia.org/wikipedia/commons/thumb/9/99/Map_of_the_full_sun.jpg/320px-Map_of_the_full_sun.jpg");
  
  CELESTIAL_SPHERE=createShape(SPHERE, CELESTIAL_SPHERE_RADIUS);
  CELESTIAL_SPHERE_TEXTURE=loadImage("eso0932a.jpg");
  //CELESTIAL_SPHERE_TEXTURE=loadImage("https://cdn.eso.org/images/large/eso0932a.jpg");
  
  EARTH.setStroke(false);                                           // Brak wyświetlania obrysu
  EARTH.setTexture(EARTH_TEXTURE);                                  // Przypisanie tekstury kształtowi Ziemi
  
  SUN.setStroke(false);                                             // Brak wyświetlania obrysu
  SUN.setTexture(SUN_TEXTURE);                                      // Przypisanie tekstury kształtowi Słońca
  
  CELESTIAL_SPHERE.setStroke(false);                                // Brak wyświetlania obrysu   
  CELESTIAL_SPHERE.setTexture(CELESTIAL_SPHERE_TEXTURE);            // Przypisanie tekstury kształtowi Sfery Niebieskiej 

  camera = new PeasyCam(this, 1000);
  
  camera.setMinimumDistance(245);
  camera.setMaximumDistance(1000);
  camera.lookAt(width/2, height/2, 0, 500);
  camera.setRightDragHandler(null);
  camera.setResetOnDoubleClick(false);
}

void draw() {
  
  background(22);
 
  translate(width/2,height/2,0);                                    // Przesunięcie środka układu współrzędnych do centrum ekranu/okna programu

  MATRIX.transpose();                                               // Transpozycja macierzy przestrzeni 3D
  MATRIX.mult(rotationVector,rotationVector);                       // Aktualizacja wartości wektora obrotu poprzez przemnożenie go przez macierz przestrzeni
  MATRIX.transpose();                                               // Ponowna transpozycja macierzy
  MATRIX.rotate(TWO_PI/360*6,rotationVector.x,rotationVector.y,rotationVector.z);      // Obrót macierzy o kąt 6 stopni wokół zmodyfikowanego wektora x,y,z
  applyMatrix(MATRIX);                                              // Zastąpienie bieżącej przestrzeni 3D przestrzenią przekształconą 3D
  
  rotationVector.set(0,0,0);                                        // Reset wektora obrotu 
  
  rotateX(TWO_PI/4);                                                // Obrót środka układu współrzędnych o 90 stopni wokół osi X
  rotateZ(TWO_PI/4);                                                // Obrót środka układu współrzędnych o 90 stopni wokół osi Z
  pushMatrix();
  rotateY(TWO_PI/12);
  rotateZ(TWO_PI/6);
  shape(CELESTIAL_SPHERE);
  popMatrix();
 
  directionalLight(255, 255, 210, -xSol, -ySol, -zSol);             // Oświetlenie Ziemi światłem jasnożółtym padającym z kierunku Słońca
   
  pushMatrix();                                                     // Wepchnięcie macierzy współrzędnych
  rotateX(-TWO_PI/4);                                               // Obrót środka układu współrzędnych o -90 stopni wokół osi X
  rotateY(PI);                                                      // Obrót środka układu współrzędnych o 180 stopni wokół osi Y
  ambientLight(28, 28, 28);                                         // Przyciemnienie Ziemi
  shape(EARTH);                                                     // Wyświetlenie obrazu Ziemi 
  lights();                                                         // Reset ustawień funkcji światła
  popMatrix();                                                      // Wypchnięcie macierzy współrzędnych 
  
  xSol=SUN_ORBIT*cos(tSol);                                         // Określenie pozycji X środka Słońca
  ySol=SUN_ORBIT*sin(tSol);                                         // Określenie pozycji Y środka Słońca
   
  tSol+=TWO_PI/360;                                                 // Ustalenie azymutu Słońca
  if (tSol>TWO_PI) tSol=0;                                          // Reset azymutu po zakreśleniu przez słońce pełnej orbity
  
  pushMatrix();                                                     // Wepchnięcie macierzy współrzędnych
  translate(xSol,ySol,zSol);                                        // Przesunięcie środka układu współrzędnych do miejsca położenia Słońca
  ambientLight(255, 255, 210);                                      // Rozświetlenie Słońca
  shape(SUN);                                                       // Wyświetlenie obrazu Słońca      
  lights();                                                         // Reset ustawień funkcji światła
  popMatrix();                                                      // Wypchnięcie macierzy współrzędnych 

}

void mouseDragged() {                                               // Funkcja obsługująca przerwanie wywołane przeciągnięciem myszki z wciśniętym klawiszem 
  if (mouseButton==RIGHT){                                          // Ograniczenie obrotów obiektów do prawego klawisza 
  rotationVector.x=(pmouseY-mouseY);                                // Przypisanie współrzędnej x wektora obrotu wartości przesunięcia myszki wzdłuż osi X
  rotationVector.y=(pmouseX-mouseX);                                // Przypisanie współrzędnej y wektora obrotu wartości przesunięcia myszki wzdłuż osi Y
}  
}

void mouseClicked(MouseEvent event) {                                  // Funkcja obsługująca przerwanie wywołane kliknięciem myszki 
  if (event.getCount() == 2) {
    MATRIX.reset();                           // Przywrócenie Ziemi pierwotnego położenia po dwukrotnym kliknięciu myszką
    camera.reset();
    camera.lookAt(width/2, height/2, 0, 500);
    
}   
}

Od Autora

Dziękuję wszystkim, którzy dotarli aż do tego miejsca. Jestem pod wrażeniem 🙂  Mam nadzieję, że temat okazał się ciekawy. Przepraszam przy okazji za wszystkie niezręczności w tekście, są praktycznie nieuniknione. Pojawia się pytanie co dalej? Tutaj ograniczeniem jest tylko pojemność kory mózgowej. Można dalej poprawiać program (jest na pewno wieele do ulepszenia, choć przyznam, że jest to najczystszy kod jaki mi osobiście kiedykolwiek udało się napisać, niestety zwykle przypomina śmietnik), można podjąć próbę zasymulowania innego układu po swojemu, można też dodać kolejne obiekty, wyposażyć w działka laserowe, oprogramować kolizje (w sumie ciekawy temat na ew. nowe artykuły) i zaprosić znajomych do własnej kosmicznej strzelaniny (biblioteka: network) w stylu "World of..", a potem ładne to wszystko ubrać i zarobić swój pierwszy 1 000 000$🤔  Czego wszystkim Czytelnikom  FORBOTa serdecznie życzę! 😃😃😃

  • Lubię! 2
Link do komentarza
Share on other sites

Bądź aktywny - zaloguj się lub utwórz konto!

Tylko zarejestrowani użytkownicy mogą komentować zawartość tej strony

Utwórz konto w ~20 sekund!

Zarejestruj nowe konto, to proste!

Zarejestruj się »

Zaloguj się

Posiadasz własne konto? Użyj go!

Zaloguj się »
×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.