RoundedPolygon.fromVertices constructor

RoundedPolygon.fromVertices({
  1. required List<double> vertices,
  2. CornerRounding rounding = .unrounded,
  3. List<CornerRounding>? perVertexRounding,
  4. double centerX = .minPositive,
  5. double centerY = .minPositive,
})

Implementation

factory RoundedPolygon.fromVertices({
  required List<double> vertices,
  CornerRounding rounding = .unrounded,
  List<CornerRounding>? perVertexRounding,
  double centerX = .minPositive,
  double centerY = .minPositive,
}) {
  if (vertices.length < 6) {
    throw ArgumentError("Polygons must have at least 3 vertices.");
  }
  if (!vertices.length.isEven) {
    throw ArgumentError("The vertices array should have even size.");
  }
  if (perVertexRounding != null &&
      perVertexRounding.length * 2 != vertices.length) {
    throw ArgumentError(
      "perVertexRounding list should be either null or "
      "the same size as the number of vertices (vertices.size / 2)",
    );
  }

  final corners = <List<Cubic>>[];
  final n = vertices.length ~/ 2;
  final roundedCorners = <_RoundedCorner>[];
  for (var i = 0; i < n; i++) {
    final vtxRounding = perVertexRounding?[i] ?? rounding;
    final prevIndex = ((i + n - 1) % n) * 2;
    final nextIndex = ((i + 1) % n) * 2;
    roundedCorners.add(
      _RoundedCorner(
        Point(vertices[prevIndex], vertices[prevIndex + 1]),
        Point(vertices[i * 2], vertices[i * 2 + 1]),
        Point(vertices[nextIndex], vertices[nextIndex + 1]),
        vtxRounding,
      ),
    );
  }

  // For each side, check if we have enough space to do the cuts needed, and if not split
  // the available space, first for round cuts, then for smoothing if there is space left.
  // Each element in this list is a pair, that represent how much we can do of the cut for
  // the given side (side i goes from corner i to corner i+1), the elements of the pair are:
  // first is how much we can use of expectedRoundCut, second how much of expectedCut
  final List<(double, double)> cutAdjusts = List.generate(n, (ix) {
    final expectedRoundCut =
        roundedCorners[ix].expectedRoundCut +
        roundedCorners[(ix + 1) % n].expectedRoundCut;
    final expectedCut =
        roundedCorners[ix].expectedCut +
        roundedCorners[(ix + 1) % n].expectedCut;
    final vtxX = vertices[ix * 2];
    final vtxY = vertices[ix * 2 + 1];
    final nextVtxX = vertices[((ix + 1) % n) * 2];
    final nextVtxY = vertices[((ix + 1) % n) * 2 + 1];
    final sideSize = distance(vtxX - nextVtxX, vtxY - nextVtxY);

    // Check expectedRoundCut first, and ensure we fulfill rounding needs first for
    // both corners before using space for smoothing
    if (expectedRoundCut > sideSize) {
      // Not enough room for fully rounding, see how much we can actually do.
      return (sideSize / expectedRoundCut, 0.0);
    } else if (expectedCut > sideSize) {
      // We can do full rounding, but not full smoothing.
      return (
        1.0,
        (sideSize - expectedRoundCut) / (expectedCut - expectedRoundCut),
      );
    } else {
      // There is enough room for rounding & smoothing.
      return (1.0, 1.0);
    }
  });

  // Create and store list of beziers for each [potentially] rounded corner
  for (var i = 0; i < n; i++) {
    // allowedCuts[0] is for the side from the previous corner to this one,
    // allowedCuts[1] is for the side from this corner to the next one.
    final allowedCuts = List<double>.filled(2, 0.0);
    for (var delta = 0; delta <= 1; delta++) {
      final (roundCutRatio, cutRatio) = cutAdjusts[(i + n - 1 + delta) % n];
      allowedCuts[delta] =
          roundedCorners[i].expectedRoundCut * roundCutRatio +
          (roundedCorners[i].expectedCut -
                  roundedCorners[i].expectedRoundCut) *
              cutRatio;
    }
    corners.add(roundedCorners[i].getCubics(allowedCuts[0], allowedCuts[1]));
  }

  // Finally, store the calculated cubics. This includes all of the rounded corners
  // from above, along with new cubics representing the edges between those corners.
  final tempFeatures = <Feature>[];
  for (var i = 0; i < n; i++) {
    // Note that these indices are for pairs of values (points), they need to be
    // doubled to access the xy values in the vertices float array
    final prevVtxIndex = (i + n - 1) % n;
    final nextVtxIndex = (i + 1) % n;
    final currVertex = Point(vertices[i * 2], vertices[i * 2 + 1]);
    final prevVertex = Point(
      vertices[prevVtxIndex * 2],
      vertices[prevVtxIndex * 2 + 1],
    );
    final nextVertex = Point(
      vertices[nextVtxIndex * 2],
      vertices[nextVtxIndex * 2 + 1],
    );
    tempFeatures
      ..add(Corner(corners[i], convex(prevVertex, currVertex, nextVertex)))
      ..add(
        Edge([
          .straightLine(
            corners[i].last.anchor1X,
            corners[i].last.anchor1Y,
            corners[(i + 1) % n].first.anchor0X,
            corners[(i + 1) % n].first.anchor0Y,
          ),
        ]),
      );
  }

  final c = centerX == .minPositive || centerY == .minPositive
      ? calculateCenter(vertices)
      : Point(centerX, centerY);
  return RoundedPolygon(tempFeatures, c);
}