/** * @author zz85 / http://www.lab4games.net/zz85/blog * * Creates extruded geometry from a path shape. * * parameters = { * * size: , // size of the text * height: , // thickness to extrude text * curveSegments: , // number of points on the curves * steps: , // number of points for z-side extrusions / used for subdividing segements of extrude spline too * amount: , // Amount * * bevelEnabled: , // turn on bevel * bevelThickness: , // how deep into text bevel goes * bevelSize: , // how far from text outline is bevel * bevelSegments: , // number of bevel layers * * extrudePath: // 3d spline path to extrude shape along. (creates Frames if .frames aren't defined) * frames: // containing arrays of tangents, normals, binormals * * material: // material index for front and back faces * extrudeMaterial: // material index for extrusion and beveled faces * uvGenerator: // object that provides UV generator functions * * } **/ THREE.ExtrudeGeometry = function ( shapes, options ) { if ( typeof( shapes ) === "undefined" ) { shapes = []; return; } THREE.Geometry.call( this ); shapes = shapes instanceof Array ? shapes : [ shapes ]; this.shapebb = shapes[ shapes.length - 1 ].getBoundingBox(); this.addShapeList( shapes, options ); this.computeCentroids(); this.computeFaceNormals(); // can't really use automatic vertex normals // as then front and back sides get smoothed too // should do separate smoothing just for sides //this.computeVertexNormals(); //console.log( "took", ( Date.now() - startTime ) ); }; THREE.ExtrudeGeometry.prototype = Object.create( THREE.Geometry.prototype ); THREE.ExtrudeGeometry.prototype.addShapeList = function ( shapes, options ) { var sl = shapes.length; for ( var s = 0; s < sl; s ++ ) { var shape = shapes[ s ]; this.addShape( shape, options ); } }; THREE.ExtrudeGeometry.prototype.addShape = function ( shape, options ) { var amount = options.amount !== undefined ? options.amount : 100; var bevelThickness = options.bevelThickness !== undefined ? options.bevelThickness : 6; // 10 var bevelSize = options.bevelSize !== undefined ? options.bevelSize : bevelThickness - 2; // 8 var bevelSegments = options.bevelSegments !== undefined ? options.bevelSegments : 3; var bevelEnabled = options.bevelEnabled !== undefined ? options.bevelEnabled : true; // false var curveSegments = options.curveSegments !== undefined ? options.curveSegments : 12; var steps = options.steps !== undefined ? options.steps : 1; var extrudePath = options.extrudePath; var extrudePts, extrudeByPath = false; var material = options.material; var extrudeMaterial = options.extrudeMaterial; // Use default WorldUVGenerator if no UV generators are specified. var uvgen = options.UVGenerator !== undefined ? options.UVGenerator : THREE.ExtrudeGeometry.WorldUVGenerator; var shapebb = this.shapebb; //shapebb = shape.getBoundingBox(); var splineTube, binormal, normal, position2; if ( extrudePath ) { extrudePts = extrudePath.getSpacedPoints( steps ); extrudeByPath = true; bevelEnabled = false; // bevels not supported for path extrusion // SETUP TNB variables // Reuse TNB from TubeGeomtry for now. // TODO1 - have a .isClosed in spline? splineTube = options.frames !== undefined ? options.frames : new THREE.TubeGeometry.FrenetFrames(extrudePath, steps, false); // console.log(splineTube, 'splineTube', splineTube.normals.length, 'steps', steps, 'extrudePts', extrudePts.length); binormal = new THREE.Vector3(); normal = new THREE.Vector3(); position2 = new THREE.Vector3(); } // Safeguards if bevels are not enabled if ( ! bevelEnabled ) { bevelSegments = 0; bevelThickness = 0; bevelSize = 0; } // Variables initalization var ahole, h, hl; // looping of holes var scope = this; var bevelPoints = []; var shapesOffset = this.vertices.length; var shapePoints = shape.extractPoints( curveSegments ); var vertices = shapePoints.shape; var holes = shapePoints.holes; var reverse = !THREE.Shape.Utils.isClockWise( vertices ) ; if ( reverse ) { vertices = vertices.reverse(); // Maybe we should also check if holes are in the opposite direction, just to be safe ... for ( h = 0, hl = holes.length; h < hl; h ++ ) { ahole = holes[ h ]; if ( THREE.Shape.Utils.isClockWise( ahole ) ) { holes[ h ] = ahole.reverse(); } } reverse = false; // If vertices are in order now, we shouldn't need to worry about them again (hopefully)! } var faces = THREE.Shape.Utils.triangulateShape ( vertices, holes ); /* Vertices */ var contour = vertices; // vertices has all points but contour has only points of circumference for ( h = 0, hl = holes.length; h < hl; h ++ ) { ahole = holes[ h ]; vertices = vertices.concat( ahole ); } function scalePt2 ( pt, vec, size ) { if ( !vec ) console.log( "die" ); return vec.clone().multiplyScalar( size ).add( pt ); } var b, bs, t, z, vert, vlen = vertices.length, face, flen = faces.length, cont, clen = contour.length; // Find directions for point movement var RAD_TO_DEGREES = 180 / Math.PI; function getBevelVec( pt_i, pt_j, pt_k ) { // Algorithm 2 return getBevelVec2( pt_i, pt_j, pt_k ); } function getBevelVec1( pt_i, pt_j, pt_k ) { var anglea = Math.atan2( pt_j.y - pt_i.y, pt_j.x - pt_i.x ); var angleb = Math.atan2( pt_k.y - pt_i.y, pt_k.x - pt_i.x ); if ( anglea > angleb ) { angleb += Math.PI * 2; } var anglec = ( anglea + angleb ) / 2; //console.log('angle1', anglea * RAD_TO_DEGREES,'angle2', angleb * RAD_TO_DEGREES, 'anglec', anglec *RAD_TO_DEGREES); var x = - Math.cos( anglec ); var y = - Math.sin( anglec ); var vec = new THREE.Vector2( x, y ); //.normalize(); return vec; } function getBevelVec2( pt_i, pt_j, pt_k ) { var a = THREE.ExtrudeGeometry.__v1, b = THREE.ExtrudeGeometry.__v2, v_hat = THREE.ExtrudeGeometry.__v3, w_hat = THREE.ExtrudeGeometry.__v4, p = THREE.ExtrudeGeometry.__v5, q = THREE.ExtrudeGeometry.__v6, v, w, v_dot_w_hat, q_sub_p_dot_w_hat, s, intersection; // good reading for line-line intersection // http://sputsoft.com/blog/2010/03/line-line-intersection.html // define a as vector j->i // define b as vectot k->i a.set( pt_i.x - pt_j.x, pt_i.y - pt_j.y ); b.set( pt_i.x - pt_k.x, pt_i.y - pt_k.y ); // get unit vectors v = a.normalize(); w = b.normalize(); // normals from pt i v_hat.set( -v.y, v.x ); w_hat.set( w.y, -w.x ); // pts from i p.copy( pt_i ).add( v_hat ); q.copy( pt_i ).add( w_hat ); if ( p.equals( q ) ) { //console.log("Warning: lines are straight"); return w_hat.clone(); } // Points from j, k. helps prevents points cross overover most of the time p.copy( pt_j ).add( v_hat ); q.copy( pt_k ).add( w_hat ); v_dot_w_hat = v.dot( w_hat ); q_sub_p_dot_w_hat = q.sub( p ).dot( w_hat ); // We should not reach these conditions if ( v_dot_w_hat === 0 ) { console.log( "Either infinite or no solutions!" ); if ( q_sub_p_dot_w_hat === 0 ) { console.log( "Its finite solutions." ); } else { console.log( "Too bad, no solutions." ); } } s = q_sub_p_dot_w_hat / v_dot_w_hat; if ( s < 0 ) { // in case of emergecy, revert to algorithm 1. return getBevelVec1( pt_i, pt_j, pt_k ); } intersection = v.multiplyScalar( s ).add( p ); return intersection.sub( pt_i ).clone(); // Don't normalize!, otherwise sharp corners become ugly } var contourMovements = []; for ( var i = 0, il = contour.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) { if ( j === il ) j = 0; if ( k === il ) k = 0; // (j)---(i)---(k) // console.log('i,j,k', i, j , k) var pt_i = contour[ i ]; var pt_j = contour[ j ]; var pt_k = contour[ k ]; contourMovements[ i ]= getBevelVec( contour[ i ], contour[ j ], contour[ k ] ); } var holesMovements = [], oneHoleMovements, verticesMovements = contourMovements.concat(); for ( h = 0, hl = holes.length; h < hl; h ++ ) { ahole = holes[ h ]; oneHoleMovements = []; for ( i = 0, il = ahole.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) { if ( j === il ) j = 0; if ( k === il ) k = 0; // (j)---(i)---(k) oneHoleMovements[ i ]= getBevelVec( ahole[ i ], ahole[ j ], ahole[ k ] ); } holesMovements.push( oneHoleMovements ); verticesMovements = verticesMovements.concat( oneHoleMovements ); } // Loop bevelSegments, 1 for the front, 1 for the back for ( b = 0; b < bevelSegments; b ++ ) { //for ( b = bevelSegments; b > 0; b -- ) { t = b / bevelSegments; z = bevelThickness * ( 1 - t ); //z = bevelThickness * t; bs = bevelSize * ( Math.sin ( t * Math.PI/2 ) ) ; // curved //bs = bevelSize * t ; // linear // contract shape for ( i = 0, il = contour.length; i < il; i ++ ) { vert = scalePt2( contour[ i ], contourMovements[ i ], bs ); //vert = scalePt( contour[ i ], contourCentroid, bs, false ); v( vert.x, vert.y, - z ); } // expand holes for ( h = 0, hl = holes.length; h < hl; h++ ) { ahole = holes[ h ]; oneHoleMovements = holesMovements[ h ]; for ( i = 0, il = ahole.length; i < il; i++ ) { vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs ); //vert = scalePt( ahole[ i ], holesCentroids[ h ], bs, true ); v( vert.x, vert.y, -z ); } } } bs = bevelSize; // Back facing vertices for ( i = 0; i < vlen; i ++ ) { vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ]; if ( !extrudeByPath ) { v( vert.x, vert.y, 0 ); } else { // v( vert.x, vert.y + extrudePts[ 0 ].y, extrudePts[ 0 ].x ); normal.copy( splineTube.normals[0] ).multiplyScalar(vert.x); binormal.copy( splineTube.binormals[0] ).multiplyScalar(vert.y); position2.copy( extrudePts[0] ).add(normal).add(binormal); v( position2.x, position2.y, position2.z ); } } // Add stepped vertices... // Including front facing vertices var s; for ( s = 1; s <= steps; s ++ ) { for ( i = 0; i < vlen; i ++ ) { vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ]; if ( !extrudeByPath ) { v( vert.x, vert.y, amount / steps * s ); } else { // v( vert.x, vert.y + extrudePts[ s - 1 ].y, extrudePts[ s - 1 ].x ); normal.copy( splineTube.normals[s] ).multiplyScalar( vert.x ); binormal.copy( splineTube.binormals[s] ).multiplyScalar( vert.y ); position2.copy( extrudePts[s] ).add( normal ).add( binormal ); v( position2.x, position2.y, position2.z ); } } } // Add bevel segments planes //for ( b = 1; b <= bevelSegments; b ++ ) { for ( b = bevelSegments - 1; b >= 0; b -- ) { t = b / bevelSegments; z = bevelThickness * ( 1 - t ); //bs = bevelSize * ( 1-Math.sin ( ( 1 - t ) * Math.PI/2 ) ); bs = bevelSize * Math.sin ( t * Math.PI/2 ) ; // contract shape for ( i = 0, il = contour.length; i < il; i ++ ) { vert = scalePt2( contour[ i ], contourMovements[ i ], bs ); v( vert.x, vert.y, amount + z ); } // expand holes for ( h = 0, hl = holes.length; h < hl; h ++ ) { ahole = holes[ h ]; oneHoleMovements = holesMovements[ h ]; for ( i = 0, il = ahole.length; i < il; i ++ ) { vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs ); if ( !extrudeByPath ) { v( vert.x, vert.y, amount + z ); } else { v( vert.x, vert.y + extrudePts[ steps - 1 ].y, extrudePts[ steps - 1 ].x + z ); } } } } /* Faces */ // Top and bottom faces buildLidFaces(); // Sides faces buildSideFaces(); ///// Internal functions function buildLidFaces() { if ( bevelEnabled ) { var layer = 0 ; // steps + 1 var offset = vlen * layer; // Bottom faces for ( i = 0; i < flen; i ++ ) { face = faces[ i ]; f3( face[ 2 ]+ offset, face[ 1 ]+ offset, face[ 0 ] + offset, true ); } layer = steps + bevelSegments * 2; offset = vlen * layer; // Top faces for ( i = 0; i < flen; i ++ ) { face = faces[ i ]; f3( face[ 0 ] + offset, face[ 1 ] + offset, face[ 2 ] + offset, false ); } } else { // Bottom faces for ( i = 0; i < flen; i++ ) { face = faces[ i ]; f3( face[ 2 ], face[ 1 ], face[ 0 ], true ); } // Top faces for ( i = 0; i < flen; i ++ ) { face = faces[ i ]; f3( face[ 0 ] + vlen * steps, face[ 1 ] + vlen * steps, face[ 2 ] + vlen * steps, false ); } } } // Create faces for the z-sides of the shape function buildSideFaces() { var layeroffset = 0; sidewalls( contour, layeroffset ); layeroffset += contour.length; for ( h = 0, hl = holes.length; h < hl; h ++ ) { ahole = holes[ h ]; sidewalls( ahole, layeroffset ); //, true layeroffset += ahole.length; } } function sidewalls( contour, layeroffset ) { var j, k; i = contour.length; while ( --i >= 0 ) { j = i; k = i - 1; if ( k < 0 ) k = contour.length - 1; //console.log('b', i,j, i-1, k,vertices.length); var s = 0, sl = steps + bevelSegments * 2; for ( s = 0; s < sl; s ++ ) { var slen1 = vlen * s; var slen2 = vlen * ( s + 1 ); var a = layeroffset + j + slen1, b = layeroffset + k + slen1, c = layeroffset + k + slen2, d = layeroffset + j + slen2; f4( a, b, c, d, contour, s, sl, j, k ); } } } function v( x, y, z ) { scope.vertices.push( new THREE.Vector3( x, y, z ) ); } function f3( a, b, c, isBottom ) { a += shapesOffset; b += shapesOffset; c += shapesOffset; // normal, color, material scope.faces.push( new THREE.Face3( a, b, c, null, null, material ) ); var uvs = isBottom ? uvgen.generateBottomUV( scope, shape, options, a, b, c ) : uvgen.generateTopUV( scope, shape, options, a, b, c ); scope.faceVertexUvs[ 0 ].push( uvs ); } function f4( a, b, c, d, wallContour, stepIndex, stepsLength, contourIndex1, contourIndex2 ) { a += shapesOffset; b += shapesOffset; c += shapesOffset; d += shapesOffset; scope.faces.push( new THREE.Face4( a, b, c, d, null, null, extrudeMaterial ) ); var uvs = uvgen.generateSideWallUV( scope, shape, wallContour, options, a, b, c, d, stepIndex, stepsLength, contourIndex1, contourIndex2 ); scope.faceVertexUvs[ 0 ].push( uvs ); } }; THREE.ExtrudeGeometry.WorldUVGenerator = { generateTopUV: function( geometry, extrudedShape, extrudeOptions, indexA, indexB, indexC ) { var ax = geometry.vertices[ indexA ].x, ay = geometry.vertices[ indexA ].y, bx = geometry.vertices[ indexB ].x, by = geometry.vertices[ indexB ].y, cx = geometry.vertices[ indexC ].x, cy = geometry.vertices[ indexC ].y; return [ new THREE.Vector2( ax, ay ), new THREE.Vector2( bx, by ), new THREE.Vector2( cx, cy ) ]; }, generateBottomUV: function( geometry, extrudedShape, extrudeOptions, indexA, indexB, indexC ) { return this.generateTopUV( geometry, extrudedShape, extrudeOptions, indexA, indexB, indexC ); }, generateSideWallUV: function( geometry, extrudedShape, wallContour, extrudeOptions, indexA, indexB, indexC, indexD, stepIndex, stepsLength, contourIndex1, contourIndex2 ) { var ax = geometry.vertices[ indexA ].x, ay = geometry.vertices[ indexA ].y, az = geometry.vertices[ indexA ].z, bx = geometry.vertices[ indexB ].x, by = geometry.vertices[ indexB ].y, bz = geometry.vertices[ indexB ].z, cx = geometry.vertices[ indexC ].x, cy = geometry.vertices[ indexC ].y, cz = geometry.vertices[ indexC ].z, dx = geometry.vertices[ indexD ].x, dy = geometry.vertices[ indexD ].y, dz = geometry.vertices[ indexD ].z; if ( Math.abs( ay - by ) < 0.01 ) { return [ new THREE.Vector2( ax, 1 - az ), new THREE.Vector2( bx, 1 - bz ), new THREE.Vector2( cx, 1 - cz ), new THREE.Vector2( dx, 1 - dz ) ]; } else { return [ new THREE.Vector2( ay, 1 - az ), new THREE.Vector2( by, 1 - bz ), new THREE.Vector2( cy, 1 - cz ), new THREE.Vector2( dy, 1 - dz ) ]; } } }; THREE.ExtrudeGeometry.__v1 = new THREE.Vector2(); THREE.ExtrudeGeometry.__v2 = new THREE.Vector2(); THREE.ExtrudeGeometry.__v3 = new THREE.Vector2(); THREE.ExtrudeGeometry.__v4 = new THREE.Vector2(); THREE.ExtrudeGeometry.__v5 = new THREE.Vector2(); THREE.ExtrudeGeometry.__v6 = new THREE.Vector2();