computeTangents method
      
void
computeTangents()
       
    
inherited
    Calculates and adds a tangent attribute to this geometry.
The computation is only supported for indexed geometries and if position,
normal, and uv attributes are defined. When using a tangent space normal
map, prefer the MikkTSpace algorithm provided by
BufferGeometryUtils.computeMikkTSpaceTangents instead.
Implementation
void computeTangents() {
  final index = this.index;
  final attributes = this.attributes;
  // based on http://www.terathon.com/code/tangent.html
  // (per vertex tangents)
  if (index == null ||
      attributes["position"] == null ||
      attributes["normal"] == null ||
      attributes["uv"] == null) {
    console.error('THREE.BufferGeometry: .computeTangents() failed. Missing required attributes (index, position, normal or uv)');
    return;
  }
  final indices = index.array;
  final positions = attributes["position"].array;
  final normals = attributes["normal"].array;
  final uvs = attributes["uv"].array;
  int nVertices = positions.length ~/ 3;
  if (attributes["tangent"] == null) {
    setAttributeFromString('tangent', Float32BufferAttribute.fromList(Float32List(4 * nVertices), 4));
  }
  final tangents = attributes["tangent"].array;
  final List<Vector3> tan1 = [], tan2 = [];
  for (int i = 0; i < nVertices; i++) {
    tan1.add(Vector3.zero());
    tan2.add(Vector3.zero());
  }
  final vA = Vector3.zero(),
      vB = Vector3.zero(),
      vC = Vector3.zero(),
      uvA = Vector2.zero(),
      uvB = Vector2.zero(),
      uvC = Vector2.zero(),
      sdir = Vector3.zero(),
      tdir = Vector3.zero();
  void handleTriangle(int a, int b, int c) {
    vA.fromNativeArray(positions, a * 3);
    vB.fromNativeArray(positions, b * 3);
    vC.fromNativeArray(positions, c * 3);
    uvA.fromNativeArray(uvs, a * 2);
    uvB.fromNativeArray(uvs, b * 2);
    uvC.fromNativeArray(uvs, c * 2);
    vB.sub(vA);
    vC.sub(vA);
    uvB.sub(uvA);
    uvC.sub(uvA);
    double r = 1.0 / (uvB.x * uvC.y - uvC.x * uvB.y);
    // silently ignore degenerate uv triangles having coincident or colinear vertices
    if (!r.isFinite) return;
    sdir.setFrom(vB);
    sdir.scale(uvC.y);
    sdir.addScaled(vC, -uvB.y);
    sdir.scale(r);
    tdir.setFrom(vC);
    tdir.scale(uvB.x);
    tdir.addScaled(vB, -uvC.x);
    tdir.scale(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);
  }
  List<Map<String,dynamic>> groups = this.groups;
  if (groups.isEmpty) {
    groups = [
      {"start": 0, "count": indices.length}
    ];
  }
  for (int i = 0, il = groups.length; i < il; ++i) {
    final group = groups[i];
    final start = group["start"];
    final count = group["count"];
    for (int j = start, jl = start + count; j < jl; j += 3) {
      handleTriangle(
        indices[j + 0].toInt(),
        indices[j + 1].toInt(),
        indices[j + 2].toInt(),
      );
    }
  }
  final tmp = Vector3.zero(), tmp2 = Vector3.zero();
  final n = Vector3.zero(), n2 = Vector3.zero();
  void handleVertex(int v) {
    n.fromNativeArray(normals, v * 3);
    n2.setFrom(n);
    final t = tan1[v];
    // Gram-Schmidt orthogonalize
    tmp.setFrom(t);
    n.scale(n.dot(t));
    tmp.sub(n);
    tmp.normalize();
    // Calculate handedness
    tmp2.cross2(n2, t);
    final test = tmp2.dot(tan2[v]);
    final 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;
  }
  for (int i = 0, il = groups.length; i < il; ++i) {
    final group = groups[i];
    final start = group["start"];
    final count = group["count"];
    for (int j = start, jl = start + count; j < jl; j += 3) {
      handleVertex(indices[j + 0].toInt());
      handleVertex(indices[j + 1].toInt());
      handleVertex(indices[j + 2].toInt());
    }
  }
}