//---------------------------------------------------------------------------

#pragma hdrstop
#include "GLForm.h"

//---------------------------------------------------------------------------
#pragma package(smart_init)

//---------------------------------------------------------------------------
//                    KONSTRUKTOR I DESTRUKTOR                             //
//---------------------------------------------------------------------------

#include <Dialogs.hpp>

__fastcall TGLForm::TGLForm(TComponent* Owner)
   :TForm(Owner),
   debug_mode(false),
   KameraKontrolaMysza(true),
   X0(0),Y0(0),
	KameraR(10.0f),KameraPhi(0.0f),KameraTheta(0.0f),
   KameraX(0.0f),KameraY(0.0f),
   SzybkoscX0(0),SzybkoscY0(0),
	SzybkoscPhi(0.0f), SzybkoscTheta(0.0f),
   SzybkoscWlaczone(true),
   SzybkoscWygaszania(0.3f),
   natezenie_swiatla_otoczenia(0.3f),
   maksymalizacjaOkna(false),
   pudlo(false),pudlo_kolor(clBlue),
   rysowanieOsi(false),
   teksturowanie(true)
{
   //zmiana wlasnosci okna
   //BorderStyle=bsSingle;
   //TBorderIcons ikonyFormy=BorderIcons;
   //ikonyFormy >> biMaximize;
   //BorderIcons=ikonyFormy;

   //tworzenie timera odpowiedzialnego za swobodne obroty
	SzybkoscTimer=new TTimer(this);
	SzybkoscTimer->Enabled=false;
	SzybkoscTimer->Interval=10;
	SzybkoscTimer->OnTimer=SzybkoscTimerTimer;

   //biezace okno staje sie oknem OpenGL
   uchwytDC=GetDC(Handle);
   if (!GL_UstalFormatPikseli(uchwytDC)) ShowMessage("Nie udało się ustalić formatu pikseli");
   uchwytRC=wglCreateContext(uchwytDC);
   if (uchwytRC==NULL) ShowMessage("Nie udało się pobrać uchwytu kontekstu grafiki");
   if (!wglMakeCurrent(uchwytDC,uchwytRC)) ShowMessage("Inicjacja grafiki OpenGL nie powiodła się");
   GL_UstawienieSceny();

   Caption=(AnsiString)"GLForm, OpenGL "+(char*)glGetString(GL_VERSION);
}

__fastcall TGLForm::~TGLForm()
{
   wglMakeCurrent(NULL,NULL);
   wglDeleteContext(uchwytRC);
   ReleaseDC(Handle,uchwytDC);
   PostQuitMessage(0);
}

//---------------------------------------------------------------------------
//                         OBSLUGA OKNA OPENGL                             //
//---------------------------------------------------------------------------

bool TGLForm::GL_UstalFormatPikseli(HDC uchwytDC)
{
   PIXELFORMATDESCRIPTOR opisFormatuPikseli;
   ZeroMemory(&opisFormatuPikseli,sizeof(opisFormatuPikseli));
   opisFormatuPikseli.nVersion=1;
   opisFormatuPikseli.dwFlags=PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW |
   PFD_DOUBLEBUFFER; //w oknie, podwojne buforowanie
   opisFormatuPikseli.iPixelType=PFD_TYPE_RGBA; //typ koloru RGB
   opisFormatuPikseli.cColorBits=32; //jakosc kolorów 4 bajty
   opisFormatuPikseli.cDepthBits=16; //glebokosc bufora Z (z-buffer)
   opisFormatuPikseli.iLayerType=PFD_MAIN_PLANE;

   int formatPikseli=ChoosePixelFormat(uchwytDC,&opisFormatuPikseli);
   if (formatPikseli==0) return false;
   if (SetPixelFormat(uchwytDC,formatPikseli,&opisFormatuPikseli)!=true) return
   false;
   return true;
}

void TGLForm::GL_UstawienieSceny()
{
   glViewport(0,0,ClientWidth,ClientHeight); //okno OpenGL = wnetrze formy (domyslnie)

   //ustawienie punktu projekcji
   glMatrixMode(GL_PROJECTION); //przełączenie na macierz projekcji
   glLoadIdentity();
   //left,right,bottom,top,znear,zfar (clipping)
   float wsp=ClientHeight/(float)ClientWidth;
   glFrustum(-0.1, 0.1, wsp*-0.1, wsp*0.1, 0.3, 100.0);
   //mnozenie macierzy rzutowania przez macierz perspektywy - ustalanie frustum
   glMatrixMode(GL_MODELVIEW); //powrót do macierzy widoku modelu
   glEnable(GL_DEPTH_TEST); //z-buffer aktywny = ukrywanie niewidocznych powierzchni

   //oswietlenie
   GL_Oswietlenie();
}

void __fastcall TGLForm::GL_Oswietlenie()
{
   const float kolor_otoczenie[]={natezenie_swiatla_otoczenia,
                                  natezenie_swiatla_otoczenia,
                                  natezenie_swiatla_otoczenia}; //biel-odcienie szarości
   glEnable(GL_LIGHTING); //wlaczenie systemu oswietlania

   glEnable(GL_COLOR_MATERIAL);
   glColorMaterial(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE);

   glLightModelfv(GL_LIGHT_MODEL_AMBIENT,kolor_otoczenie); //swiatlo tla

   Oswietlenie();

   //mieszanie kolorow
   glEnable(GL_BLEND);
   glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);

   //mgla
   //glEnable(GL_FOG);
   const float biel[4]={1.0,1.0,1.0,1.0};
   glFogfv(GL_FOG_COLOR,biel);
   glFogf(GL_FOG_START,0.0);
   glFogf(GL_FOG_END,100.0);
   glFogf(GL_FOG_MODE,GL_LINEAR);
}

void __fastcall TGLForm::GL_RysujScene()
{
   static int iloscOdswiezen=0;
   ++iloscOdswiezen;
   //Caption="Ilość odświeżeń: "+IntToStr(iloscOdswiezen);

   //Przygotowanie bufora
   glClearColor(GetRValue(kolorTla)/255.0f,GetGValue(kolorTla)/255.0f,GetBValue(kolorTla)/255.0f,0.0);
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //czysci bufory
   glLoadIdentity(); //macierz model-widok = macierz jednostkowa

   //nieruchome pudlo
	if (pudlo)
	{
		const float glebokosc=24.9f;
		const float krawedz=3.0f;
      glColor3f(GetRValue(pudlo_kolor)/255.0f,GetGValue(pudlo_kolor)/255.0f,GetBValue(pudlo_kolor)/255.0f);
		Pudlo(krawedz,glebokosc);
	}

   //glTranslatef(0.0, 0.0, -10.0); //odsuniecie calosci o 10
   /*
   gluLookAt(0,0,10,  //polozenie kamery
             0,0,0,   //punkt, na ktory skierowana jest kamera
             0,1,0);  //kierunek "do gory" kamery (polaryzacja)
   */
   gluLookAt(0,0,KameraR,  //polozenie kamery
             0,0,0,        //punkt, na ktory skierowana jest kamera
             0,1,0);       //kierunek "do gory" kamery (polaryzacja)

   //nieruchome osie ukladu wspolrzednych zwiazane ze scena (frustum)
   float rozmiarUkladuWspolrzednych=1.0f;
   if (rysowanieOsi)
   {
      glColor3ub(255,255,255);
      RysujOsie(rozmiarUkladuWspolrzednych);
   }

   //obroty i przesuniecia kamery
   glTranslatef(KameraX+tmpKameraX,KameraY+tmpKameraY,0);
   glRotatef(KameraPhi+tmpKameraPhi, 0.0, 1.0, 0.0); //wokol OY
   glRotatef(KameraTheta+tmpKameraTheta, 1.0, 0.0, 0.0); //wokol OX

   //przeksztalcenia macierzy model-widok i rysowanie figur
   if (teksturowanie) glEnable(GL_TEXTURE_2D);
   RysujScene();
   glDisable(GL_TEXTURE_2D);

   //ruchome osie ukladu wspolrzednych
   if (rysowanieOsi)
   {
      glColor3ub(100,100,255);
      RysujOsie(rozmiarUkladuWspolrzednych);
   }

   //Z bufora na ekran
   glFlush();
   SwapBuffers(uchwytDC);
}

void __fastcall TGLForm::MaksymalizujPrzywroc()
{
	maksymalizacjaOkna=!maksymalizacjaOkna;
	if(maksymalizacjaOkna)
	{
		ShowWindow(Handle,SW_MAXIMIZE);
		//this->BorderStyle=bsNone;
	}
	else
	{
		ShowWindow(Handle,SW_RESTORE);
		//this->BorderStyle=bsSingle;
	}
	GL_UstawienieSceny();
}

//---------------------------------------------------------------------------
//                                KOMUNIKATY                               //
//---------------------------------------------------------------------------

void __fastcall TGLForm::WndProc(TMessage& Message)
{
   TForm::WndProc(Message);

   switch(Message.Msg)
   {
      case WM_PAINT: //odswiezenie formy
         GL_RysujScene();
         break;

      case WM_SIZE: //zmiana rozmiaru formy
      case WM_SIZING:
         GL_UstawienieSceny();
         GL_RysujScene();
         break;

      case WM_SYSCOMMAND: //wykrywanie maksymalizacji
         if (Message.WParam==SC_MAXIMIZE) maksymalizacjaOkna=true;
         if (Message.WParam==SC_RESTORE) maksymalizacjaOkna=false;
         break;

      case WM_KEYDOWN:
         switch(Message.WParam)
         {
            case VK_ESCAPE: Close(); break;
            case VK_F1: Pomoc(); break;
            case 13: MaksymalizujPrzywroc(); break; //Enter
         }
         break;
      }

   // *********************************************************** //
   // DALEJ POWINNY BYC TYLKO KOMUNIKATY DO KONTROLI KAMERY MYSZA //
   // *********************************************************** //
   if(!KameraKontrolaMysza) return;

   switch(Message.Msg)
   {
      //KONTROLA POLOZENIA KAMERY MYSZA
      case WM_LBUTTONDOWN:
      case WM_RBUTTONDOWN:
         X0=LOWORD(Message.LParam); //biezaca poz. x kursora
         Y0=HIWORD(Message.LParam); //biezaca poz. y kursora
         //wstrzymywanie swobodnych obrotow
         SzybkoscX0=X0;
      	SzybkoscY0=Y0;
      	SzybkoscPhi=0;
      	SzybkoscTheta=0;
      	SzybkoscTimer->Enabled=false;
         break;
      case WM_MOUSEMOVE:
      {
         int X=LOWORD(Message.LParam); //int X=Message.LParam & 0x0000FFFF;
         int Y=HIWORD(Message.LParam); //int Y=(Message.LParam & 0xFFFF0000) >> 16;
         if (X>Screen->Width) X-=0xFFFF;
         if (Y>Screen->Height) Y-=0xFFFF;
         int dX=X-X0;
         int dY=Y-Y0;
         if (Message.WParam==MK_LBUTTON) //wylacznie lewy przycisk myszy
         {
            const float czuloscMyszy=5.0f;
            tmpKameraPhi=dX/czuloscMyszy;
            tmpKameraTheta=dY/czuloscMyszy;
            //szybkosc
            SzybkoscPhi=X-SzybkoscX0;
            SzybkoscTheta=Y-SzybkoscY0;
            SzybkoscX0=X;
            SzybkoscY0=Y;
            if(debug_mode) DebugInfo();
            GL_RysujScene();
         }
         if (Message.WParam==MK_RBUTTON) //wylacznie prawy przycisk myszy
         {
            const float czuloscMyszy=75.0f; //powinno zalezec od rozmiaru frustum
            tmpKameraX=dX/czuloscMyszy;
            tmpKameraY=-dY/czuloscMyszy; //po uklad wsp. OpenGL ma y do gory, a wsp. ekranu ma y do dolu
            if(debug_mode) DebugInfo();
            GL_RysujScene();
         }
         //Tu nie nalezy wywolywac GL_RysujScene, bo moze nie byc przesuwania ani obrotow
         break;
      }
      case WM_LBUTTONUP:
         KameraPhi+=tmpKameraPhi;
         KameraTheta+=tmpKameraTheta;
         tmpKameraPhi=0;
         tmpKameraTheta=0;
         //kosmetyka
         if (KameraPhi>=360) KameraPhi-=360;
         if (KameraPhi<0) KameraPhi+=360;
         if (KameraTheta>=360) KameraTheta-=360;
         if (KameraTheta<0) KameraTheta+=360;
         //inicjacja swobodnyh obrotow
      	if ((SzybkoscWlaczone && ((SzybkoscPhi!=0) || (SzybkoscTheta!=0))))
		      SzybkoscTimer->Enabled=true;
         break;
      case WM_RBUTTONUP:
         KameraX+=tmpKameraX;
         KameraY+=tmpKameraY;
         tmpKameraX=0;
         tmpKameraY=0;
         break;
      case WM_MOUSEWHEEL:
      {
         const float czuloscMyszy=10.0f;
         short WheelDelta=(short)HIWORD(Message.WParam);
         //zmiana odleglosci kamery od pocz. ukl. wsp.
         KameraR=KameraR*(1+WheelDelta/abs(WheelDelta)/czuloscMyszy);
         if(debug_mode) DebugInfo();
         GL_RysujScene();
         break;
      }
   }
}


//---------------------------------------------------------------------------
//                             INNE METODY                                 //
//---------------------------------------------------------------------------

#include <Math.h>

void __fastcall TGLForm::DebugInfo()
{
   float totalKameraPhi=KameraPhi+tmpKameraPhi;
   float totalKameraTheta=KameraTheta+tmpKameraTheta;
   if (totalKameraPhi>=360) totalKameraPhi-=360;
   if (totalKameraPhi<0) totalKameraPhi+=360;
   if (totalKameraPhi>=360) totalKameraPhi-=360;
   if (totalKameraPhi<0) totalKameraPhi+=360;
   totalKameraPhi=floor(totalKameraPhi+0.5);
   totalKameraTheta=floor(totalKameraTheta+0.5);
   AnsiString s="Kamera: R="+FloatToStr(KameraR)+", Phi="+FloatToStr(totalKameraPhi)+", Theta="+FloatToStr(totalKameraTheta);

   float totalKameraX=KameraX+tmpKameraX;
   float totalKameraY=KameraY+tmpKameraY;
   s+=(AnsiString)", X="+FloatToStr(totalKameraX)+", Y="+FloatToStr(totalKameraY);

   Caption=s;
}

void __fastcall TGLForm::Pomoc()
{
	AnsiString s=(AnsiString)"GLForm 1.1\n(c) Jacek Matulewski 2006-2007\n\n"+
			"OpenGL, wersja "+(char*)glGetString(GL_VERSION)+"\n"+
			"GLU, wersja "+(char*)gluGetString(GLU_VERSION);
	MessageBox(Handle,s.c_str(),"TGLForm",MB_OK | MB_ICONINFORMATION);
}

#include <math.h>

char sign(double arg)
{
	if (arg==0) return 0;
	else return (char)(arg/fabs(arg));
}

void __fastcall TGLForm::SzybkoscTimerTimer(TObject* Sender)
{
   //x=v*t
   KameraPhi+=SzybkoscPhi*SzybkoscTimer->Interval/100;
   KameraTheta+=SzybkoscTheta*SzybkoscTimer->Interval/100;
   GL_RysujScene();

   if(SzybkoscWygaszania>0)
	{
		float SzybkoscWygaszaniaPhi=sign(SzybkoscPhi)*SzybkoscWygaszania*SzybkoscTimer->Interval/100.0;
		float SzybkoscWygaszaniaTheta=sign(SzybkoscTheta)*SzybkoscWygaszania*SzybkoscTimer->Interval/100.0;
		if (fabs(SzybkoscPhi)<fabs(SzybkoscWygaszaniaPhi)) SzybkoscWygaszaniaPhi=SzybkoscPhi; //w nastepnym kroku zostanie zatrzymany
		if (fabs(SzybkoscTheta)<fabs(SzybkoscWygaszaniaTheta)) SzybkoscWygaszaniaTheta=SzybkoscTheta;
		SzybkoscPhi-=SzybkoscWygaszaniaPhi;
		SzybkoscTheta-=SzybkoscWygaszaniaTheta;
		if ((fabs(SzybkoscPhi)<0.01) && (fabs(SzybkoscTheta)<0.01)) //przy float nie mozna liczyc na czyste zera
      {
         SzybkoscPhi=0;
         SzybkoscTheta=0;
			SzybkoscTimer->Enabled=false;
      }
      if(debug_mode) Caption="Szybkosc obrotow: Phi="+FloatToStr(SzybkoscPhi)+", Theta="+FloatToStr(SzybkoscTheta);
	}
}

void __fastcall TGLForm::Obracaj(float SzybkoscPhi,float SzybkoscTheta,float SzybkoscWygaszania=-1)
{
   this->SzybkoscPhi=SzybkoscPhi;
   this->SzybkoscTheta=SzybkoscTheta;
   if(SzybkoscWygaszania>=0) this->SzybkoscWygaszania=SzybkoscWygaszania;
   SzybkoscTimer->Enabled=true;
}

void __fastcall TGLForm::RysujOsie(float rozmiar)
{
   glBegin(GL_LINES);
   glVertex3f(0,0,0); glVertex3f(rozmiar,0,0); //OX, w prawo
   glVertex3f(0,0,0); glVertex3f(0,rozmiar,0); //OY, do gory
   glVertex3f(0,0,0); glVertex3f(0,0,rozmiar); //OZ, do kamery
   glEnd();
}

void __fastcall TGLForm::Pudlo(float krawedz,float glebokosc) const
{
	//glDisable(GL_TEXTURE_2D);
	//glColor3f(0.1,0.1,0.5);
	glBegin(GL_QUADS);
	//tylnia
	glNormal3f(0,0,1);
	glVertex3f(-krawedz,-krawedz,-glebokosc);
	glVertex3f(-krawedz,krawedz,-glebokosc);
	glVertex3f(krawedz,krawedz,-glebokosc);
	glVertex3f(krawedz,-krawedz,-glebokosc);
	//lewa
	glNormal3f(1,0,0);
	glVertex3f(-krawedz,-krawedz,-glebokosc);
	glVertex3f(-krawedz,krawedz,-glebokosc);
	glVertex3f(-krawedz,krawedz,0);
	glVertex3f(-krawedz,-krawedz,0);
	//prawa
	glNormal3f(0,0,-1);
	glVertex3f(krawedz,-krawedz,-glebokosc);
	glVertex3f(krawedz,krawedz,-glebokosc);
	glVertex3f(krawedz,krawedz,0);
	glVertex3f(krawedz,-krawedz,0);
	//gorna
	glNormal3f(0,-1,0);
	glVertex3f(-krawedz,krawedz,-glebokosc);
	glVertex3f(krawedz,krawedz,-glebokosc);
	glVertex3f(krawedz,krawedz,0);
	glVertex3f(-krawedz,krawedz,0);
	//dolna
	glNormal3f(0,1,0);
	glVertex3f(-krawedz,-krawedz,-glebokosc);
	glVertex3f(krawedz,-krawedz,-glebokosc);
	glVertex3f(krawedz,-krawedz,0);
	glVertex3f(-krawedz,-krawedz,0);
	glEnd();
	//glEnable(GL_TEXTURE_2D);
}

//---------------------------------------------------------------------------
//                              WŁASNOŚCI                                  //
//---------------------------------------------------------------------------

void __fastcall TGLForm::UstawKolorTla(TColor kolorTla)
{
   this->kolorTla=kolorTla;
   GL_RysujScene();
}

void __fastcall TGLForm::UstawNatezenieSwiatlaOtoczenia(float NatezenieSwiatlaOtoczenia)
{
	if (NatezenieSwiatlaOtoczenia<0.0f) NatezenieSwiatlaOtoczenia=0.0f;
	if (NatezenieSwiatlaOtoczenia>1.0f) NatezenieSwiatlaOtoczenia=1.0f;
	this->natezenie_swiatla_otoczenia=NatezenieSwiatlaOtoczenia;
	if(debug_mode) Caption=(AnsiString)"Natezenie oswietlenia otoczenia: "+(int)(100.0*this->natezenie_swiatla_otoczenia)+"%";
	GL_Oswietlenie();
	GL_RysujScene();
}

bool __fastcall TGLForm::CzyMgla()
{
   return glIsEnabled(GL_FOG);
}

void __fastcall TGLForm::UstawMgla(bool mgla)
{
   if(mgla) glEnable(GL_FOG);
   else glDisable(GL_FOG);
   GL_RysujScene();
}


