Dart DocumentationthreeTextGeometry

TextGeometry class

class TextGeometry extends ExtrudeGeometry {
 factory TextGeometry( text, [ height = 50, // height <=> amount,
                               bend = false,

                               // ExtrudeGeometry parameters
                               bevelThickness = 10,
                               bevelSize = 8,
                               bevelSegments = 3,
                               bevelEnabled = false,


                               curveSegments = 12,
                               steps = 1,
                               bendPath,
                               extrudePath,
                               material,
                               extrudeMaterial,

                               // FontUtils.generateShapes parameters
                               size = 100,
                               font = "helvetiker",
                               weight = "normal",
                               style = "normal"
                               ] ) {

   var textShapes = FontUtils.generateShapes( text, size, curveSegments, font, weight, style);

   var amount = height;
   var bendPath = null;

   if (bend) {
     var b = textShapes[ textShapes.length - 1 ].getBoundingBox();
     var max = b.maxX;

     bendPath = new QuadraticBezierCurve(
         new Vector2.zero(),
         new Vector2( max / 2, 120.0 ),
         new Vector2( max, 0.0 )
     );
   }

   return new TextGeometry._internal(  textShapes,
                                       amount,
                                       bevelThickness,
                                       bevelSize,
                                       bevelSegments,
                                       bevelEnabled,
                                       curveSegments,
                                       steps,
                                       bendPath,
                                       extrudePath,
                                       material,
                                       extrudeMaterial);
 }

 TextGeometry._internal( shapes,
                         amount,
                         bevelThickness,
                         bevelSize,
                         bevelSegments,
                         bevelEnabled,
                         curveSegments,
                         steps,
                         bendPath,
                         extrudePath,
                         material,
                         extrudeMaterial) : super( shapes,
                                                   amount: amount,
                                                   bevelThickness: bevelThickness,
                                                   bevelSize: bevelSize,
                                                   bevelSegments: bevelSegments,
                                                   bevelEnabled: bevelEnabled,
                                                   curveSegments: curveSegments,
                                                   steps: steps,
                                                   bendPath: bendPath,
                                                   extrudePath: extrudePath,
                                                   material: material,
                                                   extrudeMaterial: extrudeMaterial);
}

Extends

Geometry > ExtrudeGeometry > TextGeometry

Constructors

factory TextGeometry(text, [height = 50, bend = false, bevelThickness = 10, bevelSize = 8, bevelSegments = 3, bevelEnabled = false, curveSegments = 12, steps = 1, bendPath, extrudePath, material, extrudeMaterial, size = 100, font = "helvetiker", weight = "normal", style = "normal"]) #

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
factory TextGeometry( text, [ height = 50, // height <=> amount,
                             bend = false,

                             // ExtrudeGeometry parameters
                             bevelThickness = 10,
                             bevelSize = 8,
                             bevelSegments = 3,
                             bevelEnabled = false,


                             curveSegments = 12,
                             steps = 1,
                             bendPath,
                             extrudePath,
                             material,
                             extrudeMaterial,

                             // FontUtils.generateShapes parameters
                             size = 100,
                             font = "helvetiker",
                             weight = "normal",
                             style = "normal"
                             ] ) {

 var textShapes = FontUtils.generateShapes( text, size, curveSegments, font, weight, style);

 var amount = height;
 var bendPath = null;

 if (bend) {
   var b = textShapes[ textShapes.length - 1 ].getBoundingBox();
   var max = b.maxX;

   bendPath = new QuadraticBezierCurve(
       new Vector2.zero(),
       new Vector2( max / 2, 120.0 ),
       new Vector2( max, 0.0 )
   );
 }

 return new TextGeometry._internal(  textShapes,
                                     amount,
                                     bevelThickness,
                                     bevelSize,
                                     bevelSegments,
                                     bevelEnabled,
                                     curveSegments,
                                     steps,
                                     bendPath,
                                     extrudePath,
                                     material,
                                     extrudeMaterial);
}

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 #

inherited from ExtrudeGeometry
var shapebb

List shapes #

inherited from ExtrudeGeometry
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}) #

inherited from ExtrudeGeometry
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) #

inherited from ExtrudeGeometry
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;
}