1 module gfmod.opengl.matrixstack;
2 
3 import gl3n.linalg;
4 
5 
6 /** A matrix stack designed to replace fixed-pipeline matrix stacks.
7  *
8  * This stack always exposes both the top element and its inverse.
9  */
10 class MatrixStack(F, size_t depth = 32)
11     if(depth > 0 && (is(F == float) || is(F == double)))
12 {
13 private:
14     size_t _top; // index of top matrix
15     M[depth] _matrices;
16     M[depth] _invMatrices;
17 
18 public:
19     /// The matrix type this stack works with.
20     alias M = Matrix!(F, 4, 4);
21 
22     /// Creates a matrix stack.
23     /// The stack is initialized with one element, an identity matrix.
24     this() @safe pure nothrow @nogc
25     {
26         _top = 0;
27         loadIdentity();
28     }
29 
30     /// Replacement for $(D glLoadIdentity).
31     void loadIdentity() @safe pure nothrow @nogc
32     {
33         _matrices[_top]    = M.identity();
34         _invMatrices[_top] = M.identity();
35     }
36 
37     /// Replacement for $(D glPushMatrix).
38     void push() @safe pure nothrow @nogc
39     {
40         if(_top + 1 >= depth) { assert(false, "Matrix stack is full"); }
41 
42         _matrices[_top + 1] = _matrices[_top];
43         _invMatrices[_top + 1] = _invMatrices[_top];
44         ++_top;
45     }
46 
47     /// Replacement for $(D glPopMatrix).
48     void pop() @safe pure nothrow @nogc
49     {
50         if (_top <= 0) { assert(false, "Matrix stack is empty"); }
51 
52         --_top;
53     }
54 
55     /// Returns: Top matrix.
56     /// Replaces $(D glLoadMatrix).
57     M top() @safe pure const nothrow @nogc { return _matrices[_top]; }
58 
59     /// Returns: Inverse of top matrix.
60     M invTop() @safe pure const nothrow @nogc { return _invMatrices[_top]; }
61 
62     /// Sets top matrix.
63     /// Replaces $(D glLoadMatrix).
64     void setTop(M m) @safe pure nothrow @nogc
65     {
66         _matrices[_top] = m;
67         _invMatrices[_top] = m.inverse();
68     }
69 
70     /// Replacement for $(D glMultMatrix).
71     void mult(M m) @safe pure nothrow @nogc { mult(m, m.inverse()); }
72 
73     /// Replacement for $(D glMultMatrix), with provided inverse.
74     void mult(M m, M invM) @safe pure nothrow @nogc
75     {
76         _matrices[_top]    = _matrices[_top] * m;
77         _invMatrices[_top] = invM *_invMatrices[_top];
78     }
79 
80     /// Replacement for $(D glTranslate).
81     void translate(Vector!(F, 3) v) @safe pure nothrow @nogc { translate(v.x, v.y, v.z); }
82 
83     /// Ditto.
84     void translate(F x, F y, F z) @safe pure nothrow @nogc
85     {
86         mult(M.translation(x, y, z), M.translation(-x, -y, -z));
87     }
88 
89     /// Replacement for $(D glScale).
90     void scale(Vector!(F, 3) v) @safe pure nothrow @nogc { scale(v.x, v.y, v.z); }
91 
92     /// Replacement for $(D glScale).
93     void scale(F x, F y, F z) @safe pure nothrow @nogc
94     {
95         mult(M.scaling(x, y, z), M.scaling(1 / x, 1 / y, 1 / z));
96     }
97 
98 
99     /// Replacement for $(D glRotate).
100     /// Warning: Angle is given in radians, unlike the original API.
101     void rotate(F angle, Vector!(F, 3) axis) @safe pure nothrow @nogc
102     {
103         M rot = M.rotation(angle, axis);
104         mult(rot, rot.transposed()); // inversing a rotation matrix is tranposing
105     }
106 
107     /// Replacement for $(D gluPerspective).
108     void perspective(F left, F right, F bottom, F top, F near, F far)
109         @safe pure nothrow @nogc
110     {
111         mult(M.perspective(left, right, bottom, top, near, far));
112     }
113 
114     /// Replacement for $(D glOrtho).
115     void ortho(F left, F right, F bottom, F top, F near, F far)
116         @safe pure nothrow @nogc
117     {
118         mult(M.orthographic(left, right, bottom, top, near, far));
119     }
120 }
121 
122 unittest
123 {
124     auto s = new MatrixStack!double();
125 
126     s.loadIdentity();
127     s.push();
128     s.pop();
129 
130     s.translate(vec3d(4,5,6));
131     s.scale(vec3d(0.5));
132 }