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}