This repository has been archived on 2022-03-31. You can view files and clone it, but cannot push or open issues or pull requests.
Bomberman/vtransform.c
2021-12-31 14:48:24 +01:00

241 lines
9.5 KiB
C

/*!\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 <assert.h>
/* 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. */
}