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}