/*
 * OBJ Reader
 * (c) François Pirsch 2007
 *
 * Catte classe prend en paramètre une chaîne de caractères contenant
 * le fichier obj. Elle l'analyse et expose ensuite les données.
 * On est très cool avec les erreurs...
 * On ignore les données de normales et de texture.
 */

function Model3DObj(source) {
	/* Public */
	this.erreur = null;
	this.vertice = new Array();
	this.line    = new Array();
	this.face    = new Array();
	this.sourceLine = new Array();	// Tableau représentant les lignes du fichier.


	/* Points : on retient le numéro de la ligne où ils ont été définis. */
	this.addVertex =
	function(vx, vy, vz, ligne) {
		this.vertice.push(
			{x:vx, y:vy, z:vz, ligne:ligne,
			 toString:function(){return "("+this.x+";"+this.y+";"+this.z+"), ligne="+this.ligne},
			 toObj:function(){return "v "+this.x+" "+this.y+" "+this.z+"\n" } });
	}

	/* Lignes.
	 * "numFace" est optionnel et désigne un numéro de face dont cette ligne est une arête.
	 * "ligne" est optionnel et désigne le numéro de la ligne où est la définition.
	 */
	var hashLines = new Array();		// association de clés (2points) à une ligne
	function hashKey(p1, p2) {			// une clé code 2 points
		if(p1 > p2) {
			var dummy = p1;
			p1 = p2;
			p2 = dummy;
		}
		return (p1 << 16) | p2;
	}
// faire les lignes avec des indices relatifs.
	this.addLine =
	function(p1, p2, ligne) {
		var k = hashKey(p1, p2);
		var l = hashLines[k];
		if(l) {
			if(ligne) this.erreur("Le segment défini à la ligne "+ligne+" existe déjà.");
			return l;	// Si la ligne existe déjà on s'en va.
		}
		l = {p1:p1, p2:p2, face:new Array(), ligne:ligne,
			toString:function(){return p1+"->"+p2+", ligne="+ligne},
			attachToFace:function(n){ this.face[n]=true } };
		this.line.push(l);
		hashLines[k] = l;
		return l;
	}

	this.addLineToFace =
	function(p1, p2, numFace) {
		var l = this.addLine(p1, p2);
		l.face[numFace] = true;
	}

	/* Faces */
	this.addFace =
	function(points, ligne) {
		// Vérifie tous les numéros de points
		// et convertit les relatifs en absolus.
		var i, p;
		for(i = 0; i < points.length; i++) {
			p = points[i];
			if(p < 0) p = this.vertice.length + 1 + p;
			if((p < 1) || (p > this.vertice.length)) {
				this.erreur = "Ligne "+ligne+" : numéro de point incorrect ("+points[i]+").";
				return;
			}
			points[i] = p-1;	// On numérote à partir de 0.
		}

		// Crée les lignes du contour de la face.
		i = 0;
		var nbFaces = this.face.length;
		for(i = 1; i < points.length; i++)
			this.addLineToFace(points[i-1], points[i], nbFaces);
		this.addLineToFace(points[i-1], points[0], nbFaces);

		// Crée la face.
		this.face.push({vertice:points, ligne:ligne,
			toString:function(){return "points "+this.vertice+", ligne="+this.ligne} });
	}


	/* Private */
	var i = 0;			// n° du token courant
	// Découpe les lignes, en supprimant les espaces de fin de ligne.
	this.sourceLine = source.split(/[ \t\v\f\u00a0]*\n/);
	var sourceLigne = this.sourceLine;		// 1 fois en private, 1 fois en public, on a besoin des deux.
	var numLigne = -1;
	var tokens = new Array();
	var quelleLigne;

	// Fonction qui prend les tokens 1 par 1 dans la source.
	var EOF = false;
	var nextToken =
	function() {
		// Token suivant.
		if(i < tokens.length)
			return tokens[i++];
		// Ligne suivante.
		numLigne++;
		if(numLigne < sourceLigne.length) {
			tokens = sourceLigne[numLigne].split(/[ \t\v\f\u00a0]+/);
			i = 0;
			quelleLigne = numLigne+1;
			return '\n';
		}
		// Fin du fichier.
		EOF = true;
		return '\n';
	}
	// Renvoie le dernier token ; ne peut pas passer à la ligne précédente.
	var pushBack = function() { i-- }


	// États de l'automate.
	var ETAT_DEBUT_LIGNE = 0;
	var ETAT_POINT = 1;
	var ETAT_LIGNE = 2;
	var ETAT_FACE = 3;
	var ETAT_PASSER_LIGNE = 4;
	var transition = { v:ETAT_POINT, l:ETAT_LIGNE, f:ETAT_FACE };

	// L'automate
	var etat = ETAT_DEBUT_LIGNE;
	var token;
	var nombre;
	boucle:
	while(!EOF)
	{
		token = nextToken();
		switch(etat) {
			case ETAT_DEBUT_LIGNE :
				var etat = transition[token] || ETAT_PASSER_LIGNE;
				break;

			case ETAT_POINT :
				var j = 0;
				nombre = [];
				while((j < 4) && (token != '\n')) {
					nombre[j] = parseFloat(token);
					if(isNaN(nombre[j])) {
						this.erreur = "Ligne "+quelleLigne+" : " + token + " n'est pas un nombre.";
						break boucle;
					}
					token = nextToken();
					j++;
				}
				if(j < 3) {
					this.erreur = "Ligne "+quelleLigne+" : pas assez de coordonnées";
					break boucle;
				}
				// Numérotation des lignes à partir de 0, ici.
				this.addVertex(nombre[0], nombre[1], nombre[2], quelleLigne-2);
				etat = ETAT_DEBUT_LIGNE;
				break;

			case ETAT_LIGNE :
				var j = 0;
				nombre = [];
				while(token != '\n') {
					// On prend l'entier en début de chaîne sans tenir compte du reste.
					nombre[j] = parseInt(token);
					if(isNaN(nombre[j])) {
						this.erreur = "Ligne "+quelleLigne+" : " + token + " n'est pas un nombre.";
						break boucle;
					}
					token = nextToken();
					j++;
				}
				if(j < 2) {
					this.erreur = "Ligne "+quelleLigne+" : pas assez de points pour faire une ligne.";
					break boucle;
				}
				alert("ligne : "+nombre);
				etat = ETAT_DEBUT_LIGNE;
				break;

			case ETAT_FACE :
				var j = 0;
				nombre = [];
				while(token != '\n') {
					// On prend l'entier en début de chaîne sans tenir compte du reste.
					nombre[j] = parseInt(token);
					if(isNaN(nombre[j])) {
						this.erreur = "Ligne "+quelleLigne+" : " + token + " n'est pas un nombre.";
						break boucle;
					}
					token = nextToken();
					j++;
				}
				if(j < 2) {
					this.erreur = "Ligne "+quelleLigne+" : pas assez de points pour faire une face.";
					break boucle;
				}
//				alert("face : "+nombre);
				this.addFace(nombre, quelleLigne-2);
				if(this.erreur) break boucle;
				etat = ETAT_DEBUT_LIGNE;
				break;

			// Si on ne connaît pas, on passe le ligne.
			default:
				while(token != '\n') token = nextToken();
				etat = ETAT_DEBUT_LIGNE;
		}
	}


}

