1 module des.math.linear.matrix;
2 
3 import std.math;
4 import std.traits;
5 import std.algorithm;
6 import std.range;
7 import std.array;
8 import std.exception;
9 
10 import des.ts;
11 import des.stdx.traits;
12 
13 import des.math.util;
14 import des.math.linear.vector;
15 import des.math.linear.quaterni;
16 
17 ///
18 template isMatrix(E)
19 {
20     enum isMatrix = is( typeof( impl(E.init) ) );
21     void impl(size_t H,size_t W,T)( Matrix!(H,W,T) ){}
22 }
23 
24 ///
25 template isStaticMatrix(E)
26 {
27     static if( !isMatrix!E )
28         enum isStaticMatrix = false;
29     else enum isStaticMatrix = E.isStatic;
30 }
31 
32 ///
33 template isDynamicMatrix(E)
34 {
35     static if( !isMatrix!E )
36         enum isDynamicMatrix = false;
37     else enum isDynamicMatrix = E.isDynamic;
38 }
39 
40 unittest
41 {
42     static assert( !isStaticMatrix!float );
43     static assert( !isDynamicMatrix!float );
44 }
45 
46 private @property
47 {
48     import std..string;
49 
50     string identityMatrixDataString(size_t S)()
51     { return diagMatrixDataString!(S,S)(1); }
52 
53     string zerosMatrixDataString(size_t H, size_t W)()
54     { return diagMatrixDataString!(H,W)(0); }
55 
56     string diagMatrixDataString(size_t H, size_t W)( size_t val )
57     {
58         string[] ret;
59         foreach( i; 0 .. H )
60         {
61             string[] buf;
62             foreach( j; 0 .. W )
63                 buf ~= format( "%d", i == j ? val : 0 );
64             ret ~= "[" ~ buf.join(",") ~ "]";
65         }
66         return "[" ~ ret.join(",") ~ "]";
67     }
68 
69     unittest
70     {
71         assertEq( identityMatrixDataString!3, "[[1,0,0],[0,1,0],[0,0,1]]" );
72         assertEq( zerosMatrixDataString!(3,3), "[[0,0,0],[0,0,0],[0,0,0]]" );
73     }
74 
75     string castArrayString(string type, size_t H, size_t W)()
76     { return format( "cast(%s[%d][%d])", type, W, H ); }
77 }
78 
79 /++
80  +/
81 struct Matrix(size_t H, size_t W, E)
82 {
83     ///
84     alias selftype = Matrix!(H,W,E);
85 
86     ///
87     alias datatype = E;
88 
89     /// `H == 0 || W == 0`
90     enum bool isDynamic = H == 0 || W == 0;
91     /// `H != 0 && W != 0`
92     enum bool isStatic = H != 0 && W != 0;
93 
94     /// `H == 0`
95     enum bool isDynamicHeight = H == 0;
96     /// `H != 0`
97     enum bool isStaticHeight = H != 0;
98 
99     /// `W == 0`
100     enum bool isDynamicWidth = W == 0;
101     /// `W != 0`
102     enum bool isStaticWidth = W != 0;
103 
104     /// `isStaticHeight && isDynamicWidth`
105     enum bool isStaticHeightOnly = isStaticHeight && isDynamicWidth;
106     /// `isStaticWidth && isDynamicHeight`
107     enum bool isStaticWidthOnly = isStaticWidth && isDynamicHeight;
108 
109     /// `isDynamicHeight && isDynamicWidth`
110     enum bool isDynamicAll = isDynamicHeight && isDynamicWidth;
111     /// `isStaticWidthOnly || isStaticHeightOnly`
112     enum bool isDynamicOne = isStaticWidthOnly || isStaticHeightOnly;
113 
114     static if( isStatic )
115     {
116         static if( isNumeric!E )
117         {
118             static if( H == W )
119                 /// static data ( if isNumeric!E fills valid numbers: if squred then identity matrix else zeros )
120                 E[W][H] data = mixin( castArrayString!("E",H,H) ~ identityMatrixDataString!H );
121             else 
122                 E[W][H] data = mixin( castArrayString!("E",H,W) ~ zerosMatrixDataString!(H,W) );
123         }
124         else E[W][H] data;
125     }
126     else static if( isStaticWidthOnly )
127         /// static width only
128         E[W][] data;
129     else static if( isStaticHeightOnly )
130         /// static height only
131         E[][H] data;
132     else
133         /// full dynamic
134         E[][] data;
135 
136     ///
137     alias data this;
138 
139 pure:
140 
141     private static bool allowSomeOp(size_t A, size_t B )
142     { return A==B || A==0 || B==0; }
143 
144     static if( isStatic || isDynamicOne )
145     {
146         /++ fill data and set dynamic size for `isDynamicOne` matrix
147 
148             only:
149                 if( isStatic || isDynamicOne )
150          +/
151         this(X...)( in X vals )
152             if( is(typeof(flatData!E(vals))) )
153         {
154             static if( isStatic )
155             {
156                 static if( hasNoDynamic!X )
157                 {
158                     static if( X.length > 1 )
159                     {
160                         static assert( getElemCount!X == W*H, "wrong args count" );
161                         static assert( isConvertable!(E,X), "wrong args type" );
162                         mixin( matrixStaticFill!("E","data","vals",W,E,X) );
163                     }
164                     else static if( X.length == 1 && isStaticMatrix!(X[0]) )
165                     {
166                         static assert( X[0].width == W && X[0].height == H );
167                         foreach( y; 0 .. H )
168                             foreach( x; 0 .. W )
169                                 data[y][x] = vals[0][y][x];
170                     }
171                     else enum __DF=true;
172                 }
173                 else enum __DF=true;
174 
175                 static if( is(typeof(__DF)) )
176                     _fillData( flatData!E(vals) );
177             }
178             else static if( isStaticWidthOnly )
179             {
180                 auto buf = flatData!E(vals);
181                 enforce( !(buf.length%W), "wrong args length" );
182                 resize( buf.length/W, W );
183                 _fillData( buf );
184             }
185             else static if( isStaticHeightOnly )
186             {
187                 auto buf = flatData!E(vals);
188                 enforce( !(buf.length%H), "wrong args length" );
189                 resize( H, buf.length/H );
190                 _fillData( buf );
191             }
192         }
193     }
194     else
195     {
196         /++ set size and fill data
197 
198             only:
199                 if( isDynamicAll )
200          +/
201         this(X...)( size_t iH, size_t iW, in X vals )
202         {
203             auto buf = flatData!E(vals);
204             enforce( buf.length == iH * iW || buf.length == 1, "wrong args length" );
205             resize( iH, iW );
206             _fillData( buf );
207         }
208 
209         /++ set size
210 
211             only:
212                 if( isDynamicAll )
213          +/
214         this( size_t iH, size_t iW ) { resize( iH, iW ); }
215     }
216 
217     ///
218     this(size_t oH, size_t oW, X)( auto ref const(Matrix!(oH,oW,X)) mtr )
219         if( is( typeof( E(X.init) ) ) )
220     {
221         static if( isDynamic )
222             resize( mtr.height, mtr.width );
223         foreach( i; 0 .. mtr.height )
224             foreach( j; 0 .. mtr.width )
225                 data[i][j] = E(mtr[i][j]);
226     }
227 
228     static if( isDynamic )
229     {
230         /++
231             only:
232                 if( isDynamic )
233          +/
234         this(this)
235         {
236             data = data.dup;
237             foreach( ref row; data )
238                 row = row.dup;
239         }
240 
241         /++
242             only:
243                 if( isDynamic )
244          +/
245         ref typeof(this) opAssign(size_t bH, size_t bW, X)( auto ref const(Matrix!(bH,bW,X)) b )
246             if( allowSomeOp(H,bH) && allowSomeOp(W,bW) && is(typeof(E(X.init))) )
247         {
248             static if( isStaticHeight && b.isDynamicHeight ) enforce( height == b.height, "wrong height" );
249             static if( isStaticWidth && b.isDynamicWidth ) enforce( width == b.width, "wrong width" );
250             static if( isDynamic ) resize(b.height,b.width);
251             _fillData(flatData!E(b.data));
252             return this;
253         }
254     }
255 
256     private void _fillData( in E[] vals... )
257     {
258         enforce( vals.length > 0, "no vals to fill" );
259         if( vals.length > 1 )
260         {
261             enforce( vals.length == height * width, "wrong vals length" );
262             size_t k = 0;
263             foreach( ref row; data )
264                 foreach( ref v; row )
265                     v = vals[k++];
266         }
267         else foreach( ref row; data ) row[] = vals[0];
268     }
269 
270     /// fill data
271     ref typeof(this) fill( in E[] vals... )
272     {
273         _fillData( vals );
274         return this;
275     }
276 
277     static if( (W==H && isStatic) || isDynamicOne )
278     {
279         /++ get diagonal matrix, diagonal elements fills cyclically
280 
281             only:
282                 if( (W==H && isStatic) || isDynamicOne )
283          +/
284         static auto diag(X...)( in X vals )
285         if( X.length > 0 && is(typeof(E(0))) && is(typeof(flatData!E(vals))) )
286         {
287             selftype ret;
288             static if( isStaticHeight ) auto L = H;
289             else auto L = W;
290 
291             static if( ret.isDynamic )
292                 ret.resize(L,L);
293 
294             ret._fillData( E(0) );
295             ret.fillDiag( flatData!E(vals) );
296 
297             return ret;
298         }
299     }
300 
301     ///
302     ref typeof(this) fillDiag( in E[] vals... )
303     {
304         enforce( vals.length > 0, "no vals to fill" );
305         enforce( height == width, "not squared" );
306         size_t k = 0;
307         foreach( i; 0 .. height )
308             data[i][i] = vals[k++%$];
309         return this;
310     }
311 
312     static if( isStaticHeight )
313         /++
314             only:
315                 if( isStaticHeight )
316          +/
317         enum height = H;
318     else
319     {
320         /// if isStaticHeight it's enum (not available to set)
321         @property size_t height() const { return data.length; }
322         /// if isStaticHeight it's enum (not available to set)
323         @property size_t height( size_t nh )
324         {
325             resize( nh, width );
326             return nh;
327         }
328     }
329 
330     static if( isStaticWidth )
331         /++
332             only:
333                 if( isStaticWidth )
334          +/
335         enum width = W;
336     else
337     {
338         private size_t _width = W;
339         /// if isStaticWidth it's enum (not available to set)
340         @property size_t width() const { return _width; }
341         /// if isStaticWidth it's enum (not available to set)
342         @property size_t width( size_t nw )
343         {
344             resize( height, nw );
345             return nw;
346         }
347     }
348 
349     static if( isDynamic )
350     {
351         /++ resize dynamic matrix, new height/width must equals static height/width
352 
353             only:
354                 if( isDynamic )
355          +/
356         ref typeof(this) resize( size_t nh, size_t nw )
357         {
358             static if( isStaticHeight ) enforce!Exception( nh == H, "height is static" );
359             static if( isStaticWidth ) enforce!Exception( nw == W, "width is static" );
360 
361             static if( isDynamicHeight ) data.length = nh;
362             static if( isDynamicWidth )
363             {
364                 _width = nw;
365                 foreach( i; 0 .. height )
366                     data[i].length = nw;
367             }
368             return this;
369         }
370     }
371 
372     ///
373     auto expandHeight(size_t bH, size_t bW, X)( auto ref const(Matrix!(bH,bW,X)) mtr ) const
374         if( (bW==W||W==0||bW==0) && is(typeof(E(X.init))) )
375     {
376         static if( isDynamicWidth || mtr.isDynamicWidth )
377             enforce( mtr.width == width, "wrong width" );
378 
379         auto ret = Matrix!(0,W,E)(this);
380         auto last_height = height;
381         ret.resize( ret.height + mtr.height, width );
382         foreach( i; 0 .. mtr.height )
383             foreach( j; 0 .. width )
384                 ret.data[last_height+i][j] = E(mtr.data[i][j]);
385         return ret;
386     }
387 
388     ///
389     auto expandHeight(X...)( in X vals ) const
390         if( is(typeof(Matrix!(1,W,E)(vals))) )
391     { return expandHeight( Matrix!(1,W,E)(vals) ); }
392 
393     ///
394     auto expandWidth(size_t bH, size_t bW, X)( auto ref const(Matrix!(bH,bW,X)) mtr ) const
395         if( (bH==H||H==0||bH==0) && is(typeof(E(X.init))) )
396     {
397         static if( isDynamicHeight || mtr.isDynamicHeight )
398             enforce( mtr.height == height, "wrong height" );
399         auto ret = Matrix!(H,0,E)(this);
400         auto last_width = width;
401         ret.resize( height, ret.width + mtr.width );
402         foreach( i; 0 .. height )
403             foreach( j; 0 .. mtr.width )
404                 ret.data[i][last_width+j] = E(mtr.data[i][j]);
405         return ret;
406     }
407 
408     ///
409     auto expandWidth(X...)( in X vals ) const
410         if( is(typeof(Matrix!(H,1,E)(vals))) )
411     { return expandWidth( Matrix!(H,1,E)(vals) ); }
412 
413     ///
414     auto asArray() const @property
415     {
416         auto ret = new E[](width*height);
417         foreach( i; 0 .. height )
418             foreach( j; 0 .. width )
419                 ret[i*width+j] = data[i][j];
420         return ret;
421     }
422 
423     ///
424     auto sliceHeight( size_t start, size_t count=0 ) const
425     {
426         enforce( start < height );
427         count = count ? count : height - start;
428         auto ret = Matrix!(0,W,E)(this);
429         ret.resize( count, width );
430         foreach( i; 0 .. count )
431             ret.data[i][] = data[start+i][];
432         return ret;
433     }
434 
435     ///
436     auto sliceWidth( size_t start, size_t count=0 ) const
437     {
438         enforce( start < width );
439         count = count ? count : width - start;
440         auto ret = Matrix!(H,0,E)(this);
441         ret.resize( height, count );
442         foreach( i; 0 .. height )
443             foreach( j; 0 .. count )
444             ret.data[i][j] = data[i][start+j];
445         return ret;
446     }
447 
448     ///
449     auto opUnary(string op)() const
450         if( op == "-" && is( typeof( E.init * (-1) ) ) )
451     {
452         auto ret = selftype(this);
453         foreach( ref row; ret.data )
454             foreach( ref v; row )
455                 v = v * -1;
456         return ret;
457     }
458 
459     private void checkCompatible(size_t bH, size_t bW, X)( auto ref const(Matrix!(bH,bW,X)) mtr ) const
460     {
461         static if( isDynamicHeight || mtr.isDynamicHeight )
462             enforce( height == mtr.height, "wrong height" );
463         static if( isDynamicWidth || mtr.isDynamicWidth )
464             enforce( width == mtr.width, "wrong width" );
465     }
466 
467     ///
468     auto opBinary(string op, size_t bH, size_t bW, X)( auto ref const(Matrix!(bH,bW,X)) mtr ) const
469         if( (op=="+"||op=="-") && allowSomeOp(H,bH) && allowSomeOp(W,bW) )
470     {
471         checkCompatible( mtr );
472 
473         auto ret = selftype(this);
474 
475         foreach( i; 0 .. height )
476             foreach( j; 0 .. width )
477                 mixin( `ret[i][j] = E(data[i][j] ` ~ op ~ ` mtr[i][j]);` );
478         return ret;
479     }
480 
481     ///
482     auto opBinary(string op,X)( auto ref const(X) b ) const
483         if( (op!="+" && op!="-") && isValidOp!(op,E,X) )
484     {
485         auto ret = selftype(this);
486         foreach( i; 0 .. height )
487             foreach( j; 0 .. width )
488                 mixin( `ret[i][j] = E(data[i][j] ` ~ op ~ ` b);` );
489         return ret;
490     }
491 
492     ///
493     auto opBinary(string op,size_t bH, size_t bW, X)( auto ref const(Matrix!(bH,bW,X)) mtr ) const
494         if( op=="*" && allowSomeOp(W,bH) && isValidOp!("*",E,X) )
495     {
496         static if( isDynamic || mtr.isDynamic )
497             enforce( width == mtr.height, "incompatible sizes for mul" );
498         Matrix!(H,bW,E) ret;
499         static if( ret.isDynamic ) ret.resize(height,mtr.width);
500 
501         foreach( i; 0 .. height )
502             foreach( j; 0 .. mtr.width )
503             {
504                 ret[i][j] = E( data[i][0] * mtr.data[0][j] );
505                 foreach( k; 1 .. width )
506                     ret[i][j] = ret[i][j] + E( data[i][k] * mtr.data[k][j] );
507             }
508 
509         return ret;
510     }
511 
512     ///
513     ref typeof(this) opOpAssign(string op, E)( auto ref const(E) b )
514         if( mixin( `is( typeof( selftype.init ` ~ op ~ ` E.init ) == selftype )` ) )
515     { mixin( `return this = this ` ~ op ~ ` b;` ); }
516 
517     ///
518     bool opCast(E)() const if( is( E == bool ) )
519     {
520         foreach( v; asArray ) if( !isFinite(v) ) return false;
521         return true;
522     }
523 
524     /// transponate
525     auto T() const @property
526     {
527         Matrix!(W,H,E) ret;
528         static if( isDynamic ) ret.resize(width,height);
529         foreach( i; 0 .. height)
530             foreach( j; 0 .. width )
531                 ret[j][i] = data[i][j];
532         return ret;
533     }
534 
535     ///
536     ref typeof(this) setCol(X...)( size_t no, in X vals )
537         if( is(typeof(flatData!E(vals))) )
538     {
539         enforce( no < width, "bad col index" );
540         auto buf = flatData!E(vals);
541         enforce( buf.length == height, "bad data length" );
542         foreach( i; 0 .. height )
543             data[i][no] = buf[i];
544         return this;
545     }
546 
547     ///
548     ref typeof(this) setRow(X...)( size_t no, in X vals )
549         if( is(typeof(flatData!E(vals))) )
550     {
551         enforce( no < height, "bad row index" );
552         auto buf = flatData!E(vals);
553         enforce( buf.length == width, "bad data length" );
554         data[no][] = buf[];
555         return this;
556     }
557 
558     ///
559     ref typeof(this) setRect(size_t bH, size_t bW, X)( size_t pos_row, size_t pos_col, auto ref const(Matrix!(bH,bW,X)) mtr )
560         if( is(typeof(E(X.init))) )
561     {
562         enforce( pos_row < height, "bad row index" );
563         enforce( pos_col < width, "bad col index" );
564         enforce( pos_row + mtr.height <= height, "bad height size" );
565         enforce( pos_col + mtr.width <= width, "bad width size" );
566 
567         foreach( i; 0 .. mtr.height )
568             foreach( j; 0 .. mtr.width )
569                 data[i+pos_row][j+pos_col] = E(mtr[i][j]);
570 
571         return this;
572     }
573 
574     ///
575     auto col( size_t no ) const
576     {
577         enforce( no < width, "bad col index" );
578         Matrix!(H,1,E) ret;
579         static if( ret.isDynamic )
580             ret.resize(height,1);
581         foreach( i; 0 .. height )
582             ret[i][0] = data[i][no];
583         return ret;
584     }
585 
586     ///
587     auto row( size_t no ) const
588     {
589         enforce( no < height, "bad row index" );
590         Matrix!(1,W,E) ret;
591         static if( ret.isDynamic )
592             ret.resize(1,width);
593         ret[0][] = data[no][];
594         return ret;
595     }
596 
597     ///
598     auto opBinary(string op,size_t K,X)( auto ref const(Vector!(K,X)) v ) const
599         if( op=="*" && allowSomeOp(W,K) && isValidOp!("*",E,X) && isValidOp!("+",E,E) )
600     {
601         static if( isDynamic || v.isDynamic )
602             enforce( width == v.data.length, "wrong vector length" );
603 
604         Vector!(H,E) ret;
605 
606         static if( ret.isDynamic )
607             ret.length = height;
608 
609         foreach( i; 0 .. height )
610         {
611             ret[i] = data[i][0] * v[0];
612 
613             foreach( j; 1 .. width )
614                 ret[i] = ret[i] + E(data[i][j] * v[j]);
615         }
616 
617         return ret;
618     }
619 
620     ///
621     auto opBinaryRight(string op,size_t K,X)( auto ref const(Vector!(K,X)) v ) const
622         if( op=="*" && isVector!(typeof(selftype.init.T * typeof(v).init)) )
623     { return this.T * v; }
624 
625     static if( W==4 && ( H==4 || H==3 ) && isFloatingPoint!E )
626     {
627         /++ transform vector, equals `( m * vec4( v, fc ) ).xyz`
628 
629             only:
630             W==4 && ( H==4 || H==3 ) && isFloatingPoint!E
631          +/
632         Vector!(3,E) tr(X)( in Vector!(3,X) v, E fc ) const
633         { return ( this * Vector!(4,E)( v, fc ) ).xyz; }
634 
635         /++ project vector
636          +
637          + equals:
638          + ---
639          + auto r = m * vec4( v, fc );
640          + return r.xyz / r.w;
641          + ---
642          + only:
643          +    W==4 && ( H==4 || H==3 ) && isFloatingPoint!E
644          +/
645         Vector!(3,E) prj(X)( in Vector!(3,X) v, E fc ) const
646         {
647             auto r = this * Vector!(4,E)( v, fc );
648             return r.xyz / r.w;
649         }
650        
651         @property
652         {
653             ///
654             Vector!(3,E) offset() const
655             { return Vector!(3,E)( cast(E[3])( col(3).data[0..3] ) ); }
656 
657             ///
658             Vector!(3,X) offset(X)( in Vector!(3,X) v )
659             { setCol(3, Vector!(4,E)( v, data[3][3] ) ); return v; }
660         }
661     }
662 
663     static private size_t[] getIndexesWithout(size_t max, in size_t[] arr)
664     {
665         size_t[] ret;
666         foreach( i; 0 .. max ) if( !canFind(arr,i) ) ret ~= i;
667         return ret;
668     }
669 
670     ///
671     auto subWithout( size_t[] without_rows=[], size_t[] without_cols=[] ) const
672     {
673         auto with_rows = getIndexesWithout(height,without_rows);
674         auto with_cols = getIndexesWithout(width,without_cols);
675 
676         return sub( with_rows,with_cols );
677     }
678 
679     /// get sub matrix
680     auto sub( size_t[] with_rows, size_t[] with_cols ) const
681     {
682         auto wrows = array( uniq(with_rows) );
683         auto wcols = array( uniq(with_cols) );
684 
685         enforce( all!(a=>a<height)( wrows ), "bad row index" );
686         enforce( all!(a=>a<width)( wcols ), "bad col index" );
687 
688         Matrix!(0,0,E) ret;
689         ret.resize( wrows.length, wcols.length );
690 
691         foreach( i, orig_i; wrows )
692             foreach( j, orig_j; wcols )
693                 ret[i][j] = data[orig_i][orig_j];
694 
695         return ret;
696     }
697 
698     static if( isFloatingPoint!E && ( isDynamicAll ||
699                 ( isStaticHeightOnly && H > 1 ) ||
700                 ( isStaticWidthOnly && W > 1 ) ||
701                 ( isStatic && W == H ) ) )
702     {
703         ///
704         E cofactor( size_t i, size_t j ) const
705         { return subWithout([i],[j]).det * coef(i,j); }
706 
707         private static nothrow @trusted auto coef( size_t i, size_t j )
708         { return ((i+j)%2?-1:1); }
709         
710         ///
711         auto det() const @property 
712         {
713             static if( isDynamic )
714                 enforce( width == height, "not square matrix" );
715 
716             static if( isDynamic )
717             {
718                 if( width == 1 )
719                     return data[0][0];
720                 else if( width == 2 )
721                     return data[0][0] * data[1][1] -
722                            data[0][1] * data[1][0];
723                 else return classicDet;
724             }
725             else
726             {
727                 static if( W == 1 )
728                     return data[0][0];
729                 else static if( W == 2 )
730                     return data[0][0] * data[1][1] -
731                            data[0][1] * data[1][0];
732                 else return classicDet;
733             }
734         }
735 
736         private @property classicDet() const
737         {
738             auto i = 0UL; // TODO: find max zeros line
739             auto ret = data[i][0] * cofactor(i,0);
740 
741             foreach( j; 1 .. width )
742                 ret = ret + data[i][j] * cofactor(i,j);
743 
744             return ret;
745         }
746 
747         ///
748         auto inv() const @property
749         {
750             static if( isDynamic )
751                 enforce( width == height, "not square matrix" );
752             else static assert( W==H, "not square matrix" );
753 
754             selftype buf;
755 
756             static if( isDynamic )
757                 buf.resize(height,width);
758 
759             foreach( i; 0 .. height )
760                 foreach( j; 0 .. width )
761                     buf[i][j] = cofactor(i,j);
762 
763             auto i = 0UL; // TODO: find max zeros line
764             auto d = data[i][0] * buf[i][0];
765 
766             foreach( j; 1 .. width )
767                 d = d + data[i][j] * buf[i][j];
768 
769             return buf.T / d;
770         }
771 
772         static if( (isStaticHeightOnly && H==4) ||
773                    (isStaticWidthOnly && W==4) ||
774                    (isStatic && H==W && H==4) ||
775                    isDynamicAll )
776         {
777             /++ only for transform matrix +/
778             @property auto speedTransformInv() const
779             {
780                 static if( isDynamic )
781                 {
782                     enforce( width == height, "not square matrix" );
783                     enforce( width == 4, "matrix must be 4x4" );
784                 }
785 
786                 selftype ret;
787                 static if( isDynamic )
788                     ret.resize( height, width );
789 
790                 foreach( i; 0 .. 3 )
791                     foreach( j; 0 .. 3 )
792                         ret[i][j] = this[j][i];
793 
794                 auto a22k = 1.0 / this[3][3];
795 
796                 ret[0][3] = -( ret[0][0] * this[0][3] + ret[0][1] * this[1][3] + ret[0][2] * this[2][3] ) * a22k;
797                 ret[1][3] = -( ret[1][0] * this[0][3] + ret[1][1] * this[1][3] + ret[1][2] * this[2][3] ) * a22k;
798                 ret[2][3] = -( ret[2][0] * this[0][3] + ret[2][1] * this[1][3] + ret[2][2] * this[2][3] ) * a22k;
799 
800                 ret[3][0] = -( this[3][0] * ret[0][0] + this[3][1] * ret[1][0] + this[3][2] * ret[2][0] ) * a22k;
801                 ret[3][1] = -( this[3][0] * ret[0][1] + this[3][1] * ret[1][1] + this[3][2] * ret[2][1] ) * a22k;
802                 ret[3][2] = -( this[3][0] * ret[0][2] + this[3][1] * ret[1][2] + this[3][2] * ret[2][2] ) * a22k;
803                 
804                 ret[3][3] = a22k * ( 1.0 - ( this[3][0] * ret[0][3] + this[3][1] * ret[1][3] + this[3][2] * ret[2][3] ) );
805 
806                 return ret;
807             }
808         }
809 
810         ///
811         auto rowReduceInv() const @property
812         {
813             static if( isDynamic )
814                 enforce( width == height, "not square matrix" );
815 
816             auto ln = height;
817 
818             auto orig = selftype(this);
819             selftype invt;
820             static if( isDynamic )
821             {
822                 invt.resize(ln,ln);
823                 foreach( i; 0 .. ln )
824                     foreach( j; 0 .. ln )
825                         invt[i][j] = E(i==j);
826             }
827 
828             foreach( r; 0 .. ln-1 )
829             {
830                 auto k = E(1) / orig[r][r];
831                 foreach( c; 0 .. ln )
832                 {
833                     orig[r][c] *= k;
834                     invt[r][c] *= k;
835                 }
836                 foreach( rr; r+1 .. ln )
837                 {
838                     auto v = orig[rr][r];
839                     foreach( c; 0 .. ln )
840                     {
841                         orig[rr][c] -= orig[r][c] * v;
842                         invt[rr][c] -= invt[r][c] * v;
843                     }
844                 }
845             }
846 
847             foreach_reverse( r; 1 .. ln )
848             {
849                 auto k = E(1) / orig[r][r];
850                 foreach( c; 0 .. ln )
851                 {
852                     orig[r][c] *= k;
853                     invt[r][c] *= k;
854                 }
855                 foreach_reverse( rr; 0 .. r )
856                 {
857                     auto v = orig[rr][r];
858                     foreach( c; 0 .. ln )
859                     {
860                         orig[rr][c] -= orig[r][c] * v;
861                         invt[rr][c] -= invt[r][c] * v;
862                     }
863                 }
864             }
865 
866             return invt;
867         }
868     }
869 }
870 
871 alias Matrix2(T)   = Matrix!(2,2,T); ///
872 alias Matrix2x3(T) = Matrix!(2,3,T); ///
873 alias Matrix2x4(T) = Matrix!(2,4,T); ///
874 alias Matrix2xD(T) = Matrix!(2,0,T); ///
875 alias Matrix3x2(T) = Matrix!(3,2,T); ///
876 alias Matrix3(T)   = Matrix!(3,3,T); ///
877 alias Matrix3x4(T) = Matrix!(3,4,T); ///
878 alias Matrix3xD(T) = Matrix!(3,0,T); ///
879 alias Matrix4x2(T) = Matrix!(4,2,T); ///
880 alias Matrix4x3(T) = Matrix!(4,3,T); ///
881 alias Matrix4(T)   = Matrix!(4,4,T); ///
882 alias Matrix4xD(T) = Matrix!(4,0,T); ///
883 alias MatrixDx2(T) = Matrix!(0,2,T); ///
884 alias MatrixDx3(T) = Matrix!(0,3,T); ///
885 alias MatrixDx4(T) = Matrix!(0,4,T); ///
886 alias MatrixDxD(T) = Matrix!(0,0,T); ///
887 
888 alias Matrix!(2,2,float) mat2;   ///
889 alias Matrix!(2,3,float) mat2x3; ///
890 alias Matrix!(2,4,float) mat2x4; ///
891 alias Matrix!(2,0,float) mat2xD; ///
892 alias Matrix!(3,2,float) mat3x2; ///
893 alias Matrix!(3,3,float) mat3;   ///
894 alias Matrix!(3,4,float) mat3x4; ///
895 alias Matrix!(3,0,float) mat3xD; ///
896 alias Matrix!(4,2,float) mat4x2; ///
897 alias Matrix!(4,3,float) mat4x3; ///
898 alias Matrix!(4,4,float) mat4;   ///
899 alias Matrix!(4,0,float) mat4xD; ///
900 alias Matrix!(0,2,float) matDx2; ///
901 alias Matrix!(0,3,float) matDx3; ///
902 alias Matrix!(0,4,float) matDx4; ///
903 alias Matrix!(0,0,float) matD;   ///
904 
905 alias Matrix!(2,2,double) dmat2;   ///
906 alias Matrix!(2,3,double) dmat2x3; ///
907 alias Matrix!(2,4,double) dmat2x4; ///
908 alias Matrix!(2,0,double) dmat2xD; ///
909 alias Matrix!(3,2,double) dmat3x2; ///
910 alias Matrix!(3,3,double) dmat3;   ///
911 alias Matrix!(3,4,double) dmat3x4; ///
912 alias Matrix!(3,0,double) dmat3xD; ///
913 alias Matrix!(4,2,double) dmat4x2; ///
914 alias Matrix!(4,3,double) dmat4x3; ///
915 alias Matrix!(4,4,double) dmat4;   ///
916 alias Matrix!(4,0,double) dmat4xD; ///
917 alias Matrix!(0,2,double) dmatDx2; ///
918 alias Matrix!(0,3,double) dmatDx3; ///
919 alias Matrix!(0,4,double) dmatDx4; ///
920 alias Matrix!(0,0,double) dmatD;   ///
921 
922 alias Matrix!(2,2,real) rmat2;   ///
923 alias Matrix!(2,3,real) rmat2x3; ///
924 alias Matrix!(2,4,real) rmat2x4; ///
925 alias Matrix!(2,0,real) rmat2xD; ///
926 alias Matrix!(3,2,real) rmat3x2; ///
927 alias Matrix!(3,3,real) rmat3;   ///
928 alias Matrix!(3,4,real) rmat3x4; ///
929 alias Matrix!(3,0,real) rmat3xD; ///
930 alias Matrix!(4,2,real) rmat4x2; ///
931 alias Matrix!(4,3,real) rmat4x3; ///
932 alias Matrix!(4,4,real) rmat4;   ///
933 alias Matrix!(4,0,real) rmat4xD; ///
934 alias Matrix!(0,2,real) rmatDx2; ///
935 alias Matrix!(0,3,real) rmatDx3; ///
936 alias Matrix!(0,4,real) rmatDx4; ///
937 alias Matrix!(0,0,real) rmatD;   ///
938 
939 unittest
940 {
941     static assert( Matrix!(3,3,float).sizeof == float.sizeof * 9 );
942     static assert( Matrix!(3,3,float).isStatic );
943     static assert( Matrix!(0,3,float).isDynamic );
944     static assert( Matrix!(0,3,float).isDynamicHeight );
945     static assert( Matrix!(0,3,float).isStaticWidth );
946     static assert( Matrix!(3,0,float).isDynamic );
947     static assert( Matrix!(3,0,float).isDynamicWidth );
948     static assert( Matrix!(3,0,float).isStaticHeight );
949     static assert( Matrix!(0,0,float).isDynamic );
950     static assert( Matrix!(0,0,float).isDynamicHeight );
951     static assert( Matrix!(0,0,float).isDynamicWidth );
952 }
953 
954 unittest
955 {
956     static assert( isStaticMatrix!(mat3) );
957     static assert( !isStaticMatrix!(matD) );
958     static assert( !isStaticMatrix!(int[]) );
959 }
960 
961 unittest
962 {
963     assertEq( mat3.init, [[1,0,0],[0,1,0],[0,0,1]] );
964     assertEq( mat2.init, [[1,0],[0,1]] );
965     assertEq( mat2x3.init, [[0,0,0],[0,0,0]] );
966 }
967 
968 unittest
969 {
970     auto a = Matrix!(3,2,double)( 36, 0, 3, 3, 0, 0 );
971     auto b = Matrix!(3,2,double)( [ 36, 0, 3, 3, 0, 0 ] );
972     assertEq( a.asArray, b.asArray );
973     assertEq( a.asArray, [ 36, 0, 3, 3, 0, 0 ] );
974 }
975 
976 unittest
977 {
978     auto a = mat3( 1,2,3,4,5,6,7,8,9 );
979     assertEq( a.height, 3 );
980     assertEq( a.width, 3 );
981     assertEq( a, [[1,2,3],[4,5,6],[7,8,9]] );
982     static assert( !__traits(compiles,a.resize(1,1)) );
983     a.fill(1);
984     assertEq( a, [[1,1,1], [1,1,1], [1,1,1]] );
985     a[0][0] *= 3;
986     assertEq( a, [[3,1,1], [1,1,1], [1,1,1]] );
987     a[1][0] += 3;
988     assertEq( a, [[3,1,1], [4,1,1], [1,1,1]] );
989 }
990 
991 ///
992 unittest
993 {
994     static struct Test
995     {
996         union
997         {
998             mat3 um;
999             float[9] uf;
1000         }
1001     }
1002 
1003     Test tt;
1004 
1005     foreach( i, ref v; tt.uf ) v = i+1;
1006     assertEq( tt.um, [[1,2,3],[4,5,6],[7,8,9]] );
1007 }
1008 
1009 ///
1010 unittest
1011 {
1012     auto a = matDx3( 1,2,3,4,5,6,7,8,9 );
1013     assertThrown( matDx3(1,2,3,4) );
1014     assertEq( a.height, 3 );
1015     assertEq( a.width, 3 );
1016     assertEq( a.data, [[1,2,3],[4,5,6],[7,8,9]] );
1017     assertThrown( a.resize(2,2) );
1018     a.resize(2,3);
1019     assert( eq( a, [[1,2,3],[4,5,6]] ) );
1020     assertEq( a.height, 2 );
1021     a.fill(1);
1022     assertEq( a, [[1,1,1],[1,1,1]] );
1023     a.resize(0,3);
1024     assertEq( a.width, 3 );
1025     a.resize(2,3);
1026     a.fill(1);
1027 
1028     auto b = a;
1029     assertEq( b, [[1,1,1],[1,1,1]] );
1030 }
1031 
1032 unittest
1033 {
1034     auto a = mat3xD( 1,2,3,4,5,6,7,8,9 );
1035     assertThrown( mat3xD(1,2,3,4) );
1036     assertEq( a.height, 3 );
1037     assertEq( a.width, 3 );
1038     assertEq( a, [[1,2,3],[4,5,6],[7,8,9]] );
1039     assertThrown( a.resize(2,2) );
1040     a.resize(3,2);
1041     assertEq( a, [[1,2],[4,5],[7,8]] );
1042     assertEq( a.width, 2 );
1043     a.fill(1);
1044     assertEq( a, [[1,1],[1,1],[1,1]] );
1045 
1046     auto b = a;
1047     assertEq( b, [[1,1],[1,1],[1,1]] );
1048 }
1049 
1050 unittest
1051 {
1052     auto a = matD( 3,3, 1,2,3,4,5,6,7,8,9 );
1053     assertThrown( matD(1,2,3,4,5) );
1054     assertEq( a.height, 3 );
1055     assertEq( a.width, 3 );
1056     assertEq( a, [[1,2,3],[4,5,6],[7,8,9]] );
1057     a.resize(2,2);
1058     assertEq( a, [[1,2],[4,5]] );
1059     auto b = matD(2,2);
1060     b.fill(1);
1061     assertEq( b, [[1,1],[1,1]] );
1062     b = a;
1063     assertEq( a, b );
1064     auto c = matD( Matrix!(2,4,float)(1,2,3,4,5,6,7,8) );
1065     assertEq( c, [[1,2,3,4],[5,6,7,8]] );
1066     assertEq( c.height, 2 );
1067     assertEq( c.width, 4 );
1068     assertEq( b.height, 2 );
1069     assertEq( b.width, 2 );
1070     b = c;
1071     assertEq( b.height, 2 );
1072     assertEq( b.width, 4 );
1073     assertEq( b, c );
1074     b[0][0] = 666;
1075     assertNotEq( b, c );
1076 }
1077 
1078 unittest
1079 {
1080     auto a = mat3xD( 1,2,3,4,5,6,7,8,9 );
1081     matD b;
1082     assertEq( b.height, 0 );
1083     assertEq( b.width, 0 );
1084     b = a;
1085     assertEq( b.height, 3 );
1086     assertEq( b.width, 3 );
1087     assertEq( a, b );
1088     a[0][0] = 22;
1089     assertNotEq( a, b );
1090     a = b;
1091     assertEq( a, b );
1092     b.height = 4;
1093     assertThrown( a = b );
1094     { b = a; } // no except
1095 }
1096 
1097 ///
1098 unittest
1099 {
1100     auto a = mat3( 1,2,3,4,5,6,7,8,9 );
1101     auto b = matD( 3,3, 1,2,3,4,5,6,7,8,9 );
1102     auto c = mat3xD( 1,2,3,4,5,6,7,8,9 );
1103     auto d = matDx3( 1,2,3,4,5,6,7,8,9 );
1104 
1105     auto eha = a.expandHeight( 8,8,8 );
1106     auto ehb = b.expandHeight( 8,8,8 );
1107     auto ehc = c.expandHeight( 8,8,8 );
1108     auto ehd = d.expandHeight( 8,8,8 );
1109 
1110     assertEq( eha, [[1,2,3],[4,5,6],[7,8,9],[8,8,8]] );
1111     assertEq( eha.height, 4 );
1112     assertEq( ehb.height, 4 );
1113     assertEq( ehc.height, 4 );
1114     assertEq( ehd.height, 4 );
1115     assertEq( eha, ehb );
1116     assertEq( eha, ehc );
1117     assertEq( eha, ehd );
1118 
1119     static assert( is(typeof(eha) == Matrix!(0,3,float)) );
1120     static assert( is(typeof(ehd) == Matrix!(0,3,float)) );
1121 
1122     static assert( is(typeof(ehb) == Matrix!(0,0,float)) );
1123     static assert( is(typeof(ehc) == Matrix!(0,0,float)) );
1124 
1125     auto ewa = a.expandWidth( 8,8,8 );
1126     auto ewb = b.expandWidth( 8,8,8 );
1127     auto ewc = c.expandWidth( 8,8,8 );
1128     auto ewd = d.expandWidth( 8,8,8 );
1129 
1130     assertEq( ewa, [[1,2,3,8],[4,5,6,8],[7,8,9,8]] );
1131     assertEq( ewa.width, 4 );
1132     assertEq( ewb.width, 4 );
1133     assertEq( ewc.width, 4 );
1134     assertEq( ewd.width, 4 );
1135     assertEq( ewa, ewb );
1136     assertEq( ewa, ewc );
1137     assertEq( ewa, ewd );
1138 
1139     static assert( is(typeof(ewa) == Matrix!(3,0,float)) );
1140     static assert( is(typeof(ewc) == Matrix!(3,0,float)) );
1141 
1142     static assert( is(typeof(ewb) == Matrix!(0,0,float)) );
1143     static assert( is(typeof(ewd) == Matrix!(0,0,float)) );
1144 
1145     auto aa = a.expandHeight(a);
1146     assertEq( aa, [[1,2,3],[4,5,6],[7,8,9],[1,2,3],[4,5,6],[7,8,9]] );
1147     assertEq( aa.height, 6 );
1148     static assert( is(typeof(aa) == Matrix!(0,3,float)) );
1149 }
1150 
1151 unittest
1152 {
1153     auto a = mat3( 1,2,3,4,5,6,7,8,9 );
1154     assertEq( a.asArray, [1.0f,2,3,4,5,6,7,8,9] );
1155 }
1156 
1157 ///
1158 unittest
1159 {
1160     auto a = matD(4,4,0).fillDiag(1);
1161     assertEq( a, mat4() );
1162     assertEq( a.inv, a );
1163 }
1164 
1165 ///
1166 unittest
1167 {
1168     auto a = mat4x2( 1,2,3,4,5,6,7,8 );
1169     auto b = mat4( 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16 );
1170     auto c = a.T * b * a;
1171     static assert( c.height == 2 && c.width == 2 );
1172 }
1173 
1174 unittest
1175 {
1176     matD a;
1177     matD b;
1178     a.resize( 10, 4 );
1179     b.resize( 10, 10 );
1180     auto c = a.T * b * a;
1181     assertEq( c.height, 4 );
1182     assertEq( c.width, 4 );
1183 }
1184 
1185 ///
1186 unittest
1187 {
1188     auto a = mat3.diag(1);
1189     assertEq( a, [[1,0,0],[0,1,0],[0,0,1]] );
1190     auto b = mat3xD.diag(1,2);
1191     assertEq( b, [[1,0,0],[0,2,0],[0,0,1]] );
1192     auto c = mat3xD.diag(1,2,3);
1193     assertEq( c, [[1,0,0],[0,2,0],[0,0,3]] );
1194     static assert( !__traits(compiles,matD.diag(1)) );
1195     auto d = matD(3,3).fill(0).fillDiag(1);
1196     assertEq( d, [[1,0,0],[0,1,0],[0,0,1]] );
1197 }
1198 
1199 ///
1200 unittest
1201 {
1202     auto a = mat3( 1,2,3,4,5,6,7,8,9 );
1203     auto sha = a.sliceHeight(1);
1204     assertEq( sha, [[4,5,6],[7,8,9]] );
1205     auto swsha = sha.sliceWidth(0,1);
1206     assertEq( swsha, [[4],[7]] );
1207 }
1208 
1209 ///
1210 unittest
1211 {
1212     auto a = mat3.diag(1);
1213     assertEq( -a, [[-1,0,0],[0,-1,0],[0,0,-1]] );
1214 }
1215 
1216 unittest
1217 {
1218     auto a = mat3.diag(1);
1219     auto b = a*3-a;
1220     assertEq( b, a*2 );
1221     b /= 2;
1222     assertEq( b,a );
1223 }
1224 
1225 ///
1226 unittest
1227 {
1228     auto a = rmat3( 3,2,2, 1,3,1, 5,3,4 );
1229     auto ainv = rmat3xD( 9,-2,-4,  1,2,-1, -12,1,7 ) / 5;
1230 
1231     auto b = a * ainv;
1232     static assert( is( typeof(b) == Matrix!(3,0,real) ) );
1233     assertEq( b, mat3.diag(1) );
1234 
1235     static assert( !__traits(compiles,a *= ainv ) );
1236     a *= rmat3(ainv);
1237     assertEq( a, mat3.diag(1) );
1238 }
1239 
1240 ///
1241 unittest
1242 {
1243     alias Matrix!(4,5,float) mat4x5;
1244 
1245     auto a = mat2x4.init * mat4xD.init;
1246     assertThrown( mat4xD.init * mat2x4.init );
1247     auto b = mat2x4.init * mat4x5.init;
1248 
1249     static assert( is( typeof(a) == Matrix!(2,0,float) ) );
1250     static assert( is( typeof(b) == Matrix!(2,5,float) ) );
1251     static assert( is( typeof(mat4xD.init * mat2x4.init) == Matrix!(4,4,float) ) );
1252 }
1253 
1254 unittest
1255 {
1256 
1257     auto a = mat2x3( 1,2,3, 4,5,6 );
1258     auto b = mat4xD( 1,2,3,4 );
1259 
1260     auto aT = a.T;
1261     auto bT = b.T;
1262 
1263     static assert( is( typeof(aT) == Matrix!(3,2,float) ) );
1264     static assert( is( typeof(bT) == Matrix!(0,4,float) ) );
1265 
1266     assertEq( bT.height, b.width );
1267 }
1268 
1269 unittest
1270 {
1271     auto a = mat3.diag(1);
1272 
1273     a.setRow(2,4,5,6);
1274     assertEq( a, [[1,0,0],[0,1,0],[4,5,6]] );
1275     a.setCol(1,8,4,2);
1276     assertEq( a, [[1,8,0],[0,4,0],[4,2,6]] );
1277 
1278     assertEq( a.row(0), [[1,8,0]] );
1279     assertEq( a.col(0), [[1],[0],[4]] );
1280 }
1281 
1282 ///
1283 unittest
1284 {
1285     auto b = mat3.diag(2) * vec3(2,3,4);
1286     assert( is( typeof(b) == vec3 ) );
1287     assertEq( b, [4,6,8] );
1288 
1289     auto c = vec3(1,1,1) * mat3.diag(1,2,3);
1290     assert( is( typeof(c) == vec3 ) );
1291     assertEq( c, [1,2,3] );
1292 }
1293 
1294 ///
1295 unittest
1296 {
1297     auto mtr = matD(2,3).fill( 1,2,3, 
1298                                3,4,7 );
1299 
1300     auto a = mtr * vec3(1,1,1);
1301 
1302     assert( is( typeof(a) == Vector!(0,float) ) );
1303     assertEq( a.length, 2 );
1304     assertEq( a, [ 6, 14 ] );
1305 
1306     auto b = vec3(1,1,1) * mtr.T;
1307     assertEq( a, b );
1308 }
1309 
1310 ///
1311 unittest
1312 {
1313     void check(E)( E mtr ) if( isMatrix!E )
1314     {
1315         mtr.fill( 1,2,3,4,
1316                   5,6,7,8,
1317                   9,10,11,12,
1318                   13,14,15,16 );
1319         auto sm = mtr.subWithout( [0,3,3,3], [1,2] );
1320         assert( is( typeof(sm) == matD ) );
1321         assertEq( sm.width, 2 );
1322         assertEq( sm.height, 2 );
1323         assertEq( sm, [ [5,8], [9,12] ] );
1324         assertThrown( mtr.sub( [0,4], [1,2] ) );
1325         auto sm2 = mtr.subWithout( [], [1,2] );
1326         assertEq( sm2.width, 2 );
1327         assertEq( sm2.height, 4 );
1328         assertEq( sm2, [ [1,4],[5,8],[9,12],[13,16] ] );
1329     }
1330 
1331     check( matD(4,4) );
1332     check( mat4() );
1333 }
1334 
1335 unittest
1336 {
1337     assertEq( matD(4,4,0).fillDiag(1,2,3,4).det, 24 );
1338 }
1339 
1340 unittest
1341 {
1342     auto mtr = rmatD(4,4).fill( 1,2,5,2,
1343                                 5,6,1,4,
1344                                 9,1,3,0,
1345                                 9,2,4,2 );
1346     auto xx = mtr * mtr.inv;
1347     assertEq( xx, matD(4,4,0).fillDiag(1) );
1348 }
1349 
1350 unittest
1351 {
1352     auto mtr = matD(4,4).fill( 0,1,0,2,
1353                                1,0,0,4,
1354                                0,0,1,1,
1355                                0,0,0,1 );
1356     auto vv = vec4(4,2,1,1);
1357     auto rv = mtr.speedTransformInv * (mtr*vv);
1358     assertEq( rv, vv );
1359 }
1360 
1361 unittest
1362 {
1363     auto mtr = matD(4,4).fillDiag(1);
1364     auto vv = vec4(4,2,1,1);
1365     auto rv = vv * mtr;
1366     auto vr = mtr.T * vv * mtr;
1367 }
1368 
1369 unittest
1370 {
1371     auto mtr = rmat4.diag(1);
1372     auto vv = vec4(4,2,1,1);
1373     auto rv = vv * mtr;
1374     auto vr = mtr.T * vv * mtr;
1375 
1376     auto xx = mtr * mtr.inv;
1377     assertEq( xx, mat4.diag(1) );
1378 }
1379 
1380 unittest
1381 {
1382     auto mtr = rmat4( 1,2,5,2,
1383                       5,6,1,4,
1384                       9,1,3,0,
1385                       9,2,4,2 );
1386 
1387     auto xx = mtr * mtr.rowReduceInv;
1388     assertEq( xx, mat4() );
1389 }
1390 
1391 ///
1392 unittest
1393 {
1394     auto mtr = mat4().setRect(0,0,mat3.diag(1)).setCol(3,1,2,3,4);
1395     assertEq( mtr, [[1,0,0,1],
1396                     [0,1,0,2],
1397                     [0,0,1,3],
1398                     [0,0,0,4]] );
1399 }
1400 
1401 unittest
1402 {
1403     auto stm = mat4();
1404     assert( stm );
1405     auto dnm = matD();
1406     assert( dnm );
1407 }
1408 
1409 unittest
1410 {
1411     auto t = mat4.diag(1,2,3,4);
1412     assertEq( t.offset, vec3(0) );
1413     t.setCol( 3, vec4(3,2,1,1) );
1414     assertEq( t.offset, vec3(3,2,1) );
1415     t.offset = vec3(1,2,3);
1416     assertEq( t.offset, vec3(1,2,3) );
1417 }
1418 
1419 ///
1420 auto quatToMatrix(E)( in Quaterni!E iq )
1421 {
1422     auto q = iq / iq.len2;
1423 
1424     E wx, wy, wz, xx, yy, yz, xy, xz, zz, x2, y2, z2;
1425 
1426     x2 = q.i + q.i;
1427     y2 = q.j + q.j;
1428     z2 = q.k + q.k;
1429     xx = q.i * x2;   xy = q.i * y2;   xz = q.i * z2;
1430     yy = q.j * y2;   yz = q.j * z2;   zz = q.k * z2;
1431     wx = q.a * x2;   wy = q.a * y2;   wz = q.a * z2;
1432 
1433     return Matrix!(3,3,E)( 1.0-(yy+zz),  xy-wz,        xz+wy,
1434                            xy+wz,        1.0-(xx+zz),  yz-wx,
1435                            xz-wy,        yz+wx,        1.0-(xx+yy) );
1436 }
1437 
1438 ///
1439 unittest
1440 {
1441     auto q = rquat.fromAngle( PI_2, vec3(0,0,1) );
1442 
1443     auto m = quatToMatrix(q);
1444     auto r = mat3(0,-1, 0, 1, 0, 0, 0, 0, 1);
1445     assert( eq_approx( m, r, 1e-7 ) );
1446 }
1447 
1448 ///
1449 auto quatAndPosToMatrix(E,V)( in Quaterni!E iq, in V pos )
1450     if( isSpecVector!(3,E,V) )
1451 {
1452     auto q = iq / iq.len2;
1453 
1454     E wx, wy, wz, xx, yy, yz, xy, xz, zz, x2, y2, z2;
1455 
1456     x2 = q.i + q.i;
1457     y2 = q.j + q.j;
1458     z2 = q.k + q.k;
1459     xx = q.i * x2;   xy = q.i * y2;   xz = q.i * z2;
1460     yy = q.j * y2;   yz = q.j * z2;   zz = q.k * z2;
1461     wx = q.a * x2;   wy = q.a * y2;   wz = q.a * z2;
1462 
1463     return Matrix!(4,4,E)( 1.0-(yy+zz),  xy-wz,        xz+wy,       pos.x,
1464                            xy+wz,        1.0-(xx+zz),  yz-wx,       pos.y,
1465                            xz-wy,        yz+wx,        1.0-(xx+yy), pos.z,
1466                            0,            0,            0,           1     );
1467 }
1468 
1469 ///
1470 unittest
1471 {
1472     auto q = rquat.fromAngle( PI_2, vec3(0,0,1) );
1473     auto m = quatAndPosToMatrix(q, vec3(1,2,3) );
1474     assert( eq_approx( m, [[0,-1,0,1],
1475                            [1, 0,0,2],
1476                            [0, 0,1,3],
1477                            [0, 0,0,1]], 1e-7 ) );
1478 
1479 }