001/*
002 * Renderer 4. 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   Project each {@link Vertex} of a {@link Model} from camera
016   coordinates to the {@link Camera}'s image plane {@code z = -1}.
017<p>
018   Let us derive the formulas for the perspective projection
019   transformation (the formulas for the parallel projection
020   transformation are pretty obvious). We will derive the
021   x-coordinate formula; the y-coordinate formula is similar.
022<p>
023   Let {@code (x_c, y_c, z_c)} denote a point in the 3-dimensional
024   camera coordinate system. Let {@code (x_p, y_p, -1)} denote the
025   point's perspective projection into the image plane, {@code z = -1}.
026   Here is a "picture" of just the xz-plane from camera space. This
027   picture shows the point {@code (x_c, z_c)} and its projection to
028   the point {@code (x_p, -1)} in the image plane.
029<pre>{@code
030           x
031           |                             /
032           |                           /
033       x_c +                         + (x_c, z_c)
034           |                       / |
035           |                     /   |
036           |                   /     |
037           |                 /       |
038           |               /         |
039           |             /           |
040       x_p +           +             |
041           |         / |             |
042           |       /   |             |
043           |     /     |             |
044           |   /       |             |
045           | /         |             |
046           +-----------+-------------+------------> -z
047        (0,0)         -1            z_c
048}</pre>
049<p>
050   We are looking for a formula that computes {@code x_p} in terms of
051   {@code x_c} and {@code z_c}. There are two similar triangles in this
052   picture that share a vertex at the origin. Using the properties of
053   similar triangles we have the following ratios. (Remember that these
054   are ratios of positive lengths, so we write {@code -z_c}, since
055   {@code z_c} is on the negative z-axis).
056<pre>{@code
057                 x_p       x_c
058                -----  =  -----
059                  1       -z_c
060}</pre>
061<p>
062   If we solve this ratio for the unknown, {@code x_p}, we get the
063   projection formula,
064<pre>{@code
065                 x_p = -x_c / z_c.
066}</pre>
067<p>
068   The equivalent formula for the y-coordinate is
069<pre>{@code
070                 y_p = -y_c / z_c.
071}</pre>
072*/
073public final class Projection
074{
075   /**
076      Project each {@link Vertex} from a {@link Model} to
077      the {@link Camera}'s image plane {@code z = -1}.
078      <p>
079      This pipeline stage assumes that the model's vertices
080      have all been transformed to the camera coordinate system.
081
082      @param model  {@link Model} whose {@link Vertex} objects are to be projected onto the image plane
083      @param camera  a reference to the {@link Scene}'s {@link Camera} object
084      @return a new {@link Model} object holding the projected {@link Vertex} objects
085   */
086   public static Model project(final Model model, final Camera camera)
087   {
088      // A new vertex list to hold the projected vertices.
089      final List<Vertex> newVertexList =
090                            new ArrayList<>(model.vertexList.size());
091
092      // Replace each Vertex object with one that contains
093      // the original Vertex's projected (x,y) coordinates.
094      for (final Vertex v : model.vertexList)
095      {
096         if ( camera.perspective )
097         {
098            // Calculate the perspective projection.
099            newVertexList.add(
100              new Vertex(
101                v.x / -v.z,  // xp = xc / -zc
102                v.y / -v.z,  // yp = yc / -zc
103                -1));        // zp = -1
104         }
105         else
106         {
107            // Calculate the parallel projection.
108            newVertexList.add(
109              new Vertex(
110                v.x,  // xp = xc
111                v.y,  // yp = yc
112                0));  // zp = 0
113         }
114      }
115
116      return new Model(newVertexList,
117                       model.primitiveList,
118                       model.colorList,
119                       model.name,
120                       model.visible);
121   }
122
123
124
125   // Private default constructor to enforce noninstantiable class.
126   // See Item 4 in "Effective Java", 3rd Ed, Joshua Bloch.
127   private Projection() {
128      throw new AssertionError();
129   }
130}