001/*
002
003*/
004
005package renderer.pipeline;
006import  renderer.scene.Vertex;
007import  renderer.scene.LineSegment;
008
009/**
010   Clip a (projected) {@link LineSegment} that sticks out of the
011   view rectangle in the image plane. Interpolate {@link Vertex}
012   color from any clipped off {@code Vertex} to the new {@code Vertex}.
013<p>
014   This clipping algorithm is a simplification of the Liang-Barsky Parametric
015   Line Clipping algorithm.
016<p>
017   This algorithm assumes that all {@code LineSegment} objects have been
018   projected onto the {@link Camera}'s image plane, {@code z = -1}. This
019   algorithm also assumes that the camera's view rectangle in the image
020   plane is
021   <pre>{@code
022      -1 <= x <= +1  and
023      -1 <= y <= +1.
024   }</pre>
025<p>
026   If a line segment's projected vertex has an {@code x} or {@code y}
027   coordinate with absolute value greater than 1, then that vertex
028   "sticks out" of the view rectangle. This algorithm will clip the
029   line segment so that both of the line segment's vertices are within
030   the view rectangle with {@code -1 <= x <= +1} and {@code -1 <= y <= +1}.
031<p>
032   Here is an outline of the clipping algorithm.
033<p>
034   Recursively process each line segment, using the following steps.
035<p>
036     1) Test if the line segment no longer needs to be clipped, i.e., both
037        of its vertices are within the clipping rectangle. If this is the
038        case, then return true.
039        <pre>{@code
040               x=-1        x=+1
041                 |          |
042                 |          |
043             ----+----------+----- y = +1
044                 |     v1   |
045                 |    /     |
046                 |   /      |
047                 |  /       |
048                 | v0       |
049             ----+----------+----- y = -1
050                 |          |
051                 |          |
052        }</pre>
053<p>
054     2) Test if the line segment should be "trivially rejected". A line
055        segment is "trivially rejected" if it is on the wrong side of any
056        of the four lines that bound the view rectangle (i.e., the four
057        lines {@code x = 1}, {@code x = -1}, {@code y = 1}, {@code y = -1}).
058        If so, then return {@code false} (so the line segment will not be
059        rasterized into the framebuffer).
060<p>
061        Notice that a line like the following one is trivially rejected
062        because it is on the "wrong" side of the line {@code x = 1}.
063        <pre>{@code
064                           x=1
065                            |                      v1
066                            |                    /
067                 +----------+                  /
068                 |          |                /
069                 |          |              /
070                 |          |            /
071                 |          |          /
072                 |          |        /
073                 +----------+      /
074                            |    /
075                            |  v0
076        }</pre>
077        But the following line is NOT trivially rejected because, even
078        though it is completely outside of the view rectangle, this line
079        is not entirely on the wrong side of any one of the four lines
080        {@code x = 1}, {@code x = -1}, {@code y = 1}, or {@code y = -1}.
081        The line below will get clipped at least one time (either on the
082        line {@code x = 1} or the line {@code y = -1}) before it is
083        (recursively) a candidate for "trivial rejection". Notice that
084        the line below could even be clipped twice, first on {@code y = 1},
085        then on {@code x = 1}, before it can be trivially rejected (by
086        being on the wrong side of {@code y = -1}).
087        <pre>{@code
088                           x=1
089                            |                      v1
090                            |                    /
091                 +----------+                  /
092                 |          |                /
093                 |          |              /
094                 |          |            /
095                 |          |          /
096                 |          |        /
097                 +----------+      /
098                            |    /
099                            |  /
100                             /
101                           /
102                        v0  |
103                            |
104        }</pre>
105<p>
106     3) If the line segment has been neither accepted nor rejected, then
107        it needs to be clipped. So we test the line segment against each
108        of the four clipping lines, {@code x = 1}, {@code x = -1},
109        {@code y = 1}, and {@code y = -1}, to determine if the line segment
110        crosses one of those lines. We clip the line segment against the
111        first line which we find that it crosses. Then we recursively clip
112        the resulting clipped line segment. Notice that we only clip against
113        the first clipping line which the segment is found to cross. We do
114        not continue to test against the other clipping lines. This is
115        because it may be the case, after just one clip, that the line
116        segment is now a candidate for trivial accept or reject. So rather
117        than test the line segment against several more clipping lines
118        (which may be useless tests) it is more efficient to recursively
119        clip the line segment, which will then start with the trivial accept
120        or reject tests.
121<p>
122        When we clip a line segment against a clipping line, it is always
123        the case that one endpoint of the line segment is on the "right"
124        side of the clipping line and the other endpoint of the line segment
125        is on the "wrong" side of the clipping line. In the following picture,
126        assume that {@code v0} is on the "wrong" side of the clipping line
127        ({@code x = 1}) and {@code v1} is on the "right" side. So {@code v0}
128        needs to be clipped off the line segment and replaced by a new
129        endpoint.
130        <pre>{@code
131                             x=1
132                              |
133                        v1    |
134                          \   |
135                            \ |
136                              \
137                              | \
138                              |   \
139                              |     v0
140        }</pre>
141        Represent points {@code p(t)} on the line segment between {@code v0}
142        and {@code v1} with the following parametric equation.
143        <pre>{@code
144                  p(t) = (1-t) * v0 + t * v1  with  0 <= t <= 1
145        }</pre>
146        Notice that this equation parameterizes the line segment starting
147        with {@code v0} at {@code t=0} (on the "wrong side") and ending
148        with {@code v1} at {@code t=1} (on the "right side"). We need to
149        find the value of {@code t} when the line segment crosses the
150        clipping line {@code x = 1}. Let {@code v0 = (x0, y0)} and let
151        {@code v1 = (x1, y1)}. Then the above parametric equation becomes
152        the two component equations
153        <pre>{@code
154                 x(t) = (1-t) * x0 + t * x1,
155                 y(t) = (1-t) * y0 + t * y1,  with  0 <= t <= 1.
156        }</pre>
157        Since the clipping line in this example is {@code x = 1}, we need
158        to solve the equation {@code x(t) = 1} for {@code t}. So we need
159        to solve
160        <pre>{@code
161                  (1-t) * x0 + t * x1 = 1
162        }</pre>
163        for {@code t}. Here are a few algebra steps.
164        <pre>{@code
165                  x0 - t * x0 + t * x1 = 1
166                  x0 + t * (x1 - x0) = 1
167                       t * (x1 - x0) = 1 - x0
168                       t = (1 - x0)/(x1 - x0)
169        }</pre>
170        We get similar equations for {@code t} if we clip against the other
171        clipping lines ({@code x = -1}, {@code y = 1}, or {@code y = -1}) and
172        we assume that {@code v0} is on the "wrong side" and {@code v1} is on
173        the "right side". With the above value for {@code t}, the new vertex
174        {@code p(t)} that replaces {@code v0} can easily be computed.
175        <pre>{@code
176                             x=1
177                              |
178                        v1    |
179                          \   |
180                            \ |
181                              v0=p( (1 - x0)/(x1 - x0) )
182                              |
183                              |
184                              |
185         }</pre>
186         Finally, the new line segment between {@code v1} and the new
187         {@code v0} is recursively clipped so that it can be checked
188         once again to see if it can be trivially accepted, trivially
189         rejected, or clipped again.
190*/
191public class Clip
192{
193   public static boolean debug = false;
194
195   /**
196      If the {@link LineSegment} sticks out of the view rectangle,
197      then clip it so that it is contained in the view rectangle.
198
199      @param ls  {@link LineSegment} to be clipped
200      @return a boolean that indicates if this line segment is within the view rectangle
201   */
202   public static boolean clip(LineSegment ls)
203   {
204      // Make local copies of several values.
205      Vertex v0 = ls.model.vertexList.get( ls.index[0] );
206      Vertex v1 = ls.model.vertexList.get( ls.index[1] );
207
208      double x0 = v0.x,  y0 = v0.y;
209      double x1 = v1.x,  y1 = v1.y;
210
211      // 1. Check for trivial accept.
212      if ( ! ( Math.abs( x0 ) > 1
213            || Math.abs( y0 ) > 1
214            || Math.abs( x1 ) > 1
215            || Math.abs( y1 ) > 1 ) )
216      {
217         if (debug) System.out.println(" Trivial accept.");
218         return true;
219      }
220      // 2. Check for trivial delete.
221      else if ( (x0 >  1 && x1 >  1)   // to the right of the line x = 1
222             || (x0 < -1 && x1 < -1)   // to the left of the line x = -1
223             || (y0 >  1 && y1 >  1)   // above the line y = 1
224             || (y0 < -1 && y1 < -1) ) // below the line y = -1
225      {
226         if (debug) System.out.println(" Trivial delete.");
227         return false;
228      }
229      // 3. Need to clip this line segment.
230      else if (x0 > 1 || x1 > 1)  // ls crosses the line x = 1
231      {
232         if (x1 > 1)  // clip off v1
233         {
234            if (debug) System.out.println(" Clip off v1 at x = 1.");
235            // Create a new Vertex between v0 and v1.
236            Vertex v_new = interpolateNewVertex(v0, v1, 1);
237            // Modify the Model and LineSegment to contain the new Vertex.
238            int index = ls.model.vertexList.size();
239            ls.model.addVertex(v_new);
240            ls.index[1] = index;
241         }
242         else // (x0 > 1)  // clip off v0
243         {
244            if (debug) System.out.println(" Clip off v0 at x = 1.");
245            // Create a new Vertex between v1 and v0.
246            Vertex v_new = interpolateNewVertex(v1, v0, 1);
247            // Modify the Model and LineSegment to contain the new Vertex.
248            int index = ls.model.vertexList.size();
249            ls.model.addVertex(v_new);
250            ls.index[0] = index;
251         }
252      }
253      else if (x0 < -1 || x1 < -1)  // ls crosses the line x = -1
254      {
255         if (x1 < -1)  // clip off v1
256         {
257            if (debug) System.out.println(" Clip off v1 at x = -1.");
258            // Create a new Vertex between v0 and v1.
259            Vertex v_new = interpolateNewVertex(v0, v1, 2);
260            // Modify the Model and LineSegment to contain the new Vertex.
261            int index = ls.model.vertexList.size();
262            ls.model.addVertex(v_new);
263            ls.index[1] = index;
264         }
265         else // (x0 < -1)  // clip off v0
266         {
267            if (debug) System.out.println(" Clip off v0 at x = -1.");
268            // Create a new Vertex between v1 and v0.
269            Vertex v_new = interpolateNewVertex(v1, v0, 2);
270            // Modify the Model and LineSegment to contain the new Vertex.
271            int index = ls.model.vertexList.size();
272            ls.model.addVertex(v_new);
273            ls.index[0] = index;
274         }
275      }
276      else if (y0 > 1 || y1 > 1)  // ls crosses the line y = 1
277      {
278         if (y1 > 1)  // clip off v1
279         {
280            if (debug) System.out.println(" Clip off v1 at y = 1.");
281            // Create a new Vertex between v0 and v1.
282            Vertex v_new = interpolateNewVertex(v0, v1, 3);
283            // Modify the Model and LineSegment to contain the new Vertex.
284            int index = ls.model.vertexList.size();
285            ls.model.addVertex(v_new);
286            ls.index[1] = index;
287         }
288         else // (y0 > 1)  // clip off v0
289         {
290            if (debug) System.out.println(" Clip off v0 at y = 1.");
291            // Create a new Vertex between v1 and v0.
292            Vertex v_new = interpolateNewVertex(v1, v0, 3);
293            // Modify the Model and LineSegment to contain the new Vertex.
294            int index = ls.model.vertexList.size();
295            ls.model.addVertex(v_new);
296            ls.index[0] = index;
297         }
298      }
299      else if (y0 < -1 || y1 < -1)  // ls crosses the line y = -1
300      {
301         if (y1 < -1)  // clip off v1
302         {
303            if (debug) System.out.println(" Clip off v1 at y = -1.");
304            // Create a new Vertex between v0 and v1.
305            Vertex v_new = interpolateNewVertex(v0, v1, 4);
306            // Modify the Model and LineSegment to contain the new Vertex.
307            int index = ls.model.vertexList.size();
308            ls.model.addVertex(v_new);
309            ls.index[1] = index;
310         }
311         else // (y0 < -1)  // clip off v0
312         {
313            if (debug) System.out.println(" Clip off v0 at y = -1.");
314            // Create a new Vertex between v1 and v0.
315            Vertex v_new = interpolateNewVertex(v1, v0, 4);
316            // Modify the Model and LineSegment to contain the new Vertex.
317            int index = ls.model.vertexList.size();
318            ls.model.addVertex(v_new);
319            ls.index[0] = index;
320         }
321      }
322      else // We should never get here.
323      {
324         System.err.println("Clipping Error!");
325         Thread.dumpStack();
326       //System.err.println(Arrays.toString(Thread.currentThread().getStackTrace()));
327         System.exit(-1);
328      }
329      return clip(ls); // recursively clip this line segment again
330   }
331
332
333   /**
334      This method takes in two vertices, one that is on the "right" side
335      of a clipping line and the other that is on the "wrong" side of the
336      clipping line, and an integer which specifies which clipping line
337      to use, where
338      <pre>{@code
339         eqn_number == 1 means clipping line x =  1
340         eqn_number == 2 means clipping line x = -1
341         eqn_number == 3 means clipping line y =  1
342         eqn_number == 4 means clipping line y = -1
343      }</pre>
344      This method returns the vertex that is the intersection point
345      between the given line segment and the given clipping line.
346      <p>
347      This method solves for the value of {@code t} for which the
348      parametric equation
349      <pre>{@code
350                  p(t) = (1-t) * v_outside + t * v_inside
351      }</pre>
352      intersects the given clipping line. (Notice that the equation
353      is parameterized so that we move from the outside vertex towards
354      the inside vertex as {@code t} increases from 0 to 1.) The solved
355      for value of {@code t} is then plugged into the parametric formula
356      to get the coordinates of the intersection point.
357
358      @param v_inside    the Vertex that is inside the view rectangle
359      @param v_outside   the Vertex that is outside the view rectangle
360      @param eqn_number  the identifier of the view rectangle edge crossed by the line segment
361      @return a Vertex object that replaces the clipped off vertex
362   */
363   private static Vertex interpolateNewVertex(Vertex v_inside,
364                                              Vertex v_outside,
365                                              int eqn_number)
366   {
367      if (debug) System.out.println("-Create new vertex.");
368
369      // Make local copies of several values.
370      double vix =  v_inside.x; // "i" for "inside"
371      double viy =  v_inside.y;
372      double vox = v_outside.x; // "o" for "outside"
373      double voy = v_outside.y;
374      // Interpolate between v_outside and v_inside.
375      double t = 0.0;
376      if (1 == eqn_number)            // clip to x = 1
377         t = (1 - vox) / (vix - vox);
378      else if (2 == eqn_number)       // clip to x = -1
379         t = (-1 - vox) / (vix - vox);
380      else if (3 == eqn_number)       // clip to y = 1
381         t = (1 - voy) / (viy - voy);
382      else if (4 == eqn_number)       // clip to y = -1
383         t = (-1 - voy) / (viy - voy);
384
385      // Use the value of t to interpolate the coordinates of the new vertex.
386      double x = (1-t) * vox + t * vix;
387      double y = (1-t) * voy + t * viy;
388
389      Vertex v_new = new Vertex(x, y, 0);
390
391      // Use the value of t to interpolate the color of the new vertex.
392      double r = (1-t) * v_outside.r + t * v_inside.r;
393      double g = (1-t) * v_outside.g + t * v_inside.g;
394      double b = (1-t) * v_outside.b + t * v_inside.b;
395
396      if (debug)
397      {
398         System.out.printf("- t = % .25f\n", t);
399         System.out.printf("- <x_o,y_o> = <% .24f % .24f\n",  vox, voy);
400         System.out.printf("- <x_i,y_i> = <% .24f % .24f\n",  vix, viy);
401         System.out.printf("- <x,  y>   = <% .24f % .24f\n",    x,   y);
402         System.out.printf("- <r_o,g_o,b_o> = <% .15f  % .15f  % .15f>\n",
403                              v_outside.r, v_outside.g, v_outside.b);
404         System.out.printf("- <r_i,g_i,b_i> = <% .15f  % .15f  % .15f>\n",
405                              v_inside.r, v_inside.g, v_inside.b);
406         System.out.printf("- <r,  g,  b>   = <% .15f  % .15f  % .15f>\n",
407                               r,  g,  b);
408      }
409
410      v_new.setColor((float)r, (float)g, (float)b);
411
412      return v_new;
413   }
414}