Source: papier-wavefront-utils.js

/*
	This file is a part of "Papier" a paper-crafting tool
	
Copyright (C) 2018  Saint Pierre Thomas ( s1pierro@protonmail.fr )

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
'use strict';

/**
	@function getwavefrontproperties 
	@param {object} o - the mesh to analisys
*/
function getwavefrontproperties (o)
{
	var xmax = obj.vertices[0][0];
	var xmin = obj.vertices[0][0];
	var ymax = obj.vertices[0][1];
	var ymin = obj.vertices[0][1];
	var zmax = obj.vertices[0][2];
	var zmin = obj.vertices[0][2];

	for ( var i = 0 ; i < obj.vertices.length ; i++)
	{
		if ( obj.vertices[i][0] > xmax ) xmax =  obj.vertices[i][0]
		if ( obj.vertices[i][0] < xmin ) xmin =  obj.vertices[i][0]
		if ( obj.vertices[i][1] > ymax ) ymax =  obj.vertices[i][1]
		if ( obj.vertices[i][1] < ymin ) ymin =  obj.vertices[i][1]
		if ( obj.vertices[i][2] > zmax ) zmax =  obj.vertices[i][2]
		if ( obj.vertices[i][2] < zmin ) zmin =  obj.vertices[i][2]
	}

	o.sx = xmax-xmin;
	o.sy = ymax-ymin;
	o.sz = zmax-zmin;
	
}
/**
	@function  parsewavefront
	@param {object} objText - a string containing the mesh defined
	@param {text} id - the id to attibute to the created mesh object
	@returns {object} an object that store mesh define in a format
	suitable to the app
*/
function printWavefront (o)
{
	var po = '# o '+o.nme;
	

	
	var tmp = '\n#frz';
	for ( var i = 0 ; i < o.edges.length ; i++ )
		if (edgestate(o, i) == "freeze" ) tmp+=' '+i;
		
	po = po+tmp;
	tmp = '';
	for ( var i = 0 ; i < o.vertices.length ; i++ )
		tmp+='\nv '+o.vertices[i][0]+' '+o.vertices[i][1]+' '+o.vertices[i][2];
		
	po = po+tmp;
	tmp = '';
	
	for ( var i = 0 ; i < o.triangles.length ; i++ )
		tmp+='\nf '+(o.triangles[i][0]+1)+' '+(o.triangles[i][1]+1)+' '+(o.triangles[i][2]+1);
	po = po+tmp;
	return po;

}
function parsewavefront(objText, id) {
	console.clear();
	var nv = 0;
	var nt = 0;
	var ng = 0;
	var obj = {};

	var vertexMatches = objText.match(/^v( -?\d+(\.\d+)?){3}$/gm);
//	var triMatches = objText.match(/^f( \d+){3}$/gm);
	var triMatches = objText.match(/^f( \d+){3,4}$/gm);
	var gMatches = objText.match(/^f( \d+){3,4}$|^usemtl (.+)$/gm);

	var positionMatches = objText.match(/^position( -?\d+(\.\d+)?){3}$/gm);
	var frzMatches = objText.match(/^#frz( \d+){1,}$/gm);
	var nameMatches = objText.match(/^# o( .+){1,}/gm);
	
	if (nameMatches) {

		var name = nameMatches[0].split(" ");
		name.shift(); 
		var tmpname = "";
		for ( var g = 1 ; g < name.length ; g++ )
			tmpname+=' '+name[g];
		obj.nme = tmpname;
	}
	else obj.nme ='unnamed';
if (vertexMatches) {
		obj.vertices = vertexMatches.map(function(vertex) {
			nv++;
			var vertices = vertex.split(" ");
			vertices.shift(); 
			return Float64Array.from(vertices);;
		});
	}
	if (positionMatches) {

		console.log (positionMatches );
	}
	if (frzMatches) {
			var frozen = frzMatches[0];//.map(function(frz) {

				frozen = frozen.split(" ");
				frozen.shift();	
				obj.prefreeze = Uint16Array.from(frozen);
	//	});
	}


	if (triMatches) {
			obj.triangles = triMatches.map(function(tri) {
				nt++;
				var triangle = tri.split(" ");
				triangle.shift();
//			l(triangles);
			var t = Uint16Array.from(triangle);
			t[0] = t[0]-1;
			t[1] = t[1]-1;
			t[2] = t[2]-1;
			

		//	l(t);
			return t;
		});
	}

	var mat = 'mat';
	if (gMatches) {
		gMatches.map(function(g) {
			var inc = true;
			var gMatch = g.split(" ");
			if (gMatch[0] === 'usemtl')
			{
				gMatch.shift();
				inc = false;
				mat = gMatch[0];
			} else if (gMatch[0] === 'f')
			{	
				obj.triangles[ng].state="visible";
				obj.triangles[ng].mat = mat;
				ng++;
			}
		});
	}
	for (var i = 0 ; i < obj.triangles.length ; i++ )
		obj.triangles[i].id  = id;
	obj.edges = [];
	genEdge (obj );
	rotateWavefront (obj, 21.4, 43.3, 66.3);
/*
	rotateWavefront (obj, getRandomArbitrary(0, 1),
								 getRandomArbitrary(0, 1),
								 getRandomArbitrary(0, 1));	
**/

	syncTriangleSomOrder (obj);
	genNormales(obj);
	obj.nv = nv;
	obj.nt = nt;
	obj.ng = ng;
	
	var xmax = obj.vertices[0][0];
	var xmin = obj.vertices[0][0];
	var ymax = obj.vertices[0][1];
	var ymin = obj.vertices[0][1];
	var zmax = obj.vertices[0][2];
	var zmin = obj.vertices[0][2];

	for ( var i = 0 ; i < obj.vertices.length ; i++)
	{
		if ( obj.vertices[i][0] > xmax ) xmax =  obj.vertices[i][0]
		if ( obj.vertices[i][0] < xmin ) xmin =  obj.vertices[i][0]
		if ( obj.vertices[i][1] > ymax ) ymax =  obj.vertices[i][1]
		if ( obj.vertices[i][1] < ymin ) ymin =  obj.vertices[i][1]
		if ( obj.vertices[i][2] > zmax ) zmax =  obj.vertices[i][2]
		if ( obj.vertices[i][2] < zmin ) zmin =  obj.vertices[i][2]
	}

	var sx = (xmax-xmin);
	var sy = (ymax-ymin);
	var sz = (zmax-zmin);
	var mx = xmax-sx/2;
	var my = ymax-sy/2;
	var mz = zmax-sz/2;

	var height = sx;
	if ( height < sy ) height = sy;
	if ( height < sz ) height = sz;
	obj.height = height*1.5;

	translateWavefront (obj, -mx, -my, -mz);
	obj.sx = xmax-xmin;
	obj.sy = ymax-ymin;
	obj.sz = zmax-zmin;
	$('.sizeX').text((obj.sx/10).toFixed(2));
	$('.sizeY').text((obj.sy/10).toFixed(2));
	$('.sizeZ').text((obj.sz/10).toFixed(2));

	$('#p-title-content').text(obj.nme);

	return obj;
}
window['parsewavefront'] = parsewavefront;

function genEdge (obj )
{
	var edgeOverBurdedError = false;
	
	for ( var i = 0 ; i < 	obj.triangles.length ; i++)
	{
		addEdge (obj, obj.triangles[i][obj.triangles[i].length-1], obj.triangles[i][0], i );		
		for ( var  j = 0 ; j < obj.triangles[i].length-1 ; j++ )
			addEdge (obj, obj.triangles[i][j], obj.triangles[i][j+1], i );
	}
	obj.ne = obj.edges.length;	
	
	for ( var i = 0 ; i < obj.edges.length ; i++)
		if ( obj.edges[i].tri.length > 2 )
			edgeOverBurdedError = true;
	if (edgeOverBurdedError)
		alert ( 'Error, the mesh contains edges that are shared by more than'+
				 ' two triangles. Some pattern flattening will be corrupted');

}
window['genEdge'] = genEdge;
function addEdge (obj, es1, es2, t)
{
	var e = gotEdge (obj, es1, es2 );
	if (e == -1)
	{
		var tmp = { som : [es1, es2], tri : [t], state : "hide", id : obj.edges.length };
		obj.edges.push (tmp);
	}
	else
	{
		if( gotTriangleEdge (obj, es1, es2, t ) == -1 ) obj.edges[e].tri.push (t);
	}
}
window.addEdge = addEdge;
function gotEdge (obj, es1, es2 )
{
	for ( var i = 0 ; i < 	obj.edges.length ; i++)
if ( ( obj.edges[i].som[0] == es1 && obj.edges[i].som[1] == es2 ) |
			  ( obj.edges[i].som[0] == es2 && obj.edges[i].som[1] == es1 ) )
			return i;
	return -1;
}

function gotTriangleEdge (obj, es1, es2, t )
{

		var e = gotEdge (obj, es1, es2 );
		if ( e != -1 )
		{
			for ( var j = 0 ; j < obj.edges[e].tri.length ; j++ )
			{
				if ( obj.edges[e].tri[j] == t )
					return e;
			}
		}
	return -1;
}

function loadWavefrontFromHTLM(object, id) {
	
	var contents = $(object).text();
	var obj = parsewavefront(contents, id);
	return obj;
}
window['loadWavefrontFromHTLM'] = loadWavefrontFromHTLM;

function setWavefrontId(w, id) {

	for ( var j = 0 ; j < w.nt  ; j++)
		w.triangles[ j ].id = id;
}
window['setWavefrontId'] = setWavefrontId;


function genNormales(obj) {
		obj.trianglesnorm = [];
	for (var i = 0; i < obj.triangles.length ; i += 1) {
		var norm = normalisevertex(
										vectorproduct(
											vectfromvertices(
												obj.vertices[obj.triangles[i][0]],
												obj.vertices[obj.triangles[i][2]] ).s,
											vectfromvertices(
												obj.vertices[obj.triangles[i][0]],
												obj.vertices[obj.triangles[i][1]] ).s	
										)
									);
		obj.trianglesnorm.push (norm);
		
	}
}
window['genNormales'] = genNormales;

function translateWavefront (wavefront, x, y, z)
{

	for ( var i = 0 ; i < wavefront.vertices.length ; i ++ )
	{

		wavefront.vertices[i][0] =  parseFloat(wavefront.vertices[i][0])+x;
		wavefront.vertices[i][1] =  parseFloat(wavefront.vertices[i][1])+y;
		wavefront.vertices[i][2] =  parseFloat(wavefront.vertices[i][2])+z;

	}	
}
window['translateWavefront'] = translateWavefront;
function scaleWavefront (wavefront, scale)
{
	fl( 'scaling '+scale);
	for ( var i = 0 ; i < wavefront.vertices.length ; i ++ )
	{

		wavefront.vertices[i][0] =  parseFloat(wavefront.vertices[i][0])*scale;
		wavefront.vertices[i][1] =  parseFloat(wavefront.vertices[i][1])*scale;
		wavefront.vertices[i][2] =  parseFloat(wavefront.vertices[i][2])*scale;

	}	
	var xmax = wavefront.vertices[0][0];
	var xmin = wavefront.vertices[0][0];
	var ymax = wavefront.vertices[0][1];
	var ymin = wavefront.vertices[0][1];
	var zmax = wavefront.vertices[0][2];
	var zmin = wavefront.vertices[0][2];

	for ( var i = 0 ; i < wavefront.vertices.length ; i++)
	{
		if ( wavefront.vertices[i][0] > xmax ) xmax =  wavefront.vertices[i][0]
		if ( wavefront.vertices[i][0] < xmin ) xmin =  wavefront.vertices[i][0]
		if ( wavefront.vertices[i][1] > ymax ) ymax =  wavefront.vertices[i][1]
		if ( wavefront.vertices[i][1] < ymin ) ymin =  wavefront.vertices[i][1]
		if ( wavefront.vertices[i][2] > zmax ) zmax =  wavefront.vertices[i][2]
		if ( wavefront.vertices[i][2] < zmin ) zmin =  wavefront.vertices[i][2]
	}

	wavefront.sx = xmax-xmin;
	wavefront.sy = ymax-ymin;
	wavefront.sz = zmax-zmin;
	$('.sizeX').text((wavefront.sx/10).toFixed(2));
	$('.sizeY').text((wavefront.sy/10).toFixed(2));
	$('.sizeZ').text((wavefront.sz/10).toFixed(2));	
}

function rotateWavefront (wavefront, x, y, z)
{
	var tmpmat = genrmat(x, y, z);
	var tmp = wavefront;
	for (var i = 0; i < wavefront.vertices.length; i++)
		wavefront.vertices[i] = applymat(tmpmat, wavefront.vertices[i]);
	genNormales(wavefront); 
	
}
window['rotateWavefront'] = rotateWavefront;
function readWavefrontFile(evt) {
	
	toggleDview ();
	$('#main-app-dialog-title').text( 'loading ...');//+' body : '+$( window ).width()+' '+$( window ).height() );
	$('#main-app-dialog-info').text( 'please wait');//+' body : '+$( window ).width()+' '+$( window ).height() );

	blankscene ();
	var f = evt.target.files[0];
	if (f)
	{
		var r = new FileReader();
		r.onload = function(e)
		{
			var contents = e.target.result;
			var obj = parsewavefront(e.target.result, 0);
			pobj = $.extend(true, {}, obj);
			patterns = new Patterns(pobj);
			feedscene();
			if (pobj.prefreeze != undefined )
			{
				for ( var i = 0 ; i < pobj.prefreeze.length ; i++)
					setedgestate (pobj, pobj.prefreeze[i], "freeze");
				patterns.rebuild();
			}		
			activeshape1 = -1;
			toggleDview ();
		}
		r.readAsText(f);
	} else {
		alert("Failed to load file");
	}
}
function loadWavefrontExample(file) {

	toggleDview ();
	$('#main-app-dialog-title').text( 'loading');//+' body : '+$( window ).width()+' '+$( window ).height() );

	console.log('Loading ...')
	jQuery.get(file, function(data)
	{
		var obj = parsewavefront(data, 0);
		pobj = $.extend(true, {}, obj);
		//l(wavefront);
		patterns = new Patterns(pobj);
		blankscene ();
		feedscene();
		if (pobj.prefreeze != undefined )
		{
			for ( var i = 0 ; i < pobj.prefreeze.length ; i++)
				setedgestate (pobj, pobj.prefreeze[i], "freeze");
			patterns.rebuild();
		}
		activeshape1 = -1;
		toggleDview ();
	});
}
function getEdgeId (o, s1, s2)
{
	if ( s1 == s2 ) return -2;
	if ( s1 == -1 | -1 == s2 ) return -3;
	for (var i = 0 ; i < o.edges.length ; i++ )
	{
		var ok1 = false;
		var ok2 = false;
		for ( var j = 0 ; j < o.edges[i].som.length ; j++ )
		{
			if ( o.edges[i].som[j] == s1 ) ok1 = true;
			if ( o.edges[i].som[j] == s2 ) ok2 = true;	
		}
		if ( ok1 && ok2 ) return i;
	}
	return -1;
}
function sharededge ( o, triangle_1, triangle_2)
{
	if ( triangle_1 == triangle_2 ) return -2;
	if ( triangle_1 == -1 | -1 == triangle_2 ) return -3;
	for (var i = 0 ; i < o.edges.length ; i++ )
	{
		var ok1 = false;
		var ok2 = false;
		for ( var j = 0 ; j < o.edges[i].tri.length ; j++ )
		{
			if ( o.edges[i].tri[j] == triangle_1 ) ok1 = true;
			if ( o.edges[i].tri[j] == triangle_2 ) ok2 = true;	
		}
		if ( ok1 && ok2 ) return i;
	}
	return -1;
}
function syncTriangleSomOrder (o) 
{

	var td = new ToDo ();
	td.add(0);
	
	var flipCnt = 0;

	var dnl = new processedelements ();

	var cnt = 0;
	while (td.isEmpty() == false)
	{		
			var crtTri = td.next();
		//	fl('\nLoop ('+cnt+') start, triangle : '+crtTri+'\n##################');
			dnl.add (crtTri);
			var tmp = findJoinedTriangles (o, crtTri );
			// we need to elimanate triangles that have already by checked
			for (let i = tmp.length - 1; i >= 0; i--) 			
				if (true == dnl.is(tmp[i]))
					tmp.splice(i, 1);
		//	fl(tmp.length+' triangle to check');
		//	fl(tmp);
					
			for (var i = 0 ; i < tmp.length ; i++)
			{
				//TODO test each triange with crtTri
				//fl('is done '+tmp[i]+' : '+dnl.is(tmp[i]));
				if (dnl.is(tmp[i]) == false )
				{
					if ( doesTrianleNeedFlip (o, tmp[i], crtTri) == true )
					{
						
						flipTriangle (o, tmp[i]);
						flipCnt++;
					}
					td.add(tmp[i]);
					dnl.add (tmp[i]);
				}
			}
			td.done(crtTri);
			cnt++;
	}
	if ( cnt != o.triangles.length ) alert('WARNING,the mesh is not monobloc, flattening errors could occur.')
//	fl (dnl.pe);
	//fl(flipCnt+' triangle(s) fliped')
	
	//if ( dnl.is(tmp[i]) == false ) td.add(tmp[i]);	
}

function doesTrianleNeedFlip (o, t, ref) 
{
//TODO Emergency, Very important.

	var s1 = o.edges[sharededge ( o, t, ref )].som[0];
	var s2 = o.edges[sharededge ( o, t, ref )].som[1];
	
	var st1 = -1;
	var st2 = -1;
	
	if ( o.triangles[t][0] == s1 && o.triangles[t][1] == s2 ) st1 = 1;
	if ( o.triangles[t][1] == s1 && o.triangles[t][2] == s2 ) st1 = 1;
	if ( o.triangles[t][2] == s1 && o.triangles[t][0] == s2 ) st1 = 1;
						
	if ( o.triangles[ref][0] == s1 && o.triangles[ref][1] == s2 ) st2 = 1;
	if ( o.triangles[ref][1] == s1 && o.triangles[ref][2] == s2 ) st2 = 1;
	if ( o.triangles[ref][2] == s1 && o.triangles[ref][0] == s2 ) st2 = 1;
	
	if (st1 == st2 )
		return true;
	return false;
}
function findJoinedTriangles (o, t)
{
	var tmp = TRIANGLEgetedges (o, t);
	var tmp2 = [];
	for ( var i = 0 ; i < tmp.length ; i ++ )
		for ( var j = 0 ; j < o.edges[tmp[i]].tri.length ; j++ )
			if ( o.edges[tmp[i]].tri[j] != t )
				tmp2.push(o.edges[tmp[i]].tri[j])
	return tmp2;
}
function flipTriangle (o, t) 
{
	var tmp = $.extend(true, {}, o.triangles[t]);
	o.triangles[t][0] = tmp[1];
	o.triangles[t][1] = tmp[0];
}

function TRIANGLEgetedges (o, t) 
{
	var tmp = [];
	for( var i = 0 ; i < o.edges.length ; i++ )
	{
		if ( JUNCTIONgottriangle (o.edges[i], t) != -1 ) tmp.push(i);
	}
	return tmp;
}
function JUNCTIONgottriangle (j, t)
{
	for( var i = 0 ; i < j.tri.length ; i++ )
		if ( j.tri[i] == t ) return i;
	return -1;
}
function edgestate (o, e)
{
	return o.edges[e].state;
}
function setedgestate (o, e, s)
{	
	scene.children[(2+e)].visible = true;
	o.edges[e].state = s;
	if( s == "visible")
	scene.children[(2+e)].material = materialFrontier;
	if( s == "freeze")
	{
		scene.children[(2+e)].visible = false;
	}
	if( s == "highlight")
	scene.children[(2+e)].material = materialFrontier;
	if( s == "hide")
	{
		scene.children[(2+e)].visible = false;
	}
}
function shapestate (o, t)
{
	l('## shape('+t+').state : '+o.triangles[t].state);
	return o.triangles[t].state;
}
function setshapestate (o, t, s)
{
	if ( s == undefined )
	fl('-#- ERROR unable to set shape ( '+t+' ) state to '+s+' leaving function "setshapestate"', 'lr');

	o.triangles[t].state = s;
	if( s == "visible")
	{
		scene.children[(2+t+o.ne)].material = materialVisible;
		scene.children[(2+t+o.ne)].visible = true;
	}
	if( s == "solid")
	{
		scene.children[(2+t+o.ne)].material = materialSolid;
		scene.children[(2+t+o.ne)].visible = true;
	}
	if( s == "highlight")
	{
		scene.children[(2+t+o.ne)].visible = true;
		scene.children[(2+t+o.ne)].material = materialHighlighted;
	}
}
function download(filename, text) {
  var element = document.createElement('a');
  element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
  element.setAttribute('download', filename);

  element.style.display = 'none';
  document.body.appendChild(element);

  element.click();

  document.body.removeChild(element);
}
/*
	this file is a part of 
	papier 0.4.1
		
	Author : Saint Pierre Thomas
	If you got interest in such kind of app
	feel free to contact me at spierro@protonmail.fr
	Licenced under the termes of the GNU GPL v3
*/