001/* 002 * Renderer 7. 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 located at the origin, looking down the negative z-axis, 012 with a near clipping plane. 013<p> 014 This {@code Camera} has a configurable "view volume" that 015 determines what part of space the camera "sees" when we use 016 the camera to take a picture (that is, when we render a 017 {@link Scene}). 018<p> 019 This {@code Camera} can "take a picture" two ways, using 020 a perspective projection or a parallel (orthographic) 021 projection. Each way of taking a picture has a different 022 shape for its view volume. The data in this data structure 023 determines the shape of each of the two view volumes. 024<p> 025 For the perspective projection, the view volume (in view 026 coordinates!) is the infinitely long frustum that is formed 027 by cutting at the near clipping plane, {@code z = -near}, 028 the infinitely long pyramid with its apex at the origin 029 and its cross section in the image plane, {@code z = -1}, 030 with edges {@code x = -1}, {@code x = +1}, {@code y = -1}, 031 and {@code y = +1}. The perspective view volume's shape is 032 set by the {@link projPerspective} method. 033<p> 034 For the orthographic projection, the view volume (in view 035 coordinates!) is the semi-infinite rectangular cylinder 036 parallel to the z-axis, with base in the near clipping plane, 037 {@code z = -near}, and with edges {@code x = left}, 038 {@code x = right}, {@code y = bottom}, {@code y = top} (a 039 semi-infinite parallelepiped). The orthographic view volume's 040 shape is set by the {@link projOrtho} method. 041<p> 042 When the graphics rendering {@link renderer.pipeline.Pipeline} 043 uses this {@code Camera} to render a {@link Scene}, the renderer 044 only "sees" the geometry from the scene that is contained in this 045 camera's view volume. (Notice that this means the orthographic 046 camera can see geometry that is behind the camera. In fact, the 047 perspective camera can also sees geometry that is behind the 048 camera.) The renderer's {@link renderer.pipeline.NearClip} and 049 {@link renderer.pipeline.Clip} pipeline stages are responsible 050 for making sure that the scene's geometry that is outside of 051 this camera's view volume is not visible. 052<p> 053 The plane {@code z = -1} (in view coordinates) is the camera's 054 "image plane". The rectangle in the image plane with corners 055 {@code (left, bottom, -1)} and {@code (right, top, -1)} is the 056 camera's "view rectangle". The view rectangle is like the film 057 in a real camera, it is where the camera's image appears when you 058 take a picture. The contents of the camera's view rectangle (after 059 it gets "normalized" to camera coordinates by the renderer's 060 {@link renderer.pipeline.View2Camera} stage) is what gets rasterized, 061 by the renderer's {@link renderer.pipeline.Rasterize} 062 pipeline stage, into a {@link renderer.framebuffer.FrameBuffer}'s 063 {@link renderer.framebuffer.FrameBuffer.Viewport}. 064<p> 065 For both the perspective and the parallel projections, the camera's 066 near plane is there to prevent the camera from seeing what is "behind" 067 the near plane. For the perspective projection, the near plane also 068 prevents the renderer from incorrectly rasterizing line segments that 069 cross the camera plane, {@code z = 0}. 070*/ 071public final class Camera 072{ 073 // Choose either perspective or parallel projection. 074 public final boolean perspective; 075 // The following five numbers define the camera's view volume. 076 public final double left; 077 public final double right; 078 public final double bottom; 079 public final double top; 080 public final double n; // near clipping plane 081 082 /** 083 A private {@code Camera} constructor for 084 use by the static factory methods. 085 */ 086 private Camera(final boolean perspective, 087 final double left, 088 final double right, 089 final double bottom, 090 final double top, 091 final double n) 092 { 093 this.perspective = perspective; 094 this.left = left; 095 this.right = right; 096 this.bottom = bottom; 097 this.top = top; 098 this.n = n; 099 } 100 101 102 /** 103 This is a static factory method. 104 <p> 105 Set up this {@code Camera}'s view volume as a perspective projection 106 of the normalized infinite view pyramid extending along the negative 107 z-axis. 108 109 @return a new {@code Camera} object with the default perspective parameters 110 */ 111 public static Camera projPerspective() 112 { 113 return projPerspective(-1.0, +1.0, -1.0, +1.0); // left, right, bottom, top 114 } 115 116 117 /** 118 This is a static factory method. 119 <p> 120 Set up this {@code Camera}'s view volume as a perspective projection 121 of an infinite view pyramid extending along the negative z-axis. 122 123 @param left left edge of view rectangle in the image plane 124 @param right right edge of view rectangle in the image plane 125 @param bottom bottom edge of view rectangle in the image plane 126 @param top top edge of view rectangle in the image plane 127 @return a new {@code Camera} object with the given parameters 128 */ 129 public static Camera projPerspective(final double left, final double right, 130 final double bottom, final double top) 131 { 132 return projPerspective(left, right, bottom, top, 1.0); 133 } 134 135 136 /** 137 This is a static factory method. 138 <p> 139 Set up this {@code Camera}'s view volume as a perspective projection 140 of an infinite view pyramid extending along the negative z-axis. 141 <p> 142 Use {@code focalLength} to determine the image plane. So the 143 {@code left}, {@code right}, {@code bottom}, {@code top} 144 parameters are used in the plane {@code z = -focalLength}. 145 <p> 146 The {@code focalLength} parameter can be used to zoom an 147 asymmetric view volume, much like the {@code fovy} parameter 148 for the symmetric view volume, or the "near" parameter for 149 the OpenGL gluPerspective() function. 150 151 @param left left edge of view rectangle in the image plane 152 @param right right edge of view rectangle in the image plane 153 @param bottom bottom edge of view rectangle in the image plane 154 @param top top edge of view rectangle in the image plane 155 @param focalLength distance from the origin to the image plane 156 @return a new {@code Camera} object with the given parameters 157 */ 158 public static Camera projPerspective(final double left, final double right, 159 final double bottom, final double top, 160 final double focalLength) 161 { 162 return new Camera(true, 163 left / focalLength, 164 right / focalLength, 165 bottom / focalLength, 166 top / focalLength, 167 -0.1); // near clipping plane (near = +0.1) 168 } 169 170 171 /** 172 This is a static factory method. 173 <p> 174 Set up this {@code Camera}'s view volume as a symmetric infinite 175 view pyramid extending along the negative z-axis. 176 <p> 177 Here, the view volume is determined by a vertical "field of view" 178 angle and an aspect ratio for the view rectangle in the image plane. 179 180 @param fovy angle in the y-direction subtended by the view rectangle in the image plane 181 @param aspect aspect ratio of the view rectangle in the image plane 182 @return a new {@code Camera} object with the given parameters 183 */ 184 public static Camera projPerspective(final double fovy, final double aspect) 185 { 186 final double top = Math.tan((Math.PI/180.0)*fovy/2.0); 187 final double bottom = -top; 188 final double right = top * aspect; 189 final double left = -right; 190 191 return projPerspective(left, right, bottom, top); 192 } 193 194 195 /** 196 This is a static factory method. 197 <p> 198 Set up this {@code Camera}'s view volume as a parallel (orthographic) 199 projection of the normalized infinite view parallelepiped extending 200 along the z-axis. 201 202 @return a new {@code Camera} object with the default orthographic parameters 203 */ 204 public static Camera projOrtho() 205 { 206 return projOrtho(-1.0, +1.0, -1.0, +1.0); // left, right, bottom, top 207 } 208 209 210 /** 211 This is a static factory method. 212 <p> 213 Set up this {@code Camera}'s view volume as a parallel (orthographic) 214 projection of an infinite view parallelepiped extending along the 215 z-axis. 216 217 @param left left edge of view rectangle in the xy-plane 218 @param right right edge of view rectangle in the xy-plane 219 @param bottom bottom edge of view rectangle in the xy-plane 220 @param top top edge of view rectangle in the xy-plane 221 @return a new {@code Camera} object with the given parameters 222 */ 223 public static Camera projOrtho(final double left, final double right, 224 final double bottom, final double top) 225 { 226 return new Camera(false, 227 left, 228 right, 229 bottom, 230 top, 231 +1.0); // near clipping plane (near = -1.0) 232 } 233 234 235 /** 236 This is a static factory method. 237 <p> 238 Set up this {@code Camera}'s view volume as a symmetric infinite 239 view parallelepiped extending along the z-axis. 240 <p> 241 Here, the view volume is determined by a vertical "field-of-view" 242 angle and an aspect ratio for the view rectangle in the image plane. 243 244 @param fovy angle in the y-direction subtended by the view rectangle in the image plane 245 @param aspect aspect ratio of the view rectangle in the image plane 246 @return a new {@code Camera} object with the given parameters 247 */ 248 public static Camera projOrtho(final double fovy, final double aspect) 249 { 250 final double top = Math.tan((Math.PI/180.0)*fovy/2.0); 251 final double bottom = -top; 252 final double right = top * aspect; 253 final double left = -right; 254 255 return projOrtho(left, right, bottom, top); 256 } 257 258 259 /** 260 Create a new {@code Camera} that is essentially the same as this 261 {@code Camera} but with the given distance from the camera to 262 the near clipping plane. 263 <p> 264 When {@code near} is positive, the near clipping plane is in 265 front of the camera. When {@code near} is negative, the near 266 clipping plane is behind the camera. 267 268 @param near distance from the new {@code Camera} to its near clipping plane 269 @return a new {@code Camera} object with the given value for near 270 */ 271 public Camera changeNear(final double near) 272 { 273 return new Camera(this.perspective, 274 this.left, 275 this.right, 276 this.bottom, 277 this.top, 278 -near); 279 } 280 281 282 /** 283 For debugging. 284 285 @return {@link String} representation of this {@code Camera} object 286 */ 287 public String toString() 288 { 289 final double fovy = (180./Math.PI) * Math.atan(top) 290 + (180./Math.PI) * Math.atan(-bottom); 291 final double ratio = (right - left) / (top - bottom); 292 String result = ""; 293 result += "Camera: \n"; 294 result += " perspective = " + perspective + "\n"; 295 result += " left = " + left + ", " 296 + " right = " + right + "\n" 297 + " bottom = " + bottom + ", " 298 + " top = " + top + "\n" 299 + " near = " + -n + "\n" 300 + " (fovy = " + fovy 301 + ", aspect ratio = " + String.format("%.2f", ratio) + ")"; 302 return result; 303 } 304}