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 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 nothrow @nogc:
24 
25 private float[ 6 ] cperspective( float fovy, float aspect, float near, float far ) {
26 	float top = near * tan( fovy * ( PI / 360.0 ));
27 	float bottom = - top;
28 	float right = top * aspect;
29 	float left = - right;
30 
31 	return [ left, right, bottom, top, near, far ];
32 }
33 
34 /// Construct a symmetric perspective matrix ( 4x4 and floating - point matrices only ).
35 mat4 glPerspective( float fovy, float aspect, float near, float far ) {
36 	float[ 6 ] cdata = cperspective( fovy, aspect, near, far );
37 	return glPerspective( cdata[ 0 ], cdata[ 1 ], cdata[ 2 ], cdata[ 3 ], cdata[ 4 ], cdata[ 5 ] );
38 }
39 
40 /// Construct an optionally non-symmetric perspective matrix
41 mat4 glPerspective( float left, float right, float bottom, float top, float near, float far )
42 in {
43 	assert( right - left != 0 );
44 	assert( top - bottom != 0 );
45 	assert( far - near != 0 );
46 }
47 body {
48 	mat4 result;
49 	result.clear( 0 );
50 	result.data[ 0 ][ 0 ] = ( 2 * near ) / ( right - left );
51 	result.data[ 2 ][ 0 ] = ( right + left ) / ( right - left );
52 	result.data[ 1 ][ 1 ] = ( 2 * near ) / ( top - bottom );
53 	result.data[ 2 ][ 1 ] = ( top + bottom ) / ( top - bottom );
54 	result.data[ 2 ][ 2 ] = - ( far + near ) / ( far - near );
55 	result.data[ 3 ][ 2 ] = - ( 2 * far * near ) / ( far - near );
56 	result.data[ 2 ][ 3 ] = - 1;
57 
58 	return result;
59 }
60 
61 /// Construct an inverse, symmetric perspective matrix ( 4x4 and floating - point matrices only ).
62 mat4 glInversePerspective( float fovy, float aspect, float near, float far ) {
63 	float[ 6 ] cdata = cperspective( fovy, aspect, near, far );
64 	return glInversePerspective( cdata[ 0 ], cdata[ 1 ], cdata[ 2 ], cdata[ 3 ], cdata[ 4 ], cdata[ 5 ] );
65 }
66 
67 /// Construct an inverse, optionally non-symmetric perspective matrix
68 mat4 glInversePerspective( float left, float right, float bottom, float top, float near, float far )
69 in {
70 	assert( right - left != 0 );
71 	assert( top - bottom != 0 );
72 	assert( far - near != 0 );
73 }
74 body {
75 	mat4 result;
76 	result.clear( 0 );
77 
78 	result.data[ 0 ][ 0 ] = ( right - left ) / ( 2 * near );
79 	result.data[ 3 ][ 0 ] = ( right + left ) / ( 2 * near );
80 	result.data[ 1 ][ 1 ] = ( top - bottom ) / ( 2 * near );
81 	result.data[ 3 ][ 1 ] = ( top + bottom ) / ( 2 * near );
82 	result.data[ 3 ][ 2 ] = - 1;
83 	result.data[ 2 ][ 3 ] = - ( far - near ) / ( 2 * far * near );
84 	result.data[ 3 ][ 3 ] =   ( far + near ) / ( 2 * far * near );
85 
86 	return result;
87 }
88 
89 // ( 2 ) and ( 3 ) say this one is correct
90 /// Construct an orthographic matrix ( 4x4 and floating - point matrices only ).
91 mat4 glOrthographic( float left, float right, float bottom, float top, float near, float far )
92 in {
93 	assert( right - left != 0 );
94 	assert( top - bottom != 0 );
95 	assert( far - near != 0 );
96 }
97 body {
98 	mat4 result = void;
99 	result.clear( 0 );
100 
101 	result.data[ 0 ][ 0 ] = 2 / ( right - left );
102 	result.data[ 3 ][ 0 ] = - ( right + left ) / ( right - left );
103 	result.data[ 1 ][ 1 ] = 2 / ( top - bottom );
104 	result.data[ 3 ][ 1 ] = - ( top + bottom ) / ( top - bottom );
105 	result.data[ 2 ][ 2 ] = - 2 / ( far - near );
106 	result.data[ 3 ][ 2 ] = - ( far + near ) / ( far - near );
107 	result.data[ 3 ][ 3 ] = 1;
108 
109 	return result;
110 }
111 
112 // ( 1 ) and ( 2 ) say this one is correct
113 /// Returns an inverse orographic matrix ( 4x4 and floating - point matrices only ).
114 mat4 glInverseOrthographic( float left, float right, float bottom, float top, float near, float far ) {
115 	mat4 result;
116 	result.clear( 0 );
117 
118 	result.data[ 0 ][ 0 ] = ( right - left ) / 2;
119 	result.data[ 3 ][ 0 ] = ( right + left ) / 2;
120 	result.data[ 1 ][ 1 ] = ( top - bottom ) / 2;
121 	result.data[ 3 ][ 1 ] = ( top + bottom ) / 2;
122 	result.data[ 2 ][ 2 ] = ( far - near ) / - 2;
123 	result.data[ 3 ][ 2 ] = ( far + near ) / 2;
124 	result.data[ 3 ][ 3 ] = 1;
125 
126 	return result;
127 }
128 
129 
130 unittest {
131 	float aspect = 6.0 / 9.0;
132 	float[ 6 ] cp = cperspective( 60f, aspect, 1f, 100f );
133 	assert(  cp[ 4 ] == 1.0f );
134 	assert(  cp[ 5 ] == 100.0f );
135 	assert(  cp[ 0 ] == - cp[ 1 ] );
136 	assert(( cp[ 0 ] < - 0.38489f ) && ( cp[ 0 ] > - 0.38491f ));
137 	assert(  cp[ 2 ] == - cp[ 3 ] );
138 	assert(( cp[ 2 ] < - 0.577349f ) && ( cp[ 2 ] > - 0.577351f ));
139 
140 	assert( mat4.perspective( 60.0, aspect, 1.0, 100.0 ) == mat4.perspective( cp[ 0 ], cp[ 1 ], cp[ 2 ], cp[ 3 ], cp[ 4 ], cp[ 5 ] ));
141 	float[ 4 ][ 4 ] m4p = mat4.perspective( 60.0, aspect, 1.0, 100.0 ).data;
142 	assert(( m4p[ 0 ][ 0 ] < 2.598077f ) && ( m4p[ 0 ][ 0 ] > 2.598075f ));
143 	assert(  m4p[ 0 ][ 2 ] == 0.0f );
144 	assert(( m4p[ 1 ][ 1 ] < 1.732052 ) && ( m4p[ 1 ][ 1 ] > 1.732050 ));
145 	assert(  m4p[ 1 ][ 2 ] == 0.0f );
146 	assert(( m4p[ 2 ][ 2 ] < - 1.020201 ) && ( m4p[ 2 ][ 2 ] > - 1.020203 ));
147 	assert(( m4p[ 3 ][ 2 ] < - 2.020201 ) && ( m4p[ 3 ][ 2 ] > - 2.020203 ));
148 	assert(( m4p[ 2 ][ 3 ] < - 0.90000f ) && ( m4p[ 2 ][ 3 ] > - 1.10000f ));
149 
150 	float[ 4 ][ 4 ] m4pi = mat4.glInversePerspective( 60.0, aspect, 1.0, 100.0 ).data;
151 	assert(( m4pi[ 0 ][ 0 ] < 0.384901 ) && ( m4pi[ 0 ][ 0 ] > 0.384899 ));
152 	assert(  m4pi[ 0 ][ 3 ] == 0.0f );
153 	assert(( m4pi[ 1 ][ 1 ] < 0.577351 ) && ( m4pi[ 1 ][ 1 ] > 0.577349 ));
154 	assert(  m4pi[ 1 ][ 3 ] == 0.0f );
155 	assert(  m4pi[ 3 ][ 2 ] == - 1.0f );
156 	assert(( m4pi[ 2 ][ 3 ] < - 0.494999 ) && ( m4pi[ 2 ][ 3 ] > - 0.495001 ));
157 	assert(( m4pi[ 3 ][ 3 ] <   0.505001 ) && ( m4pi[ 3 ][ 3 ] > 0.504999 ));
158 
159 	// maybe the next tests should be improved
160 	float[ 4 ][ 4 ] m4o = mat4.orthographic( - 1.0f, 1.0f, - 1.0f, 1.0f, - 1.0f, 1.0f ).data;
161 	assert( m4o == [
162 		[ 1.0f, 0.0f,   0.0f, 0.0f ],
163 		[ 0.0f, 1.0f,   0.0f, 0.0f ],
164 		[ 0.0f, 0.0f, - 1.0f, 0.0f ],
165 		[ 0.0f, 0.0f,   0.0f, 1.0f ] ] );
166 
167 	float[ 4 ][ 4 ] m4oi = mat4.inverseOrthographic( - 1.0f, 1.0f, - 1.0f, 1.0f, - 1.0f, 1.0f ).data;
168 	assert( m4oi == [
169 		[ 1.0f, 0.0f,   0.0f, 0.0f ],
170 		[ 0.0f, 1.0f,   0.0f, 0.0f ],
171 		[ 0.0f, 0.0f, - 1.0f, 0.0f ],
172 		[ 0.0f, 0.0f,   0.0f, 1.0f ] ] );
173 }
174 
175 
176 ////////////////////////
177 // Vulkan Projections //
178 ////////////////////////
179 
180 mat4 vkPerspective( float fovy, float aspect, float near, float far ) {
181 	mat4 result = void;
182 	result.clear( 0 );
183 
184 	const float t = 1.0f / tan( 0.5f * fovy * deg2rad );
185 	const float n_f = near - far;
186 /*
187 	result[0][0] = t / aspect;
188 	result[1][1] = t;
189 	result[2][2] = - ( near + far ) / n_f;
190 	result[2][3] = 1;
191 	result[3][2] = ( 2 * near * far ) / n_f;
192 
193 	// premultiplying a glProjection with this clip matrix results in expected vkProjection
194 	// source: https://matthewwellings.com/blog/the-new-vulkan-coordinate-system/
195 	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 ));
196 	return clip * result;
197 */
198 	// we can avoid matrix multiply of clip and projection with following variant for the matrix entries
199 	result[0][0] = t / aspect;
200 	result[1][1] = - t;
201 	result[2][2] = 0.5 - 0.5 * ( near + far ) / n_f;
202 	result[2][3] = 1;
203 	result[3][2] = near * far / n_f;
204 
205 	return result;
206 }