001/* 002 * Renderer 9. The MIT License. 003 * Copyright (c) 2022 rlkraft@pnw.edu 004 * See LICENSE for details. 005*/ 006 007package renderer.scene; 008 009/** 010 This {@code Camera} data structure represents a camera 011 that can be translated around within world coordinates. 012<p> 013 This {@code Camera} has a {@link viewVector} which associates 014 to this {@code Camera} a location in the world coordinate system. 015 A {@code Camera} object is moved within world coordinates by 016 changing its {@link viewVector}. 017<p> 018 A {@code Camera}'s {@link translate} method is used to move 019 the {@code Camera} in world coordinates exactly as the 020 {@link Position#translate} method is used to move a 021 {@link Model} in world coordinates. 022<p> 023 The renderer uses the camera's {@link viewVector} in its 024 {@link renderer.pipeline.World2View} pipeline stage. In that stage, 025 the renderer subtracts the camera's {@link viewVector} from every 026 vertex of every model in the scene to convert each vertex from 027 world coordinates to the camera's view coordinate system. The reason 028 for using subtraction is that when the camera moves forward by 5 units 029 in the z-direction, every vertex in the scene will appear 5 units 030 closer to the camera. So every vertex in the scene needs a translation 031 by -5 in the z-direction. Similarly, if the camera moves up 5 units 032 in the y-direction, every vertex in the scene will appear to the 033 camera to be 5 units lower. So every vertex in the scene needs a 034 translation by -5 in the y-direction. 035<p> 036 This {@code Camera} has a configurable "view volume" that 037 determines what part of world space the camera "sees" when 038 we use the camera to take a picture (that is, when we render 039 a {@link Scene}). 040<p> 041 This {@code Camera} can "take a picture" two ways, using 042 a perspective projection or a parallel (orthographic) 043 projection. Each way of taking a picture has a different 044 shape for its view volume. The data in this data structure 045 determines the shape of each of the two view volumes. 046<p> 047 For the perspective projection, the view volume (in view 048 coordinates!) is the infinitely long frustum that is formed 049 by cutting at the near clipping plane, {@code z = -near}, 050 the infinitely long pyramid with its apex at the origin 051 and its cross section in the image plane, {@code z = -1}, 052 with edges {@code x = -1}, {@code x = +1}, {@code y = -1}, 053 and {@code y = +1}. The perspective view volume's shape is 054 set by the {@link projPerspective} method. 055<p> 056 For the orthographic projection, the view volume (in view 057 coordinates!) is the semi-infinite rectangular cylinder 058 parallel to the z-axis, with base in the near clipping plane, 059 {@code z = -near}, and with edges {@code x = left}, 060 {@code x = right}, {@code y = bottom}, {@code y = top} (a 061 semi-infinite parallelepiped). The orthographic view volume's 062 shape is set by the {@link projOrtho} method. 063<p> 064 When the graphics rendering {@link renderer.pipeline.Pipeline} 065 uses this {@code Camera} to render a {@link Scene}, the renderer 066 only "sees" the geometry from the scene that is contained in this 067 camera's view volume. (Notice that this means the orthographic 068 camera can see geometry that is behind the camera. In fact, the 069 perspective camera can also sees geometry that is behind the 070 camera.) The renderer's {@link renderer.pipeline.NearClip} and 071 {@link renderer.pipeline.Clip} pipeline stages are responsible 072 for making sure that the scene's geometry that is outside of 073 this camera's view volume is not visible. 074<p> 075 The plane {@code z = -1} (in view coordinates) is the camera's 076 "image plane". The rectangle in the image plane with corners 077 {@code (left, bottom, -1)} and {@code (right, top, -1)} is the 078 camera's "view rectangle". The view rectangle is like the film 079 in a real camera, it is where the camera's image appears when you 080 take a picture. The contents of the camera's view rectangle (after 081 it gets "normalized" to camera coordinates by the renderer's 082 {@link renderer.pipeline.View2Camera} stage) is what gets rasterized, 083 by the renderer's {@link renderer.pipeline.Rasterize} 084 pipeline stage, into a {@link renderer.framebuffer.FrameBuffer}'s 085 {@link renderer.framebuffer.FrameBuffer.Viewport}. 086<p> 087 For both the perspective and the parallel projections, the camera's 088 near plane is there to prevent the camera from seeing what is "behind" 089 the near plane. For the perspective projection, the near plane also 090 prevents the renderer from incorrectly rasterizing line segments that 091 cross the camera plane, {@code z = 0}. 092*/ 093public final class Camera 094{ 095 // Choose either perspective or parallel projection. 096 public final boolean perspective; 097 // The following five numbers define the camera's view volume. 098 // The first four will be encoded into the camera's normalization matrix. 099 public final double left; 100 public final double right; 101 public final double bottom; 102 public final double top; 103 public final double n; // near clipping plane 104 // This Vector determines the Camera's location in world space. 105 private Vector viewVector; 106 107 /** 108 A private {@code Camera} constructor for 109 use by the static factory methods. 110 */ 111 private Camera(final boolean perspective, 112 final double left, 113 final double right, 114 final double bottom, 115 final double top, 116 final double n, 117 final Vector viewVector) 118 { 119 this.perspective = perspective; 120 this.left = left; 121 this.right = right; 122 this.bottom = bottom; 123 this.top = top; 124 this.n = n; 125 this.viewVector = viewVector; 126 } 127 128 129 /** 130 This is a static factory method. 131 <p> 132 Set up this {@code Camera}'s view volume as a perspective projection 133 of the normalized infinite view pyramid extending along the negative 134 z-axis. 135 136 @return a new {@code Camera} object with the default perspective parameters 137 */ 138 public static Camera projPerspective() 139 { 140 return projPerspective(-1.0, +1.0, -1.0, +1.0); // left, right, bottom, top 141 } 142 143 144 /** 145 This is a static factory method. 146 <p> 147 Set up this {@code Camera}'s view volume as a perspective projection 148 of an infinite view pyramid extending along the negative z-axis. 149 150 @param left left edge of view rectangle in the image plane 151 @param right right edge of view rectangle in the image plane 152 @param bottom bottom edge of view rectangle in the image plane 153 @param top top edge of view rectangle in the image plane 154 @return a new {@code Camera} object with the given parameters 155 */ 156 public static Camera projPerspective(final double left, final double right, 157 final double bottom, final double top) 158 { 159 return projPerspective(left, right, bottom, top, 1.0); 160 } 161 162 163 /** 164 This is a static factory method. 165 <p> 166 Set up this {@code Camera}'s view volume as a perspective projection 167 of an infinite view pyramid extending along the negative z-axis. 168 <p> 169 Use {@code focalLength} to determine the image plane. So the 170 {@code left}, {@code right}, {@code bottom}, {@code top} 171 parameters are used in the plane {@code z = -focalLength}. 172 <p> 173 The {@code focalLength} parameter can be used to zoom an 174 asymmetric view volume, much like the {@code fovy} parameter 175 for the symmetric view volume, or the "near" parameter for 176 the OpenGL gluPerspective() function. 177 178 @param left left edge of view rectangle in the image plane 179 @param right right edge of view rectangle in the image plane 180 @param bottom bottom edge of view rectangle in the image plane 181 @param top top edge of view rectangle in the image plane 182 @param focalLength distance from the origin to the image plane 183 @return a new {@code Camera} object with the given parameters 184 */ 185 public static Camera projPerspective(final double left, final double right, 186 final double bottom, final double top, 187 final double focalLength) 188 { 189 return new Camera(true, 190 left / focalLength, 191 right / focalLength, 192 bottom / focalLength, 193 top / focalLength, 194 -0.1, // near clipping plane (near = +0.1) 195 new Vector(0,0,0)); // viewVector 196 } 197 198 199 /** 200 This is a static factory method. 201 <p> 202 Set up this {@code Camera}'s view volume as a symmetric infinite 203 view pyramid extending along the negative z-axis. 204 <p> 205 Here, the view volume is determined by a vertical "field of view" 206 angle and an aspect ratio for the view rectangle in the image plane. 207 208 @param fovy angle in the y-direction subtended by the view rectangle in the image plane 209 @param aspect aspect ratio of the view rectangle in the image plane 210 @return a new {@code Camera} object with the given parameters 211 */ 212 public static Camera projPerspective(final double fovy, final double aspect) 213 { 214 final double top = Math.tan((Math.PI/180.0)*fovy/2.0); 215 final double bottom = -top; 216 final double right = top * aspect; 217 final double left = -right; 218 219 return projPerspective(left, right, bottom, top); 220 } 221 222 223 /** 224 This is a static factory method. 225 <p> 226 Set up this {@code Camera}'s view volume as a parallel (orthographic) 227 projection of the normalized infinite view parallelepiped extending 228 along the z-axis. 229 230 @return a new {@code Camera} object with the default orthographic parameters 231 */ 232 public static Camera projOrtho() 233 { 234 return projOrtho(-1.0, +1.0, -1.0, +1.0); // left, right, bottom, top 235 } 236 237 238 /** 239 This is a static factory method. 240 <p> 241 Set up this {@code Camera}'s view volume as a parallel (orthographic) 242 projection of an infinite view parallelepiped extending along the 243 z-axis. 244 245 @param left left edge of view rectangle in the xy-plane 246 @param right right edge of view rectangle in the xy-plane 247 @param bottom bottom edge of view rectangle in the xy-plane 248 @param top top edge of view rectangle in the xy-plane 249 @return a new {@code Camera} object with the given parameters 250 */ 251 public static Camera projOrtho(final double left, final double right, 252 final double bottom, final double top) 253 { 254 return new Camera(false, 255 left, 256 right, 257 bottom, 258 top, 259 +1.0, // near clipping plane (near = -1.0) 260 new Vector(0,0,0)); // viewVector 261 } 262 263 264 /** 265 This is a static factory method. 266 <p> 267 Set up this {@code Camera}'s view volume as a symmetric infinite 268 view parallelepiped extending along the z-axis. 269 <p> 270 Here, the view volume is determined by a vertical "field-of-view" 271 angle and an aspect ratio for the view rectangle in the image plane. 272 273 @param fovy angle in the y-direction subtended by the view rectangle in the image plane 274 @param aspect aspect ratio of the view rectangle in the image plane 275 @return a new {@code Camera} object with the given parameters 276 */ 277 public static Camera projOrtho(final double fovy, final double aspect) 278 { 279 final double top = Math.tan((Math.PI/180.0)*fovy/2.0); 280 final double bottom = -top; 281 final double right = top * aspect; 282 final double left = -right; 283 284 return projOrtho(left, right, bottom, top); 285 } 286 287 288 /** 289 Create a new {@code Camera} that is essentially the same as this 290 {@code Camera} but with the given distance from the camera to 291 the near clipping plane. 292 <p> 293 When {@code near} is positive, the near clipping plane is in 294 front of the camera. When {@code near} is negative, the near 295 clipping plane is behind the camera. 296 297 @param near distance from the new {@code Camera} to its near clipping plane 298 @return a new {@code Camera} object with the given value for near 299 */ 300 public Camera changeNear(final double near) 301 { 302 return new Camera(this.perspective, 303 this.left, 304 this.right, 305 this.bottom, 306 this.top, 307 -near, 308 this.viewVector); 309 } 310 311 312 /** 313 Create a new {@code Camera} that is essentially the same as 314 this {@code Camera} but with the given location. 315 316 @param x translated location, in the x-direction, for the new {@code Camera} 317 @param y translated location, in the y-direction, for the new {@code Camera} 318 @param z translated location, in the z-direction, for the new {@code Camera} 319 @return a new {@code Camera} object with the given translated location 320 */ 321 public Camera translate(final double x, 322 final double y, 323 final double z) 324 { 325 return new Camera(this.perspective, 326 this.left, 327 this.right, 328 this.bottom, 329 this.top, 330 this.n, 331 new Vector(x, y, z)); // viewVector 332 } 333 334 335 /** 336 Get a reference to this {@code Camera}'s view {@link Vector}. 337 338 @return a reference to this {@code Camera}'s {@link Vector} object 339 */ 340 public Vector getViewVector() 341 { 342 return this.viewVector; 343 } 344 345 346 /** 347 Get a reference to this {@code Camera}'s normalization {@link Matrix}. 348 349 @return a reference to this {@code Camera}'s normalizing {@link Matrix} object 350 */ 351 public Matrix getNormalizeMatrix() 352 { 353 if (perspective) 354 { 355 return PerspectiveNormalizeMatrix.build(left, right, bottom, top); 356 } 357 else 358 { 359 return OrthographicNormalizeMatrix.build(left, right, bottom, top); 360 } 361 } 362 363 364 /** 365 For debugging. 366 367 @return {@link String} representation of this {@code Camera} object 368 */ 369 public String toString() 370 { 371 final double fovy = (180./Math.PI) * Math.atan(top) 372 + (180./Math.PI) * Math.atan(-bottom); 373 final double ratio = (right - left) / (top - bottom); 374 String result = ""; 375 result += "Camera: \n"; 376 result += " perspective = " + perspective + "\n"; 377 result += " left = " + left + ", " 378 + " right = " + right + "\n" 379 + " bottom = " + bottom + ", " 380 + " top = " + top + "\n" 381 + " near = " + -n + "\n" 382 + " (fovy = " + fovy 383 + ", aspect ratio = " + String.format("%.2f", ratio) + ")\n" 384 + "Normalization Matrix\n" 385 + getNormalizeMatrix() + "\n" 386 + "Translation Vector\n" 387 + " " + viewVector; 388 return result; 389 } 390}