1 /** 2 dlsl.projection 3 4 5 Authors: Peter Particle ( based on gl3n by David Herberth ) 6 License: MIT 7 8 Note: All methods marked with pure are weakly pure since, they all access an instance member. 9 All static methods are strongly pure. 10 */ 11 12 module dlsl.projection; 13 14 public import dlsl.matrix; 15 import dlsl.vector; 16 17 import std.math : sqrt, sin, cos, tan, PI; 18 import std.traits : isFloatingPoint; 19 20 21 /// http://www.geeks3d.com/20090729/howto-perspective-projection-matrix-in-opengl/ 22 23 24 private float[ 6 ] cperspective( float fovy, float aspect, float near, float far ) { 25 float top = near * tan( fovy * ( PI / 360.0 )); 26 float bottom = - top; 27 float right = top * aspect; 28 float left = - right; 29 30 return [ left, right, bottom, top, near, far ]; 31 } 32 33 /// Construct a symmetric perspective matrix ( 4x4 and floating - point matrices only ). 34 mat4 glPerspective( float fovy, float aspect, float near, float far ) { 35 float[ 6 ] cdata = cperspective( fovy, aspect, near, far ); 36 return glPerspective( cdata[ 0 ], cdata[ 1 ], cdata[ 2 ], cdata[ 3 ], cdata[ 4 ], cdata[ 5 ] ); 37 } 38 39 /// Construct an optionally non-symmetric perspective matrix 40 mat4 glPerspective( float left, float right, float bottom, float top, float near, float far ) 41 in { 42 assert( right - left != 0 ); 43 assert( top - bottom != 0 ); 44 assert( far - near != 0 ); 45 } 46 body { 47 mat4 result; 48 result.clear( 0 ); 49 result.data[ 0 ][ 0 ] = ( 2 * near ) / ( right - left ); 50 result.data[ 2 ][ 0 ] = ( right + left ) / ( right - left ); 51 result.data[ 1 ][ 1 ] = ( 2 * near ) / ( top - bottom ); 52 result.data[ 2 ][ 1 ] = ( top + bottom ) / ( top - bottom ); 53 result.data[ 2 ][ 2 ] = - ( far + near ) / ( far - near ); 54 result.data[ 3 ][ 2 ] = - ( 2 * far * near ) / ( far - near ); 55 result.data[ 2 ][ 3 ] = - 1; 56 57 return result; 58 } 59 60 /// Construct an inverse, symmetric perspective matrix ( 4x4 and floating - point matrices only ). 61 mat4 glInversePerspective( float fovy, float aspect, float near, float far ) { 62 float[ 6 ] cdata = cperspective( fovy, aspect, near, far ); 63 return glInversePerspective( cdata[ 0 ], cdata[ 1 ], cdata[ 2 ], cdata[ 3 ], cdata[ 4 ], cdata[ 5 ] ); 64 } 65 66 /// Construct an inverse, optionally non-symmetric perspective matrix 67 mat4 glInversePerspective( float left, float right, float bottom, float top, float near, float far ) 68 in { 69 assert( right - left != 0 ); 70 assert( top - bottom != 0 ); 71 assert( far - near != 0 ); 72 } 73 body { 74 mat4 result; 75 result.clear( 0 ); 76 77 result.data[ 0 ][ 0 ] = ( right - left ) / ( 2 * near ); 78 result.data[ 3 ][ 0 ] = ( right + left ) / ( 2 * near ); 79 result.data[ 1 ][ 1 ] = ( top - bottom ) / ( 2 * near ); 80 result.data[ 3 ][ 1 ] = ( top + bottom ) / ( 2 * near ); 81 result.data[ 3 ][ 2 ] = - 1; 82 result.data[ 2 ][ 3 ] = - ( far - near ) / ( 2 * far * near ); 83 result.data[ 3 ][ 3 ] = ( far + near ) / ( 2 * far * near ); 84 85 return result; 86 } 87 88 // ( 2 ) and ( 3 ) say this one is correct 89 /// Construct an orthographic matrix ( 4x4 and floating - point matrices only ). 90 mat4 glOrthographic( float left, float right, float bottom, float top, float near, float far ) 91 in { 92 assert( right - left != 0 ); 93 assert( top - bottom != 0 ); 94 assert( far - near != 0 ); 95 } 96 body { 97 mat4 result = void; 98 result.clear( 0 ); 99 100 result.data[ 0 ][ 0 ] = 2 / ( right - left ); 101 result.data[ 3 ][ 0 ] = - ( right + left ) / ( right - left ); 102 result.data[ 1 ][ 1 ] = 2 / ( top - bottom ); 103 result.data[ 3 ][ 1 ] = - ( top + bottom ) / ( top - bottom ); 104 result.data[ 2 ][ 2 ] = - 2 / ( far - near ); 105 result.data[ 3 ][ 2 ] = - ( far + near ) / ( far - near ); 106 result.data[ 3 ][ 3 ] = 1; 107 108 return result; 109 } 110 111 // ( 1 ) and ( 2 ) say this one is correct 112 /// Returns an inverse orographic matrix ( 4x4 and floating - point matrices only ). 113 mat4 glInverseOrthographic( float left, float right, float bottom, float top, float near, float far ) { 114 mat4 result; 115 result.clear( 0 ); 116 117 result.data[ 0 ][ 0 ] = ( right - left ) / 2; 118 result.data[ 3 ][ 0 ] = ( right + left ) / 2; 119 result.data[ 1 ][ 1 ] = ( top - bottom ) / 2; 120 result.data[ 3 ][ 1 ] = ( top + bottom ) / 2; 121 result.data[ 2 ][ 2 ] = ( far - near ) / - 2; 122 result.data[ 3 ][ 2 ] = ( far + near ) / 2; 123 result.data[ 3 ][ 3 ] = 1; 124 125 return result; 126 } 127 128 129 unittest { 130 float aspect = 6.0 / 9.0; 131 float[ 6 ] cp = cperspective( 60f, aspect, 1f, 100f ); 132 assert( cp[ 4 ] == 1.0f ); 133 assert( cp[ 5 ] == 100.0f ); 134 assert( cp[ 0 ] == - cp[ 1 ] ); 135 assert(( cp[ 0 ] < - 0.38489f ) && ( cp[ 0 ] > - 0.38491f )); 136 assert( cp[ 2 ] == - cp[ 3 ] ); 137 assert(( cp[ 2 ] < - 0.577349f ) && ( cp[ 2 ] > - 0.577351f )); 138 139 assert( mat4.perspective( 60.0, aspect, 1.0, 100.0 ) == mat4.perspective( cp[ 0 ], cp[ 1 ], cp[ 2 ], cp[ 3 ], cp[ 4 ], cp[ 5 ] )); 140 float[ 4 ][ 4 ] m4p = mat4.perspective( 60.0, aspect, 1.0, 100.0 ).data; 141 assert(( m4p[ 0 ][ 0 ] < 2.598077f ) && ( m4p[ 0 ][ 0 ] > 2.598075f )); 142 assert( m4p[ 0 ][ 2 ] == 0.0f ); 143 assert(( m4p[ 1 ][ 1 ] < 1.732052 ) && ( m4p[ 1 ][ 1 ] > 1.732050 )); 144 assert( m4p[ 1 ][ 2 ] == 0.0f ); 145 assert(( m4p[ 2 ][ 2 ] < - 1.020201 ) && ( m4p[ 2 ][ 2 ] > - 1.020203 )); 146 assert(( m4p[ 3 ][ 2 ] < - 2.020201 ) && ( m4p[ 3 ][ 2 ] > - 2.020203 )); 147 assert(( m4p[ 2 ][ 3 ] < - 0.90000f ) && ( m4p[ 2 ][ 3 ] > - 1.10000f )); 148 149 float[ 4 ][ 4 ] m4pi = mat4.glInversePerspective( 60.0, aspect, 1.0, 100.0 ).data; 150 assert(( m4pi[ 0 ][ 0 ] < 0.384901 ) && ( m4pi[ 0 ][ 0 ] > 0.384899 )); 151 assert( m4pi[ 0 ][ 3 ] == 0.0f ); 152 assert(( m4pi[ 1 ][ 1 ] < 0.577351 ) && ( m4pi[ 1 ][ 1 ] > 0.577349 )); 153 assert( m4pi[ 1 ][ 3 ] == 0.0f ); 154 assert( m4pi[ 3 ][ 2 ] == - 1.0f ); 155 assert(( m4pi[ 2 ][ 3 ] < - 0.494999 ) && ( m4pi[ 2 ][ 3 ] > - 0.495001 )); 156 assert(( m4pi[ 3 ][ 3 ] < 0.505001 ) && ( m4pi[ 3 ][ 3 ] > 0.504999 )); 157 158 // maybe the next tests should be improved 159 float[ 4 ][ 4 ] m4o = mat4.orthographic( - 1.0f, 1.0f, - 1.0f, 1.0f, - 1.0f, 1.0f ).data; 160 assert( m4o == [ 161 [ 1.0f, 0.0f, 0.0f, 0.0f ], 162 [ 0.0f, 1.0f, 0.0f, 0.0f ], 163 [ 0.0f, 0.0f, - 1.0f, 0.0f ], 164 [ 0.0f, 0.0f, 0.0f, 1.0f ] ] ); 165 166 float[ 4 ][ 4 ] m4oi = mat4.inverseOrthographic( - 1.0f, 1.0f, - 1.0f, 1.0f, - 1.0f, 1.0f ).data; 167 assert( m4oi == [ 168 [ 1.0f, 0.0f, 0.0f, 0.0f ], 169 [ 0.0f, 1.0f, 0.0f, 0.0f ], 170 [ 0.0f, 0.0f, - 1.0f, 0.0f ], 171 [ 0.0f, 0.0f, 0.0f, 1.0f ] ] ); 172 173 //TODO: look_at tests 174 } 175 176 177 //////////////////////// 178 // Vulkan Projections // 179 //////////////////////// 180 181 mat4 vkPerspective( float fovy, float aspect, float near, float far ) { 182 mat4 result = void; 183 result.clear( 0 ); 184 185 const float t = 1.0f / tan( 0.5f * fovy * deg2rad ); 186 const nf = near - far; 187 188 result[0][0] = t / aspect; 189 result[1][1] = t; 190 result[2][2] = - ( near + far ) / nf; 191 result[2][3] = 1; 192 result[3][2] = ( 2 * near * far ) / nf; 193 194 // premultiplying a glProjection with this clip matrix results in expected vkProjection 195 // source: https://matthewwellings.com/blog/the-new-vulkan-coordinate-system/ 196 auto clip = mat4( vec4( 1, 0, 0, 0 ), vec4( 0, -1, 0, 0 ), vec4( 0, 0, 0.5, 0 ), vec4( 0, 0, 0.5, 1 )); 197 return clip * result; 198 }