view Resources/Nexus/js/meco.js @ 62:b798387b085c

added 3DHOP viewer version 4.3
author Sebastien Jodogne <s.jodogne@gmail.com>
date Sat, 15 Jun 2024 16:08:52 +0200
parents 967f947014ac
children
line wrap: on
line source

/*
Nexus
Copyright (c) 2012-2018, Visual Computing Lab, ISTI - CNR
All rights reserved.

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/>.
*/

onmessage = function(job) {
	if(typeof(job.data) == "string") return;
	var node = job.data.node;
	var signature = job.data.signature;
	var patches = job.data.patches;
//	var now =new Date().getTime();

	var size;
	if(!node.buffer) return;
	else size = node.buffer.byteLength;
	var buffer;
	for(var i =0 ; i < 1; i++) {
		var coder = new MeshCoder(signature, node, patches);
		buffer = coder.decode(node.buffer);
	}
	node.buffer = buffer;
	node.owner = job.owner;
//	var elapsed = new Date().getTime() - now;
//	var t = node.nface;
//	console.log("Z Time: " + elapsed + " Size: " + size + " KT/s " + (t/(elapsed)) + " Mbps " + (8*1000*node.buffer.byteLength/elapsed)/(1<<20));
	postMessage(node);
}

//	actually bitstreams expects a little endian uin64 type. convert it to 2 uint32

BitStream = function(array) { 
	this.a = array;
	for(var i = 0; i < array.length; i += 2) {
		var s = array[i];
		array[i] = array[i+1];
		array[i+1] = s;
	}
	this.position = 0;
	this.bitsPending = 0;
};
 
BitStream.prototype = { 
	read: function(bits) {
		var bitBuffer = 0;
		while(bits > 0) {
			var partial;
			var bitsConsumed;
			if (this.bitsPending > 0) {
				var byte = (this.a[this.position - 1] & (0xffffffff >>> (32 - this.bitsPending)))>>>0;
				bitsConsumed = Math.min(this.bitsPending, bits);
				this.bitsPending -= bitsConsumed;
				partial = byte >>> this.bitsPending;
			} else {
				bitsConsumed = Math.min(32, bits);
				this.bitsPending = 32 - bitsConsumed;
				partial = this.a[this.position++] >>> this.bitsPending;
			}
			bits -= bitsConsumed;
			bitBuffer = ((bitBuffer << bitsConsumed) | partial)>>>0;
		}
		return bitBuffer; 
	},
	replace: function(bits, value) {
		//zero last part
		value = (value & (0xffffffff >>> 32 - bits)) >>> 0;
		value = (value | read(bits)) >>> 0;
		return value;
	}
};

Stream = function(buffer) {
	this.data = buffer;
	this.buffer = new Uint8Array(buffer);
	this.pos = 0;
}

Stream.prototype = {
	readChar: function() {
		var c = this.buffer[this.pos++];
		if(c > 127) c -= 256;
		return c;
	},
	readUChar: function() {
		return this.buffer[this.pos++];
	},	
	readInt: function() {
		var c = this.buffer[this.pos + 3]
		c <<= 8;
		c |= this.buffer[this.pos + 2];
		c <<= 8;
		c |= this.buffer[this.pos + 1];
		c <<= 8;
		c |= this.buffer[this.pos + 0];
		this.pos += 4;
		return c;
	},
	readArray: function(n) {
		var a = this.buffer.subarray(this.pos, this.pos+n);
		this.pos += n;
		return a;
	},
	readBitStream:function() {
		var n = this.readInt();
		var pad = this.pos & 0x3;
		if(pad != 0)
			this.pos += 4 - pad;
		var b = new BitStream(new Uint32Array(this.data, this.pos, n*2));
		this.pos += n*8;
		return b;
	}
};

function Tunstall(wordsize, lookup_size) {
	this.wordsize = wordsize? wordsize : 8;
	this.lookup_size = lookup_size? lookup_size : 8;
}

Tunstall.prototype = {
	decompress: function(stream) {
		var nsymbols = stream.readUChar();
		this.probabilities = stream.readArray(nsymbols*2);
		this.createDecodingTables();
		var size = stream.readInt();
		var data = new Uint8Array(size);
		var compressed_size = stream.readInt();
		var compressed_data = stream.readArray(compressed_size);
		if(size)
			this._decompress(compressed_data, compressed_size, data, size);
		return data;
	}, 

	createDecodingTables: function() {
		//read symbol,prob,symbol,prob as uchar.
		//Here probabilities will range from 0 to 0xffff for better precision

		var n_symbols = this.probabilities.length/2;
		if(n_symbols <= 1) return;

		var queues = []; //array of arrays
		var buffer = []; 

		//initialize adding all symbols to queues
		for(var i = 0; i < n_symbols; i++) {
			var symbol = this.probabilities[i*2];
			var s = [(this.probabilities[i*2+1])<<8, buffer.length, 1]; //probability, position in the buffer, length
			queues[i] = [s];
			buffer.push(this.probabilities[i*2]); //symbol
		}
		var dictionary_size = 1<<this.wordsize;
		var n_words = n_symbols;
		var table_length = n_symbols;

		//at each step we grow all queues using the most probable sequence
		while(n_words < dictionary_size - n_symbols +1) {
			//Should use a stack or something to be faster, but we have few symbols
			//find highest probability word
			var best = 0;
			var max_prob = 0;
			for(var i = 0; i < n_symbols; i++) {
				var p = queues[i][0][0]; //front of queue probability.
				if(p > max_prob) {
					best = i;
					max_prob = p;
				}
			}
			var symbol = queues[best][0];
			var pos = buffer.length;
			
			for(var i = 0; i < n_symbols; i++) {
				var sym = this.probabilities[i*2];
				var prob = this.probabilities[i*2+1]<<8;
				var s = [((prob*symbol[0])>>>16), pos, symbol[2]+1]; //combine probabilities, keep track of buffer, keep length of queue

				for(var k  = 0; k < symbol[2]; k++)
					buffer[pos+k] = buffer[symbol[1] + k]; //copy sequence of symbols

				pos += symbol[2];
				buffer[pos++] = sym; //append symbol
				queues[i].push(s);
			}
			table_length += (n_symbols-1)*(symbol[2] + 1) +1; 
			n_words += n_symbols -1;
			queues[best].shift(); //remove first thing
		}

		this.index = new Uint32Array(n_words);
		this.lengths = new Uint32Array(n_words);
		this.table = new Uint8Array(table_length);
		var word = 0;
		var pos = 0;
		for(i = 0; i < queues.length; i++) {
			var queue = queues[i];
			for(var k = 0; k < queue.length; k++) {
				var s = queue[k];
				this.index[word] = pos;
				this.lengths[word] = s[2]; //length
				word++;

				for(var j = 0; j < s[2]; j++)
					this.table[pos + j] = buffer[s[1] + j]; //buffer of offset
				pos += s[2]; //length
			}
		}
	},
	_decompress: function(input, input_size, output, output_size) {
		var input_pos = 0;
		var output_pos = 0;
		if(this.probabilities.length == 2) {
			var symbol = this.probabilities[0];
			for(var i = 0; i < output_size; i++)
				output[i] = symbol;
			return;
		}

		while(input_pos < input_size-1) {
			var symbol = input[input_pos++];
			var start = this.index[symbol];
			var end = start + this.lengths[symbol];
			for(var i = start; i < end; i++) 
				output[output_pos++] = this.table[i];
		}

		//last symbol might override so we check.
		var symbol = input[input_pos];
		var start = this.index[symbol];
		var end = start + output_size - output_pos;
		var length = output_size - output_pos;
		for(var i = start; i < end; i++)
			output[output_pos++] = this.table[i];

		return output;
	}
}

ZPoint = function(h, l) {
	this.lo = l;
	this.hi = h;
}

ZPoint.prototype = {
	copy: function(z) {
		this.lo = z.lo;
		this.hi = z.hi;
	},
	setBit: function(d) {
		if(d < 32)
			this.lo = (this.lo | (1<<d))>>>0;
		else
			this.hi = (this.hi | (1<<(d-32)))>>>0;
	},
	toPoint: function(min, step, buffer, pos) {
		var x = this.morton3(this.lo, this.hi>>>1);
		var y = this.morton3(this.lo>>>1, this.hi>>>2);
		var z = this.morton3((this.lo>>>2 | (this.hi & 0x1)<<30 )>>>0, this.hi>>>3); //first hi bit needs to go into low.

		buffer[pos+0] = (x + min[0])*step;
		buffer[pos+1] = (y + min[1])*step;
		buffer[pos+2] = (z + min[2])*step;
	},
	morton3: function(lo, hi) {
		lo = ( lo                & 0x49249249)>>>0;
        lo = ((lo | (lo >>> 2 )) & 0xc30c30c3)>>>0;
        lo = ((lo | (lo >>> 4 )) & 0x0f00f00f)>>>0;
        lo = ((lo | (lo >>> 8 )) & 0xff0000ff)>>>0;
        lo = ((lo | (lo >>> 16)) & 0x0000ffff)>>>0;

		hi = ( hi                & 0x49249249)>>>0;
        hi = ((hi | (hi >> 2 ))  & 0xc30c30c3)>>>0;
        hi = ((hi | (hi >> 4 ))  & 0x0f00f00f)>>>0;
        hi = ((hi | (hi >> 8 ))  & 0xff0000ff)>>>0;
        hi = ((hi | (hi >> 16))  & 0x0000ffff)>>>0;

		return ((hi<<11) | lo)>>>0;
	}
};

//node is an object with nvert, nface
//patches is an array of offsets in the index, triangle are grouped by those offsets
//signature tells wether mesh has indices, normals, colors, etc. {'colors': true, 'normals':true, 'indices': true }

function MeshCoder(signature, node, patches) {
	this.sig = signature;
	this.node = node;
	this.patches = patches;

	this.last = new Int32Array(this.node.nvert);
	this.last_count = 0;
}

MeshCoder.prototype = {
	//assumes input is an ArrayBuffer
decode: function(input) {
	var t = this;

	t.buffer = new ArrayBuffer(t.node.nvert*(12 + t.sig.texcoords*8 + t.sig.normals*6 + t.sig.colors*4) + t.node.nface*t.sig.indices*6);

	var size = t.node.nvert*12; //float
	t.coords = new Float32Array(t.buffer, 0, t.node.nvert*3);

	if(t.sig.texcoords) {
		t.texcoords = new Float32Array(t.buffer, size, t.node.nvert*2);
		size += t.node.nvert*8; //float
	}
	if(t.sig.normals) {
		t.normals = new Int16Array(t.buffer, size, t.node.nvert*3);
		size += t.node.nvert*6; //short
	}
	if(t.sig.colors) {
		t.colors = new Uint8ClampedArray(t.buffer, size, t.node.nvert*4);
		size += t.node.nvert*4; //chars
	}
	if(t.sig.indices) {
		t.faces = new Uint16Array(t.buffer, size, t.node.nface*3);
		size += t.node.nface*6; //short
	}

	t.stream = new Stream(input);
	
	t.stack = new Float32Array(12); //min0, min1, min2, step, tmin0, tmin1, tstep

	t.stack[3] = t.stream.readInt();
	t.stack[4] = t.stream.readInt();
	t.stack[5] = t.stream.readInt();

	t.coord_q = t.stream.readChar();
	t.coord_bits = t.stream.readChar()*3;

	t.stack[6] = Math.pow(2.0, t.coord_q);

	if(t.sig.texcoords) {
		t.stack[9] = t.stream.readInt();
		t.stack[10] = t.stream.readInt();

		t.texcoord_q = t.stream.readChar();
		t.texcoord_bits = t.stream.readChar()*2;
		t.stack[11] = Math.pow(2.0, t.texcoord_q);
	}

	if(t.sig.indices) {
		t.decodeFaces();

//	var faces = window.performance.now() - start;
//	start += faces;
	} else {
		t.decodeCoordinates();
		
//	var coords = window.performance.now() - start;
//	start += coords;
	}

	if(t.sig.normals)
		t.decodeNormals();
//	var normals = window.performance.now() - start;
//	start += normals;
	if(t.sig.colors)
		t.decodeColors();
//	var colors = window.performance.now() - start;
//	start += colors;
//	console.log("Decode " + (faces + coords + normals + colors) + "ms. C: " + coords + " F: " + faces + " N: " + normals + " C: " + colors);

	return t.buffer;
},

decodeCoordinates: function() {
	var t = this;
	t.min = [t.stack[3], t.stack[4], t.stack[5]];

	var step = Math.pow(2.0, t.coord_q);

	var hi_bits = Math.max(t.coord_bits - 32, 0);
	var lo_bits = Math.min(t.coord_bits, 32);

    var bitstream = t.stream.readBitStream();

	var tunstall = new Tunstall;
	var diffs = tunstall.decompress(t.stream);	

	var hi = bitstream.read(hi_bits);
	var lo = bitstream.read(lo_bits);
	var p = new ZPoint(hi, lo);
	var count = 0;
	p.toPoint(t.min, step, t.coords, count);
	count += 3;
    for(var i = 1; i < t.node.nvert; i++) {
		var d = diffs[i-1];
		p.setBit(d, 1);
		if(d > 32) {
			p.hi = (p.hi & ~((1<<(d-32))-1))>>>0;
			var e = bitstream.read(d - 32);
			p.hi = (p.hi | e)>>>0;
			p.lo = bitstream.read(32);
		} else {

			if(d == 32) {
				p.lo = bitstream.read(d);
			} else {
				var e = bitstream.read(d);
				p.lo = (p.lo & ~((1<<d) -1))>>>0;
				p.lo = (p.lo | e)>>>0;
			}
		}
		p.toPoint(t.min, step, t.coords, count);
		count += 3;
	}
},

decodeFaces: function() {
	if(!this.node.nface) return;
	
	this.vertex_count = 0;
	var start = 0;
	for(var p = 0; p < this.patches.length; p++) {
		var end = this.patches[p];
		this.decodeConnectivity(end - start, start*3);
		start = end;
	}
	//dequantize positions
	var tot = this.node.nvert*3;
	var coords = this.coords;
	var stack = this.stack;
	for(var i = 0; i < tot; ) {
		coords[i] = (coords[i] + stack[3])*stack[6]; i++;
		coords[i] = (coords[i] + stack[4])*stack[6]; i++;
		coords[i] = (coords[i] + stack[5])*stack[6]; i++;
	}
	if(this.sig.texcoords) {
		var t_tot = this.node.nvert*2;
		var t_coords = this.texcoords;
		for(var i = 0; i < tot; ) {
			t_coords[i] = (t_coords[i] + stack[9])*stack[11]; i++;
			t_coords[i] = (t_coords[i] + stack[10])*stack[11]; i++;
		}		
	}
},

decodeNormals: function() {
	var norm_q = this.stream.readChar();

	var dtunstall = new Tunstall;
	var diffs = dtunstall.decompress(this.stream);

	var stunstall = new Tunstall;
	var signs = stunstall.decompress(this.stream);
	var bitstream = this.stream.readBitStream();

	var side = (1<<(16 - norm_q))>>>0;
	var diffcount = 0;
	var signcount = 0;

	if(!this.sig.indices) {
		for(var k = 0; k < 2; k++) {
			var on = 0;
			for(var i = 0; i < this.node.nvert; i++) {
				var d = this.decodeDiff(diffs[diffcount++], bitstream);
				on = on + d;
				this.normals[3*i + k] = on*side;
			}
		}
		for(var i = 0; i < this.node.nvert; i++) {
			var offset = i*3;
			var x = this.normals[offset + 0];
			var y = this.normals[offset + 1];
			var z = 32767.0*32767.0 - x*x - y*y;

        	if(z < 0) z = 0;
        	z = Math.sqrt(z);
			if(z > 32767) z = 32767;
			if(signs[i] == 0)
				z = -z;
			this.normals[offset + 2] = z;
		}
		return;
	}

	var boundary = this.markBoundary();
	this.computeNormals();

	if(this.sig.texcoords) //hack, fixing normals makes it worse actually
		return;

	var stat = 0;
	//get difference between original and predicted
	for(var i = 0; i < this.node.nvert; i++) {
		if(!boundary[i]) continue;
		var offset = i*3;
		var x = (this.normals[offset + 0]/side);
		var y = (this.normals[offset + 1]/side);
		var dx = this.decodeDiff(diffs[diffcount++], bitstream);
		var dy = this.decodeDiff(diffs[diffcount++], bitstream);
		x = (x + dx)*side;
		y = (y + dy)*side;

        var z = 32767.0*32767.0 - x*x - y*y;

        if(z < 0) z = 0;
        z = Math.sqrt(z);
        //sign
        if(z > 32767.0) z = 32767.0;
        var signbit = signs[signcount++];
//        if(this.normals[offset+2] < 0 != signbit)
        if((this.normals[offset+2] < 0 && signbit == 0) || (this.normals[offset+2] > 0 && signbit == 1))
        	z = -z;
		this.normals[offset + 0] = x;
		this.normals[offset + 1] = y;
        this.normals[offset + 2] = z;
	}
},

decodeColors: function() {
	var color_q = [];
	for(var k = 0; k < 4; k++)
		color_q[k] = this.stream.readChar();

	var diffs = [];
	for(var k = 0; k < 4; k++) {
		var tunstall = new Tunstall;;
		diffs[k] = tunstall.decompress(this.stream);
	}
	var bitstream = this.stream.readBitStream();

	var count = 0;
	if(this.sig.indices) {
		for(var i = 0; i < this.node.nvert; i++) {
			var last  = this.last[i]*4;
			var offset = i*4;

			for(var k = 0; k < 4; k++) {
				var c = this.decodeDiff(diffs[k][count], bitstream);

				if(last >= 0)
					c += this.colors[last + k];
				this.colors[offset] = c;
				offset++;
			}
			count++;
		}
	} else {
		for(var k = 0; k < 4; k++)
			this.colors[k] = this.decodeDiff(diffs[k][count], bitstream); 
		count++;

		var offset = 4;
		for(var i = 1; i < this.node.nvert; i++) {
			for(var k = 0; k < 4; k++) {
				var d = this.decodeDiff(diffs[k][count], bitstream); 
				this.colors[offset] = this.colors[offset-4] + d;
				offset ++;
			}
			count++;
		}
	}

	var steps = [];
	for(var k = 0; k < 4; k++)
		steps[k] = (1<<(8 - color_q[k]));

	//convert to rgb
	for(var i = 0; i < this.node.nvert; i++) {
		var offset = i*4;

		var e0 = this.colors[offset + 0] * steps[0];
		var e1 = this.colors[offset + 1] * steps[1];
		var e2 = this.colors[offset + 2] * steps[2];

		this.colors[offset + 0] = (e2 + e0)&0xff;
		this.colors[offset + 1] = e0;
		this.colors[offset + 2] = (e1 + e0)&0xff;
	}
},

//how to determine if a vertex is a boundary without topology:
//for each edge a vertex is in, add or subtract the id of the other vertex depending on order
//for internal vertices sum is zero.
//unless we have strange configurations and a lot of sfiga, zero wont happen. //TODO think about this
markBoundary: function() {
//	var boundary = new Uint8Array(this.node.nvert);
	var count = new Uint32Array(this.node.nvert);

	var offset = 0;
	for(var i = 0; i < this.node.nface; i++) {
		count[this.faces[offset + 0]] += this.faces[offset + 1] - this.faces[offset + 2];
		count[this.faces[offset + 1]] += this.faces[offset + 2] - this.faces[offset + 0];
		count[this.faces[offset + 2]] += this.faces[offset + 0] - this.faces[offset + 1];
		offset += 3;
	}
	return count;
//	for(var i = 0; i < this.node.nvert; i++)
//		if(count[i] != 0)
//			boundary[i] = true;
//	return boundary;
},

norm: function(buffer, a, b, c) { //a b c offsets in the buffer
	var ba0 = buffer[b+0] - buffer[a+0];
	var ba1 = buffer[b+1] - buffer[a+1];
	var ba2 = buffer[b+2] - buffer[a+2];

	var ca0 = buffer[c+0] - buffer[a+0];
	var ca1 = buffer[c+1] - buffer[a+1];
	var ca2 = buffer[c+2] - buffer[a+2];

	var p = [];
	p[0] = ba1*ca2 - ba2*ca1;
	p[1] = ba2*ca0 - ba0*ca2;
	p[2] = ba0*ca1 - ba1*ca0;
	return p;
},

normalize: function(buffer, offset) {
	var x = buffer[offset + 0];
	var y = buffer[offset + 1];
	var z = buffer[offset + 2];
	var n = Math.sqrt(x*x + y*y + z*z);
	if(n > 0) {
		buffer[offset + 0] = x/n;
		buffer[offset + 1] = y/n;
		buffer[offset + 2] = z/n;
	}
},

computeNormals:function() {
	var tmp_normals = new Float32Array(this.node.nvert*3);

	var offset = 0;
	for(var i = 0; i < this.node.nface; i++) {
		var a = 3*this.faces[offset + 0];
		var b = 3*this.faces[offset + 1];
		var c = 3*this.faces[offset + 2];

		var buffer = this.coords;
		var ba0 = buffer[b+0] - buffer[a+0];
		var ba1 = buffer[b+1] - buffer[a+1];
		var ba2 = buffer[b+2] - buffer[a+2];

		var ca0 = buffer[c+0] - buffer[a+0];
		var ca1 = buffer[c+1] - buffer[a+1];
		var ca2 = buffer[c+2] - buffer[a+2];

		var n0 = ba1*ca2 - ba2*ca1;
		var n1 = ba2*ca0 - ba0*ca2;
		var n2 = ba0*ca1 - ba1*ca0;

		tmp_normals[a + 0] += n0;
		tmp_normals[a + 1] += n1;
		tmp_normals[a + 2] += n2;
		tmp_normals[b + 0] += n0;
		tmp_normals[b + 1] += n1;
		tmp_normals[b + 2] += n2;
		tmp_normals[c + 0] += n0;
		tmp_normals[c + 1] += n1;
		tmp_normals[c + 2] += n2;
		offset += 3;
	}

	//normalize
	var offset = 0;
	for(var i = 0; i < this.node.nvert; i++) {
		var x = tmp_normals[offset + 0];
		var y = tmp_normals[offset + 1];
		var z = tmp_normals[offset + 2];
		var n = Math.sqrt(x*x + y*y + z*z);
		if(n > 0) {
			tmp_normals[offset + 0] = x/n;
			tmp_normals[offset + 1] = y/n;
			tmp_normals[offset + 2] = z/n;
		}
		this.normals[offset + 0] = tmp_normals[offset + 0]*32767;
		this.normals[offset + 1] = tmp_normals[offset + 1]*32767;
		this.normals[offset + 2] = tmp_normals[offset + 2]*32767;
		offset += 3;
	}
},

decodeDiff: function(diff, bitstream) {
	var val;
	if(diff == 0) {
		val = 1;
	} else {
		val = 1<<(diff);
		val |= bitstream.read(diff);
	};
	val--; //vall is always >= 1
	if(val & 0x1)
		val = -((val+1)>>1);
	else
		val = val>>1;
	return val;
},

/* an edge is:   uint16_t face, uint16_t side, uint32_t prev, next, bool deleted
I do not want to create millions of small objects, I will use aUint32Array.
Problem is how long, sqrt(nface) we will over blow using nface.
*/

decodeConnectivity: function(length, start) {

	var t = this;
	var ctunstall = new Tunstall;
	var clers = ctunstall.decompress(this.stream);
	var cler_count = 0;

	var dtunstall = new Tunstall;
	var diffs = dtunstall.decompress(this.stream);
	var diff_count = 0;

	var tdiffs;
	var tdiff_count = 0;
	if(t.sig.texcoords) {
		var ttunstall = new Tunstall;
		tdiffs = ttunstall.decompress(this.stream);	
	}

	var bitstream = this.stream.readBitStream(bitstream);

	var current_face = 0;          //keep track of connected component start
	//t.vertex_count = 0;
	var front = new Uint32Array(this.node.nface*18);
	var front_count = 0; //count each integer so it's front_back*5
	function addFront(_v0, _v1, _v2, _prev, _next) {
		front[front_count++] = _v0;
		front[front_count++] = _v1;
		front[front_count++] = _v2;
		front[front_count++] = _prev;
		front[front_count++] = _next;
		front[front_count++] = 0; //deleted
	}
	function _next(t) {
		t++;
		if(t == 3) t = 0;
		return t;
	}
	function _prev(t) {
		t--;
		if(t == -1) t = 2;
		return t;
	}

	var delayed = [];
	var faceorder = [];

	var faces_count = start; //count indices in this.faces array
	var totfaces = length;
//	var estimated = [0, 0, 0]; //no! use stack.
	var stack = this.stack;
	var coords = this.coords;
	var texcoords = this.texcoords;
	var hasTexCoords = t.sig.texcoords;

	while(totfaces > 0) {
		if(!faceorder.length && !delayed.length) {
			if(current_face == this.node.nface) break; //no more faces to encode exiting

			stack[0] = stack[1] = stack[2] = 0;
			stack[7] = stack[8] = 0; //texcoords
			var last_index = -1;
			var index = [];
			for(var k = 0; k < 3; k++) {
				this.last[this.last_count++] = last_index;
				var diff = diffs[diff_count++];
				var tdiff = diff && hasTexCoords? tdiffs[tdiff_count++] : 0;
				var v = this.decodeVertex(bitstream, diff, tdiff);
				index[k] = v; 
				this.faces[faces_count++] = v;
				stack[0] = coords[v*3];
				stack[1] = coords[v*3+1];
				stack[2] = coords[v*3+2]; 
				if(t.sig.texcoords) {
					stack[7] = texcoords[v*2];
					stack[8] = texcoords[v*2+1];
				}
				last_index = v;
			}
			var current_edge = front_count;
			for(var k = 0; k < 3; k++) {
				faceorder.push(front_count);
				front[front_count++] = index[_next(k)];
				front[front_count++] = index[_prev(k)]; 
				front[front_count++] = index[k];
				front[front_count++] = current_edge + _prev(k)*6;
				front[front_count++] = current_edge + _next(k)*6;
				front_count++;
//				addFront(index[_next(k)], index[_prev(k)], index[k], current_edge + _prev(k)*6, current_edge + _next(k)*6);
			}
			current_face++;
			totfaces--;
			continue;
		}
		var f;
		if(faceorder.length) 
			f = faceorder.shift();
		else 
			f = delayed.pop();
		
		var edge_start = f;

		if(front[edge_start + 5]) continue; //deleted
		front[edge_start + 5] = 1; //set edge as deleted anyway

		var c = clers[cler_count++];
		if(c == 4) continue; //BOUNDARY

		var v0   = front[edge_start + 0];
		var v1   = front[edge_start + 1];
		var v2   = front[edge_start + 2];
		var prev = front[edge_start + 3];
		var next = front[edge_start + 4];

		var first_edge = front_count; //points to new edge to be inserted
		var opposite = -1;
		if(c == 0) { //VERTEX
			//predict position based on v0, v1 and v2
			for(var k = 0; k < 3; k++) 
				stack[k] = coords[v0*3 + k] + coords[v1*3 + k] - coords[v2*3 + k];

			if(hasTexCoords)
				for(var k = 0; k < 2; k++)
					stack[7+k] = texcoords[v0*2 + k]  + texcoords[v1*2 + k] - texcoords[v2*2 + k];
			
			var diff = diffs[diff_count++];
			var tdiff = diff && hasTexCoords? tdiffs[tdiff_count++] : 0;
			opposite = this.decodeVertex(bitstream, diff, tdiff);
			if(diff != 0)
				this.last[this.last_count++] = v1;

			front[prev + 4] = first_edge;
			front[next + 3] = first_edge + 6;
			faceorder.unshift(front_count);

			front[front_count++] = v0;
			front[front_count++] = opposite;
			front[front_count++] = v1;
			front[front_count++] = prev;
			front[front_count++] = first_edge+6;
			front_count++; 
//			addFront(v0, opposite, v1, prev, first_edge + 6);

			faceorder.push(front_count);

			front[front_count++] = opposite;
			front[front_count++] = v1;
			front[front_count++] = v0;
			front[front_count++] = first_edge; 
			front[front_count++] = next;
			front_count++; 
//			addFront(opposite, v1, v0, first_edge, next);

		} else if(c == 3) { //END
			front[prev + 5] = 1;
			front[next + 5] = 1;
			front[front[prev + 3] + 4] = front[next + 4];
			front[front[next + 4] + 3] = front[prev + 3];
			opposite = front[prev + 0];

		} else if(c == 1) { //LEFT
			front[prev + 5] = 1; //deleted
			front[front[prev + 3] + 4] = first_edge;
			front[next + 3] = first_edge;
			opposite = front[prev + 0];

			faceorder.unshift(front_count);

			front[front_count++] = opposite;
			front[front_count++] = v1;
			front[front_count++] = v0;
			front[front_count++] = front[prev +3];
			front[front_count++] = next;
			front_count++; 
//			addFront(opposite, v1, v0, front[prev + 3], next);

		} else if(c == 2) { //RIGHT
			front[next + 5] = 1;
			front[front[next + 4] + 3] = first_edge;
			front[prev + 4] = first_edge;
			opposite = front[next + 1];


			faceorder.unshift(front_count);

			front[front_count++] = v0;
			front[front_count++] = opposite;
			front[front_count++] = v1;
			front[front_count++] = prev;
			front[front_count++] = front[next+4];
			front_count++; 
//			addFront(v0, opposite, v1, prev, front[next + 4]);

		} else if(c == 5) { //DELAY
			front[edge_start + 5] = 0;
			delayed.push(edge_start);
			continue;
		}
		this.faces[faces_count++] = v1;
		this.faces[faces_count++] = v0;
		this.faces[faces_count++] = opposite;
		totfaces--;
	}
},
   
decodeVertex: function(bitstream, diff, tdiff) {
	if(diff == 0) 
		return bitstream.read(16);

	var v = this.vertex_count++;

	var max = 1<<(diff-1);

	for(var k = 0; k < 3; k++) {
		var d = bitstream.read(diff) - max;
		this.coords[v*3+k] = this.stack[k] + d; //stack 0-3 is used as extimated
	}
	if(this.sig.texcoords) {
		var tmax = 1<<(tdiff-1);
		for(var k = 0; k < 2; k++) {
			var d = bitstream.read(tdiff) - tmax;
			this.texcoords[v*2+k] = this.stack[7+k] + d; //stack 7-9 is used as extimated
		}
	}
	return v;
},

decodeDiff: function(diff, bitstream) {
	var val;
	if(diff == 0) {
		return 0;
	} 
	val = 1<<diff;
	val += bitstream.read(diff);


	if(val & 0x1) 
		val >>>= 1;
	else 
		val = -(val>>>1);

	return val;
}

};

var tot = 0;