import algebra.*; import java.lang.Math.*; /** * The Rasterizer class is responsible for the discretization of geometric * primitives * (edges and faces) over the screen pixel grid and generates Fragment (pixels * with * interpolated attributes). Those Fragment are then passed to a Shader object, * which will produce the final color of the fragment. * * @author morin, chambon, cdehais */ public class Rasterizer { Shader shader; public Rasterizer(Shader shader) { this.shader = shader; } public void setShader(Shader shader) { this.shader = shader; } /** * Linear interpolation of a Fragment f on the edge defined by Fragment's v1 and * v2 */ private void interpolate2(Fragment v1, Fragment v2, Fragment f) { int x1 = v1.getX(); int y1 = v1.getY(); int x2 = v2.getX(); int y2 = v2.getY(); int x = f.getX(); int y = f.getX(); double alpha; if (Math.abs(x2 - x1) > Math.abs(y2 - y1)) { alpha = (double) (x - x1) / (double) (x2 - x1); } else { if (y2 != y1) { alpha = (double) (y - y1) / (double) (y2 - y1); } else { alpha = 0.5; } } int numAttributes = f.getNumAttributes(); for (int i = 0; i < numAttributes; i++) { f.setAttribute(i, (1.0 - alpha) * v1.getAttribute(i) + alpha * v2.getAttribute(i)); } } /* * Swaps x and y coordinates of the fragment. Used by the Bresenham algorithm. */ private static void swapXAndY(Fragment f) { f.setPosition(f.getY(), f.getX()); } /** * Rasterizes the edge between the projected vectors v1 and v2. * Generates Fragment's and calls the Shader::shade() metho on each of them. */ public void rasterizeEdge(Fragment v1, Fragment v2) { /* Coordinates of V1 and V2 */ int x1 = v1.getX(); int y1 = v1.getY(); int x2 = v2.getX(); int y2 = v2.getY(); // /* For now : just display the vertices */ // Fragment f = new Fragment(0, 0); // int size = 2; // for (int i = 0; i < v1.getNumAttributes(); i++) { // f.setAttribute(i, v1.getAttribute(i)); // } // for (int i = -size; i <= size; i++) { // for (int j = -size; j <= size; j++) { // f.setPosition(x1 + i, y1 + j); // shader.shade(f); // } // } // tracé d'un segment avec l'algo de Bresenham int numAttributes = v1.getNumAttributes(); Fragment fragment = new Fragment(0, 0); // , numAttributes); boolean sym = (Math.abs(y2 - y1) > Math.abs(x2 - x1)); if (sym) { int temp; temp = x1; x1 = y1; y1 = temp; temp = x2; x2 = y2; y2 = temp; } if (x1 > x2) { Fragment ftemp; int temp; temp = x1; x1 = x2; x2 = temp; temp = y1; y1 = y2; y2 = temp; ftemp = v1; v1 = v2; v2 = ftemp; } int ystep; if (y1 < y2) { ystep = 1; } else { ystep = -1; } int x = x1; float y_courant = y1; int y = y1; float delta_y = y2 - y1; float delta_x = x2 - x1; float m = delta_y / delta_x; for (int i = 1; i <= delta_x; i++) { x = x + 1; y_courant = y_courant + m; if ((ystep == 1) && (y_courant < y + 0.5) || ((ystep == -1) && (y_courant > y - 0.5))) { y = y; } else { y = y + ystep; } // envoi du fragment au shader fragment.setPosition(x, y); if (!shader.isClipped(fragment)) { // interpolation des attributs interpolate2(v1, v2, fragment); if (sym) { swapXAndY(fragment); } shader.shade(fragment); } } } static double triangleArea(Fragment v1, Fragment v2, Fragment v3) { return (double) v2.getX() * v3.getY() - v2.getY() * v3.getX() + v3.getX() * v1.getY() - v1.getX() * v3.getY() + v1.getX() * v2.getY() - v2.getX() * v1.getY(); } static protected Matrix makeBarycentricCoordsMatrix(Fragment v1, Fragment v2, Fragment v3) { Matrix C = null; try { C = new Matrix(3, 3); } catch (InstantiationException e) { /* unreached */ } double area = triangleArea(v1, v2, v3); int x1 = v1.getX(); int y1 = v1.getY(); int x2 = v2.getX(); int y2 = v2.getY(); int x3 = v3.getX(); int y3 = v3.getY(); C.set(0, 0, (x2 * y3 - x3 * y2) / area); C.set(0, 1, (y2 - y3) / area); C.set(0, 2, (x3 - x2) / area); C.set(1, 0, (x3 * y1 - x1 * y3) / area); C.set(1, 1, (y3 - y1) / area); C.set(1, 2, (x1 - x3) / area); C.set(2, 0, (x1 * y2 - x2 * y1) / area); C.set(2, 1, (y1 - y2) / area); C.set(2, 2, (x2 - x1) / area); return C; } private int min3(int a, int b, int c) { return Math.min(a, Math.min(b, c)); } private int max3(int a, int b, int c) { return Math.max(a, Math.max(b, c)); } /** * Rasterizes the triangular face made of the Fragment v1, v2 and v3 */ public void rasterizeFace(Fragment v1, Fragment v2, Fragment v3) { Matrix C = makeBarycentricCoordsMatrix(v1, v2, v3); /* iterate over the triangle's bounding box */ int x_hg = min3(v1.getX(), v2.getX(), v3.getX()); int y_hg = min3(v1.getY(), v2.getY(), v3.getY()); int x_bd = max3(v1.getX(), v2.getX(), v3.getX()); int y_bd = max3(v1.getY(), v2.getY(), v3.getY()); for (int px = x_hg; px < x_bd; px++) { boucle: for (int py = y_hg; py < y_bd; py++) { // System.out.println(px); // System.out.println(py); // System.out.println(); Vector3 point = new Vector3(1, px, py); Vector barycentre = null; try { barycentre = C.multiply(point); } catch (SizeMismatchException e) { e.printStackTrace(); } for (int i = 0; i < 3; i++) { // si une des coordonnées barycentrique est négative, // le pixel n'est pas dans le triangle if (barycentre.get(i) < -1e-3) { // on passe au pixel suivant continue boucle; } } // System.out.println(barycentre); // Le pixel est dans le triangle // On créé un fragment Fragment couleur = new Fragment(px, py); // on interpole la couleur for (int i = 0; i < couleur.getNumAttributes(); i++) { double pondere = 0; pondere += v1.getAttribute(i) * barycentre.get(0); pondere += v2.getAttribute(i) * barycentre.get(1); pondere += v3.getAttribute(i) * barycentre.get(2); couleur.setAttribute(i, pondere); } // on affiche le pixel shader.shade(couleur); } } } }