//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop

#include "obraz.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)

//const double DOZWOLONY_UCHYB=0.000001;  //tolerancja oceny, czy promien trafia w trójkąt
const double DOZWOLONY_UCHYB=0.00000001;  //tolerancja oceny, czy promien trafia w trójkąt
//---------------------------------------------------------------------------
//  Współczynnik odbicia barwy na powierzchni, zadany trójką liczb rzeczywistych.
//  Składniki r, g, b, odpowiadające za odbicie poszczególnych barw,
//  powinny być z przedziału 0...1.0.
//---------------------------------------------------------------------------
TOdbicie :: TOdbicie( void)
{
 r = g = b = 1.0;             //pełne odbicie
}
//---------------------------------------------------------------------------
//  Konstruktor merytoryczny
TOdbicie :: TOdbicie( double Ar, double Ag, double Ab)
{
 r = Ar;
 g = Ag;
 b = Ab;
}
//---------------------------------------------------------------------------
//  Konstruktor kopiujący
TOdbicie :: TOdbicie( const TOdbicie &o)
{
 r = o.r;
 g = o.g;
 b = o.b;
}
//---------------------------------------------------------------------------
//  Operator przypisania
TOdbicie & TOdbicie :: operator= (const TOdbicie &o)
{
 if( this != &o)
 {
    r = o.r;
    g = o.g;
    b = o.b;
 }
 return *this;
}
//---------------------------------------------------------------------------
//  Barwa promienia lub powierzchni, zadana trójką liczb rzeczywistych.
//---------------------------------------------------------------------------
TSwiatlo :: TSwiatlo( void)
{
 r = g = b = 0;             //czerń
}
//---------------------------------------------------------------------------
TSwiatlo :: TSwiatlo( double Ar, double Ag, double Ab)
{
 r = Ar;
 g = Ag;
 b = Ab;
}
//---------------------------------------------------------------------------
//  Konstruktor kopiujący
TSwiatlo :: TSwiatlo( const TSwiatlo &i)
{
 r = i.r;
 g = i.g;
 b = i.b;
}
//---------------------------------------------------------------------------
//  Operator przypisania
TSwiatlo & TSwiatlo :: operator= (const TSwiatlo &i)
{
 if( this != &i)
 {
    r = i.r;
    g = i.g;
    b = i.b;
 }
 return *this;
}
//---------------------------------------------------------------------------
//  Operator sumowania świateł
TSwiatlo & TSwiatlo :: operator+= (const TSwiatlo &i)
{
 r += i.r;
 g += i.g;
 b += i.b;
 return *this;
}
//---------------------------------------------------------------------------
//  Odbicie - stłumienie światła barwowymi współczynnikami pochłaniania.
void TSwiatlo :: odbij( TOdbicie o)
{
 r *= o.r;
 g *= o.g;
 b *= o.b;
}
//---------------------------------------------------------------------------
//  Zamiana czynników r, g, b na kolor, z ewentualnym przemnożeniem przez
//  faktor jaskrawości, zadany procentowo
TColor TSwiatlo :: daj_kolor( double procent)
{
 double p = procent / 100.;
 return RGB( (int)(r * p), (int)(g * p), (int)(b * p));
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//  Konstruktor domyślny. Trójkąt zerowy, nieświecący, bez pochłaniania.
TTrojkat :: TTrojkat( void)
{
}
//---------------------------------------------------------------------------
//  Konstruktor podstawowy
TTrojkat :: TTrojkat( TPunkt Ap1, TPunkt Ap2, TPunkt Ap3, TSwiatlo Ai, TOdbicie Ao)
{
 p1 = Ap1;
 p2 = Ap2;
 p3 = Ap3;
 swiatlo = Ai;
 odbicie = Ao;
 N = iloczyn_wektorowy( TWektor( p2, p1), TWektor( p3, p1));//wektor normalny
 N.unormuj();
}
//---------------------------------------------------------------------------
//  Konstruktor kopiujący
TTrojkat :: TTrojkat( const TTrojkat &t)
{
 p1 = t.p1;
 p2 = t.p2;
 p3 = t.p3;
 swiatlo = t.swiatlo;
 odbicie  = t.odbicie;
 N  = t.N;
}
//---------------------------------------------------------------------------
//  Operator przypisania
TTrojkat & TTrojkat :: operator= (const TTrojkat &t)
{
 if( this != &t)
 {
    p1 = t.p1;
    p2 = t.p2;
    p3 = t.p3;
    swiatlo = t.swiatlo;
    odbicie = t.odbicie;
    N  = t.N;
 }
 return *this;
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//  Konstruktor domyślny. swiatlo = 0
TPromien :: TPromien( void)
{
 r0 = TPunkt();
 kierunek = TWektor();
}
//---------------------------------------------------------------------------
//  Konstruktor podstawowy. Promien z punktu Ar0, skierowany w Akierunek
//  Akierunek musi być unormowany (nie jest normowany tutaj)
TPromien :: TPromien( TPunkt Ar0, TWektor Akierunek)
{
 r0 = Ar0;
 kierunek = Akierunek;
}
//---------------------------------------------------------------------------
//  Inny konstruktor. Promien skierowany od a do b. Zawiera normowanie.
TPromien :: TPromien( TPunkt Aa, TPunkt Ab)
{
 r0 = Aa;
 kierunek = TWektor( Ab.x - Aa.x, Ab.y - Aa.y, Ab.z - Aa.z);
 kierunek.unormuj();
}
//---------------------------------------------------------------------------
//  Konstruktor kopiujący
TPromien :: TPromien( const TPromien &p)
{
 r0 = p.r0;
 kierunek = p.kierunek;
 swiatlo = p.swiatlo;
}
//---------------------------------------------------------------------------
//  Operator przypisania
TPromien & TPromien :: operator= (const TPromien &p)
{
 if( this != &p)
 {
    r0 = p.r0;
    kierunek = p.kierunek;
    swiatlo = p.swiatlo;
 }
 return *this;
}
//---------------------------------------------------------------------------
//  Wyznaczenie punktu przecięcia promienia z płaszczyzną trójkąta.
//  Za chwilę wyznaczymy, czy punkt ten leży wewnątrz trójkąta,
//  tzn, czy promień trafia w trójkąt.
//  Parametry referencyjne są wypełniane, o ile przecięcie istnieje.
bool TPromien :: czy_trafia_w_plaszczyzne( TTrojkat t, TPunkt &p)
{
 double D = -(t.N.x * t.p1.x +
              t.N.y * t.p1.y +
              t.N.z * t.p1.z);      //współczynnik D r-nia płaszczyzny Ax+By+Cz+D=0
 double a = t.N.x * kierunek.x +
            t.N.y * kierunek.y +
            t.N.z * kierunek.z;     //rzut promienia na kierunek normalny do płaszczyzny
 if( a == 0)
    return false;                   //promień równoległy do płaszczyzny trójkąta.
                                    //Poniższe wynika z podstawienia równania
                                    //promienia do równania płaszczyzny
 double b = t.N.x * r0.x +
            t.N.y * r0.y +
            t.N.z * r0.z;
 double c = (-D - b) / a;

 if( c <= DOZWOLONY_UCHYB)
    return false;                   //trójkąt leży z 'niewłaściwiej' strony promienia
                                    //Także eliminacja kolizji dopiero co wysłanego

 p.x = r0.x + kierunek.x * c;       //współrzędne przecięcia promienia z płaszczyzną
 p.y = r0.y + kierunek.y * c;
 p.z = r0.z + kierunek.z * c;
 return true;
}
//---------------------------------------------------------------------------
//  Szukanie przecięcia promienia z trójkątem zabudowy sceny.
//  Parametry referencyjne są wypełniane, o ile przecięcie istnieje.
//  p - punkt trafienia.
//  Najpierw wyznacza się przecięcie promienia z płaszczyzną trójkąta.
//  Potem wyszukuje się płaszczyznę osi układu XYZ, na którą najwygodniej
//  zrzutować trójkąt i punkt cieniowo.
bool TPromien :: czy_trafia_w_trojkat( TTrojkat t, TPunkt &p)
{
 if( czy_trafia_w_plaszczyzne( t, p))   //jeśli tak, wyznacza współrzędne trafienia p
 {
    double max_kierunek = max( t.N.x, t.N.y, t.N.z);
    double p1x, p1y, p2x, p2y, p3x, p3y, px, py;    //współrzędne po zrzutowaniu
    double pole, pole1, pole2, pole3;   //polowy test trafiania w trójkąt
    if( max_kierunek == t.N.x)      //rzutowanie na płaszczyznę YZ
    {
        p1x = t.p1.y;
        p1y = t.p1.z;
        p2x = t.p2.y;
        p2y = t.p2.z;
        p3x = t.p3.y;
        p3y = t.p3.z;
        px  = p.y;
        py  = p.z;
    }
    else
    if( max_kierunek == t.N.y)      //rzutowanie na płaszczyznę XZ
    {
        p1x = t.p1.x;
        p1y = t.p1.z;
        p2x = t.p2.x;
        p2y = t.p2.z;
        p3x = t.p3.x;
        p3y = t.p3.z;
        px  = p.x;
        py  = p.z;
    }
    else                            //rzutowanie na XY
    {
        p1x = t.p1.x;
        p1y = t.p1.y;
        p2x = t.p2.x;
        p2y = t.p2.y;
        p3x = t.p3.x;
        p3y = t.p3.y;
        px  = p.x;
        py  = p.y;
    }
    pole  = fabs(( p2x-p1x) * (p3y-p1y) - (p2y-p1y) * (p3x-p1x)) / 2.;
    pole1 = fabs(( p2x-px) * (p3y-py) - (p2y-py) * (p3x-px)) / 2.;
    pole2 = fabs(( p1x-px) * (p3y-py) - (p1y-py) * (p3x-px)) / 2.;
    pole3 = fabs(( p2x-px) * (p1y-py) - (p2y-py) * (p1x-px)) / 2.;

    return ((pole+DOZWOLONY_UCHYB) >= (pole1 + pole2 + pole3));    //trafił w trójkąt
 }
 return false;
}
//---------------------------------------------------------------------------
//  Wywołuj po sprawdzeniu, ze promien *this trafia w trójkąt t
void TPromien :: odbij( TTrojkat t, TPunkt p)
{
 r0 = p;                   //początek nowego (odbitego) promienia
 kierunek = suma( iloczyn( -2 * iloczyn_skalarny( kierunek, t.N), t.N), kierunek);
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//  Obiekt ujmujący cały, obserwowany teren.
TScena :: TScena( void)
{
}
//---------------------------------------------------------------------------
void TScena :: dodaj_trojkat( TTrojkat t)
{
 trojkaty.insert( trojkaty.end(), t);
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//  Konstruktor merytoryczny. Uruchamia konstruktora klasy bazowej
//  obs - pozycja obserwatora,
//  eszer, ewys - rozmiary pikselowe pola obrazowego,
//  odl_ekr - odległość pola obrazowego [m],
//  szer, wys - rozmiar pola obrazowego [m].
//  scena - wskaźnik do obiektu przechowującego trójkąty zabudowy
TObraz :: TObraz( TPunkt Aobs, int eszer, int ewys,
            double Aodl_ekr, double szer, double wys, TScena *Ascena,
            int Aglebokosc_rekurencji) :
            TSkalowanie( 0, 0, eszer, ewys, 0, 0, szer, wys)
{
 obs = Aobs;
 Rxy = sqrt( obs.x * obs.x + obs.y * obs.y);
 R = sqrt( Rxy * Rxy + obs.z * obs.z);
 odl_ekr = Aodl_ekr;
 scena = Ascena;
 glebokosc_rekurencji = Aglebokosc_rekurencji;

 if( Rxy != 0)
    sf = obs.y / Rxy, cf = obs.x / Rxy;    //współczynniki macierzy obrotu
 else
    sf = 0, cf = 1;

 st = Rxy / R, ct = obs.z / R;
}
//---------------------------------------------------------------------------
//  Główna funkcja algorytmu
//  Wyliczenie koloru podanego piksela (rozmiar ekranu jest określony
//  w konstruktorze TObraz) i przemnozenie go przez procentową jasność jasn_proc.
TColor TObraz :: kolor_piksela( int i, int j, int jasn_proc)
{
 TPromien r;

 r = inicjuj_promien( i, j);    //wyznaczenie promienia przez piksel
 w_droge( r, 0);                //tutaj zaczyna się droga promienia

 return r.swiatlo.daj_kolor( jasn_proc);
}
//---------------------------------------------------------------------------
//  Prywatna f. pomocnicza, konstruująca promien wychodzący
//  od obserwatora i biegnący przez piksel (i, j) płaszczyzny obrazowej.
TPromien TObraz :: inicjuj_promien( int i, int j)
{
 double eh, ev;                 //metryczne współrzędne piksela (i, j) w płaszczyźnie ekranu
 TPunkt e;                      //metryczne współrzędne piksela w przestrzeni 3d

 eh = daj_real_x( i);           //funkcje odziedziczone z klasy bazowej
 ev = daj_real_y( j);

 TPunkt z( -ev, eh, R-odl_ekr); //gdy ekran jest w zenicie, piksel ma takie współrzędne

 e.x = cf * ct * z.x - sf * z.y + cf * st * z.z;
 e.y = sf * ct * z.x + cf * z.y + sf * st * z.z;
 e.z = -st * z.x + ct * z.z;

 return TPromien( obs, e);
}
//---------------------------------------------------------------------------
//  Propagator fotonu. Po trafieniu w przeszkodę, wyznacza nowe parametry
//  fotonu po odbiciu i rekurencyjnie wypuszcza go w dalszą drogę.
//  retval - czy znalazł przeszkodę.
bool TObraz :: w_droge( TPromien &r, int nr_odbicia)
{
 TTrojkat t_trafiony;
 TPunkt p_trafienia;

 if( nr_odbicia > glebokosc_rekurencji) //strażnik zbyt głębokiej rekurencji
    return false;

 if( daj_najblizszy_trafiony_trojkat( r, t_trafiony, p_trafienia))
 {
    r.odbij( t_trafiony, p_trafienia);  //odwrócenie promienia (prawo odbicia)
    if( w_droge( r, ++nr_odbicia))      //rekurencja. Promień poszedł w dalszą drogę
    {
        r.swiatlo.odbij( t_trafiony.odbicie);    //odbierz trochę blasku - pochłanianie
    }
    r.swiatlo += t_trafiony.swiatlo;    //jeśli trójkąt świeci, promień staje się jaśniejszy
    return true;
 }
 return false;                          //nie znalazł kolejnego odbicia
}
//---------------------------------------------------------------------------
//  Promien r próbuje trafic kolejno w każdy trójkąt ze sceny.
//  Najbliższe trafienie jest właściwe.
//  return - czy trafiono
//  Argumenty referencyjne są wypełniane, gdy trafiono
bool TObraz :: daj_najblizszy_trafiony_trojkat( TPromien r, TTrojkat &t, TPunkt &p)
{
 int i, il = scena -> il();
 double dystans, min_dystans = 100000.0;
 TPunkt p1;
 bool znaleziono = false;

 for( i = 0; i < il; i ++)
 {
    if( r.czy_trafia_w_trojkat( scena -> trojkaty[ i], p1))
    {
        dystans = odleglosc( r.r0, p1);
        if( dystans < min_dystans)
        {
            t = scena -> trojkaty[ i];     //przekazanie do argumentów referencyjnych
            p = p1;
            min_dystans = dystans;
            znaleziono = true;
        }
    }
 }
 return znaleziono;
}

