cap function
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);
}
}