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