Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

LatheGeometry fix + optimization (dependent on PR#2867 'Geometry.mergeVertices fix') #2868

Merged
merged 6 commits into from

3 participants

Ben Houston Mr.doob WestLangley
Ben Houston

I noticed that LatheGeometry has a bug/contradiction and is slower than necessary:

This PR removes addresses the above via:

  • I removed the assumption that the lathe geometry is always closed -- I no longer wrap the vertices. To make a closed geometry, called Geometry.mergeVertices after generation and it will close the main seam as well as the top and bottom correctly if they can be merged. Thus this is a general solution.

  • I optimized rotation around the axis via Math.cos and Math.sin.

I updated webgl_geometry_normals.html (here: bhouston@f454d09 ) so that you can see the LatheGeometry examples with partial lathes and unclosed lathes and see that they all work correctly (and with correct normals after running Geometry.mergeVertices.)

Mr.doob mrdoob merged commit f454d09 into from
Mr.doob
Owner

Thanks!

WestLangley
Collaborator

@bhouston

There are problems here with the UVs. I am not sure this was tested -- it looks like the problems existed before your recent changes.

  1. The texture is reversed.
  2. The entire texture is not mapped to the surface.
  3. Also, there are multiple vertex normals at shared vertices on the seam.

Would you mind having a look at my dev-lathe branch, and providing a second opinion on my (very simple) attempt at addressing these issues. There is more than one way to solve this problem, but if you concur, I'll submit a PR.

One point -- the points array, I guess, is assumed to be increasing in z. Actually, it would be better to pass in a 2D THREE.Path in x and y, and rotate that around the x-axis, but I didn't change any of that.

Ben Houston

@WestLangley, to resolve the shared vertices just call Geometry.mergeVertices(), I fixed it so that it will work correctly on LatheGeometry results. Remember that LatheGeometries are not necessary closed at the top and along the main seam so many simple approaches are not general solutions.

The reversal of the texture should be fairly easy to resolve, but it seems like this was done on purpose in the code so I left it in.

The entire texture should be mapped, I'll have a look at this.

Ben Houston

@WestLangley, I just looked at your changes, I like them. You are using this.mergeVertices() which is by far the best method to fix the duplicate vertices. I am okay with the test of your changes. I would suggest doing a PR with your changes as I will support it.

WestLangley
Collaborator

@bhouston Done. Thank you for your code review. :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Dec 31, 2012
  1. Ben Houston

    robust support for multiple duplicate vertices in Face3 and Face4, ad…

    bhouston authored
    …d handling of vertex colors and vertex UVs.
  2. Ben Houston
  3. Ben Houston

    add webgl_geometry_normals.html - example that shows vertex normals a…

    bhouston authored
    …nd face normals of standard geometry types before and after Geometry.mergeVertices
  4. Ben Houston

    Fix lathe-geometry in presence of angle < PI*2, add ability to specif…

    bhouston authored
    …y phiStart, phiLength + optimize.
  5. Ben Houston
  6. Ben Houston
This page is out of date. Refresh to see the latest.
423 examples/webgl_geometry_normals.html
View
@@ -0,0 +1,423 @@
+<!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: '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,100,100) ], 12, 0, Math.PI ] },
+ { type: 'LatheGeometry', args: [ [
+ new THREE.Vector3(0,10,-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) ], 12, Math.PI*2/3, Math.PI*3/2 ] },
+ { 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>
+
87 src/core/Geometry.js
View
@@ -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;
67 src/extras/geometries/LatheGeometry.js
View
@@ -1,55 +1,74 @@
/**
* @author astrodud / http://astrodud.isgreat.org/
* @author zz85 / https://github.com/zz85
+ * @author bhouston / http://exocortex.com
*/
-THREE.LatheGeometry = function ( points, steps, angle ) {
+// points - to create a closed torus, one must use a set of points
+// like so: [ a, b, c, d, a ], see first is the same as last.
+// segments - the number of circumference segments to create
+// phiStart - the starting radian
+// phiLength - the radian (0 to 2*PI) range of the lathed section
+// 2*pi is a closed lathe, less than 2PI is a portion.
+THREE.LatheGeometry = function ( points, segments, phiStart, phiLength ) {
THREE.Geometry.call( this );
- var _steps = steps || 12;
- var _angle = angle || 2 * Math.PI;
+ segments = segments || 12;
+ phiStart = phiStart || 0;
+ phiLength = phiLength || 2 * Math.PI;
- var _newV = [];
- var _matrix = new THREE.Matrix4().makeRotationZ( _angle / _steps );
+ var inversePointLength = 1.0 / points.length;
+ var inverseSegments = 1.0 / segments;
- for ( var j = 0; j < points.length; j ++ ) {
+ for ( var i = 0, il = segments; i <= il; i ++ ) {
- _newV[ j ] = points[ j ].clone();
- this.vertices.push( _newV[ j ] );
+ var phi = phiStart + i * inverseSegments * phiLength;
- }
+ var c = Math.cos( phi ),
+ s = Math.sin( phi );
+
+ for ( var j = 0, jl = points.length; j < jl; j ++ ) {
- var i, il = _steps + 1;
+ var pt = points[ j ];
- for ( i = 0; i < il; i ++ ) {
+ var vertex = new THREE.Vector3();
- for ( var j = 0; j < _newV.length; j ++ ) {
+ vertex.x = c * pt.x - s * pt.y;
+ vertex.y = s * pt.x + c * pt.y;
+ vertex.z = pt.z;
- _newV[ j ] = _newV[ j ].clone().applyMatrix4( _matrix );
- this.vertices.push( _newV[ j ] );
+ this.vertices.push( vertex );
}
}
- for ( i = 0; i < _steps; i ++ ) {
+ var np = points.length;
- for ( var k = 0, kl = points.length; k < kl - 1; k ++ ) {
+ for ( var i = 0, il = segments; i < il; i ++ ) {
- var a = i * kl + k;
- var b = ( ( i + 1 ) % il ) * kl + k;
- var c = ( ( i + 1 ) % il ) * kl + ( k + 1 ) % kl;
- var d = i * kl + ( k + 1 ) % kl;
+ for ( var j = 0, jl = points.length - 1; j < jl; j ++ ) {
+
+ var base = j + np * i;
+ var a = base;
+ var b = base + np;
+ var c = base + 1 + np;
+ var d = base + 1;
this.faces.push( new THREE.Face4( a, b, c, d ) );
+ var u0 = 1 - i * inverseSegments;
+ var v0 = j * inversePointLength;
+ var u1 = u0 - inverseSegments;
+ var v1 = v0 + inversePointLength;
+
this.faceVertexUvs[ 0 ].push( [
- new THREE.Vector2( 1 - i / _steps, k / kl ),
- new THREE.Vector2( 1 - ( i + 1 ) / _steps, k / kl ),
- new THREE.Vector2( 1 - ( i + 1 ) / _steps, ( k + 1 ) / kl ),
- new THREE.Vector2( 1 - i / _steps, ( k + 1 ) / kl )
+ new THREE.Vector2( u0, v0 ),
+ new THREE.Vector2( u1, v0 ),
+ new THREE.Vector2( u1, v1 ),
+ new THREE.Vector2( u0, v1 )
] );
Something went wrong with that request. Please try again.