//###############################
//
// Code by Horst Rudolph, Germany
//
//###############################

//###############################
// Basic GL-Functions
//###############################
	var deg = 180.0/3.14159265359;
	var rad = 1.0/deg;
	var gl;										// Web-GL-context
	var shaderProg;	    			// compiled and linked shader-program
	var vertexPositionAttr;		// Vertex-Position    = Attribute for the vertex-shader
	var vertexNormalAttr;			// Vertex-Normal      = Attribute for the vertex-shader
	var vertexColorAttr;			// Vertex-Color       = Attribute for the vertex-shader
	var textureCoordAttr;			// Vertex-Texturcoord = Attribute for the vertex-shader
	var CanvasWidth = 1;
	var CanvasHeight = 1;


function initGL(canvas) { // Gets the Web-GL-context and does some compatibility
	try { gl = canvas.getContext("experimental-webgl"); }	catch(e) { }
	if (!gl) { try { gl = canvas.getContext("webkit-3d"); } catch(e) { } }
	if (!gl) { try { gl = canvas.getContext("moz-webgl"); } catch(e) { } }
	if (!gl) { alert("Could not initialise WebGL, sorry :-("); }

	// This temporary code provides support for Google Chrome
	if (!gl.getProgramParameter) { gl.getProgramParameter = gl.getProgrami }
	if (!gl.getShaderParameter)  { gl.getShaderParameter = gl.getShaderi }
	// End of Chrome compatibility code

	gl.clearColor(Math.random()*0.1, Math.random()*0.1, Math.random()*0.1, 1.0);
	gl.clearDepth(1.0);
	gl.enable(gl.DEPTH_TEST);
	gl.depthFunc(gl.LEQUAL);
//	gl.enable(gl.CULL_FACE);
//	gl.cullFace(gl.FRONT);
}

function getShader(id) { // Creates (fragment- or vertex-) shader, compiles it, then returns its handle
	var shaderScript = document.getElementById(id);
	if (!shaderScript) return null;

	var str = "";
	var k = shaderScript.firstChild;
	while (k) {
		if (k.nodeType == 3) str += k.textContent;
		k = k.nextSibling;
	}

	var shader;
	if (shaderScript.type == "x-shader/x-fragment") { shader = gl.createShader(gl.FRAGMENT_SHADER);	}
	else if (shaderScript.type == "x-shader/x-vertex") { shader = gl.createShader(gl.VERTEX_SHADER); }
	else { return null; }

	gl.shaderSource(shader, str);
	gl.compileShader(shader);
	if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
		alert(gl.getShaderInfoLog(shader));
		return null;
	}
	return shader;
}

function initShaders() { // links shader-program, uses it and assigns attribute-locations to the vertex-attributes
	var fragmentShader = getShader("shader-fs");
	var vertexShader = getShader("shader-vs");

	shaderProg = gl.createProgram();
	gl.attachShader(shaderProg, vertexShader);
	gl.attachShader(shaderProg, fragmentShader);
	gl.linkProgram(shaderProg);

	if (!gl.getProgramParameter(shaderProg, gl.LINK_STATUS)) { alert("Could not initialise shaders"); }

	gl.useProgram(shaderProg);
	vertexPositionAttr = gl.getAttribLocation(shaderProg, "VSAttrVertexPosition");
	vertexNormalAttr =   gl.getAttribLocation(shaderProg, "VSAttrVertexNormal");
	textureCoordAttr =   gl.getAttribLocation(shaderProg, "VSAttrTextureCoord");
	vertexColorAttr =    gl.getAttribLocation(shaderProg, "VSAttrVertexColor");
}


//###############################
// Matrix Functions
//###############################

var ProjectionMatrix;
var ModelViewMatrix;
var MatrixStack = [];

function setMatrixUniforms() {
	var mvUniform = gl.getUniformLocation(shaderProg, "uMVMatrix");
	gl.uniformMatrix4fv(mvUniform, false, new WebGLFloatArray(ModelViewMatrix.flatten()));

	var normalMatrix = ModelViewMatrix.inverse();
	normalMatrix = normalMatrix.transpose();
	var nUniform = gl.getUniformLocation(shaderProg, "uNMatrix");
	gl.uniformMatrix4fv(nUniform, false, new WebGLFloatArray(normalMatrix.flatten()));
}

function pTranslate(v) {
	var m = Matrix.Translation($V([v[0], v[1], v[2]])).ensure4x4();
	ProjectionMatrix = ProjectionMatrix.x(m);
}

function pScale(v) {
	var m = Matrix.Scale($V([v[0], v[1], v[2]])).ensure4x4();
	ProjectionMatrix = ProjectionMatrix.x(m);
}

function pRotate(ang, v) {
	var arad = ang * Math.PI / 180.0;
	var m = Matrix.Rotation(arad, $V([v[0], v[1], v[2]])).ensure4x4();
 ProjectionMatrix = ProjectionMatrix.x(m);
}

function mvTranslate(v) {
	var m = Matrix.Translation($V([v[0], v[1], v[2]])).ensure4x4();
	ModelViewMatrix = ModelViewMatrix.x(m);
}

function mvScale(v) {
	var m = Matrix.Scale($V([v[0], v[1], v[2]])).ensure4x4();
	ModelViewMatrix = ModelViewMatrix.x(m);
}

function mvRotate(ang, v) {
	var arad = ang * Math.PI / 180.0;
	var m = Matrix.Rotation(arad, $V([v[0], v[1], v[2]])).ensure4x4();
 ModelViewMatrix = ModelViewMatrix.x(m);
}

Matrix.Translation = function (v) {
  if (v.elements.length == 2) {
    var r = Matrix.I(3);
    r.elements[2][0] = v.elements[0];
    r.elements[2][1] = v.elements[1];
    return r;
  }
  if (v.elements.length == 3) {
    var r = Matrix.I(4);
    r.elements[0][3] = v.elements[0];
    r.elements[1][3] = v.elements[1];
    r.elements[2][3] = v.elements[2];
    return r;
  }
  throw "Invalid length for Translation";
}

Matrix.Scale = function (v) {
  if (v.elements.length == 2) {
    var r = Matrix.I(3);
    r.elements[0][0] = v.elements[0];
    r.elements[1][1] = v.elements[1];
    return r;
  }
  if (v.elements.length == 3) {
    var r = Matrix.I(4);
    r.elements[0][0] = v.elements[0];
    r.elements[1][1] = v.elements[1];
    r.elements[2][2] = v.elements[2];
    return r;
  }
  throw "Invalid length for Translation";
}
	
function makeLookAt(ex, ey, ez, cx, cy, cz, ux, uy, uz) {
	var eye = $V([ex, ey, ez]);
	var center = $V([cx, cy, cz]);
	var up = $V([ux, uy, uz]);
	var mag;
	var z = eye.subtract(center).toUnitVector();
	var x = up.cross(z).toUnitVector();
	var y = z.cross(x).toUnitVector();

	var m = $M([[x.e(1), x.e(2), x.e(3), 0],
							[y.e(1), y.e(2), y.e(3), 0],
							[z.e(1), z.e(2), z.e(3), 0],
							[0, 0, 0, 1]]);

	var t = $M([[1, 0, 0, -ex],
							[0, 1, 0, -ey],
							[0, 0, 1, -ez],
							[0, 0, 0, 1]]);
	ProjectionMatrix = m.x(t);
}

function Perspective(fovy, aspect, znear, zfar) {
	var ymax = znear * Math.tan(fovy * Math.PI / 360.0);
	var ymin = -ymax;
	var xmin = ymin * aspect;
	var xmax = ymax * aspect;

	Frustum(xmin, xmax, ymin, ymax, znear, zfar);
}

function Frustum(left, right, bottom, top, znear, zfar) {
	var X = 2*znear/(right-left);
	var Y = 2*znear/(top-bottom);
	var A = (right+left)/(right-left);
	var B = (top+bottom)/(top-bottom);
	var C = -(zfar+znear)/(zfar-znear);
	var D = -2*zfar*znear/(zfar-znear);

	ProjectionMatrix = $M([[X, 0, A, 0],
						 [0, Y, B, 0],
						 [0, 0, C, D],
						 [0, 0, -1, 0]]);
}

function Ortho(left, right, bottom, top, znear, zfar) {
	var tx = -(right+left)/(right-left);
	var ty = -(top+bottom)/(top-bottom);
	var tz = -(zfar+znear)/(zfar-znear);

	ProjectionMatrix = $M([[2/(right-left), 0, 0, tx],
						 [0, 2/(top-bottom), 0, ty],
						 [0, 0, -2/(zfar-znear), tz],
						 [0, 0, 0, 1]]);
}

function PushMatrix(m) { // Pushes ModelViewMatrix onto matrixstack
	if (m) {
		MatrixStack.push(m.dup());
		ModelViewMatrix = m.dup();
	} else {
		MatrixStack.push(ModelViewMatrix.dup());
	}
}

function PopMatrix() { // Gets ModelViewMatrix from matrixstack
	if (MatrixStack.length == 0) throw "Invalid popMatrix!";
	ModelViewMatrix = MatrixStack.pop();
  setMatrixUniforms();
	return ModelViewMatrix;
}

Matrix.prototype.flatten = function () { // makes matrix 1-dimensional
	var result = [];
	if (this.elements.length == 0) return [];

	for (var j = 0; j < this.elements[0].length; j++)
		for (var i = 0; i < this.elements.length; i++)
			result.push(this.elements[i][j]);
	return result;
}

Matrix.prototype.ensure4x4 = function() {
	if (this.elements.length == 4 && this.elements[0].length == 4) return this;
	if (this.elements.length >  4 || this.elements[0].length >  4) return null;

	for (var i = 0; i < this.elements.length; i++) {
		for (var j = this.elements[i].length; j < 4; j++) {
			if (i == j)	this.elements[i].push(1);
			else				this.elements[i].push(0);
		}
	}
	for (var i = this.elements.length; i < 4; i++) {
		if (i == 0)					this.elements.push([1, 0, 0, 0]);
		else if (i == 1)		this.elements.push([0, 1, 0, 0]);
		else if (i == 2)		this.elements.push([0, 0, 1, 0]);
		else if (i == 3)		this.elements.push([0, 0, 0, 1]);
	}
	return this;
}

Matrix.prototype.make3x3 = function() {
	if (this.elements.length != 4 || this.elements[0].length != 4) return null;

	return Matrix.create([[this.elements[0][0], this.elements[0][1], this.elements[0][2]],
												[this.elements[1][0], this.elements[1][1], this.elements[1][2]],
												[this.elements[2][0], this.elements[2][1], this.elements[2][2]]]);
}

Vector.prototype.flatten = function () {
    return this.elements;
}

function mht(m) {
	var s = "";
	if (m.length == 16) {
		for (var i = 0; i < 4; i++) {
			s += "<span style='font-family: monospace'>[" + m[i*4+0].toFixed(4) + "," + m[i*4+1].toFixed(4) + "," + m[i*4+2].toFixed(4) + "," + m[i*4+3].toFixed(4) + "]</span><br>";
		}
	} else if (m.length == 9) {
		for (var i = 0; i < 3; i++) {
			s += "<span style='font-family: monospace'>[" + m[i*3+0].toFixed(4) + "," + m[i*3+1].toFixed(4) + "," + m[i*3+2].toFixed(4) + "]</font><br>";
		}
	} else { return m.toString();	}
	return s;
}


//###############################
// Texture functions
//###############################

function SetTexParams(mipmap) {
	gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
	if (mipmap) {
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST);
		gl.generateMipmap(gl.TEXTURE_2D);
	} else {
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
	}
}

function handleLoadedTexture(img, tex, mipmap) {
	gl.bindTexture(gl.TEXTURE_2D, tex);
	gl.texImage2D(gl.TEXTURE_2D, 0, img);
	SetTexParams(mipmap);
	gl.bindTexture(gl.TEXTURE_2D, null);
}

function initTexture(file, mipmap) {
	var tex = gl.createTexture();
	gl.enable(gl.TEXTURE_2D);
	if (file == "#white") { // create white procedural texture
		var pixels = new WebGLUnsignedByteArray([255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]);
		gl.bindTexture(gl.TEXTURE_2D, tex);
		gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
		gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, 2, 2, 0, gl.RGB, gl.UNSIGNED_BYTE, pixels);
		SetTexParams(mipmap);
		gl.bindTexture(gl.TEXTURE_2D, null);
	} else { // load texture from image file
		var img = new Image();
		img.onload = function() { handleLoadedTexture(img, tex, mipmap); }
		img.src = file;
	}
	return tex;
}

var Tex1, Tex2, Tex3, Tex4, Tex5, Tex6;


//###############################
// Object: Camera
//###############################

function ObjCamera() { }
ObjCamera.prototype = {
	Init: function() {
		this.Koords = [-4, 10, 20];
		this.Rotate = [32, 12, 0];
		this.Zoom = 0.85;
		this.Speed = 0.2;
		
		return this;
	},
	
	Set: function() {
    Perspective(45*this.Zoom, CanvasWidth/CanvasHeight, 0.1, 1100.0);
    pRotate(this.Rotate[0], [1, 0, 0]);
    pRotate(this.Rotate[1], [0, 1, 0]);
    pRotate(this.Rotate[2], [0, 0, 1]);
    pTranslate([-this.Koords[0], -this.Koords[1], -this.Koords[2]]);
		
		var pUniform = gl.getUniformLocation(shaderProg, "uPMatrix");
		gl.uniformMatrix4fv(pUniform, false, new WebGLFloatArray(ProjectionMatrix.flatten()));
	},
}

ObjCamera.Create = function() {
  var newCamera = new ObjCamera();
  return newCamera.Init();
}

var Kamera;


//###############################
// Object: Light
//###############################

function ObjLight() { }
ObjLight.prototype = {
	Init: function() {
		this.Koords  = [0,0,0,1]; // w = 1 sets light position, w = 0 sets light direction
		this.Diffuse = [1,1,0.8, 0.8]; // alpha sets intensity of diffuse light
		this.Ambient = [0,0,1, 0.2]; // alpha sets intensity of ambient light
		this.SwitchedOn = true;
		this.BlendValue = 0.0;
    gl.uniform1i(gl.getUniformLocation(shaderProg, "uLightOn"), this.SwitchedOn);
		gl.uniform4f( gl.getUniformLocation(shaderProg, "uAmbient"), this.Ambient[0], this.Ambient[1], this.Ambient[2], this.Ambient[3]);
		gl.uniform4f( gl.getUniformLocation(shaderProg, "uLightKoord"), this.Koords[0], this.Koords[1], this.Koords[2], this.Koords[3]);
		gl.uniform4f( gl.getUniformLocation(shaderProg, "uDiffuse"), this.Diffuse[0], this.Diffuse[1], this.Diffuse[2], this.Diffuse[3]);
		gl.enable(gl.DEPTH_TEST);
		gl.disable(gl.BLEND);
		gl.uniform1f(gl.getUniformLocation(shaderProg, "uAlpha"), 1.0);
		return this;
	},
	
	SetBlending: function(newBlendValue) {
		this.BlendValue = newBlendValue;
		if (this.BlendValue > 0.0) {
			gl.enable(gl.BLEND);
			gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
			gl.uniform1f(gl.getUniformLocation(shaderProg, "uAlpha"), this.BlendValue);
		} else {
			gl.disable(gl.BLEND);
			gl.uniform1f(gl.getUniformLocation(shaderProg, "uAlpha"), 1.0);
		}
	},
	
	On: function() {
		this.SwitchedOn = true;
    gl.uniform1i(gl.getUniformLocation(shaderProg, "uLightOn"), this.SwitchedOn);
	},
	
	Off: function() {
		this.SwitchedOn = false;
    gl.uniform1i(gl.getUniformLocation(shaderProg, "uLightOn"), this.SwitchedOn);
	},
	
	SetAmbient: function(NewColor) {
    this.Ambient = (NewColor.Ambient || NewColor).slice();
		gl.uniform4f( gl.getUniformLocation(shaderProg, "uAmbient"), this.Ambient[0], this.Ambient[1], this.Ambient[2], this.Ambient[3]);
	},
	
	SetColor: function(NewColor) {
    this.Colors = (NewColor.Colors || NewColor).slice();
		gl.uniform4f( gl.getUniformLocation(shaderProg, "uDiffuse"), this.Diffuse[0], this.Diffuse[1], this.Diffuse[2], this.Diffuse[3]);
	},
	
	SetKoords: function(NewKoords) {
    this.Koords = (NewKoords.Koords || NewKoords).slice();
		gl.uniform4f( gl.getUniformLocation(shaderProg, "uLightKoord"), this.Koords[0], this.Koords[1], this.Koords[2], this.Koords[3]);
	},
}

ObjLight.Create = function() {
  var newLight = new ObjLight();
  return newLight.Init();
}

var Licht;


//###############################
// Object: Font
//###############################

function ObjFont() { }
ObjFont.prototype = {
	Init: function(file) {
		this.Koords = [0, 0, 0];
		this.File = file;
		this.Textur = initTexture(file, false);;
		this.Square = ObjSquare.Create(this.Textur);
		return this;
	},
	
	Draw: function(NewKoords, txt) {
 		this.Koords  = (NewKoords.Koords || NewKoords).slice();
		var LichtStatus = Licht.SwitchedOn;
		var lbv = Licht.BlendValue;
		Licht.Off();
		Licht.SetBlending(1);
		PushMatrix(ModelViewMatrix);		
		mvTranslate(this.Koords);
		mvScale([0.1,0.2,0.2]);
    setMatrixUniforms();
		var d0 = 16/256;
		var d1 = 4/256;
		var i = 1;
		var j = 6
		for (var n = 0; n < txt.length; n++) {
			var ch = txt.charCodeAt(n);
			i = ch % 16;
			j =Math.floor( ch / 16);
			gl.bindBuffer(gl.ARRAY_BUFFER, this.Square.VBTexKoord);
			this.Square.TexKoord[0] = i*d0+d1;
			this.Square.TexKoord[1] = (j+1)*d0;
			this.Square.TexKoord[2] = i*d0+d0-d1;
			this.Square.TexKoord[3] = (j+1)*d0;
			this.Square.TexKoord[4] = i*d0+d0-d1;
			this.Square.TexKoord[5] = j*d0;
			this.Square.TexKoord[6] = i*d0+d1;
			this.Square.TexKoord[7] = j*d0;
			gl.bufferData(gl.ARRAY_BUFFER, new WebGLFloatArray(this.Square.TexKoord), gl.STATIC_DRAW);
			this.Square.Draw();
			mvTranslate([2,0,0]);
			setMatrixUniforms();
		}
		PopMatrix();
		Licht.SetBlending(lbv);
		if (LichtStatus) Licht.On();
	},
}

ObjFont.Create = function(file) {
  var newFont = new ObjFont();
  return newFont.Init(file);
}

var Font1;


//###############################
// Object: Line
//###############################

function ObjLine() { }
ObjLine.prototype = {
	Init: function(tex) {
		this.Koords  = [0.0, 0.0, 0.0,  1.0, 0.0, 0.0];
		this.Normal  = [1.0, 0.0, 0.0];
		this.Colors  = [1.0, 1.0, 1.0, 1.0];
		this.Indices = [ 0, 1];
		this.Textur = tex;
		
		this.VBPosition = gl.createBuffer();
		gl.bindBuffer(gl.ARRAY_BUFFER, this.VBPosition);
		gl.bufferData(gl.ARRAY_BUFFER, new WebGLFloatArray(this.Koords), gl.STATIC_DRAW);

		this.VBIndex = gl.createBuffer();
		gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.VBIndex);
		gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new WebGLUnsignedShortArray(this.Indices), gl.STATIC_DRAW);
		return this;
	},
	
	Draw: function(NewKoords) {
    setMatrixUniforms();
 		this.Koords  = (NewKoords.Koords || NewKoords).slice();
		gl.bindBuffer(gl.ARRAY_BUFFER, this.VBPosition);
		gl.bufferData(gl.ARRAY_BUFFER, WebGLFloatArray(this.Koords), gl.STATIC_DRAW);
	  	
    gl.enableVertexAttribArray(vertexPositionAttr);
    gl.disableVertexAttribArray(vertexNormalAttr);
    gl.disableVertexAttribArray(textureCoordAttr);
    gl.disableVertexAttribArray(vertexColorAttr);
		
    gl.bindBuffer(gl.ARRAY_BUFFER, this.VBPosition);
    gl.vertexAttribPointer(vertexPositionAttr, 3, gl.FLOAT, false, 0, 0);
		gl.vertexAttrib3fv(vertexNormalAttr, this.Normal);
		gl.vertexAttrib4fv(vertexColorAttr, this.Colors);
		
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.VBIndex);
		gl.drawElements(gl.LINES, 2, gl.UNSIGNED_SHORT, 0);
	},
	
	SetColor: function(NewColor) {
    this.Colors = (NewColor.Colors || NewColor).slice();
	},
}

ObjLine.Create = function(tex) {
  var newLine = new ObjLine();
  return newLine.Init(tex);
}

var Line1;


//###############################
// Object: Cube
//###############################

function ObjCube() { }
ObjCube.prototype = {
	Init: function(tex) {
		this.Wire = false;
		this.Colors = [1,1,1,0.0];
		this.Textur = tex;
		
		this.VBPosition = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, this.VBPosition);
    this.Koords = [
      -1.0, -1.0,  1.0,       1.0, -1.0,  1.0,       1.0,  1.0,  1.0,      -1.0,  1.0,  1.0, // Front face
      -1.0, -1.0, -1.0,      -1.0,  1.0, -1.0,       1.0,  1.0, -1.0,       1.0, -1.0, -1.0, // Back face
      -1.0,  1.0, -1.0,      -1.0,  1.0,  1.0,       1.0,  1.0,  1.0,       1.0,  1.0, -1.0, // Top face
      -1.0, -1.0, -1.0,       1.0, -1.0, -1.0,       1.0, -1.0,  1.0,      -1.0, -1.0,  1.0, // Bottom face
       1.0, -1.0, -1.0,       1.0,  1.0, -1.0,       1.0,  1.0,  1.0,       1.0, -1.0,  1.0, // Right face
      -1.0, -1.0, -1.0,      -1.0, -1.0,  1.0,      -1.0,  1.0,  1.0,      -1.0,  1.0, -1.0, // Left face
    ];
    gl.bufferData(gl.ARRAY_BUFFER, new WebGLFloatArray(this.Koords), gl.STATIC_DRAW);

    this.VBNormal = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, this.VBNormal);
    this.Normal = [
       0.0,  0.0,  1.0,       0.0,  0.0,  1.0,       0.0,  0.0,  1.0,       0.0,  0.0,  1.0, // Front face
       0.0,  0.0, -1.0,       0.0,  0.0, -1.0,       0.0,  0.0, -1.0,       0.0,  0.0, -1.0, // Back face
       0.0,  1.0,  0.0,       0.0,  1.0,  0.0,       0.0,  1.0,  0.0,       0.0,  1.0,  0.0, // Top face
       0.0, -1.0,  0.0,       0.0, -1.0,  0.0,       0.0, -1.0,  0.0,       0.0, -1.0,  0.0, // Bottom face
       1.0,  0.0,  0.0,       1.0,  0.0,  0.0,       1.0,  0.0,  0.0,       1.0,  0.0,  0.0, // Right face
      -1.0,  0.0,  0.0,      -1.0,  0.0,  0.0,      -1.0,  0.0,  0.0,      -1.0,  0.0,  0.0, // Left face
    ];
    gl.bufferData(gl.ARRAY_BUFFER, new WebGLFloatArray(this.Normal), gl.STATIC_DRAW);

    this.VBTexKoord = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, this.VBTexKoord);
    this.TexKoord = [
      0.0, 0.0,      1.0, 0.0,      1.0, 1.0,      0.0, 1.0, // Front face
      1.0, 0.0,      1.0, 1.0,      0.0, 1.0,      0.0, 0.0, // Back face
      0.0, 1.0,      0.0, 0.0,      1.0, 0.0,      1.0, 1.0, // Top face
      1.0, 1.0,      0.0, 1.0,      0.0, 0.0,      1.0, 0.0, // Bottom face
      1.0, 0.0,      1.0, 1.0,      0.0, 1.0,      0.0, 0.0, // Right face
      0.0, 0.0,      1.0, 0.0,      1.0, 1.0,      0.0, 1.0, // Left face
    ];
    gl.bufferData(gl.ARRAY_BUFFER, new WebGLFloatArray(this.TexKoord), gl.STATIC_DRAW);

    this.VBIndex = gl.createBuffer();
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.VBIndex);
    this.Indices = [
      0, 1, 2,      0, 2, 3,    // Front face
      4, 5, 6,      4, 6, 7,    // Back face
      8, 9, 10,     8, 10, 11,  // Top face
      12, 13, 14,   12, 14, 15, // Bottom face
      16, 17, 18,   16, 18, 19, // Right face
      20, 21, 22,   20, 22, 23  // Left face
    ]
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new WebGLUnsignedShortArray(this.Indices), gl.STATIC_DRAW);
		return this;
	},
	
	Draw: function() {
    setMatrixUniforms();
    gl.enableVertexAttribArray(vertexPositionAttr);
    gl.enableVertexAttribArray(vertexNormalAttr);
    gl.enableVertexAttribArray(textureCoordAttr);
    gl.disableVertexAttribArray(vertexColorAttr);
		
    gl.bindBuffer(gl.ARRAY_BUFFER, this.VBPosition);
    gl.vertexAttribPointer(vertexPositionAttr, 3, gl.FLOAT, false, 0, 0);
    gl.bindBuffer(gl.ARRAY_BUFFER, this.VBNormal);
    gl.vertexAttribPointer(vertexNormalAttr, 3, gl.FLOAT, false, 0, 0);
    gl.bindBuffer(gl.ARRAY_BUFFER, this.VBTexKoord);
    gl.vertexAttribPointer(textureCoordAttr, 2, gl.FLOAT, false, 0, 0);

    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(gl.TEXTURE_2D, this.Textur);
    gl.uniform1i(gl.getUniformLocation(shaderProg, "uSampler"), 0);

    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.VBIndex);

		gl.vertexAttrib4fv(vertexColorAttr, this.Colors);	  
		
    if (this.Wire == true) {gl.drawElements(gl.LINE_STRIP, 36, gl.UNSIGNED_SHORT, 0);
		} else {
			gl.drawElements(gl.TRIANGLES, 36, gl.UNSIGNED_SHORT, 0);
		}
		gl.bindTexture(gl.TEXTURE_2D, null);
	},
	
	SetColor: function(NewColor) {
    this.Colors = (NewColor.Colors || NewColor).slice();
	},
}

ObjCube.Create = function(tex) {
  var newCube = new ObjCube();
  return newCube.Init(tex);
}

var Cube1;


//###############################
// Object: Square
//###############################

function ObjSquare() { }
ObjSquare.prototype = {
	Init: function(tex) {
		this.Wire = false;
		this.Colors = [0,0,0,0];
		this.Textur = tex;
		
		this.VBPosition = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, this.VBPosition);
    this.Koords = [ -1.0, -1.0,  0.0,       1.0, -1.0,  0.0,       1.0,  1.0,  0.0,      -1.0,  1.0,  0.0 ];
    gl.bufferData(gl.ARRAY_BUFFER, new WebGLFloatArray(this.Koords), gl.STATIC_DRAW);

    this.VBNormal = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, this.VBNormal);
    this.Normal = [ 0.0,  0.0,  1.0,       0.0,  0.0,  1.0,       0.0,  0.0,  1.0,       0.0,  0.0,  1.0 ];
    gl.bufferData(gl.ARRAY_BUFFER, new WebGLFloatArray(this.Normal), gl.STATIC_DRAW);

    this.VBTexKoord = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, this.VBTexKoord);
    this.TexKoord = [ 0.0, 1.0,      1.0, 1.0,      1.0, 0.0,      0.0, 0.0 ];
    gl.bufferData(gl.ARRAY_BUFFER, new WebGLFloatArray(this.TexKoord), gl.STATIC_DRAW);

    this.VBIndex = gl.createBuffer();
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.VBIndex);
    this.Indices = [ 0, 1, 2,      0, 2, 3 ]
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new WebGLUnsignedShortArray(this.Indices), gl.STATIC_DRAW);
		return this;
	},
	
	Draw: function() {
    setMatrixUniforms();
    gl.enableVertexAttribArray(vertexPositionAttr);
    gl.enableVertexAttribArray(vertexNormalAttr);
    gl.enableVertexAttribArray(textureCoordAttr);
    gl.disableVertexAttribArray(vertexColorAttr);
		
    gl.bindBuffer(gl.ARRAY_BUFFER, this.VBPosition);
    gl.vertexAttribPointer(vertexPositionAttr, 3, gl.FLOAT, false, 0, 0);
    gl.bindBuffer(gl.ARRAY_BUFFER, this.VBNormal);
    gl.vertexAttribPointer(vertexNormalAttr, 3, gl.FLOAT, false, 0, 0);
    gl.bindBuffer(gl.ARRAY_BUFFER, this.VBTexKoord);
    gl.vertexAttribPointer(textureCoordAttr, 2, gl.FLOAT, false, 0, 0);

    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(gl.TEXTURE_2D, this.Textur);
    gl.uniform1i(gl.getUniformLocation(shaderProg, "uSampler"), 0);

    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.VBIndex);

		gl.vertexAttrib4fv(vertexColorAttr, this.Colors);	  
		
    if (this.Wire == true) {gl.drawElements(gl.LINE_STRIP, 6, gl.UNSIGNED_SHORT, 0);
		} else {
			gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
		}
		gl.bindTexture(gl.TEXTURE_2D, null);
	},
	
	SetColor: function(NewColor) {
    this.Colors = (NewColor.Colors || NewColor).slice();
	},
}

ObjSquare.Create = function(tex) {
  var newSquare = new ObjSquare();
  return newSquare.Init(tex);
}

var Square1;


//###############################
// Object: Disc
//###############################

function ObjDisc() { }
ObjDisc.prototype = {
	Init: function(tex, longs) {
		this.Koords = [ ];
		this.Normal = [ ];
		this.TexKoord = [ ];
		this.Indices = [ ];
		this.Wire = false;
		this.Colors = [0,0,0,0];
		this.Textur = tex;
		
		this.Koords.push(0);    this.Koords.push(0);    this.Koords.push(0);
		this.Normal.push(0);		this.Normal.push(1);		this.Normal.push(0);
		this.TexKoord.push(0.5);  this.TexKoord.push(0.5);    
		for (var j = 0; j <= longs; ++j) {
			var phi = j * 2 * Math.PI / longs;
			var sinPhi = Math.sin(phi);
			var cosPhi = Math.cos(phi);
			var x = cosPhi;
			var z = sinPhi;
			var u = 0.5*x+0.5;
			var v = 0.5*z-0.5;
			
			this.Koords.push(x);    this.Koords.push(0);    this.Koords.push(z);
			this.Normal.push(0);		this.Normal.push(1);		this.Normal.push(0);
			this.TexKoord.push(u);  this.TexKoord.push(v);    
		}
		
		for (var j = 1; j <= longs; ++j) {
			var n1 = j+1;
			if (n1 > longs) n1 = 1;
			this.Indices.push(j);		this.Indices.push(j+1);  this.Indices.push(0);
		}
		
		this.VBPosition = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, this.VBPosition);
    gl.bufferData(gl.ARRAY_BUFFER, new WebGLFloatArray(this.Koords), gl.STATIC_DRAW);

    this.VBNormal = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, this.VBNormal);
    gl.bufferData(gl.ARRAY_BUFFER, new WebGLFloatArray(this.Normal), gl.STATIC_DRAW);

    this.VBTexKoord = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, this.VBTexKoord);
    gl.bufferData(gl.ARRAY_BUFFER, new WebGLFloatArray(this.TexKoord), gl.STATIC_DRAW);

    this.VBIndex = gl.createBuffer();
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.VBIndex);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new WebGLUnsignedShortArray(this.Indices), gl.STATIC_DRAW);
    this.VBIndex.numItems = this.Indices.length;	
				
		return this;
	},
	
	Draw: function() {
    setMatrixUniforms();
    gl.enableVertexAttribArray(vertexPositionAttr);
    gl.enableVertexAttribArray(vertexNormalAttr);
    gl.enableVertexAttribArray(textureCoordAttr);
    gl.disableVertexAttribArray(vertexColorAttr);
		
    gl.bindBuffer(gl.ARRAY_BUFFER, this.VBPosition);
    gl.vertexAttribPointer(vertexPositionAttr, 3, gl.FLOAT, false, 0, 0);
    gl.bindBuffer(gl.ARRAY_BUFFER, this.VBNormal);
    gl.vertexAttribPointer(vertexNormalAttr, 3, gl.FLOAT, false, 0, 0);
    gl.bindBuffer(gl.ARRAY_BUFFER, this.VBTexKoord);
    gl.vertexAttribPointer(textureCoordAttr, 2, gl.FLOAT, false, 0, 0);

    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(gl.TEXTURE_2D, this.Textur);
    gl.uniform1i(gl.getUniformLocation(shaderProg, "uSampler"), 0);

    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.VBIndex);

		gl.vertexAttrib4fv(vertexColorAttr, this.Colors);	  
		
    if (this.Wire == true) {gl.drawElements(gl.LINE_STRIP, this.VBIndex.numItems, gl.UNSIGNED_SHORT, 0);
		} else {
			gl.drawElements(gl.TRIANGLES, this.VBIndex.numItems, gl.UNSIGNED_SHORT, 0);
		}
		gl.bindTexture(gl.TEXTURE_2D, null);
	},
	
	SetColor: function(NewColor) {
    this.Colors = (NewColor.Colors || NewColor).slice();
	},
}

ObjDisc.Create = function(tex, longs) {
  var newDisc = new ObjDisc();
  return newDisc.Init(tex, longs);
}

var Disc1, Disc2;


//###############################
// Object: Cylinder
//###############################

function ObjCylinder() { }
ObjCylinder.prototype = {
	Init: function(tex, ro, ru, lats, longs) {
		this.Koords = [ ];
		this.Normal = [ ];
		this.TexKoord = [ ];
		this.Indices = [ ];
		this.Wire = false;
		this.Colors = [0,0,0,0];
		this.Textur = tex;
		
		for (var i = 0; i <= lats; ++i) {
			for (var j = 0; j <= longs; ++j) {
				var phi = j * 2 * Math.PI / longs;
				var sinPhi = Math.sin(phi);
				var cosPhi = Math.cos(phi);
				var r = ro - (ro-ru) * i/lats;
				var x = r * cosPhi;
				var y = 1 - i/lats;
				var ny = Math.atan(ru-ro);
				var z = r * sinPhi;
				var u = 1-j/longs;
				var v = i/lats;
				
				this.Koords.push(x);    this.Koords.push(y);    this.Koords.push(z);
				this.Normal.push(cosPhi*Math.cos(ny));
				this.Normal.push(Math.sin(ny));
				this.Normal.push(sinPhi*Math.cos(ny));
				this.TexKoord.push(u);  this.TexKoord.push(v);    
			}
		}
		
		for (var i = 0; i < lats; ++i) {
			for (var j = 0; j < longs; ++j) {
				var n1 = (i * (longs+1)) + j;
				var n2 = n1 + longs + 1;
		    this.Indices.push(n1+1);		this.Indices.push(n1);  this.Indices.push(n2);
				this.Indices.push(n2);  this.Indices.push(n2+1);  this.Indices.push(n1+1);
			}
		}
		
		this.VBPosition = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, this.VBPosition);
    gl.bufferData(gl.ARRAY_BUFFER, new WebGLFloatArray(this.Koords), gl.STATIC_DRAW);

    this.VBNormal = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, this.VBNormal);
    gl.bufferData(gl.ARRAY_BUFFER, new WebGLFloatArray(this.Normal), gl.STATIC_DRAW);

    this.VBTexKoord = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, this.VBTexKoord);
    gl.bufferData(gl.ARRAY_BUFFER, new WebGLFloatArray(this.TexKoord), gl.STATIC_DRAW);

    this.VBIndex = gl.createBuffer();
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.VBIndex);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new WebGLUnsignedShortArray(this.Indices), gl.STATIC_DRAW);
    this.VBIndex.numItems = this.Indices.length;	
				
		return this;
	},
	
	Draw: function() {
    setMatrixUniforms();
    gl.enableVertexAttribArray(vertexPositionAttr);
    gl.enableVertexAttribArray(vertexNormalAttr);
    gl.enableVertexAttribArray(textureCoordAttr);
    gl.disableVertexAttribArray(vertexColorAttr);
		
    gl.bindBuffer(gl.ARRAY_BUFFER, this.VBPosition);
    gl.vertexAttribPointer(vertexPositionAttr, 3, gl.FLOAT, false, 0, 0);
    gl.bindBuffer(gl.ARRAY_BUFFER, this.VBNormal);
    gl.vertexAttribPointer(vertexNormalAttr, 3, gl.FLOAT, false, 0, 0);
    gl.bindBuffer(gl.ARRAY_BUFFER, this.VBTexKoord);
    gl.vertexAttribPointer(textureCoordAttr, 2, gl.FLOAT, false, 0, 0);

    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(gl.TEXTURE_2D, this.Textur);
    gl.uniform1i(gl.getUniformLocation(shaderProg, "uSampler"), 0);

    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.VBIndex);

		gl.vertexAttrib4fv(vertexColorAttr, this.Colors);	  
		
    if (this.Wire == true) {gl.drawElements(gl.LINES, this.VBIndex.numItems, gl.UNSIGNED_SHORT, 0);
		} else {
			gl.drawElements(gl.TRIANGLES, this.VBIndex.numItems, gl.UNSIGNED_SHORT, 0);
		}
		gl.bindTexture(gl.TEXTURE_2D, null);
	},
	
	SetColor: function(NewColor) {
    this.Colors = (NewColor.Colors || NewColor).slice();
	},
}

ObjCylinder.Create = function(tex, ro, ru, lats, longs) {
  var newCylinder = new ObjCylinder();
  return newCylinder.Init(tex, ro ,ru, lats, longs);
}

var Cylinder1, Cylinder2, Cylinder3;


//###############################
// Object: Sphere
//###############################

function ObjSphere() { }
ObjSphere.prototype = {
	Init: function(tex, lats, longs) {
		this.Koords = [ ];
		this.Normal = [ ];
		this.TexKoord = [ ];
		this.Indices = [ ];
		this.Wire = false;
		this.Colors = [0,0,0,0];
		this.Textur = tex;
		
		for (var i = 0; i <= lats; ++i) {
			for (var j = 0; j <= longs; ++j) {
				var theta = i * Math.PI / lats;
				var phi = j * 2 * Math.PI / longs;
				var sinTheta = Math.sin(theta);
				var sinPhi = Math.sin(phi);
				var cosTheta = Math.cos(theta);
				var cosPhi = Math.cos(phi);
				
				var x = cosPhi * sinTheta;
				var y = cosTheta;
				var z = sinPhi * sinTheta;
				var u = 1-j/longs;
				var v = i/lats;
				
				this.Koords.push(x);    this.Koords.push(y);    this.Koords.push(z);
				this.Normal.push(x);    this.Normal.push(y);    this.Normal.push(z);
				this.TexKoord.push(u);  this.TexKoord.push(v);    
			}
		}
		
		for (var i = 0; i < lats; ++i) {
			for (var j = 0; j < longs; ++j) {
				var n1 = (i * (longs+1)) + j;
				var n2 = n1 + longs + 1;
		    this.Indices.push(n1+1);		this.Indices.push(n1);  this.Indices.push(n2);
				this.Indices.push(n2);  this.Indices.push(n2+1);  this.Indices.push(n1+1);
			}
		}
		
		this.VBPosition = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, this.VBPosition);
    gl.bufferData(gl.ARRAY_BUFFER, new WebGLFloatArray(this.Koords), gl.STATIC_DRAW);

    this.VBNormal = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, this.VBNormal);
    gl.bufferData(gl.ARRAY_BUFFER, new WebGLFloatArray(this.Normal), gl.STATIC_DRAW);

    this.VBTexKoord = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, this.VBTexKoord);
    gl.bufferData(gl.ARRAY_BUFFER, new WebGLFloatArray(this.TexKoord), gl.STATIC_DRAW);

    this.VBIndex = gl.createBuffer();
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.VBIndex);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new WebGLUnsignedShortArray(this.Indices), gl.STATIC_DRAW);
    this.VBIndex.numItems = this.Indices.length;	
				
		return this;
	},
	
	Draw: function() {
    setMatrixUniforms();
    gl.enableVertexAttribArray(vertexPositionAttr);
    gl.enableVertexAttribArray(vertexNormalAttr);
    gl.enableVertexAttribArray(textureCoordAttr);
    gl.disableVertexAttribArray(vertexColorAttr);
		
    gl.bindBuffer(gl.ARRAY_BUFFER, this.VBPosition);
    gl.vertexAttribPointer(vertexPositionAttr, 3, gl.FLOAT, false, 0, 0);
    gl.bindBuffer(gl.ARRAY_BUFFER, this.VBNormal);
    gl.vertexAttribPointer(vertexNormalAttr, 3, gl.FLOAT, false, 0, 0);
    gl.bindBuffer(gl.ARRAY_BUFFER, this.VBTexKoord);
    gl.vertexAttribPointer(textureCoordAttr, 2, gl.FLOAT, false, 0, 0);

    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(gl.TEXTURE_2D, this.Textur);
    gl.uniform1i(gl.getUniformLocation(shaderProg, "uSampler"), 0);

    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.VBIndex);

		gl.vertexAttrib4fv(vertexColorAttr, this.Colors);	  
		
    if (this.Wire == true) {gl.drawElements(gl.LINES, this.VBIndex.numItems, gl.UNSIGNED_SHORT, 0);
		} else {
			gl.drawElements(gl.TRIANGLES, this.VBIndex.numItems, gl.UNSIGNED_SHORT, 0);
		}
		gl.bindTexture(gl.TEXTURE_2D, null);
	},
	
	SetColor: function(NewColor) {
    this.Colors = (NewColor.Colors || NewColor).slice();
	},
}

ObjSphere.Create = function(tex, nlats, nlongs) {
  var newSphere = new ObjSphere();
  return newSphere.Init(tex, nlats, nlongs);
}

var Erde, Gitter, Sonne, Mond, Sterne;

