1 module des.space.transform;
2 
3 public import des.math.linear.vector;
4 public import des.math.linear.matrix;
5 
6 import std.math;
7 import std.exception;
8 import std..string : format;
9 
10 ///
11 interface Transform
12 {
13     ///
14     mat4 matrix() @property const;
15 
16     ///
17     protected final static mat4 getMatrix( const(Transform) tr )
18     {
19         if( tr !is null )
20             return tr.matrix;
21         return mat4.diag(1);
22     }
23 }
24 
25 ///
26 class SimpleTransform : Transform
27 {
28 protected:
29     mat4 mtr; ///
30 
31 public:
32     @property
33     {
34         ///
35         mat4 matrix() const { return mtr; }
36         ///
37         void matrix( in mat4 m ) { mtr = m; }
38     }
39 }
40 
41 ///
42 class TransformList : Transform
43 {
44     Transform[] list; ///
45     enum Order { DIRECT, REVERSE }
46     Order order = Order.DIRECT; ///
47 
48     ///
49     @property mat4 matrix() const
50     {
51         mat4 buf;
52         if( order == Order.DIRECT )
53             foreach( tr; list )
54                 buf *= tr.matrix;
55         else
56             foreach_reverse( tr; list )
57                 buf *= tr.matrix;
58         return buf;
59     }
60 }
61 
62 ///
63 class CachedTransform : Transform
64 {
65 protected:
66     mat4 mtr; ///
67     Transform transform_source; ///
68 
69 public:
70 
71     ///
72     this( Transform ntr ) { setTransform( ntr ); }
73 
74     ///
75     void setTransform( Transform ntr )
76     {
77         transform_source = ntr;
78         recalc();
79     }
80 
81     ///
82     void recalc()
83     {
84         if( transform_source !is null )
85             mtr = transform_source.matrix;
86         else mtr = mat4.diag(1);
87     }
88 
89     ///
90     @property mat4 matrix() const { return mtr; }
91 }
92 
93 ///
94 class LookAtTransform : Transform
95 {
96     ///
97     vec3 pos=vec3(0), target=vec3(0), up=vec3(0,0,1);
98 
99     ///
100     @property mat4 matrix() const
101     { return calcLookAt( pos, target, up ); }
102 }
103 
104 ///
105 class ViewTransform : Transform
106 {
107 protected:
108 
109     float _ratio = 4.0f / 3.0f;
110     float _near = 1e-1;
111     float _far = 1e5;
112 
113     mat4 self_mtr;
114 
115     ///
116     abstract void recalc();
117 
118     invariant()
119     {
120         assert( _ratio > 0 );
121         assert( 0 < _near && _near < _far );
122         assert( !!self_mtr );
123     }
124 
125 public:
126 
127     enum MAX_RATIO = 65536;
128 
129     @property
130     {
131         ///
132         float ratio() const { return _ratio; }
133         ///
134         float ratio( float v )
135         {
136             checkLimit( 1.0f / MAX_RATIO, v, MAX_RATIO, "min ratio", "ratio value", "max ratio" );
137             _ratio = v;
138             recalc();
139             return v;
140         }
141 
142         ///
143         float near() const { return _near; }
144         ///
145         float near( float v )
146         {
147             checkLimit( 0, v, _far, "zero", "near value", "far value" );
148             _near = v;
149             recalc();
150             return v;
151         }
152 
153         ///
154         float far() const { return _far; }
155         ///
156         float far( float v )
157         {
158             checkLimit( _near, v, float.max, "near value", "far value", "float.max" );
159             enforce( v > _near );
160             _far = v;
161             recalc();
162             return v;
163         }
164 
165         ///
166         mat4 matrix() const { return self_mtr; }
167     }
168 
169 protected:
170 
171     final void checkLimit(string file=__FILE__,size_t line=__LINE__)
172         ( float min_value, float value, float max_value,
173           string min_name, string name, string max_name ) const
174     {
175         enforce( min_value < value, new Exception( format( "%s (%s) less that %s (%s)",
176                                                            name, value, min_name, min_value ),
177                                                    file, line ) );
178 
179         enforce( value < max_value, new Exception( format( "%s (%s) more that %s (%s)",
180                                                            name, value, max_name, max_value ),
181                                                    file, line ) );
182     }
183 }
184 
185 ///
186 class PerspectiveTransform : ViewTransform
187 {
188 protected:
189     float _fov = 70;
190 
191     override void recalc() { self_mtr = calcPerspective( _fov, _ratio, _near, _far ); }
192 
193     invariant() { assert( _fov > 0 ); }
194 
195 public:
196 
197     enum MIN_FOV = 1e-5;
198     enum MAX_FOV = 180 - MIN_FOV;
199 
200     @property
201     {
202         ///
203         float fov() const { return _fov; }
204         ///
205         float fov( float v )
206         {
207             checkLimit( MIN_FOV, v, MAX_FOV, "min fov", "fov value", "max fov" );
208             _fov = v;
209             recalc();
210             return v;
211         }
212     }
213 }
214 
215 ///
216 class OrthoTransform : ViewTransform
217 {
218 protected:
219 
220     float _scale = 1;
221 
222     invariant() { assert( _scale > 0 ); }
223 
224     override void recalc()
225     {
226         auto s = 1.0 / _scale;
227         auto r = s * _ratio;
228         auto z = -2.0f / ( _far - _near );
229         auto o = -( _far + _near ) / ( _far - _near );
230 
231         self_mtr = mat4( s, 0, 0, 0,
232                          0, r, 0, 0,
233                          0, 0, z, o,
234                          0, 0, 0, 1 );
235     }
236 
237 public:
238 
239     @property
240     {
241         ///
242         float scale() const { return _scale; }
243         ///
244         float scale( float v )
245         {
246             checkLimit( 0, v, float.max, "zero", "scale value", "float.max" );
247             _scale = v;
248             recalc();
249             return v;
250         }
251     }
252 }
253 
254 private:
255 
256 mat4 calcLookAt( in vec3 pos, in vec3 trg, in vec3 up )
257 in {
258     assert( !!pos );
259     assert( !!trg );
260     assert( !!up );
261 }
262 out(mtr) { assert( !!mtr ); }
263 body {
264     auto z = (pos-trg).e;
265     auto x = cross(up,z).e;
266     vec3 y;
267     if( x ) y = cross(z,x).e;
268     else
269     {
270         y = cross(z,vec3(1,0,0)).e;
271         x = cross(y,z).e;
272     }
273     return mat4( x.x, y.x, z.x, pos.x,
274                  x.y, y.y, z.y, pos.y,
275                  x.z, y.z, z.z, pos.z,
276                    0,   0,   0,     1 );
277 }
278 
279 mat4 calcPerspective( float fov_degree, float ratio, float znear, float zfar )
280 in {
281     assert( fov_degree > 0 );
282     assert( ratio > 0 );
283     assert( znear !is float.nan );
284     assert( zfar !is float.nan );
285 }
286 out(mtr) { assert( !!mtr ); }
287 body {
288                         /+ fov conv to radians and div 2 +/
289     float h = 1.0 / tan( fov_degree * PI / 360.0 );
290     float w = h / ratio;
291 
292     float depth = znear - zfar;
293     float q = ( znear + zfar ) / depth;
294     float n = ( 2.0f * znear * zfar ) / depth;
295 
296     return mat4( w, 0,  0, 0,
297                  0, h,  0, 0,
298                  0, 0,  q, n,
299                  0, 0, -1, 0 );
300 }
301 
302 mat4 calcOrtho( float w, float h, float znear, float zfar )
303 in {
304     assert( w > 0 );
305     assert( h > 0 );
306     assert( znear !is float.nan );
307     assert( zfar !is float.nan );
308 }
309 out(mtr) { assert( !!mtr ); }
310 body {
311     float x = znear - zfar;
312     return mat4( 2/w, 0,   0,       0,
313                  0,   2/h, 0,       0,
314                  0,   0,  -1/x,     0,
315                  0,   0,   znear/x, 1 );
316 }