Browse Source

Replaces Catmull-Clark Algorithm with Loop Scheme for Subdivision Modifier

- This provides better results for subdivisions of triangles only
- Subdivision examples looks much better now
- Fixes #4455, #3663
- Known issues: doesnt handle UVs, sharp, and semi-sharp edges for now
Joshua Koo 11 years ago
parent
commit
c289f7118f
1 changed files with 196 additions and 617 deletions
  1. 196 617
      examples/js/modifiers/SubdivisionModifier.js

+ 196 - 617
examples/js/modifiers/SubdivisionModifier.js

@@ -2,102 +2,23 @@
  *	@author zz85 / http://twitter.com/blurspline / http://www.lab4games.net/zz85/blog 
  *
  *	Subdivision Geometry Modifier 
- *		using Catmull-Clark Subdivision Surfaces
- *		for creating smooth geometry meshes
+ *		using Loop Subdivision Scheme
  *
- *	Note: a modifier modifies vertices and faces of geometry,
- *		so use geometry.clone() if original geometry needs to be retained
- * 
- *	Readings: 
- *		http://en.wikipedia.org/wiki/Catmull%E2%80%93Clark_subdivision_surface
- *		http://www.rorydriscoll.com/2008/08/01/catmull-clark-subdivision-the-basics/
- *		http://xrt.wikidot.com/blog:31
- *		"Subdivision Surfaces in Character Animation"
+ *	References:
+ *		http://graphics.stanford.edu/~mdfisher/subdivision.html
+ *		http://www.holmes3d.net/graphics/subdivision/
+ *		http://www.cs.rutgers.edu/~decarlo/readings/subdiv-sg00c.pdf
  *
- *		(on boundary edges)
- *		http://rosettacode.org/wiki/Catmull%E2%80%93Clark_subdivision_surface
- *		https://graphics.stanford.edu/wikis/cs148-09-summer/Assignment3Description
+ *	Known Issues:
+ *		- currently doesn't handle UVs
+ *		- currently doesn't handle "Sharp Edges"
  *
- *	Supports:
- *		Closed and Open geometries.
- *
- *	TODO:
- *		crease vertex and "semi-sharp" features
- *		selective subdivision
  */
 
-THREE.Face4Stub = function ( a, b, c, d, normal, color, materialIndex ) {
-
-	this.a = a;
-	this.b = b;
-	this.c = c;
-	this.d = d;
-
-	this.normal = normal instanceof THREE.Vector3 ? normal : new THREE.Vector3();
-	this.vertexNormals = normal instanceof Array ? normal : [ ];
-
-	this.color = color instanceof THREE.Color ? color : new THREE.Color();
-	this.vertexColors = color instanceof Array ? color : [];
-
-	this.vertexTangents = [];
-
-	this.materialIndex = materialIndex !== undefined ? materialIndex : 0;
-
-	this.centroid = new THREE.Vector3();
-
-};
-
-
-THREE.GeometryUtils.convertFace4s = function(geometry) {
-
-	// return geometry;
-
-	var faces = geometry.faces;
-	var faceVertexUvs = geometry.faceVertexUvs[0];
-
-	var newfaces = [];
-	var newfaceVertexUvs = [];
-
-	var f, fl, face, uv;
-
-	for (f=0, fl=faces.length; f < fl; f++) {
-
-		face = faces[f];
-		uv = faceVertexUvs[f];
-
-		if ( face instanceof THREE.Face3 ) {
-			
-			newfaces.push(face);
-			if (uv) newfaceVertexUvs.push(uv);
-
-		} else {
-
-			newfaces.push( new THREE.Face3( face.a, face.b, face.c, null, face.color, face.materialIndex) );
-			newfaces.push( new THREE.Face3( face.d, face.a, face.c, null, face.color, face.materialIndex) );
-
-
-			if (uv) newfaceVertexUvs.push([uv[0], uv[1], uv[2]]);
-			if (uv) newfaceVertexUvs.push([uv[3], uv[0], uv[2]]);
-
-		}
-
-	}
-
-	geometry.faces = newfaces;
-	geometry.faceVertexUvs = [newfaceVertexUvs];
-
-}
-
-
 THREE.SubdivisionModifier = function ( subdivisions ) {
 
 	this.subdivisions = (subdivisions === undefined ) ? 1 : subdivisions;
 
-	// Settings
-	this.useOldVertexColors = false;
-	this.supportUVs = true;
-	this.debug = false;
-
 };
 
 // Applies the "modify" pattern
@@ -109,655 +30,313 @@ THREE.SubdivisionModifier.prototype.modify = function ( geometry ) {
 		this.smooth( geometry );
 	}
 
-	THREE.GeometryUtils.convertFace4s( geometry );
 	delete geometry.__tmpVertices;
 	geometry.computeCentroids();
 	geometry.computeFaceNormals();
 	geometry.computeVertexNormals();
 
-
-};
-
-/// REFACTORING THIS OUT
-
-THREE.GeometryUtils.orderedKey = function ( a, b ) {
-
-	return Math.min( a, b ) + "_" + Math.max( a, b );
-
 };
 
+(function() {
 
-// Returns a hashmap - of { edge_key: face_index }
-THREE.GeometryUtils.computeEdgeFaces = function ( geometry ) {
-
-	var i, il, v1, v2, j, k,
-		face, faceIndices, faceIndex,
-		edge,
-		hash,
-		edgeFaceMap = {};
-
-	var orderedKey = THREE.GeometryUtils.orderedKey;
-
-	function mapEdgeHash( hash, i ) {
-
-		if ( edgeFaceMap[ hash ] === undefined ) {
-
-			edgeFaceMap[ hash ] = [];
-
-		}
-
-		edgeFaceMap[ hash ].push( i );
-	}
-
-
-	// construct vertex -> face map
-
-	for( i = 0, il = geometry.faces.length; i < il; i ++ ) {
-
-		face = geometry.faces[ i ];
+	// Some constants
+	var WARNINGS = !true; // Set to true for development
+	var ABC = ['a', 'b', 'c'];
+	
 
-		if ( face instanceof THREE.Face3 ) {
+	function getEdge( a, b, map ) {
 
-			hash = orderedKey( face.a, face.b );
-			mapEdgeHash( hash, i );
+		var vertexIndexA = Math.min( a, b );
+		var vertexIndexB = Math.max( a, b );
 
-			hash = orderedKey( face.b, face.c );
-			mapEdgeHash( hash, i );
+		var key = vertexIndexA + "_" + vertexIndexB;
 
-			hash = orderedKey( face.c, face.a );
-			mapEdgeHash( hash, i );
-
-		} else if ( face instanceof THREE.Face4Stub ) {
-
-			hash = orderedKey( face.a, face.b );
-			mapEdgeHash( hash, i );
-
-			hash = orderedKey( face.b, face.c );
-			mapEdgeHash( hash, i );
-
-			hash = orderedKey( face.c, face.d );
-			mapEdgeHash( hash, i );
-
-			hash = orderedKey( face.d, face.a );
-			mapEdgeHash( hash, i );
-
-		}
-
-	}
-
-	// extract faces
-
-	// var edges = [];
-	// 
-	// var numOfEdges = 0;
-	// for (i in edgeFaceMap) {
-	// 	numOfEdges++;
-	//
-	// 	edge = edgeFaceMap[i];
-	// 	edges.push(edge);
-	//
-	// }
-
-	//debug('edgeFaceMap', edgeFaceMap, 'geometry.edges',geometry.edges, 'numOfEdges', numOfEdges);
-
-	return edgeFaceMap;
-
-}
-
-/////////////////////////////
-
-// Performs an iteration of Catmull-Clark Subdivision
-THREE.SubdivisionModifier.prototype.smooth = function ( oldGeometry ) {
-
-	//debug( 'running smooth' );
-
-	// New set of vertices, faces and uvs
-	var newVertices = [], newFaces = [], newUVs = [];
-
-	function v( x, y, z ) {
-		newVertices.push( new THREE.Vector3( x, y, z ) );
-	}
-
-	var scope = this;
-	var orderedKey = THREE.GeometryUtils.orderedKey;
-	var computeEdgeFaces = THREE.GeometryUtils.computeEdgeFaces;
-
-	function assert() {
-
-		if (scope.debug && console && console.assert) console.assert.apply(console, arguments);
-
-	}
-
-	function debug() {
-
-		if (scope.debug) console.log.apply(console, arguments);
+		return map[ key ];
 
 	}
 
-	function warn() {
-
-		if (console)
-		console.log.apply(console, arguments);
-
-	}
-
-	function f4( a, b, c, d, oldFace, orders, facei ) {
-
-		// TODO move vertex selection over here!
-
-		var newFace = new THREE.Face4Stub( a, b, c, d, null, oldFace.color, oldFace.materialIndex );
-
-		if (scope.useOldVertexColors) {
-
-			newFace.vertexColors = []; 
-
-			var color, tmpColor, order;
-
-			for (var i=0;i<4;i++) {
-
-				order = orders[i];
-
-				color = new THREE.Color(),
-				color.setRGB(0,0,0);
 
-				for (var j=0, jl=0; j<order.length;j++) {
-					tmpColor = oldFace.vertexColors[order[j]-1];
-					color.r += tmpColor.r;
-					color.g += tmpColor.g;
-					color.b += tmpColor.b;
-				}
-
-				color.r /= order.length;
-				color.g /= order.length;
-				color.b /= order.length;
-
-				newFace.vertexColors[i] = color;
-
-			}
-
-		}
+	function processEdge( a, b, vertices, map, face, metaVertices ) {
 
-		newFaces.push( newFace );
-
-		if (scope.supportUVs) {
-
-			var aUv = [
-				getUV(a, ''),
-				getUV(b, facei),
-				getUV(c, facei),
-				getUV(d, facei)
-			];
-
-			if (!aUv[0]) debug('a :( ', a+':'+facei);
-			else if (!aUv[1]) debug('b :( ', b+':'+facei);
-			else if (!aUv[2]) debug('c :( ', c+':'+facei);
-			else if (!aUv[3]) debug('d :( ', d+':'+facei);
-			else 
-				newUVs.push( aUv );
-
-		}
-	}
+		var vertexIndexA = Math.min( a, b );
+		var vertexIndexB = Math.max( a, b );
 
-	var originalPoints = oldGeometry.vertices;
-	var originalFaces = oldGeometry.faces;
-	var originalVerticesLength = originalPoints.length;
+		var key = vertexIndexA + "_" + vertexIndexB;
 
-	var newPoints = originalPoints.concat(); // New set of vertices to work on
+		var edge;
 
-	var facePoints = [], // these are new points on exisiting faces
-		edgePoints = {}; // these are new points on exisiting edges
+		if ( key in map ) {
 
-	var sharpEdges = {}, sharpVertices = []; // Mark edges and vertices to prevent smoothening on them
-	// TODO: handle this correctly.
+			edge = map[ key ];
 
-	var uvForVertices = {}; // Stored in {vertex}:{old face} format
-
-
-	function debugCoreStuff() {
-
-		console.log('facePoints', facePoints, 'edgePoints', edgePoints);
-		console.log('edgeFaceMap', edgeFaceMap, 'vertexEdgeMap', vertexEdgeMap);
-
-	}
-
-	function getUV(vertexNo, oldFaceNo) {
-		var j,jl;
-
-		var key = vertexNo+':'+oldFaceNo;
-		var theUV = uvForVertices[key];
-
-		if (!theUV) {
-			if (vertexNo>=originalVerticesLength && vertexNo < (originalVerticesLength + originalFaces.length)) {
-				debug('face pt');
-			} else {
-				debug('edge pt');
-			}
-
-			warn('warning, UV not found for', key);
-
-			return null;
-		}
-
-		return theUV;
- 
-		// Original faces -> Vertex Nos. 
-		// new Facepoint -> Vertex Nos.
-		// edge Points
-
-	}
-
-	function addUV(vertexNo, oldFaceNo, value) {
-
-		var key = vertexNo+':'+oldFaceNo;
-		if (!(key in uvForVertices)) {
-			uvForVertices[key] = value;
 		} else {
-			warn('dup vertexNo', vertexNo, 'oldFaceNo', oldFaceNo, 'value', value, 'key', key, uvForVertices[key]);
-		}
-	}
-
-	// Step 1
-	//	For each face, add a face point
-	//	Set each face point to be the centroid of all original points for the respective face.
-	// debug(oldGeometry);
-	var i, il, j, jl, face;
-
-	// For Uvs
-	var uvs = oldGeometry.faceVertexUvs[0];
-	var abcd = 'abcd', vertice;
-
-	debug('originalFaces, uvs, originalVerticesLength', originalFaces.length, uvs.length, originalVerticesLength);
+			
+			var vertexA = vertices[ vertexIndexA ];
+			var vertexB = vertices[ vertexIndexB ];
 
-	if (scope.supportUVs)
+			edge = {
 
-	for (i=0, il = uvs.length; i<il; i++ ) {
+				a: vertexA, // pointer reference
+				b: vertexB,
+				newEdge: null,
+				// aIndex: a, // numbered reference
+				// bIndex: b,
+				faces: [] // pointers to face
 
-		for (j=0,jl=uvs[i].length;j<jl;j++) {
+			};
 
-			vertice = originalFaces[i][abcd.charAt(j)];
-			addUV(vertice, i, uvs[i][j]);
+			map[ key ] = edge;
 
 		}
 
-	}
+		edge.faces.push( face );
 
-	if (uvs.length == 0) scope.supportUVs = false;
+		metaVertices[ a ].edges.push( edge );
+		metaVertices[ b ].edges.push( edge );
+		
 
-	// Additional UVs check, if we index original 
-	var uvCount = 0;
-	for (var u in uvForVertices) {
-		uvCount++;
-	}
-	if (!uvCount) {
-		scope.supportUVs = false;
-		debug('no uvs');
 	}
 
-	var avgUv ;
-
-	for (i=0, il = originalFaces.length; i<il ;i++) {
+	function generateLookups( vertices, faces, metaVertices, edges ) {
 
-		face = originalFaces[ i ];
-		facePoints.push( face.centroid );
-		newPoints.push( face.centroid );
-
-		if (!scope.supportUVs) continue;
-
-		// Prepare subdivided uv
-
-		avgUv = new THREE.Vector2();
-
-		if ( face instanceof THREE.Face3 ) {
-
-			avgUv.x = getUV( face.a, i ).x + getUV( face.b, i ).x + getUV( face.c, i ).x;
-			avgUv.y = getUV( face.a, i ).y + getUV( face.b, i ).y + getUV( face.c, i ).y;
-			avgUv.x /= 3;
-			avgUv.y /= 3;
-
-		} else if ( face instanceof THREE.Face4Stub ) {
-
-			avgUv.x = getUV( face.a, i ).x + getUV( face.b, i ).x + getUV( face.c, i ).x + getUV( face.d, i ).x;
-			avgUv.y = getUV( face.a, i ).y + getUV( face.b, i ).y + getUV( face.c, i ).y + getUV( face.d, i ).y;
-			avgUv.x /= 4;
-			avgUv.y /= 4;
+		var i, il, face, edge;
 
+		for (i=0, il=vertices.length; i < il; i++) {
+			metaVertices[ i ] = { edges: [] };
 		}
+		
+		for (i=0, il=faces.length; i < il; i++) {
+			face = faces[i];
 
-		addUV(originalVerticesLength + i, '', avgUv);
-
-	}
-
-	// Step 2
-	//	For each edge, add an edge point.
-	//	Set each edge point to be the average of the two neighbouring face points and its two original endpoints.
-
-	var edgeFaceMap = computeEdgeFaces ( oldGeometry ); // Edge Hash -> Faces Index  eg { edge_key: [face_index, face_index2 ]}
-	var edge, faceIndexA, faceIndexB, avg;
-
-	// debug('edgeFaceMap', edgeFaceMap);
-
-	var edgeCount = 0;
-
-	var edgeVertex, edgeVertexA, edgeVertexB;
-
-	////
-
-	var vertexEdgeMap = {}; // Gives edges connecting from each vertex
-	var vertexFaceMap = {}; // Gives faces connecting from each vertex
-
-	function addVertexEdgeMap(vertex, edge) {
-
-		if (vertexEdgeMap[vertex]===undefined) {
-
-			vertexEdgeMap[vertex] = [];
+			processEdge( face.a, face.b, vertices, edges, face, metaVertices );
+			processEdge( face.b, face.c, vertices, edges, face, metaVertices );
+			processEdge( face.c, face.a, vertices, edges, face, metaVertices );
 
 		}
-
-		vertexEdgeMap[vertex].push(edge);
 	}
 
-	function addVertexFaceMap(vertex, face, edge) {
+	function newFace( newFaces, a, b, c ) {
 
-		if (vertexFaceMap[vertex]===undefined) {
+		newFaces.push( new THREE.Face3( a, b, c ) );
 
-			vertexFaceMap[vertex] = {};
-
-		}
-
-		vertexFaceMap[vertex][face] = edge;
-		// vertexFaceMap[vertex][face] = null;
 	}
 
-	// Prepares vertexEdgeMap and vertexFaceMap
-	for (i in edgeFaceMap) { // This is for every edge
-		edge = edgeFaceMap[i];
 
-		edgeVertex = i.split('_');
-		edgeVertexA = edgeVertex[0];
-		edgeVertexB = edgeVertex[1];
+	/////////////////////////////
 
-		// Maps an edgeVertex to connecting edges
-		addVertexEdgeMap(edgeVertexA, [edgeVertexA, edgeVertexB] );
-		addVertexEdgeMap(edgeVertexB, [edgeVertexA, edgeVertexB] );
+	// Performs one iteration of Subdivision
+	THREE.SubdivisionModifier.prototype.smooth = function ( geometry ) {
 
-		for (j=0,jl=edge.length;j<jl;j++) {
+		var tmp = new THREE.Vector3();
 
-			face = edge[j];
-			addVertexFaceMap(edgeVertexA, face, i);
-			addVertexFaceMap(edgeVertexB, face, i);
+		var oldVertices, oldFaces;
+		var newVertices, newFaces; // newUVs = [];
 
-		}
+		var n, l, i, il, j, k;
+		var metaVertices, sourceEdges;
 
-		// {edge vertex: { face1: edge_key, face2: edge_key.. } }
+		// new stuff.
+		var sourceEdges, newEdgeVertices, newSourceVertices
 
-		// this thing is fishy right now.
-		if (edge.length < 2) {
+		oldVertices = geometry.vertices; // { x, y, z}
+		oldFaces = geometry.faces; // { a: oldVertex1, b: oldVertex2, c: oldVertex3 }
 
-			// edge is "sharp";
-			sharpEdges[i] = true;
-			sharpVertices[edgeVertexA] = true;
-			sharpVertices[edgeVertexB] = true;
+		/******************************************************
+		 *
+		 * Step 0: Preprocess Geometry to Generate edges Lookup
+		 *
+		 *******************************************************/
 
-		}
+		metaVertices = new Array( oldVertices.length );
+		sourceEdges = {}; // Edge => { oldVertex1, oldVertex2, faces[]  }
 
-	}
+		generateLookups(oldVertices, oldFaces, metaVertices, sourceEdges);
 
-	for (i in edgeFaceMap) {
 
-		edge = edgeFaceMap[i];
+		/******************************************************
+		 *
+		 *	Step 1. 
+		 *	For each edge, create a new Edge Vertex,
+		 *	then position it.
+		 *
+		 *******************************************************/
 
-		faceIndexA = edge[0]; // face index a
-		faceIndexB = edge[1]; // face index b
+		newEdgeVertices = [];
+		var other, currentEdge, newEdge, face;
+		var edgeVertexWeight, adjacentVertexWeight, connectedFaces;
 
-		edgeVertex = i.split('_');
-		edgeVertexA = edgeVertex[0];
-		edgeVertexB = edgeVertex[1];
+		for (i in sourceEdges) {
+			currentEdge = sourceEdges[i];
+			newEdge = new THREE.Vector3();
 
-		avg = new THREE.Vector3();
+			edgeVertexWeight = 3 / 8;
+			adjacentVertexWeight = 1 / 8;
 
-		//debug(i, faceIndexB,facePoints[faceIndexB]);
+			connectedFaces = currentEdge.faces.length;
 
-		assert(edge.length > 0, 'an edge without faces?!');
+			// check how many linked faces. 2 should be correct.
+			if (connectedFaces != 2) {
 
-		if (edge.length==1) {
+				// if length is not 2, handle condition
+				edgeVertexWeight = 0.5;
+				adjacentVertexWeight = 0;
 
-			avg.add( originalPoints[ edgeVertexA ] );
-			avg.add( originalPoints[ edgeVertexB ] );
-			avg.multiplyScalar( 0.5 );
-
-			sharpVertices[newPoints.length] = true;
-
-		} else {
-
-			avg.add( facePoints[ faceIndexA ] );
-			avg.add( facePoints[ faceIndexB ] );
-
-			avg.add( originalPoints[ edgeVertexA ] );
-			avg.add( originalPoints[ edgeVertexB ] );
-
-			avg.multiplyScalar( 0.25 );
+				if (connectedFaces != 1 ) {
+					
+					if (WARNINGS) console.warn('Subdivision Modifier: Number of connected faces != 2, is: ', connectedFaces, currentEdge);
+			
+				}
 
-		}
+			}
 
-		edgePoints[i] = originalVerticesLength + originalFaces.length + edgeCount;
+			newEdge.addVectors( currentEdge.a, currentEdge.b ).multiplyScalar( edgeVertexWeight );
 
-		newPoints.push( avg );
+			tmp.set( 0, 0, 0 );
 
-		edgeCount ++;
+			for (j = 0; j < connectedFaces; j++ ) {
 
-		if (!scope.supportUVs) {
-			continue;
-		}
+				face = currentEdge.faces[ j ];
+				
+				for (k = 0; k < 3; k++) {
 
-		// Prepare subdivided uv
+					other = oldVertices[ face[ ABC[k] ] ];
+					if (other !== currentEdge.a && other !== currentEdge.b ) break;
 
-		avgUv = new THREE.Vector2();
+				}
 
-		avgUv.x = getUV(edgeVertexA, faceIndexA).x + getUV(edgeVertexB, faceIndexA).x;
-		avgUv.y = getUV(edgeVertexA, faceIndexA).y + getUV(edgeVertexB, faceIndexA).y;
-		avgUv.x /= 2;
-		avgUv.y /= 2;
+				tmp.add( other );
 
-		addUV(edgePoints[i], faceIndexA, avgUv);
+			}
 
-		if (edge.length>=2) {
-			assert(edge.length == 2, 'did we plan for more than 2 edges?');
-			avgUv = new THREE.Vector2();
+			tmp.multiplyScalar( adjacentVertexWeight );
+			newEdge.add( tmp );
 
-			avgUv.x = getUV(edgeVertexA, faceIndexB).x + getUV(edgeVertexB, faceIndexB).x;
-			avgUv.y = getUV(edgeVertexA, faceIndexB).y + getUV(edgeVertexB, faceIndexB).y;
-			avgUv.x /= 2;
-			avgUv.y /= 2;
+			currentEdge.newEdge = newEdgeVertices.length;
+			newEdgeVertices.push(newEdge);
 
-			addUV(edgePoints[i], faceIndexB, avgUv);
+			// console.log(currentEdge, newEdge);
 		}
 
-	}
-
-	debug('-- Step 2 done');
-
-	// Step 3
-	//	For each face point, add an edge for every edge of the face, 
-	//	connecting the face point to each edge point for the face.
-
-	var facePt, currentVerticeIndex;
-
-	var hashAB, hashBC, hashCD, hashDA, hashCA;
-
-	var abc123 = ['123', '12', '2', '23'];
-	var bca123 = ['123', '23', '3', '31'];
-	var cab123 = ['123', '31', '1', '12'];
-	var abc1234 = ['1234', '12', '2', '23'];
-	var bcd1234 = ['1234', '23', '3', '34'];
-	var cda1234 = ['1234', '34', '4', '41'];
-	var dab1234 = ['1234', '41', '1', '12'];
-
-	for (i=0, il = facePoints.length; i<il ;i++) { // for every face
-		facePt = facePoints[i];
-		face = originalFaces[i];
-		currentVerticeIndex = originalVerticesLength+ i;
+		/******************************************************
+		 *
+		 *	Step 2. 
+		 *	Reposition each source vertices.
+		 *
+		 *******************************************************/
 
-		if ( face instanceof THREE.Face3 ) {
+		var beta, sourceVertexWeight, connectingVertexWeight;
+		var connectingEdge, connectingEdges, oldVertex, newSourceVertex;
+		newSourceVertices = [];
 
-			// create 3 face4s
+		for ( i=0, il=oldVertices.length; i < il; i++ ) {
 
-			hashAB = orderedKey( face.a, face.b );
-			hashBC = orderedKey( face.b, face.c );
-			hashCA = orderedKey( face.c, face.a );
+			oldVertex = oldVertices[i];
 
-			f4( currentVerticeIndex, edgePoints[hashAB], face.b, edgePoints[hashBC], face, abc123, i );
-			f4( currentVerticeIndex, edgePoints[hashBC], face.c, edgePoints[hashCA], face, bca123, i );
-			f4( currentVerticeIndex, edgePoints[hashCA], face.a, edgePoints[hashAB], face, cab123, i );
+			// find all connecting edges (using lookupTable)
+			connectingEdges = metaVertices[ i ].edges;
+			n = connectingEdges.length;
+			beta;
 
-		} else if ( face instanceof THREE.Face4Stub ) {
+			if (n == 3) {
 
-			// create 4 face4s
+				beta = 3 / 16;
 
-			hashAB = orderedKey( face.a, face.b );
-			hashBC = orderedKey( face.b, face.c );
-			hashCD = orderedKey( face.c, face.d );
-			hashDA = orderedKey( face.d, face.a );
+			} else if (n > 3) {
 
-			f4( currentVerticeIndex, edgePoints[hashAB], face.b, edgePoints[hashBC], face, abc1234, i );
-			f4( currentVerticeIndex, edgePoints[hashBC], face.c, edgePoints[hashCD], face, bcd1234, i );
-			f4( currentVerticeIndex, edgePoints[hashCD], face.d, edgePoints[hashDA], face, cda1234, i );
-			f4( currentVerticeIndex, edgePoints[hashDA], face.a, edgePoints[hashAB], face, dab1234, i );
-
-
-		} else {
-
-			debug('face should be a face!', face);
-
-		}
-
-	}
-
-	newVertices = newPoints;
-
-	// Step 4
-
-	//	For each original point P, 
-	//		take the average F of all n face points for faces touching P, 
-	//		and take the average R of all n edge midpoints for edges touching P, 
-	//		where each edge midpoint is the average of its two endpoint vertices. 
-	//	Move each original point to the point
+				beta = 3 / ( 8 * n ); // Warren's modified formula
 
+			}
 
-	var F = new THREE.Vector3();
-	var R = new THREE.Vector3();
+			// Loop's original beta formula
+			// beta = 1 / n * ( 5/8 - Math.pow( 3/8 + 1/4 * Math.cos( 2 * Math. PI / n ), 2) );
 
-	var n;
-	for (i=0, il = originalPoints.length; i<il; i++) {
-		// (F + 2R + (n-3)P) / n
+			sourceVertexWeight = 1 - n * beta;
+			connectingVertexWeight = beta;
 
-		if (vertexEdgeMap[i]===undefined) continue;
+			if (n <= 2) {
+				
+				// crease and boundary rules
+				// console.warn('crease and boundary rules');
 
-		F.set(0,0,0);
-		R.set(0,0,0);
-		var newPos =  new THREE.Vector3(0,0,0);
+				if (n == 2) {
 
-		var f = 0; // this counts number of faces, original vertex is connected to (also known as valance?)
-		for (j in vertexFaceMap[i]) {
-			F.add(facePoints[j]);
-			f++;
-		}
+					if (WARNINGS) console.warn('2 connecting edges', connectingEdges);
+					sourceVertexWeight = 3 / 4;
+					connectingVertexWeight = 1 / 8;
 
-		var sharpEdgeCount = 0;
+					// sourceVertexWeight = 1;
+					// connectingVertexWeight = 0;
 
-		n = vertexEdgeMap[i].length; // given a vertex, return its connecting edges
+				} else if (n == 1) {
 
-		// Are we on the border?
-		var boundary_case = f != n;
+					if (WARNINGS) console.warn('only 1 connecting edge');
 
-		// if (boundary_case) {
-		// 	console.error('moo', 'o', i, 'faces touched', f, 'edges',  n, n == 2);
-		// }
+				} else if (n == 0) {
 
-		for (j=0;j<n;j++) {
-			if (
-				sharpEdges[
-					orderedKey(vertexEdgeMap[i][j][0],vertexEdgeMap[i][j][1])
-				]) {
-					sharpEdgeCount++;
+					if (WARNINGS) console.warn('0 connecting edges');
+			
 				}
-		}
-
-		// if ( sharpEdgeCount==2 ) {
-		// 	continue;
-		// 	// Do not move vertex if there's 2 connecting sharp edges.
-		// }
-
-		/*
-		if (sharpEdgeCount>2) {
-			// TODO
-		}
-		*/
+			
+			}
 
-		F.divideScalar(f);
+			newSourceVertex = oldVertex.clone().multiplyScalar( sourceVertexWeight );
 
+			tmp.set(0, 0, 0);
 
-		var boundary_edges = 0;
+			for ( j=0; j < n; j++ ) {
 
-		if (boundary_case) {
+				connectingEdge = connectingEdges[ j ];
+				other = connectingEdge.a !== oldVertex ? connectingEdge.a : connectingEdge.b;
+				tmp.add( other );
 
-			var bb_edge;
-			for (j=0; j<n;j++) {
-				edge = vertexEdgeMap[i][j];
-				bb_edge = edgeFaceMap[orderedKey(edge[0], edge[1])].length == 1
-				if (bb_edge) {
-					var midPt = originalPoints[edge[0]].clone().add(originalPoints[edge[1]]).divideScalar(2);
-					R.add(midPt);
-					boundary_edges++;
-				}
 			}
 
-			R.divideScalar(4);
-			// console.log(j + ' --- ' + n + ' --- ' + boundary_edges);
-			assert(boundary_edges == 2, 'should have only 2 boundary edges');
-
-		} else {
-			for (j=0; j<n;j++) {
-				edge = vertexEdgeMap[i][j];
-				var midPt = originalPoints[edge[0]].clone().add(originalPoints[edge[1]]).divideScalar(2);
-				R.add(midPt);
-			}
+			tmp.multiplyScalar( connectingVertexWeight );
+			newSourceVertex.add( tmp );
+			
+			newSourceVertices.push( newSourceVertex );
 
-			R.divideScalar(n);
 		}
 
-		// Sum the formula
-		newPos.add(originalPoints[i]);
+							   
+		/******************************************************
+		 *
+		 *	Step 3. 
+		 *	Generate Faces between source vertecies
+		 *	and edge vertices.
+		 *
+		 *******************************************************/
 
+		newVertices = newSourceVertices.concat( newEdgeVertices );
+		var sl = newSourceVertices.length, edge1, edge2, edge3;
+		newFaces = [];
 
-		if (boundary_case) {
+		for ( i=0, il=oldFaces.length; i < il; i++ ) {
 
-			newPos.divideScalar(2);
-			newPos.add(R);
+			face = oldFaces[i];
 
-		} else {
-
-			newPos.multiplyScalar(n - 3);
+			// find the 3 new edges vertex of each old face
 
-			newPos.add(F);
-			newPos.add(R.multiplyScalar(2));
-			newPos.divideScalar(n);
+			edge1 = getEdge( face.a, face.b, sourceEdges ).newEdge + sl;
+			edge2 = getEdge( face.b, face.c, sourceEdges ).newEdge + sl;
+			edge3 = getEdge( face.c, face.a, sourceEdges ).newEdge + sl;
 
-		}
+			// create 4 faces.
 
-		newVertices[i] = newPos;
+			newFace( newFaces, edge1, edge2, edge3 );
+			newFace( newFaces, face.a, edge1, edge3 );
+			newFace( newFaces, face.b, edge2, edge1 );
+			newFace( newFaces, face.c, edge3, edge2 );
 
-	}
+		}
 
-	var newGeometry = oldGeometry; // Let's pretend the old geometry is now new :P
+		// Overwrite old arrays
+		geometry.vertices = newVertices;
+		geometry.faces = newFaces;
 
-	newGeometry.vertices = newVertices;
-	newGeometry.faces = newFaces;
-	newGeometry.faceVertexUvs[ 0 ] = newUVs;
+		// console.log('done');
 
-	delete newGeometry.__tmpVertices; // makes __tmpVertices undefined :P
+	};
 
-	newGeometry.computeCentroids();
-	newGeometry.computeFaceNormals();
-	newGeometry.computeVertexNormals();
 
-};
+})();