Dart DocumentationthreeExtrudeGeometry

ExtrudeGeometry class

@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> | <List>, // number of points or list of U positions for z-side extrusions / used for subdividing segements of extrude spline too amount: <int>, // Amount

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> // 2d/3d spline path to extrude shape orthogonality to frames: <THREE.TubeGeometry.FrenetFrames> // containing arrays of tangents, normals, binormals

bendPath: <THREE.CurvePath> // 2d path for bend the shape around x/y plane

material: <int> // material index for front and back faces extrudeMaterial: <int> // material index for extrusion and beveled faces

}

class ExtrudeGeometry extends Geometry {

 List shapes;

 var shapebb;

 ExtrudeGeometry( this.shapes,
                 {
                   amount: 100,
                   bevelThickness: 6.0,
                   bevelSize: null,
                   bevelSegments: 3,
                   bevelEnabled: true,
                   curveSegments: 12,
                   steps: 1, //can assume a number of steps or a List with all U's of steps
                   bendPath,
                   extrudePath,
                   frames,
                   material,
                   extrudeMaterial } ) : super() {

   if (bevelSize == null) bevelSize = bevelThickness - 2.0;

   if (shapes == null) {
     shapes = [];
     return;
   }

   shapebb = shapes.last.getBoundingBox();

   addShapeList( shapes,
     amount, bevelThickness, bevelSize, bevelSegments,bevelEnabled,
     curveSegments, steps, bendPath, extrudePath, frames, material, extrudeMaterial );

   computeCentroids();
   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 ) );

 }



 addShapeList(shapes, amount, bevelThickness, bevelSize, bevelSegments,bevelEnabled,
              curveSegments,steps,bendPath,extrudePath,frames,material, extrudeMaterial) {
   var sl = shapes.length;

   for ( var s = 0; s < sl; s ++ ) {
     var shape = shapes[ s ];
     addShape( shape, amount, bevelThickness, bevelSize, bevelSegments,bevelEnabled,
               curveSegments, steps, bendPath, extrudePath, frames, material, extrudeMaterial );
   }
 }

 // addShape Helpers
 _scalePt2 ( Vector2 pt, Vector2 vec, num size ) {
   if ( vec == null ) print( "die" );
   return vec.clone().scale( size ).add( pt );
 }

 _getBevelVec2( Vector2 pt_i, Vector2 pt_j, Vector2 pt_k ) {

   var a = ExtrudeGeometry.__v1,
     b = ExtrudeGeometry.__v2,
     v_hat = ExtrudeGeometry.__v3,
     w_hat = ExtrudeGeometry.__v4,
     p = ExtrudeGeometry.__v5,
     q = 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.setValues( pt_i.x - pt_j.x, pt_i.y - pt_j.y );
   b.setValues( 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.setValues( -v.y, v.x );
   w_hat.setValues( w.y, -w.x );

   // pts from i

   p.setFrom( pt_i ).add( v_hat );
   q.setFrom( pt_i ).add( w_hat );

   if ( p.x == q.x && p.y == q.y) { // TODO add vector_math ".equals(p, 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.setFrom( pt_j ).add( v_hat );
   q.setFrom( 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 ) {

     print( "Either infinite or no solutions!" );

     if ( q_sub_p_dot_w_hat == 0 ) {

       print( "Its finite solutions." );

     } else {

       print( "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.scale( s ).add( p );

   return intersection.sub( pt_i ).clone(); // Don't normalize!, otherwise sharp corners become ugly

 }


 static var RAD_TO_DEGREES = 180 / Math.PI;


// Algorithm 2
 _getBevelVec( pt_i, pt_j, pt_k ) => _getBevelVec2( pt_i, pt_j, pt_k );

 _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 Vector2( x, y ); //.normalize();

   return vec;

 }

   _v( x, y, z ) {
     vertices.add( new Vector3( x, y, z ) );
   }


   // TODO - This is a helper function to reverse a list added by nelsonsilva
  List _reverse(List list) {
     List reversed = [];
     var i = list.length;
     while (i > 0) {
       reversed.add(list[--i]);
     }
     return reversed;
  }

 addShape( Shape shape, amount, bevelThickness, bevelSize, bevelSegments, bevelEnabled,
           curveSegments, steps, bendPath, extrudePath, TubeGeometry frames, material, extrudeMaterial,
           {ExtrudeGeometryWorldUVGenerator UVGenerator }) {


   var extrudePts, extrudeByPath = false;

   //shapebb = shape.getBoundingBox();

   // set UV generator
   var uvgen = (UVGenerator!= null) ? UVGenerator : new ExtrudeGeometryWorldUVGenerator();

   TubeGeometry splineTube;
   Vector3 binormal, normal, position2;

   var nSteps = (steps is List)? steps.length : steps;

   if ( extrudePath != null ) {

     if(steps is List){
       List divisions = [0];
       divisions.addAll(steps);
       extrudePts = extrudePath.getUPoints(divisions);
     } else{
       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 = (frames != null) ? frames : new TubeGeometry.FrenetFrames(extrudePath, steps, false);

     // console.log(splineTube, 'splineTube', splineTube.normals.length, 'steps', steps, 'extrudePts', extrudePts.length);

     binormal = new Vector3.zero();
     normal = new Vector3.zero();
     position2 = new Vector3.zero();

   }

   // Safeguards if bevels are not enabled

   if ( ! bevelEnabled ) {

     bevelSegments = 0;
     bevelThickness = 0;
     bevelSize = 0;

   }

   // Variables initalization

   List<Vector2> ahole;
   var h, hl; // looping of holes

   var bevelPoints = [];

   var shapesOffset = this.vertices.length;

   if ( bendPath != null ) {

     shape.addWrapPath( bendPath );

   }

   var shapePoints = shape.extractPoints();

   List vertices = shapePoints["shape"];
   List<List<Vector2>> holes = shapePoints["holes"];

   var reverse = !ShapeUtils.isClockWise( vertices ) ;

   if ( reverse ) {

     vertices = _reverse(vertices);

     // Maybe we should also check if holes are in the opposite direction, just to be safe ...

     for ( h = 0; h < holes.length; h ++ ) {

       ahole = holes[ h ];

       if ( ShapeUtils.isClockWise( ahole ) ) {

         holes[ h ] = _reverse(ahole);

       }

     }

     reverse = false; // If vertices are in order now, we shouldn't need to worry about them again (hopefully)!

   }



   var faces = ShapeUtils.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;  h < holes.length; h ++ ) {

     ahole = holes[ h ];

     vertices = new List.from(vertices);
     vertices.addAll( ahole );

   }


   var b, bs, t, z,
     vert, vlen = vertices.length,
     face, flen = faces.length,
     cont, clen = contour.length;


   //------
   // Find directions for point movement
   //



   var contourMovements = new List(contour.length);

   num i = 0,
       il = contour.length,
       j = il - 1,
       k = i + 1;
   for ( i = 0; i < il; i ++ ) {

     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 ] );
     j ++; k ++;
   }

   List  holesMovements = [],
         oneHoleMovements,
         verticesMovements = new List.from(contourMovements);

   for ( h = 0; h < holes.length; h ++ ) {

     ahole = holes[ h ];

     oneHoleMovements = new List(ahole.length);

     i = 0;
     il = ahole.length;
     j = il - 1;
     k = i + 1;

     for ( i = 0; i < il; i++) {

       if ( j == il ) j = 0;
       if ( k == il ) k = 0;

       //  (j)---(i)---(k)
       oneHoleMovements[ i ]= _getBevelVec( ahole[ i ], ahole[ j ], ahole[ k ] );
       j++;
       k++;
     }

     holesMovements.add( oneHoleMovements );
     verticesMovements.addAll( 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; i < contour.length; 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; h < holes.length; h++ ) {

       ahole = holes[ h ];
       oneHoleMovements = holesMovements[ h ];

       for ( i = 0; i < ahole.length; 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.0 );

     } else {

       // v( vert.x, vert.y + extrudePts[ 0 ].y, extrudePts[ 0 ].x );

       normal.setFrom(splineTube.normals[0]).scale(vert.x);
       binormal.setFrom(splineTube.binormals[0]).scale(vert.y);

       position2.setFrom(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 <= nSteps; 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 / nSteps * s );

       } else {

         // v( vert.x, vert.y + extrudePts[ s - 1 ].y, extrudePts[ s - 1 ].x );

         normal.setFrom(splineTube.normals[s]).scale(vert.x);
         binormal.setFrom(splineTube.binormals[s]).scale(vert.y);

         position2.setFrom(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; i < contour.length; i ++ ) {

       vert = _scalePt2( contour[ i ], contourMovements[ i ], bs );
       _v( vert.x, vert.y,  amount + z );

     }

     // expand holes

     for ( h = 0; h < holes.length; h ++ ) {

       ahole = holes[ h ];
       oneHoleMovements = holesMovements[ h ];

       for ( i = 0; i < ahole.length; i++ ) {

         vert = _scalePt2( ahole[ i ], oneHoleMovements[ i ], bs );

         if ( !extrudeByPath ) {

           _v( vert.x, vert.y,  amount + z );

         } else {

           _v( vert.x, vert.y + extrudePts[ nSteps - 1 ].y, extrudePts[ nSteps - 1 ].x + z );

         }

       }

     }

   }


   ////
   ///   Handle Faces
   ////

 /////  Internal functions

   f3( a, b, c, isBottom ) {
     a += shapesOffset;
     b += shapesOffset;
     c += shapesOffset;

     // normal, color, material
     this.faces.add( new Face3( a, b, c, null, null, material ) );

     var uvs = isBottom ? uvgen.generateBottomUV( this, shape, null, a, b, c)
                        : uvgen.generateTopUV( this, shape, null, a, b, c);

     this.faceVertexUvs[ 0 ].add(uvs);
   }

   f4( a, b, c, d, wallContour, stepIndex, stepsLength ) {
     a += shapesOffset;
     b += shapesOffset;
     c += shapesOffset;
     d += shapesOffset;

     this.faces.add( new Face4( a, b, c, d, null, null, extrudeMaterial ) );

     var uvs = uvgen.generateSideWallUV( this, shape, wallContour, null, a, b, c, d, stepIndex, stepsLength);
     this.faceVertexUvs[ 0 ].add(uvs);
   }

   // Top and bottom faces
   //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 = nSteps + 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 * nSteps, face[ 1 ] + vlen * nSteps, face[ 2 ] + vlen * nSteps, false );
     }
   }

   sidewalls( contour, layeroffset ) {
     var i, 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 = nSteps  + 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 );
       }
     }
   }

   // Sides faces
   //buildSideFaces() {
   // Create faces for the z-sides of the shape
   var layeroffset = 0;
   sidewalls( contour, layeroffset );
   layeroffset += contour.length;

   for ( h = 0;  h < holes.length; h ++ ) {
     ahole = holes[ h ];
     sidewalls( ahole, layeroffset );

     //, true
     layeroffset += ahole.length;
   }
 }


 static Vector2 ___v1 = null; static get __v1 => (___v1 == null)? ___v1 = new Vector2.zero() : ___v1;
 static Vector2 ___v2 = null; static get __v2 => (___v2 == null)? ___v2 = new Vector2.zero() : ___v2;
 static Vector2 ___v3 = null; static get __v3 => (___v3 == null)? ___v3 = new Vector2.zero() : ___v3;
 static Vector2 ___v4 = null; static get __v4 => (___v4 == null)? ___v4 = new Vector2.zero() : ___v4;
 static Vector2 ___v5 = null; static get __v5 => (___v5 == null)? ___v5 = new Vector2.zero() : ___v5;
 static Vector2 ___v6 = null; static get __v6 => (___v6 == null)? ___v6 = new Vector2.zero() : ___v6;
}

Extends

Geometry > ExtrudeGeometry

Subclasses

TextGeometry

Static Properties

var RAD_TO_DEGREES #

static var RAD_TO_DEGREES = 180 / Math.PI

Constructors

new ExtrudeGeometry(List shapes, {amount: 100, bevelThickness: 6.0, bevelSize: null, bevelSegments: 3, bevelEnabled: true, curveSegments: 12, steps: 1, bendPath, extrudePath, frames, material, extrudeMaterial}) #

Creates a new Object instance.

Object instances have no meaningful state, and are only useful through their identity. An Object instance is equal to itself only.

docs inherited from Object
ExtrudeGeometry( this.shapes,
               {
                 amount: 100,
                 bevelThickness: 6.0,
                 bevelSize: null,
                 bevelSegments: 3,
                 bevelEnabled: true,
                 curveSegments: 12,
                 steps: 1, //can assume a number of steps or a List with all U's of steps
                 bendPath,
                 extrudePath,
                 frames,
                 material,
                 extrudeMaterial } ) : super() {

 if (bevelSize == null) bevelSize = bevelThickness - 2.0;

 if (shapes == null) {
   shapes = [];
   return;
 }

 shapebb = shapes.last.getBoundingBox();

 addShapeList( shapes,
   amount, bevelThickness, bevelSize, bevelSegments,bevelEnabled,
   curveSegments, steps, bendPath, extrudePath, frames, material, extrudeMaterial );

 computeCentroids();
 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 ) );

}

Properties

var animation #

inherited from Geometry
var bones, animation

var bones #

inherited from Geometry
var bones

BoundingBox boundingBox #

inherited from Geometry
BoundingBox boundingBox

BoundingSphere boundingSphere #

inherited from Geometry
BoundingSphere boundingSphere

List colors #

inherited from Geometry
List colors

List<Face> faces #

inherited from Geometry
List<Face> faces

List faceUvs #

inherited from Geometry
List faceUvs

List<List> faceVertexUvs #

inherited from Geometry
List<List> faceVertexUvs

bool hasTangents #

inherited from Geometry
bool hasTangents

int id #

inherited from Geometry
int id

bool isDynamic #

inherited from Geometry
bool get isDynamic => _dynamic;
set isDynamic(bool value) => _dynamic = value;

List lineDistances #

inherited from Geometry
List lineDistances

List materials #

inherited from Geometry
List materials

List morphColors #

inherited from Geometry
List morphColors

List morphNormals #

inherited from Geometry
List morphColors, morphNormals

List<MorphTarget> morphTargets #

inherited from Geometry
List<MorphTarget> morphTargets

String name #

inherited from Geometry
String name

List normals #

inherited from Geometry
List normals = []

var shapebb #

var shapebb

List shapes #

List shapes

List skinIndices #

inherited from Geometry
List skinWeights, skinIndices

List skinWeights #

inherited from Geometry
List skinWeights

List<Vector3> vertices #

inherited from Geometry
List<Vector3> vertices

Operators

dynamic operator [](String key) #

inherited from Geometry
operator [] (String key) => _data[key];

dynamic operator []=(String key, value) #

inherited from Geometry
operator []= (String key, value) => _data[key] = value;

Methods

dynamic addShape(Shape shape, amount, bevelThickness, bevelSize, bevelSegments, bevelEnabled, curveSegments, steps, bendPath, extrudePath, TubeGeometry frames, material, extrudeMaterial, {ExtrudeGeometryWorldUVGenerator UVGenerator}) #

addShape( Shape shape, amount, bevelThickness, bevelSize, bevelSegments, bevelEnabled,
         curveSegments, steps, bendPath, extrudePath, TubeGeometry frames, material, extrudeMaterial,
         {ExtrudeGeometryWorldUVGenerator UVGenerator }) {


 var extrudePts, extrudeByPath = false;

 //shapebb = shape.getBoundingBox();

 // set UV generator
 var uvgen = (UVGenerator!= null) ? UVGenerator : new ExtrudeGeometryWorldUVGenerator();

 TubeGeometry splineTube;
 Vector3 binormal, normal, position2;

 var nSteps = (steps is List)? steps.length : steps;

 if ( extrudePath != null ) {

   if(steps is List){
     List divisions = [0];
     divisions.addAll(steps);
     extrudePts = extrudePath.getUPoints(divisions);
   } else{
     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 = (frames != null) ? frames : new TubeGeometry.FrenetFrames(extrudePath, steps, false);

   // console.log(splineTube, 'splineTube', splineTube.normals.length, 'steps', steps, 'extrudePts', extrudePts.length);

   binormal = new Vector3.zero();
   normal = new Vector3.zero();
   position2 = new Vector3.zero();

 }

 // Safeguards if bevels are not enabled

 if ( ! bevelEnabled ) {

   bevelSegments = 0;
   bevelThickness = 0;
   bevelSize = 0;

 }

 // Variables initalization

 List<Vector2> ahole;
 var h, hl; // looping of holes

 var bevelPoints = [];

 var shapesOffset = this.vertices.length;

 if ( bendPath != null ) {

   shape.addWrapPath( bendPath );

 }

 var shapePoints = shape.extractPoints();

 List vertices = shapePoints["shape"];
 List<List<Vector2>> holes = shapePoints["holes"];

 var reverse = !ShapeUtils.isClockWise( vertices ) ;

 if ( reverse ) {

   vertices = _reverse(vertices);

   // Maybe we should also check if holes are in the opposite direction, just to be safe ...

   for ( h = 0; h < holes.length; h ++ ) {

     ahole = holes[ h ];

     if ( ShapeUtils.isClockWise( ahole ) ) {

       holes[ h ] = _reverse(ahole);

     }

   }

   reverse = false; // If vertices are in order now, we shouldn't need to worry about them again (hopefully)!

 }



 var faces = ShapeUtils.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;  h < holes.length; h ++ ) {

   ahole = holes[ h ];

   vertices = new List.from(vertices);
   vertices.addAll( ahole );

 }


 var b, bs, t, z,
   vert, vlen = vertices.length,
   face, flen = faces.length,
   cont, clen = contour.length;


 //------
 // Find directions for point movement
 //



 var contourMovements = new List(contour.length);

 num i = 0,
     il = contour.length,
     j = il - 1,
     k = i + 1;
 for ( i = 0; i < il; i ++ ) {

   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 ] );
   j ++; k ++;
 }

 List  holesMovements = [],
       oneHoleMovements,
       verticesMovements = new List.from(contourMovements);

 for ( h = 0; h < holes.length; h ++ ) {

   ahole = holes[ h ];

   oneHoleMovements = new List(ahole.length);

   i = 0;
   il = ahole.length;
   j = il - 1;
   k = i + 1;

   for ( i = 0; i < il; i++) {

     if ( j == il ) j = 0;
     if ( k == il ) k = 0;

     //  (j)---(i)---(k)
     oneHoleMovements[ i ]= _getBevelVec( ahole[ i ], ahole[ j ], ahole[ k ] );
     j++;
     k++;
   }

   holesMovements.add( oneHoleMovements );
   verticesMovements.addAll( 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; i < contour.length; 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; h < holes.length; h++ ) {

     ahole = holes[ h ];
     oneHoleMovements = holesMovements[ h ];

     for ( i = 0; i < ahole.length; 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.0 );

   } else {

     // v( vert.x, vert.y + extrudePts[ 0 ].y, extrudePts[ 0 ].x );

     normal.setFrom(splineTube.normals[0]).scale(vert.x);
     binormal.setFrom(splineTube.binormals[0]).scale(vert.y);

     position2.setFrom(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 <= nSteps; 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 / nSteps * s );

     } else {

       // v( vert.x, vert.y + extrudePts[ s - 1 ].y, extrudePts[ s - 1 ].x );

       normal.setFrom(splineTube.normals[s]).scale(vert.x);
       binormal.setFrom(splineTube.binormals[s]).scale(vert.y);

       position2.setFrom(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; i < contour.length; i ++ ) {

     vert = _scalePt2( contour[ i ], contourMovements[ i ], bs );
     _v( vert.x, vert.y,  amount + z );

   }

   // expand holes

   for ( h = 0; h < holes.length; h ++ ) {

     ahole = holes[ h ];
     oneHoleMovements = holesMovements[ h ];

     for ( i = 0; i < ahole.length; i++ ) {

       vert = _scalePt2( ahole[ i ], oneHoleMovements[ i ], bs );

       if ( !extrudeByPath ) {

         _v( vert.x, vert.y,  amount + z );

       } else {

         _v( vert.x, vert.y + extrudePts[ nSteps - 1 ].y, extrudePts[ nSteps - 1 ].x + z );

       }

     }

   }

 }


 ////
 ///   Handle Faces
 ////

/////  Internal functions

 f3( a, b, c, isBottom ) {
   a += shapesOffset;
   b += shapesOffset;
   c += shapesOffset;

   // normal, color, material
   this.faces.add( new Face3( a, b, c, null, null, material ) );

   var uvs = isBottom ? uvgen.generateBottomUV( this, shape, null, a, b, c)
                      : uvgen.generateTopUV( this, shape, null, a, b, c);

   this.faceVertexUvs[ 0 ].add(uvs);
 }

 f4( a, b, c, d, wallContour, stepIndex, stepsLength ) {
   a += shapesOffset;
   b += shapesOffset;
   c += shapesOffset;
   d += shapesOffset;

   this.faces.add( new Face4( a, b, c, d, null, null, extrudeMaterial ) );

   var uvs = uvgen.generateSideWallUV( this, shape, wallContour, null, a, b, c, d, stepIndex, stepsLength);
   this.faceVertexUvs[ 0 ].add(uvs);
 }

 // Top and bottom faces
 //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 = nSteps + 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 * nSteps, face[ 1 ] + vlen * nSteps, face[ 2 ] + vlen * nSteps, false );
   }
 }

 sidewalls( contour, layeroffset ) {
   var i, 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 = nSteps  + 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 );
     }
   }
 }

 // Sides faces
 //buildSideFaces() {
 // Create faces for the z-sides of the shape
 var layeroffset = 0;
 sidewalls( contour, layeroffset );
 layeroffset += contour.length;

 for ( h = 0;  h < holes.length; h ++ ) {
   ahole = holes[ h ];
   sidewalls( ahole, layeroffset );

   //, true
   layeroffset += ahole.length;
 }
}

dynamic addShapeList(shapes, amount, bevelThickness, bevelSize, bevelSegments, bevelEnabled, curveSegments, steps, bendPath, extrudePath, frames, material, extrudeMaterial) #

addShapeList(shapes, amount, bevelThickness, bevelSize, bevelSegments,bevelEnabled,
            curveSegments,steps,bendPath,extrudePath,frames,material, extrudeMaterial) {
 var sl = shapes.length;

 for ( var s = 0; s < sl; s ++ ) {
   var shape = shapes[ s ];
   addShape( shape, amount, bevelThickness, bevelSize, bevelSegments,bevelEnabled,
             curveSegments, steps, bendPath, extrudePath, frames, material, extrudeMaterial );
 }
}

void applyMatrix(Matrix4 matrix) #

inherited from Geometry
void applyMatrix( Matrix4 matrix ) {
 Matrix4 matrixRotation = new Matrix4.identity();
 extractRotation( matrixRotation, matrix);

 vertices.forEach((vertex) =>  vertex.applyProjection(matrix));

 faces.forEach((face) {

   face.normal.applyProjection(matrixRotation);

   face.vertexNormals.forEach((normal) => normal.applyProjection(matrixRotation));

   face.centroid.applyProjection(matrix);
 });
}

dynamic clone() #

inherited from Geometry
clone() {

 // TODO

}

void computeBoundingBox() #

inherited from Geometry
void computeBoundingBox() {
 if ( boundingBox == null ) {
   boundingBox = new BoundingBox( min: new Vector3.zero(), max: new Vector3.zero() );
 }

 if ( vertices.length > 0 ) {
   Vector3 position, firstPosition = vertices[ 0 ];

   boundingBox.min.setFrom( firstPosition );
   boundingBox.max.setFrom( firstPosition );

   Vector3 min = boundingBox.min,
           max = boundingBox.max;

   num vl = vertices.length;
   for ( int v = 1; v < vl; v ++ ) {
     position = vertices[ v ];

     if ( position.x < min.x ) {
       min.x = position.x;
     } else if ( position.x > max.x ) {
       max.x = position.x;
     }

     if ( position.y < min.y ) {
       min.y = position.y;
     } else if ( position.y > max.y ) {
       max.y = position.y;
     }

     if ( position.z < min.z ) {
       min.z = position.z;
     } else if ( position.z > max.z ) {
       max.z = position.z;
     }
   }
 }
}

void computeBoundingSphere() #

inherited from Geometry
void computeBoundingSphere() {
 num radiusSq;

 var maxRadiusSq = vertices.fold(0, (num curMaxRadiusSq, Vector3 vertex) {
   radiusSq = vertex.length2;
   return ( radiusSq > curMaxRadiusSq ) ?  radiusSq : curMaxRadiusSq;
 });

 boundingSphere = new BoundingSphere(radius: Math.sqrt(maxRadiusSq) );
}

void computeCentroids() #

inherited from Geometry
void computeCentroids() {

 faces.forEach((Face face) {

   face.centroid.setValues( 0.0, 0.0, 0.0 );

   face.indices.forEach((idx) {
     face.centroid.add( vertices[ idx ] );
   });

   face.centroid /= face.size.toDouble();

 });
}

void computeFaceNormals() #

inherited from Geometry
void computeFaceNormals() {
 faces.forEach((face) {

   var vA = vertices[ face.a ],
       vB = vertices[ face.b ],
       vC = vertices[ face.c ];

   Vector3 cb = vC - vB;
   Vector3 ab = vA - vB;
   cb = cb.cross( ab );

   cb.normalize();

   face.normal = cb;

 });
}

void computeTangents() #

inherited from Geometry
void computeTangents() {
 // based on http://www.terathon.com/code/tangent.html
 // tangents go to vertices

 var f, fl, face;
 num i, il, vertexIndex, test, w;
 Vector3 vA, vB, vC;
 UV uvA, uvB, uvC;

 List uv;

 num x1, x2, y1, y2, z1, z2, s1, s2, t1, t2, r;

 Vector3 sdir = new Vector3.zero(),
         tdir = new Vector3.zero(),
         tmp = new Vector3.zero(),
         tmp2 = new Vector3.zero(),
         n = new Vector3.zero(),
         t;

 List<Vector3> tan1 = vertices.map((_) => new Vector3.zero()).toList(),
               tan2 = vertices.map((_) => new Vector3.zero()).toList();

 var handleTriangle = ( context, a, b, c, ua, ub, uc ) {

   vA = context.vertices[ a ];
   vB = context.vertices[ b ];
   vC = context.vertices[ c ];

   uvA = uv[ ua ];
   uvB = uv[ ub ];
   uvC = uv[ uc ];

   x1 = vB.x - vA.x;
   x2 = vC.x - vA.x;
   y1 = vB.y - vA.y;
   y2 = vC.y - vA.y;
   z1 = vB.z - vA.z;
   z2 = vC.z - vA.z;

   s1 = uvB.u - uvA.u;
   s2 = uvC.u - uvA.u;
   t1 = uvB.v - uvA.v;
   t2 = uvC.v - uvA.v;

   r = 1.0 / ( s1 * t2 - s2 * t1 );
   sdir.setValues( ( t2 * x1 - t1 * x2 ) * r,
       ( t2 * y1 - t1 * y2 ) * r,
       ( t2 * z1 - t1 * z2 ) * r );
   tdir.setValues( ( s1 * x2 - s2 * x1 ) * r,
         ( s1 * y2 - s2 * y1 ) * r,
         ( s1 * z2 - s2 * z1 ) * r );

   tan1[ a ].add( sdir );
   tan1[ b ].add( sdir );
   tan1[ c ].add( sdir );

   tan2[ a ].add( tdir );
   tan2[ b ].add( tdir );
   tan2[ c ].add( tdir );

 };

 fl = this.faces.length;

 for ( f = 0; f < fl; f ++ ) {

   face = this.faces[ f ];
   UV uv = faceVertexUvs[ 0 ][ f ]; // use UV layer 0 for tangents

   // TODO - Come up with a way to handle an arbitrary number of vertexes
   var triangles = [];
   if ( face.size == 3 ) {
     triangles.add([0, 1, 2]);
   } else if ( face.size == 4 ) {
     triangles.add([0, 1, 3]);
     triangles.add([1, 2, 3]);
   }

   triangles.forEach((t) {
     handleTriangle( this, face.indices[t[0]], face.indices[t[1]], face.indices[t[2]], t[0], t[1], t[2] );
   });
 }

 faces.forEach((face) {

   il = face.vertexNormals.length;

   for ( i = 0; i < il; i++ ) {

     n.setFrom( face.vertexNormals[ i ] );

     vertexIndex = face.indices[i];

     t = tan1[ vertexIndex ];

     // Gram-Schmidt orthogonalize

     tmp.setFrom( t );
     tmp.sub( n.scale( n.dot( t ) ) ).normalize();

     // Calculate handedness

     tmp2 = face.vertexNormals[i].cross(t);
     test = tmp2.dot( tan2[ vertexIndex ] );
     w = (test < 0.0) ? -1.0 : 1.0;

     face.vertexTangents[ i ] = new Vector4( tmp.x, tmp.y, tmp.z, w );

   }

 });

 hasTangents = true;

}

void computeVertexNormals() #

inherited from Geometry
void computeVertexNormals() {

 List<Vector3> vertices;


 // create internal buffers for reuse when calling this method repeatedly
 // (otherwise memory allocation / deallocation every frame is big resource hog)
 if ( __tmpVertices == null ) {

   __tmpVertices = [];
   this.vertices.forEach((_) => __tmpVertices.add(new Vector3.zero()));
   vertices = __tmpVertices;

   faces.forEach((face) {
     face.vertexNormals = new List.generate(face.size, (_) => new Vector3.zero(), growable: false);
   });

 } else {
   vertices = __tmpVertices;

   var vl = this.vertices.length;
   for ( var v = 0; v < vl; v ++ ) {
     vertices[ v ].setValues( 0.0, 0.0, 0.0 );
   }

 }

 faces.forEach((Face face) {

   face.indices.forEach((idx) {
     vertices[ idx ].add( face.normal );
   });

 });

 vertices.forEach((v) => v.normalize());

 faces.forEach((Face face) {

   var i = 0;
   face.indices.forEach((idx) {
     face.vertexNormals[ i++ ].setFrom( vertices[ idx ] );
   });

 });
}

int mergeVertices() #

inherited from Geometry
int mergeVertices() {
 Map verticesMap = {}; // Hashmap for looking up vertice by position coordinates (and making sure they are unique)
 List<Vector3> unique = [];
 List<int> changes = [];

 String key;
 int precisionPoints = 4; // number of decimal points, eg. 4 for epsilon of 0.0001
 num precision = Math.pow( 10, precisionPoints );
 int i, il;
 var abcd = 'abcd', o, k, j, jl, u;

 Vector3 v;
 il = this.vertices.length;

 for( i = 0; i < il; i++) {
   v = this.vertices[i];

   key = [ ( v.x * precision ).round().toStringAsFixed(0),
                         ( v.y * precision ).round().toStringAsFixed(0),
                         ( v.z * precision ).round().toStringAsFixed(0) ].join('_' );

   if ( verticesMap[ key ] == null ) {
     verticesMap[ key ] = i;
     unique.add( v );
     //TODO: pretty sure this is an acceptable change in syntax here:
     //changes[ i ] = unique.length - 1;
     changes.add( unique.length - 1);
   } else {
     //print('Duplicate vertex found. $i could be using  ${verticesMap[key]}');
     //print('changes len ${changes.length} add at i = $i');
     //changes[ i ] = changes[ verticesMap[ key ] ];
     changes.add( changes[ verticesMap[ key ] ] );
   }

 }


 // Start to patch face indices

 faces.forEach((Face face) {
   for (var i = 0; i < face.size; i++) {
     face.indices[i] = changes[ face.indices[i] ];

     /* TODO

     // check dups in (a, b, c, d) and convert to -> face3

     var o = [ face.a, face.b, face.c, face.d ];

     for ( var k = 3; k > 0; k -- ) {

       if ( o.indexOf( face[ abcd[ k ] ] ) != k ) {

         // console.log('faces', face.a, face.b, face.c, face.d, 'dup at', k);

         o.removeAt( k );

         this.faces[ i ] = new THREE.Face3( o[0], o[1], o[2], face.normal, face.color, face.materialIndex );

         for ( j = 0, jl = this.faceVertexUvs.length; j < jl; j ++ ) {

           u = this.faceVertexUvs[ j ][ i ];
           if ( u ) u.removeAt( k );

         }

         this.faces[ i ].vertexColors = face.vertexColors;

         break;
       }

     }*/

   }
 });

 // Use unique set of vertices
 var diff = vertices.length - unique.length;
 vertices = unique;
 return diff;
}