1 module gfm.opengl.matrixstack;
2 
3 import core.stdc.stdlib: malloc, free;
4 
5 import gfm.opengl.opengl;
6 
7 import gfm.math.vector,
8        gfm.math.matrix;
9 
10 /// A matrix stack designed to replace fixed-pipeline matrix stacks.
11 /// This stack always expose both the top element and its inverse.
12 final class MatrixStack(int R, T) if (R == 3 || R == 4)
13 {
14     public
15     {
16         alias Matrix!(T, R, R) matrix_t; /// Type of matrices in the stack. Can be 3x3 or 4x4.
17 
18         /// Creates a matrix stack.
19         /// The stack is initialized with one element, an identity matrix.
20         this(size_t depth = 32) nothrow
21         {
22             assert(depth > 0);
23             size_t memNeeded = matrix_t.sizeof * depth * 2;
24             void* data = malloc(memNeeded * 2);
25             _matrices = cast(matrix_t*)data;
26             _invMatrices = cast(matrix_t*)(data + memNeeded);
27             _top = 0;
28             _depth = depth;
29             loadIdentity();
30         }
31 
32         /// Releases the matrix stack memory.
33         ~this()
34         {
35             if (_matrices !is null)
36             {
37                 ensureNotInGC("MatrixStack");
38                 free(_matrices);
39                 _matrices = null;
40             }
41         }
42 
43         /// Replacement for $(D glLoadIdentity).
44         void loadIdentity() pure nothrow
45         {
46             _matrices[_top] = matrix_t.identity();
47             _invMatrices[_top] = matrix_t.identity();
48         }
49 
50         /// Replacement for $(D glPushMatrix).
51         void push() pure nothrow
52         {
53             if(_top + 1 >= _depth)
54                 assert(false, "Matrix stack is full");
55 
56             _matrices[_top + 1] = _matrices[_top];
57             _invMatrices[_top + 1] = _invMatrices[_top];
58             ++_top;
59         }
60 
61         /// Replacement for $(D glPopMatrix).
62         void pop() pure nothrow
63         {
64             if (_top <= 0)
65                 assert(false, "Matrix stack is empty");
66 
67             --_top;
68         }
69 
70         /// Returns: Top matrix.
71         /// Replaces $(D glLoadMatrix).
72         matrix_t top() pure const nothrow
73         {
74             return _matrices[_top];
75         }
76 
77         /// Returns: Inverse of top matrix.
78         matrix_t invTop() pure const nothrow
79         {
80             return _invMatrices[_top];
81         }
82 
83         /// Sets top matrix.
84         /// Replaces $(D glLoadMatrix).
85         void setTop(matrix_t m) pure nothrow
86         {
87             _matrices[_top] = m;
88             _invMatrices[_top] = m.inverse();
89         }
90 
91         /// Replacement for $(D glMultMatrix).
92         void mult(matrix_t m) pure nothrow
93         {
94             mult(m, m.inverse());
95         }
96 
97         /// Replacement for $(D glMultMatrix), with provided inverse.
98         void mult(matrix_t m, matrix_t invM) pure nothrow
99         {
100             _matrices[_top] = _matrices[_top] * m;
101             _invMatrices[_top] = invM *_invMatrices[_top];
102         }
103 
104         /// Replacement for $(D glTranslate).
105         void translate(Vector!(T, R-1) v) pure nothrow
106         {
107             mult(matrix_t.translation(v), matrix_t.translation(-v));
108         }
109 
110         /// Replacement for $(D glScale).
111         void scale(Vector!(T, R-1) v) pure nothrow
112         {
113             mult(matrix_t.scaling(v), matrix_t.scaling(1 / v));
114         }
115 
116         static if (R == 4)
117         {
118             /// Replacement for $(D glRotate).
119             /// Warning: Angle is given in radians, unlike the original API.
120             void rotate(T angle, Vector!(T, 3) axis) pure nothrow
121             {
122                 matrix_t rot = matrix_t.rotation(angle, axis);
123                 mult(rot, rot.transposed()); // inversing a rotation matrix is tranposing
124             }
125 
126             /// Replacement for $(D gluPerspective).
127             /// Warning: FOV is given in radians, unlike the original API.
128             void perspective(T FOVInRadians, T aspect, T zNear, T zFar) pure nothrow
129             {
130                 mult(matrix_t.perspective(FOVInRadians, aspect, zNear, zFar));
131             }
132 
133             /// Replacement for $(D glOrtho).
134             void ortho(T left, T right, T bottom, T top, T near, T far) pure nothrow
135             {
136                 mult(matrix_t.orthographic(left, right, bottom, top, near, far));
137             }
138 
139             /// Replacement for $(D gluLookAt).
140             void lookAt(vec3!T eye, vec3!T target, vec3!T up) pure nothrow
141             {
142                 mult(matrix_t.lookAt(eye, target, up));
143             }
144         }
145     }
146 
147     private
148     {
149         size_t _top; // index of top matrix
150         size_t _depth;
151         matrix_t* _matrices;
152         matrix_t* _invMatrices;
153     }
154 }
155 
156 unittest
157 {
158     auto s = new MatrixStack!(4, double)();
159     scope(exit) destroy(s);
160     s.loadIdentity();
161     s.push();
162     s.pop();
163     s.translate(vec3d(4, 5, 6));
164     s.scale(vec3d(0.5));
165 
166 
167     auto t = new MatrixStack!(3, float)();
168     scope(exit) destroy(t);
169     t.loadIdentity();
170     t.push();
171     t.pop();
172     t.translate(vec2f(-4, 5));
173     t.scale(vec2f(0.5));
174 }