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}