faceRunsAmong function
host's face runs among walls (any walls; host itself, degenerate
walls and walls that do not touch it are ignored), spec 09c D3. The
left face's runs, then the right face's, each ascending along the
frame's direction. Empty for a degenerate host.
- The face lines are the drawn ones (S-2, T-3): in
host's HostFrame the left face passes throughstartCap.firstandendCap.last, the right face throughstartCap.lastandendCap.first, each mapped to world throughhost.toWorld. A face's direction is the frame'sdthroughtoWorld.transformDirection, normalised (never the difference of its cap points); the line passes through its start-cap point; its extent is the interval between the projections of its two cap points. The frame'slOff/rOffare not used (unscaled, they are off the drawn face under a scaled group). - FaceRun.m is the normal pointing away from the other face line,
FaceRun.thickness
|(pLeft − pRight)·m|for the start-cap points. - Cuts from the walls obstaclesOf names (its intervals are not
used;
stretchesOfnever is): a T (an end of B strictly insidehost) cuts only the face it butts, the left one whentoLocal.transformDirection(End(B, k).a) · frame.n > 0(the host's local frame, S-3); an X cuts each face. Each cut is the range, on that face line, of the two points where B's two drawn face lines (this rule applied to B's own frame, T-2) cross it, so an oblique X cuts each face by its own interval (S-7). Pieces no longer thanwallJoin.linearare dropped. Openings do not cut.
Implementation
List<FaceRun> faceRunsAmong(WorldWall host, List<WorldWall> walls) {
if (host.degenerate) return const [];
final frame = hostFrameOf(host, walls);
if (frame == null) return const [];
final f = _drawnFaces(host, frame);
final d = f.d;
final nd = Vector2(-d.y, d.x);
final mLeft = (f.ls - f.rs).dot(nd) > 0 ? nd : -nd;
final thickness = (f.ls - f.rs).dot(mLeft).abs();
final leftCuts = <(double, double)>[], rightCuts = <(double, double)>[];
final toLocal = host.toWorld.invert();
final done = <int>{};
for (final o in obstaclesOf(frame, host, walls)) {
if (!done.add(o.wall.value)) continue;
final b = walls.firstWhere((x) => x.handle == o.wall);
final others = [
host,
for (final x in walls)
if (x.handle != b.handle) x,
]..sort((x, y) => x.handle.value.compareTo(y.handle.value));
final bFrame = hostFrameOf(b, others);
if (bFrame == null) continue;
final bf = _drawnFaces(b, bFrame);
// The range along d, from [p0], of B's two drawn face lines' crossings
// with the face line through [p0]; null when either is parallel.
(double, double)? cutOn(Vector2 p0) {
final q1 = intersect(p0, d, bf.ls, bf.d);
final q2 = intersect(p0, d, bf.rs, bf.d);
if (q1 == null || q2 == null) return null;
final v1 = (q1 - p0).dot(d), v2 = (q2 - p0).dot(d);
return v1 <= v2 ? (v1, v2) : (v2, v1);
}
var tee = false;
for (final k in const [0, 1]) {
if (!strictlyInside(b.endpoint(k), host)) continue;
tee = true;
final left = toLocal.transformDirection(End(b, k).a).dot(frame.n) > 0;
final cut = cutOn(left ? f.ls : f.rs);
if (cut != null) (left ? leftCuts : rightCuts).add(cut);
}
if (tee) continue;
final l = cutOn(f.ls), r = cutOn(f.rs);
if (l != null) leftCuts.add(l);
if (r != null) rightCuts.add(r);
}
return [
..._pieces(f.ls, (f.le - f.ls).dot(d), d, mLeft, leftCuts, thickness,
host.handle, FaceSide.left),
..._pieces(f.rs, (f.re - f.rs).dot(d), d, -mLeft, rightCuts, thickness,
host.handle, FaceSide.right),
];
}