discontinuedPackage jet_cad_floor_plan has been discontinued and replaced by jet_cad_2d_plan.

cap function

Cap cap(
  1. End me,
  2. Joint joint
)

The cap of me under joint.

  • Free: square at the end's own point.
  • Tee (D4.1, D6): the two faces meet the through wall's near face (the one on the side the stem's body goes to); a corner farther than mitreLimit / 2 × the thicker wall from the end's point squares the end at its own point instead, inside the through wall's body.
  • Node (D5): the straight segment from this end's left corner (the wedge after it) to its right corner (the wedge before it). The node point is never inserted as a cap vertex (D5.3). At two ends that is the mitre (D5.4). At three or more, or when a wedge is clamped, the corners enclose a central polygon, split at exactly repeated vertices into lobes; the lowest (handle, end) whose cap is an edge of a lobe owns it and its cap walks the lobe from its left corner to its right corner (D5.5). A folded (signed area ≤ 0) or self-crossing lobe is dropped and every cap stays straight; a crossing lobe of positive area leaves a hole, which its owner reports (D5.6).

Implementation

Cap cap(End me, Joint joint) {
  switch (joint) {
    case Free():
      return (
        points: [me.leftPoint(me.p), me.rightPoint(me.p)],
        ownsHole: false
      );
    case Tee(:final through):
      final (l, r) = through.offsets;
      final n = Vector2(-through.d.y, through.d.x);
      final off = me.a.dot(n) > 0 ? l : r;
      final q = through.s + n * off;
      final cl = intersect(me.leftPoint(me.p), me.a, q, through.d);
      final cr = intersect(me.rightPoint(me.p), me.a, q, through.d);
      final limit = mitreLimit / 2 * math.max(me.wall.t, through.t);
      if (cl == null ||
          cr == null ||
          (cl - me.p).length > limit ||
          (cr - me.p).length > limit) {
        return (
          points: [me.leftPoint(me.p), me.rightPoint(me.p)],
          ownsHole: false
        );
      }
      return (points: [cl, cr], ownsHole: false);
    case NodeJoint(:final ends):
      final i = ends
          .indexWhere((x) => x.wall.handle == me.wall.handle && x.k == me.k);
      final hub = nodeHub(ends);
      final n = ends.length;
      final wedges = [for (var w = 0; w < n; w++) wedge(ends, w, hub)];
      final left = wedges[i].first; // on my outgoing-left face
      final right = wedges[(i - 1 + n) % n].last; // on my outgoing-right face
      if (n == 2 && wedges.every((w) => w.length == 1)) {
        return (points: [left, right], ownsHole: false); // the mitre
      }
      // The central polygon, anticlockwise: edge q runs from vertex q to
      // q + 1 and carries the index of the end whose cap it is, or -1 for a
      // clamp edge between two feet.
      final verts = <Vector2>[], edgeEnd = <int>[];
      for (var w = 0; w < n; w++) {
        final pts = wedges[w];
        for (var q = 0; q < pts.length; q++) {
          verts.add(pts[q]);
          edgeEnd.add(q < pts.length - 1 ? -1 : (w + 1) % n);
        }
      }
      for (final (ring, labels) in lobes(verts, edgeEnd)) {
        final capsOn = [
          for (final e in labels)
            if (e >= 0) e
        ];
        if (!capsOn.contains(i)) continue;
        capsOn.sort((x, y) => _byHandleThenEnd(ends[x], ends[y]));
        final owner = capsOn.first == i;
        if (ring.length < 3 || !isSimpleCcw(ring)) {
          return (
            points: [left, right],
            ownsHole: owner && ring.length >= 3 && signedArea(ring) > 0
          );
        }
        if (!owner) break;
        // My cap edge runs from my right corner (vertex q) to my left corner
        // (vertex q + 1): start just after it.
        final q = labels.indexOf(i);
        final m = ring.length;
        return (
          points: [for (var s = 1; s <= m; s++) ring[(q + s) % m]],
          ownsHole: false
        );
      }
      return (points: [left, right], ownsHole: false);
  }
}