/*!\file vtransforms.c * \brief transformations spatiales pour un moteur de rendu basé raster DIY. * * IL RESTE DES CHOSES À IMPLÉMENTER OU À OPTIMISER. * * \author Farès BELHADJ, amsi@up8.edu * \date November 17, 2021. * * \todo COMPLÉTER LE CLIPPING POUR GÉRER ICI LES TRIANGLES * PARTIELLEMENT HORS-CHAMP ET ÉVITER DES TESTS GOURMANDS DANS \ref * rasterize.c */ #include "rasterize.h" #include /* Fonctions locale (static) */ static inline void clip2_unit_cube(triangle_t * t); /*!\brief Projette le sommet \a v à l'écran (le \a viewport) selon la matrice de model-view \a model_view_matrix et de projection \a projection_matrix. \a ti_model_view_matrix est la transposée de l'inverse de la matrice \a model_view_matrix.*/ vertex_t vtransform(surface_t * s, vertex_t v, float * model_view_matrix, float * ti_model_view_matrix, float * projection_matrix, float * viewport) { float dist = 1.0f; vec4 r1, r2; v.state = PS_NONE; MMAT4XVEC4((float *)&r1, model_view_matrix, (float *)&(v.position)); MMAT4XVEC4((float *)&r2, projection_matrix, (float *)&r1); r2.x /= r2.w; r2.y /= r2.w; r2.z /= r2.w; r2.w = 1.0f; /* dist doit être à 1 ci-après */ if(r2.x < -dist) v.state |= PS_OUT_LEFT; if(r2.x > dist) v.state |= PS_OUT_RIGHT; if(r2.y < -dist) v.state |= PS_OUT_BOTTOM; if(r2.y > dist) v.state |= PS_OUT_TOP; if(r2.z < -dist) v.state |= PS_OUT_NEAR; if(r2.z > dist) v.state |= PS_OUT_FAR; /* "hack" pas terrible permettant d'éviter les gros triangles * partiellement hors-champ. Modifier dist pour jouer sur la taille * (une fois projetés) des triangles qu'on laisse passer (plus c'est * gros plus c'est lent avec les gros triangles). La "vraie" * solution est obtenue en calculant l'intersection exacte entre le * triangle et le cube unitaire ; attention, ceci produit * potentiellement une nouvelle liste de triangles à chaque frame, * et les attributs des sommets doivent être recalculés. */ dist = 10.0f; if(r2.x < -dist || r2.x > dist || r2.y < -dist || r2.y > dist || r2.z < -dist || r2.z > dist) { v.state |= PS_TOO_FAR; return v; } /* Gouraud */ if(s->options & SO_USE_LIGHTING) { /* la lumière est positionnelle et fixe dans la scène. \todo dans * scene.c la rendre modifiable, voire aussi pouvoir la placer par * rapport aux objets (elle subirait la matrice modèle). */ const vec4 lp[1] = { {0.0f, 0.0f, 1.0f} }; vec4 ld = {lp[0].x - r1.x, lp[0].y - r1.y, lp[0].z - r1.z, lp[0].w - r1.w}; float n[4] = {v.normal.x, v.normal.y, v.normal.z, 0.0f}, res[4]; MMAT4XVEC4(res, ti_model_view_matrix, n); MVEC3NORMALIZE(res); MVEC3NORMALIZE((float *)&ld); v.li = MVEC3DOT(res, (float *)&ld); v.li = MIN(MAX(0.0f, v.li), 1.0f); } else v.li = 1.0f; v.icolor = v.color0; /* Mapping du cube unitaire vers l'écran */ v.x = viewport[0] + ((r2.x + 1.0f) * 0.5f) * (viewport[2] - EPSILON); v.y = viewport[1] + ((r2.y + 1.0f) * 0.5f) * (viewport[3] - EPSILON); v.z = pow((-r2.z + 1.0f) * 0.5f, 0.5); /* sinon pour near = 0.1f et far = 10.0f on peut rendre non linéaire la depth avec */ /* v.z = 1.0f - (1.0f / r2.z - 1.0f / 0.1f) / (1.0f / 10.0f - 1.0f / 0.1f); */ v.zmod = r1.z; return v; } /*!\brief Projette le triangle \a t à l'écran (\a W x \a H) selon la * matrice de model-view \a model_view_matrix et de projection \a projection_matrix. * * Cette fonction utilise \a vtransform sur chaque sommet de la * surface. Elle utilise aussi \a clip2_unit_cube pour connaître l'état * du triangle par rapport au cube unitaire. * * \see vtransform * \see clip2_unit_cube */ void stransform(surface_t * s, float * model_view_matrix, float * projection_matrix, float * viewport) { int i, j; float ti_model_view_matrix[16]; triangle_t vcull; /* calcul de la transposée de l'inverse de la matrice model-view * pour la transformation des normales et le calcul du lambertien * utilisé par le shading Gouraud dans vtransform. */ memcpy(ti_model_view_matrix, model_view_matrix, sizeof ti_model_view_matrix); MMAT4INVERSE(ti_model_view_matrix); MMAT4TRANSPOSE(ti_model_view_matrix); for(i = 0; i < s->n; ++i) { s->t[i].state = PS_NONE; for(j = 0; j < 3; ++j) { s->t[i].v[j] = vtransform(s, s->t[i].v[j], model_view_matrix, ti_model_view_matrix, projection_matrix, viewport); if(s->options & SO_CULL_BACKFACES) { vcull.v[j].position.x = s->t[i].v[j].x; vcull.v[j].position.y = s->t[i].v[j].y; vcull.v[j].position.z = 0.0f; } } if(s->options & SO_CULL_BACKFACES) { tnormal(&vcull); if(vcull.normal.z <= 0.0f) { s->t[i].state |= PS_CULL; continue; } } clip2_unit_cube(&(s->t[i])); } } /*!\brief Multiplie deux matrices : \a res = \a res x \a m */ void mult_matrix(float * res, float * m) { /* res = res x m */ float cpy[16]; memcpy(cpy, res, sizeof cpy); MMAT4XMAT4(res, cpy, m); } /*!\brief Ajoute (multiplication droite) une translation à la matrice * \a m */ void translate(float * m, float tx, float ty, float tz) { float mat[] = { 1.0f, 0.0f, 0.0f, tx, 0.0f, 1.0f, 0.0f, ty, 0.0f, 0.0f, 1.0f, tz, 0.0f, 0.0f, 0.0f, 1.0f }; mult_matrix(m, mat); } /*!\brief Ajoute (multiplication droite) une rotation à la matrice \a * m */ void rotate(float * m, float angle, float x, float y, float z) { float n = sqrtf(x * x + y * y + z * z); if ( n > 0.0f ) { float a, s, c, cc, x2, y2, z2, xy, yz, zx, xs, ys, zs; float mat[] = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f }; s = sinf ( a = (angle * (float)M_PI / 180.0f) ); cc = 1.0f - (c = cosf ( a )); x /= n; y /= n; z /= n; x2 = x * x; y2 = y * y; z2 = z * z; xy = x * y; yz = y * z; zx = z * x; xs = x * s; ys = y * s; zs = z * s; mat[0] = (cc * x2) + c; mat[1] = (cc * xy) - zs; mat[2] = (cc * zx) + ys; /* mat[3] = 0.0f; */ mat[4] = (cc * xy) + zs; mat[5] = (cc * y2) + c; mat[6] = (cc * yz) - xs; /* mat[7] = 0.0f; */ mat[8] = (cc * zx) - ys; mat[9] = (cc * yz) + xs; mat[10] = (cc * z2) + c; /* mat[11] = 0.0f; */ /* mat[12] = 0.0f; mat[= 0.0f; mat[14] = 0.0f; mat[15] = 1.0f; */ mult_matrix(m, mat); } } /*!\brief Ajoute (multiplication droite) un scale à la matrice \a m */ void scale(float * m, float sx, float sy, float sz) { float mat[] = { sx , 0.0f, 0.0f, 0.0f, 0.0f, sy, 0.0f, 0.0f, 0.0f, 0.0f, sz, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f }; mult_matrix(m, mat); } /*!\brief Simule une free-camera, voir la doc de gluLookAt */ void lookAt(float * m, float eyeX, float eyeY, float eyeZ, float centerX, float centerY, float centerZ, float upX, float upY, float upZ) { float forward[3], side[3], up[3]; float mat[] = { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f }; forward[0] = centerX - eyeX; forward[1] = centerY - eyeY; forward[2] = centerZ - eyeZ; up[0] = upX; up[1] = upY; up[2] = upZ; MVEC3NORMALIZE(forward); /* side = forward x up */ MVEC3CROSS(side, forward, up); MVEC3NORMALIZE(side); /* up = side x forward */ MVEC3CROSS(up, side, forward); mat[0] = side[0]; mat[1] = side[1]; mat[2] = side[2]; mat[4] = up[0]; mat[5] = up[1]; mat[6] = up[2]; mat[8] = -forward[0]; mat[9] = -forward[1]; mat[10] = -forward[2]; mult_matrix(m, mat); translate(m, -eyeX, -eyeY, -eyeZ); } /*!\brief Intersection triangle-cube unitaire, à compléter (voir le * todo du fichier et le commentaire dans le code) */ void clip2_unit_cube(triangle_t * t) { int i, oleft = 0, oright = 0, obottom = 0, otop = 0, onear = 0, ofar = 0; for (i = 0; i < 3; ++i) { if(t->v[i].state & PS_OUT_LEFT) ++oleft; if(t->v[i].state & PS_OUT_RIGHT) ++oright; if(t->v[i].state & PS_OUT_BOTTOM) ++obottom; if(t->v[i].state & PS_OUT_TOP) ++otop; if(t->v[i].state & PS_OUT_NEAR) ++onear; if(t->v[i].state & PS_OUT_FAR) ++ofar; } if(!(oleft | oright | obottom | otop | onear | ofar)) return; if(oleft == 3 || oright == 3 || obottom == 3 || otop == 3 || onear == 3 || ofar == 3) { t->state |= PS_TOTALLY_OUT; return; } t->state |= PS_PARTIALLY_OUT; /* le cas PARTIALLY_OUT n'est pas réellement géré. Il serait * nécessaire à partir d'ici de construire la liste des triangles * qui repésentent l'intersection entre le triangle d'origine et * le cube unitaire. Ceci permettrait de ne plus avoir besoin de * tester si le pixel produit par le raster est bien dans le * "screen" avant d'écrire ; et aussi de se passer du "hack" * PS_TOO_FAR qui est problématique. Vous pouvez vous inspirer de * ce qui est fait là : * https://github.com/erich666/GraphicsGems/blob/master/gems/PolyScan/poly_clip.c * en le ramenant au cas d'un triangle. */ }