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
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; }
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 boundingBox #
var boundingBox = null
var boundingSphere #
var boundingSphere = null
Operators
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 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}'); }