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