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}