
		/**
		 *   zeros()
		 *	@param <int> o ... Zeilen
		 *	@param <int> p ... Spalten
		 * 	@return <Array> A 
		 *	@description Erzeugt eine Matrix A(o,p), 
		 *				 die mit Nullen gefüllt ist
		 *
		**/			
		Math.zeros = function(o,p) {
			var A = new Array(o);
			for (var i=0; i<o; i++) {
				A[i] = new Array(p);
				for (var j=0; j<p; j++)
					A[i][j]=0.0;
			}
			return A;
		}
	
	
			
		/**
		*   trans()
		*	@param <Array> A
		* 	@return <Array> AT 
		*	@description Transponiert eine Matrix A
		*				 und gibt diese (AT) zurück
		*
		**/				
		Math.trans = function(A) {
			var AT = Math.zeros(A[0].length, A.length);
			for (var i=0; i<A.length; i++) 
				for (var j=0; j<A[i].length; j++) 
					AT[j][i] = A[i][j];	
			return AT;
		}
		
		/**
		 *   multi()
		 *	@param <Array> A1
		 *	@param <Array> A2
		 * 	@return <Array> R = A1*A2 
		 *	@description Multipliziert zwei Matrizen A1(o,p), A2(l,k) 
		 *				 und gibt das Produkt R(p,l) zurück
		 *			 	 
		**/				
		Math.multi = function (A1, A2){
			if (A1[0].length == A2.length){
				var R = Math.zeros(A1.length,A2[0].length); 
				for (var i=0; i<A1.length; i++){
					for (var j=0; j<A2[0].length; j++){
						R[i][j] = 0.0;
						for (var k=0; k<A1[0].length; k++)
							R[i][j] += A1[i][k] * A2[k][j];
					}		
				}
				return R;
			}
			return null;

		}
		
		/**
		 * Liefert die Eigenwerte und die Eigenvektoren - Berechnung nach [Jaeger et al, 2005]
		 * liefert null, wenn max, Iterationszahl (20) erreicht ist oder die Matrix nicht quadratisch ist
		 *
		 * @param <Array> A
		 * @return eig <Array<Array,Array>>
		 */
		Math.eig = function(A) {
			if (A[0].length != A.length)
				return null;
			var n = A.length;
			var D = A; // Eigenwerte
			var V = new Array(n); // Eigenvektoren
			for (var k=0; k<n; k++) {
				V[k] = new Array(n);
				for (var l=0; l<n; l++) {
					V[k][l] = 0.0;
				}
				V[k][k] = 1.0;
			}
			var phi = Number.MAX_VALUE;
	        var iter = 20;
	        while (Math.abs(phi) > 1.0E-8 && iter-->0)  {
				for (var i=0; i<n-1; i++) {
	        		for (var j=i+1; j<n; j++) {
	        			var tmpV = new Array(n);
						for (var k=0; k<n; k++) {
							tmpV[k] = new Array(n);
							for (var l=0; l<n; l++) {
								tmpV[k][l] = V[k][l];
							}
	        			}
                        phi = 0.5*Math.atan2(2.0*D[i][j], D[i][i]-D[j][j] );
                        
						for (var k=0; k<n; k++) {
							V[k] = new Array(n);
							for (var l=0; l<n; l++) {
								V[k][l] = 0.0;
							}
							V[k][k] = 1.0;
						}
						

	                    V[i][i] =  Math.cos(phi);
	                    V[i][j] = -Math.sin(phi);
	                    V[j][i] =  Math.sin(phi);
	                    V[j][j] =  Math.cos(phi);

	                    var VT = Math.trans(V);

	                    D = Math.multi(VT, (Math.multi(D, V)));

	                    V = Math.multi(tmpV, V);
	        		}
	        	}
	        }
			if (iter<=0)
				return null;
			for (var i=0; i<D.length; i++)
				for (var j=0; j<D[i].length; j++)
					if (i!=j)
						D[i][j] = 0.0;
			return [V, D];
		}
		
		
		/**
		*	trim()
		*	@return <String> s ... Zeichenkette
		*	@description entfernt Whitspaces am Anfang und am Ende
		*	             einer Zeichenkette - Prototyp-Erweiterung
		*	             String-Objekt
		*
		**/	
		String.prototype.trim = function(){
			return this.replace(/^\s*|\s*$/g, "");
		}
		
		
		function QuaternionenTransformation() {
			this.trafoInputArea = null;
			this.calcButton = null;
			this.q = [];
			this.m = 1.0;
			this.t = [];
			this.R = [];
			this.parameterTable = null;
			
			this.init = function() {
				if (!document.getElementById || !document.createElement ||
						(this.parameterTable = document.getElementById("quatTrafo")) == null)
					return false;
				
				this.trafoInputArea = this.replaceElementByTextarea( this.parameterTable.getElementsByTagName("pre")[0], 10, 80 );
				this.parameters = this.parameterTable.getElementsByTagName("input");
				this.initButton();
				return true;
			},
			
			this.replaceElementByTextarea = function (el, rows, cols) {
				var content = el.firstChild.nodeValue;
				var area = document.createElement("textarea");
				area.rows = rows; 
				area.cols = cols;
				area.value = content;
				area.id = el.id;
				var parent = el.parentNode;
				parent.replaceChild(area, el);
				return area;
			},
			
			this.createInputElement = function(value, type) {
				var el = document.createElement("input");
				el.type = type;
				el.value = value;
			
				return el;
			},
			
			this.initButton = function() {
			
				var td = document.getElementById("calcButton");
				this.calcButton = this.createInputElement("Transformationsparameter bestimmen", "button");
				var self = this;
				this.calcButton.onclick = function(e) {
					self.calculate();
				};
				td.appendChild( this.calcButton );
			
			},
			
			this.splitData2Arrays = function(str) {
				var lines = str.split("\n");
				var startSystem  = [];
				var targetSystem = [];
				for (var i=0, k=0; i<lines.length; i++) {
					var line = lines[i].trim();
					var lineData = line.split(/\s+/);
			
					if (lineData.length != 6) 
						continue;

					var xs = parseFloat(lineData[0].trim());
					var ys = parseFloat(lineData[1].trim());
					var zs = parseFloat(lineData[2].trim());
					
					var xt = parseFloat(lineData[3].trim());
					var yt = parseFloat(lineData[4].trim());
					var zt = parseFloat(lineData[5].trim());
					
					if (isNaN(xs) || isNaN(ys) || isNaN(zs) || isNaN(xt) || isNaN(yt) || isNaN(zt)) 
						continue;
					startSystem[k]  = [xs, ys, zs];
					targetSystem[k] = [xt, yt, zt];
					k++;
				}
				return [startSystem, targetSystem];
			},
			
			this.getCentroid = function(a) {
				var x = 0.0, y = 0.0, z = 0.0;
				for (var i=0; i<a.length; i++) {
					x += a[i][0];
					y += a[i][1];
					z += a[i][2];
				}
				return [x/a.length, y/a.length, z/a.length];
			},
		
			this.transform = function(startSystem, targetSystem) {
				if (startSystem.length > 0 && startSystem.length == targetSystem.length) {
					var Sxx = 0.0, Sxy = 0.0, Sxz = 0.0;
					var Syx = 0.0, Syy = 0.0, Syz = 0.0;
					var Szx = 0.0, Szy = 0.0, Szz = 0.0;
				
					var cs = this.getCentroid(startSystem);
					var ct = this.getCentroid(targetSystem);

					var redStart = [];
					var redTarget = [];
				
					for (var i=0; i<startSystem.length; i++) {
						redStart[i]  = [ startSystem[i][0] - cs[0],  startSystem[i][1] - cs[1],  startSystem[i][2] - cs[2] ];
						redTarget[i] = [ targetSystem[i][0] - ct[0], targetSystem[i][1] - ct[1], targetSystem[i][2] - ct[2] ];
					
						Sxx += ((startSystem[i][0] - cs[0]) * (targetSystem[i][0] - ct[0]));
						Sxy += ((startSystem[i][0] - cs[0]) * (targetSystem[i][1] - ct[1]));
						Sxz += ((startSystem[i][0] - cs[0]) * (targetSystem[i][2] - ct[2]));
					
						Syx += ((startSystem[i][1] - cs[1]) * (targetSystem[i][0] - ct[0]));
						Syy += ((startSystem[i][1] - cs[1]) * (targetSystem[i][1] - ct[1]));
						Syz += ((startSystem[i][1] - cs[1]) * (targetSystem[i][2] - ct[2]));
					
						Szx += ((startSystem[i][2] - cs[2]) * (targetSystem[i][0] - ct[0]));
						Szy += ((startSystem[i][2] - cs[2]) * (targetSystem[i][1] - ct[1]));
						Szz += ((startSystem[i][2] - cs[2]) * (targetSystem[i][2] - ct[2]));
					}	
			
					var N = [[ Sxx+Syy+Szz,     Syz-Szy,         Szx-Sxz,       Sxy-Syx],
							 [	Syz-Szy,      Sxx-Syy-Szz,       Sxy+Syx,       Szx+Sxz],
							 [  Szx-Sxz,        Sxy+Syx,      -Sxx+Syy-Szz,     Syz+Szy],
							 [  Sxy-Syx,        Szx+Sxz,         Syz+Szy,    -Sxx-Syy+Szz]];
 
					var VD = Math.eig(N);
					if (VD == null) {
						window.alert("Eigenwerte und -vektoren konnten nicht ermittelt werden!");
						return null;
					}
				
					var indexOfMaxEig = 0;
					var maxEig = VD[1][indexOfMaxEig][indexOfMaxEig];
					for (var i=1; i<VD[1].length; i++) {
						if (maxEig < VD[1][i][i]) {
							indexOfMaxEig = i;
							maxEig = VD[1][i][i];
						}
					}

					var q = [VD[0][0][indexOfMaxEig], VD[0][1][indexOfMaxEig], VD[0][2][indexOfMaxEig], VD[0][3][indexOfMaxEig]];
					
					this.R = [[2.0*q[0]*q[0]-1.0+2.0*q[1]*q[1],  2.0*(q[1]*q[2]-q[0]*q[3]),  2.0*(q[1]*q[3]+q[0]*q[2]) ],
							  [2.0*(q[1]*q[2]+q[0]*q[3]),  2.0*q[0]*q[0]-1.0+2.0*q[2]*q[2],  2.0*(q[2]*q[3]-q[0]*q[1]) ],
							  [2.0*(q[1]*q[3]-q[0]*q[2]),  2.0*(q[2]*q[3]+q[0]*q[1]),  2.0*q[0]*q[0]-1.0+2.0*q[3]*q[3] ]];
					var m1 = 0;
					var m2 = 0;

					for (var i=0; i<startSystem.length; i++) {
						var ps = [redStart[i]];
						var pt = [redTarget[i]];
						m1 += Math.multi( pt, (Math.multi(this.R, Math.trans(ps))) )[0][0];
						m2 += Math.multi( ps, Math.trans(ps) )[0][0];
					}
					this.m = Math.abs(m1/m2);

					this.t = Math.multi(this.R, Math.trans([cs]));
					for (var i=0; i<3; i++) {
						this.t[i] = ct[i] - this.m*this.t[i];
					}
					this.q = q;
					return true
				}
				return false;
			},
			
			this.calculate = function() {
				var systems = this.splitData2Arrays(this.trafoInputArea.value);
				if (systems != null && this.transform(systems[0], systems[1]))
					this.showResult();
				
			},
			
			this.showResult = function() {
				var c = this.parameterTable.getElementsByTagName("code");
				c[0].firstChild.nodeValue = (this.m-1.0).toFixed(9);
				
				c[1].firstChild.nodeValue = this.t[0].toFixed(4);
				c[2].firstChild.nodeValue = this.t[1].toFixed(4);
				c[3].firstChild.nodeValue = this.t[2].toFixed(4);
				
				this.parameterTable.getElementsByTagName("pre")[0].firstChild.nodeValue = this.array2String(this.R);

			},
			
			this.array2String = function(arr) {
				var str = "";
				for (var i=0; i<arr.length; i++) {
					for (var j=0; j<arr[i].length; j++)
						if (arr[i][j]<0)
							str += arr[i][j].toFixed(11) + " \t";
						else
							str += " " + arr[i][j].toFixed(11) + " \t";
					str += "\r\n";
				}
				return str;
			}

		}
		

