1 module gl3n.ext.matrixstack;
2 
3 private {
4     import gl3n.util : is_matrix;
5 }
6 
7 
8 /// A matrix stack similiar to OpenGLs glPushMatrix/glPopMatrix
9 struct MatrixStack(T) if(is_matrix!T) {
10     alias T Matrix; /// Holds the internal matrix type
11 
12     Matrix top = Matrix.identity; /// The top matrix, the one you work with
13     private Matrix[] stack;
14     private size_t _top_pos = 0;
15 
16     alias top this;
17 
18     /// If the stack is too small to hold more items,
19     /// space for $(B realloc_interval) more elements will be allocated
20     size_t realloc_interval = 8;
21 
22     deprecated("Use matrixStack() instead.")
23     @disable this();
24 
25     /// Sets the stacks initial size to $(B depth) elements
26     deprecated("Use matrixStack() instead.")
27     this(size_t depth) pure nothrow {
28         stack = new Matrix[](depth);
29     }
30 
31     /// Sets the top matrix
32     void set(Matrix matrix) pure nothrow {
33         top = matrix;
34     }
35 
36     /// Pushes the top matrix on the stack and keeps a copy as the new top matrix
37     void push() pure nothrow {
38         if(stack.length <= _top_pos) {
39             stack.length += realloc_interval;
40         }
41 
42         stack[_top_pos++] = top;
43     }
44 
45     /// Pushes the top matrix on the stack and sets $(B matrix) as the new top matrix.
46     void push(Matrix matrix) pure nothrow {
47         push();
48         top = matrix;
49     }
50 
51     /// Pops a matrix from the stack and sets it as top matrix.
52     /// Also returns a reference to the new top matrix.
53     ref Matrix pop() pure nothrow
54         in { assert(_top_pos >= 1, "popped too often from matrix stack"); }
55         do {
56             top = stack[--_top_pos];
57             return top;
58         }
59 }
60 
61 /// Constructs a new stack with an initial size of $(B depth) elements
62 MatrixStack!T matrixStack(T)(size_t depth = 16) pure nothrow {
63     typeof(return) res = MatrixStack!T.init;
64     res.stack.length = depth;
65     return res;
66 }
67 
68 unittest {
69     import gl3n.linalg : mat4;
70 
71     static assert(!__traits(compiles, {auto m = MatrixStack!mat4();}));
72     static assert(!__traits(compiles, {MatrixStack!mat4 m;}));
73     auto m1 = matrixStack!mat4();
74     assert(m1.stack.length == 16);
75     auto m2 = matrixStack!mat4(20);
76     assert(m2.stack.length == 20);
77 
78     assert(m1.top == mat4.identity);
79     assert(m1._top_pos == 0);
80 }
81 
82 unittest {
83     import gl3n.linalg : mat4;
84 
85     auto ms = matrixStack!mat4();
86     // just a few tests to make sure it forwards correctly to Matrix
87     static assert(__traits(hasMember, ms, "make_identity"));
88     static assert(__traits(hasMember, ms, "transpose"));
89     static assert(__traits(hasMember, ms, "invert"));
90     static assert(__traits(hasMember, ms, "scale"));
91     static assert(__traits(hasMember, ms, "rotate"));
92     
93     assert(ms.top == mat4.identity);
94     assert(ms == ms.top); // make sure there is an proper alias this
95     ms.push();
96 
97     auto m1 = mat4(1, 0, 0, 0,
98                    0, 0, 0, 0,
99                    0, 0, 1, 0,
100                    0, 0, 0, 1);
101 
102     ms.set(m1);
103     assert(ms.top == m1);
104     assert(ms == ms.top);
105     ms.push();
106     
107     assert(ms.top == m1);
108     ms.top = ms.translate(0, 3, 2);
109     ms.push(mat4.identity);
110     
111     assert(ms.top == mat4.identity);
112     ms.push();
113 
114     ms.pop();
115     assert(ms.top == mat4.identity);
116 
117     ms.pop();
118     assert(ms.top == mat4(m1).translate(0, 3, 2));
119 
120     ms.pop();
121     assert(ms.top == m1);
122 
123     ms.pop();
124     assert(ms.top == mat4.identity);
125 }