+ return ortho(0, w, 0, h, -1, 1);
+}
+
+Matrix Matrix::ortho_topleft(float w, float h)
+{
+ return ortho(0, w, h, 0, -1, 1);
+}
+
+Matrix Matrix::frustum(float l, float r, float b, float t, float n, float f)
+{
+ if(l==r || b==t || n<=0 || f<=n)
+ throw invalid_argument("Matrix::frustum");
+
+ Matrix result;
+ result(0, 0) = 2*n/(r-l);
+ result(1, 1) = 2*n/(t-b);
+ result(0, 2) = (r+l)/(r-l);
+ result(1, 2) = (t+b)/(t-b);
+ result(2, 2) = -(f+n)/(f-n);
+ result(3, 2) = -1;
+ result(2, 3) = -2*f*n/(f-n);
+ result(3, 3) = 0;
+ return result;
+}
+
+Matrix Matrix::frustum_centered(float w, float h, float n, float f)
+{
+ return frustum(-w/2, w/2, -h/2, h/2, n, f);
+}
+
+Matrix Matrix::perspective(const Angle &h, float a, float n, float f)
+{
+ float hh = tan(h/2.0f)*n;
+ return frustum(-hh*a, hh*a, -hh, hh, n, f);
+}
+
+
+GLenum MatrixStack::current_mode = GL_MODELVIEW;
+
+MatrixStack::MatrixStack(GLenum m):
+ mode(m)
+{
+ matrices.reserve(mode==GL_MODELVIEW ? 32 : 4);
+ matrices.push_back(Matrix());
+}
+
+MatrixStack::MatrixStack():
+ mode(0)
+{
+ matrices.reserve(32);
+ matrices.push_back(Matrix());
+}
+
+const Matrix &MatrixStack::top() const
+{
+ return matrices.back();
+}
+
+void MatrixStack::load(const Matrix &m)
+{
+ matrices.back() = m;
+ update();
+}
+
+void MatrixStack::multiply(const Matrix &m)
+{
+ matrices.back() *= m;
+ update();
+}
+
+void MatrixStack::push()
+{
+ matrices.push_back(top());
+}
+
+void MatrixStack::pop()
+{
+ if(matrices.size()==1)
+ throw stack_underflow("MatrixStack::pop()");
+
+ matrices.pop_back();
+ update();
+}
+
+void MatrixStack::update()
+{
+ if(!mode)
+ return;
+
+ static Require _req(MSP_legacy_features);
+
+ if(mode!=current_mode)
+ {
+ glMatrixMode(mode);
+ current_mode = mode;
+ }
+
+ glLoadMatrixf(matrices.back().data());