/*
 *  calcul3D.js
 *  (c) François Pirsch 2007
 *
 *  Calculs de géométrie analytique courante dans l'espace à 3 dimensions.
 */

// On ajoute quelques fonctions mathématiques pour compenser par des arrondis
// les erreurs de calcul de JavaScript.
Math.noround = function(x) { return x }
Math.round15 = function(x) { return Math.round(x*1000000000000000)/1000000000000000 }
Math.round12 = function(x) { return Math.round(x*1000000000000)/1000000000000 }
Math.round9  = function(x) { return Math.round(x*1000000000)/1000000000 }
Math.round6  = function(x) { return Math.round(x*1000000)/1000000 }


/******************************
 *	Vecteurs
 ******************************/
// En ce qui nous concerne, un point et un vecteur c'est la même chose :
// la donnée de 3 coordonnées.
Vector =
function(x, y, z) {
	this.x = x;
	this.y = y;
	this.z = z;

	// Méthodes d'instance.
	// Test
	this.isZero =
	function() {
		return !this.x && !this.y && !this.z;
	}

	// Affichage
	this.toString =
	function() {
		return Vector.round(this.x)+";"+Vector.round(this.y)+";"+Vector.round(this.z);
	}

	// Norme
	this.norm =
	function() {
		return Math.sqrt(this.x*this.x + this.y*this.y + this.z*this.z);
	}

	this.normalize =
	function() {
		var l = this.norm();
		this.x /= l;
		this.y /= l;
		this.z /= l;
	}

	// Somme, différence, opposé
	this.plus =
	function(v) {
		return new Vector(this.x+v.x, this.y+v.y, this.z+v.z);
	}
	this.minus =
	function(v) {
		return new Vector(this.x-v.x, this.y-v.y, this.z-v.z);
	}
	this.opposite =
	function() {
		return new Vector(-this.x, -this.y, -this.z);
	}

	// Produit par un scalaire
	this.mult =
	function(k) {
		return new Vector(this.x*k, this.y*k, this.z*k);
	}

	// Produit vectoriel
	this.crossProduct =
	function(v) {
		return new Vector(this.y*v.z-this.z*v.y,
			this.z*v.x-this.x*v.z,
			this.x*v.y-this.y*v.x);
	}

	// Produit scalaire
	this.dotProduct =
	function(v) {
		return this.x*v.x + this.y*v.y + this.z*v.z;
	}
	this.square =
	function() { return this.dotProduct(this) }

}


// Propriétés et méthodes statiques.
Vector.round = Math.round12;
Vector.points =
function(A, B) {
	return Vector.minus(new Vector(B.x, B.y, B.z), new Vector(A.x, A.y, A.z));
}
Vector.mult			= function(u, k) { return u.mult(k) }
Vector.crossProduct	= function(u, v) { return u.crossProduct(v) }
Vector.dotProduct	= function(u, v) { return u.dotProduct(v) }
Vector.square		= function(u)	 { return u.dotProduct(u) }
Vector.plus			= function(u, v) { return u.plus(v) }
Vector.minus		= function(u, v) { return u.minus(v) }


/******************************
 *	Plans
 ******************************/
Plane =
function(a, b, c, d) {
	this.a = a;
	this.b = b;
	this.c = c;
	this.d = d;
	this.l = Math.sqrt(a*a + b*b + c*c);
	this.normal = new Vector(a, b, c);

	// Copie
	this.copy =
	function() {
		return new Plane(this.a, this.b, this.c, this.d);
	}

	// Comparaison simpliste des coefficients 2 à 2.
	this.equals =
	function(P) {
		return (this.a == P.a) && (this.b == P.b) && (this.c == P.c) && (this.d == P.d);
	}

	// Affichage
	var coeff =			// cette fonction est privée
	function (n, v, tete) {
		if(n == 0) return '';
		if((n == 1) && v) return (tete ? '':'+')+v;
		if((n == -1) && v) return '-'+v;
		return (((n > 0) && !tete) ? '+':'')+Plane.round(n)+v;
	}

	// On n'affiche pas le =0 final, pour pouvoir mettre un <= 0 par exemple.
	this.toString =
	function() {
		return coeff(this.a, 'x', true) + coeff(this.b, 'y') + coeff(this.c, 'z') + coeff(this.d, '');
	}

	// Pour le point donné, renvoie ax+by+cz , éventuellement + d.
	this.axbyczd =
	function(A) {
		return this.a*A.x + this.b*A.y + this.c*A.z + this.d;
	}

	// Distance (orientée) du plan à un point.
	this.distanceTo =
	function(A) {
		return this.axbyczd(A) / this.l;
	}
}

// Méthodes de classe.
Plane.round = Math.round12;
Plane.points = function(A, B, C) {
	// Calcul de l'équation du plan passant par A, B et C.
	// Vecteur normal
	var n = Vector.points(A, B).crossProduct(Vector.points(A, C));
	if(n.isZero()) return null;

	// Il n'y a plus qu'à calculer le terme constant.
	var d = -n.dotProduct(A);
	return new Plane(n.x, n.y, n.z, d);
}

Plane.fromString = function(s) {
	// Analyse l'équation dans la chaîne s, du type "3x+2-z=1".
	s = s.replace(/\s+/g, '');
	if(!s) return undefined;

	var egal = s.match(egal);
	if(egal && egal.length > 1) return null;	// Plus d'un signe égal.
	if(s.charAt(0) == '=') return null;

	var a, b, c, d;
	var nombre = '(?:\\.\\d+|\\d+(?:\\.\\d*)?)(?:e[+-]?\\d{1,3})?';
	var re_monome = new RegExp('^[+-]?(?:'+nombre+'[xyz]?|[xyz])');
	var i = 0;
	var trouve;
	var monome;
	var apres_egal = false;
	var variable;
	var coeff = new Array();
	coeff['_'] = 0;
	var coefficient;
	while(s) {
		if(s.charAt(0) == '=') {
			apres_egal = true;
			s = s.substr(1);
		}
		trouve = re_monome.exec(s);
		if(!trouve) return null;		// erreur de syntaxe
		monome = trouve.toString();

		variable = monome.charAt(monome.length-1);
		if(variable < 'a') variable = '_';
		else if(coeff[variable] != undefined) return null; // variable présente 2 fois

		// Ajoute le 1 implicite.
		coefficient = parseFloat(monome.replace(/^([+-]?)[x-z]/,'$1'+'1'));
		// On peut définir plusieurs constantes, genre x+2=0
		if(variable != '_')
			coeff[variable] = apres_egal ? -coefficient : coefficient;
		else
			coeff['_'] += apres_egal ? -coefficient : coefficient;
		s = s.substr(trouve.index+monome.length);
	}

	// On renvoie les bons coeffs, avec 0 pour ceux qui n'ont pas été définis.
	return new Plane(coeff.x || 0, coeff.y || 0, coeff.z || 0, coeff['_'] || 0);
}
