computeTangents method

void computeTangents()

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