ExtrudeGeometry constructor

ExtrudeGeometry(
  1. List<Shape> shapes,
  2. ExtrudeGeometryOptions options
)

shapes — Shape or an array of shapes.

options — Object that can contain the following parameters.

Implementation

ExtrudeGeometry(this.shapes, ExtrudeGeometryOptions options) : super() {
  type = "ExtrudeGeometry";
  parameters = {"shapes": shapes, "options": options};

  final scope = this;

  List<double> verticesArray = [];
  List<double> uvArray = [];

  void addShape(Shape shape) {
    List<double> placeholder = [];

    // options

    int curveSegments = options.curveSegments;
    final steps = options.steps;
    final depth = options.depth;
    bool bevelEnabled = options.bevelEnabled;
    num bevelThickness = options.bevelThickness;
    num bevelSize = options.bevelSize;
    num bevelOffset = options.bevelOffset;
    int bevelSegments = options.bevelSegments;
    final Curve? extrudePath = options.extrudePath;
    const String uvgen = "WorldUVGenerator";// options["UVGenerator"] ??


    late List<Vector?> extrudePts;
    bool extrudeByPath = false;
    late FrenetFrames splineTube;
    Vector3 binormal = Vector3.zero();
    Vector3 normal = Vector3.zero();
    Vector3 position2 = Vector3.zero();

    if (extrudePath != null) {
      extrudePts = extrudePath.getSpacedPoints(steps);

      extrudeByPath = true;
      bevelEnabled = false; // bevels not supported for path extrusion

      // SETUP TNB variables

      // TODO1 - have a .isClosed in spline?

      splineTube = extrudePath.computeFrenetFrames(steps, false);
    }

    // Safeguards if bevels are not enabled

    if (!bevelEnabled) {
      bevelSegments = 0;
      bevelThickness = 0;
      bevelSize = 0;
      bevelOffset = 0;
    }

    // Variables initialization

    final shapePoints = shape.extractPoints(curveSegments);

    List<Vector?> vertices = shapePoints["shape"];
    List<List<Vector?>> holes = (shapePoints["holes"] as List).map((item) => item as List<Vector?>).toList();

    final reverse = !ShapeUtils.isClockWise(vertices);

    if (reverse) {
      vertices = vertices.reversed.toList();

      // Maybe we should also check if holes are in the opposite direction, just to be safe ...

      for (int h = 0, hl = holes.length; h < hl; h++) {
        final ahole = holes[h];

        if (ShapeUtils.isClockWise(ahole)) {
          holes[h] = ahole.reversed.toList();
        }
      }
    }

    final faces = ShapeUtils.triangulateShape(vertices, holes);

    /* Vertices */
    List<Vector?> contour = vertices.sublist(0); // vertices has all points but contour has only points of circumference

    for (int h = 0, hl = holes.length; h < hl; h++) {
      List<Vector?> ahole = holes[h];
      vertices.addAll(ahole);
    }

    scalePt2(pt, vec, size) {
      if (vec == null) {
        console.info('ExtrudeGeometry: vec does not exist');
      }

      return vec.clone().scale(size).add(pt);
    }

    final vlen = vertices.length, flen = faces.length;

    // Find directions for point movement

    Vector2 getBevelVec(Vector inPt, Vector inPrev, Vector inNext) {
      // computes for inPt the corresponding point inPt' on a new contour
      //   shifted by 1 unit (length of normalized vector) to the left
      // if we walk along contour clockwise, this new contour is outside the old one
      //
      // inPt' is the intersection of the two lines parallel to the two
      //  adjacent edges of inPt at a distance of 1 unit on the left side.

      late double vTransX;
      late double vTransY;
      late double shrinkBy; // resulting translation vector for inPt

      // good reading for geometry algorithms (here: line-line intersection)
      // http://geomalgorithms.com/a05-_intersect-1.html

      final vPrevX = inPt.x - inPrev.x;
      final vPrevY = inPt.y - inPrev.y;
      final vNextX = inNext.x - inPt.x;
      final vNextY = inNext.y - inPt.y;

      final vPrevLensq = (vPrevX * vPrevX + vPrevY * vPrevY);

      // check for collinear edges
      final collinear0 = (vPrevX * vNextY - vPrevY * vNextX);

      if (collinear0.abs() > MathUtils.epsilon) {
        // not collinear

        // length of vectors for normalizing

        final vPrevLen = math.sqrt(vPrevLensq);
        final vNextLen = math.sqrt(vNextX * vNextX + vNextY * vNextY);

        // shift adjacent points by unit vectors to the left

        final ptPrevShiftX = (inPrev.x - vPrevY / vPrevLen);
        final ptPrevShiftY = (inPrev.y + vPrevX / vPrevLen);

        final ptNextShiftX = (inNext.x - vNextY / vNextLen);
        final ptNextShiftY = (inNext.y + vNextX / vNextLen);

        // scaling factor for v_prev to intersection point

        final sf = ((ptNextShiftX - ptPrevShiftX) * vNextY -
                (ptNextShiftY - ptPrevShiftY) * vNextX) /
            (vPrevX * vNextY - vPrevY * vNextX);

        // vector from inPt to intersection point

        vTransX = (ptPrevShiftX + vPrevX * sf - inPt.x);
        vTransY = (ptPrevShiftY + vPrevY * sf - inPt.y);

        // Don't normalize!, otherwise sharp corners become ugly
        //  but prevent crazy spikes
        final vTransLensq = (vTransX * vTransX + vTransY * vTransY);
        if (vTransLensq <= 2) {
          return Vector2(vTransX, vTransY);
        } else {
          shrinkBy = math.sqrt(vTransLensq / 2);
        }
      } else {
        // handle special case of collinear edges

        bool directionEq = false; // assumes: opposite

        if (vPrevX > MathUtils.epsilon) {
          if (vNextX > MathUtils.epsilon) {
            directionEq = true;
          }
        } else {
          if (vPrevX < -MathUtils.epsilon) {
            if (vNextX < -MathUtils.epsilon) {
              directionEq = true;
            }
          } else {
            if (vPrevY.sign == vNextY.sign) {
              directionEq = true;
            }
          }
        }

        if (directionEq) {
          // Console.log("Warning: lines are a straight sequence");
          vTransX = -vPrevY;
          vTransY = vPrevX;
          shrinkBy = math.sqrt(vPrevLensq);
        } else {
          // Console.log("Warning: lines are a straight spike");
          vTransX = vPrevX;
          vTransY = vPrevY;
          shrinkBy = math.sqrt(vPrevLensq / 2);
        }
      }

      return Vector2(vTransX / shrinkBy, vTransY / shrinkBy);
    }

    final contourMovements = [];

    for (int i = 0, il = contour.length, j = il - 1, k = i + 1;
        i < il;
        i++, j++, k++) {
      if (j == il) j = 0;
      if (k == il) k = 0;

      //  (j)---(i)---(k)
      // Console.log('i,j,k', i, j , k)

      final v = getBevelVec(contour[i]!, contour[j]!, contour[k]!);

      contourMovements.add(v);
    }

    final holesMovements = [];
    List oneHoleMovements, verticesMovements = contourMovements.sublist(0);

    for (int h = 0, hl = holes.length; h < hl; h++) {
      final ahole = holes[h];

      oneHoleMovements = List<Vector2>.filled(ahole.length, Vector2(0, 0));

      for (int i = 0, il = ahole.length, j = il - 1, k = i + 1;
          i < il;
          i++, j++, k++) {
        if (j == il) j = 0;
        if (k == il) k = 0;

        //  (j)---(i)---(k)
        oneHoleMovements[i] = getBevelVec(ahole[i]!, ahole[j]!, ahole[k]!);
      }

      holesMovements.add(oneHoleMovements);
      verticesMovements.addAll(oneHoleMovements);
    }

    void v(double x, double y, double z) {
      placeholder.add(x);
      placeholder.add(y);
      placeholder.add(z);
    }

    // Loop bevelSegments, 1 for the front, 1 for the back

    for (int b = 0; b < bevelSegments; b++) {
      //for ( b = bevelSegments; b > 0; b -- ) {

      final t = b / bevelSegments;
      final z = bevelThickness * math.cos(t * math.pi / 2);
      final bs = bevelSize * math.sin(t * math.pi / 2) + bevelOffset;

      // contract shape

      for (int i = 0, il = contour.length; i < il; i++) {
        final vert = scalePt2(contour[i], contourMovements[i], bs);

        v(vert.x, vert.y, -z);
      }

      // expand holes

      for (int h = 0, hl = holes.length; h < hl; h++) {
        final ahole = holes[h];
        oneHoleMovements = holesMovements[h];

        for (int i = 0, il = ahole.length; i < il; i++) {
          final vert = scalePt2(ahole[i], oneHoleMovements[i], bs);

          v(vert.x, vert.y, -z);
        }
      }
    }

    final bs = bevelSize + bevelOffset;

    // Back facing vertices

    for (int i = 0; i < vlen; i++) {
      final vert = bevelEnabled
          ? scalePt2(vertices[i], verticesMovements[i], bs)
          : vertices[i];

      if (!extrudeByPath) {
        v(vert.x, vert.y, 0);
      } else {
        normal..setFrom(splineTube.normals![0])..scale(vert.x);
        binormal..setFrom(splineTube.binormals![0])..scale(vert.y);
        position2..setFrom(extrudePts[0]! as Vector3)..add(normal)..add(binormal);
        v(position2.x, position2.y, position2.z);
      }
    }

    // Add stepped vertices...
    // Including front facing vertices

    for (int s = 1; s <= steps; s++) {
      for (int i = 0; i < vlen; i++) {
        final vert = bevelEnabled
            ? scalePt2(vertices[i], verticesMovements[i], bs)
            : vertices[i];

        if (!extrudeByPath) {
          v(vert.x, vert.y, depth / steps * s);
        }
        else {
          normal..setFrom(splineTube.normals![s])..scale(vert.x);
          binormal..setFrom(splineTube.binormals![s])..scale(vert.y);
          position2..setFrom(extrudePts[s] as Vector3)..add(normal)..add(binormal);
          v(position2.x, position2.y, position2.z);
        }
      }
    }

    // Add bevel segments planes

    //for ( b = 1; b <= bevelSegments; b ++ ) {
    for (int b = bevelSegments - 1; b >= 0; b--) {
      final t = b / bevelSegments;
      final z = bevelThickness * math.cos(t * math.pi / 2);
      final bs = bevelSize * math.sin(t * math.pi / 2) + bevelOffset;

      // contract shape

      for (int i = 0, il = contour.length; i < il; i++) {
        final vert = scalePt2(contour[i], contourMovements[i], bs);
        v(vert.x, vert.y, depth + z);
      }

      // expand holes
      for (int h = 0, hl = holes.length; h < hl; h++) {
        final ahole = holes[h];
        oneHoleMovements = holesMovements[h];

        for (int i = 0, il = ahole.length; i < il; i++) {
          final vert = scalePt2(ahole[i], oneHoleMovements[i], bs);

          if (!extrudeByPath) {
            v(vert.x, vert.y, depth + z);
          } else {
            v(vert.x, vert.y + extrudePts[steps - 1]!.y, extrudePts[steps - 1]!.x + z);
          }
        }
      }
    }

    void addUV(Vector2 vector2) {
      uvArray.add(vector2.x);
      uvArray.add(vector2.y);
    }

    void addVertex(num index) {
      verticesArray.add(placeholder[index.toInt() * 3 + 0]);
      verticesArray.add(placeholder[index.toInt() * 3 + 1]);
      verticesArray.add(placeholder[index.toInt() * 3 + 2]);
    }

    void f3(num a, num b, num c) {
      addVertex(a);
      addVertex(b);
      addVertex(c);

      final nextIndex = verticesArray.length ~/ 3;
      dynamic uvs;

      if (uvgen == "WorldUVGenerator") {
        uvs = WorldUVGenerator.generateTopUV(verticesArray, nextIndex - 3, nextIndex - 2, nextIndex - 1);
      }
      else {
        throw ("ExtrudeBufferGeometry uvgen: $uvgen is not support yet ");
      }

      // final uvs = uvgen.generateTopUV( scope, verticesArray, nextIndex - 3, nextIndex - 2, nextIndex - 1 );

      addUV(uvs[0]);
      addUV(uvs[1]);
      addUV(uvs[2]);
    }

    void buildLidFaces() {
      final start = verticesArray.length / 3;

      if (bevelEnabled) {
        int layer = 0; // steps + 1
        int offset = vlen * layer;

        // Bottom faces

        for (int i = 0; i < flen; i++) {
          final face = faces[i];
          f3(face[2] + offset, face[1] + offset, face[0] + offset);
        }

        layer = steps + bevelSegments * 2;
        offset = vlen * layer;

        // Top faces

        for (int i = 0; i < flen; i++) {
          final face = faces[i];
          f3(face[0] + offset, face[1] + offset, face[2] + offset);
        }
      } else {
        // Bottom faces

        for (int i = 0; i < flen; i++) {
          final face = faces[i];
          f3(face[2], face[1], face[0]);
        }

        // Top faces

        for (int i = 0; i < flen; i++) {
          final face = faces[i];
          f3(face[0] + vlen * steps, face[1] + vlen * steps,
              face[2] + vlen * steps);
        }
      }

      scope.addGroup(start.toInt(), (verticesArray.length / 3 - start).toInt(),0);
    }

    void f4(num a, num b, num c, num d) {
      addVertex(a);
      addVertex(b);
      addVertex(d);

      addVertex(b);
      addVertex(c);
      addVertex(d);

      final nextIndex = verticesArray.length ~/ 3;

      dynamic uvs;
      if (uvgen == "WorldUVGenerator") {
        uvs = WorldUVGenerator.generateSideWallUV(verticesArray, nextIndex - 6, nextIndex - 3, nextIndex - 2, nextIndex - 1);
      } else {
        throw ("ExtrudeBufferGeometry uvgen: $uvgen is not support yet ");
      }
      // final uvs = uvgen.generateSideWallUV( scope, verticesArray, nextIndex - 6, nextIndex - 3, nextIndex - 2, nextIndex - 1 );

      addUV(uvs[0]);
      addUV(uvs[1]);
      addUV(uvs[3]);

      addUV(uvs[1]);
      addUV(uvs[2]);
      addUV(uvs[3]);
    }

    void sidewalls(contour, int layeroffset) {
      int i = contour.length;

      while (--i >= 0) {
        final j = i;
        int k = i - 1;
        if (k < 0) k = contour.length - 1;

        //Console.log('b', i,j, i-1, k,vertices.length);

        for (int s = 0, sl = (steps + bevelSegments * 2); s < sl; s++) {
          final slen1 = vlen * s;
          final slen2 = vlen * (s + 1);

          final a = layeroffset + j + slen1;
          final b = layeroffset + k + slen1;
          final c = layeroffset + k + slen2;
          final d = layeroffset + j + slen2;

          f4(a, b, c, d);
        }
      }
    }

    // Create faces for the z-sides of the shape
    void buildSideFaces() {
      int start = verticesArray.length ~/ 3.0;
      int layeroffset = 0;
      sidewalls(contour, layeroffset);
      layeroffset = layeroffset + contour.length;

      for (int h = 0, hl = holes.length; h < hl; h++) {
        List ahole = holes[h];

        sidewalls(ahole, layeroffset);

        //, true
        layeroffset += ahole.length;
      }

      // TODO WHY???  need fix ???
      scope.addGroup(start, (verticesArray.length / 3 - start).toInt(),1);
    }

    /* Faces */

    // Top and bottom faces
    buildLidFaces();

    // Sides faces
    buildSideFaces();
  }

  for (int i = 0, l = shapes.length; i < l; i++) {
    final shape = shapes[i];
    addShape(shape);
  }

  // build geometry

  setAttribute(Attribute.position,Float32BufferAttribute.fromList(Float32List.fromList(verticesArray), 3, false));
  setAttribute(Attribute.uv, Float32BufferAttribute.fromList(Float32List.fromList(uvArray), 2, false));

  computeVertexNormals();

  // functions
}