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   We use two steps to transform the camera's configurable perspective
011   view volume into the standard perspective view volume. The first step
012   skews the camera's view volume so that its center line is the negative
013   z-axis (this takes an asymmetric view volume and makes it symmetric).
014   The second step scales the skewed view volume so that it intersects
015   the image plane, {@code z = -1}, with corners {@code (-1, -1, -1)}
016   and {@code (+1, +1, -1)} (this gives the symmetric view volume a 90
017   degree field-of-view).
018<p>
019   Let us derive the matrix for transforming the camera's perspective
020   view volume into the standard perspective view volume. Suppose the
021   camera's perspective view volume has an asymmetrical cross section
022   in the yz-plane that is determined by the top and bottom points
023   {@code (t, -1)} and {@code (b, -1)}. The center line of this cross
024   section is determined by the point {@code ((t+b)/2, -1)}. We want to
025   skew the yz-plane in the y-direction along the z-axis so that the
026   field-of-view's center line becomes the z-axis. So we need to solve
027   this matrix equation for the value of the skew factor {@code s}.
028   <pre>{@code
029      [ 1  s ] * [ (t+b)/2 ] = [  0 ]
030      [ 0  1 ]   [    -1   ]   [ -1 ]
031   }</pre>
032   This simplifies to the equation
033   <pre>{@code
034      (t + b)/2 - s = 0,
035      s = (t + b)/(2).
036   }</pre>
037<p>
038   A similar calculation can be made for skewing the field-of-view in
039   the xz-plane.
040<p>
041   Once the field-of-view in the yz-plane has been made symmetric with
042   respect to the z-axis, we want to scale it in the y-direction so that
043   the scaled field-of-view has an angle at the origin of 90 degrees. We
044   need to scale the point {@code ((t-b)/2, -1)} to the point {@code (1, -1)}
045   (and the point {@code ((b-t)/2, -1)} to the point {@code (-1, -1)}). So
046   we need to solve this matrix equation for the value of the scale factor
047   {@code s}.
048   <pre>{@code
049      [ s  0 ] * [ (t-b)/2 ] = [  1 ]
050      [ 0  1 ]   [    -1   ]   [ -1 ]
051   }</pre>
052   This simplifies to the equation
053   <pre>{@code
054      s * (t - b)/2 = 1,
055      s = 2/(t - b).
056   }</pre>
057<p>
058   A similar calculation can be made for scaling the skewed field-of-view
059   in the xz-plane.
060<p>
061   The following matrix skews the camera's view volume along the z-axis so
062   that the transformed view volume will be centered on the negative z-axis.
063   <pre>{@code
064     [ 1  0  (r+l)/(2)  0 ]
065     [ 0  1  (t+b)/(2)  0 ]
066     [ 0  0      1      0 ]
067     [ 0  0      0      1 ]
068   }</pre>
069   The following matrix scales the skewed view volume so that it will
070   be 2 units wide and 2 units tall at the image plane {@code z = -1}.
071   <pre>{@code
072     [ 2/(r-l)      0     0  0 ]
073     [    0      2/(t-b)  0  0 ]
074     [    0         0     1  0 ]
075     [    0         0     0  1 ]
076   }</pre>
077   The matrix product looks like this.
078   <pre>{@code
079     [ 1  0  (r+l)/2  0 ]   [ 2/(r-l)      0     0  0 ]
080     [ 0  1  (t+b)/2  0 ] * [    0      2/(t-b)  0  0 ]
081     [ 0  0     1     0 ]   [    0         0     1  0 ]
082     [ 0  0     0     1 ]   [    0         0     0  1 ]
083
084         [ 2/(r-l)      0     (r+l)/(r-l)  0 ]
085       = [    0      2/(t-b)  (t+b)/(t-b)  0 ]
086         [    0         0          1       0 ]
087         [    0         0          0       1 ]
088   }</pre>
089   This product matrix transforms the camera's configurable perspective
090   view volume into the standard normalized perspective view volume
091   whose intersection with the image plane, {@code z = -1}, has
092   {@code left = -1}, {@code right = +1}, {@code bottom = -1},
093   and {@code top = +1}.
094*/
095public final class PerspectiveNormalizeMatrix
096{
097   /**
098      This is a static factory method.
099      <p>
100      Construct the {@link Matrix} that transforms from the
101      {@link Camera}'s perspective view coordinate system to
102      the normalized perspective camera coordinate system.
103
104      @param l  left edge of view rectangle in the image plane z = -1
105      @param r  right edge of view rectangle in the image plane z = -1
106      @param b  bottom edge of view rectangle in the image plane z = -1
107      @param t  top edge of view rectangle in the image plane z = -1
108      @return a new {@code Matrix} object containing the perspective normalization matrix
109   */
110   public static Matrix build(final double l, final double r,
111                              final double b, final double t)
112   {
113      final Matrix m1, m2;
114
115      m1 = Matrix.buildFromColumns(
116               new Vector(  1.0,      0.0,    0.0,  0.0),
117               new Vector(  0.0,      1.0,    0.0,  0.0),
118               new Vector((r+l)/2,  (t+b)/2,  1.0,  0.0),
119               new Vector(  0.0,      0.0,    0.0,  1.0));
120
121      m2 = Matrix.buildFromColumns(
122               new Vector(2/(r-l),     0.0,    0.0,  0.0),
123               new Vector(  0.0,     2/(t-b),  0.0,  0.0),
124               new Vector(  0.0,       0.0,    1.0,  0.0),
125               new Vector(  0.0,       0.0,    0.0,  1.0));
126
127      return m2.times(m1);
128   }
129}