You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
183 lines
5.9 KiB
183 lines
5.9 KiB
1 year ago
|
import {cartesian, cartesianAddInPlace, cartesianCross, cartesianDot, cartesianScale, spherical} from "../cartesian";
|
||
|
import {circleStream} from "../circle";
|
||
|
import {abs, cos, epsilon, pi, radians, sqrt} from "../math";
|
||
|
import pointEqual from "../pointEqual";
|
||
|
import clip from "./index";
|
||
|
|
||
|
export default function(radius) {
|
||
|
var cr = cos(radius),
|
||
|
delta = 6 * radians,
|
||
|
smallRadius = cr > 0,
|
||
|
notHemisphere = abs(cr) > epsilon; // TODO optimise for this common case
|
||
|
|
||
|
function interpolate(from, to, direction, stream) {
|
||
|
circleStream(stream, radius, delta, direction, from, to);
|
||
|
}
|
||
|
|
||
|
function visible(lambda, phi) {
|
||
|
return cos(lambda) * cos(phi) > cr;
|
||
|
}
|
||
|
|
||
|
// Takes a line and cuts into visible segments. Return values used for polygon
|
||
|
// clipping: 0 - there were intersections or the line was empty; 1 - no
|
||
|
// intersections 2 - there were intersections, and the first and last segments
|
||
|
// should be rejoined.
|
||
|
function clipLine(stream) {
|
||
|
var point0, // previous point
|
||
|
c0, // code for previous point
|
||
|
v0, // visibility of previous point
|
||
|
v00, // visibility of first point
|
||
|
clean; // no intersections
|
||
|
return {
|
||
|
lineStart: function() {
|
||
|
v00 = v0 = false;
|
||
|
clean = 1;
|
||
|
},
|
||
|
point: function(lambda, phi) {
|
||
|
var point1 = [lambda, phi],
|
||
|
point2,
|
||
|
v = visible(lambda, phi),
|
||
|
c = smallRadius
|
||
|
? v ? 0 : code(lambda, phi)
|
||
|
: v ? code(lambda + (lambda < 0 ? pi : -pi), phi) : 0;
|
||
|
if (!point0 && (v00 = v0 = v)) stream.lineStart();
|
||
|
// Handle degeneracies.
|
||
|
// TODO ignore if not clipping polygons.
|
||
|
if (v !== v0) {
|
||
|
point2 = intersect(point0, point1);
|
||
|
if (!point2 || pointEqual(point0, point2) || pointEqual(point1, point2)) {
|
||
|
point1[0] += epsilon;
|
||
|
point1[1] += epsilon;
|
||
|
v = visible(point1[0], point1[1]);
|
||
|
}
|
||
|
}
|
||
|
if (v !== v0) {
|
||
|
clean = 0;
|
||
|
if (v) {
|
||
|
// outside going in
|
||
|
stream.lineStart();
|
||
|
point2 = intersect(point1, point0);
|
||
|
stream.point(point2[0], point2[1]);
|
||
|
} else {
|
||
|
// inside going out
|
||
|
point2 = intersect(point0, point1);
|
||
|
stream.point(point2[0], point2[1]);
|
||
|
stream.lineEnd();
|
||
|
}
|
||
|
point0 = point2;
|
||
|
} else if (notHemisphere && point0 && smallRadius ^ v) {
|
||
|
var t;
|
||
|
// If the codes for two points are different, or are both zero,
|
||
|
// and there this segment intersects with the small circle.
|
||
|
if (!(c & c0) && (t = intersect(point1, point0, true))) {
|
||
|
clean = 0;
|
||
|
if (smallRadius) {
|
||
|
stream.lineStart();
|
||
|
stream.point(t[0][0], t[0][1]);
|
||
|
stream.point(t[1][0], t[1][1]);
|
||
|
stream.lineEnd();
|
||
|
} else {
|
||
|
stream.point(t[1][0], t[1][1]);
|
||
|
stream.lineEnd();
|
||
|
stream.lineStart();
|
||
|
stream.point(t[0][0], t[0][1]);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (v && (!point0 || !pointEqual(point0, point1))) {
|
||
|
stream.point(point1[0], point1[1]);
|
||
|
}
|
||
|
point0 = point1, v0 = v, c0 = c;
|
||
|
},
|
||
|
lineEnd: function() {
|
||
|
if (v0) stream.lineEnd();
|
||
|
point0 = null;
|
||
|
},
|
||
|
// Rejoin first and last segments if there were intersections and the first
|
||
|
// and last points were visible.
|
||
|
clean: function() {
|
||
|
return clean | ((v00 && v0) << 1);
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
|
||
|
// Intersects the great circle between a and b with the clip circle.
|
||
|
function intersect(a, b, two) {
|
||
|
var pa = cartesian(a),
|
||
|
pb = cartesian(b);
|
||
|
|
||
|
// We have two planes, n1.p = d1 and n2.p = d2.
|
||
|
// Find intersection line p(t) = c1 n1 + c2 n2 + t (n1 ⨯ n2).
|
||
|
var n1 = [1, 0, 0], // normal
|
||
|
n2 = cartesianCross(pa, pb),
|
||
|
n2n2 = cartesianDot(n2, n2),
|
||
|
n1n2 = n2[0], // cartesianDot(n1, n2),
|
||
|
determinant = n2n2 - n1n2 * n1n2;
|
||
|
|
||
|
// Two polar points.
|
||
|
if (!determinant) return !two && a;
|
||
|
|
||
|
var c1 = cr * n2n2 / determinant,
|
||
|
c2 = -cr * n1n2 / determinant,
|
||
|
n1xn2 = cartesianCross(n1, n2),
|
||
|
A = cartesianScale(n1, c1),
|
||
|
B = cartesianScale(n2, c2);
|
||
|
cartesianAddInPlace(A, B);
|
||
|
|
||
|
// Solve |p(t)|^2 = 1.
|
||
|
var u = n1xn2,
|
||
|
w = cartesianDot(A, u),
|
||
|
uu = cartesianDot(u, u),
|
||
|
t2 = w * w - uu * (cartesianDot(A, A) - 1);
|
||
|
|
||
|
if (t2 < 0) return;
|
||
|
|
||
|
var t = sqrt(t2),
|
||
|
q = cartesianScale(u, (-w - t) / uu);
|
||
|
cartesianAddInPlace(q, A);
|
||
|
q = spherical(q);
|
||
|
|
||
|
if (!two) return q;
|
||
|
|
||
|
// Two intersection points.
|
||
|
var lambda0 = a[0],
|
||
|
lambda1 = b[0],
|
||
|
phi0 = a[1],
|
||
|
phi1 = b[1],
|
||
|
z;
|
||
|
|
||
|
if (lambda1 < lambda0) z = lambda0, lambda0 = lambda1, lambda1 = z;
|
||
|
|
||
|
var delta = lambda1 - lambda0,
|
||
|
polar = abs(delta - pi) < epsilon,
|
||
|
meridian = polar || delta < epsilon;
|
||
|
|
||
|
if (!polar && phi1 < phi0) z = phi0, phi0 = phi1, phi1 = z;
|
||
|
|
||
|
// Check that the first point is between a and b.
|
||
|
if (meridian
|
||
|
? polar
|
||
|
? phi0 + phi1 > 0 ^ q[1] < (abs(q[0] - lambda0) < epsilon ? phi0 : phi1)
|
||
|
: phi0 <= q[1] && q[1] <= phi1
|
||
|
: delta > pi ^ (lambda0 <= q[0] && q[0] <= lambda1)) {
|
||
|
var q1 = cartesianScale(u, (-w + t) / uu);
|
||
|
cartesianAddInPlace(q1, A);
|
||
|
return [q, spherical(q1)];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Generates a 4-bit vector representing the location of a point relative to
|
||
|
// the small circle's bounding box.
|
||
|
function code(lambda, phi) {
|
||
|
var r = smallRadius ? radius : pi - radius,
|
||
|
code = 0;
|
||
|
if (lambda < -r) code |= 1; // left
|
||
|
else if (lambda > r) code |= 2; // right
|
||
|
if (phi < -r) code |= 4; // below
|
||
|
else if (phi > r) code |= 8; // above
|
||
|
return code;
|
||
|
}
|
||
|
|
||
|
return clip(visible, clipLine, interpolate, smallRadius ? [0, -radius] : [-pi, radius - pi]);
|
||
|
}
|