Dart DocumentationthreeBufferGeometry

BufferGeometry class

class BufferGeometry implements Geometry {

	int id = GeometryCount ++;

	// attributes
	Map<String, GeometryAttribute> attributes = {};

 // offsets for chunks when using indexed elements
	List<Chunk> offsets = [];

	// attributes typed arrays are kept only if dynamic flag is set
	bool _dynamic = false;

	// boundings
	var boundingBox = null;
	var boundingSphere = null;

	bool hasTangents;

	// for compatibility
	List morphTargets = [];
	List morphNormals = [];

	applyMatrix ( Matrix4 matrix ) {

		var positionArray;
		var normalArray;

		if ( aPosition != null ) positionArray = aPosition.array;
		if ( aNormal != null ) normalArray = aNormal.array;

		if ( positionArray != null) {

			multiplyVector3Array(matrix, positionArray);
			this["verticesNeedUpdate"] = true;

		}

		if ( normalArray != null ) {

			var matrixRotation = new Matrix4.identity();
			extractRotation( matrixRotation, matrix );

			multiplyVector3Array(matrixRotation, normalArray );
			this["normalsNeedUpdate"] = true;

		}

	}

	computeBoundingBox () {

		if ( boundingBox == null ) {

			boundingBox = new BoundingBox(
				min: new Vector3( double.INFINITY, double.INFINITY, double.INFINITY ),
				max: new Vector3( -double.INFINITY, -double.INFINITY, -double.INFINITY )
			);

		}

		var positions = aPosition.array;

		if ( positions ) {

			var bb = boundingBox;
			var x, y, z;

			for ( var i = 0, il = positions.length; i < il; i += 3 ) {

				x = positions[ i ];
				y = positions[ i + 1 ];
				z = positions[ i + 2 ];

				// bounding box

				if ( x < bb.min.x ) {

					bb.min.x = x;

				} else if ( x > bb.max.x ) {

					bb.max.x = x;

				}

				if ( y < bb.min.y ) {

					bb.min.y = y;

				} else if ( y > bb.max.y ) {

					bb.max.y = y;

				}

				if ( z < bb.min.z ) {

					bb.min.z = z;

				} else if ( z > bb.max.z ) {

					bb.max.z = z;

				}

			}

		}

		if ( positions == null || positions.length == 0 ) {

			boundingBox.min.setValues( 0, 0, 0 );
			boundingBox.max.setValues( 0, 0, 0 );

		}

	}

	computeBoundingSphere() {

		if ( boundingSphere == null ) boundingSphere = new BoundingSphere( radius: 0 );

		var positions = aPosition.array;

		if ( positions != null ) {

			var radiusSq, maxRadiusSq = 0;
			var x, y, z;

			for ( var i = 0, il = positions.length; i < il; i += 3 ) {

				x = positions[ i ];
				y = positions[ i + 1 ];
				z = positions[ i + 2 ];

				radiusSq =  x * x + y * y + z * z;
				if ( radiusSq > maxRadiusSq ) maxRadiusSq = radiusSq;

			}

			boundingSphere.radius = Math.sqrt( maxRadiusSq );

		}

	}

	computeVertexNormals() {

		if ( aPosition != null && aIndex != null ) {

			var i, il;
			var j, jl;

			if ( aNormal == null ) {

				attributes[ GeometryAttribute.NORMAL ] = new GeometryAttribute.float32(aPosition.numItems, 3);

			} else {

				// reset existing normals to zero
			  il = aNormal.array.length;

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

					attributes[ "normal" ].array[ i ] = 0.0;

				}

			}

			var indices = aIndex.array;
			var positions = aPosition.array;
			var normals = aNormal.array;

			var vA, vB, vC, x, y, z,

			pA = new Vector3.zero(),
			pB = new Vector3.zero(),
			pC = new Vector3.zero(),

			cb = new Vector3.zero(),
			ab = new Vector3.zero();

			jl = offsets.length;
			for ( j = 0; j < jl; ++ j ) {

				var start = offsets[ j ].start;
				var count = offsets[ j ].count;
				var index = offsets[ j ].index;

				il = start + count;
				for ( i = start; i < il; i += 3 ) {

					vA = index + indices[ i ];
					vB = index + indices[ i + 1 ];
					vC = index + indices[ i + 2 ];

					x = positions[ vA * 3 ];
					y = positions[ vA * 3 + 1 ];
					z = positions[ vA * 3 + 2 ];
					pA.setValues( x, y, z );

					x = positions[ vB * 3 ];
					y = positions[ vB * 3 + 1 ];
					z = positions[ vB * 3 + 2 ];
					pB.setValues( x, y, z );

					x = positions[ vC * 3 ];
					y = positions[ vC * 3 + 1 ];
					z = positions[ vC * 3 + 2 ];
					pC.setValues( x, y, z );

					cb = pC - pB;
					ab = pA - pB;
					cb = cb.cross( ab );

					normals[ vA * 3 ] += cb.x;
					normals[ vA * 3 + 1 ] += cb.y;
					normals[ vA * 3 + 2 ] += cb.z;

					normals[ vB * 3 ] += cb.x;
					normals[ vB * 3 + 1 ] += cb.y;
					normals[ vB * 3 + 2 ] += cb.z;

					normals[ vC * 3 ] += cb.x;
					normals[ vC * 3 + 1 ] += cb.y;
					normals[ vC * 3 + 2 ] += cb.z;

				}

			}

			// normalize normals
			il = normals.length;
			for ( i = 0; i < il; i += 3 ) {

				x = normals[ i ];
				y = normals[ i + 1 ];
				z = normals[ i + 2 ];

				var n = 1.0 / Math.sqrt( x * x + y * y + z * z );

				normals[ i ] *= n;
				normals[ i + 1 ] *= n;
				normals[ i + 2 ] *= n;

			}

			this["normalsNeedUpdate"] = true;

		}

	}

	computeTangents() {

		// based on http://www.terathon.com/code/tangent.html
		// (per vertex tangents)

		if ( aIndex == null || aPosition == null || aNormal == null || aUV == null ) {

			print( "Missing required attributes (index, position, normal or uv) in BufferGeometry.computeTangents()" );
			return;

		}

		var indices = aIndex.array;
		var positions = aPosition.array;
		var normals = aNormal.array;
		var uvs = aUV.array;

		var nVertices = aPosition.numItems ~/ 3;

		if ( aTangent == null ) {

			attributes[ "tangent" ] = new GeometryAttribute.float32(nVertices, 4);

		}

		var tangents = aTangent.array;

		List<Vector3> tan1 = [], tan2 = [];

		for ( var k = 0; k < nVertices; k ++ ) {

			tan1[ k ] = new Vector3.zero();
			tan2[ k ] = new Vector3.zero();

		}

		var xA, yA, zA,
			xB, yB, zB,
			xC, yC, zC,

			uA, vA,
			uB, vB,
			uC, vC,

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

		var sdir = new Vector3.zero(),
		    tdir = new Vector3.zero();

		var handleTriangle = ( a, b, c ) {

			xA = positions[ a * 3 ];
			yA = positions[ a * 3 + 1 ];
			zA = positions[ a * 3 + 2 ];

			xB = positions[ b * 3 ];
			yB = positions[ b * 3 + 1 ];
			zB = positions[ b * 3 + 2 ];

			xC = positions[ c * 3 ];
			yC = positions[ c * 3 + 1 ];
			zC = positions[ c * 3 + 2 ];

			uA = uvs[ a * 2 ];
			vA = uvs[ a * 2 + 1 ];

			uB = uvs[ b * 2 ];
			vB = uvs[ b * 2 + 1 ];

			uC = uvs[ c * 2 ];
			vC = uvs[ c * 2 + 1 ];

			x1 = xB - xA;
			x2 = xC - xA;

			y1 = yB - yA;
			y2 = yC - yA;

			z1 = zB - zA;
			z2 = zC - zA;

			s1 = uB - uA;
			s2 = uC - uA;

			t1 = vB - vA;
			t2 = vC - vA;

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

		};

		var i, il;
		var j, jl;
		var iA, iB, iC;

		jl = offsets.length;
		for ( j = 0; j < jl; ++ j ) {

			var start = offsets[ j ].start;
			var count = offsets[ j ].count;
			var index = offsets[ j ].index;

			il = start + count;
			for ( i = start; i < il; i += 3 ) {

				iA = index + indices[ i ];
				iB = index + indices[ i + 1 ];
				iC = index + indices[ i + 2 ];

				handleTriangle( iA, iB, iC );

			}

		}

		var tmp = new Vector3.zero(),
		    tmp2 = new Vector3.zero();
		var n = new Vector3.zero(),
		    n2 = new Vector3.zero();
		var w, t, test;
		var nx, ny, nz;

		var handleVertex = ( v ) {

			n.x = normals[ v * 3 ];
			n.y = normals[ v * 3 + 1 ];
			n.z = normals[ v * 3 + 2 ];

			n2.setFrom( n );

			t = tan1[ v ];

			// Gram-Schmidt orthogonalize

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

			// Calculate handedness

			tmp2 = n2.cross( t );
			test = tmp2.dot( tan2[ v ] );
			w = ( test < 0.0 ) ? -1.0 : 1.0;

			tangents[ v * 4 ] 	  = tmp.x;
			tangents[ v * 4 + 1 ] = tmp.y;
			tangents[ v * 4 + 2 ] = tmp.z;
			tangents[ v * 4 + 3 ] = w;

		};

		jl = offsets.length;
		for ( j = 0; j < jl; ++ j ) {

			var start = offsets[ j ].start;
			var count = offsets[ j ].count;
			var index = offsets[ j ].index;

			il = start + count;
			for ( i = start; i < il; i += 3 ) {

				iA = index + indices[ i ];
				iB = index + indices[ i + 1 ];
				iC = index + indices[ i + 2 ];

				handleVertex( iA );
				handleVertex( iB );
				handleVertex( iC );

			}

		}

		hasTangents = true;
		this["tangentsNeedUpdate"] = true;

	}

 // dynamic is a reserved word in Dart
 bool get isDynamic => _dynamic;
 set isDynamic(bool value) => _dynamic = value;

 // default attributes
 GeometryAttribute<Float32List> get aPosition => attributes[GeometryAttribute.POSITION];
 set aPosition(a){ attributes[GeometryAttribute.POSITION] = a; }

 GeometryAttribute<Float32List> get aNormal => attributes[GeometryAttribute.NORMAL];
 set aNormal(a){ attributes[GeometryAttribute.NORMAL] = a; }

 GeometryAttribute<Int16List> get aIndex => attributes[GeometryAttribute.INDEX];
 set aIndex(a){ attributes[GeometryAttribute.INDEX] = a; }

 GeometryAttribute<Float32List> get aUV => attributes[GeometryAttribute.UV];
 set aUV(a){ attributes[GeometryAttribute.UV] = a; }

 GeometryAttribute<Float32List> get aTangent => attributes[GeometryAttribute.TANGENT];
 set aTangent(a){ attributes[GeometryAttribute.TANGENT] = a; }

 GeometryAttribute<Float32List> get aColor => attributes[GeometryAttribute.COLOR];
 set aColor(a){ attributes[GeometryAttribute.COLOR] = a; }

 noSuchMethod(Invocation invocation) {
   throw new Exception('Unimplemented ${invocation.memberName}');
 }

	 // Quick hack to allow setting new properties (used by the renderer)
 Map __data;

 get _data {
   if (__data == null) {
     __data = {};
   }
   return __data;
 }

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

}

Implements

Geometry

Properties

GeometryAttribute<Float32List> aColor #

GeometryAttribute<Float32List> get aColor => attributes[GeometryAttribute.COLOR];
set aColor(a){ attributes[GeometryAttribute.COLOR] = a; }

GeometryAttribute<Int16List> aIndex #

GeometryAttribute<Int16List> get aIndex => attributes[GeometryAttribute.INDEX];
set aIndex(a){ attributes[GeometryAttribute.INDEX] = a; }

var animation #

inherited from Geometry
var bones, animation

GeometryAttribute<Float32List> aNormal #

GeometryAttribute<Float32List> get aNormal => attributes[GeometryAttribute.NORMAL];
set aNormal(a){ attributes[GeometryAttribute.NORMAL] = a; }

GeometryAttribute<Float32List> aPosition #

GeometryAttribute<Float32List> get aPosition => attributes[GeometryAttribute.POSITION];
set aPosition(a){ attributes[GeometryAttribute.POSITION] = a; }

GeometryAttribute<Float32List> aTangent #

GeometryAttribute<Float32List> get aTangent => attributes[GeometryAttribute.TANGENT];
set aTangent(a){ attributes[GeometryAttribute.TANGENT] = a; }

Map<String, GeometryAttribute> attributes #

Map<String, GeometryAttribute> attributes = {}

GeometryAttribute<Float32List> aUV #

GeometryAttribute<Float32List> get aUV => attributes[GeometryAttribute.UV];
set aUV(a){ attributes[GeometryAttribute.UV] = a; }

var bones #

inherited from Geometry
var bones

var boundingBox #

var boundingBox = null

var boundingSphere #

var boundingSphere = null

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 #

bool hasTangents

int id #

int id = GeometryCount ++

bool isDynamic #

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 #

List morphNormals = []

List morphTargets #

List morphTargets = []

String name #

inherited from Geometry
String name

List normals #

inherited from Geometry
List normals = []

List<Chunk> offsets #

List<Chunk> offsets = []

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) #

operator [] (String key) => _data[key];

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

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

Methods

dynamic applyMatrix(Matrix4 matrix) #

applyMatrix ( Matrix4 matrix ) {

		var positionArray;
		var normalArray;

		if ( aPosition != null ) positionArray = aPosition.array;
		if ( aNormal != null ) normalArray = aNormal.array;

		if ( positionArray != null) {

			multiplyVector3Array(matrix, positionArray);
			this["verticesNeedUpdate"] = true;

		}

		if ( normalArray != null ) {

			var matrixRotation = new Matrix4.identity();
			extractRotation( matrixRotation, matrix );

			multiplyVector3Array(matrixRotation, normalArray );
			this["normalsNeedUpdate"] = true;

		}

	}

dynamic clone() #

inherited from Geometry
clone() {

 // TODO

}

dynamic computeBoundingBox() #

computeBoundingBox () {

		if ( boundingBox == null ) {

			boundingBox = new BoundingBox(
				min: new Vector3( double.INFINITY, double.INFINITY, double.INFINITY ),
				max: new Vector3( -double.INFINITY, -double.INFINITY, -double.INFINITY )
			);

		}

		var positions = aPosition.array;

		if ( positions ) {

			var bb = boundingBox;
			var x, y, z;

			for ( var i = 0, il = positions.length; i < il; i += 3 ) {

				x = positions[ i ];
				y = positions[ i + 1 ];
				z = positions[ i + 2 ];

				// bounding box

				if ( x < bb.min.x ) {

					bb.min.x = x;

				} else if ( x > bb.max.x ) {

					bb.max.x = x;

				}

				if ( y < bb.min.y ) {

					bb.min.y = y;

				} else if ( y > bb.max.y ) {

					bb.max.y = y;

				}

				if ( z < bb.min.z ) {

					bb.min.z = z;

				} else if ( z > bb.max.z ) {

					bb.max.z = z;

				}

			}

		}

		if ( positions == null || positions.length == 0 ) {

			boundingBox.min.setValues( 0, 0, 0 );
			boundingBox.max.setValues( 0, 0, 0 );

		}

	}

dynamic computeBoundingSphere() #

computeBoundingSphere() {

		if ( boundingSphere == null ) boundingSphere = new BoundingSphere( radius: 0 );

		var positions = aPosition.array;

		if ( positions != null ) {

			var radiusSq, maxRadiusSq = 0;
			var x, y, z;

			for ( var i = 0, il = positions.length; i < il; i += 3 ) {

				x = positions[ i ];
				y = positions[ i + 1 ];
				z = positions[ i + 2 ];

				radiusSq =  x * x + y * y + z * z;
				if ( radiusSq > maxRadiusSq ) maxRadiusSq = radiusSq;

			}

			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;

 });
}

dynamic computeTangents() #

computeTangents() {

		// based on http://www.terathon.com/code/tangent.html
		// (per vertex tangents)

		if ( aIndex == null || aPosition == null || aNormal == null || aUV == null ) {

			print( "Missing required attributes (index, position, normal or uv) in BufferGeometry.computeTangents()" );
			return;

		}

		var indices = aIndex.array;
		var positions = aPosition.array;
		var normals = aNormal.array;
		var uvs = aUV.array;

		var nVertices = aPosition.numItems ~/ 3;

		if ( aTangent == null ) {

			attributes[ "tangent" ] = new GeometryAttribute.float32(nVertices, 4);

		}

		var tangents = aTangent.array;

		List<Vector3> tan1 = [], tan2 = [];

		for ( var k = 0; k < nVertices; k ++ ) {

			tan1[ k ] = new Vector3.zero();
			tan2[ k ] = new Vector3.zero();

		}

		var xA, yA, zA,
			xB, yB, zB,
			xC, yC, zC,

			uA, vA,
			uB, vB,
			uC, vC,

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

		var sdir = new Vector3.zero(),
		    tdir = new Vector3.zero();

		var handleTriangle = ( a, b, c ) {

			xA = positions[ a * 3 ];
			yA = positions[ a * 3 + 1 ];
			zA = positions[ a * 3 + 2 ];

			xB = positions[ b * 3 ];
			yB = positions[ b * 3 + 1 ];
			zB = positions[ b * 3 + 2 ];

			xC = positions[ c * 3 ];
			yC = positions[ c * 3 + 1 ];
			zC = positions[ c * 3 + 2 ];

			uA = uvs[ a * 2 ];
			vA = uvs[ a * 2 + 1 ];

			uB = uvs[ b * 2 ];
			vB = uvs[ b * 2 + 1 ];

			uC = uvs[ c * 2 ];
			vC = uvs[ c * 2 + 1 ];

			x1 = xB - xA;
			x2 = xC - xA;

			y1 = yB - yA;
			y2 = yC - yA;

			z1 = zB - zA;
			z2 = zC - zA;

			s1 = uB - uA;
			s2 = uC - uA;

			t1 = vB - vA;
			t2 = vC - vA;

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

		};

		var i, il;
		var j, jl;
		var iA, iB, iC;

		jl = offsets.length;
		for ( j = 0; j < jl; ++ j ) {

			var start = offsets[ j ].start;
			var count = offsets[ j ].count;
			var index = offsets[ j ].index;

			il = start + count;
			for ( i = start; i < il; i += 3 ) {

				iA = index + indices[ i ];
				iB = index + indices[ i + 1 ];
				iC = index + indices[ i + 2 ];

				handleTriangle( iA, iB, iC );

			}

		}

		var tmp = new Vector3.zero(),
		    tmp2 = new Vector3.zero();
		var n = new Vector3.zero(),
		    n2 = new Vector3.zero();
		var w, t, test;
		var nx, ny, nz;

		var handleVertex = ( v ) {

			n.x = normals[ v * 3 ];
			n.y = normals[ v * 3 + 1 ];
			n.z = normals[ v * 3 + 2 ];

			n2.setFrom( n );

			t = tan1[ v ];

			// Gram-Schmidt orthogonalize

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

			// Calculate handedness

			tmp2 = n2.cross( t );
			test = tmp2.dot( tan2[ v ] );
			w = ( test < 0.0 ) ? -1.0 : 1.0;

			tangents[ v * 4 ] 	  = tmp.x;
			tangents[ v * 4 + 1 ] = tmp.y;
			tangents[ v * 4 + 2 ] = tmp.z;
			tangents[ v * 4 + 3 ] = w;

		};

		jl = offsets.length;
		for ( j = 0; j < jl; ++ j ) {

			var start = offsets[ j ].start;
			var count = offsets[ j ].count;
			var index = offsets[ j ].index;

			il = start + count;
			for ( i = start; i < il; i += 3 ) {

				iA = index + indices[ i ];
				iB = index + indices[ i + 1 ];
				iC = index + indices[ i + 2 ];

				handleVertex( iA );
				handleVertex( iB );
				handleVertex( iC );

			}

		}

		hasTangents = true;
		this["tangentsNeedUpdate"] = true;

	}

dynamic computeVertexNormals() #

computeVertexNormals() {

		if ( aPosition != null && aIndex != null ) {

			var i, il;
			var j, jl;

			if ( aNormal == null ) {

				attributes[ GeometryAttribute.NORMAL ] = new GeometryAttribute.float32(aPosition.numItems, 3);

			} else {

				// reset existing normals to zero
			  il = aNormal.array.length;

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

					attributes[ "normal" ].array[ i ] = 0.0;

				}

			}

			var indices = aIndex.array;
			var positions = aPosition.array;
			var normals = aNormal.array;

			var vA, vB, vC, x, y, z,

			pA = new Vector3.zero(),
			pB = new Vector3.zero(),
			pC = new Vector3.zero(),

			cb = new Vector3.zero(),
			ab = new Vector3.zero();

			jl = offsets.length;
			for ( j = 0; j < jl; ++ j ) {

				var start = offsets[ j ].start;
				var count = offsets[ j ].count;
				var index = offsets[ j ].index;

				il = start + count;
				for ( i = start; i < il; i += 3 ) {

					vA = index + indices[ i ];
					vB = index + indices[ i + 1 ];
					vC = index + indices[ i + 2 ];

					x = positions[ vA * 3 ];
					y = positions[ vA * 3 + 1 ];
					z = positions[ vA * 3 + 2 ];
					pA.setValues( x, y, z );

					x = positions[ vB * 3 ];
					y = positions[ vB * 3 + 1 ];
					z = positions[ vB * 3 + 2 ];
					pB.setValues( x, y, z );

					x = positions[ vC * 3 ];
					y = positions[ vC * 3 + 1 ];
					z = positions[ vC * 3 + 2 ];
					pC.setValues( x, y, z );

					cb = pC - pB;
					ab = pA - pB;
					cb = cb.cross( ab );

					normals[ vA * 3 ] += cb.x;
					normals[ vA * 3 + 1 ] += cb.y;
					normals[ vA * 3 + 2 ] += cb.z;

					normals[ vB * 3 ] += cb.x;
					normals[ vB * 3 + 1 ] += cb.y;
					normals[ vB * 3 + 2 ] += cb.z;

					normals[ vC * 3 ] += cb.x;
					normals[ vC * 3 + 1 ] += cb.y;
					normals[ vC * 3 + 2 ] += cb.z;

				}

			}

			// normalize normals
			il = normals.length;
			for ( i = 0; i < il; i += 3 ) {

				x = normals[ i ];
				y = normals[ i + 1 ];
				z = normals[ i + 2 ];

				var n = 1.0 / Math.sqrt( x * x + y * y + z * z );

				normals[ i ] *= n;
				normals[ i + 1 ] *= n;
				normals[ i + 2 ] *= n;

			}

			this["normalsNeedUpdate"] = true;

		}

	}

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;
}

dynamic noSuchMethod(Invocation invocation) #

noSuchMethod is invoked when users invoke a non-existent method on an object. The name of the method and the arguments of the invocation are passed to noSuchMethod in an Invocation. If noSuchMethod returns a value, that value becomes the result of the original invocation.

The default behavior of noSuchMethod is to throw a NoSuchMethodError.

docs inherited from Object
noSuchMethod(Invocation invocation) {
 throw new Exception('Unimplemented ${invocation.memberName}');
}