<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" 
width="100%" height="100%" 
onload="startup(evt)">
<script>
<![CDATA[

// OpenGl stack in javascript using svg in less than 500 lines
// There is no z-buffer, so some occlusions might not get resolved correctly.
// Author: Ian Blanes
// License: cc by (Creative Commons Attribution)

xmlns = "http://www.w3.org/2000/svg";

// constants

var gl_constants = {
	GL_INVALID : 0,
	GL_POINTS : 1,
        GL_LINES : 2,
        GL_LINE_STRIP : 3,
        GL_LINE_LOOP : 4,

	GL_MODELVIEW : 5,
	GL_PROJECTION : 6,
	GL_TEXTURE : 7,
};

// internal functions and variables

var gl_state = {	
	gl_begin_mode : gl_constants.GL_INVALID,
	vertices : null,

	display_function : null,

	frame_buffer : null,

	stopped : true,

	svgDocument : null,

	currentMatrix : gl_constants.GL_MODELVIEW,

	modelviewMatrixStack : new Array([1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]),
	projectionMatrixStack : new Array([1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]),

	geometricElements: new Array(),

	cachedCurrentMatrix : null,
};

function glutInit(evt) {
	gl_state.svgDocument=evt.target.ownerDocument;
	gl_state.frame_buffer = gl_state.svgDocument.getElementById("frame-buffer");

	evt.target.setAttribute("onclick","gl_state.stopped = !gl_state.stopped; glutMainLoop();");

	// Initial state
	//gl_state.stopped = false;
	glutMainLoop();
}

function glutMainLoop() {
	if (gl_state.display_function != null) gl_state.display_function();
	if (gl_state.stopped) return;
	window.setTimeout("glutMainLoop()",45);
}

function getCurrentMatrixStack() {
	if(gl_state.currentMatrix == gl_constants.GL_MODELVIEW) {
		return gl_state.modelviewMatrixStack;
	} else if (gl_state.currentMatrix == gl_constants.GL_PROJECTION) {
		return gl_state.projectionMatrixStack;
	}
}

function matrixMultiplication(m1,m2) {
	// This seems to be the fastest way...
	var result = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
	
	result[0] = m1[0] * m2[0] + m1[4] * m2[1] + m1[8] * m2[2] + m1[12] * m2[3];
	result[1] = m1[1] * m2[0] + m1[5] * m2[1] + m1[9] * m2[2] + m1[13] * m2[3];
	result[2] = m1[2] * m2[0] + m1[6] * m2[1] + m1[10] * m2[2] + m1[14] * m2[3];
	result[3] = m1[3] * m2[0] + m1[7] * m2[1] + m1[11] * m2[2] + m1[15] * m2[3];

	result[4] = m1[0] * m2[4] + m1[4] * m2[5] + m1[8] * m2[6] + m1[12] * m2[7];
	result[5] = m1[1] * m2[4] + m1[5] * m2[5] + m1[9] * m2[6] + m1[13] * m2[7];
	result[6] = m1[2] * m2[4] + m1[6] * m2[5] + m1[10] * m2[6] + m1[14] * m2[7];
	result[7] = m1[3] * m2[4] + m1[7] * m2[5] + m1[11] * m2[6] + m1[15] * m2[7];

	result[8] = m1[0] * m2[8] + m1[4] * m2[9] + m1[8] * m2[10] + m1[12] * m2[11];
	result[9] = m1[1] * m2[8] + m1[5] * m2[9] + m1[9] * m2[10] + m1[13] * m2[11];
	result[10] = m1[2] * m2[8] + m1[6] * m2[9] + m1[10] * m2[10] + m1[14] * m2[11];
	result[11] = m1[3] * m2[8] + m1[7] * m2[9] + m1[11] * m2[10] + m1[15] * m2[11];

	result[12] = m1[0] * m2[12] + m1[4] * m2[13] + m1[8] * m2[14] + m1[12] * m2[15];
	result[13] = m1[1] * m2[12] + m1[5] * m2[13] + m1[9] * m2[14] + m1[13] * m2[15];
	result[14] = m1[2] * m2[12] + m1[6] * m2[13] + m1[10] * m2[14] + m1[14] * m2[15];
	result[15] = m1[3] * m2[12] + m1[7] * m2[13] + m1[11] * m2[14] + m1[15] * m2[15];

	return result;
}

function matrixVectorMultiplication(m,v) {
	var result = [
		m[0]*v[0] + m[4]*v[1] + m[8]*v[2] + m[12]*v[3],
		m[1]*v[0] + m[5]*v[1] + m[9]*v[2] + m[13]*v[3],
		m[2]*v[0] + m[6]*v[1] + m[10]*v[2] + m[14]*v[3],
		m[3]*v[0] + m[7]*v[1] + m[11]*v[2] + m[15]*v[3],
	];

	return result;
}

function projectPoint(point) {
	var matrix = gl_state.cachedCurrentMatrix;

	if (matrix == null) {
		var stack = gl_state.modelviewMatrixStack;
		var m1 = stack[stack.length - 1];

		stack = gl_state.projectionMatrixStack;
		var m2 = stack[stack.length - 1];

		matrix = matrixMultiplication(m2, m1);
		gl_state.cachedCurrentMatrix = matrix;
	}
	
	var result = matrixVectorMultiplication(matrix, point);

	result[0] = result[0] / result[3];
	result[1] = result[1] / result[3];
	result[2] = result[2] / result[3];
	result[3] = 1;
	
	return result;
}

function normalVector(vector) {
	var norm = Math.sqrt(vector[0] * vector[0] + vector[1] * vector[1] + vector[2] * vector[2]);
	var normal = [vector[0] / norm, vector[1] / norm, vector[2] / norm];

	return normal;
}

function vectorCrossProduct(a, b) {
	var c = [ a[1]*b[2] - a[2]*b[1], a[2]*b[0] - a[0]*b[2], a[0]*b[1] - a[1]*b[0] ];
	return c;
}

function dotProduct(a, b) {
	var c = 0;

	for (i = 0; i < a.length; i++) {
		c += a[i] * b[i];
	}

	return c;
}

function depthSort (a, b){
	return b[1] - a[1];
}

// exported functions

function glBegin(mode) {
	gl_state.gl_begin_mode = mode;
	gl_state.vertices = new Array();
}


function glEnd(mode) {
	// Flush the polygon

	var v = gl_state.vertices;
	var npoints = v.length;

	var center = [0, 0, 0, npoints];
	var points = new Array();

	for (i in v) {
		center[0] += v[i][0];
		center[1] += v[i][1]; 
		center[2] += v[i][2];

		var p = projectPoint(v[i]);

		points.push(p[0])
		points.push(-p[1]);
	}

	// Cleanup
	gl_state.vertices = null;

	// Back-face culling
	if ((points[2] - points[0]) * (points[3] - points[5]) - (points[1] - points[3]) * (points[4] - points[2]) < 0) {
		gl_state.gl_begin_mode = gl_constants.GL_INVALID;
		return;
	}

	// Fake depth buffer
	center = projectPoint(center);
	var depth = center[2];

	// No things poping out of the screen
	if (depth < 0) { // zNear/zFar?
		return;
	}

	// Draw center
//	points.push(center[0]); points.push(-center[1]);

	// Draw
	gl_state.geometricElements.push([points.join(" "), depth]);

	gl_state.gl_begin_mode = gl_constants.GL_INVALID;
}

// the bottom left is (-1, -1), the top right is (1, 1)
function glVertex(x, y, z) {
	gl_state.vertices.push([x, y, z, 1]);
}

function glClear() {
	var f = gl_state.frame_buffer;

	while (f.childNodes.length > 0) {
		f.removeChild(f.lastChild);
	}

	gl_state.geometricElements = new Array();
}

function glColor() {
}

function glutDisplayFunc(display) {
	gl_state.display_function = display;
}

function glutSwapBuffers() {
	var e = gl_state.geometricElements;

	e.sort(depthSort);

	// Delay the real append for better performance
	var np = gl_state.svgDocument.createElementNS(xmlns,"g");

	// TODO: Perhaps some node reuse might speed things up
	var np2 = gl_state.svgDocument.createElementNS(xmlns,"polygon");

	for (i in e) {
		var np3 = np2.cloneNode(false);
		np3.setAttribute("points", e[i][0]);
		np.appendChild(np3);
	}

	gl_state.frame_buffer.appendChild(np);
}

function glMatrixMode(mode) {
	gl_state.currentMatrix = mode;
}

function glLoadIdentity() {
	var id = [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1];
	var stack = getCurrentMatrixStack();

	stack[stack.length - 1] = id;

	gl_state.cachedCurrentMatrix = null;
}

function glPushMatrix() {
	var stack = getCurrentMatrixStack();
	stack.push(stack[stack.length - 1]);
}

function glPopMatrix() {
	var stack = getCurrentMatrixStack();
	stack.pop();

	gl_state.cachedCurrentMatrix = null;
}

// Matrices are first by columns and then by rows (e.g. m[col][row])
function glMultMatrix(matrix) {
	var stack = getCurrentMatrixStack();
	var top = stack[stack.length - 1];
	var result = matrixMultiplication(top, matrix);
	stack[stack.length - 1] = result;

	gl_state.cachedCurrentMatrix = null;
}

function glTranslate(x, y, z) {
	var matrix = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, x, y, z, 1];
	glMultMatrix(matrix);
}

function glRotate(angle, x, y, z) {
	var v = normalVector([x, y, z]);
	x = v[0]; y = v[1]; z = v[2];

	var c = Math.cos(Math.PI * angle / 180);
	var s = Math.sin(Math.PI * angle / 180);

	var matrix = [x*x*(1-c)+c, y*x*(1-c)+z*s, x*z*(1-c)-y*s, 0,
			x*y*(1-c)-z*s, y*y*(1-c)+c, y*z*(1-c)+x*s, 0,
			x*z*(1-c)+y*s, y*z*(1-c)-x*s, z*z*(1-c)+c, 0,
			0, 0, 0, 1];
	glMultMatrix(matrix);
}

function gluPerspective(fovy, aspect, zNear, zFar) {
	var f = 1 / Math.tan(Math.PI * fovy / 360);

	var matrix = [f / aspect, 0, 0, 0,
		0, f, 0, 0,
		0, 0, (zFar + zNear) / (zNear - zFar), -1,
		0, 0, (2 * zFar * zNear) / (zNear - zFar), 0];

	glMultMatrix(matrix);
}

function gluLookAt(eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ) {
	var F = [centerX - eyeX, centerY - eyeY, centerZ - eyeZ];
	var UP = [upX, upY, upZ];

	var f = normalVector(F);
	var up = normalVector(UP);
	var s = vectorCrossProduct(f, up);
	var u = vectorCrossProduct(s, f);

	var matrix = [s[0], u[0], -f[0], 0, s[1], u[1], -f[1], 0, s[2], u[2], -f[2], 0, 0, 0, 0, 1];
	glMultMatrix(matrix);
	glTranslate(-eyeX, -eyeY, -eyeZ);
}

// "userspace" code

var svgDocument=null;

function startup(evt) {
	svgDocument=evt.target.ownerDocument;
	glutInit(evt);

	setup();
	glutDisplayFunc(display);

	glutMainLoop();	
}

function setup() {
	glMatrixMode(gl_constants.GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(70.0, 1.0, 1.5, 20.0);
}

function drawBox() {
	glBegin(gl_constants.GL_LINE_LOOP); // front
	glVertex(-.5,-.5,-.5);glVertex(-.5,.5,-.5);glVertex(.5,.5,-.5);glVertex(.5,-.5,-.5);glEnd();

	glBegin(gl_constants.GL_LINE_LOOP); // back
	glVertex(-.5,-.5,.5);glVertex(.5,-.5,.5);glVertex(.5,.5,.5);glVertex(-.5,.5,.5);glEnd();

	glBegin(gl_constants.GL_LINE_LOOP); // right side
	glVertex(.5,-.5,.5);glVertex(.5,-.5,-.5);glVertex(.5,.5,-.5);glVertex(.5,.5,.5);glEnd();

	glBegin(gl_constants.GL_LINE_LOOP); // left side
	glVertex(-.5,-.5,-.5);glVertex(-.5,-.5,.5);glVertex(-.5,.5,.5);glVertex(-.5,.5,-.5);glEnd();

	glBegin(gl_constants.GL_LINE_LOOP); // top
	glVertex(-.5,-.5,.5);glVertex(-.5,-.5,-.5);glVertex(.5,-.5,-.5);glVertex(.5,-.5,.5);glEnd();

	glBegin(gl_constants.GL_LINE_LOOP); // bottom
	glVertex(-.5,.5,.5);glVertex(.5,.5,.5);glVertex(.5,.5,-.5);glVertex(-.5,.5,-.5);glEnd();
}

function drawGround() {
	glBegin(gl_constants.GL_LINE_LOOP); // top
	glVertex(-2,0,2);glVertex(-2,0,-2);glVertex(2,0,-2);glVertex(2,0,2);glEnd();

	glBegin(gl_constants.GL_LINE_LOOP); // bottom
	glVertex(-2,0,2);glVertex(2,0,2);glVertex(2,0,-2);glVertex(-2,0,-2);glEnd();
}

document.onmousemove = storeMousePosition;

var screenW = screen.width;
var screenH = screen.height;
var mouseX = screen.width / 2;
var mouseY = screen.height / 2;

function storeMousePosition(event) {  
	mouseX = event.screenX; mouseY = event.screenY;
	//mouseX = e.pageX; mouseY = e.pageY;
	return true;
}

var c = 0;
var d = 0;
var e = 0;

function display() {
	glClear();
	
	glMatrixMode(gl_constants.GL_MODELVIEW);
	glLoadIdentity();

/*
	var d = 60 * (c-180) / (2 * 360);
	if (c < 180) {
		d = 0;
	}
*/
	d = (mouseY * 180 / screen.height) - 90;
	e = (mouseX * 180 / screen.width) - 90;

	gluLookAt(0, 2, 5, 0, -1, 0, 0, 1, 0);

	glRotate(d, 1, 0, 0);
	glRotate(e, 0, 1, 0);

	glPushMatrix();
	glRotate(c, 0, 1, 0);
	glTranslate(1,0,1.3);
	glRotate(c, 0, 1, 1);
	drawBox()
	glPopMatrix();

	glPushMatrix();
	glRotate(c, 0, 1, 0);
	glTranslate(-1,0,1.3);
	glRotate(c, 0, 1, 1);
	drawBox();
	glPopMatrix();

	glPushMatrix();
	glTranslate(0,-2,0);
	drawGround();
	glPopMatrix();

	//if (c == 45) { gl_state.stopped = true; c = 0; }
	c = (c + 1) % 360;

	glutSwapBuffers();
}

//]]>
</script>
<!--setup background -->
<defs>
<radialGradient id="background" cx=".5" cy="1" fx=".5" fy=".82" r="1">
<stop style="stop-color:#f4ae20;stop-opacity:.4;" offset="0" id="stop3596" />
<stop style="stop-color:#ffffff;stop-opacity:0;" offset="1" id="stop3598" />
</radialGradient>
</defs>
<rect x="0" y="0" width="100%" height="100%" style="fill:#f4ae20;fill-opacity:1"/>
<rect x="0" y="0" width="100%" height="100%" style="fill:url(#background);fill-opacity:1"/>
<!-- drawing area -->
<text style="font-size:18px;font-style:normal;font-weight:normal;fill:#ffffff;fill-opacity:1;stroke:none;font-family:verdana">
<tspan x="290" y="290">click me!</tspan></text>
<g stroke-width="0.005" stroke-linecap="round" stroke-linejoin="round" fill="white" stroke="#f4ae20"> <!-- correct stroke-width with js -->
<svg id="frame-buffer" preserveAspectRatio="xMidYMid meet" viewBox="-1 -1 2 2" width="100%" height="100%" />
</g>
</svg>