
var gl;
var rm; //renderModule
var scene;
///var program;
var nav;


function TextParser(byteArray) {
	this.byteArray = byteArray;
	this.aPos = 0;

	this.GetSize = function () {
		return this.byteArray.byteLength;
	}
	this.GetPos = function() {
		return this.aPos;
	}
	this.GetArray = function() {
		return this.byteArray;
	}
	this.IsEmpty = function () {
		return this.aPos >= this.byteArray.byteLength;
	}
	this.GetChar = function () {
		return this.byteArray[this.aPos++];
	}
	this.GetLastChar = function () {
		if (this.aPos > 0)
			return this.byteArray[this.aPos - 1];
		return 0;
	}
	this.ReadString = function () {
		var str = "";
		while (this.aPos < this.byteArray.byteLength) {
			var chr = this.byteArray[this.aPos++];
			if (chr == 0)
				return str;
			str += String.fromCharCode(chr);
		}
	}
	this.ReadFloat = function () {
		if (this.aPos + 4 < this.byteArray.byteLength) {
			var buffer = new ArrayBuffer(4);
			var intView = new Int8Array(buffer);
			var floatView = new Float32Array(buffer);
			for (var i = 0; i < 4; i++) {
				intView[i] = this.GetChar();
			}
			return floatView[0];
		}
		return 0.0;
	}
	this.ReadVector = function (numComponents) {
		var ret = [];
		for (var i = 0; i < numComponents; i++) {
			ret.push(this.ReadFloat());
		}
		return ret;
	}
	this.ReadULong = function (numComponents) {
		var ret = 0;
		for (var i = 0; i < numComponents; i++) {
			ret += this.GetChar() << (i * 8);
		}
		return ret;
	}
}

function Material() {
	this.mid = -1;
	this.name = "";
	this.ambient = [0, 0, 0, 1];
	this.diffuse = [0, 0, 0, 1];
	this.specular = [0, 0, 0, 1];
	this.emission = [0, 0, 0, 1];
	this.shininess = 1;
	this.reflectivity = 0;
	this.depthFunc = 203;
	this.textureMix = 1;
	this.flags = 0;
	this.imageName = "";
	this.texFlags = 0;
	this.blendSrc = 0;
	this.blendDst = 0;
	this.shaderType = 0;
	this.shaderfsSource = "";
	this.shaderfsFilename = "";
	this.shadervsSource = "";
	this.shadervsFilename = "";
	this.shaderParameters = [];
}
function SceneNode() {
	this.id = -1;
	this.name = "";
	this.type=0;
	this.parentID=0;
	this.active = true;
	this.children = [];
	this.gid = -1;
	this.mid = -1;
	this.localMatrix = mat4.create();
    this.light = undefined;
    this.activeChild = 0;
    this.camera = undefined;
}

function AABB() {
	this.SetInvalid = function()
	{
		this.MinBound = vec3.fromValues(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE);
		this.MaxBound = vec3.fromValues(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE);
		this.Center = vec3.fromValues(0,0,0);
		this.Radius = 0.0;
	}
	this.SetInvalid();
	this.ExpandWithPoint = function(pointX, pointY, pointZ)
	{
		if (this.MaxBound[0] < pointX)
			this.MaxBound[0] = pointX;
		if (this.MinBound[0] > pointX)
			this.MinBound[0] = pointX;
		if (this.MaxBound[1] < pointY)
			this.MaxBound[1] = pointY;
		if (this.MinBound[1] > pointY)
			this.MinBound[1] = pointY;
		if (this.MaxBound[2] < pointZ)
			this.MaxBound[2] = pointZ;
		if (this.MinBound[2] > pointZ)
			this.MinBound[2] = pointZ;
	}
	this.ComputeCenter = function(){
		vec3.lerp(this.Center, this.MinBound, this.MaxBound, 0.5);
		this.Radius = vec3.distance(this.Center, this.MinBound);
	}
}

function Geometry(gl) {
	this.gid=-1;
	this.vertices = gl.createBuffer();
	this.normals = gl.createBuffer();
	this.texCoords = gl.createBuffer();
	this.indices = gl.createBuffer();
	this.indicesLength = 0;
	this.indicesWF = gl.createBuffer();
	this.indicesWFLength = 0;
	this.triDescList = [];
	this.firstTri = [];
	this.firstNorm = vec3.create();
	this.aabb = new AABB();
}

function Light() {
	this.type=4;//UNKNOWN
	this.diffuse=[0,0,0,1];
	this.angle=0;
	this.exponent=1;
	this.attenuation=[1,0,0];
	this.ies="";
	this.envLight=undefined;
}

function Scene(gl) {
	this.gl = gl;
	this.MaterialList = [];
	this.SceneNodesList = [];
	this.GeometryList = [];
	this.AddMaterial = function (material) {
		material.mid=this.MaterialList.length;
		this.MaterialList.push(material);
		return material.mid;
	}
	this.AddSceneNode = function (node) {
		this.SceneNodesList[node.id] = node;
		if (node.parentID >= 0) {
			if (this.SceneNodesList[node.parentID].type==6)//SceneNode::SWITCH
			{
				//solve switch node
				var activeChild=this.SceneNodesList[node.parentID].activeChild;
				node.active=((activeChild==-2) || (activeChild==this.SceneNodesList[node.parentID].children.length));
			}
			this.SceneNodesList[node.parentID].children.push(node.id);
		}
		//return node.id;
	}
	this.GetSceneNode = function (nodeID) {
		if (nodeID >= 0 && nodeID < this.SceneNodesList.length)
			return this.SceneNodesList[nodeID];
		else
			return null;
	}
	this.GetSceneNodesByType = function (type) {
		var nodes = [];
		for (var i = 0; i < this.SceneNodesList.length; i++) {
			var node = this.SceneNodesList[i];
			if (node.type == type)
				nodes.push(node);
		}
		return nodes;
	}
	this.IsNodeReflective = function(node) {
		var mid = node.mid;
		if (mid < 0) return false;
		return (this.MaterialList[mid].reflectivity > 0.0);
	}
	this.IsNodeTransparent = function(node) {
		var mid = node.mid;
		if (mid < 0) return false;
		return (this.MaterialList[mid].diffuse[3] < 1.0);
	}
	this.GetNodesAABB = function(node) {
		var gid = node.gid;
		if (gid < 0) return undefined;
		return this.GeometryList[gid].aabb;
	}
	this.GetAABB = function(nodeID) {
		this.nodeQueue = [];
		
		var aabb = new AABB();
		var node = this.GetSceneNode(nodeID);
		if (node == null) return undefined;
		var worldMat = node.localMatrix;
		while (true) {
			if (node == null || (node.active == false)) {
				if (this.nodeQueue.length > 0) {
					worldMat = this.nodeQueue[this.nodeQueue.length-1].matrix;
					node = this.nodeQueue[this.nodeQueue.length-1].node;
					this.nodeQueue.pop();
					continue;
				}
				break;
			}
			var nodeWorldMat = mat4.create();
			if (isMat4Unit(node.localMatrix)) {
				nodeWorldMat = mat4.clone(worldMat);
			} else {
				mat4.multiply(nodeWorldMat, worldMat, node.localMatrix);
			}
			if (node.type == 3) { //geometry
				var min = vec3.clone(this.GetNodesAABB(node).MinBound);
				var max = vec3.clone(this.GetNodesAABB(node).MaxBound);
				vec3.transformMat4(min, min, nodeWorldMat);
				vec3.transformMat4(max, max, nodeWorldMat);
				aabb.ExpandWithPoint(min[0], min[1], min[2]);
				aabb.ExpandWithPoint(max[0], max[1], max[2]);
			}
			for (var i = 1; i < node.children.length; i++){
				var pair = {
					node: this.GetSceneNode(node.children[i]),
					matrix: mat4.clone(nodeWorldMat)
				};
				this.nodeQueue.push(pair);
			}
			if (node.children.length > 0) {
				worldMat = mat4.clone(nodeWorldMat);
				node = this.GetSceneNode(node.children[0]);
			}
			else {
				if (this.nodeQueue.length > 0) {
					worldMat = this.nodeQueue[this.nodeQueue.length-1].matrix;
					node = this.nodeQueue[this.nodeQueue.length-1].node;
					this.nodeQueue.pop();
				}
				else
					break;
			}
		}
		aabb.ComputeCenter();
		return aabb;
	}
	this.GetNodesTransformation = function(nodeID) {
		var node = this.GetSceneNode(nodeID);
		if (node == null) return undefined;
		var lMat = mat4.clone(node.localMatrix);
		var parent = node.parentID;
		while (parent >= 0) {
			node = this.GetSceneNode(parent);
			if(!isMat4Unit(node.localMatrix))
				mat4.multiply(lMat, node.localMatrix, lMat);
			parent = node.parentID;
		}
		return lMat;
	}
	this.AddGeometry = function (geometry) {
		this.GeometryList[geometry.gid]=geometry;
		//return geometry.gid;
	}
	this.SetGeometry = function (nodeID, geometryID) {
		this.SceneNodesList[nodeID].gid = geometryID;
	}
	this.SetMaterial = function (nodeID, materialName) {
		for (var i = 0; i < this.MaterialList.length; i++) {
			if (this.MaterialList[i].name == materialName) {
				this.SceneNodesList[nodeID].mid = i;
				return;
			}
		}
	}
	this.SetTransformation = function (nodeID, localMatrix) {
		this.SceneNodesList[nodeID].localMatrix = localMatrix;
	}
	this.SetLight = function (nodeID, light) {
		this.SceneNodesList[nodeID].light = light;
	}
	this.SetActive = function (nodeID, active) {
		this.SceneNodesList[nodeID].active = active;
	}
	this.SetSwitchActiveChild = function (nodeID, activeChild) {
		if (this.SceneNodesList[nodeID].type==6)//SceneNode::SWITCH
		{
			/*if ((activeChild!=(size_t)-1) && (activeChild!=(size_t)-2) && (children.size()<=activeChild))
			{
				LOGWARNING(wxString::Format(wxT("<Scene>Not existing activeChild %lu in switch node with ID %lu"), (unsigned long)activeChild, (unsigned long)switchID));
			}*/
			this.SceneNodesList[nodeID].activeChild=activeChild;
			if ((activeChild==-1) || (activeChild==-2) || (this.SceneNodesList[nodeID].children.length<=activeChild))
			{
				var active=(activeChild==-2);
				for (var i=0; i<this.SceneNodesList[nodeID].children.length; ++i)
					this.SetActive(this.SceneNodesList[nodeID].children[i], active);
			}
			else
			{
				for (var i=0; i<this.SceneNodesList[nodeID].children.length; ++i)
					this.SetActive(this.SceneNodesList[nodeID].children[i], (activeChild==i));
			}
		}
	}
	this.Fit = function(camera, nodeID) {
		if (camera == undefined) return;
		if (nodeID == undefined) nodeID = 0;

		//get AABB of all affected nodes
		var aabb = this.GetAABB(nodeID);
		if (aabb == undefined) return;

		// turn camera to center of aabb
		camera.lookAt(vec3.clone(aabb.Center));
		// zoom to fit TODO
		var height = camera.centerDist * Math.tan(camera.fov/2);
		var newDist = camera.centerDist*(aabb.Radius/height);
		camera.zoom(newDist - camera.centerDist);
		// adjust perspective
		camera.nearPlane = aabb.Radius/100;
		camera.farPlane = (camera.centerDist+aabb.Radius)*2;
		camera.updatePerspective();
		// rerender scene
		requestAnimationFrame(render);
	}
}

function handleTextureLoaded(material) {
	gl.bindTexture(gl.TEXTURE_2D, material.texture);
    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
    //gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
    //gl.pixelStorei(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, false);
    var w = material.img.width, h = material.img.height;
    if(( !(w == 0) && !(w & (w - 1))) && ( !(h == 0) && !(h & (h - 1)))) { // image width and height are power of two
		//console.log(material.imageName+" w="+w+" h="+h+" power of two");
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
		gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, material.img);
		gl.generateMipmap(gl.TEXTURE_2D);
	} else {
		//console.log(material.imageName+" w="+w+" h="+h+" non power of two");
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
		gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, material.img);
	}
	gl.bindTexture(gl.TEXTURE_2D, null);
	material.textureLoad = true;
	if(rm != undefined) requestAnimationFrame(render);
}

function loadTexture(mat, src) {
	if(mat == undefined) return;
	if(src == undefined) return;
	mat.texture = gl.createTexture();
	mat.textureLoad = false;
	mat.img = new Image();
	mat.img.onload = function() {handleTextureLoaded(mat);}
	mat.img.src = src;
}

function parseVrut(scene, arrayBuffer) {
	var textParser = new TextParser(new Uint8Array(arrayBuffer));
	document.getElementById("log").innerHTML = "file length is " + textParser.GetSize();
	if (textParser.GetSize() < 6) {
		document.getElementById("log").innerHTML = "too short file " + textParser.GetSize();
		return;
	}
	if ((textParser.GetChar() != 86) || (textParser.GetChar() != 82) || (textParser.GetChar() != 85) || (textParser.GetChar() != 84) || (textParser.GetChar() != 0)) {
		document.getElementById("log").innerHTML = "wrong file header";
		return;
	}
	if (textParser.GetChar() != 0) {
		document.getElementById("log").innerHTML = "wrong file format version";
		return;
	}
	//add root node
	//{
		var node = new SceneNode();
		node.type=1;
		node.id=0;
		node.name = "FILE_ROOT";
		node.parentID = -1;
		scene.AddSceneNode(node);
	//}

	while (!textParser.IsEmpty()) {
		switch (textParser.GetChar()) {
			case 0://SDT_SCENE_NODE_INSERT
				var sceneID = textParser.GetChar();
				var node = new SceneNode();
				node.type=textParser.GetChar();
				node.id=textParser.ReadULong(4);
				node.parentID=textParser.ReadULong(4);
				node.active=(textParser.GetChar()!=0);
				node.name = textParser.ReadString();
				scene.AddSceneNode(node);
				//document.getElementById("log").innerHTML += "<br>SDT_SCENE_NODE_INSERT " + node.name;
				break;
			case 1://SDT_SCENE_NODE_TRANSFORM_SET
				var sceneID = textParser.GetChar();
				var nodeID = textParser.ReadULong(4);
				var localMatrix = new Float32Array(arrayBuffer.slice(textParser.GetPos(), textParser.GetPos() + 4 * 16));
				textParser.aPos += 4 * 16;
				scene.SetTransformation(nodeID, localMatrix);
				//document.getElementById("log").innerHTML += "<br>SDT_SCENE_NODE_TRANSFORM_SET";
				break;
			case 2://SDT_SCENE_GEOMETRY_TRIANGLES_ADD
				//document.getElementById("log").innerHTML += "<br>SDT_SCENE_GEOMETRY_TRIANGLES_ADD";
				var sceneID = textParser.GetChar();
				var geometry=new Geometry(scene.gl);
				geometry.gid=textParser.ReadULong(4);
				var vertsNum=textParser.ReadULong(4);
				var bytesPerInd = 1 + ((vertsNum>256)?1:0) + ((vertsNum>65536)?1:0) + ((vertsNum>16777216)?1:0);
				var tmp = [];
				if (vertsNum>0)
				{
					scene.gl.bindBuffer(scene.gl.ARRAY_BUFFER, geometry.vertices);
					tmp=new Float32Array(arrayBuffer.slice(textParser.GetPos(), textParser.GetPos() + vertsNum * 4 * 3));
					scene.gl.bufferData(scene.gl.ARRAY_BUFFER, tmp, scene.gl.STATIC_DRAW);
					textParser.aPos += vertsNum * 4 * 3;
					for (var i=0; i<vertsNum+2; i+=3)
						geometry.aabb.ExpandWithPoint(tmp[i], tmp[i+1], tmp[i+2]);
					geometry.aabb.ComputeCenter();
				}
				var normsNum=textParser.ReadULong(4);
				if (normsNum > 0) {
					scene.gl.bindBuffer(scene.gl.ARRAY_BUFFER, geometry.normals);
					scene.gl.bufferData(scene.gl.ARRAY_BUFFER, new Float32Array(arrayBuffer.slice(textParser.GetPos(), textParser.GetPos() + normsNum * 4 * 3)), scene.gl.STATIC_DRAW);
					textParser.aPos += normsNum * 4 * 3;
				}
				var texCNum = textParser.ReadULong(4);
				if (texCNum > 0) {
					scene.gl.bindBuffer(scene.gl.ARRAY_BUFFER, geometry.texCoords);
					scene.gl.bufferData(scene.gl.ARRAY_BUFFER, new Float32Array(arrayBuffer.slice(textParser.GetPos(), textParser.GetPos() + texCNum * 4 * 2)), scene.gl.STATIC_DRAW);
					textParser.aPos += texCNum * 4 * 2;
				}
				var indsNum = textParser.ReadULong(4);
				var bytesPerTri = 1 + ((indsNum>256)?1:0) + ((indsNum>65536)?1:0) + ((indsNum>16777216)?1:0);
				var ind=[];
				for (var i = 0; i < indsNum; i++) {
					ind[i] = textParser.ReadULong(bytesPerInd);
				}
				scene.gl.bindBuffer(scene.gl.ELEMENT_ARRAY_BUFFER, geometry.indices);
				scene.gl.bufferData(scene.gl.ELEMENT_ARRAY_BUFFER, new Uint32Array(ind), scene.gl.STATIC_DRAW);
				geometry.indicesLength = indsNum;
				// first triangle and normal for reflections
				var tri = [vec3.fromValues(tmp[ind[0]*3],tmp[ind[0]*3+1],tmp[ind[0]*3+2]),vec3.fromValues(tmp[ind[1]*3],tmp[ind[1]*3+1],tmp[ind[1]*3+2]),vec3.fromValues(tmp[ind[2]*3],tmp[ind[2]*3+1],tmp[ind[2]*3+2])];
				var a = vec3.create(), b = vec3.create(), n = vec3.create();
				vec3.sub(a, tri[1], tri[0]);
				vec3.sub(b, tri[2], tri[0]);
				vec3.cross(n,a,b);
				vec3.normalize(n,n);
				geometry.firstNorm = n;
				geometry.firstTri = tri;
				// indices for wireframe mode
				var indLines = [];
				var j = 0;
				for (var i = 0; i < indsNum; i+=3) {
					indLines[j++] = ind[i];
					indLines[j++] = ind[i+1];
					indLines[j++] = ind[i+1];
					indLines[j++] = ind[i+2];
					indLines[j++] = ind[i+2];
					indLines[j++] = ind[i];
				}
				scene.gl.bindBuffer(scene.gl.ELEMENT_ARRAY_BUFFER, geometry.indicesWF);
				scene.gl.bufferData(scene.gl.ELEMENT_ARRAY_BUFFER, new Uint32Array(indLines), scene.gl.STATIC_DRAW);
				geometry.indicesWFLength = j;
				//
				var triDNum = textParser.ReadULong(4);
				for (var i = 0; i < triDNum; i++) {
					var ind=textParser.ReadULong(bytesPerTri);
					var primType=textParser.GetChar();
					geometry.triDescList.push([primType, ind]);
				}
				scene.AddGeometry(geometry);
				break;
			case 3://SDT_SCENE_NODE_GEOMETRY_SET
				//document.getElementById("log").innerHTML += "<br>SDT_SCENE_NODE_GEOMETRY_SET";
				var sceneID = textParser.GetChar();
				var nodeID = textParser.ReadULong(4);
				var geometryID = textParser.ReadULong(4);
				scene.SetGeometry(nodeID, geometryID);
				break;
			case 4://SDT_SCENE_MATERIAL_ADD
				var sceneID = textParser.GetChar();
				var mat = new Material();
				mat.name = textParser.ReadString();
				mat.ambient = textParser.ReadVector(4);
				mat.diffuse = textParser.ReadVector(4);
				mat.specular = textParser.ReadVector(4);
				mat.emission = textParser.ReadVector(4);
				mat.shininess = textParser.ReadFloat();
				mat.reflectivity = textParser.ReadFloat();
				mat.depthFunc = textParser.ReadULong(4);
				mat.textureMix = textParser.ReadFloat();
				mat.flags = textParser.ReadULong(4);
				mat.texFlags = textParser.ReadULong(4);
				mat.imageName = textParser.ReadString();
				if (mat.imageName.length!=0) {
					//call add image
					loadTexture(mat, mat.imageName);
				}
				mat.blendSrc = textParser.ReadULong(4);
				mat.blendDst = textParser.ReadULong(4);
				mat.shaderType = textParser.GetChar();
				switch (mat.shaderType) {
					case 0://LANG_GLSL
						mat.shaderfsSource = textParser.ReadString();
						mat.shaderfsFilename = textParser.ReadString();
						mat.shadervsSource = textParser.ReadString();
						mat.shadervsFilename = textParser.ReadString();
						break;
					case 1://LANG_OPENRT
						break;
					case 2://LANG_CGFX
						mat.shadervsSource = textParser.ReadString();
						mat.shadervsFilename = textParser.ReadString();
						break;
					default:
						break;
				}
				var shaderParamCount = textParser.GetChar();
				for (var i = 0; i < shaderParamCount; i++) {
					var name = textParser.ReadString();
					var type = textParser.GetChar();
					switch (type)
					{
						case 0://ShaderParam::BOOL:
							mat.shaderParameters.push([name, type, textParser.GetChar()]);
							break;
						case 1://ShaderParam::INT:
							mat.shaderParameters.push([name, type, textParser.ReadULong(4)]);
							break;
						case 2://ShaderParam::FLOAT1:
							mat.shaderParameters.push([name, type, textParser.ReadFloat()]);
							break;
						case 3://ShaderParam::FLOAT2:
							mat.shaderParameters.push([name, type, textParser.ReadVector(2)]);
							break;
						case 4://ShaderParam::FLOAT3:
							mat.shaderParameters.push([name, type, textParser.ReadVector(3)]);
							break;
						case 5://ShaderParam::FLOAT4:
							mat.shaderParameters.push([name, type, textParser.ReadVector(4)]);
							break;
						case 6://ShaderParam::SAMPLER:
						case 7://ShaderParam::SAMPLERCUBE:
						case 8://ShaderParam::SAMPLER3D:
							mat.shaderParameters.push([name, type, textParser.ReadString()]);
							break;
					}
				}
				scene.AddMaterial(mat);
				//document.getElementById("log").innerHTML += "<br>SDT_SCENE_MATERIAL_ADD " + mat.name;
				break;
			case 5://SDT_SCENE_NODE_MATERIAL_SET
				//document.getElementById("log").innerHTML += "<br>SDT_SCENE_NODE_MATERIAL_SET";
				var sceneID = textParser.GetChar();
				var nodeID = textParser.ReadULong(4);
				var materialName = textParser.ReadString();
				scene.SetMaterial(nodeID, materialName);
				break;
			case 6://SDT_SCENE_NODE_SWITCH_ACTIVECHILD_SET:
				var sceneID = textParser.GetChar();
				var nodeID = textParser.ReadULong(4);
				var activeChild = textParser.ReadULong(4);
				/*if (activeChild==(unsigned long)-1)
					activeChild1=(size_t)-1;
				if (activeChild==(unsigned long)-2)
					activeChild1=(size_t)-2;*/
				scene.SetSwitchActiveChild(nodeID, activeChild);
				break;
			case 7://SDT_SCENE_NODE_LIGHT_SET
				var sceneID = textParser.GetChar();
				var nodeID = textParser.ReadULong(4);
				var light = new Light();
				light.type=textParser.GetChar();
				light.angle=textParser.ReadFloat();
				light.exponent=textParser.ReadFloat();
				light.diffuse=textParser.ReadVector(4);
				light.attenuation=textParser.ReadVector(3);
				light.diffuse[3]=textParser.ReadFloat();
				light.ies=textParser.ReadString();
				scene.SetLight(nodeID, light);
				break;
			default:
				document.getElementById("log").innerHTML += "<br>unknown " + textParser.GetLastChar();
				return;
				break;
		}
	}
	document.getElementById("log").innerHTML += "<br>parsing done";
}

function Camera(z) {
    var zDef = z || 30;
	this.fov = Math.PI / 4; //45deg
	this.nearPlane = 1;
	this.farPlane = 1000000;
	this.width = 640;
	this.height = 480;
	this.center = vec3.fromValues(0, 0, 0); // stred rotace(pivot)
	this.focus = vec3.fromValues(0, 0, 0); // bod na ktery se "diva" kamera
	this.eye = vec3.fromValues(0, 0, zDef); // pozice oka/kamery ve world space
	this.up = vec3.fromValues(0, 1, 0); // up vektor oka/kamery
	this.view = vec3.fromValues(0, 0, -zDef/Math.abs(zDef)); // vektor pohledu oka/kamery
	this.centerDist = Math.abs(zDef); // vzdalenost oka/kamery od focus(look at) pointu
	this.cmatrix = mat4.create(); // camera matrix = inversion of view matrix
	this.vmatrix = mat4.create();
	this.pmatrix = mat4.create();
	
	this.getCenterDist = function () {
		return this.centerDist;
	}
	this.uploadMatrices = function(vmLoc, pmLoc) {
		gl.uniformMatrix4fv(vmLoc, false, this.vmatrix);
		gl.uniformMatrix4fv(pmLoc, false, this.pmatrix);
	}
	this.updatePerspective = function () {
		this.width = gl.canvas.clientWidth;
		this.height = gl.canvas.clientHeight
		mat4.perspective(this.pmatrix, this.fov, this.width/this.height, this.nearPlane, this.farPlane);
	}
	this.pan = function (dx, dy) {
		var move = vec3.create();
		vec3.cross(move, this.up, this.view);
		vec3.scale(move, move, dx);
		var moveY = vec3.create();
		vec3.scale(moveY, this.up, dy);
		vec3.add(move, move, moveY);
		vec3.add(this.eye, this.eye, move); 
		vec3.add(this.focus, this.focus, move); 
		mat4.lookAt(this.vmatrix, this.eye, this.focus, this.up); 
		mat4.invert(this.cmatrix, this.vmatrix); 
	}
	this.zoom = function (dz) {
		if(this.centerDist + dz < 0) {
			return;
		}
		var move = vec3.create();
		vec3.scale(move, this.view, -dz);
		vec3.add(this.eye, this.eye, move);
		mat4.lookAt(this.vmatrix, this.eye, this.focus, this.up); 
		mat4.invert(this.cmatrix, this.vmatrix); 
		this.centerDist = this.centerDist + dz;
	}
	this.rotate = function (ax, ay) {
		var center2origin = vec3.create();
		vec3.subtract(center2origin, this.center, vec3.fromValues(0,0,0));
		var m = mat4.create();
		var mrot = mat4.create();
		var Xaxis = vec3.create();
		vec3.cross(Xaxis, this.view, this.up);
		mat4.rotate(mrot, mrot, ax, this.up);
		mat4.rotate(mrot, mrot, ay, Xaxis);
		vec3.transformMat4(this.up, this.up, mrot); 
		vec3.transformMat4(this.view, this.view, mrot);
		mat4.translate(m, m, center2origin);
		mat4.multiply(m,m,mrot);
		mat4.translate(m, m, vec3.negate(center2origin, center2origin));
		vec3.transformMat4(this.eye, this.eye, m);
		vec3.transformMat4(this.focus, this.focus, m);
		mat4.lookAt(this.vmatrix, this.eye, this.focus, this.up); 
		mat4.invert(this.cmatrix, this.vmatrix); 
	}
	/*this.roll = function (az) {
		var center2origin = vec3.create();
		vec3.subtract(center2origin, vec3.fromValues(0,0,0), this.center);
		var m = mat4.create();
		mat4.translate(m, m, center2origin);
		mat4.rotate(m, m, az, this.view);
		mat4.translate(m, m, vec3.negate(center2origin, center2origin));
		vec3.transformMat4(this.up, this.up, m); 
		vec3.transformMat4(this.focus, this.focus, m);
		mat4.lookAt(this.vmatrix, this.eye, this.focus, this.up);
		gl.uniformMatrix4fv(this.vmLoc, false, this.vmatrix);
	}*/
	this.lookAt = function (newfocus) {
		var ctof = vec3.create();
		vec3.sub(ctof, newfocus, this.focus);
		vec3.add(this.focus, this.focus, ctof);
		vec3.add(this.eye, this.eye, ctof);
		mat4.lookAt(this.vmatrix, this.eye, this.focus, this.up); 
		mat4.invert(this.cmatrix, this.vmatrix); 
	}
	// inicializace matic
	mat4.lookAt(this.vmatrix, this.eye, this.focus, this.up);
	mat4.invert(this.cmatrix, this.vmatrix); 
	this.updatePerspective();
}

function copyTouch(touch) {
	return { identifier: touch.identifier, pageX: touch.pageX, pageY: touch.pageY };
}
function ongoingTouchIndexById(idToFind) {
  for (var i = 0; i < _this.ongoingTouches.length; i++) {
    var id = _this.ongoingTouches[i].identifier;
    
    if (id == idToFind) {
      return i;
    }
  }
  return -1;    // not found
}
function toRadians(degrees) {
	return degrees * (Math.PI/180);
}
function toDegrees(radians) {
	return (radians / (Math.PI/180))%360;
}

function Navigation() {
	this.camera = null;
	this.rotSpeed = 0.5;
	this.panSpeed = 0.2;
	this.zoomSpeed = 0.2;
	this.speedModifier = 1
	this.rotX = 0;
	this.rotY = 0;
	this.panX = 0;
	this.panY = 0;
	this.ongoingTouches = [];
	this.dblTapTimer = null;
	this.pressTap = false;
	this.pressTapTimer = null;
	this.leftDrag = false;
	this.middleDrag = false;
	this.rightDrag = false;
	this.leftDragOut = false;
	this.middleDragOut = false;
	this.rightDragOut = false;
	this.mouseX = 0;
	this.mouseY = 0;
	_this = this;
	
	this.changeCamera = function (camNode) {
		_this.camera = camNode.camera;
		_this.speedModifier = Math.abs(_this.camera.getCenterDist() * 0.01);
		requestAnimationFrame(render);
	}
	
	/* mysi handlery */
	/// TODO!!!
	this.mouseDownHandler = function (e) { 
		_this.mouseX = e.pageX;
		_this.mouseY = e.pageY;
		switch(e.button) {
			case 0:
				_this.leftDrag = true;
				break;
			case 1:
				e.preventDefault();
				_this.middleDrag = true;
				break;
			case 2:
				_this.rightDrag = true;
				break;
			default:
				break;
		}
	}
	
	this.mouseMoveHandler = function (e) {
		if (_this.camera == null) 
			return;
		var relX = _this.mouseX - e.pageX; 
		var relY = _this.mouseY - e.pageY; 
		_this.mouseX = e.pageX;
		_this.mouseY = e.pageY;
		//var speedModifier = Math.abs(camera.getCenterDist() * 0.01);
		if (_this.leftDrag) {
			//rotation
			_this.rotX = _this.rotSpeed * toRadians(relX) /* * _this.speedModifier*/;
			_this.rotY = _this.rotSpeed * toRadians(relY) /* * _this.speedModifier*/;
			_this.camera.rotate(_this.rotX, _this.rotY);
			requestAnimationFrame(render);
		}
		if (_this.middleDrag) {
			//zoom
			_this.camera.zoom(-relY * _this.zoomSpeed * _this.speedModifier);
			_this.speedModifier = Math.abs(_this.camera.getCenterDist() * 0.01);
			requestAnimationFrame(render);
		}
		if (_this.rightDrag) {
			//pan
			_this.panX = -relX * _this.panSpeed * _this.speedModifier;
			_this.panY = -relY * _this.panSpeed * _this.speedModifier;
			_this.camera.pan(_this.panX, _this.panY);
			requestAnimationFrame(render);
		}
	} 
	
	this.mouseUpHandler = function (e) {
		switch(e.button) {
			case 0:
				_this.leftDrag = false;
				_this.leftDragOut = false;
				break;
			case 1:
				_this.middleDrag = false;
				_this.middleDragOut = false;
				break;
			case 2:
				_this.rightDrag = false;
				_this.rightDragOut = false;
				break;
			default:
				break;
		}
		if(e.detail == 2) {
			if(rm == undefined) return;
			if(scene == undefined) return;
			var id = rm.pickingDraw(e.pageX, e.pageY);
			if (id > 0) {
				var node = scene.GetSceneNode(id);
				switch(e.button) {
					case 0: // select geometry
						//console.log("Selected: "+node.id+". "+node.name);
						rm.setSelectedNode(node);
						break;
					case 1: // pick rotation pivot and center view
						var aabb = scene.GetNodesAABB(node);
						var center = vec3.create();
						vec3.transformMat4(center, aabb.Center, scene.GetNodesTransformation(id));
						_this.camera.center = vec3.clone(center);
						_this.camera.lookAt(center);
						requestAnimationFrame(render); // we need to redraw because camera moved
						break
					case 2: // pick rotation pivot
						var aabb = scene.GetNodesAABB(node);
						var center = vec3.create();
						vec3.transformMat4(center, aabb.Center, scene.GetNodesTransformation(id));
						_this.camera.center = vec3.clone(center);
						break
					default:
						break;
				}
			} else {
				switch(e.button) {
					case 0: // select geometry
						rm.setSelectedNode(); // undefined
						break;
					case 1: // pick rotation pivot and center view
						var center = vec3.create(); // 0,0,0
						vec3.transformMat4(center, center, scene.GetNodesTransformation(id));
						_this.camera.center = vec3.clone(center);
						_this.camera.lookAt(center);
						requestAnimationFrame(render);
						break
					case 2: // pick rotation pivot
						var center = vec3.create(); // 0,0,0
						vec3.transformMat4(center, center, scene.GetNodesTransformation(id));
						_this.camera.center = vec3.clone(center);
						break
					default:
						break;
				}
			}
		}
	}      
         
	this.mouseWheelHandler = function (e) {	
		if (_this.camera == null) 
			return;
		//zoom
		_this.camera.zoom(e.deltaY*10 * _this.zoomSpeed * _this.speedModifier);
		_this.speedModifier = Math.abs(_this.camera.getCenterDist() * 0.01);
		requestAnimationFrame(render);
	} 
	
	this.mouseOutHandler = function (e) {
		if (_this.leftDrag) {
			_this.leftDrag = false;
			_this.leftDragOut = true;
		}
		if (_this.middleDrag) {
			_this.middleDrag = false;
			_this.middleDragOut = true;
		}
		if (_this.rightDrag) {
			_this.rightDrag = false;
			_this.rightDragOut = true;
		}
		window.addEventListener('mouseup', _this.mouseUpHandler);
	}      
	
	this.mouseEnterHandler = function (e) {
		window.removeEventListener('mouseup', _this.mouseUpHandler);
		_this.mouseX = e.pageX;
		_this.mouseY = e.pageY;
		if (_this.leftDragOut) {
			_this.leftDrag = true;
			_this.leftDragOut = false;
		}
		if (_this.middleDragOut) {
			_this.middleDrag = true;
			_this.middleDragOut = false;
		}
		if (_this.rightDragOut) {
			_this.rightDrag = true;
			_this.rightDragOut = false;
		}
	}     
	
	/* dotykove handlery */
	this.touchStartHandler = function(e) {
		e.preventDefault();
		if (_this.camera == null) 
			return;
		var touches = e.changedTouches;
		if(_this.pressTap && touches.length == 1 && _this.ongoingTouches.length == 1) {
			_this.pressTap = false;
			_this.pressTapTimer = setTimeout( function() {
				_this.pressTapTimer = null;	
			}, 500);
		}
		for (var i = 0; i < touches.length; i++) {
			_this.ongoingTouches.push(copyTouch(touches[i]));
		}
		if (_this.ongoingTouches.length == 1) {
			_this.pressTap = true;
			if (_this.dblTapTimer == null) {
				_this.dblTapTimer = setTimeout( function() {
					_this.dblTapTimer = null;
				}, 500);
			} else { // select geometry
				clearTimeout(_this.dblTapTimer);
				_this.dblTapTimer = null;
				if(rm == undefined) return;
				if(scene == undefined) return;
				var id = rm.pickingDraw(_this.ongoingTouches[0].pageX, _this.ongoingTouches[0].pageY);
				if (id > 0) {
					var node = scene.GetSceneNode(id);
					//console.log("Selected: "+node.id+". "+node.name);
					rm.setSelectedNode(node);
				} else {
					rm.setSelectedNode(); //undefined
				}
			}
		} else {
			_this.pressTap = false;
		}
	}
	
	this.touchMoveHandler = function(e) {
		e.preventDefault();
		if (_this.camera == null) 
			return;
		_this.pressTap = false;
		var tTouches =  e.targetTouches;
		if (_this.ongoingTouches.length == 1) {
			touch = tTouches[0];
			oldTouch = _this.ongoingTouches[0];
			if (touch.identifier == oldTouch.identifier) {
				//rotation
				_this.rotX = _this.rotSpeed * toRadians(oldTouch.pageX-touch.pageX)/* * _this.speedModifier*2*/;
				_this.rotY = _this.rotSpeed * toRadians(oldTouch.pageY-touch.pageY)/* * _this.speedModifier*2*/;
				_this.camera.rotate(_this.rotX, _this.rotY);
				requestAnimationFrame(render);
			}
		}
		if (_this.ongoingTouches.length == 2) {
			var newTouch1 = vec2.fromValues(tTouches[0].pageX, tTouches[0].pageY);
			var newTouch2 = vec2.fromValues(tTouches[1].pageX, tTouches[1].pageY);
			var id1 = ongoingTouchIndexById(tTouches[0].identifier);
			var id2 = ongoingTouchIndexById(tTouches[1].identifier);
			var oldTouch1 = vec2.fromValues(_this.ongoingTouches[id1].pageX, _this.ongoingTouches[id1].pageY);
			var oldTouch2 = vec2.fromValues(_this.ongoingTouches[id2].pageX, _this.ongoingTouches[id2].pageY);
			var touchVector1 = vec2.create();
			var touchVector2 = vec2.create();
			var combinedVec = vec2.create();
			vec2.subtract(touchVector1, oldTouch1, newTouch1);
			vec2.subtract(touchVector2, oldTouch2, newTouch2);
			vec2.add(combinedVec,touchVector1,touchVector2);
			vec2.scale(combinedVec,combinedVec,0.5);
			_this.panX = -combinedVec[0] * _this.panSpeed * _this.speedModifier;
			_this.panY = -combinedVec[1] * _this.panSpeed * _this.speedModifier;
			var dist1 = vec2.distance(oldTouch1,oldTouch2);
			var dist2 = vec2.distance(newTouch1,newTouch2);
			var zoom = (dist1-dist2);
			_this.camera.pan(_this.panX, _this.panY);
			_this.camera.zoom(zoom * _this.panSpeed * _this.speedModifier);
			_this.speedModifier = Math.abs(_this.camera.getCenterDist() * 0.01);
			requestAnimationFrame(render);
		}
		var touches = e.changedTouches;
		for (var i = 0; i < touches.length; i++) {
			var idx = ongoingTouchIndexById(touches[i].identifier);
			if (idx >= 0) {
				_this.ongoingTouches.splice(idx, 1, copyTouch(touches[i]));
			}
		}
	}
	
	this.touchEndHandler = function(e) {
		e.preventDefault();
		if (_this.camera == null) 
			return;
		_this.pressTap = false;
		var touches = e.changedTouches;
		if (_this.pressTapTimer != null && _this.ongoingTouches.length == 2 && touches.length == 1 && touches[0].identifier == _this.ongoingTouches[1].identifier){			
			clearTimeout(_this.pressTapTimer);
			_this.pressTapTimer = null;
			// pick rotation pivot and center view
			if(rm != undefined && scene != undefined) {
				var id = rm.pickingDraw(_this.ongoingTouches[1].pageX, _this.ongoingTouches[1].pageY);
				if (id > 0) {
					var aabb = scene.GetNodesAABB(scene.GetSceneNode(id));
					var center = vec3.create();
					vec3.transformMat4(center, aabb.Center, scene.GetNodesTransformation(id));
					_this.camera.center = vec3.clone(center);
					_this.camera.lookAt(center);
					requestAnimationFrame(render); // we need to redraw because camera moved
				} else {
					var center = vec3.create(); // 0,0,0
					vec3.transformMat4(center, center, scene.GetNodesTransformation(id));
					_this.camera.center = vec3.clone(center);
					_this.camera.lookAt(center);
					requestAnimationFrame(render);
				}
			}
		}
		for (var i = 0; i < touches.length; i++) {
			var idx = ongoingTouchIndexById(touches[i].identifier);
			if (idx >= 0) {
				_this.ongoingTouches.splice(idx, 1);
			}
		}
		requestAnimationFrame(render);
	}
	
	this.initHandlers = function(canvas) {
		if ('ontouchstart' in window) {
			canvas.addEventListener('touchstart', _this.touchStartHandler);
			canvas.addEventListener('touchmove', _this.touchMoveHandler);
			canvas.addEventListener('touchend', _this.touchEndHandler);
		}
		canvas.addEventListener('contextmenu', function(e){e.preventDefault();});
		canvas.addEventListener('mousedown', _this.mouseDownHandler);
		canvas.addEventListener('mousemove', _this.mouseMoveHandler);
		canvas.addEventListener('mouseup', _this.mouseUpHandler);
		canvas.addEventListener('wheel', _this.mouseWheelHandler);
		canvas.addEventListener('mouseenter', _this.mouseEnterHandler);
		canvas.addEventListener('mouseout', _this.mouseOutHandler);
		//canvas.addEventListener('dblclick', this.checkE);
		//canvas.addEventListener('click', this.checkE);
		//canvas.addEventListener('contextmenu', this.checkE);
		//canvas.addEventListener('mouseleave', this.checkE);
		//canvas.addEventListener('mouseover', this.checkE);
	}
}

function onResize(canvas, appDiv) {
	document.getElementById("log").innerHTML = "resizing";
	canvas.width = appDiv.clientWidth;
	canvas.height = appDiv.clientHeight;
	if (_this.camera == null) 
		return;
	nav.camera.updatePerspective();
	requestAnimationFrame(render);
}

function isMat4Unit(a) {
	return ((a[0] == 1.0) && (a[1] == 0.0) && (a[2] == 0.0) && (a[3] == 0.0) &&
			(a[4] == 0.0) && (a[5] == 1.0) && (a[6] == 0.0) && (a[7] == 0.0) && 
			(a[8] == 0.0) && (a[9] == 0.0) && (a[10] == 1.0) && (a[11] == 0.0) &&
			(a[12] == 0.0) && (a[13] == 0.0) && (a[14] == 0.0) && (a[15] == 1.0));
}
function isMatrixMirror(a) {
	var v1 = vec3.fromValues(a[0], a[1], a[2]);
	var v2 = vec3.fromValues(a[4], a[5], a[6]);
	var v3 = vec3.fromValues(a[8], a[9], a[10]);
	var cross = vec3.create();
	vec3.cross(cross, v1, v2);
	var dot = vec3.dot(cross, v3);
	return dot < 0.0;
	//return (Vector3(_m11, _m12, _m13).Cross(Vector3(_m21, _m22, _m23)).Dot(Vector3(_m31, _m32, _m33))<0.0);
}
function RenderModule(gl) {
	if(gl == undefined) return;
	this.gl = gl;
	this.id = 0;
	this.name = "RenderWebGLModule";
	this.scene = undefined;
	this.program = undefined;
	this.camera = undefined;
	this.cameraPosition = vec3.create();
	this.viewMatrix = mat4.create();
	this.cameraMatrix = mat4.create();
	this.perspMatrix = mat4.create();
	this.lights = [];
	this.activeLight = -1;
	this.lightPosition = vec3.create();
	this.backgrounds = [];
	this.defaultBackground = [0.4, 0.3, 0.6];
	this.activeBackground = -1;
	this.defaultMaterial = new Material();
	this.defaultMaterial.ambient =  [0.1, 0.1, 0.1, 1.0];
	this.defaultMaterial.diffuse =  [0.6, 0.6, 0.6, 1.0];
	this.shadowQueue = [];
	this.itemsList = [];
	this.blendItemsList = [];
	this.reflectiveItemsList = [];
	this.backgroundItemsList = [];
	this.wireframe = false;
	
	this.initCamera = function() {
		if(this.scene == undefined) return;
		var id = this.scene.SceneNodesList.length;
		var node = new SceneNode();
		node.id = id;
		node.name = "camera";
		node.type = 2;
		node.camera = new Camera();
		node.localMatrix = node.camera.cmatrix;
		scene.AddSceneNode(node);
		this.camera = node;
		this.cameraPosition = node.camera.eye;
	}
	this.updateViewMatrix = function() {
		if (this.scene == undefined) return;
		if (this.camera == undefined) return;
		var node = this.camera;
		mat4.multiply(this.viewMatrix, node.camera.vmatrix, this.scene.GetNodesTransformation(node.parentID));
		//gl.uniformMatrix4fv(this.vmLoc, false, vMat);
	}
	this.updatePerspectiveMatrix = function() {
		if (this.scene == undefined) return;
		if (this.camera == undefined) return;
		
		this.perspMatrix = this.camera.camera.pmatrix;
		//gl.uniformMatrix4fv(this.pmLoc, false, this.cameras[activeCamera].camera.pmatrix);
	}	
	this.initHeadlight = function() {
		var id = scene.SceneNodesList.length;
		var node = new SceneNode();
		node.id = id;
		node.parentID = this.camera.id;
		node.name = "headlight";
		node.type = 4;
		node.light = new Light();
		node.light.type=1;
		node.light.diffuse=[1,1,1,1];
		node.light.attenuation=[1,0,0];
		scene.AddSceneNode(node);
	}
	// this should help in future with choosing light from GUI or console
	this.switchLight = function(lightNum) {
		if (this.scene == undefined) return;
		if (this.lights.length > 0 && lightNum < this.lights.length) {
			this.activeLight = lightNum;
		}
	}
	this.findLights = function() {
		if (this.scene == undefined) return;
		this.lights = this.scene.GetSceneNodesByType(4);
		//this.switchLight(this.lights.length-1);
	}
	this.updateLightPosition = function() {
		if (this.scene == undefined) return;
		if (this.activeLight < 0) return;
		var node = this.lights[this.activeLight];
		if (node.name == "headlight") { // headlight neni treba pocitat, poloha je svazana s kamerou
			if(isMat4Unit(this.camera.localMatrix))
				this.lightPosition = this.cameraPosition;
			else	
				vec3.transformMat4(this.lightPosition, vec3.fromValues(0,0,0), this.camera.localMatrix);
			return;
		}
		vec3.transformMat4(this.lightPosition, vec3.fromValues(0,0,0), this.scene.GetNodesTransformation(node.id));
		//gl.uniform3fv(this.lightPosLoc, camera.eye);
	}
	// this should help in future with choosing bg from GUI or console
	this.switchBackground = function(backgroundNum) {
		if (this.scene == undefined) return;
		if (this.backgrounds.length > 0 && backgroundNum < this.backgrounds.length) {
			this.activeBackground = backgroundNum;
		}
	}
	this.findBackgrounds = function() {
		if (this.scene == undefined) return;
		this.backgrounds = this.scene.GetSceneNodesByType(7);
	}
	this.mirrorMat = function(p, n) {
		var a = n[0];
		var b = n[1];
		var c = n[2];
		var d = -vec3.dot(p,n);
		var mirror = mat4.create();
		mirror[0] = -2*a*a+1;
		mirror[1] = -2*a*b;
		mirror[2] = -2*a*c;
		mirror[3] =  0;
		
		mirror[4] = -2*b*a;
		mirror[5] = -2*b*b+1;
		mirror[6] = -2*b*c;
		mirror[7] =  0;
		
		mirror[8] = -2*c*a;
		mirror[9] = -2*c*b;
		mirror[10]= -2*c*c+1;
		mirror[11]=  0;
		
		mirror[12] = -2*a*d;
		mirror[13] = -2*b*d;
		mirror[14] = -2*c*d;
		mirror[15] = 1;
		return mirror;
	}
	this.getPlaneEquation = function(p, n) {
		var d = -vec3.dot(n, p);
		var plane = vec4.fromValues(n[0],n[1],n[2],d);
		return plane;
	}
	this.getNormalModelMatrix = function(mmatrix) {
		var nMat = mat3.create();
		mat3.fromMat4(nMat, mmatrix);
		mat3.invert(nMat, nMat);
		mat3.transpose(nMat, nMat);
		return nMat;
	}
	this.initShaders = function() {
		if (this.scene == undefined) return;
		// Attributes
		this.posLoc = this.gl.getAttribLocation(this.program, "pos");
		this.gl.enableVertexAttribArray(this.posLoc);
		this.normalLoc = this.gl.getAttribLocation(this.program, "normal");
		this.gl.enableVertexAttribArray(this.normalLoc);
		this.texLoc = this.gl.getAttribLocation(this.program, "texCoord");
		this.gl.enableVertexAttribArray(this.texLoc);
		
		// Global uniforms
		this.vmLoc = this.gl.getUniformLocation(this.program, "VMatrix");
		this.pmLoc = this.gl.getUniformLocation(this.program, "PMatrix");
		this.camPosLoc = this.gl.getUniformLocation(this.program, "cameraPosition");
		this.lightPosLoc = this.gl.getUniformLocation(this.program, "lightPosition");
		this.lightDifLoc = this.gl.getUniformLocation(this.program, "lightColor");
		this.lightAttLoc = this.gl.getUniformLocation(this.program, "lightAttenuation");
		this.mirClippingLoc = this.gl.getUniformLocation(this.program, "mirrorClipping");
		this.mirClipPlaneLoc = this.gl.getUniformLocation(this.program, "mirrorClipPlane");
		
		// Local uniforms
		this.mmLoc = this.gl.getUniformLocation(this.program, "MMatrix");
		this.nmLoc = this.gl.getUniformLocation(this.program, "NMatrix");
		this.matAmbLoc = this.gl.getUniformLocation(this.program, "matAmbient");
		this.matDifLoc = this.gl.getUniformLocation(this.program, "matDiffuse");
		this.matSpeLoc = this.gl.getUniformLocation(this.program, "matSpecular");
		this.matEmiLoc = this.gl.getUniformLocation(this.program, "matEmission");
		this.matShiLoc = this.gl.getUniformLocation(this.program, "matShininess");
		this.texEnableLoc = this.gl.getUniformLocation(this.program, "textureEnable");
		this.texSamplerLoc = this.gl.getUniformLocation(this.program, "texSampler");
		
		this.gl.useProgram(this.program);
		this.gl.enable(this.gl.DEPTH_TEST);
	}
	this.updateGlobalUnifroms = function() {
		if (this.scene == undefined) return;
		this.updatePerspectiveMatrix();
		this.gl.uniformMatrix4fv(this.pmLoc, false, this.perspMatrix);
		this.updateViewMatrix();
		this.gl.uniformMatrix4fv(this.vmLoc, false, this.viewMatrix);
		this.gl.uniform3fv(this.camPosLoc, this.cameraPosition);
		this.updateLightPosition();
		this.gl.uniform3fv(this.lightPosLoc, this.lightPosition);
		var ligthNode = this.lights[this.activeLight];
		this.gl.uniform4fv(this.lightDifLoc, ligthNode.light.diffuse);
		this.gl.uniform3fv(this.lightAttLoc, ligthNode.light.attenuation);
		this.gl.uniform1i(this.mirClippingLoc, false);
		var clipPlane = [1.0,0.0,0.0,1.0];
		this.gl.uniform4fv(this.mirClipPlaneLoc, clipPlane);
	}
	this.updateLocalUniforms = function(node, matrix) {
		if (this.scene == undefined) return;
		this.gl.uniformMatrix4fv(this.mmLoc, false, matrix);
		var nMat = this.getNormalModelMatrix(matrix);
		this.gl.uniformMatrix3fv(this.nmLoc, false, nMat);
		var mid = node.mid;
		if (node.mid >= 0) {
			this.gl.uniform4fv(this.matAmbLoc, this.scene.MaterialList[mid].ambient);
			this.gl.uniform4fv(this.matDifLoc, this.scene.MaterialList[mid].diffuse);
			this.gl.uniform4fv(this.matSpeLoc, this.scene.MaterialList[mid].specular);
			this.gl.uniform4fv(this.matEmiLoc, this.scene.MaterialList[mid].emission);
			this.gl.uniform1f(this.matShiLoc, this.scene.MaterialList[mid].shininess);
			if(this.scene.MaterialList[mid].textureLoad) {
				this.gl.activeTexture(this.gl.TEXTURE0);
				this.gl.bindTexture(this.gl.TEXTURE_2D, this.scene.MaterialList[mid].texture);
				this.gl.uniform1i(this.texSamplerLoc, 0);
				this.gl.uniform1i(this.texEnableLoc, true);
				this.gl.enableVertexAttribArray(this.texLoc);
			} else {
				this.gl.uniform1i(this.texEnableLoc, false);
				this.gl.disableVertexAttribArray(this.texLoc);
			}
		} else {
			this.gl.uniform4fv(this.matAmbLoc, this.defaultMaterial.ambient);
			this.gl.uniform4fv(this.matDifLoc, this.defaultMaterial.diffuse);
			this.gl.uniform4fv(this.matSpeLoc, this.defaultMaterial.specular);
			this.gl.uniform4fv(this.matEmiLoc, this.defaultMaterial.emission);
			this.gl.uniform1f(this.matShiLoc, this.defaultMaterial.shininess);
			this.gl.uniform1i(this.texEnableLoc, false);
			this.gl.disableVertexAttribArray(this.texLoc);
		}
	}
	this.updateAttributes = function(node) {
		if (this.scene == undefined) return;
		var gid = node.gid;
		if (gid >= 0) {
			this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.scene.GeometryList[gid].vertices);
			this.gl.vertexAttribPointer(this.posLoc, 3, gl.FLOAT, false, 0, 0);

			this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.scene.GeometryList[gid].normals);
			this.gl.vertexAttribPointer(this.normalLoc, 3, gl.FLOAT, false, 0, 0);
			
			var mid = node.mid;
			if (mid >= 0 && this.scene.MaterialList[mid].textureLoad) {
				this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.scene.GeometryList[gid].texCoords);
				this.gl.vertexAttribPointer(this.texLoc, 2, gl.FLOAT, false, 0, 0);
			}
			
			if (this.wireframe) {
				this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.scene.GeometryList[gid].indicesWF);
			} else {
				this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.scene.GeometryList[gid].indices);
			}
		}
	}
	this.drawBackground = function(ItemsList) {
		if (this.scene == undefined) return;
		if (this.activeBackground < 0 || ItemsList.length <= 0) {
			this.gl.clearColor(this.defaultBackground[0], this.defaultBackground[1], this.defaultBackground[2], 1);
			this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
			this.gl.canvas.style.backgroundImage = "none";
			this.gl.canvas.style.backgroundColor = "rgba("+(this.defaultBackground[0]*255).toFixed()+","+(this.defaultBackground[1]*255).toFixed()+","+(this.defaultBackground[2]*255).toFixed()+",1.0)";
		} else {
			var item;
			// choose the right bg from all bgs after culling
			for (var i = 0; i < ItemsList.length; i++) {
				if (this.backgroundItemsList[i].node.id == this.backgrounds[this.activeBackground].id) {
					item = this.backgroundItemsList[i];
					break;
				}
			}
			this.gl.canvas.style.backgroundImage = "none";
			this.gl.canvas.style.backgroundColor = "rgba("+(this.defaultBackground[0]*255).toFixed()+","+(this.defaultBackground[1]*255).toFixed()+","+(this.defaultBackground[2]*255).toFixed()+",1.0)";
			if(item == undefined) { // activeBackground is not an active node
				this.gl.clearColor(this.defaultBackground[0], this.defaultBackground[1], this.defaultBackground[2], 1);
				this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
			} else {
				if (this.scene.MaterialList[item.node.mid].imageName != "") { // material has texture
					if (this.scene.MaterialList[item.node.mid].textureLoad) {
						this.gl.canvas.style.backgroundImage = "url('"+this.scene.MaterialList[item.node.mid].imageName+"')";
						this.gl.canvas.style.backgroundSize = "100% 100%";
						this.gl.clearColor(0, 0, 0, 0);
						this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
						
						/*
						this.gl.depthMask(false);
						this.updateLocalUniforms(item.node, item.matrix)
						*/
						/*var matUnit = mat4.create();
						var nmatUnit = mat3.create();
						this.gl.uniformMatrix4fv(this.pmLoc, false, matUnit);
						this.gl.uniformMatrix4fv(this.vmLoc, false, matUnit);
						this.gl.uniformMatrix4fv(this.mmLoc, false, matUnit);
						this.gl.uniformMatrix3fv(this.nmLoc, false, nmatUnit);
						
						var screen_coords = [
							-1.0, -1.0, 0.0,
							 1.0, -1.0, 0.0,
							 1.0,  1.0, 0.0,
							-1.0,  1.0, 0.0,
						];
						var mcbuffer = this.gl.createBuffer();
						this.gl.bindBuffer(this.gl.ARRAY_BUFFER, mcbuffer);
						this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(screen_coords), this.gl.STATIC_DRAW);
						this.gl.vertexAttribPointer(this.posLoc, 3, gl.FLOAT, false, 0, 0);
						*/
						/*var matrix = mat4.create();
						var pmInv = mat4.create();
						mat4.invert(pmInv, this.perspMatrix);
						var vmInv = mat4.create();
						mat4.invert(vmInv, this.viewMatrix);
						var mmInv = mat4.create();
						mat4.invert(mmInv, item.matrix);
						mat4.mul(matrix, vmInv, pmInv);
						mat4.mul(matrix, mmInv, matrix);
						var screen_coords = [
							vec3.fromValues(-10.0, -10.0, 0.0),
							vec3.fromValues( 10.0, -10.0, 0.0),
							vec3.fromValues( 10.0,  10.0, 0.0),
							vec3.fromValues(-10.0,  10.0, 0.0),
						];
						*/
						/*vec3.transformMat4(screen_coords[0],screen_coords[0], matrix);
						vec3.transformMat4(screen_coords[1],screen_coords[1], matrix);
						vec3.transformMat4(screen_coords[2],screen_coords[2], matrix);
						vec3.transformMat4(screen_coords[3],screen_coords[3], matrix);
						*/
						/*var model_coords = [
							screen_coords[0][0], screen_coords[0][0], screen_coords[0][0], 
							screen_coords[1][0], screen_coords[1][0], screen_coords[1][0], 
							screen_coords[2][0], screen_coords[2][0], screen_coords[2][0], 
							screen_coords[3][0], screen_coords[3][0], screen_coords[3][0]
						];
						var mcbuffer = this.gl.createBuffer();
						this.gl.bindBuffer(this.gl.ARRAY_BUFFER, mcbuffer);
						this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(model_coords), this.gl.STATIC_DRAW);
						this.gl.vertexAttribPointer(this.posLoc, 3, gl.FLOAT, false, 0, 0);
						
						var normals = [
							0.0, 0.0, 1.0,
							0.0, 0.0, 1.0,
							0.0, 0.0, 1.0,
							0.0, 0.0, 1.0,
						];
						var nbuffer = this.gl.createBuffer();
						this.gl.bindBuffer(this.gl.ARRAY_BUFFER, nbuffer);
						this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(normals), this.gl.STATIC_DRAW);
						this.gl.vertexAttribPointer(this.normalLoc, 3, gl.FLOAT, false, 0, 0);
						var tex_coords = [
							0.0, 0.0,
							1.0, 0.0,
							1.0, 1.0,
							0.0, 1.0,
						];
						var tcbuffer = this.gl.createBuffer();
						this.gl.bindBuffer(this.gl.ARRAY_BUFFER, tcbuffer);
						this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(tex_coords), this.gl.STATIC_DRAW);
						this.gl.enableVertexAttribArray(this.texLoc);
						this.gl.vertexAttribPointer(this.texLoc, 2, gl.FLOAT, false, 0, 0);
						var indices = [0,1,2,0,2,3];
						var ibuffer = this.gl.createBuffer();
						this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, ibuffer);
						this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, new Uint32Array(indices), this.gl.STATIC_DRAW);
						
						this.gl.drawElements(this.gl.TRIANGLES, 6, this.gl.UNSIGNED_INT, 0);
						*/
						// restore original matrices
						//this.gl.uniformMatrix4fv(this.pmLoc, false, this.perspMatrix);
						//this.gl.uniformMatrix4fv(this.vmLoc, false, this.viewMatrix);

						
						//this.gl.depthMask(false);
						
					} else { // texture didnt load yet
						this.gl.clearColor(this.defaultBackground[0], this.defaultBackground[1], this.defaultBackground[2], 1);
						this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
					}
				} else { // no texture, just color
					var bgcolor = this.scene.MaterialList[item.node.mid].diffuse;
					this.gl.canvas.style.backgroundColor = "rgba("+(bgcolor[0]*255).toFixed()+","+(bgcolor[1]*255).toFixed()+","+(bgcolor[2]*255).toFixed()+",1.0)";
					this.gl.clearColor(bgcolor[0], bgcolor[1], bgcolor[2], 1);
					this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
				}
			}
		}
	}
	this.drawSolidGeometry = function(ItemsList) {
		if (this.scene == undefined) return;
		for (var i = 0; i < ItemsList.length; i++) {
			var item = ItemsList[i];
			this.updateLocalUniforms(item.node, item.matrix);
			this.updateAttributes(item.node);
			if(this.wireframe) {
				this.gl.drawElements(this.gl.LINES, this.scene.GeometryList[item.node.gid].indicesWFLength, this.gl.UNSIGNED_INT, 0);
			} else {
				this.gl.drawElements(this.gl.TRIANGLES, this.scene.GeometryList[item.node.gid].indicesLength, this.gl.UNSIGNED_INT, 0);
			}
		}
	}
	this.drawReflectiveGeometry = function(ItemsList) {
		if (this.scene == undefined) return;
		for (var i = ItemsList.length-1; i >= 0; i--) {
			var item = ItemsList[i];
			var vertex = vec3.clone(this.scene.GeometryList[item.node.gid].firstTri[0]);
			vec3.transformMat4(vertex, vertex, item.matrix);
			var normal = vec3.clone(this.scene.GeometryList[item.node.gid].firstNorm);
			var nmatrix = this.getNormalModelMatrix(item.matrix);
			vec3.transformMat3(normal, normal, nmatrix);
			var camDir = vec3.create();
			vec3.sub(camDir, this.cameraPosition, vertex);
			vec3.normalize(camDir, camDir);
			// if normal away from camera -> flip normal
			if (vec3.dot(camDir,normal) < 0) {
				vec3.scale(normal, normal, -1);
			}
			// draw geometry into stencil buffer
			this.gl.enable(this.gl.STENCIL_TEST);
			this.gl.stencilFunc(this.gl.ALWAYS, 1, 0xFF);
			this.gl.stencilOp(this.gl.KEEP, this.gl.KEEP, this.gl.REPLACE);
			this.gl.stencilMask(0xFF);
			this.gl.depthMask(false);
			this.gl.colorMask(false, false, false, false);
			this.gl.clear(this.gl.STENCIL_BUFFER_BIT);
			
			this.updateLocalUniforms(item.node, item.matrix);
			this.updateAttributes(item.node);
			if(this.wireframe) {
				this.gl.drawElements(this.gl.LINES, this.scene.GeometryList[item.node.gid].indicesWFLength, this.gl.UNSIGNED_INT, 0);
			} else {
				this.gl.drawElements(this.gl.TRIANGLES, this.scene.GeometryList[item.node.gid].indicesLength, this.gl.UNSIGNED_INT, 0);
			}
			// draw scene reflection
			// 		clip by mirrorplane in fragment shader
			this.gl.stencilFunc(this.gl.EQUAL, 1, 0xFF);
			this.gl.stencilMask(0x00);
			this.gl.depthMask(true);
			this.gl.colorMask(true, true, true, true);
			
			var rplane = this.getPlaneEquation(vertex, normal);
			var rmatrix = this.mirrorMat(vertex, normal);
			this.drawSceneReflection(rmatrix, rplane, item.node);
			
			// draw geometry into depth buffer
			this.gl.stencilMask(0x00);
			this.gl.depthMask(true);
			this.gl.colorMask(false, false, false, false);
			
			this.updateLocalUniforms(item.node, item.matrix);
			this.updateAttributes(item.node);
			if(this.wireframe) {
				this.gl.drawElements(this.gl.LINES, this.scene.GeometryList[item.node.gid].indicesWFLength, this.gl.UNSIGNED_INT, 0);
			} else {
				this.gl.drawElements(this.gl.TRIANGLES, this.scene.GeometryList[item.node.gid].indicesLength, this.gl.UNSIGNED_INT, 0);
			}
			// enable color buffer writing
			this.gl.colorMask(true, true, true, true);
			this.gl.disable(this.gl.STENCIL_TEST);
		}
	}
	this.drawSceneReflection = function(rmatrix, rplane, mirrorNode) {
		var itemsList = [];
		var blendItemsList = [];
		var reflectiveItemsList = [];
		var backgroundItemsList = [];
		// make copy of itemslist and mirror matrices
		for (var i = 0; i < this.itemsList.length; i++) {
			var itemInfo = {distance: 0, node: this.itemsList[i].node, matrix: mat4.clone(this.itemsList[i].matrix)};
			mat4.multiply(itemInfo.matrix, rmatrix, itemInfo.matrix);
			itemsList.push(itemInfo);
		}
		for (var i = 0; i < this.reflectiveItemsList.length; i++) {
			if(this.reflectiveItemsList[i].node == mirrorNode) continue;
			var itemInfo = {distance: 0, node: this.reflectiveItemsList[i].node, matrix: mat4.clone(this.reflectiveItemsList[i].matrix)};
			mat4.multiply(itemInfo.matrix, rmatrix, itemInfo.matrix);
			reflectiveItemsList.push(itemInfo);
		}
		for (var i = 0; i < this.backgroundItemsList.length; i++) {
			var itemInfo = {distance: 0, node: this.backgroundItemsList[i].node, matrix: mat4.clone(this.backgroundItemsList[i].matrix)};
			mat4.multiply(itemInfo.matrix, rmatrix, itemInfo.matrix);
			backgroundItemsList.push(itemInfo);
		}
		for (var i = 0; i < this.blendItemsList.length; i++) {
			var itemInfo = {distance: 0, node: this.blendItemsList[i].node, matrix: mat4.clone(this.blendItemsList[i].matrix)};
			mat4.multiply(itemInfo.matrix, rmatrix, itemInfo.matrix);
			var center = vec3.clone(this.scene.GetNodesAABB(itemInfo.node).Center);
			vec3.transformMat4(center, center, itemInfo.matrix);
			itemInfo.distance = vec3.dist(this.cameraPosition, center)
			blendItemsList.push(itemInfo);
		}
		blendItemsList.sort(function(a,b){return a.distance - b.distance;});
		
		// mirror light
		var mirroredLightPosition = vec3.create();
		vec3.transformMat4(mirroredLightPosition, this.lightPosition, rmatrix);
		this.gl.uniform3fv(this.lightPosLoc, mirroredLightPosition);
		// mirror camera
		var mirroredCameraPosition = vec3.create();
		vec3.transformMat4(mirroredCameraPosition, this.cameraPosition, rmatrix);
		this.gl.uniform3fv(this.camPosLoc, mirroredCameraPosition);
		// enable clipping
		this.gl.uniform1i(this.mirClippingLoc, true);
		this.gl.uniform4fv(this.mirClipPlaneLoc, rplane);
		
		//this.drawBackground(backgroundItemsList);
		
		// draw scene
        this.drawSolidGeometry(reflectiveItemsList);
		this.drawSolidGeometry(itemsList);
		this.drawTransparentGeometry(blendItemsList);
		
		// disable clipping
		this.gl.uniform1i(this.mirClippingLoc, false);
		// restore original light and camera
		this.gl.uniform3fv(this.lightPosLoc, this.lightPosition);
		this.gl.uniform3fv(this.camPosLoc, this.cameraPosition);
		
	}
	this.drawTransparentGeometry = function(ItemsList) {
		if (this.scene == undefined) return;
		//this.gl.depthMask(false);
		this.gl.blendFuncSeparate(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA, this.gl.ONE, this.gl.ONE);
		this.gl.enable(this.gl.BLEND);
		for (var i = ItemsList.length-1; i >= 0; i--) {
			var item = ItemsList[i];
			this.updateLocalUniforms(item.node, item.matrix);
			this.updateAttributes(item.node);
			if(this.wireframe) {
				this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.scene.GeometryList[item.node.gid].indicesWF);
				this.gl.drawElements(this.gl.LINES, this.scene.GeometryList[item.node.gid].indicesWFLength, this.gl.UNSIGNED_INT, 0);
			} else {
				this.gl.drawElements(this.gl.TRIANGLES, this.scene.GeometryList[item.node.gid].indicesLength, this.gl.UNSIGNED_INT, 0);
			}
		}
		this.gl.disable(this.gl.BLEND);
		//this.gl.depthMask(true);
	}
	this.sceneCull = function() {
		this.shadowQueue = [];
		this.itemsList = [];
		this.blendItemsList = [];
		this.reflectiveItemsList = [];
		this.backgroundItemsList = [];
		
		var node = this.scene.GetSceneNode(0);
		if (node == null) return;
		var worldMat = node.localMatrix;
		while (true) {
			if (node == null || (node.active == false)) {
				if (this.shadowQueue.length > 0) {
					worldMat = this.shadowQueue[this.shadowQueue.length-1].matrix;
					node = this.shadowQueue[this.shadowQueue.length-1].node;
					this.shadowQueue.pop();
					continue;
				}
				break;
			}
			var nodeWorldMat = mat4.create();
			if (isMat4Unit(node.localMatrix)) {
				nodeWorldMat = mat4.clone(worldMat);
			} else {
				mat4.multiply(nodeWorldMat, worldMat, node.localMatrix);
			}
			var itemInfo = {
				distance: 0,
				node: node,
				matrix: mat4.clone(nodeWorldMat)
			};
			if (node.type == 3) { //geometry
				var center = vec3.clone(this.scene.GetNodesAABB(node).Center);
				vec3.transformMat4(center, center, nodeWorldMat);
				itemInfo.distance = vec3.dist(this.cameraPosition, center)
				if (this.scene.IsNodeReflective(node))
					this.reflectiveItemsList.push(itemInfo);
				else if (this.scene.IsNodeTransparent(node))
					this.blendItemsList.push(itemInfo);
				else
					this.itemsList.push(itemInfo);
			}
			else if (node.type == 7) { //background
				this.backgroundItemsList.push(itemInfo);
			}
			for (var i = 1; i < node.children.length; i++){
				var pair = {
					node: this.scene.GetSceneNode(node.children[i]),
					matrix: mat4.clone(nodeWorldMat)
				};
				this.shadowQueue.push(pair);
			}
			if (node.children.length > 0) {
				worldMat = mat4.clone(nodeWorldMat);
				node = this.scene.GetSceneNode(node.children[0]);
			}
			else {
				if (this.shadowQueue.length > 0) {
					worldMat = this.shadowQueue[this.shadowQueue.length-1].matrix;
					node = this.shadowQueue[this.shadowQueue.length-1].node;
					this.shadowQueue.pop();
				}
				else
					break;
			}
		}
		this.blendItemsList.sort(function(a,b){return a.distance - b.distance;});
		//this.reflectiveItemsList.sort(function(a,b){return a.distance - b.distance;});
	} 
	this.drawScene = function() {
		if (this.scene == undefined) return;
		this.updateGlobalUnifroms();
		this.sceneCull();
		this.gl.viewport(0, 0, this.camera.camera.width, this.camera.camera.height);
		this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
		this.drawBackground(this.backgroundItemsList);
        this.drawReflectiveGeometry(this.reflectiveItemsList);
		this.drawSolidGeometry(this.itemsList);
		this.drawTransparentGeometry(this.blendItemsList);
		
		document.getElementById("log").innerHTML = " cDist: " + this.camera.camera.centerDist.toFixed(2) + " touches " + nav.ongoingTouches.length + " panX:" + nav.panX.toFixed(2) + " panY:" + nav.panY.toFixed(2) + " rotX:" + toDegrees(nav.rotX).toFixed(2) + " rotY:" + toDegrees(nav.rotY).toFixed(2); // debug info
	}
	this.initScene = function(scene) {
		// creates new camera and headlight and inserts them into scene graph
		if(scene == undefined) return;
		this.scene = scene;
		this.initCamera();
		this.initHeadlight();
		this.findLights();
		this.switchLight(this.lights.length-1); // last should be newly created Headlight
		this.findBackgrounds();
		this.switchBackground(0);
		this.initShaders();
		
		this.scene.Fit(this.camera.camera); // fit scene to window;
		nav.changeCamera(this.camera); // tell navigation to use new camera
	}
	this.initPicking = function() {
		var vs = 	
			"attribute vec3 pos;" +
			"uniform mat4 MMatrix;" +
			"uniform mat4 VMatrix;" +
			"uniform mat4 PMatrix;" +
			"void main(void) {" +
			"	gl_Position = PMatrix * VMatrix * MMatrix * vec4(pos, 1.0);" +
			"}";
		var fs = 
			"precision mediump float;" +
			"uniform vec4 color;" +
			"void main(void) {" +
			"	gl_FragColor = color;" +
			"}";
		// init program
		this.pProgram = programFromJSVariables(this.gl, vs, fs);
		this.pPosLoc = this.gl.getAttribLocation(this.pProgram, "pos");
		this.gl.enableVertexAttribArray(this.pPosLoc);
		this.pMMLoc = this.gl.getUniformLocation(this.pProgram, "MMatrix");
		this.pVMLoc = this.gl.getUniformLocation(this.pProgram, "VMatrix");
		this.pPMLoc = this.gl.getUniformLocation(this.pProgram, "PMatrix");
		this.pColLoc = this.gl.getUniformLocation(this.pProgram, "color");
		//init framebuffer
		this.frameBuffer = this.gl.createFramebuffer();
		this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.frameBuffer);
		var texture = this.gl.createTexture();
		this.pWidth = this.camera.camera.width;
		this.pHeight = this.camera.camera.height;
		this.gl.bindTexture(this.gl.TEXTURE_2D, texture);
		this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.NEAREST);
		this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.NEAREST);
		this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE);
		this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE);
		this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.pWidth, this.pHeight, 0, this.gl.RGBA, this.gl.UNSIGNED_BYTE, null);
		var renderBuffer = this.gl.createRenderbuffer();
		this.gl.bindRenderbuffer(this.gl.RENDERBUFFER, renderBuffer);
		this.gl.renderbufferStorage(this.gl.RENDERBUFFER, this.gl.DEPTH_COMPONENT16, this.pWidth, this.pHeight);
		this.gl.framebufferTexture2D(this.gl.FRAMEBUFFER, this.gl.COLOR_ATTACHMENT0, this.gl.TEXTURE_2D, texture, 0);
		this.gl.framebufferRenderbuffer(this.gl.FRAMEBUFFER, this.gl.DEPTH_ATTACHMENT, this.gl.RENDERBUFFER, renderBuffer);
		this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
		this.pickingInit = true;
	}
	this.nodeIDToVec4 = function(id) {
		var ab = new ArrayBuffer(4);
		var ab32 = new Uint32Array(ab);
		ab32[0] = id;
		var ab8 = new Uint8Array(ab);
		var f32 = new Float32Array(4);
		f32[0] = ab8[0] / 255;
		f32[1] = ab8[1] / 255;
		f32[2] = ab8[2] / 255;
		f32[3] = ab8[3] / 255;
		return f32;
	}
	this.vec4ToNodeID = function(ab) {
		var ab32 = new Uint32Array(ab);
		return ab32[0];
	}
	this.pickingDrawItem = function(item) {
		this.gl.uniformMatrix4fv(this.pMMLoc, false, item.matrix);	
		this.gl.uniform4fv(this.pColLoc, this.nodeIDToVec4(item.node.id));
		this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.scene.GeometryList[item.node.gid].vertices);
		this.gl.vertexAttribPointer(this.pPosLoc, 3, gl.FLOAT, false, 0, 0);
		this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.scene.GeometryList[item.node.gid].indices);
		this.gl.drawElements(this.gl.TRIANGLES, this.scene.GeometryList[item.node.gid].indicesLength, this.gl.UNSIGNED_INT, 0);
	}
	this.pickingDraw = function(x,y) {
		if (this.scene == undefined) return -1;
		if(!this.pickingInit || this.camera.camera.width != this.pWidth  || this.camera.camera.height != this.pHeight) {
			this.initPicking();
		}
		this.gl.useProgram(this.pProgram);
		this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.frameBuffer);
		this.gl.clearColor(0,0,0,0);
		this.gl.viewport(0, 0, this.camera.camera.width, this.camera.camera.height);
		this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
		
		this.gl.uniformMatrix4fv(this.pPMLoc, false, this.perspMatrix);
		this.gl.uniformMatrix4fv(this.pVMLoc, false, this.viewMatrix);
		// draw geometries
		for(var i = 0; i < this.reflectiveItemsList.length; i++){
			this.pickingDrawItem(this.reflectiveItemsList[i]);
		}
		for(var i = 0; i < this.itemsList.length; i++){
			this.pickingDrawItem(this.itemsList[i]);
		}
		for(var i = 0; i < this.blendItemsList.length; i++){
			this.pickingDrawItem(this.blendItemsList[i]);
		}
		// read back pixel
		var ab = new ArrayBuffer(4);
		this.gl.readPixels(x, this.pHeight-y, 1, 1, this.gl.RGBA, this.gl.UNSIGNED_BYTE, new Uint8Array(ab));
		// set default framebuffer and program
		this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
		this.gl.useProgram(this.program);
		return this.vec4ToNodeID(ab);
	}
}

function render() {
	rm.drawScene();
}

function programFromHTMLScripts(gl, vsID, fsID) {
	var str = document.getElementById(vsID).textContent;
	var vs = gl.createShader(gl.VERTEX_SHADER);
	gl.shaderSource(vs, str);
	gl.compileShader(vs);

	var str = document.getElementById(fsID).textContent;
	var fs = gl.createShader(gl.FRAGMENT_SHADER);
	gl.shaderSource(fs, str);
	gl.compileShader(fs);

	var program = gl.createProgram();
	gl.attachShader(program, vs);
	gl.attachShader(program, fs);
	gl.linkProgram(program);
	return program;
}

function programFromJSVariables(gl, vsName, fsName) {
	var vs = gl.createShader(gl.VERTEX_SHADER);
	gl.shaderSource(vs, vsName);
	gl.compileShader(vs);

	var fs = gl.createShader(gl.FRAGMENT_SHADER);
	gl.shaderSource(fs, fsName);
	gl.compileShader(fs);

	var program = gl.createProgram();
	gl.attachShader(program, vs);
	gl.attachShader(program, fs);
	gl.linkProgram(program);
	return program;
}

function AJAXOnLoadInitScene(response, scene) {
	var arrayBuffer = response;
	if (arrayBuffer) {
		var infoDiv = document.getElementById("info");
		infoDiv.innerHTML = "Parsing...";
		parseVrut(scene, arrayBuffer);
		infoDiv.style.display = 'none';
	}
}
function AJAXOnError(url, emsg) {
	var infoDiv = document.getElementById("info");
	infoDiv.innerHTML = "Error loading scene at: \""+ url +"\"";
	if(emsg) infoDiv.innerHTML += "<br>" + emsg;
	infoDiv.className = "error";
}
/* scene should be clean before calling this*/
function loadScene(url, scene, callback) {
	var infoDiv = document.getElementById("info");
	sceneLoaded = false;
	infoDiv.innerHTML = "Loading...";
	infoDiv.style.display = 'block';
	var xhr = new XMLHttpRequest();
	xhr.open("get", url, true);
	xhr.responseType="arraybuffer";
	xhr.onload = function (oEvent) {
		AJAXOnLoadInitScene(xhr.response, scene);// Note: not xhr.responseText
		callback();
	};
	xhr.onerror = function () {
		AJAXOnError(url);
	};
	try {
		xhr.send();
	} catch(e) {
		AJAXOnError(url, e.message);
	}
}

function onLoadAM() {
	var canvas = document.querySelector("canvas");
	
	var appDiv = document.getElementById("app");
	canvas.width = appDiv.clientWidth;
	canvas.height = appDiv.clientHeight;
	
	var infoDiv = document.getElementById("info");
	gl = canvas.getContext("webgl", {stencil:true}) || canvas.getContext("experimental-webgl", {stencil:true});
	var contextAttributes = gl.getContextAttributes();
    var haveStencilBuffer = contextAttributes.stencil;
	var ext = gl.getExtension('OES_element_index_uint');
	if (gl == undefined || !haveStencilBuffer || !ext) {
		infoDiv.innerHTML = "Your device does not support WebGL or some of its extensions!<br>Please contact us at: <a href='mailto:Antonin.Misek@skoda-auto.cz' >Antonin.Misek@skoda-auto.cz</a>";
		infoDiv.className = "error";
		return -1;
	}

	scene = new Scene(gl);

	rm = new RenderModule(gl);
	rm.program = programFromHTMLScripts(gl, "vs", "fs");
	
	nav = new Navigation();
	nav.initHandlers(canvas);
	
	window.addEventListener('resize', function(){onResize(canvas, appDiv);}); // pri zmene velikosti okna se zmeni velikost canvasu
	
	/*** zde je mozno zmenit zobrazovany model ***/
	var url = "malytest.vrut";
	loadScene(url, scene, function(){rm.initScene(scene);});
	
}
