1 module des.space.transform;
2 
3 public import des.math.linear.vector;
4 public import des.math.linear.matrix;
5 
6 import des.util.logsys;
7 
8 import std.math;
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     float nflim = 1e-5;
114 
115     mat4 self_mtr;
116 
117     ///
118     abstract void recalc();
119 
120     invariant()
121     {
122         assert( _ratio > 0 );
123         assert( _near > 0 && _near < _far );
124         assert( _far > 0 );
125         assert( nflim > 0 );
126         assert( !!self_mtr );
127     }
128 
129 public:
130 
131     @property
132     {
133         ///
134         float ratio() const { return _ratio; }
135         ///
136         float ratio( float v )
137         in { assert( v !is float.nan ); } body
138         {
139             enum lim = 1000000.0f;
140             if( v <= 0 )
141             {
142                 v = 1 / lim;
143                 logger.warn( "value <= 0, set to: ", 1 / lim );
144             }
145             if( v > lim )
146             {
147                 v = lim;
148                 logger.warn( "value > %s, set to: %s", lim, lim );
149             }
150             _ratio = v;
151             recalc();
152             return v;
153         }
154 
155         ///
156         float near() const { return _near; }
157         ///
158         float near( float v )
159         in { assert( v !is float.nan ); } body
160         {
161             if( v > _far )
162             {
163                 _far = v + nflim;
164                 logger.warn( "value > far, far set to 'value + nflim': ", v + nflim );
165             }
166             if( v < 0 )
167             {
168                 v = 0;
169                 logger.warn( "value < 0, set to: 0" );
170             }
171             _near = v;
172             recalc();
173             return v;
174         }
175 
176         ///
177         float far() const { return _far; }
178         ///
179         float far( float v )
180         in { assert( v !is float.nan ); } body
181         {
182             if( v < nflim * 2 )
183             {
184                 v = nflim * 2;
185                 logger.warn( "value < nflim * 2, set to: ", nflim * 2 );
186             }
187             if( v < _near )
188             {
189                 _near = v - nflim;
190                 logger.warn( "value < near, near set to 'value - nflim': ", v - nflim );
191             }
192             _far = v;
193             recalc();
194             return v;
195         }
196 
197         ///
198         mat4 matrix() const { return self_mtr; }
199     }
200 }
201 
202 ///
203 class PerspectiveTransform : ViewTransform
204 {
205 protected:
206     float _fov = 70;
207 
208     override void recalc() { self_mtr = calcPerspective( _fov, _ratio, _near, _far ); }
209 
210     invariant() { assert( _fov > 0 ); }
211 
212 public:
213 
214     @property
215     {
216         ///
217         float fov() const { return _fov; }
218         ///
219         float fov( float v )
220         in { assert( v !is float.nan ); } body
221         {
222             enum minfov = 1e-5;
223             enum maxfov = 180 - minfov;
224             if( v < minfov )
225             {
226                 v = minfov;
227                 logger.warn( "value < minfov, set to minfov: ", minfov );
228             }
229             if( v > maxfov )
230             {
231                 v = maxfov;
232                 logger.warn( "value > maxfov, set to maxfov: ", maxfov );
233             }
234             _fov = v;
235             recalc();
236             return v;
237         }
238     }
239 }
240 
241 ///
242 class OrthoTransform : ViewTransform
243 {
244 protected:
245 
246     float _scale = 1;
247 
248     invariant()
249     {
250         assert( _scale > 0 );
251     }
252 
253     override void recalc()
254     {
255         auto s = 1.0 / _scale;
256         auto r = s * _ratio;
257         auto z = -2.0f / ( _far - _near );
258         auto o = -( _far + _near ) / ( _far - _near );
259 
260         self_mtr = mat4( s, 0, 0, 0,
261                          0, r, 0, 0,
262                          0, 0, z, o,
263                          0, 0, 0, 1 );
264     }
265 
266 public:
267 
268     @property
269     {
270         ///
271         float scale() const { return _scale; }
272         ///
273         float scale( float v )
274         in { assert( v !is float.nan ); } body
275         {
276             if( v < 1e-8 )
277             {
278                 v = 1e-8;
279                 logger.warn( "value < 1e-8, set to 1e-8" );
280             }
281             _scale = v;
282             recalc();
283             return v;
284         }
285     }
286 }
287 
288 private:
289 
290 mat4 calcLookAt( in vec3 pos, in vec3 trg, in vec3 up )
291 {
292     auto z = (pos-trg).e;
293     auto x = cross(up,z).e;
294     vec3 y;
295     if( x ) y = cross(z,x).e;
296     else
297     {
298         y = cross(z,vec3(1,0,0)).e;
299         x = cross(y,z).e;
300     }
301     return mat4( x.x, y.x, z.x, pos.x,
302                  x.y, y.y, z.y, pos.y,
303                  x.z, y.z, z.z, pos.z,
304                    0,   0,   0,     1 );
305 }
306 
307 mat4 calcPerspective( float fov_degree, float ratio, float znear, float zfar )
308 {
309                         /+ fov conv to radians and div 2 +/
310     float h = 1.0 / tan( fov_degree * PI / 360.0 );
311     float w = h / ratio;
312 
313     float depth = znear - zfar;
314     float q = ( znear + zfar ) / depth;
315     float n = ( 2.0f * znear * zfar ) / depth;
316 
317     return mat4( w, 0,  0, 0,
318                  0, h,  0, 0,
319                  0, 0,  q, n,
320                  0, 0, -1, 0 );
321 }
322 
323 mat4 calcOrtho( float w, float h, float znear, float zfar )
324 {
325     float x = znear - zfar;
326     return mat4( 2/w, 0,   0,       0,
327                  0,   2/h, 0,       0,
328                  0,   0,  -1/x,     0,
329                  0,   0,   znear/x, 1 );
330 }