Pārlūkot izejas kodu

Merge remote-tracking branch 'bhouston/geometry-mergeVertices-fix' into dev

Mr.doob 12 gadi atpakaļ
vecāks
revīzija
72cb3cf68e
2 mainītis faili ar 485 papildinājumiem un 13 dzēšanām
  1. 411 0
      examples/webgl_geometry_normals.html
  2. 74 13
      src/core/Geometry.js

+ 411 - 0
examples/webgl_geometry_normals.html

@@ -0,0 +1,411 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - geometry - Subdivisions with Catmull-Clark</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<style>
+			body {
+				font-family: Monospace;
+				background-color: #f0f0f0;
+				margin: 0px;
+				overflow: hidden;
+			}
+		</style>
+	</head>
+	<body>
+
+		<script src="../build/three.js"></script>
+		<script src="js/libs/stats.min.js"></script>
+		<script src="fonts/helvetiker_regular.typeface.js"></script>
+
+		<script>
+
+			var container, stats;
+
+			var camera, scene, renderer;
+
+			var cube, plane;
+
+			var targetYRotation = targetXRotation = 0;
+			var targetYRotationOnMouseDown = targetXRotationOnMouseDown = 0;
+
+			var mouseX = 0, mouseY = 0;
+			var mouseXOnMouseDown = 0;
+
+			var windowHalfX = window.innerWidth / 2;
+			var windowHalfY = window.innerHeight / 2;
+
+
+			// Create new object by parameters
+
+			var createSomething = function( klass, args ) {
+
+				var F = function( klass, args ) {
+
+				    return klass.apply( this, args );
+
+				}
+
+				F.prototype = klass.prototype;
+
+				return new F( klass, args );
+
+			};
+
+
+			// Cube
+
+			var materials = [];
+
+			for ( var i = 0; i < 6; i ++ ) {
+
+				materials.push( [ new THREE.MeshBasicMaterial( { color: Math.random() * 0xffffff, wireframe: false } ) ] );
+
+			}
+
+
+
+			var geometriesParams = [
+
+				{ type: 'CubeGeometry', args: [ 200, 200, 200, 2, 2, 2, materials ] },
+				{ type: 'TorusGeometry', args: [ 100, 60, 12, 12 ] },
+				{ type: 'TorusKnotGeometry', args: [  ] },
+				{ type: 'SphereGeometry', args: [ 100, 12, 12 ] },
+				{ type: 'SphereGeometry', args: [ 100, 5, 5 ] },
+				{ type: 'SphereGeometry', args: [ 100, 13, 13 ] },
+				{ type: 'IcosahedronGeometry', args: [ 100, 1 ] },
+				{ type: 'CylinderGeometry', args: [ 25, 75, 200, 8, 3 ]} ,
+				{ type: 'OctahedronGeometry', args: [200, 0] },
+				{ type: 'LatheGeometry', args: [ [
+					new THREE.Vector3(0,0,-100),
+					new THREE.Vector3(0,50,-50),
+					new THREE.Vector3(0,10,0),
+					new THREE.Vector3(0,50,050),
+					new THREE.Vector3(0,0,100) ] ]},
+				{ type: 'TextGeometry', args: ['&', {
+										size: 200,
+										height: 50,
+										curveSegments: 1,
+										font: "helvetiker"
+
+									}]},
+				{ type: 'PlaneGeometry', args: [ 200, 200, 4, 4 ] }
+
+			];
+
+			var info;
+			var geometryIndex = 0;
+
+			// start scene
+
+			init();
+			animate();
+
+			function nextGeometry() {
+
+				geometryIndex ++;
+
+				if ( geometryIndex > geometriesParams.length - 1 ) {
+
+					geometryIndex = 0;
+
+				}
+
+				addStuff();
+
+			}
+
+			function switchGeometry(i) {
+
+				geometryIndex = i;
+
+				addStuff();
+			}
+
+			function updateInfo() {
+
+				var params = geometriesParams[ geometryIndex ];
+
+				var dropdown = '<select id="dropdown" onchange="switchGeometry(this.value)">';
+
+				for (  i = 0; i < geometriesParams.length; i ++ ) {
+					dropdown += '<option value="' + i + '"';
+
+					dropdown += (geometryIndex == i)  ? ' selected' : '';
+
+					dropdown += '>' + geometriesParams[i].type + '</option>';
+				}
+
+				dropdown += '</select>';
+			
+				var text =
+					'Drag to spin THREE.' + params.type +
+				 	'<br>' +
+					'<br>Geometry: ' + dropdown + ' <a href="#" onclick="nextGeometry();return false;">next</a>';
+
+				text +=
+					'<br><br><font color="3333FF">Blue Arrows: Face Normals</font>' + 
+					'<br><font color="FF3333">Red Arrows: Vertex Normals before Geometry.mergeVertices</font>' + 
+					'<br>Black Arrows: Vertex Normals after Geometry.mergeVertices';
+
+				info.innerHTML = text;
+
+			}
+
+			function addStuff() {
+
+				if ( window.group !== undefined ) {
+
+					scene.remove( group );
+	
+				}
+
+
+
+				var params = geometriesParams[ geometryIndex ];
+
+				geometry = createSomething( THREE[ params.type ], params.args );
+
+				// scale geometry to a uniform size
+				geometry.computeBoundingSphere();
+				
+				var scaleFactor = 160 / geometry.boundingSphere.radius;
+				geometry.applyMatrix( new THREE.Matrix4().makeScale( new THREE.Vector3( scaleFactor, scaleFactor, scaleFactor ) ) );
+
+				var originalGeometry = geometry.clone();
+				originalGeometry.computeFaceNormals();
+				originalGeometry.computeVertexNormals( true );
+			
+				// in case of duplicated vertices
+				geometry.mergeVertices();
+				geometry.computeCentroids();
+				geometry.computeFaceNormals();
+				geometry.computeVertexNormals( true );
+			
+				updateInfo();
+
+				var faceABCD = "abcd";
+				var color, f, p, n, vertexIndex;
+
+				for ( i = 0; i < geometry.faces.length; i ++ ) {
+
+					f  = geometry.faces[ i ];
+
+
+					n = ( f instanceof THREE.Face3 ) ? 3 : 4;
+
+					for( var j = 0; j < n; j++ ) {
+
+						vertexIndex = f[ faceABCD.charAt( j ) ];
+
+						p = geometry.vertices[ vertexIndex ];
+
+						color = new THREE.Color( 0xffffff );
+						color.setHSV( ( p.y ) / 400 + 0.5, 1.0, 1.0 );
+
+						f.vertexColors[ j ] = color;
+
+					}
+
+				}
+
+
+				group = new THREE.Object3D();
+				var mesh = new THREE.Mesh( geometry, new THREE.MeshBasicMaterial( { color: 0xfefefe, wireframe: true, opacity: 0.5 } ) );
+				group.add( mesh );
+				scene.add( group );
+
+				var fvNames = [ 'a', 'b', 'c', 'd' ];
+
+				var normalLength = 15;
+				
+			
+				for( var f = 0, fl = geometry.faces.length; f < fl; f ++ ) {
+					var face = geometry.faces[ f ];
+					var arrow = new THREE.ArrowHelper( 
+							face.normal,
+							face.centroid,
+							normalLength,
+							0x3333FF );
+					mesh.add( arrow );
+				}
+
+				for( var f = 0, fl = originalGeometry.faces.length; f < fl; f ++ ) {
+					var face = originalGeometry.faces[ f ];
+					if( face.vertexNormals === undefined ) {
+						continue;
+					}
+					for( var v = 0, vl = face.vertexNormals.length; v < vl; v ++ ) {
+						var arrow = new THREE.ArrowHelper( 
+								face.vertexNormals[ v ],
+								originalGeometry.vertices[ face[ fvNames[ v ] ] ],
+								normalLength,
+								0xFF3333 );
+						mesh.add( arrow );
+					}
+				}
+
+				for( var f = 0, fl = mesh.geometry.faces.length; f < fl; f ++ ) {
+					var face = mesh.geometry.faces[ f ];
+					if( face.vertexNormals === undefined ) {
+						continue;
+					}
+					for( var v = 0, vl = face.vertexNormals.length; v < vl; v ++ ) {
+						var arrow = new THREE.ArrowHelper( 
+								face.vertexNormals[ v ],
+								mesh.geometry.vertices[ face[ fvNames[ v ] ] ],
+								normalLength,
+								0x000000 );
+						mesh.add( arrow );
+					}
+				}
+
+			}
+
+			function init() {
+
+				container = document.createElement( 'div' );
+				document.body.appendChild( container );
+
+				info = document.createElement( 'div' );
+				info.style.position = 'absolute';
+				info.style.top = '10px';
+				info.style.width = '100%';
+				info.style.textAlign = 'center';
+				info.innerHTML = 'Drag to spin the geometry ';
+				container.appendChild( info );
+
+				camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 1000 );
+				camera.position.z = 500;
+
+				scene = new THREE.Scene();
+
+				var light = new THREE.PointLight( 0xffffff, 1.5 );
+				light.position.set( 1000, 1000, 2000 );
+				scene.add( light );
+
+				addStuff();
+
+				renderer = new THREE.WebGLRenderer( { antialias: true } ); // WebGLRenderer CanvasRenderer
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+				container.appendChild( renderer.domElement );
+
+				stats = new Stats();
+				stats.domElement.style.position = 'absolute';
+				stats.domElement.style.top = '0px';
+				container.appendChild( stats.domElement );
+
+				document.addEventListener( 'mousedown', onDocumentMouseDown, false );
+				document.addEventListener( 'touchstart', onDocumentTouchStart, false );
+				document.addEventListener( 'touchmove', onDocumentTouchMove, false );
+
+				//
+
+				window.addEventListener( 'resize', onWindowResize, false );
+
+			}
+
+			function onWindowResize() {
+
+				windowHalfX = window.innerWidth / 2;
+				windowHalfY = window.innerHeight / 2;
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			//
+
+			function onDocumentMouseDown( event ) {
+
+				//event.preventDefault();
+
+				document.addEventListener( 'mousemove', onDocumentMouseMove, false );
+				document.addEventListener( 'mouseup', onDocumentMouseUp, false );
+				document.addEventListener( 'mouseout', onDocumentMouseOut, false );
+
+				mouseXOnMouseDown = event.clientX - windowHalfX;
+				mouseYOnMouseDown = event.clientY - windowHalfY;
+				targetYRotationOnMouseDown = targetYRotation;
+				targetXRotationOnMouseDown = targetXRotation;
+
+			}
+
+			function onDocumentMouseMove( event ) {
+
+				mouseX = event.clientX - windowHalfX;
+				mouseY = event.clientY - windowHalfY;
+
+				targetYRotation = targetYRotationOnMouseDown + ( mouseX - mouseXOnMouseDown ) * 0.02;
+				targetXRotation = targetXRotationOnMouseDown + ( mouseY - mouseYOnMouseDown ) * 0.02;
+
+			}
+
+			function onDocumentMouseUp( event ) {
+
+				document.removeEventListener( 'mousemove', onDocumentMouseMove, false );
+				document.removeEventListener( 'mouseup', onDocumentMouseUp, false );
+				document.removeEventListener( 'mouseout', onDocumentMouseOut, false );
+			}
+
+			function onDocumentMouseOut( event ) {
+
+				document.removeEventListener( 'mousemove', onDocumentMouseMove, false );
+				document.removeEventListener( 'mouseup', onDocumentMouseUp, false );
+				document.removeEventListener( 'mouseout', onDocumentMouseOut, false );
+			}
+
+			function onDocumentTouchStart( event ) {
+
+				if ( event.touches.length == 1 ) {
+
+					event.preventDefault();
+
+					mouseXOnMouseDown = event.touches[ 0 ].pageX - windowHalfX;
+					targetRotationOnMouseDown = targetRotation;
+
+				}
+			}
+
+			function onDocumentTouchMove( event ) {
+
+				if ( event.touches.length == 1 ) {
+
+					event.preventDefault();
+
+					mouseX = event.touches[ 0 ].pageX - windowHalfX;
+					targetRotation = targetRotationOnMouseDown + ( mouseX - mouseXOnMouseDown ) * 0.05;
+
+				}
+			}
+
+			//
+
+			function animate() {
+
+				requestAnimationFrame( animate );
+
+				render();
+				stats.update();
+
+			}
+
+			function render() {
+
+				group.rotation.x = ( targetXRotation) * 0.15;
+				group.rotation.y =  ( targetYRotation ) * 0.15;
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+
+	</body>
+</html>
+

+ 74 - 13
src/core/Geometry.js

@@ -610,7 +610,10 @@ THREE.Geometry.prototype = {
 		var precisionPoints = 4; // number of decimal points, eg. 4 for epsilon of 0.0001
 		var precision = Math.pow( 10, precisionPoints );
 		var i,il, face;
-		var abcd = 'abcd', o, k, j, jl, u;
+		var indices, k, j, jl, u;
+
+		// reset cache of vertices as it now will be changing.
+		this.__tmpVertices = undefined;
 
 		for ( i = 0, il = this.vertices.length; i < il; i ++ ) {
 
@@ -633,7 +636,9 @@ THREE.Geometry.prototype = {
 		};
 
 
-		// Start to patch face indices
+		// if faces are completely degenerate after merging vertices, we
+		// have to remove them from the geometry.
+		var faceIndicesToRemove = [];
 
 		for( i = 0, il = this.faces.length; i < il; i ++ ) {
 
@@ -645,6 +650,22 @@ THREE.Geometry.prototype = {
 				face.b = changes[ face.b ];
 				face.c = changes[ face.c ];
 
+				indices = [ face.a, face.b, face.c ];
+
+				var dupIndex = -1;
+				
+				// if any duplicate vertices are found in a Face3
+				// we have to remove the face as nothing can be saved
+				for( var n = 0; n < 3; n ++ ) {
+					if( indices[ n ] == indices[ ( n + 1 ) % 3 ] ) {
+
+						dupIndex = n;
+						faceIndicesToRemove.push( i );
+						break;
+
+					}
+				}
+
 			} else if ( face instanceof THREE.Face4 ) {
 
 				face.a = changes[ face.a ];
@@ -654,36 +675,76 @@ THREE.Geometry.prototype = {
 
 				// check dups in (a, b, c, d) and convert to -> face3
 
-				o = [ face.a, face.b, face.c, face.d ];
+				indices = [ face.a, face.b, face.c, face.d ];
 
-				for ( k = 3; k > 0; k -- ) {
+				var dupIndex = -1;
 
-					if ( o.indexOf( face[ abcd[ k ] ] ) !== k ) {
+				for( var n = 0; n < 4; n ++ ) {
+					if( indices[ n ] == indices[ ( n + 1 ) % 4 ] ) {
 
-						// console.log('faces', face.a, face.b, face.c, face.d, 'dup at', k);
 
-						o.splice( k, 1 );
+						// if more than one duplicated vertex is found
+						// we can't generate any valid Face3's, thus
+						// we need to remove this face complete.
+						if( dupIndex >= 0 ) {
+
+							faceIndicesToRemove.push( i );
+
+						}
+
+						dupIndex = n;
+
+					}
+				}
+
+				if( dupIndex >= 0 ) {
 
-						this.faces[ i ] = new THREE.Face3( o[0], o[1], o[2], face.normal, face.color, face.materialIndex );
+					indices.splice( dupIndex, 1 );
 
-						for ( j = 0, jl = this.faceVertexUvs.length; j < jl; j ++ ) {
+					var newFace = new THREE.Face3( indices[0], indices[1], indices[2], face.normal, face.color, face.materialIndex );
 
-							u = this.faceVertexUvs[ j ][ i ];
-							if ( u ) u.splice( k, 1 );
+					for ( j = 0, jl = this.faceVertexUvs.length; j < jl; j ++ ) {
 
+						u = this.faceVertexUvs[ j ][ i ];
+
+						if ( u ) {
+							u.splice( dupIndex, 1 );
 						}
 
-						this.faces[ i ].vertexColors = face.vertexColors;
+					}
+
+					if( face.vertexNormals && face.vertexNormals.length > 0) {
 
-						break;
+						newFace.vertexNormals = face.vertexNormals;
+						newFace.vertexNormals.splice( dupIndex, 1 );
+
+					}
+
+					if( face.vertexColors && face.vertexColors.length > 0 ) {
+
+						newFace.vertexColors = face.vertexColors;
+						newFace.vertexColors.splice( dupIndex, 1 );
 					}
 
+					this.faces[ i ] = newFace;
 				}
 
 			}
 
 		}
 
+		for( i = faceIndicesToRemove.length - 1; i >= 0; i -- ) {
+
+			this.faces.splice( i, 1 );
+			
+			for ( j = 0, jl = this.faceVertexUvs.length; j < jl; j ++ ) {
+
+				this.faceVertexUvs[ j ].splice( i, 1 );
+
+			}
+
+		}
+
 		// Use unique set of vertices
 
 		var diff = this.vertices.length - unique.length;