001/*
002
003*/
004
005package renderer.pipeline;
006import  renderer.scene.*;
007import  renderer.framebuffer.*;
008
009import java.awt.Color;
010
011/**
012   Rasterize a {@link LineSegment} into pixels in the
013   {@link FrameBuffer}'s viewport.
014<p>
015   This pipeline stage takes a {@link LineSegment} with vertices
016   in the logical viewport coordinate system and rasterizezs the
017   line segment into pixels in the {@link FrameBuffer}'s viewport.
018<p>
019   Notice that we have two uses of the word "viewport",
020   <ul>
021   <li>The "logical viewport" is a rectangle in a plane (so its points
022       have real number coordinates). The "logical pixels" are the points
023       with integer coordinates.
024   <li>The "physical viewport" is part of the {@link FrameBuffer}'s pixel
025       array (so its entries have integer coordinates)
026   </ul>
027<p>
028   This rasterization algorithm is based on
029<pre>
030     "Fundamentals of Computer Graphics", 3rd Edition,
031      by Peter Shirley, pages 163-165.
032</pre>
033*/
034public class Rasterize
035{
036   public static boolean debug = false;
037
038   /**
039      Rasterize a {@link LineSegment} into pixels in the
040      {@link FrameBuffer}'s viewport.
041
042      @param ls  {@link LineSegment} to rasterize into the {@link FrameBuffer}
043      @param fb  {@link FrameBuffer} to hold rasterized pixels
044   */
045   public static void rasterize(LineSegment ls, FrameBuffer fb)
046   {
047      // Make local copies of several values.
048      int w = fb.getWidthVP();
049      int h = fb.getHeightVP();
050
051      Vertex v0 = ls.model.vertexList.get( ls.index[0] );
052      Vertex v1 = ls.model.vertexList.get( ls.index[1] );
053
054      // Get the colors from the two vertices.
055      float r0 = v0.r,  g0 = v0.g,  b0 = v0.b;
056      float r1 = v1.r,  g1 = v1.g,  b1 = v1.b;
057
058      // Round the line segment's two endpoints to the nearest
059      // logical pixel. This makes the algorithm a lot simpler,
060      // but it can cause a slight, but noticable, shift of the
061      // line segment.
062      double x0 = Math.round(v0.x),  y0 = Math.round(v0.y);
063      double x1 = Math.round(v1.x),  y1 = Math.round(v1.y);
064
065      // Rasterize a degenerate line segment (a line segment
066      // that projected onto a point) as a single pixel.
067      if ( (x0 == x1) && (y0 == y1) )
068      {
069         // We don't know which endpoint of the line segment
070         // is in front, so just pick v0.
071         fb.setPixelVP((int)x0 - 1, h - (int)y0, new Color(r0, g0, b0));
072         return;
073      }
074
075      if (Math.abs(y1 - y0) <= Math.abs(x1 - x0)) // if abs(slope) <= 1
076      {
077         if (x1 < x0) // swap (x0, y0) with (x1, y1)
078         {
079            double tempX = x0;
080            double tempY = y0;
081            x0 = x1;
082            y0 = y1;
083            x1 = tempX;
084            y1 = tempY;
085            // swap the colors too
086            float tempR = r0;
087            float tempG = g0;
088            float tempB = b0;
089            r0 = r1;
090            g0 = g1;
091            b0 = b1;
092            r1 = tempR;
093            g1 = tempG;
094            b1 = tempB;
095         }
096
097         // Compute this line segment's slope.
098         double      m = (y1 - y0) / (x1 - x0);
099         double slopeR = (r1 - r0) / (x1 - x0);
100         double slopeG = (g1 - g0) / (x1 - x0);
101         double slopeB = (b1 - b0) / (x1 - x0);
102
103         // In the following loop, as x moves across the logical
104         // horizontal pixels, we will compute a y value for each x.
105         double y = y0;
106
107         // Rasterize this line segment.
108         for (int x = (int)x0; x <= (int)x1; x++, y += m)
109         {
110            // Interpolate this pixel's color between the two endpoint's colors.
111            float r = (float)Math.abs(r0 + slopeR*(x - x0));
112            float g = (float)Math.abs(g0 + slopeG*(x - x0));
113            float b = (float)Math.abs(b0 + slopeB*(x - x0));
114            // We need the Math.abs() because otherwise, we sometimes get -0.0.
115
116            // The value of y will almost always be between
117            // two vertical pixel coordinates. By rounding off
118            // the value of y, we are choosing the nearest logical
119            // vertical pixel coordinate.
120            fb.setPixelVP(x - 1, h - (int)Math.round(y), new Color(r, g, b));
121
122            // log interesting information to standard output
123            if (debug) logPixel(w, h, x, y, r, g, b);
124
125            // Advance (x,y) to the next pixel. Since delta_x = 1, we need delta_y = m.
126         }
127      }
128      else  // abs(slope) > 1
129      {
130         if (y1 < y0) // swap (x0, y0) with (x1, y1)
131         {
132            double tempX = x0;
133            double tempY = y0;
134            x0 = x1;
135            y0 = y1;
136            x1 = tempX;
137            y1 = tempY;
138            // swap the colors too
139            float tempR = r0;
140            float tempG = g0;
141            float tempB = b0;
142            r0 = r1;
143            g0 = g1;
144            b0 = b1;
145            r1 = tempR;
146            g1 = tempG;
147            b1 = tempB;
148         }
149
150         // Compute this line segment's slope.
151         double      m = (x1 - x0) / (y1 - y0);
152         double slopeR = (r1 - r0) / (y1 - y0);
153         double slopeG = (g1 - g0) / (y1 - y0);
154         double slopeB = (b1 - b0) / (y1 - y0);
155
156         // In the following loop, as y moves across the logical
157         // vertical pixels, we will compute a x value for each y.
158         double x = x0;
159
160         // Rasterize this line segment.
161         for (int y = (int)y0; y <= (int)y1; x += m, y++)
162         {
163            // Interpolate this pixel's color between the two endpoint's colors.
164            float r = (float)Math.abs(r0 + slopeR*(y - y0));
165            float g = (float)Math.abs(g0 + slopeG*(y - y0));
166            float b = (float)Math.abs(b0 + slopeB*(y - y0));
167            // We need the Math.abs() because otherwise, we sometimes get -0.0.
168
169            // The value of x will almost always be between
170            // two horizontal pixel coordinates. By rounding off
171            // the value of x, we are choosing the nearest logical
172            // horizontal pixel coordinate.
173            fb.setPixelVP((int)Math.round(x) - 1, h - y, new Color(r, g, b));
174
175            // log interesting information to standard output
176            if (debug) logPixel(w, h, x, y, r, g, b);
177
178            // Advance (x,y) to the next pixel. Since delta_y = 1, we need delta_x = m.
179         }
180      }
181   }
182
183
184   private static void logPixel(int w, int h, int x, double y,
185                                              float r, float g, float b)
186   {
187      System.out.printf("[w=%d, h=%d]  x=%d, y=%f, r=%f, g=%f, b=%f\n",
188                          w, h, x, y, r, g, b);
189   }
190
191   private static void logPixel(int w, int h, double x, int y,
192                                              float r, float g, float b)
193   {
194      System.out.printf("[w=%d, h=%d]  x=%f, y=%d, r=%f, g=%f, b=%f\n",
195                          w, h, x, y, r, g, b);
196   }
197}