Browse Source

TubeGeometry: Added support for closed paths using parallel transport frames

WestLangley 13 years ago
parent
commit
a44bf80cd6
2 changed files with 142 additions and 102 deletions
  1. 8 7
      examples/webgl_geometry_extrude_splines.html
  2. 134 95
      src/extras/geometries/TubeGeometry.js

+ 8 - 7
examples/webgl_geometry_extrude_splines.html

@@ -396,7 +396,8 @@
           // 3d shape
           // 3d shape
           var mesh = THREE.SceneUtils.createMultiMaterialObject(geometry, [
           var mesh = THREE.SceneUtils.createMultiMaterialObject(geometry, [
             new THREE.MeshLambertMaterial({
             new THREE.MeshLambertMaterial({
-                color: color
+                color: color,
+                opacity: 0.2
             }),
             }),
            new THREE.MeshBasicMaterial({
            new THREE.MeshBasicMaterial({
               color: 0x000000,
               color: 0x000000,
@@ -433,7 +434,7 @@
       //   new THREE.Vector3(0, -40, 40),
       //   new THREE.Vector3(0, -40, 40),
       // ]);
       // ]);
 
 
-      // extrudePath = new THREE.TrefoilKnot();
+      extrudePath = new THREE.TrefoilKnot();
       // extrudePath = new THREE.TorusKnot(20);
       // extrudePath = new THREE.TorusKnot(20);
       // extrudePath = new THREE.CinquefoilKnot(20);
       // extrudePath = new THREE.CinquefoilKnot(20);
       // extrudePath = new THREE.TrefoilPolynomialKnot(14);
       // extrudePath = new THREE.TrefoilPolynomialKnot(14);
@@ -441,13 +442,13 @@
       // extrudePath = new THREE.DecoratedTorusKnot4a();
       // extrudePath = new THREE.DecoratedTorusKnot4a();
 	  // extrudePath = new THREE.DecoratedTorusKnot4b();
 	  // extrudePath = new THREE.DecoratedTorusKnot4b();
       // extrudePath = new THREE.DecoratedTorusKnot5a();
       // extrudePath = new THREE.DecoratedTorusKnot5a();
-      extrudePath = new THREE.DecoratedTorusKnot5c();
+      // extrudePath = new THREE.DecoratedTorusKnot5c();
 
 
-       //var tube = new THREE.TubeGeometry(5, 100, 20, extrudePath, true);
-      var tube = new THREE.TubeGeometry(2, 400, 2, extrudePath, true);
+      var closed = true;
+      var debug = true;
+	  var tube = new THREE.TubeGeometry(extrudePath, 100, 2, 3, closed, debug);
 
 
-
-      addGeometry(tube, 0xff00ff, 0, -80, 0, 0, 0, 0, 3);
+      addGeometry(tube, 0xff00ff, 0, -80, 0, 0, 0, 0, 6);
 
 
 
 
       //
       //

+ 134 - 95
src/extras/geometries/TubeGeometry.js

@@ -2,138 +2,182 @@
  * @author WestLangley / https://github.com/WestLangley
  * @author WestLangley / https://github.com/WestLangley
  * @author zz85 / https://github.com/zz85
  * @author zz85 / https://github.com/zz85
  * @author miningold / https://github.com/miningold
  * @author miningold / https://github.com/miningold
- *
- * Modified from the TorusKnotGeometry by @oosmoxiecode
+ *	modified from the TorusKnotGeometry by @oosmoxiecode
  *
  *
  * Creates a tube which extrudes along a 3d spline
  * Creates a tube which extrudes along a 3d spline
+ *
+ * Uses parallel transport frames as described in
+ * http://www.cs.indiana.edu/pub/techreports/TR425.pdf
  */
  */
 
 
-THREE.TubeGeometry = function( radius, segments, segmentsRadius, path, debug ) {
+THREE.TubeGeometry = function( path, segments, radius, segmentsRadius, closed, debug ) {
 
 
 	THREE.Geometry.call( this );
 	THREE.Geometry.call( this );
 
 
-	var scope = this;
-
-	this.radius = radius || 40;
+	this.path = path;
 	this.segments = segments || 64;
 	this.segments = segments || 64;
+	this.radius = radius || 1;
 	this.segmentsRadius = segmentsRadius || 8;
 	this.segmentsRadius = segmentsRadius || 8;
-	this.grid = new Array( this.segments );
-	this.path = path;
-
+	this.closed = closed || false;
 	if ( debug ) this.debug = new THREE.Object3D();
 	if ( debug ) this.debug = new THREE.Object3D();
 
 
-	var tang = new THREE.Vector3();
-	var binormal = new THREE.Vector3();
-	var normal = new THREE.Vector3();
-	var pos = new THREE.Vector3();
+	this.grid = [];
 
 
-	var epsilon = 0.001;
+	var scope = this,
+		tangent = new THREE.Vector3(),
+		normal = new THREE.Vector3(),
+		binormal = new THREE.Vector3(),
 
 
-	var u, v;
+		vec = new THREE.Vector3(),
+		mat = new THREE.Matrix4(),
 
 
-	var p1, p2;
-	var cx, cy;
+		tangents = [],
+		normals = [],
+		binormals = [],
 
 
-	var oldB;
+		numpoints = this.segments + 1,
+		theta,
+		epsilon = 0.0001,
+		smallest,
+		x, y, z,
+		tx, ty, tz,
+		u, v,
+		p1, p2,
+		cx, cy,
+		pos, pos2,
+		i, j,
+		ip, jp,
+		a, b, c, d,
+		uva, uvb, uvc, uvd;
 
 
-	for ( var i = 0; i < this.segments; ++ i ) {
 
 
-		this.grid[ i ] = new Array( this.segmentsRadius );
+	function vert( x, y, z ) {
 
 
-		u = i / ( this.segments - 1 );
+		return scope.vertices.push( new THREE.Vertex( new THREE.Vector3( x, y, z ) ) ) - 1;
 
 
-		pos = this.path.getPointAt( u );
-		tang = this.path.getTangentAt( u );
+	}
+
+	// compute the tangent vectors for each segment on the path
+
+	for ( i = 0; i < numpoints; i++ ) {
 
 
-		if ( oldB === undefined ) {
+		u = i / ( numpoints - 1 );
 
 
-			// Method 1, random arbitrary vector
-			// oldB = new THREE.Vector3(Math.random(), Math.random(), Math.random()).normalize();
+		tangents[ i ] = this.path.getTangentAt( u );
+		tangents[ i ].normalize();
 
 
-			// Method 2, use a fixed start binormal. Has dangers of 0 vectors too.
-			// oldB = new THREE.Vector3( 0, -1, 0 );
+	}
+
+
+	// select an initial normal vector perpenicular to the first tangent vector,
+	// and in the direction of the smallest tangent xyz component
+
+	normals[ 0 ] = new THREE.Vector3();
+	binormals[ 0 ] = new THREE.Vector3();
+	smallest = Number.MAX_VALUE;
+	tx = Math.abs( tangents[ 0 ].x );
+	ty = Math.abs( tangents[ 0 ].y );
+	tz = Math.abs( tangents[ 0 ].z );
+
+	if ( tx <= smallest ) {
+		smallest = tx;
+		normal.set( 1, 0, 0 );
+	}
 
 
-			// Method 3 - This uses the Frenet-Serret formula for deriving binormal
+	if ( ty <= smallest ) {
+		smallest = ty;
+		normal.set( 0, 1, 0 );
+	}
+
+	if ( tz <= smallest ) {
+		normal.set( 0, 0, 1 );
+	}
 
 
-			var t1, t2;
+	vec.cross( tangents[ 0 ], normal ).normalize();
 
 
-			t1 = u - epsilon;
-			if (t1 < 0) t1 = 0;
-			t1 = this.path.getTangentAt( t1 );
+	normals[ 0 ].cross( tangents[ 0 ], vec );
+	binormals[ 0 ].cross( tangents[ 0 ], normals[ 0 ] );
 
 
-			t2 = u + epsilon;
-			if (t2 > 1) t2 = 1;
-			t2 = this.path.getTangentAt( t2 );
 
 
-			normal.sub( t2, t1 ).normalize();
+	// compute the slowly-varying normal and binormal vectors for each segment on the path
 
 
-			binormal.cross( tang, normal );
-			oldB = binormal;
+	for ( i = 1; i < numpoints; i++ ) {
 
 
-			if ( oldB.length() == 0 ) {
+		normals[ i ] = normals[ i-1 ].clone();
 
 
-				// When binormal is a zero vector, we could brute force another vector ?
-				// oldB.set( 1, 0, 0 );
-				// if (normal.cross(oldB, tang).normalize().length()==0) {
-				// 	oldB.set( 0, 1, 0 );
-				// 	if (normal.cross(oldB, tang).normalize().length()==0) {
-				// 		oldB.set( 0, 0, 1 );
-				// 	}
-				// }
+		binormals[ i ] = binormals[ i-1 ].clone();
 
 
-				// Method 4 - Sets binormal direction in the smallest tangent xyz component
+		vec.cross( tangents[ i-1 ], tangents[ i ] );
 
 
-				var smallest = Number.MAX_VALUE;
-				var x, y, z;
+		if ( vec.length() > epsilon ) {
 
 
-				var tx = Math.abs( tang.x );
-				var ty = Math.abs( tang.y );
-				var tz = Math.abs( tang.z );
+			vec.normalize();
 
 
-				if ( tx <= smallest ) {
+			theta = Math.acos( tangents[ i-1 ].dot( tangents[ i ] ) );
 
 
-					smallest = tx;
-					oldB.set( 1, 0, 0 );
+			mat.setRotationAxis( vec, theta ).multiplyVector3( normals[ i ] );
 
 
-				}
+		}
+
+		binormals[ i ].cross( tangents[ i ], normals[ i ] );
 
 
-				if ( ty <= smallest ) {
+	}
 
 
-					smallest = ty;
-					oldB.set( 0, 1, 0 );
 
 
-				}
+	// if the curve is closed, postprocess the vectors so the first and last normal vectors are the same
 
 
-				if ( tz <= smallest ) {
+	if ( this.closed ) {
 
 
-					oldB.set( 0, 0, 1 );
+		theta = Math.acos( normals[ 0 ].dot( normals[ numpoints-1 ] ) );
+		theta /= ( numpoints - 1 );
 
 
-				}
+		if ( tangents[ 0 ].dot( vec.cross( normals[ 0 ], normals[ numpoints-1 ] ) ) > 0 ) {
 
 
-			}
+			theta = -theta;
 
 
 		}
 		}
 
 
-		normal.cross( oldB, tang ).normalize();
-		binormal.cross( tang, normal ).normalize();
-		oldB = binormal;
+		for ( i = 1; i < numpoints; i++ ) {
+
+			// twist a little...
+			mat.setRotationAxis( tangents[ i ], theta * i ).multiplyVector3( normals[ i ] );
+			binormals[ i ].cross( tangents[ i ], normals[ i ] );
+
+		}
+
+	}
+
+
+	// consruct the grid
+
+	for ( i = 0; i < numpoints; i++ ) {
+
+		this.grid[ i ] = [];
+
+		u = i / ( numpoints - 1 );
+
+		pos = this.path.getPointAt( u );
+
+		tangent = tangents[ i ];
+		normal = normals[ i ];
+		binormal = binormals[ i ];
 
 
 		if ( this.debug ) {
 		if ( this.debug ) {
 
 
-			this.debug.add( new THREE.ArrowHelper( normal, pos, radius * 2, 0xff0000 ) );
-			// this.debug.add(new THREE.ArrowHelper(binormal, pos, radius * 2, 0x00ff00));
-			// this.debug.add(new THREE.ArrowHelper(tang, pos, radius * 2, 0x0000ff));
+			this.debug.add(new THREE.ArrowHelper(tangent, pos, radius, 0x0000ff));	
+			this.debug.add(new THREE.ArrowHelper(normal, pos, radius, 0xff0000));
+			this.debug.add(new THREE.ArrowHelper(binormal, pos, radius, 0x00ff00));
 
 
 		}
 		}
 
 
-		for ( var j = 0; j < this.segmentsRadius; ++ j ) {
+		for ( j = 0; j < this.segmentsRadius; j++ ) {
 
 
 			v = j / this.segmentsRadius * 2 * Math.PI;
 			v = j / this.segmentsRadius * 2 * Math.PI;
 
 
 			cx = -this.radius * Math.cos( v ); // TODO: Hack: Negating it so it faces outside.
 			cx = -this.radius * Math.cos( v ); // TODO: Hack: Negating it so it faces outside.
 			cy = this.radius * Math.sin( v );
 			cy = this.radius * Math.sin( v );
 
 
-            var pos2 = pos.clone();
+            pos2 = new THREE.Vector3().copy( pos );
             pos2.x += cx * normal.x + cy * binormal.x;
             pos2.x += cx * normal.x + cy * binormal.x;
             pos2.y += cx * normal.y + cy * binormal.y;
             pos2.y += cx * normal.y + cy * binormal.y;
             pos2.z += cx * normal.z + cy * binormal.z;
             pos2.z += cx * normal.z + cy * binormal.z;
@@ -141,44 +185,39 @@ THREE.TubeGeometry = function( radius, segments, segmentsRadius, path, debug ) {
             this.grid[ i ][ j ] = vert( pos2.x, pos2.y, pos2.z );
             this.grid[ i ][ j ] = vert( pos2.x, pos2.y, pos2.z );
 
 
 		}
 		}
-
 	}
 	}
 
 
-	for ( var i = 0; i < this.segments -1; ++ i ) {
 
 
-		for ( var j = 0; j < this.segmentsRadius; ++ j ) {
+	// construct the mesh
+
+	for ( i = 0; i < this.segments; i++ ) {
 
 
-			var ip = ( i + 1 ) % this.segments;
-			var jp = ( j + 1 ) % this.segmentsRadius;
+		for ( j = 0; j < this.segmentsRadius; j++ ) {
 
 
-			var a = this.grid[ i  ][ j ];
-			var b = this.grid[ ip ][ j ];
-			var c = this.grid[ ip ][ jp ];
-			var d = this.grid[ i  ][ jp ];
+			ip = ( closed ) ? (i + 1) % this.segments : i + 1;
+			jp = (j + 1) % this.segmentsRadius;
 
 
-			var uva = new THREE.UV( i / this.segments, j / this.segmentsRadius );
-			var uvb = new THREE.UV( ( i + 1 ) / this.segments, j / this.segmentsRadius );
-			var uvc = new THREE.UV( ( i + 1 ) / this.segments, ( j + 1 ) / this.segmentsRadius );
-			var uvd = new THREE.UV( i / this.segments, ( j + 1 ) / this.segmentsRadius );
+			a = this.grid[ i ][ j ];		// *** NOT NECESSARILY PLANAR ! ***
+			b = this.grid[ ip ][ j ];
+			c = this.grid[ ip ][ jp ];
+			d = this.grid[ i ][ jp ];
+
+			uva = new THREE.UV( i / this.segments, j / this.segmentsRadius );
+			uvb = new THREE.UV( ( i + 1 ) / this.segments, j / this.segmentsRadius );
+			uvc = new THREE.UV( ( i + 1 ) / this.segments, ( j + 1 ) / this.segmentsRadius );
+			uvd = new THREE.UV( i / this.segments, ( j + 1 ) / this.segmentsRadius );
 
 
 			this.faces.push( new THREE.Face4( a, b, c, d ) );
 			this.faces.push( new THREE.Face4( a, b, c, d ) );
 			this.faceVertexUvs[ 0 ].push( [ uva, uvb, uvc, uvd ] );
 			this.faceVertexUvs[ 0 ].push( [ uva, uvb, uvc, uvd ] );
 
 
 		}
 		}
-
 	}
 	}
 
 
 	this.computeCentroids();
 	this.computeCentroids();
 	this.computeFaceNormals();
 	this.computeFaceNormals();
 	this.computeVertexNormals();
 	this.computeVertexNormals();
 
 
-	function vert( x, y, z ) {
-
-		return scope.vertices.push( new THREE.Vertex( new THREE.Vector3( x, y, z ) ) ) - 1;
-
-	}
-
 };
 };
 
 
 THREE.TubeGeometry.prototype = new THREE.Geometry();
 THREE.TubeGeometry.prototype = new THREE.Geometry();
-THREE.TubeGeometry.prototype.constructor = THREE.TubeGeometry;
+THREE.TubeGeometry.prototype.constructor = THREE.TubeGeometry;