001/*
002 * Renderer 7. The MIT License.
003 * Copyright (c) 2022 rlkraft@pnw.edu
004 * See LICENSE for details.
005*/
006
007package renderer.pipeline;
008
009import renderer.scene.*;
010
011import java.util.List;
012import java.util.ArrayList;
013
014/**
015   Transform each {@link Vertex} of a {@link Model} from the
016   {@link Camera}'s (shared) view coordinates to normalized
017   camera coordinates.
018<p>
019   This stage transforms the {@link Camera}'s view volume
020   from a user defined shape (in the view coordinate system)
021   into the standard normalized view volume (in the camera
022   coordinate system) used by the {@link Clip} pipeline stage.
023<p>
024   There are two standard normalized view volumes, one for
025   perspective projection and one for orthographic projection.
026<p>
027   The standard normalized perspective view volume is the infinitely
028   long pyramid with its apex at the origin and intersecting the
029   image plane {@code z = -1} at the corners {@code (-1, -1, -1)}
030   and {@code (+1, +1, -1)}.
031<p>
032   The standard normalized orthographic view volume is the infinitely
033   long parallelepiped centered on the z-axis and intersecting the
034   image plane {@code z = -1} at the corners {@code (-1, -1, -1)}
035   and {@code (+1, +1, -1)}.
036<p>
037   The user defined view volume determined by the {@link Scene}'s
038   {@link Camera} object is either the infinitely long pyramid with its
039   apex at the origin and intersecting the image plane {@code z = -1} at
040   the corners {@code (left, bottom, -1)} and {@code (right, top, -1)},
041   or it is the infinitely long parallelepiped parallel to the z-axis
042   and intersecting the image plane {@code z = -1} at the corners
043   {@code (left, bottom, -1)} and {@code (right, top, -1)}.
044<p>
045   The view coordinate system is relative to the user defined view volume.
046<p>
047   The normalized camera coordinate system is relative to the normalized
048   view volume.
049<p>
050   The transformation formulas that transform the user defined view volume
051   into the normalized view volume also transform the view coordinate
052   system into the normalized camera coordinate system.
053<p>
054   We use two steps to transform the camera's perspective view volume
055   into the standard perspective view volume. The first step skews the
056   camera's view volume so that its center line is the negative z-axis
057   (this takes an asymmetric view volume and makes it symmetric). The
058   second step scales the skewed view volume so that it intersects the
059   image plane, {@code z = -1}, with corners {@code (-1, -1, -1)} and
060   {@code (+1, +1, -1)} (this gives the symmetric view volume a 90 degree
061   field-of-view).
062<p>
063   We also use two steps to transform the camera's orthographic view volume
064   into the standard orthographic view volume. The first step translates the
065   camera's view volume so that its center line is the negative z-axis (this
066   takes an asymmetric view volume and makes it symmetric). The second step
067   scales the translated view volume so that it intersects the image plane
068   {@code z = -1} with corners {@code (-1, -1, -1)} and {@code (+1, +1, -1)}.
069<p>
070   Let us derive the formulas for transforming the camera's perspective
071   view volume into the standard perspective view volume. Suppose the
072   camera's perspective view volume has an asymmetrical cross section
073   in the yz-plane that is determined by the top and bottom points
074   {@code (t, -1)} and {@code (b, -1)}. The center line of this cross
075   section is determined by the point {@code ((t+b)/2, -1)}. We want to
076   skew the yz-plane in the y-direction along the z-axis so that the
077   field-of-view's center line becomes the z-axis. So we need to solve
078   this matrix equation for the value of the skew factor {@code s}.
079   <pre>{@code
080      [ 1  s ] * [ (t+b)/2 ] = [  0 ]
081      [ 0  1 ]   [    -1   ]   [ -1 ]
082   }</pre>
083   This simplifies to
084   <pre>{@code
085      (t + b)/2 - s = 0,
086      s = (t + b)/2.
087   }</pre>
088<p>
089   A similar calculation can be made for skewing the field-of-view in the
090   xz-plane.
091<p>
092   The following matrix equation skews the camera's view volume along the
093   z-axis so that the transformed view volume will be centered on the
094   negative z-axis.
095   <pre>{@code
096     [ 1  0  (r+l)/2 ]   [ x ]   [ x + z * (r + l)/2 ]
097     [ 0  1  (t+b)/2 ] * [ y ] = [ y + z * (t + b)/2 ]
098     [ 0  0     1    ]   [ z ]   [     z             ]
099   }</pre>
100<p>
101   Once the field-of-view in the yz-plane has been made symmetric with
102   respect to the z-axis, we want to scale it in the y-direction so that
103   the scaled field-of-view has an angle at the origin of 90 degrees. We
104   need to scale the point {@code ((t-b)/2, -1)} to the point {@code (1, -1)}
105   (and the point {@code ((b-t)/2, -1)} to the point {@code (-1, -1)}). So
106   we need to solve this matrix equation for the value of the scale factor
107   {@code s}.
108   <pre>{@code
109      [ s  0 ] * [ (t-b)/2 ] = [  1 ]
110      [ 0  1 ]   [    -1   ]   [ -1 ]
111   }</pre>
112   This simplifies to
113   <pre>{@code
114      s * (t - b)/2 = 1,
115      s = 2/(t - b).
116   }</pre>
117<p>
118   A similar calculation can be made for scaling the skewed field-of-view
119   in the xz-plane.
120<p>
121   The following matrix equation scales the skewed view volume to be 2 units
122   wide and 2 units tall at the image plane {@code z = -1}.
123   <pre>{@code
124     [ 2/(r-l)     0     0 ]   [ x ]   [ (2 * x)/(r - l) ]
125     [    0     2/(t-b)  0 ] * [ y ] = [ (2 * y)/(t - b) ]
126     [    0        0     1 ]   [ z ]   [        z        ]
127   }</pre>
128<p>
129   The formulas for transforming the camera's orthographic view volume into
130   the standard orthographic view volume (a translation followed by a scale)
131   are similar but a bit easier to derive. The derivation is left as an
132   exercise for the reader.
133*/
134public final class View2Camera
135{
136   /**
137      Use the {@link Camera}'s view volume data to transform each
138      {@link Vertex} from the {@link Camera}'s view coordinate system
139      to the normalized camera coordinate system.
140
141      @param model  {@link Model} with {@link Vertex} objects in the camera's view coordinate system
142      @param camera  the {@link Scene}'s {@link Camera} with the view volume data
143      @return a new {@link Model} with {@link Vertex} objects in the normalized camera coordinate system
144   */
145   public static Model view2camera(final Model model, final Camera camera)
146   {
147      final double l = camera.left; // This data defines the camera's view volume.
148      final double r = camera.right;
149      final double b = camera.bottom;
150      final double t = camera.top;
151
152      // A new vertex list to hold the transformed vertices.
153      final List<Vertex> newVertexList =
154                            new ArrayList<>(model.vertexList.size());
155
156      // Replace each Vertex object with one that
157      // contains normalized camera coordinates.
158      for (final Vertex v : model.vertexList)
159      {
160         double v_x, v_y, v_z;
161
162         if ( camera.perspective )
163         {
164            // We use two steps to transform the camera's perspective
165            // view volume into the standard perspective view volume.
166            // The first step skews the current view volume so that
167            // its center line is the negative z-axis (this takes an
168            // asymmetric view volume and makes it symmetric). The second
169            // step scales the skewed view volume so that it intersects the
170            // image plane z = -1 with corners (-1, -1, -1) and (+1, +1, -1)
171            // (this gives the symmetric view volume a 90 degree field-of-view).
172
173            // For each vertex, skew its coordinates so that the
174            // negative z-axis is at the center of the view volume.
175            v_z = v.z;
176            v_x = v.x + v_z * (r + l)/2.0;
177            v_y = v.y + v_z * (t + b)/2.0;
178
179            // For each vertex, scale its coordinates so that the
180            // view volume has corners (-1, -1, -1) and (+1, +1, -1).
181            v_x = (2.0 * v_x)/(r - l);
182            v_y = (2.0 * v_y)/(t - b);
183         }
184         else
185         {
186            // We use two steps to transform the camera's orthographic
187            // view volume into the standard orthographic view volume.
188            // The first step translates the current view volume so that
189            // its center line is the z-axis. The second step scales the
190            // translated view volume so that it intersects the image plane
191            // z = -1 with corners (-1, -1, -1) and (+1, +1, -1).
192
193            // For each vertex, translate its coordinates so that
194            // the z-axis is at the center of the view volume.
195            v_z = v.z;
196            v_x = v.x - (r + l)/2.0;
197            v_y = v.y - (t + b)/2.0;
198
199            // For each vertex, scale its coordinates so that the
200            // view volume has corners (-1, -1, -1) and (+1, +1, -1).
201            v_x = (2.0 * v_x)/(r - l);
202            v_y = (2.0 * v_y)/(t - b);
203         }
204
205         newVertexList.add( new Vertex(v_x, v_y, v_z) );
206      }
207
208      return new Model(newVertexList,
209                       model.primitiveList,
210                       model.colorList,
211                       model.name,
212                       model.visible);
213   }
214
215
216
217   // Private default constructor to enforce noninstantiable class.
218   // See Item 4 in "Effective Java", 3rd Ed, Joshua Bloch.
219   private View2Camera() {
220      throw new AssertionError();
221   }
222}