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