/*
* Copyright 2021 Markus Heimerl, OTH Regensburg
* Licensed under CC BY-NC 4.0
*
* ANY USE OF THIS SOFTWARE MUST COMPLY WITH THE
* CREATIVE COMMONS ATTRIBUTION-NONCOMMERCIAL 4.0 INTERNATIONAL LICENSE
* AGREEMENTS
*/

const m4 = {

	degreeToRadians: function(angle){return angle * Math.PI / 180;},

	mult: function (mat4_a, mat4_b){
		return [mat4_a[0]  * mat4_b[0] + mat4_a[1]  * mat4_b[4] + mat4_a[2]  * mat4_b[8]  +  mat4_a[3] * mat4_b[12],
					  mat4_a[0]  * mat4_b[1] + mat4_a[1]  * mat4_b[5] + mat4_a[2]  * mat4_b[9]  +  mat4_a[3] * mat4_b[13],
					  mat4_a[0]  * mat4_b[2] + mat4_a[1]  * mat4_b[6] + mat4_a[2]  * mat4_b[10] +  mat4_a[3] * mat4_b[14],
					  mat4_a[0]  * mat4_b[3] + mat4_a[1]  * mat4_b[7] + mat4_a[2]  * mat4_b[11] +  mat4_a[3] * mat4_b[15],

						mat4_a[4]  * mat4_b[0] + mat4_a[5]  * mat4_b[4] + mat4_a[6]  * mat4_b[8]  +  mat4_a[7] * mat4_b[12],
					  mat4_a[4]  * mat4_b[1] + mat4_a[5]  * mat4_b[5] + mat4_a[6]  * mat4_b[9]  +  mat4_a[7] * mat4_b[13],
					  mat4_a[4]  * mat4_b[2] + mat4_a[5]  * mat4_b[6] + mat4_a[6]  * mat4_b[10] +  mat4_a[7] * mat4_b[14],
					  mat4_a[4]  * mat4_b[3] + mat4_a[5]  * mat4_b[7] + mat4_a[6]  * mat4_b[11] +  mat4_a[7] * mat4_b[15],

						mat4_a[8]  * mat4_b[0] + mat4_a[9]  * mat4_b[4] + mat4_a[10] * mat4_b[8]  + mat4_a[11] * mat4_b[12],
					  mat4_a[8]  * mat4_b[1] + mat4_a[9]  * mat4_b[5] + mat4_a[10] * mat4_b[9]  + mat4_a[11] * mat4_b[13],
					  mat4_a[8]  * mat4_b[2] + mat4_a[9]  * mat4_b[6] + mat4_a[10] * mat4_b[10] + mat4_a[11] * mat4_b[14],
					  mat4_a[8]  * mat4_b[3] + mat4_a[9]  * mat4_b[7] + mat4_a[10] * mat4_b[11] + mat4_a[11] * mat4_b[15],

						mat4_a[12] * mat4_b[0] + mat4_a[13] * mat4_b[4] + mat4_a[14] * mat4_b[8]  + mat4_a[15] * mat4_b[12],
					  mat4_a[12] * mat4_b[1] + mat4_a[13] * mat4_b[5] + mat4_a[14] * mat4_b[9]  + mat4_a[15] * mat4_b[13],
					  mat4_a[12] * mat4_b[2] + mat4_a[13] * mat4_b[6] + mat4_a[14] * mat4_b[10] + mat4_a[15] * mat4_b[14],
					  mat4_a[12] * mat4_b[3] + mat4_a[13] * mat4_b[7] + mat4_a[14] * mat4_b[11] + mat4_a[15] * mat4_b[15]];
	},
	

	createIdentityMatrix: function(){
		return [1.0, 0.0, 0.0, 0.0,
					  0.0, 1.0, 0.0, 0.0,
					  0.0, 0.0, 1.0, 0.0,
					  0.0, 0.0, 0.0, 1.0];
	},

	createTranslationMatrix: function(transx, transy, transz){
		return [   1.0,    0.0,    0.0,    0.0,
				       0.0,    1.0,    0.0,    0.0,
				       0.0,    0.0,    1.0,    0.0,
				    transx, transy, transz,    1.0];
	},

	createXRotationMatrix: function(angleInRadians){
		var s = Math.sin(angleInRadians);
		var c = Math.cos(angleInRadians);
		return [1.0, 0.0, 0.0, 0.0,
					  0.0,   c,   s, 0.0,
				    0.0,  -s,   c, 0.0,
				    0.0, 0.0, 0.0, 1.0];
	},

	createYRotationMatrix: function(angleInRadians){
		var s = Math.sin(angleInRadians);
		var c = Math.cos(angleInRadians);
		return [  c, 0.0,  -s, 0.0,
					  0.0, 1.0, 0.0, 0.0,
				      s, 0.0,   c, 0.0,
				    0.0, 0.0, 0.0, 1.0];
	},

	createZRotationMatrix: function(angleInRadians){
		var s = Math.sin(angleInRadians);
		var c = Math.cos(angleInRadians);
		return [  c,   s, 0.0, 0.0,
					   -s,   c, 0.0, 0.0,
				    0.0, 0.0, 1.0, 0.0,
				    0.0, 0.0, 0.0, 1.0];
	},

	createScaleMatrix: function(scalex, scaley, scalez){
		return [scalex,    0.0,    0.0,    0.0,
					 		 0.0, scaley,    0.0,    0.0,
					 		 0.0,    0.0, scalez,    0.0,
					 		 0.0,    0.0,    0.0,    1.0];
	},

	createOrthographicMatrix: function(left, right, bottom, top, near, far){
		return [2 / (right - left), 0, 0, 0,
		0, 2 / (top - bottom), 0, 0,
		0, 0, 2 / (near - far), 0,
		(left + right) / (left - right), (bottom + top) / (bottom + top), (near + far) / (near - far), 1];
	},

	createSimpleProjectionMatrix: function(width, height, depth){
		return [ (2/width), 0.0, 0.0, 0.0,
				0.0, (-2/height), 0.0, 0.0,
				0.0, 0.0, (2/depth), 0.0,
				-1.0, 1.0, 0.0, 1.0];
	},

	createPerspectiveMatrix: function(fovInRadians, aspect, near, far){
		var f = Math.tan(Math.PI * 0.5 - 0.5 * fovInRadians);
		var rangeInv = 1.0 / (near - far);
	
		return [ f/aspect, 0, 0, 0,
				0, f, 0, 0,
				0, 0, (near + far) * rangeInv, -1,
				0, 0, near * far * rangeInv * 2, 0
		];
	},

	// leibnitz forumlar: InvA = 1/det(A) * Aadj
  inverse: function(m) {
    var m00 = m[0 * 4 + 0];
    var m01 = m[0 * 4 + 1];
    var m02 = m[0 * 4 + 2];
    var m03 = m[0 * 4 + 3];
    var m10 = m[1 * 4 + 0];
    var m11 = m[1 * 4 + 1];
    var m12 = m[1 * 4 + 2];
    var m13 = m[1 * 4 + 3];
    var m20 = m[2 * 4 + 0];
    var m21 = m[2 * 4 + 1];
    var m22 = m[2 * 4 + 2];
    var m23 = m[2 * 4 + 3];
    var m30 = m[3 * 4 + 0];
    var m31 = m[3 * 4 + 1];
    var m32 = m[3 * 4 + 2];
    var m33 = m[3 * 4 + 3];
    var tmp_0  = m22 * m33;
    var tmp_1  = m32 * m23;
    var tmp_2  = m12 * m33;
    var tmp_3  = m32 * m13;
    var tmp_4  = m12 * m23;
    var tmp_5  = m22 * m13;
    var tmp_6  = m02 * m33;
    var tmp_7  = m32 * m03;
    var tmp_8  = m02 * m23;
    var tmp_9  = m22 * m03;
    var tmp_10 = m02 * m13;
    var tmp_11 = m12 * m03;
    var tmp_12 = m20 * m31;
    var tmp_13 = m30 * m21;
    var tmp_14 = m10 * m31;
    var tmp_15 = m30 * m11;
    var tmp_16 = m10 * m21;
    var tmp_17 = m20 * m11;
    var tmp_18 = m00 * m31;
    var tmp_19 = m30 * m01;
    var tmp_20 = m00 * m21;
    var tmp_21 = m20 * m01;
    var tmp_22 = m00 * m11;
    var tmp_23 = m10 * m01;

    var t0 = (tmp_0 * m11 + tmp_3 * m21 + tmp_4 * m31) -
        (tmp_1 * m11 + tmp_2 * m21 + tmp_5 * m31);
    var t1 = (tmp_1 * m01 + tmp_6 * m21 + tmp_9 * m31) -
        (tmp_0 * m01 + tmp_7 * m21 + tmp_8 * m31);
    var t2 = (tmp_2 * m01 + tmp_7 * m11 + tmp_10 * m31) -
        (tmp_3 * m01 + tmp_6 * m11 + tmp_11 * m31);
    var t3 = (tmp_5 * m01 + tmp_8 * m11 + tmp_11 * m21) -
        (tmp_4 * m01 + tmp_9 * m11 + tmp_10 * m21);

    var d = 1.0 / (m00 * t0 + m10 * t1 + m20 * t2 + m30 * t3);

    return [
      d * t0,
      d * t1,
      d * t2,
      d * t3,
      d * ((tmp_1 * m10 + tmp_2 * m20 + tmp_5 * m30) -
            (tmp_0 * m10 + tmp_3 * m20 + tmp_4 * m30)),
      d * ((tmp_0 * m00 + tmp_7 * m20 + tmp_8 * m30) -
            (tmp_1 * m00 + tmp_6 * m20 + tmp_9 * m30)),
      d * ((tmp_3 * m00 + tmp_6 * m10 + tmp_11 * m30) -
            (tmp_2 * m00 + tmp_7 * m10 + tmp_10 * m30)),
      d * ((tmp_4 * m00 + tmp_9 * m10 + tmp_10 * m20) -
            (tmp_5 * m00 + tmp_8 * m10 + tmp_11 * m20)),
      d * ((tmp_12 * m13 + tmp_15 * m23 + tmp_16 * m33) -
            (tmp_13 * m13 + tmp_14 * m23 + tmp_17 * m33)),
      d * ((tmp_13 * m03 + tmp_18 * m23 + tmp_21 * m33) -
            (tmp_12 * m03 + tmp_19 * m23 + tmp_20 * m33)),
      d * ((tmp_14 * m03 + tmp_19 * m13 + tmp_22 * m33) -
            (tmp_15 * m03 + tmp_18 * m13 + tmp_23 * m33)),
      d * ((tmp_17 * m03 + tmp_20 * m13 + tmp_23 * m23) -
            (tmp_16 * m03 + tmp_21 * m13 + tmp_22 * m23)),
      d * ((tmp_14 * m22 + tmp_17 * m32 + tmp_13 * m12) -
            (tmp_16 * m32 + tmp_12 * m12 + tmp_15 * m22)),
      d * ((tmp_20 * m32 + tmp_12 * m02 + tmp_19 * m22) -
            (tmp_18 * m22 + tmp_21 * m32 + tmp_13 * m02)),
      d * ((tmp_18 * m12 + tmp_23 * m32 + tmp_15 * m02) -
            (tmp_22 * m32 + tmp_14 * m02 + tmp_19 * m12)),
      d * ((tmp_22 * m22 + tmp_16 * m02 + tmp_21 * m12) -
            (tmp_20 * m12 + tmp_23 * m22 + tmp_17 * m02))
    ];
  },

	transpose: function(m){

		return [ m[0], m[4], m[8],  m[12],
			m[1], m[5], m[9],  m[13],
			m[2], m[6], m[10], m[14],
			m[3], m[7], m[11], m[15]
		];
	},

	cross: function(a, b) {
		return [a[1] * b[2] - a[2] * b[1],
		        a[2] * b[0] - a[0] * b[2],
		        a[0] * b[1] - a[1] * b[0]];
	},

	subtractVectors: function(a, b) {
  	return [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
	},

	normalize: function(v) {
		var length = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
		if (length > 0.000001) {
		  return [v[0] / length, v[1] / length, v[2] / length];
		} else {
		  return [0, 0, 0];
		}
	},

  lookAt: function(cameraPosition, target, up) {
    var zAxis = m4.normalize(m4.subtractVectors(cameraPosition, target));
    var xAxis = m4.normalize(m4.cross(up, zAxis));
    var yAxis = m4.normalize(m4.cross(zAxis, xAxis));
 
    return [
       xAxis[0], xAxis[1], xAxis[2], 0,
       yAxis[0], yAxis[1], yAxis[2], 0,
       zAxis[0], zAxis[1], zAxis[2], 0,
       cameraPosition[0],
       cameraPosition[1],
       cameraPosition[2],
       1,
    ];
	}

};


const parentr3webgl = {

	createProgram: function(vertexshader, fragmentshader){
		var gl = this.gl;
		var program = gl.createProgram();
		gl.attachShader(program, vertexshader);
		gl.attachShader(program, fragmentshader);
		gl.linkProgram(program);
		if(gl.getProgramParameter(program, gl.LINK_STATUS)){return program;}
		console.log(gl.getProgramInfoLog(program));
		gl.deleteProgram(program);
	},

	compileShader: function(type, source){
		var gl = this.gl;
		var shader = gl.createShader(type);
		gl.shaderSource(shader, source);
		gl.compileShader(shader);
		if(gl.getShaderParameter(shader, gl.COMPILE_STATUS)){return shader;}
		console.log(gl.getShaderInfoLog(shader));
		gl.deleteShader(shader);
	},
	
	createShaderProgram: function(vertexshadersource, fragmentshadersource){
		var gl = this.gl;
		return this.createProgram(this.compileShader(gl.VERTEX_SHADER, vertexshadersource), this.compileShader(gl.FRAGMENT_SHADER, fragmentshadersource));
	},

	getAttribLocations: function(program, names){
		var attriblocations = {};
		for(var i = 0; i < names.length; i++){attriblocations[names[i]] = this.gl.getAttribLocation(program, names[i]);}
		return attriblocations;
	},

	getUniformLocations: function(program, names){
		var uniformlocations = {};
		for(var i = 0; i < names.length; i++){uniformlocations[names[i]] = this.gl.getUniformLocation(program, names[i]);}
		return uniformlocations;
	},

	init3D: function(){
		var gl = this.gl;
		gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
		gl.clearColor(0.0, 0.0, 0.0, 1.0);
		gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
		gl.enable(gl.CULL_FACE);
		gl.enable(gl.DEPTH_TEST);
	},

	createModelMatrix: function(tx, ty, tz, rx, ry ,rz, sx, sy, sz){
		var modelmatrix = m4.createIdentityMatrix();
		modelmatrix = m4.mult(m4.createTranslationMatrix(tx, ty, tz), modelmatrix);
		modelmatrix = m4.mult(m4.createXRotationMatrix(m4.degreeToRadians(rx)), modelmatrix);
		modelmatrix = m4.mult(m4.createYRotationMatrix(m4.degreeToRadians(ry)), modelmatrix);
		modelmatrix = m4.mult(m4.createZRotationMatrix(m4.degreeToRadians(rz)), modelmatrix);
		modelmatrix = m4.mult(m4.createScaleMatrix(sx, sy, sz), modelmatrix);
		return modelmatrix;
	},

	createBuffer: function(type, data){
		var gl = this.gl;
		var buffer = gl.createBuffer();
		gl.bindBuffer(type, buffer);
		gl.bufferData(type, new Float32Array(data), gl.STATIC_DRAW);
		return buffer;
	},

	connectBufferToAttribute: function(type, buffer, attriblocation, valuespervertex, enable){
		var gl = this.gl;
		if(enable) gl.enableVertexAttribArray(attriblocation);
		gl.bindBuffer(type, buffer);
		gl.vertexAttribPointer(attriblocation, valuespervertex, gl.FLOAT, false, 0, 0);
	},

	createTexture: function(dim){
		dim = dim ? dim : {x: 1, y: 1};
		var gl = this.gl;
		var texture = gl.createTexture();
		gl.bindTexture(gl.TEXTURE_2D, texture);
		gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, dim.x, dim.y, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
		return texture;
	},

	createRandomNoiseTexture: function(dim){
		var gl = this.gl;
		var tex = gl.createTexture();
		gl.bindTexture(gl.TEXTURE_2D, tex);
		var data = [];
		for(var i = 0; i < gl.canvas.width*gl.canvas.height; i++){var col = Math.round(Math.random()*Math.random())*255; data.push(col, col, col);}
		gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, dim.x, dim.y, 0, gl.RGB, gl.UNSIGNED_BYTE, new Uint8Array(data));
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
		return tex;
	},

	attachTextureSourceAsync: function(texture, source, flipVertically){
		var gl = this.gl;
		var image = new Image();
		image.src = source;
		
		image.addEventListener("load", function(){
			if(flipVertically){
				var canvas = document.createElement("canvas");
				canvas.width = image.width;
				canvas.height = image.height;
				canvas.getContext("2d").scale(1, -1);
				canvas.getContext("2d").drawImage(image, 0, image.height*-1);
				var flipImage = new Image();
				flipImage.src = canvas.toDataURL("image/png");
				flipImage.addEventListener("load", function(){
					addTexture(flipImage);
				});
			}else{
				addTexture(image);
			}
		});

		function addTexture(img){
			gl.bindTexture(gl.TEXTURE_2D, texture);
			gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
			gl.generateMipmap(gl.TEXTURE_2D);
		}
	}
};