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 }