<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>
<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"/>
<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">
<svg id="frame-buffer" preserveAspectRatio="xMidYMid meet" viewBox="-1 -1 2 2" width="100%" height="100%" />
</g>
</svg>