/**
* @author zz85 / http://www.lab4games.net/zz85/blog
*
* Creates extruded geometry from a path shape.
*
* parameters = {
*
* size: <float>, // size of the text
* height: <float>, // thickness to extrude text
* curveSegments: <int>, // number of points on the curves
* steps: <int>, // number of points for z-side extrusions
*
* font: <string>, // font name
* weight: <string>, // font weight (normal, bold)
* style: <string>, // font style (normal, italics)
*
* bevelEnabled: <bool>, // turn on bevel
* bevelThickness: <float>, // how deep into text bevel goes
* bevelSize: <float>, // how far from text outline is bevel
* bevelSegments: <int>, // number of bevel layers
*
* extrudePath: <THREE.CurvePath> // path to extrude shape along
* bendPath: <THREE.CurvePath> // path to bend the geometry around
*
* material: <THREE.Material> // material for front and back faces
* extrudeMaterial: <THREE.Material> // material for extrusion and beveled faces
*
* }
**/
THREE.ExtrudeGeometry = function( shapes, options ) {
if( typeof( shapes ) === "undefined" ) {
shapes = [];
return;
}
THREE.Geometry.call( this );
shapes = shapes instanceof Array ? shapes : [ shapes ];
var s, sl = shapes.length, shape;
this.shapebb = shapes[ sl - 1 ].getBoundingBox();
for ( s = 0; s < sl; s ++ ) {
shape = shapes[ s ];
this.addShape( shape, 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 = new THREE.Geometry();
THREE.ExtrudeGeometry.prototype.constructor = THREE.ExtrudeGeometry;
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 bendPath = options.bendPath;
var extrudePath = options.extrudePath;
var extrudePts, extrudeByPath = false;
var useSpacedPoints = options.useSpacedPoints !== undefined ? options.useSpacedPoints : false;
var material = options.material;
var extrudeMaterial = options.extrudeMaterial;
var shapebb = this.shapebb;
//shapebb = shape.getBoundingBox();
if ( extrudePath ) {
extrudePts = extrudePath.getPoints( curveSegments );
steps = extrudePts.length;
extrudeByPath = true;
bevelEnabled = false; // bevels not supported for path extrusion
}
// Safeguards if bevels are not enabled
if ( !bevelEnabled ) {
bevelSegments = 0;
bevelThickness = 0;
bevelSize = 0;
}
// TODO, extrude by path's tangents? also via 3d path?
// Variables initalization
var ahole, h, hl; // looping of holes
var scope = this;
var bevelPoints = [];
var shapesOffset = this.vertices.length;
if ( bendPath ) {
shape.addWrapPath( bendPath );
}
var shapePoints;
if ( !useSpacedPoints ) {
shapePoints = shape.extractAllPoints( curveSegments ); //
} else {
// QN - Would it be better to pass useSpacePoints parameter to shape, just like bendpath ?
shapePoints = shape.extractAllSpacedPoints( curveSegments ) // for points with equal divisions
}
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 );
//var faces = THREE.Shape.Utils.triangulate2( vertices, holes );
// Would it be better to move points after triangulation?
// shapePoints = shape.extractAllPointsWithBend( curveSegments, bendPath );
// vertices = shapePoints.shape;
// holes = shapePoints.holes;
//console.log(faces);
////
/// Handle 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 );
}
var i, il;
function scalePt2 ( pt, vec, size ) {
if ( !vec ) console.log( "die" );
return vec.clone().multiplyScalar( size ).addSelf( 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 ).addSelf( v_hat );
q.copy( pt_i ).addSelf( 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 ).addSelf( v_hat );
q.copy( pt_k ).addSelf( w_hat );
v_dot_w_hat = v.dot( w_hat );
q_sub_p_dot_w_hat = q.subSelf( 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 ).addSelf( p );
return intersection.subSelf( pt_i ).clone(); // Don't normalize!, otherwise sharp corners become ugly
}
var contourMovements = [];
for ( 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 );
}
}
// 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 );
}
}
}
// 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 );
}
}
}
}
////
/// Handle Faces
////
// Bottom faces
if ( bevelEnabled ) {
var layer = 0 ; // steps + 1
var offset = vlen * layer;
for ( i = 0; i < flen; i ++ ) {
face = faces[ i ];
f3( face[ 2 ]+ offset, face[ 1 ]+ offset, face[ 0 ] + offset );
}
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 );
}
} else {
for ( i = 0; i < flen; i++ ) {
face = faces[ i ];
f3( face[ 2 ], face[ 1 ], face[ 0 ] );
}
// Top faces
for ( i = 0; i < flen; i ++ ) {
face = faces[ i ];
f3( face[ 0 ] + vlen * steps, face[ 1 ] + vlen * steps, face[ 2 ] + vlen * steps );
}
}
var tmpPt;
var j, k, l, m;
var layeroffset = 0;
// Sides faces
sidewalls( contour );
layeroffset += contour.length;
for ( h = 0, hl = holes.length; h < hl; h ++ ) {
ahole = holes[ h ];
sidewalls( ahole );
//, true
layeroffset += ahole.length;
}
// Create faces for the z-sides of the shape
function sidewalls( contour ) {
i = contour.length;
while ( --i >= 0 ) {
tmpPt = contour[ i ];
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 );
if ( extrudeMaterial ) {
var v1 = s / sl;
var v2 = ( s + 1 ) / sl;
var ztol = ( amount + bevelThickness * 2 );
var u1 = ( scope.vertices[ a ].position.z + bevelThickness ) / ztol;
var u2 = ( scope.vertices[ d ].position.z + bevelThickness ) / ztol;
//console.log(vy1, vy2);
scope.faceVertexUvs[ 0 ].push( [
new THREE.UV( u1, v1 ),
new THREE.UV( u2, v1 ),
new THREE.UV( u2, v2 ),
new THREE.UV( u1, v2 )
] );
}
}
}
}
function v( x, y, z ) {
scope.vertices.push( new THREE.Vertex( new THREE.Vector3( x, y, z ) ) );
}
function f3( a, b, c ) {
a += shapesOffset;
b += shapesOffset;
c += shapesOffset;
scope.faces.push( new THREE.Face3( a, b, c, null, null, material ) );
//normal, color, materials
if ( material ) {
var mx = shapebb.minX, my = shapebb.minY;
var uy = shapebb.maxY; // - shapebb.minY;
var ux = shapebb.maxX; // - shapebb.minX;
var ax = scope.vertices[ a ].position.x,
ay = scope.vertices[ a ].position.y,
bx = scope.vertices[ b ].position.x,
by = scope.vertices[ b ].position.y,
cx = scope.vertices[ c ].position.x,
cy = scope.vertices[ c ].position.y;
scope.faceVertexUvs[ 0 ].push( [
new THREE.UV( ax / ux, ay / uy ),
new THREE.UV( bx / ux, by / uy ),
new THREE.UV( cx / ux, cy / uy )
] );
}
}
function f4( a, b, c, d ) {
a += shapesOffset;
b += shapesOffset;
c += shapesOffset;
d += shapesOffset;
scope.faces.push( new THREE.Face4( a, b, c, d, null, null, extrudeMaterial ) );
}
};
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();