extractEdges method

LineSegmentData extractEdges({
  1. double? creaseAngleDegrees,
})

The mesh's unique undirected edges as disconnected line segments.

With a null creaseAngleDegrees every edge is kept (a full wireframe). With a value, an edge is kept only when its two adjacent faces meet at more than that angle (a feature-edge wireframe); boundary edges (a single adjacent face) are always kept.

The output carries per-endpoint source normals when this mesh has normals, so callers can offset the segments off the surface.

Implementation

LineSegmentData extractEdges({double? creaseAngleDegrees}) {
  _requireTriangles('extractEdges');
  final count = triangleCount;

  // Unique undirected edges, keyed lo * vertexCount + hi (safe well past
  // any practical vertex count). Each edge tracks up to two face normals
  // for the crease filter.
  final edgeSlot = <int, int>{};
  final edgeA = <int>[];
  final edgeB = <int>[];
  final faceNx = <double>[];
  final faceNy = <double>[];
  final faceNz = <double>[];
  // Per edge: first face index, second face index (or -1).
  final firstFace = <int>[];
  final secondFace = <int>[];

  for (var t = 0; t < count; t++) {
    final i0 = _cornerIndex(t * 3);
    final i1 = _cornerIndex(t * 3 + 1);
    final i2 = _cornerIndex(t * 3 + 2);

    if (creaseAngleDegrees != null) {
      final ax = positions[i0 * 3],
          ay = positions[i0 * 3 + 1],
          az = positions[i0 * 3 + 2];
      final e1x = positions[i1 * 3] - ax,
          e1y = positions[i1 * 3 + 1] - ay,
          e1z = positions[i1 * 3 + 2] - az;
      final e2x = positions[i2 * 3] - ax,
          e2y = positions[i2 * 3 + 1] - ay,
          e2z = positions[i2 * 3 + 2] - az;
      var nx = e1y * e2z - e1z * e2y;
      var ny = e1z * e2x - e1x * e2z;
      var nz = e1x * e2y - e1y * e2x;
      final len = math.sqrt(nx * nx + ny * ny + nz * nz);
      if (len > 0) {
        nx /= len;
        ny /= len;
        nz /= len;
      }
      faceNx.add(nx);
      faceNy.add(ny);
      faceNz.add(nz);
    }

    void addEdge(int a, int b) {
      final lo = math.min(a, b);
      final hi = math.max(a, b);
      final key = lo * vertexCount + hi;
      final slot = edgeSlot[key];
      if (slot == null) {
        edgeSlot[key] = edgeA.length;
        edgeA.add(lo);
        edgeB.add(hi);
        firstFace.add(t);
        secondFace.add(-1);
      } else if (secondFace[slot] == -1) {
        secondFace[slot] = t;
      }
    }

    addEdge(i0, i1);
    addEdge(i1, i2);
    addEdge(i2, i0);
  }

  // cos of the crease angle; adjacent faces whose normals agree more than
  // this are coplanar enough to drop.
  final creaseCos = creaseAngleDegrees == null
      ? null
      : math.cos(creaseAngleDegrees * math.pi / 180.0);

  final srcNormals = normals;
  final outPositions = <double>[];
  final outNormals = srcNormals == null ? null : <double>[];
  for (var e = 0; e < edgeA.length; e++) {
    if (creaseCos != null && secondFace[e] != -1) {
      final f0 = firstFace[e];
      final f1 = secondFace[e];
      final dot =
          faceNx[f0] * faceNx[f1] +
          faceNy[f0] * faceNy[f1] +
          faceNz[f0] * faceNz[f1];
      if (dot > creaseCos) continue;
    }
    for (final v in [edgeA[e], edgeB[e]]) {
      outPositions
        ..add(positions[v * 3])
        ..add(positions[v * 3 + 1])
        ..add(positions[v * 3 + 2]);
      outNormals
        ?..add(srcNormals![v * 3])
        ..add(srcNormals[v * 3 + 1])
        ..add(srcNormals[v * 3 + 2]);
    }
  }

  return LineSegmentData(
    positions: Float32List.fromList(outPositions),
    normals: outNormals == null ? null : Float32List.fromList(outNormals),
  );
}