3D grafika a animácie

V príkladoch:

* kreslíme do grafickej plochy Image1
* potrebujeme globálne premenné:
TCanvas *g; // grafická plocha
int StredX, StredY; // stred plochy
* používame časovač Timer1 s hodnotou Interval=50,
* používame matematické funkcie - nezabudnúť na: #include <math.h>


Bod v 3d

Definujeme triedu TBod. Objekt tejto triedy si:

* pamätá súradnice bodu v priestore,
* má metódy, pomocou ktorý môžeme bod posúvať a otáčať okolo osí x, y, alebo z,
* má metódu, pomocou ktorej zistíme jeho súradnice v grafickej ploche Image1 (t.j. jeho priemet v dvojrozmernej rovine).

class TBod
{
public:
float X, Y, Z;
TBod();
TBod(float nX, float nY, float nZ);
void Posun(float dX, float dY, float dZ);
void OtocX(float U);
void OtocY(float U);
void OtocZ(float U);
TPoint Priemet();
};

TBod::TBod()
{
X=0;
Y=0;
Z=0;
}

TBod::TBod(float nX, float nY, float nZ)
{
X=nX;
Y=nY;
Z=nZ;
}

void TBod::Posun(float dX, float dY, float dZ)
{
X=X+dX;
Y=Y+dY;
Z=Z+dZ;
}

void TBod::OtocX(float U)
{
float nY, nZ;
nY=Y*cos(U)-Z*sin(U);
nZ=Y*sin(U)+Z*cos(U);
Y=nY;
Z=nZ;
}

void TBod::OtocY(float U)
{
float nZ, nX;
nZ=Z*cos(U)-X*sin(U);
nX=Z*sin(U)+X*cos(U);
Z=nZ;
X=nX;
}

void TBod::OtocZ(float U)
{
float nX, nY;
nX=X*cos(U)-Y*sin(U);
nY=X*sin(U)+Y*cos(U);
X=nX;
Y=nY;
}

TPoint TBod::Priemet()
{
TPoint pom;
pom.x=StredX+X;
pom.y=StredY-Y;
return pom;
}

Ukážka použitia:
TBod b(50, 100, 0);

void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
TPoint p;
g->FillRect(Image1->ClientRect);
b.OtocZ(0.01);
p=b.Priemet();
g->Ellipse(p.x-5, p.y-5, p.x+5, p.y+5);
}


Drôtený model

Trieda TUtvar nám umožní zobrazovať drôtené modely geometrických útvarov (t.j. hrany geometrických útvarov):
struct TUsecka
{
TBod A, B;
};

class TUtvar
{
private:
TUsecka *U;
int poc;
public:
TUtvar();
~TUtvar();
void Pridaj(TBod A, TBod B);
void Posun(float dX, float dY, float dZ);
void Otoc(float oX, float oY, float oZ);
void Kresli();
};

TUtvar::TUtvar()
{
U=NULL;
poc=0;
}

TUtvar::~TUtvar()
{
delete[] U;
}

void TUtvar::Pridaj(TBod A, TBod B)
{
TUsecka *N;
int i;
N=new TUsecka[poc+1];
for (i=0; i<poc; i++) N[i]=U[i];
delete[] U;
U=N;
U[poc].A=A;
U[poc].B=B;
poc++;
}

void TUtvar::Posun(float dX, float dY, float dZ)
{
for (int i=0; i<poc; i++) {
U[i].A.Posun(dX, dY, dZ);
U[i].B.Posun(dX, dY, dZ);
}
}

void TUtvar::Otoc(float oX, float oY, float oZ)
{
for (int i=0; i<poc; i++) {
U[i].A.OtocX(oX);
U[i].A.OtocY(oY);
U[i].A.OtocZ(oZ);
U[i].B.OtocX(oX);
U[i].B.OtocY(oY);
U[i].B.OtocZ(oZ);
}
}

void TUtvar::Kresli()
{
TPoint A, B;
for (int i=0; i<poc; i++) {
A=U[i].A.Priemet();
B=U[i].B.Priemet();
g->MoveTo(A.x, A.y);
g->LineTo(B.x, B.y);
}
}

NDÚ: V triede TUtvar používame dynamické pole úsečiek. Pri pridávaní novej úsečky kopírujeme úsečky, čo je neefektívne, pretože sa pri kopírovaní každej úsečky sa prenáša veľa bajtov - sizeof(TUsecka) je 24 bajtov. Ideálne riešenie by bolo, keby sme namiesto dynamického poľa úsečiek použili dynamické pole smerníkov na úsečky TUsecka **U. Pri pridávaní novej úsečky by sa namiesto úsečiek kopírovali iba smerníky na úsečky - t.j. iba 4 bajty. Skúste program takto upraviť.

Ukážka použitia:
TUtvar Troj;

void __fastcall TForm1::FormCreate(TObject *Sender)
{
Troj.Pridaj(TBod( +0, +0, +0), TBod( +100, +0, +0));
Troj.Pridaj(TBod( +100, +0, +0), TBod( +0, +100, +0));
Troj.Pridaj(TBod( +0, +100, +0), TBod( +0, +0, +0));
}

void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
g->FillRect(Image1->ClientRect);
Troj.Otoc(0, 0, 0.1);
Troj.Kresli();
}

Zápis TBod(0, 0, 0) spôsobí, že v pamäti vznikne dočasne objekt typu TBod, tento objekt sa použije ako parameter funkcie a po skončení volania objekt automaticky zanikne.


Perspektívne premietanie

Skúsime sa pozrieť ešte na jeden geometrický útvar - kocku:
TUtvar Kocka;

void __fastcall TForm1::FormCreate(TObject *Sender)
{
// predná strana:
Kocka.Pridaj(TBod( -100, -100, -100), TBod( -100, +100, -100));
Kocka.Pridaj(TBod( -100, +100, -100), TBod( +100, +100, -100));
Kocka.Pridaj(TBod( +100, +100, -100), TBod( +100, -100, -100));
Kocka.Pridaj(TBod( +100, -100, -100), TBod( -100, -100, -100));
// zadná strana:
Kocka.Pridaj(TBod( -100, -100, +100), TBod( -100, +100, +100));
Kocka.Pridaj(TBod( -100, +100, +100), TBod( +100, +100, +100));
Kocka.Pridaj(TBod( +100, +100, +100), TBod( +100, -100, +100));
Kocka.Pridaj(TBod( +100, -100, +100), TBod( -100, -100, +100));
// boky:
Kocka.Pridaj(TBod( -100, -100, -100), TBod( -100, -100, +100));
Kocka.Pridaj(TBod( -100, +100, -100), TBod( -100, +100, +100));
Kocka.Pridaj(TBod( +100, +100, -100), TBod( +100, +100, +100));
Kocka.Pridaj(TBod( +100, -100, -100), TBod( +100, -100, +100));
}

void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
g->FillRect(Image1->ClientRect);
Kocka.Otoc(0, 0, 0.01);
Kocka.Kresli();
}

V predchádzajúcom príklade uvidíme namiesto rotujúcej kocky otáčajúci sa štvorec. Je to preto, lebo v metóde TBod::Priemet používame kolmé premietanie a ignorujeme z-ovú súradnicu. Tým strácame informáciu o hĺbke. Namiesto kolmého radšej používame perspektívne premietanie. Upravíme metódu TBod::Priemet():
TPoint TBod::Priemet()
{
const POZ=500;
TPoint pom;
pom.x=StredX+X*POZ/(POZ+Z);
pom.y=StredY-Y*POZ/(POZ+Z);
return pom;
}

program na ukážku


Zhrnutie

* zadefinovali sme triedu TBod, ktorá umožnila manipulovať s bodmi v trojrozmernom priestore,
* zoznam úsečiek sme udržiavali v triede TUtvar,
* vytvorili sme jednoduchý otáčajúci sa útvar a zobrazili sme ho na obrazovke v perspektívnom premietaní.