This is an old revision of the document!
Obiectele 2D sunt definite intr-un sistem de coordonate carteziene 2D, de exemplu, XOY, XOZ sau YOZ. In cadrul acestui laborator vom implementa diferite tipuri de transformari ce pot fi aplicate obiectelor definite in planul XOY: translatii, rotatii si scalari. Aceastea sunt definite in format matriceal, in coordonate omgene, asa cum ati invatat deja la curs. Matricile acestor transformari sunt urmatoarele:
$$ \begin{bmatrix} {x}'\\ {y}'\\ 1 \end{bmatrix} = \begin{bmatrix} 1 & 0 & t_x\\ 0 & 1 & t_y\\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} x\\ y\\ 1 \end{bmatrix} $$
$$ \begin{bmatrix} {x}'\\ {y}'\\ 1 \end{bmatrix} = \begin{bmatrix} cos(u) & -sin(u) & 0\\ sin(u) & cos(u) & 0\\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} x\\ y\\ 1 \end{bmatrix} $$
Rotatia relativa la un punct oarecare se rezolva in cel mai simplu mod prin:
$$ \begin{bmatrix} {x}'\\ {y}'\\ 1 \end{bmatrix} = \begin{bmatrix} s_x & 0 & 0\\ 0 & s_y & 0\\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} x\\ y\\ 1 \end{bmatrix} $$
Daca $sx = sy$ atunci avem scalare uniforma, altfel avem scalare neuniforma.
Scalarea relativa la un punct oarecare se rezolva similar cu rotatia relativa la un punct oarecare.
In cadrul laboratorului folosim biblioteca GLM care este o biblioteca implementata cu matrici in forma coloana, exact acelasi format ca OpenGL. Forma coloana difera de forma linie prin ordinea de stocare a elementelor maricei in memorie, Matricea de translatie arata in modul urmator in memorie:
glm::mat3 Translate(float tx, float ty) { return glm::mat3( 1, 0, 0, // coloana 1 in memorie 0, 1, 0, // coloana 2 in memorie tx, ty, 1); // coloana 3 in memorie }
Din aceasta cauza, este convenabil ca matricile sa fie scrise manual in forma aceasta:
glm::mat3 Translate(float tx, float ty) { return glm::transpose( glm::mat3( 1, 0, tx, 0, 1, ty, 0, 0, 1) ); }
Transform2D.h
sunt definite functiile pentru calculul matricilor de translatie, rotatie si scalare. In momentul acesta toate functiile intorc matricea identitate. In cadrul laboratorului va trebui sa modificati codul pentru a calcula matricile respective.
De ce sunt necesare matricile? Pentru a reprezenta printr-o singura matrice de transformari o secventa de transformari elementare, in locul aplicarii unei secvente de transformari elementare pe un anume obiect.
Deci, daca dorim sa aplicam o rotatie, o scalare si o translatie pe un obiect, nu facem rotatia obiectului, scalarea obiectului urmata de translatia lui, ci calculam o matrice care reprezinta transformarea compusa (de rotatie, scalare si translatie), dupa care aplicam aceasta transformare compusa pe obiectul care se doreste a fi transformat.
Astfel, daca dorim sa aplicam o rotatie (cu matricea de rotatie $R$), urmata de o scalare ($S$), urmata de o translatie ($T$) pe un punct ($x$,$y$), punctul transformat (${x}'$,${y}'$) se va calcula astfel:
$$ \begin{bmatrix} {x}'\\ {y}'\\ 1 \end{bmatrix} = \begin{bmatrix} 1 & 0 & t_x\\ 0 & 1 & t_y\\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} sx & 0 & 0\\ 0 & sy & 0\\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} cos(u) & -sin(u) & 0\\ sin(u) & cos(u) & 0\\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} x\\ y\\ 1 \end{bmatrix} $$
Deci, matricea de transformari compuse $M$ este $M = T * S * R$.
Laborator3.cpp
, exista o serie de obiecte (patrate) pentru care, in functia Update()
, inainte de desenare, se definesc matricile de transformari. Comanda de desenare se da prin functia RenderMesh2D()
.
modelMatrix = glm::mat3(1); modelMatrix *= Transform2D::Translate(150, 250); RenderMesh2D(meshes["square1"], shaders["VertexColor"], modelMatrix);
Pentru exemplul anterior, matricea de translatie creata va avea ca efect translatarea patratului curent cu (150, 250). Pentru efecte de animatie continua, pasii de translatie ar trebui sa se modifice in timp.
Exemplu:
tx += deltaTimeSeconds * 100; ty += deltaTimeSeconds * 100; model_matrix *= Transform2D::Translate(tx, ty);
deltaTimeSeconds
), veti crea animatii dependente de platforma.
Exemplu: daca la fiecare frame cresteti pe tx cu un pas constant (ex: tx += 0.01
), atunci animatia se va comporta diferit pe un calculator care merge mai repede fata de unul care merge mai incet. Pe un calculator care ruleaza la 50 FPS, obiectul se va deplasa 0.01 * 50 = 0.5 unitati in dreapta intr-o secunda. In schimb, pe un calculator mai incet, care ruleaza la 10 FPS, obiectul se va deplasa 0.01 * 10 = 0.1 unitati in dreapta intr-o secunda, deci animatia va fi de 5 ori mai lenta.
Din acest motiv este bine sa tineti cont de viteza de rulare a fiecarui calculator (data prin deltaTimeSeconds
, care reprezinta timpul de rulare al frame-ului anterior) si sa modificati pasii de translatie, unghiurile de rotatie si factorii de scalare in functie de aceasta variabila.
Desenele reprezentate intr-un program de aplicatie grafica (2D sau 3D) sunt, de regula, raportate la un sistem de coordonate diferit de cel al suprafetei de afisare.
glViewport()
.
Exemplu: Daca viewport-ul meu are coltul din stanga jos (0, 0) si are latimea 1280 si inaltimea 720, atunci toate obiectele ar trebui desenate in acest interval, daca vreau sa fie vizibile. Acest lucru ma conditioneaza sa imi gandesc toata scena in (0, 0) - (1280, 720). Daca vreau sa scap de aceasta limitare, pot sa imi gandesc scena intr-un spatiu logic (de exemplu imi creez toate obiectele in spatiul (-1, -1) - (1, 1), si apoi sa le desenez in poarta de afisare, dar aplicand ceea ce se numeste transformarea fereastra poarta.
In cele ce urmeaza vedem ce presupune aceasta transformare si cum pot sa imi gandesc scena fara sa fiu limitat de dimensiunea viewport-ului.
$$ \frac{xp - xpmin}{xpmax - xpmin} = \frac{xf - xfmin}{xfmax - xfmin} $$ $$ \frac{yp - ypmin}{ypmax - ypmin} = \frac{yf - yfmin}{yfmax - yfmin} $$
Transformarea este definita prin 2 dreptunghiuri, in cele doua sisteme de coordonate, numite fereastra sau spatiul logic si poarta, sau spatiul de afisare. De aici numele de transformarea fereastra-poarta sau transformarea de vizualizare 2D.
F: un punct din fereastra
P: punctul in care se transforma F prin transformarea de vizualizare
Pozitia relativa a lui P in poarta de afisare trebuie sa fie aceeasi cu pozitia relativa a lui F in fereastra.
$$ sx = \frac{xpmax - xpmin}{xfmax - xfmin} $$
$$ sy = \frac{ypmax - ypmin}{yfmax - yfmin} $$
$$ tx = xpmin - sx * xfmin $$ $$ ty = ypmin - sy * yfmin $$
In final, transformarea fereastra poarta are urmatoarele ecuatii:
$$ xp = xf * sx + tx $$ $$ yp = yf * sy + ty $$
Consideram o aceeasi orientare a axelor celor doua sisteme de coordonate. Daca acestea au orientari diferite (ca in prima imagine), trebuie aplicata o transformare suplimentara de corectie a coordonatei y.
$$Tsx = (xpmax - xpmin - s*(xfmax - xfmin)) / 2$$ $$Tsy = (ypmax - ypmin - s*(yfmax - yfmin)) / 2$$
De retinut este ca transformarea fereastra poarta presupune o scalare si o translatie. Ea are urmatoarea expresie, cu formulele de calcul pentru sx, sy, tx, ty prezentate anterior:
$$ \begin{bmatrix} xp\\ yp\\ 1 \end{bmatrix} = \begin{bmatrix} sx & 0 & tx\\ 0 & sy & ty\\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} xf\\ yf\\ 1 \end{bmatrix} $$
Laborator3_Vis2D
:
//2D vizualization matrix glm::mat3 Laborator3_Vis2D::VisualizationTransf2D(const LogicSpace & logicSpace, const ViewportSpace & viewSpace) { float sx, sy, tx, ty; sx = viewSpace.width / logicSpace.width; sy = viewSpace.height / logicSpace.height; tx = viewSpace.x - sx * logicSpace.x; ty = viewSpace.y - sy * logicSpace.y; return glm::transpose(glm::mat3( sx, 0.0f, tx, 0.0f, sy, ty, 0.0f, 0.0f, 1.0f)); }
Laborator3_Vis2D
, este creat un patrat, in spatiul logic (0,0) - (4,4). De retinut este faptul ca acum nu mai trebuie sa raportam coordonatele patratului la spatiul de vizualizare (cum se intampla in exercitiile anterioare), ci la spatiul logic pe care l-am definit noi.
logicSpace.x = 0; // logic x logicSpace.y = 0; // logic y logicSpace.width = 4; // logic width logicSpace.height = 4; // logic height glm::vec3 corner = glm::vec3(0.001, 0.001, 0); length = 0.99f; Mesh* square1 = Object2D::CreateSquare("square1", corner, length, glm::vec3(1, 0, 0)); AddMeshToList(square1);
In functia Update()
se deseneaza acelasi patrat creat anterior, de 5 ori: patru patrate in cele patru colturi si un patrat in mijlocul spatiului logic. Se definesc 2 viewport-uri, ambele continand aceleasi obiecte. Primul viewport este definit in jumatatea din stanga a ferestrei de afisare, iar al doilea, in jumatatea din dreapta. Pentru primul viewport se defineste transformarea fereastra poarta default si pentru al doilea viewport, cea uniforma. Observati ca in al doilea viewport patratele raman intotdeauna patrate, pe cand in primul viewport se vad ca dreptunghiuri (adica sunt deformate), daca spatiul logic si spatiul de vizualizare nu sunt reprezentate prin dreptunghiuri asemenea.
Unde se poate folosi aceasta transformare fereastra poarta? De exemplu, intr-un joc 2D cu masini de curse, se doreste in dreapta-jos a ecranului vizualizarea masinii proprii, intr-un minimap. Acest lucru se face prin desenarea scenei de doua ori.
Daca de exemplu toata scena (traseul si toate masinile) este gandita in spatiul logic (-10,-10) - (10,10) (care are dimensiunea 20×20) si spatiul de afisare este (0,0) - (1280, 720), prima data se deseneaza toata scena cu parametrii functiei fereastra-poarta anterior mentionati:
LogicSpace logic_space = LogicSpace(-10, -10, 20, 20); ViewportSpace view_space = ViewportSpace(0, 0, 1280, 720); vis_matrix *= VisualizationTransf2D(logic_space, view_space);
Daca la un moment dat masina proprie este in spatiul (2,2) - (5,5), adica de dimensiune 3×3 si vreau sa creez un minimap in coltul din dreapta jos al ecranului de rezolutie 280×220, pot desena din nou aceeasi scena, dar cu urmatoarea transformare fereastra-poarta:
LogicSpace logic_space = LogicSpace(2, 2, 3, 3); ViewportSpace view_space = ViewportSpace(1000, 500, 280, 220); vis_matrix *= VisualizationTransf2D(logic_space, view_space);
Laborator3.cpp
, pentru familiarizarea cu transformarile 2D de translatie, rotatie si scalareLaborator3_Vis2D.cpp
, pentru familiarizarea cu transformarea fereastra-poarta
Din clasa Main
puteti sa alegeti ce laborator rulati:
World *world = new Laborator3();
sau
World *world = new Laborator3_Vis2D();
Transformarea fereastra-poarta este si ea simulata pentru acest framework, dar veti invata pe parcurs ca ea este de fapt inclusa in lantul de transformari OpenGL si ca nu trebuie definita explicit.
/Laborator3/Transform2D.h