131 lines
3.5 KiB
JavaScript
131 lines
3.5 KiB
JavaScript
import clipBuffer from "./buffer.js";
|
|
import clipRejoin from "./rejoin.js";
|
|
import {epsilon, halfPi} from "../math.js";
|
|
import polygonContains from "../polygonContains.js";
|
|
import {merge} from "d3-array";
|
|
|
|
export default function(pointVisible, clipLine, interpolate, start) {
|
|
return function(sink) {
|
|
var line = clipLine(sink),
|
|
ringBuffer = clipBuffer(),
|
|
ringSink = clipLine(ringBuffer),
|
|
polygonStarted = false,
|
|
polygon,
|
|
segments,
|
|
ring;
|
|
|
|
var clip = {
|
|
point: point,
|
|
lineStart: lineStart,
|
|
lineEnd: lineEnd,
|
|
polygonStart: function() {
|
|
clip.point = pointRing;
|
|
clip.lineStart = ringStart;
|
|
clip.lineEnd = ringEnd;
|
|
segments = [];
|
|
polygon = [];
|
|
},
|
|
polygonEnd: function() {
|
|
clip.point = point;
|
|
clip.lineStart = lineStart;
|
|
clip.lineEnd = lineEnd;
|
|
segments = merge(segments);
|
|
var startInside = polygonContains(polygon, start);
|
|
if (segments.length) {
|
|
if (!polygonStarted) sink.polygonStart(), polygonStarted = true;
|
|
clipRejoin(segments, compareIntersection, startInside, interpolate, sink);
|
|
} else if (startInside) {
|
|
if (!polygonStarted) sink.polygonStart(), polygonStarted = true;
|
|
sink.lineStart();
|
|
interpolate(null, null, 1, sink);
|
|
sink.lineEnd();
|
|
}
|
|
if (polygonStarted) sink.polygonEnd(), polygonStarted = false;
|
|
segments = polygon = null;
|
|
},
|
|
sphere: function() {
|
|
sink.polygonStart();
|
|
sink.lineStart();
|
|
interpolate(null, null, 1, sink);
|
|
sink.lineEnd();
|
|
sink.polygonEnd();
|
|
}
|
|
};
|
|
|
|
function point(lambda, phi) {
|
|
if (pointVisible(lambda, phi)) sink.point(lambda, phi);
|
|
}
|
|
|
|
function pointLine(lambda, phi) {
|
|
line.point(lambda, phi);
|
|
}
|
|
|
|
function lineStart() {
|
|
clip.point = pointLine;
|
|
line.lineStart();
|
|
}
|
|
|
|
function lineEnd() {
|
|
clip.point = point;
|
|
line.lineEnd();
|
|
}
|
|
|
|
function pointRing(lambda, phi) {
|
|
ring.push([lambda, phi]);
|
|
ringSink.point(lambda, phi);
|
|
}
|
|
|
|
function ringStart() {
|
|
ringSink.lineStart();
|
|
ring = [];
|
|
}
|
|
|
|
function ringEnd() {
|
|
pointRing(ring[0][0], ring[0][1]);
|
|
ringSink.lineEnd();
|
|
|
|
var clean = ringSink.clean(),
|
|
ringSegments = ringBuffer.result(),
|
|
i, n = ringSegments.length, m,
|
|
segment,
|
|
point;
|
|
|
|
ring.pop();
|
|
polygon.push(ring);
|
|
ring = null;
|
|
|
|
if (!n) return;
|
|
|
|
// No intersections.
|
|
if (clean & 1) {
|
|
segment = ringSegments[0];
|
|
if ((m = segment.length - 1) > 0) {
|
|
if (!polygonStarted) sink.polygonStart(), polygonStarted = true;
|
|
sink.lineStart();
|
|
for (i = 0; i < m; ++i) sink.point((point = segment[i])[0], point[1]);
|
|
sink.lineEnd();
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Rejoin connected segments.
|
|
// TODO reuse ringBuffer.rejoin()?
|
|
if (n > 1 && clean & 2) ringSegments.push(ringSegments.pop().concat(ringSegments.shift()));
|
|
|
|
segments.push(ringSegments.filter(validSegment));
|
|
}
|
|
|
|
return clip;
|
|
};
|
|
}
|
|
|
|
function validSegment(segment) {
|
|
return segment.length > 1;
|
|
}
|
|
|
|
// Intersections are sorted along the clip edge. For both antimeridian cutting
|
|
// and circle clipping, the same comparison is used.
|
|
function compareIntersection(a, b) {
|
|
return ((a = a.x)[0] < 0 ? a[1] - halfPi - epsilon : halfPi - a[1])
|
|
- ((b = b.x)[0] < 0 ? b[1] - halfPi - epsilon : halfPi - b[1]);
|
|
}
|