mirror of
https://github.com/Luzifer/staticmap.git
synced 2025-01-02 03:01:17 +00:00
1405 lines
57 KiB
Go
1405 lines
57 KiB
Go
/*
|
|
Copyright 2015 Google Inc. All rights reserved.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package s2
|
|
|
|
import (
|
|
"math"
|
|
|
|
"github.com/golang/geo/r1"
|
|
"github.com/golang/geo/r2"
|
|
"github.com/golang/geo/r3"
|
|
"github.com/golang/geo/s1"
|
|
)
|
|
|
|
const (
|
|
// edgeClipErrorUVCoord is the maximum error in a u- or v-coordinate
|
|
// compared to the exact result, assuming that the points A and B are in
|
|
// the rectangle [-1,1]x[1,1] or slightly outside it (by 1e-10 or less).
|
|
edgeClipErrorUVCoord = 2.25 * dblEpsilon
|
|
|
|
// edgeClipErrorUVDist is the maximum distance from a clipped point to
|
|
// the corresponding exact result. It is equal to the error in a single
|
|
// coordinate because at most one coordinate is subject to error.
|
|
edgeClipErrorUVDist = 2.25 * dblEpsilon
|
|
|
|
// faceClipErrorRadians is the maximum angle between a returned vertex
|
|
// and the nearest point on the exact edge AB. It is equal to the
|
|
// maximum directional error in PointCross, plus the error when
|
|
// projecting points onto a cube face.
|
|
faceClipErrorRadians = 3 * dblEpsilon
|
|
|
|
// faceClipErrorDist is the same angle expressed as a maximum distance
|
|
// in (u,v)-space. In other words, a returned vertex is at most this far
|
|
// from the exact edge AB projected into (u,v)-space.
|
|
faceClipErrorUVDist = 9 * dblEpsilon
|
|
|
|
// faceClipErrorUVCoord is the maximum angle between a returned vertex
|
|
// and the nearest point on the exact edge AB expressed as the maximum error
|
|
// in an individual u- or v-coordinate. In other words, for each
|
|
// returned vertex there is a point on the exact edge AB whose u- and
|
|
// v-coordinates differ from the vertex by at most this amount.
|
|
faceClipErrorUVCoord = 9.0 * (1.0 / math.Sqrt2) * dblEpsilon
|
|
|
|
// intersectsRectErrorUVDist is the maximum error when computing if a point
|
|
// intersects with a given Rect. If some point of AB is inside the
|
|
// rectangle by at least this distance, the result is guaranteed to be true;
|
|
// if all points of AB are outside the rectangle by at least this distance,
|
|
// the result is guaranteed to be false. This bound assumes that rect is
|
|
// a subset of the rectangle [-1,1]x[-1,1] or extends slightly outside it
|
|
// (e.g., by 1e-10 or less).
|
|
intersectsRectErrorUVDist = 3 * math.Sqrt2 * dblEpsilon
|
|
|
|
// intersectionError can be set somewhat arbitrarily, because the algorithm
|
|
// uses more precision if necessary in order to achieve the specified error.
|
|
// The only strict requirement is that intersectionError >= dblEpsilon
|
|
// radians. However, using a larger error tolerance makes the algorithm more
|
|
// efficient because it reduces the number of cases where exact arithmetic is
|
|
// needed.
|
|
intersectionError = s1.Angle(4 * dblEpsilon)
|
|
|
|
// intersectionMergeRadius is used to ensure that intersection points that
|
|
// are supposed to be coincident are merged back together into a single
|
|
// vertex. This is required in order for various polygon operations (union,
|
|
// intersection, etc) to work correctly. It is twice the intersection error
|
|
// because two coincident intersection points might have errors in
|
|
// opposite directions.
|
|
intersectionMergeRadius = 2 * intersectionError
|
|
)
|
|
|
|
// SimpleCrossing reports whether edge AB crosses CD at a point that is interior
|
|
// to both edges. Properties:
|
|
//
|
|
// (1) SimpleCrossing(b,a,c,d) == SimpleCrossing(a,b,c,d)
|
|
// (2) SimpleCrossing(c,d,a,b) == SimpleCrossing(a,b,c,d)
|
|
func SimpleCrossing(a, b, c, d Point) bool {
|
|
// We compute the equivalent of Sign for triangles ACB, CBD, BDA,
|
|
// and DAC. All of these triangles need to have the same orientation
|
|
// (CW or CCW) for an intersection to exist.
|
|
|
|
ab := a.Vector.Cross(b.Vector)
|
|
acb := -(ab.Dot(c.Vector))
|
|
bda := ab.Dot(d.Vector)
|
|
if acb*bda <= 0 {
|
|
return false
|
|
}
|
|
|
|
cd := c.Vector.Cross(d.Vector)
|
|
cbd := -(cd.Dot(b.Vector))
|
|
dac := cd.Dot(a.Vector)
|
|
return (acb*cbd > 0) && (acb*dac > 0)
|
|
}
|
|
|
|
// VertexCrossing reports whether two edges "cross" in such a way that point-in-polygon
|
|
// containment tests can be implemented by counting the number of edge crossings.
|
|
//
|
|
// Given two edges AB and CD where at least two vertices are identical
|
|
// (i.e. CrossingSign(a,b,c,d) == 0), the basic rule is that a "crossing"
|
|
// occurs if AB is encountered after CD during a CCW sweep around the shared
|
|
// vertex starting from a fixed reference point.
|
|
//
|
|
// Note that according to this rule, if AB crosses CD then in general CD
|
|
// does not cross AB. However, this leads to the correct result when
|
|
// counting polygon edge crossings. For example, suppose that A,B,C are
|
|
// three consecutive vertices of a CCW polygon. If we now consider the edge
|
|
// crossings of a segment BP as P sweeps around B, the crossing number
|
|
// changes parity exactly when BP crosses BA or BC.
|
|
//
|
|
// Useful properties of VertexCrossing (VC):
|
|
//
|
|
// (1) VC(a,a,c,d) == VC(a,b,c,c) == false
|
|
// (2) VC(a,b,a,b) == VC(a,b,b,a) == true
|
|
// (3) VC(a,b,c,d) == VC(a,b,d,c) == VC(b,a,c,d) == VC(b,a,d,c)
|
|
// (3) If exactly one of a,b equals one of c,d, then exactly one of
|
|
// VC(a,b,c,d) and VC(c,d,a,b) is true
|
|
//
|
|
// It is an error to call this method with 4 distinct vertices.
|
|
func VertexCrossing(a, b, c, d Point) bool {
|
|
// If A == B or C == D there is no intersection. We need to check this
|
|
// case first in case 3 or more input points are identical.
|
|
if a.ApproxEqual(b) || c.ApproxEqual(d) {
|
|
return false
|
|
}
|
|
|
|
// If any other pair of vertices is equal, there is a crossing if and only
|
|
// if OrderedCCW indicates that the edge AB is further CCW around the
|
|
// shared vertex O (either A or B) than the edge CD, starting from an
|
|
// arbitrary fixed reference point.
|
|
switch {
|
|
case a.ApproxEqual(d):
|
|
return OrderedCCW(Point{a.Ortho()}, c, b, a)
|
|
case b.ApproxEqual(c):
|
|
return OrderedCCW(Point{b.Ortho()}, d, a, b)
|
|
case a.ApproxEqual(c):
|
|
return OrderedCCW(Point{a.Ortho()}, d, b, a)
|
|
case b.ApproxEqual(d):
|
|
return OrderedCCW(Point{b.Ortho()}, c, a, b)
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// DistanceFraction returns the distance ratio of the point X along an edge AB.
|
|
// If X is on the line segment AB, this is the fraction T such
|
|
// that X == Interpolate(T, A, B).
|
|
//
|
|
// This requires that A and B are distinct.
|
|
func DistanceFraction(x, a, b Point) float64 {
|
|
d0 := x.Angle(a.Vector)
|
|
d1 := x.Angle(b.Vector)
|
|
return float64(d0 / (d0 + d1))
|
|
}
|
|
|
|
// Interpolate returns the point X along the line segment AB whose distance from A
|
|
// is the given fraction "t" of the distance AB. Does NOT require that "t" be
|
|
// between 0 and 1. Note that all distances are measured on the surface of
|
|
// the sphere, so this is more complicated than just computing (1-t)*a + t*b
|
|
// and normalizing the result.
|
|
func Interpolate(t float64, a, b Point) Point {
|
|
if t == 0 {
|
|
return a
|
|
}
|
|
if t == 1 {
|
|
return b
|
|
}
|
|
ab := a.Angle(b.Vector)
|
|
return InterpolateAtDistance(s1.Angle(t)*ab, a, b)
|
|
}
|
|
|
|
// InterpolateAtDistance returns the point X along the line segment AB whose
|
|
// distance from A is the angle ax.
|
|
func InterpolateAtDistance(ax s1.Angle, a, b Point) Point {
|
|
aRad := ax.Radians()
|
|
|
|
// Use PointCross to compute the tangent vector at A towards B. The
|
|
// result is always perpendicular to A, even if A=B or A=-B, but it is not
|
|
// necessarily unit length. (We effectively normalize it below.)
|
|
normal := a.PointCross(b)
|
|
tangent := normal.Vector.Cross(a.Vector)
|
|
|
|
// Now compute the appropriate linear combination of A and "tangent". With
|
|
// infinite precision the result would always be unit length, but we
|
|
// normalize it anyway to ensure that the error is within acceptable bounds.
|
|
// (Otherwise errors can build up when the result of one interpolation is
|
|
// fed into another interpolation.)
|
|
return Point{(a.Mul(math.Cos(aRad)).Add(tangent.Mul(math.Sin(aRad) / tangent.Norm()))).Normalize()}
|
|
}
|
|
|
|
// RectBounder is used to compute a bounding rectangle that contains all edges
|
|
// defined by a vertex chain (v0, v1, v2, ...). All vertices must be unit length.
|
|
// Note that the bounding rectangle of an edge can be larger than the bounding
|
|
// rectangle of its endpoints, e.g. consider an edge that passes through the North Pole.
|
|
//
|
|
// The bounds are calculated conservatively to account for numerical errors
|
|
// when points are converted to LatLngs. More precisely, this function
|
|
// guarantees the following:
|
|
// Let L be a closed edge chain (Loop) such that the interior of the loop does
|
|
// not contain either pole. Now if P is any point such that L.ContainsPoint(P),
|
|
// then RectBound(L).ContainsPoint(LatLngFromPoint(P)).
|
|
type RectBounder struct {
|
|
// The previous vertex in the chain.
|
|
a Point
|
|
// The previous vertex latitude longitude.
|
|
aLL LatLng
|
|
bound Rect
|
|
}
|
|
|
|
// NewRectBounder returns a new instance of a RectBounder.
|
|
func NewRectBounder() *RectBounder {
|
|
return &RectBounder{
|
|
bound: EmptyRect(),
|
|
}
|
|
}
|
|
|
|
// maxErrorForTests returns the maximum error in RectBound provided that the
|
|
// result does not include either pole. It is only used for testing purposes
|
|
func (r *RectBounder) maxErrorForTests() LatLng {
|
|
// The maximum error in the latitude calculation is
|
|
// 3.84 * dblEpsilon for the PointCross calculation
|
|
// 0.96 * dblEpsilon for the Latitude calculation
|
|
// 5 * dblEpsilon added by AddPoint/RectBound to compensate for error
|
|
// -----------------
|
|
// 9.80 * dblEpsilon maximum error in result
|
|
//
|
|
// The maximum error in the longitude calculation is dblEpsilon. RectBound
|
|
// does not do any expansion because this isn't necessary in order to
|
|
// bound the *rounded* longitudes of contained points.
|
|
return LatLng{10 * dblEpsilon * s1.Radian, 1 * dblEpsilon * s1.Radian}
|
|
}
|
|
|
|
// AddPoint adds the given point to the chain. The Point must be unit length.
|
|
func (r *RectBounder) AddPoint(b Point) {
|
|
bLL := LatLngFromPoint(b)
|
|
|
|
if r.bound.IsEmpty() {
|
|
r.a = b
|
|
r.aLL = bLL
|
|
r.bound = r.bound.AddPoint(bLL)
|
|
return
|
|
}
|
|
|
|
// First compute the cross product N = A x B robustly. This is the normal
|
|
// to the great circle through A and B. We don't use RobustSign
|
|
// since that method returns an arbitrary vector orthogonal to A if the two
|
|
// vectors are proportional, and we want the zero vector in that case.
|
|
n := r.a.Sub(b.Vector).Cross(r.a.Add(b.Vector)) // N = 2 * (A x B)
|
|
|
|
// The relative error in N gets large as its norm gets very small (i.e.,
|
|
// when the two points are nearly identical or antipodal). We handle this
|
|
// by choosing a maximum allowable error, and if the error is greater than
|
|
// this we fall back to a different technique. Since it turns out that
|
|
// the other sources of error in converting the normal to a maximum
|
|
// latitude add up to at most 1.16 * dblEpsilon, and it is desirable to
|
|
// have the total error be a multiple of dblEpsilon, we have chosen to
|
|
// limit the maximum error in the normal to be 3.84 * dblEpsilon.
|
|
// It is possible to show that the error is less than this when
|
|
//
|
|
// n.Norm() >= 8 * sqrt(3) / (3.84 - 0.5 - sqrt(3)) * dblEpsilon
|
|
// = 1.91346e-15 (about 8.618 * dblEpsilon)
|
|
nNorm := n.Norm()
|
|
if nNorm < 1.91346e-15 {
|
|
// A and B are either nearly identical or nearly antipodal (to within
|
|
// 4.309 * dblEpsilon, or about 6 nanometers on the earth's surface).
|
|
if r.a.Dot(b.Vector) < 0 {
|
|
// The two points are nearly antipodal. The easiest solution is to
|
|
// assume that the edge between A and B could go in any direction
|
|
// around the sphere.
|
|
r.bound = FullRect()
|
|
} else {
|
|
// The two points are nearly identical (to within 4.309 * dblEpsilon).
|
|
// In this case we can just use the bounding rectangle of the points,
|
|
// since after the expansion done by GetBound this Rect is
|
|
// guaranteed to include the (lat,lng) values of all points along AB.
|
|
r.bound = r.bound.Union(RectFromLatLng(r.aLL).AddPoint(bLL))
|
|
}
|
|
r.a = b
|
|
r.aLL = bLL
|
|
return
|
|
}
|
|
|
|
// Compute the longitude range spanned by AB.
|
|
lngAB := s1.EmptyInterval().AddPoint(r.aLL.Lng.Radians()).AddPoint(bLL.Lng.Radians())
|
|
if lngAB.Length() >= math.Pi-2*dblEpsilon {
|
|
// The points lie on nearly opposite lines of longitude to within the
|
|
// maximum error of the calculation. The easiest solution is to assume
|
|
// that AB could go on either side of the pole.
|
|
lngAB = s1.FullInterval()
|
|
}
|
|
|
|
// Next we compute the latitude range spanned by the edge AB. We start
|
|
// with the range spanning the two endpoints of the edge:
|
|
latAB := r1.IntervalFromPoint(r.aLL.Lat.Radians()).AddPoint(bLL.Lat.Radians())
|
|
|
|
// This is the desired range unless the edge AB crosses the plane
|
|
// through N and the Z-axis (which is where the great circle through A
|
|
// and B attains its minimum and maximum latitudes). To test whether AB
|
|
// crosses this plane, we compute a vector M perpendicular to this
|
|
// plane and then project A and B onto it.
|
|
m := n.Cross(r3.Vector{0, 0, 1})
|
|
mA := m.Dot(r.a.Vector)
|
|
mB := m.Dot(b.Vector)
|
|
|
|
// We want to test the signs of "mA" and "mB", so we need to bound
|
|
// the error in these calculations. It is possible to show that the
|
|
// total error is bounded by
|
|
//
|
|
// (1 + sqrt(3)) * dblEpsilon * nNorm + 8 * sqrt(3) * (dblEpsilon**2)
|
|
// = 6.06638e-16 * nNorm + 6.83174e-31
|
|
|
|
mError := 6.06638e-16*nNorm + 6.83174e-31
|
|
if mA*mB < 0 || math.Abs(mA) <= mError || math.Abs(mB) <= mError {
|
|
// Minimum/maximum latitude *may* occur in the edge interior.
|
|
//
|
|
// The maximum latitude is 90 degrees minus the latitude of N. We
|
|
// compute this directly using atan2 in order to get maximum accuracy
|
|
// near the poles.
|
|
//
|
|
// Our goal is compute a bound that contains the computed latitudes of
|
|
// all S2Points P that pass the point-in-polygon containment test.
|
|
// There are three sources of error we need to consider:
|
|
// - the directional error in N (at most 3.84 * dblEpsilon)
|
|
// - converting N to a maximum latitude
|
|
// - computing the latitude of the test point P
|
|
// The latter two sources of error are at most 0.955 * dblEpsilon
|
|
// individually, but it is possible to show by a more complex analysis
|
|
// that together they can add up to at most 1.16 * dblEpsilon, for a
|
|
// total error of 5 * dblEpsilon.
|
|
//
|
|
// We add 3 * dblEpsilon to the bound here, and GetBound() will pad
|
|
// the bound by another 2 * dblEpsilon.
|
|
maxLat := math.Min(
|
|
math.Atan2(math.Sqrt(n.X*n.X+n.Y*n.Y), math.Abs(n.Z))+3*dblEpsilon,
|
|
math.Pi/2)
|
|
|
|
// In order to get tight bounds when the two points are close together,
|
|
// we also bound the min/max latitude relative to the latitudes of the
|
|
// endpoints A and B. First we compute the distance between A and B,
|
|
// and then we compute the maximum change in latitude between any two
|
|
// points along the great circle that are separated by this distance.
|
|
// This gives us a latitude change "budget". Some of this budget must
|
|
// be spent getting from A to B; the remainder bounds the round-trip
|
|
// distance (in latitude) from A or B to the min or max latitude
|
|
// attained along the edge AB.
|
|
latBudget := 2 * math.Asin(0.5*(r.a.Sub(b.Vector)).Norm()*math.Sin(maxLat))
|
|
maxDelta := 0.5*(latBudget-latAB.Length()) + dblEpsilon
|
|
|
|
// Test whether AB passes through the point of maximum latitude or
|
|
// minimum latitude. If the dot product(s) are small enough then the
|
|
// result may be ambiguous.
|
|
if mA <= mError && mB >= -mError {
|
|
latAB.Hi = math.Min(maxLat, latAB.Hi+maxDelta)
|
|
}
|
|
if mB <= mError && mA >= -mError {
|
|
latAB.Lo = math.Max(-maxLat, latAB.Lo-maxDelta)
|
|
}
|
|
}
|
|
r.a = b
|
|
r.aLL = bLL
|
|
r.bound = r.bound.Union(Rect{latAB, lngAB})
|
|
}
|
|
|
|
// RectBound returns the bounding rectangle of the edge chain that connects the
|
|
// vertices defined so far. This bound satisfies the guarantee made
|
|
// above, i.e. if the edge chain defines a Loop, then the bound contains
|
|
// the LatLng coordinates of all Points contained by the loop.
|
|
func (r *RectBounder) RectBound() Rect {
|
|
return r.bound.expanded(LatLng{s1.Angle(2 * dblEpsilon), 0}).PolarClosure()
|
|
}
|
|
|
|
// ExpandForSubregions expands a bounding Rect so that it is guaranteed to
|
|
// contain the bounds of any subregion whose bounds are computed using
|
|
// ComputeRectBound. For example, consider a loop L that defines a square.
|
|
// GetBound ensures that if a point P is contained by this square, then
|
|
// LatLngFromPoint(P) is contained by the bound. But now consider a diamond
|
|
// shaped loop S contained by L. It is possible that GetBound returns a
|
|
// *larger* bound for S than it does for L, due to rounding errors. This
|
|
// method expands the bound for L so that it is guaranteed to contain the
|
|
// bounds of any subregion S.
|
|
//
|
|
// More precisely, if L is a loop that does not contain either pole, and S
|
|
// is a loop such that L.Contains(S), then
|
|
//
|
|
// ExpandForSubregions(L.RectBound).Contains(S.RectBound).
|
|
//
|
|
func ExpandForSubregions(bound Rect) Rect {
|
|
// Empty bounds don't need expansion.
|
|
if bound.IsEmpty() {
|
|
return bound
|
|
}
|
|
|
|
// First we need to check whether the bound B contains any nearly-antipodal
|
|
// points (to within 4.309 * dblEpsilon). If so then we need to return
|
|
// FullRect, since the subregion might have an edge between two
|
|
// such points, and AddPoint returns Full for such edges. Note that
|
|
// this can happen even if B is not Full for example, consider a loop
|
|
// that defines a 10km strip straddling the equator extending from
|
|
// longitudes -100 to +100 degrees.
|
|
//
|
|
// It is easy to check whether B contains any antipodal points, but checking
|
|
// for nearly-antipodal points is trickier. Essentially we consider the
|
|
// original bound B and its reflection through the origin B', and then test
|
|
// whether the minimum distance between B and B' is less than 4.309 * dblEpsilon.
|
|
|
|
// lngGap is a lower bound on the longitudinal distance between B and its
|
|
// reflection B'. (2.5 * dblEpsilon is the maximum combined error of the
|
|
// endpoint longitude calculations and the Length call.)
|
|
lngGap := math.Max(0, math.Pi-bound.Lng.Length()-2.5*dblEpsilon)
|
|
|
|
// minAbsLat is the minimum distance from B to the equator (if zero or
|
|
// negative, then B straddles the equator).
|
|
minAbsLat := math.Max(bound.Lat.Lo, -bound.Lat.Hi)
|
|
|
|
// latGapSouth and latGapNorth measure the minimum distance from B to the
|
|
// south and north poles respectively.
|
|
latGapSouth := math.Pi/2 + bound.Lat.Lo
|
|
latGapNorth := math.Pi/2 - bound.Lat.Hi
|
|
|
|
if minAbsLat >= 0 {
|
|
// The bound B does not straddle the equator. In this case the minimum
|
|
// distance is between one endpoint of the latitude edge in B closest to
|
|
// the equator and the other endpoint of that edge in B'. The latitude
|
|
// distance between these two points is 2*minAbsLat, and the longitude
|
|
// distance is lngGap. We could compute the distance exactly using the
|
|
// Haversine formula, but then we would need to bound the errors in that
|
|
// calculation. Since we only need accuracy when the distance is very
|
|
// small (close to 4.309 * dblEpsilon), we substitute the Euclidean
|
|
// distance instead. This gives us a right triangle XYZ with two edges of
|
|
// length x = 2*minAbsLat and y ~= lngGap. The desired distance is the
|
|
// length of the third edge z, and we have
|
|
//
|
|
// z ~= sqrt(x^2 + y^2) >= (x + y) / sqrt(2)
|
|
//
|
|
// Therefore the region may contain nearly antipodal points only if
|
|
//
|
|
// 2*minAbsLat + lngGap < sqrt(2) * 4.309 * dblEpsilon
|
|
// ~= 1.354e-15
|
|
//
|
|
// Note that because the given bound B is conservative, minAbsLat and
|
|
// lngGap are both lower bounds on their true values so we do not need
|
|
// to make any adjustments for their errors.
|
|
if 2*minAbsLat+lngGap < 1.354e-15 {
|
|
return FullRect()
|
|
}
|
|
} else if lngGap >= math.Pi/2 {
|
|
// B spans at most Pi/2 in longitude. The minimum distance is always
|
|
// between one corner of B and the diagonally opposite corner of B'. We
|
|
// use the same distance approximation that we used above; in this case
|
|
// we have an obtuse triangle XYZ with two edges of length x = latGapSouth
|
|
// and y = latGapNorth, and angle Z >= Pi/2 between them. We then have
|
|
//
|
|
// z >= sqrt(x^2 + y^2) >= (x + y) / sqrt(2)
|
|
//
|
|
// Unlike the case above, latGapSouth and latGapNorth are not lower bounds
|
|
// (because of the extra addition operation, and because math.Pi/2 is not
|
|
// exactly equal to Pi/2); they can exceed their true values by up to
|
|
// 0.75 * dblEpsilon. Putting this all together, the region may contain
|
|
// nearly antipodal points only if
|
|
//
|
|
// latGapSouth + latGapNorth < (sqrt(2) * 4.309 + 1.5) * dblEpsilon
|
|
// ~= 1.687e-15
|
|
if latGapSouth+latGapNorth < 1.687e-15 {
|
|
return FullRect()
|
|
}
|
|
} else {
|
|
// Otherwise we know that (1) the bound straddles the equator and (2) its
|
|
// width in longitude is at least Pi/2. In this case the minimum
|
|
// distance can occur either between a corner of B and the diagonally
|
|
// opposite corner of B' (as in the case above), or between a corner of B
|
|
// and the opposite longitudinal edge reflected in B'. It is sufficient
|
|
// to only consider the corner-edge case, since this distance is also a
|
|
// lower bound on the corner-corner distance when that case applies.
|
|
|
|
// Consider the spherical triangle XYZ where X is a corner of B with
|
|
// minimum absolute latitude, Y is the closest pole to X, and Z is the
|
|
// point closest to X on the opposite longitudinal edge of B'. This is a
|
|
// right triangle (Z = Pi/2), and from the spherical law of sines we have
|
|
//
|
|
// sin(z) / sin(Z) = sin(y) / sin(Y)
|
|
// sin(maxLatGap) / 1 = sin(dMin) / sin(lngGap)
|
|
// sin(dMin) = sin(maxLatGap) * sin(lngGap)
|
|
//
|
|
// where "maxLatGap" = max(latGapSouth, latGapNorth) and "dMin" is the
|
|
// desired minimum distance. Now using the facts that sin(t) >= (2/Pi)*t
|
|
// for 0 <= t <= Pi/2, that we only need an accurate approximation when
|
|
// at least one of "maxLatGap" or lngGap is extremely small (in which
|
|
// case sin(t) ~= t), and recalling that "maxLatGap" has an error of up
|
|
// to 0.75 * dblEpsilon, we want to test whether
|
|
//
|
|
// maxLatGap * lngGap < (4.309 + 0.75) * (Pi/2) * dblEpsilon
|
|
// ~= 1.765e-15
|
|
if math.Max(latGapSouth, latGapNorth)*lngGap < 1.765e-15 {
|
|
return FullRect()
|
|
}
|
|
}
|
|
// Next we need to check whether the subregion might contain any edges that
|
|
// span (math.Pi - 2 * dblEpsilon) radians or more in longitude, since AddPoint
|
|
// sets the longitude bound to Full in that case. This corresponds to
|
|
// testing whether (lngGap <= 0) in lngExpansion below.
|
|
|
|
// Otherwise, the maximum latitude error in AddPoint is 4.8 * dblEpsilon.
|
|
// In the worst case, the errors when computing the latitude bound for a
|
|
// subregion could go in the opposite direction as the errors when computing
|
|
// the bound for the original region, so we need to double this value.
|
|
// (More analysis shows that it's okay to round down to a multiple of
|
|
// dblEpsilon.)
|
|
//
|
|
// For longitude, we rely on the fact that atan2 is correctly rounded and
|
|
// therefore no additional bounds expansion is necessary.
|
|
|
|
latExpansion := 9 * dblEpsilon
|
|
lngExpansion := 0.0
|
|
if lngGap <= 0 {
|
|
lngExpansion = math.Pi
|
|
}
|
|
return bound.expanded(LatLng{s1.Angle(latExpansion), s1.Angle(lngExpansion)}).PolarClosure()
|
|
}
|
|
|
|
// EdgeCrosser allows edges to be efficiently tested for intersection with a
|
|
// given fixed edge AB. It is especially efficient when testing for
|
|
// intersection with an edge chain connecting vertices v0, v1, v2, ...
|
|
type EdgeCrosser struct {
|
|
a Point
|
|
b Point
|
|
aXb Point
|
|
|
|
// To reduce the number of calls to expensiveSign, we compute an
|
|
// outward-facing tangent at A and B if necessary. If the plane
|
|
// perpendicular to one of these tangents separates AB from CD (i.e., one
|
|
// edge on each side) then there is no intersection.
|
|
aTangent Point // Outward-facing tangent at A.
|
|
bTangent Point // Outward-facing tangent at B.
|
|
|
|
// The fields below are updated for each vertex in the chain.
|
|
c Point // Previous vertex in the vertex chain.
|
|
acb Direction // The orientation of triangle ACB.
|
|
}
|
|
|
|
// NewEdgeCrosser returns an EdgeCrosser with the fixed edge AB.
|
|
func NewEdgeCrosser(a, b Point) *EdgeCrosser {
|
|
norm := a.PointCross(b)
|
|
return &EdgeCrosser{
|
|
a: a,
|
|
b: b,
|
|
aXb: Point{a.Cross(b.Vector)},
|
|
aTangent: Point{a.Cross(norm.Vector)},
|
|
bTangent: Point{norm.Cross(b.Vector)},
|
|
}
|
|
}
|
|
|
|
// A Crossing indicates how edges cross.
|
|
type Crossing int
|
|
|
|
const (
|
|
// Cross means the edges cross.
|
|
Cross Crossing = iota
|
|
// MaybeCross means two vertices from different edges are the same.
|
|
MaybeCross
|
|
// DoNotCross means the edges do not cross.
|
|
DoNotCross
|
|
)
|
|
|
|
// CrossingSign reports whether the edge AB intersects the edge CD. If any two
|
|
// vertices from different edges are the same, returns MaybeCross. If either edge
|
|
// is degenerate (A == B or C == D), returns either DoNotCross or MaybeCross.
|
|
//
|
|
// Properties of CrossingSign:
|
|
//
|
|
// (1) CrossingSign(b,a,c,d) == CrossingSign(a,b,c,d)
|
|
// (2) CrossingSign(c,d,a,b) == CrossingSign(a,b,c,d)
|
|
// (3) CrossingSign(a,b,c,d) == MaybeCross if a==c, a==d, b==c, b==d
|
|
// (3) CrossingSign(a,b,c,d) == DoNotCross or MaybeCross if a==b or c==d
|
|
//
|
|
// Note that if you want to check an edge against a chain of other edges,
|
|
// it is slightly more efficient to use the single-argument version
|
|
// ChainCrossingSign below.
|
|
func (e *EdgeCrosser) CrossingSign(c, d Point) Crossing {
|
|
if c != e.c {
|
|
e.RestartAt(c)
|
|
}
|
|
return e.ChainCrossingSign(d)
|
|
}
|
|
|
|
// EdgeOrVertexCrossing reports whether if CrossingSign(c, d) > 0, or AB and
|
|
// CD share a vertex and VertexCrossing(a, b, c, d) is true.
|
|
//
|
|
// This method extends the concept of a "crossing" to the case where AB
|
|
// and CD have a vertex in common. The two edges may or may not cross,
|
|
// according to the rules defined in VertexCrossing above. The rules
|
|
// are designed so that point containment tests can be implemented simply
|
|
// by counting edge crossings. Similarly, determining whether one edge
|
|
// chain crosses another edge chain can be implemented by counting.
|
|
func (e *EdgeCrosser) EdgeOrVertexCrossing(c, d Point) bool {
|
|
if c != e.c {
|
|
e.RestartAt(c)
|
|
}
|
|
return e.EdgeOrVertexChainCrossing(d)
|
|
}
|
|
|
|
// NewChainEdgeCrosser is a convenience constructor that uses AB as the fixed edge,
|
|
// and C as the first vertex of the vertex chain (equivalent to calling RestartAt(c)).
|
|
//
|
|
// You don't need to use this or any of the chain functions unless you're trying to
|
|
// squeeze out every last drop of performance. Essentially all you are saving is a test
|
|
// whether the first vertex of the current edge is the same as the second vertex of the
|
|
// previous edge.
|
|
func NewChainEdgeCrosser(a, b, c Point) *EdgeCrosser {
|
|
e := NewEdgeCrosser(a, b)
|
|
e.RestartAt(c)
|
|
return e
|
|
}
|
|
|
|
// RestartAt sets the current point of the edge crosser to be c.
|
|
// Call this method when your chain 'jumps' to a new place.
|
|
// The argument must point to a value that persists until the next call.
|
|
func (e *EdgeCrosser) RestartAt(c Point) {
|
|
e.c = c
|
|
e.acb = -triageSign(e.a, e.b, e.c)
|
|
}
|
|
|
|
// ChainCrossingSign is like CrossingSign, but uses the last vertex passed to one of
|
|
// the crossing methods (or RestartAt) as the first vertex of the current edge.
|
|
func (e *EdgeCrosser) ChainCrossingSign(d Point) Crossing {
|
|
// For there to be an edge crossing, the triangles ACB, CBD, BDA, DAC must
|
|
// all be oriented the same way (CW or CCW). We keep the orientation of ACB
|
|
// as part of our state. When each new point D arrives, we compute the
|
|
// orientation of BDA and check whether it matches ACB. This checks whether
|
|
// the points C and D are on opposite sides of the great circle through AB.
|
|
|
|
// Recall that triageSign is invariant with respect to rotating its
|
|
// arguments, i.e. ABD has the same orientation as BDA.
|
|
bda := triageSign(e.a, e.b, d)
|
|
if e.acb == -bda && bda != Indeterminate {
|
|
// The most common case -- triangles have opposite orientations. Save the
|
|
// current vertex D as the next vertex C, and also save the orientation of
|
|
// the new triangle ACB (which is opposite to the current triangle BDA).
|
|
e.c = d
|
|
e.acb = -bda
|
|
return DoNotCross
|
|
}
|
|
return e.crossingSign(d, bda)
|
|
}
|
|
|
|
// EdgeOrVertexChainCrossing is like EdgeOrVertexCrossing, but uses the last vertex
|
|
// passed to one of the crossing methods (or RestartAt) as the first vertex of the current edge.
|
|
func (e *EdgeCrosser) EdgeOrVertexChainCrossing(d Point) bool {
|
|
// We need to copy e.c since it is clobbered by ChainCrossingSign.
|
|
c := e.c
|
|
switch e.ChainCrossingSign(d) {
|
|
case DoNotCross:
|
|
return false
|
|
case Cross:
|
|
return true
|
|
}
|
|
return VertexCrossing(e.a, e.b, c, d)
|
|
}
|
|
|
|
// crossingSign handle the slow path of CrossingSign.
|
|
func (e *EdgeCrosser) crossingSign(d Point, bda Direction) Crossing {
|
|
// Compute the actual result, and then save the current vertex D as the next
|
|
// vertex C, and save the orientation of the next triangle ACB (which is
|
|
// opposite to the current triangle BDA).
|
|
defer func() {
|
|
e.c = d
|
|
e.acb = -bda
|
|
}()
|
|
|
|
// At this point, a very common situation is that A,B,C,D are four points on
|
|
// a line such that AB does not overlap CD. (For example, this happens when
|
|
// a line or curve is sampled finely, or when geometry is constructed by
|
|
// computing the union of S2CellIds.) Most of the time, we can determine
|
|
// that AB and CD do not intersect using the two outward-facing
|
|
// tangents at A and B (parallel to AB) and testing whether AB and CD are on
|
|
// opposite sides of the plane perpendicular to one of these tangents. This
|
|
// is moderately expensive but still much cheaper than expensiveSign.
|
|
|
|
// The error in RobustCrossProd is insignificant. The maximum error in
|
|
// the call to CrossProd (i.e., the maximum norm of the error vector) is
|
|
// (0.5 + 1/sqrt(3)) * dblEpsilon. The maximum error in each call to
|
|
// DotProd below is dblEpsilon. (There is also a small relative error
|
|
// term that is insignificant because we are comparing the result against a
|
|
// constant that is very close to zero.)
|
|
maxError := (1.5 + 1/math.Sqrt(3)) * dblEpsilon
|
|
if (e.c.Dot(e.aTangent.Vector) > maxError && d.Dot(e.aTangent.Vector) > maxError) || (e.c.Dot(e.bTangent.Vector) > maxError && d.Dot(e.bTangent.Vector) > maxError) {
|
|
return DoNotCross
|
|
}
|
|
|
|
// Otherwise, eliminate the cases where any two vertices are equal. (These
|
|
// cases could be handled in the code below, but since ExpensiveSign lives
|
|
// up to its name we would rather avoid calling it if possible.)
|
|
//
|
|
// These are the cases where two vertices from different edges are equal.
|
|
if e.a == e.c || e.a == d || e.b == e.c || e.b == d {
|
|
return MaybeCross
|
|
}
|
|
|
|
// These are the cases where an input edge is degenerate. (Note that in
|
|
// most cases, if CD is degenerate then this method is not even called
|
|
// because acb and bda have different signs. That's why this method is
|
|
// documented to return either MaybeCross or DoNotCross when an input
|
|
// edge is degenerate.)
|
|
if e.a == e.b || e.c == d {
|
|
return MaybeCross
|
|
}
|
|
|
|
// Otherwise it's time to break out the big guns.
|
|
if e.acb == Indeterminate {
|
|
e.acb = -expensiveSign(e.a, e.b, e.c)
|
|
}
|
|
if bda == Indeterminate {
|
|
bda = expensiveSign(e.a, e.b, d)
|
|
}
|
|
|
|
if bda != e.acb {
|
|
return DoNotCross
|
|
}
|
|
|
|
cbd := -RobustSign(e.c, d, e.b)
|
|
if cbd != e.acb {
|
|
return DoNotCross
|
|
}
|
|
dac := RobustSign(e.c, d, e.a)
|
|
if dac != e.acb {
|
|
return DoNotCross
|
|
}
|
|
return Cross
|
|
}
|
|
|
|
// pointUVW represents a Point in (u,v,w) coordinate space of a cube face.
|
|
type pointUVW Point
|
|
|
|
// intersectsFace reports whether a given directed line L intersects the cube face F.
|
|
// The line L is defined by its normal N in the (u,v,w) coordinates of F.
|
|
func (p pointUVW) intersectsFace() bool {
|
|
// L intersects the [-1,1]x[-1,1] square in (u,v) if and only if the dot
|
|
// products of N with the four corner vertices (-1,-1,1), (1,-1,1), (1,1,1),
|
|
// and (-1,1,1) do not all have the same sign. This is true exactly when
|
|
// |Nu| + |Nv| >= |Nw|. The code below evaluates this expression exactly.
|
|
u := math.Abs(p.X)
|
|
v := math.Abs(p.Y)
|
|
w := math.Abs(p.Z)
|
|
|
|
// We only need to consider the cases where u or v is the smallest value,
|
|
// since if w is the smallest then both expressions below will have a
|
|
// positive LHS and a negative RHS.
|
|
return (v >= w-u) && (u >= w-v)
|
|
}
|
|
|
|
// intersectsOppositeEdges reports whether a directed line L intersects two
|
|
// opposite edges of a cube face F. This includs the case where L passes
|
|
// exactly through a corner vertex of F. The directed line L is defined
|
|
// by its normal N in the (u,v,w) coordinates of F.
|
|
func (p pointUVW) intersectsOppositeEdges() bool {
|
|
// The line L intersects opposite edges of the [-1,1]x[-1,1] (u,v) square if
|
|
// and only exactly two of the corner vertices lie on each side of L. This
|
|
// is true exactly when ||Nu| - |Nv|| >= |Nw|. The code below evaluates this
|
|
// expression exactly.
|
|
u := math.Abs(p.X)
|
|
v := math.Abs(p.Y)
|
|
w := math.Abs(p.Z)
|
|
|
|
// If w is the smallest, the following line returns an exact result.
|
|
if math.Abs(u-v) != w {
|
|
return math.Abs(u-v) >= w
|
|
}
|
|
|
|
// Otherwise u - v = w exactly, or w is not the smallest value. In either
|
|
// case the following returns the correct result.
|
|
if u >= v {
|
|
return u-w >= v
|
|
}
|
|
return v-w >= u
|
|
}
|
|
|
|
// axis represents the possible results of exitAxis.
|
|
type axis int
|
|
|
|
const (
|
|
axisU axis = iota
|
|
axisV
|
|
)
|
|
|
|
// exitAxis reports which axis the directed line L exits the cube face F on.
|
|
// The directed line L is represented by its CCW normal N in the (u,v,w) coordinates
|
|
// of F. It returns axisU if L exits through the u=-1 or u=+1 edge, and axisV if L exits
|
|
// through the v=-1 or v=+1 edge. Either result is acceptable if L exits exactly
|
|
// through a corner vertex of the cube face.
|
|
func (p pointUVW) exitAxis() axis {
|
|
if p.intersectsOppositeEdges() {
|
|
// The line passes through through opposite edges of the face.
|
|
// It exits through the v=+1 or v=-1 edge if the u-component of N has a
|
|
// larger absolute magnitude than the v-component.
|
|
if math.Abs(p.X) >= math.Abs(p.Y) {
|
|
return axisV
|
|
}
|
|
return axisU
|
|
}
|
|
|
|
// The line passes through through two adjacent edges of the face.
|
|
// It exits the v=+1 or v=-1 edge if an even number of the components of N
|
|
// are negative. We test this using signbit() rather than multiplication
|
|
// to avoid the possibility of underflow.
|
|
var x, y, z int
|
|
if math.Signbit(p.X) {
|
|
x = 1
|
|
}
|
|
if math.Signbit(p.Y) {
|
|
y = 1
|
|
}
|
|
if math.Signbit(p.Z) {
|
|
z = 1
|
|
}
|
|
|
|
if x^y^z == 0 {
|
|
return axisV
|
|
}
|
|
return axisU
|
|
}
|
|
|
|
// exitPoint returns the UV coordinates of the point where a directed line L (represented
|
|
// by the CCW normal of this point), exits the cube face this point is derived from along
|
|
// the given axis.
|
|
func (p pointUVW) exitPoint(a axis) r2.Point {
|
|
if a == axisU {
|
|
u := -1.0
|
|
if p.Y > 0 {
|
|
u = 1.0
|
|
}
|
|
return r2.Point{u, (-u*p.X - p.Z) / p.Y}
|
|
}
|
|
|
|
v := -1.0
|
|
if p.X < 0 {
|
|
v = 1.0
|
|
}
|
|
return r2.Point{(-v*p.Y - p.Z) / p.X, v}
|
|
}
|
|
|
|
// clipDestination returns a score which is used to indicate if the clipped edge AB
|
|
// on the given face intersects the face at all. This function returns the score for
|
|
// the given endpoint, which is an integer ranging from 0 to 3. If the sum of the scores
|
|
// from both of the endpoints is 3 or more, then edge AB does not intersect this face.
|
|
//
|
|
// First, it clips the line segment AB to find the clipped destination B' on a given
|
|
// face. (The face is specified implicitly by expressing *all arguments* in the (u,v,w)
|
|
// coordinates of that face.) Second, it partially computes whether the segment AB
|
|
// intersects this face at all. The actual condition is fairly complicated, but it
|
|
// turns out that it can be expressed as a "score" that can be computed independently
|
|
// when clipping the two endpoints A and B.
|
|
func clipDestination(a, b, scaledN, aTan, bTan pointUVW, scaleUV float64) (r2.Point, int) {
|
|
var uv r2.Point
|
|
|
|
// Optimization: if B is within the safe region of the face, use it.
|
|
maxSafeUVCoord := 1 - faceClipErrorUVCoord
|
|
if b.Z > 0 {
|
|
uv = r2.Point{b.X / b.Z, b.Y / b.Z}
|
|
if math.Max(math.Abs(uv.X), math.Abs(uv.Y)) <= maxSafeUVCoord {
|
|
return uv, 0
|
|
}
|
|
}
|
|
|
|
// Otherwise find the point B' where the line AB exits the face.
|
|
uv = scaledN.exitPoint(scaledN.exitAxis()).Mul(scaleUV)
|
|
|
|
p := pointUVW(Point{r3.Vector{uv.X, uv.Y, 1.0}})
|
|
|
|
// Determine if the exit point B' is contained within the segment. We do this
|
|
// by computing the dot products with two inward-facing tangent vectors at A
|
|
// and B. If either dot product is negative, we say that B' is on the "wrong
|
|
// side" of that point. As the point B' moves around the great circle AB past
|
|
// the segment endpoint B, it is initially on the wrong side of B only; as it
|
|
// moves further it is on the wrong side of both endpoints; and then it is on
|
|
// the wrong side of A only. If the exit point B' is on the wrong side of
|
|
// either endpoint, we can't use it; instead the segment is clipped at the
|
|
// original endpoint B.
|
|
//
|
|
// We reject the segment if the sum of the scores of the two endpoints is 3
|
|
// or more. Here is what that rule encodes:
|
|
// - If B' is on the wrong side of A, then the other clipped endpoint A'
|
|
// must be in the interior of AB (otherwise AB' would go the wrong way
|
|
// around the circle). There is a similar rule for A'.
|
|
// - If B' is on the wrong side of either endpoint (and therefore we must
|
|
// use the original endpoint B instead), then it must be possible to
|
|
// project B onto this face (i.e., its w-coordinate must be positive).
|
|
// This rule is only necessary to handle certain zero-length edges (A=B).
|
|
score := 0
|
|
if p.Sub(a.Vector).Dot(aTan.Vector) < 0 {
|
|
score = 2 // B' is on wrong side of A.
|
|
} else if p.Sub(b.Vector).Dot(bTan.Vector) < 0 {
|
|
score = 1 // B' is on wrong side of B.
|
|
}
|
|
|
|
if score > 0 { // B' is not in the interior of AB.
|
|
if b.Z <= 0 {
|
|
score = 3 // B cannot be projected onto this face.
|
|
} else {
|
|
uv = r2.Point{b.X / b.Z, b.Y / b.Z}
|
|
}
|
|
}
|
|
|
|
return uv, score
|
|
}
|
|
|
|
// ClipToFace returns the (u,v) coordinates for the portion of the edge AB that
|
|
// intersects the given face, or false if the edge AB does not intersect.
|
|
// This method guarantees that the clipped vertices lie within the [-1,1]x[-1,1]
|
|
// cube face rectangle and are within faceClipErrorUVDist of the line AB, but
|
|
// the results may differ from those produced by faceSegments.
|
|
func ClipToFace(a, b Point, face int) (aUV, bUV r2.Point, intersects bool) {
|
|
return ClipToPaddedFace(a, b, face, 0.0)
|
|
}
|
|
|
|
// ClipToPaddedFace returns the (u,v) coordinates for the portion of the edge AB that
|
|
// intersects the given face, but rather than clipping to the square [-1,1]x[-1,1]
|
|
// in (u,v) space, this method clips to [-R,R]x[-R,R] where R=(1+padding).
|
|
// Padding must be non-negative.
|
|
func ClipToPaddedFace(a, b Point, f int, padding float64) (aUV, bUV r2.Point, intersects bool) {
|
|
// Fast path: both endpoints are on the given face.
|
|
if face(a.Vector) == f && face(b.Vector) == f {
|
|
au, av := validFaceXYZToUV(f, a.Vector)
|
|
bu, bv := validFaceXYZToUV(f, b.Vector)
|
|
return r2.Point{au, av}, r2.Point{bu, bv}, true
|
|
}
|
|
|
|
// Convert everything into the (u,v,w) coordinates of the given face. Note
|
|
// that the cross product *must* be computed in the original (x,y,z)
|
|
// coordinate system because PointCross (unlike the mathematical cross
|
|
// product) can produce different results in different coordinate systems
|
|
// when one argument is a linear multiple of the other, due to the use of
|
|
// symbolic perturbations.
|
|
normUVW := pointUVW(faceXYZtoUVW(f, a.PointCross(b)))
|
|
aUVW := pointUVW(faceXYZtoUVW(f, a))
|
|
bUVW := pointUVW(faceXYZtoUVW(f, b))
|
|
|
|
// Padding is handled by scaling the u- and v-components of the normal.
|
|
// Letting R=1+padding, this means that when we compute the dot product of
|
|
// the normal with a cube face vertex (such as (-1,-1,1)), we will actually
|
|
// compute the dot product with the scaled vertex (-R,-R,1). This allows
|
|
// methods such as intersectsFace, exitAxis, etc, to handle padding
|
|
// with no further modifications.
|
|
scaleUV := 1 + padding
|
|
scaledN := pointUVW{r3.Vector{X: scaleUV * normUVW.X, Y: scaleUV * normUVW.Y, Z: normUVW.Z}}
|
|
if !scaledN.intersectsFace() {
|
|
return aUV, bUV, false
|
|
}
|
|
|
|
// TODO(roberts): This is a workaround for extremely small vectors where some
|
|
// loss of precision can occur in Normalize causing underflow. When PointCross
|
|
// is updated to work around this, this can be removed.
|
|
if math.Max(math.Abs(normUVW.X), math.Max(math.Abs(normUVW.Y), math.Abs(normUVW.Z))) < math.Ldexp(1, -511) {
|
|
normUVW = pointUVW{normUVW.Mul(math.Ldexp(1, 563))}
|
|
}
|
|
|
|
normUVW = pointUVW{normUVW.Normalize()}
|
|
|
|
aTan := pointUVW{normUVW.Cross(aUVW.Vector)}
|
|
bTan := pointUVW{bUVW.Cross(normUVW.Vector)}
|
|
|
|
// As described in clipDestination, if the sum of the scores from clipping the two
|
|
// endpoints is 3 or more, then the segment does not intersect this face.
|
|
aUV, aScore := clipDestination(bUVW, aUVW, pointUVW{scaledN.Mul(-1)}, bTan, aTan, scaleUV)
|
|
bUV, bScore := clipDestination(aUVW, bUVW, scaledN, aTan, bTan, scaleUV)
|
|
|
|
return aUV, bUV, aScore+bScore < 3
|
|
}
|
|
|
|
// interpolateDouble returns a value with the same combination of a1 and b1 as the
|
|
// given value x is of a and b. This function makes the following guarantees:
|
|
// - If x == a, then x1 = a1 (exactly).
|
|
// - If x == b, then x1 = b1 (exactly).
|
|
// - If a <= x <= b, then a1 <= x1 <= b1 (even if a1 == b1).
|
|
// This requires a != b.
|
|
func interpolateDouble(x, a, b, a1, b1 float64) float64 {
|
|
// To get results that are accurate near both A and B, we interpolate
|
|
// starting from the closer of the two points.
|
|
if math.Abs(a-x) <= math.Abs(b-x) {
|
|
return a1 + (b1-a1)*(x-a)/(b-a)
|
|
}
|
|
return b1 + (a1-b1)*(x-b)/(a-b)
|
|
}
|
|
|
|
// updateEndpoint returns the interval with the specified endpoint updated to
|
|
// the given value. If the value lies beyond the opposite endpoint, nothing is
|
|
// changed and false is returned.
|
|
func updateEndpoint(bound r1.Interval, highEndpoint bool, value float64) (r1.Interval, bool) {
|
|
if !highEndpoint {
|
|
if bound.Hi < value {
|
|
return bound, false
|
|
}
|
|
if bound.Lo < value {
|
|
bound.Lo = value
|
|
}
|
|
return bound, true
|
|
}
|
|
|
|
if bound.Lo > value {
|
|
return bound, false
|
|
}
|
|
if bound.Hi > value {
|
|
bound.Hi = value
|
|
}
|
|
return bound, true
|
|
}
|
|
|
|
// clipBoundAxis returns the clipped versions of the bounding intervals for the given
|
|
// axes for the line segment from (a0,a1) to (b0,b1) so that neither extends beyond the
|
|
// given clip interval. negSlope is a precomputed helper variable that indicates which
|
|
// diagonal of the bounding box is spanned by AB; it is false if AB has positive slope,
|
|
// and true if AB has negative slope. If the clipping interval doesn't overlap the bounds,
|
|
// false is returned.
|
|
func clipBoundAxis(a0, b0 float64, bound0 r1.Interval, a1, b1 float64, bound1 r1.Interval,
|
|
negSlope bool, clip r1.Interval) (bound0c, bound1c r1.Interval, updated bool) {
|
|
|
|
if bound0.Lo < clip.Lo {
|
|
// If the upper bound is below the clips lower bound, there is nothing to do.
|
|
if bound0.Hi < clip.Lo {
|
|
return bound0, bound1, false
|
|
}
|
|
// narrow the intervals lower bound to the clip bound.
|
|
bound0.Lo = clip.Lo
|
|
if bound1, updated = updateEndpoint(bound1, negSlope, interpolateDouble(clip.Lo, a0, b0, a1, b1)); !updated {
|
|
return bound0, bound1, false
|
|
}
|
|
}
|
|
|
|
if bound0.Hi > clip.Hi {
|
|
// If the lower bound is above the clips upper bound, there is nothing to do.
|
|
if bound0.Lo > clip.Hi {
|
|
return bound0, bound1, false
|
|
}
|
|
// narrow the intervals upper bound to the clip bound.
|
|
bound0.Hi = clip.Hi
|
|
if bound1, updated = updateEndpoint(bound1, !negSlope, interpolateDouble(clip.Hi, a0, b0, a1, b1)); !updated {
|
|
return bound0, bound1, false
|
|
}
|
|
}
|
|
return bound0, bound1, true
|
|
}
|
|
|
|
// edgeIntersectsRect reports whether the edge defined by AB intersects the
|
|
// given closed rectangle to within the error bound.
|
|
func edgeIntersectsRect(a, b r2.Point, r r2.Rect) bool {
|
|
// First check whether the bounds of a Rect around AB intersects the given rect.
|
|
if !r.Intersects(r2.RectFromPoints(a, b)) {
|
|
return false
|
|
}
|
|
|
|
// Otherwise AB intersects the rect if and only if all four vertices of rect
|
|
// do not lie on the same side of the extended line AB. We test this by finding
|
|
// the two vertices of rect with minimum and maximum projections onto the normal
|
|
// of AB, and computing their dot products with the edge normal.
|
|
n := b.Sub(a).Ortho()
|
|
|
|
i := 0
|
|
if n.X >= 0 {
|
|
i = 1
|
|
}
|
|
j := 0
|
|
if n.Y >= 0 {
|
|
j = 1
|
|
}
|
|
|
|
max := n.Dot(r.VertexIJ(i, j).Sub(a))
|
|
min := n.Dot(r.VertexIJ(1-i, 1-j).Sub(a))
|
|
|
|
return (max >= 0) && (min <= 0)
|
|
}
|
|
|
|
// clippedEdgeBound returns the bounding rectangle of the portion of the edge defined
|
|
// by AB intersected by clip. The resulting bound may be empty. This is a convenience
|
|
// function built on top of clipEdgeBound.
|
|
func clippedEdgeBound(a, b r2.Point, clip r2.Rect) r2.Rect {
|
|
bound := r2.RectFromPoints(a, b)
|
|
if b1, intersects := clipEdgeBound(a, b, clip, bound); intersects {
|
|
return b1
|
|
}
|
|
return r2.EmptyRect()
|
|
}
|
|
|
|
// clipEdgeBound clips an edge AB to sequence of rectangles efficiently.
|
|
// It represents the clipped edges by their bounding boxes rather than as a pair of
|
|
// endpoints. Specifically, let A'B' be some portion of an edge AB, and let bound be
|
|
// a tight bound of A'B'. This function returns the bound that is a tight bound
|
|
// of A'B' intersected with a given rectangle. If A'B' does not intersect clip,
|
|
// it returns false and the original bound.
|
|
func clipEdgeBound(a, b r2.Point, clip, bound r2.Rect) (r2.Rect, bool) {
|
|
// negSlope indicates which diagonal of the bounding box is spanned by AB: it
|
|
// is false if AB has positive slope, and true if AB has negative slope. This is
|
|
// used to determine which interval endpoints need to be updated each time
|
|
// the edge is clipped.
|
|
negSlope := (a.X > b.X) != (a.Y > b.Y)
|
|
|
|
b0x, b0y, up1 := clipBoundAxis(a.X, b.X, bound.X, a.Y, b.Y, bound.Y, negSlope, clip.X)
|
|
if !up1 {
|
|
return bound, false
|
|
}
|
|
b1y, b1x, up2 := clipBoundAxis(a.Y, b.Y, b0y, a.X, b.X, b0x, negSlope, clip.Y)
|
|
if !up2 {
|
|
return r2.Rect{b0x, b0y}, false
|
|
}
|
|
return r2.Rect{X: b1x, Y: b1y}, true
|
|
}
|
|
|
|
// ClipEdge returns the portion of the edge defined by AB that is contained by the
|
|
// given rectangle. If there is no intersection, false is returned and aClip and bClip
|
|
// are undefined.
|
|
func ClipEdge(a, b r2.Point, clip r2.Rect) (aClip, bClip r2.Point, intersects bool) {
|
|
// Compute the bounding rectangle of AB, clip it, and then extract the new
|
|
// endpoints from the clipped bound.
|
|
bound := r2.RectFromPoints(a, b)
|
|
if bound, intersects = clipEdgeBound(a, b, clip, bound); !intersects {
|
|
return aClip, bClip, false
|
|
}
|
|
ai := 0
|
|
if a.X > b.X {
|
|
ai = 1
|
|
}
|
|
aj := 0
|
|
if a.Y > b.Y {
|
|
aj = 1
|
|
}
|
|
|
|
return bound.VertexIJ(ai, aj), bound.VertexIJ(1-ai, 1-aj), true
|
|
}
|
|
|
|
// ClosestPoint returns the point along the edge AB that is closest to the point X.
|
|
// The fractional distance of this point along the edge AB can be obtained
|
|
// using DistanceFraction.
|
|
//
|
|
// This requires that all points are unit length.
|
|
func ClosestPoint(x, a, b Point) Point {
|
|
aXb := a.PointCross(b)
|
|
// Find the closest point to X along the great circle through AB.
|
|
p := x.Sub(aXb.Mul(x.Dot(aXb.Vector) / aXb.Vector.Norm2()))
|
|
|
|
// If this point is on the edge AB, then it's the closest point.
|
|
if Sign(aXb, a, Point{p}) && Sign(Point{p}, b, aXb) {
|
|
return Point{p.Normalize()}
|
|
}
|
|
|
|
// Otherwise, the closest point is either A or B.
|
|
if x.Sub(a.Vector).Norm2() <= x.Sub(b.Vector).Norm2() {
|
|
return a
|
|
}
|
|
return b
|
|
}
|
|
|
|
// minUpdateDistanceMaxError returns the maximum error in the result of
|
|
// MaybeUpdateMinDistance (and the associated functions such as
|
|
// MaybeUpdateMinInteriorDistance, IsDistanceLess, etc), assuming that all
|
|
// input points are normalized to within the bounds guaranteed by r3.Vector's
|
|
// Normalize. The error can be added or subtracted from an s1.ChordAngle
|
|
// using its Expanded method.
|
|
func minUpdateDistanceMaxError(dist s1.ChordAngle) float64 {
|
|
// There are two cases for the maximum error in MaybeUpdateMinDistance(),
|
|
// depending on whether the closest point is interior to the edge.
|
|
return math.Max(minUpdateInteriorDistanceMaxError(dist), dist.MaxPointError())
|
|
}
|
|
|
|
// minUpdateInteriorDistanceMaxError returns the maximum error in the result of
|
|
// MaybeUpdateMinInteriorDistance, assuming that all input points are normalized
|
|
// to within the bounds guaranteed by Point's Normalize. The error can be added
|
|
// or subtracted from an s1.ChordAngle using its Expanded method.
|
|
func minUpdateInteriorDistanceMaxError(dist s1.ChordAngle) float64 {
|
|
// This bound includes all source of error, assuming that the input points
|
|
// are normalized. a and b are components of chord length that are
|
|
// perpendicular and parallel to a plane containing the edge respectively.
|
|
b := 0.5 * float64(dist) * float64(dist)
|
|
a := float64(dist) * math.Sqrt(1-0.5*b)
|
|
return ((2.5+2*math.Sqrt(3)+8.5*a)*a +
|
|
(2+2*math.Sqrt(3)/3+6.5*(1-b))*b +
|
|
(23+16/math.Sqrt(3))*dblEpsilon) * dblEpsilon
|
|
}
|
|
|
|
// DistanceFromSegment returns the distance of point X from line segment AB.
|
|
// The points are expected to be normalized.
|
|
func DistanceFromSegment(x, a, b Point) s1.Angle {
|
|
var c s1.ChordAngle
|
|
d, _ := updateMinDistance(x, a, b, c, true)
|
|
return d.Angle()
|
|
}
|
|
|
|
// IsDistanceLess reports whether the distance from X to the edge AB is less
|
|
// than limit. This method is faster than DistanceFromSegment(). If you want to
|
|
// compare against a fixed s1.Angle, you should convert it to an s1.ChordAngle
|
|
// once and save the value, since this conversion is relatively expensive.
|
|
func IsDistanceLess(x, a, b Point, limit s1.ChordAngle) bool {
|
|
_, less := MaybeUpdateMinDistance(x, a, b, limit)
|
|
return less
|
|
}
|
|
|
|
// MaybeUpdateMinDistance checks if the distance from X to the edge AB is less
|
|
// then minDist, and if returns the possibly updated value and whether it was updated.
|
|
// The case A == B is handled correctly.
|
|
//
|
|
// Use this method when you want to compute many distances and keep track of
|
|
// the minimum. It is significantly faster than using DistanceFromSegment
|
|
// because (1) using s1.ChordAngle is much faster than s1.Angle, and (2) it
|
|
// can save a lot of work by not actually computing the distance when it is
|
|
// obviously larger than the current minimum.
|
|
func MaybeUpdateMinDistance(x, a, b Point, minDist s1.ChordAngle) (s1.ChordAngle, bool) {
|
|
return updateMinDistance(x, a, b, minDist, false)
|
|
}
|
|
|
|
// IsInteriorDistanceLess reports whether the minimum distance from X to the
|
|
// edge AB is attained at an interior point of AB (i.e., not an endpoint), and
|
|
// that distance is less than limit.
|
|
func IsInteriorDistanceLess(x, a, b Point, limit s1.ChordAngle) bool {
|
|
_, less := MaybeUpdateMinInteriorDistance(x, a, b, limit)
|
|
return less
|
|
}
|
|
|
|
// MaybeUpdateMinInteriorDistance reports whether the minimum distance from X to AB
|
|
// is attained at an interior point of AB (i.e., not an endpoint), and that distance
|
|
// is less than minDist. If so, the value of minDist is updated and returns true.
|
|
// Otherwise it is unchanged and returns false.
|
|
func MaybeUpdateMinInteriorDistance(x, a, b Point, minDist s1.ChordAngle) (s1.ChordAngle, bool) {
|
|
return interiorDist(x, a, b, minDist, false)
|
|
}
|
|
|
|
// updateMinDistance computes the distance from a point X to a line segment AB,
|
|
// and if either the distance was less than the given minDist, or alwaysUpdate is
|
|
// true, the value and whether it was updated are returned.
|
|
func updateMinDistance(x, a, b Point, minDist s1.ChordAngle, alwaysUpdate bool) (s1.ChordAngle, bool) {
|
|
if d, ok := interiorDist(x, a, b, minDist, alwaysUpdate); ok {
|
|
// Minimum distance is attained along the edge interior.
|
|
return d, true
|
|
}
|
|
|
|
// Otherwise the minimum distance is to one of the endpoints.
|
|
xa2, xb2 := (x.Sub(a.Vector)).Norm2(), x.Sub(b.Vector).Norm2()
|
|
dist := s1.ChordAngle(math.Min(xa2, xb2))
|
|
if !alwaysUpdate && dist >= minDist {
|
|
return minDist, false
|
|
}
|
|
return dist, true
|
|
}
|
|
|
|
// interiorDist returns the shortest distance from point x to edge ab, assuming
|
|
// that the closest point to X is interior to AB. If the closest point is not
|
|
// interior to AB, interiorDist returns (minDist, false). If alwaysUpdate is set to
|
|
// false, the distance is only updated when the value exceeds certain the given minDist.
|
|
func interiorDist(x, a, b Point, minDist s1.ChordAngle, alwaysUpdate bool) (s1.ChordAngle, bool) {
|
|
// Chord distance of x to both end points a and b.
|
|
xa2, xb2 := (x.Sub(a.Vector)).Norm2(), x.Sub(b.Vector).Norm2()
|
|
|
|
// The closest point on AB could either be one of the two vertices (the
|
|
// vertex case) or in the interior (the interior case). Let C = A x B.
|
|
// If X is in the spherical wedge extending from A to B around the axis
|
|
// through C, then we are in the interior case. Otherwise we are in the
|
|
// vertex case.
|
|
//
|
|
// Check whether we might be in the interior case. For this to be true, XAB
|
|
// and XBA must both be acute angles. Checking this condition exactly is
|
|
// expensive, so instead we consider the planar triangle ABX (which passes
|
|
// through the sphere's interior). The planar angles XAB and XBA are always
|
|
// less than the corresponding spherical angles, so if we are in the
|
|
// interior case then both of these angles must be acute.
|
|
//
|
|
// We check this by computing the squared edge lengths of the planar
|
|
// triangle ABX, and testing acuteness using the law of cosines:
|
|
//
|
|
// max(XA^2, XB^2) < min(XA^2, XB^2) + AB^2
|
|
if math.Max(xa2, xb2) >= math.Min(xa2, xb2)+(a.Sub(b.Vector)).Norm2() {
|
|
return minDist, false
|
|
}
|
|
|
|
// The minimum distance might be to a point on the edge interior. Let R
|
|
// be closest point to X that lies on the great circle through AB. Rather
|
|
// than computing the geodesic distance along the surface of the sphere,
|
|
// instead we compute the "chord length" through the sphere's interior.
|
|
//
|
|
// The squared chord length XR^2 can be expressed as XQ^2 + QR^2, where Q
|
|
// is the point X projected onto the plane through the great circle AB.
|
|
// The distance XQ^2 can be written as (X.C)^2 / |C|^2 where C = A x B.
|
|
// We ignore the QR^2 term and instead use XQ^2 as a lower bound, since it
|
|
// is faster and the corresponding distance on the Earth's surface is
|
|
// accurate to within 1% for distances up to about 1800km.
|
|
c := a.PointCross(b)
|
|
c2 := c.Norm2()
|
|
xDotC := x.Dot(c.Vector)
|
|
xDotC2 := xDotC * xDotC
|
|
if !alwaysUpdate && xDotC2 >= c2*float64(minDist) {
|
|
// The closest point on the great circle AB is too far away.
|
|
return minDist, false
|
|
}
|
|
|
|
// Otherwise we do the exact, more expensive test for the interior case.
|
|
// This test is very likely to succeed because of the conservative planar
|
|
// test we did initially.
|
|
cx := c.Cross(x.Vector)
|
|
if a.Dot(cx) >= 0 || b.Dot(cx) <= 0 {
|
|
return minDist, false
|
|
}
|
|
|
|
// Compute the squared chord length XR^2 = XQ^2 + QR^2 (see above).
|
|
// This calculation has good accuracy for all chord lengths since it
|
|
// is based on both the dot product and cross product (rather than
|
|
// deriving one from the other). However, note that the chord length
|
|
// representation itself loses accuracy as the angle approaches π.
|
|
qr := 1 - math.Sqrt(cx.Norm2()/c2)
|
|
dist := s1.ChordAngle((xDotC2 / c2) + (qr * qr))
|
|
|
|
if !alwaysUpdate && dist >= minDist {
|
|
return minDist, false
|
|
}
|
|
|
|
return dist, true
|
|
}
|
|
|
|
// WedgeRel enumerates the possible relation between two wedges A and B.
|
|
type WedgeRel int
|
|
|
|
// Define the different possible relationships between two wedges.
|
|
const (
|
|
WedgeEquals WedgeRel = iota // A and B are equal.
|
|
WedgeProperlyContains // A is a strict superset of B.
|
|
WedgeIsProperlyContained // A is a strict subset of B.
|
|
WedgeProperlyOverlaps // A-B, B-A, and A intersect B are non-empty.
|
|
WedgeIsDisjoint // A and B are disjoint.
|
|
)
|
|
|
|
// WedgeRelation reports the relation between two non-empty wedges
|
|
// A=(a0, ab1, a2) and B=(b0, ab1, b2).
|
|
func WedgeRelation(a0, ab1, a2, b0, b2 Point) WedgeRel {
|
|
// There are 6 possible edge orderings at a shared vertex (all
|
|
// of these orderings are circular, i.e. abcd == bcda):
|
|
//
|
|
// (1) a2 b2 b0 a0: A contains B
|
|
// (2) a2 a0 b0 b2: B contains A
|
|
// (3) a2 a0 b2 b0: A and B are disjoint
|
|
// (4) a2 b0 a0 b2: A and B intersect in one wedge
|
|
// (5) a2 b2 a0 b0: A and B intersect in one wedge
|
|
// (6) a2 b0 b2 a0: A and B intersect in two wedges
|
|
//
|
|
// We do not distinguish between 4, 5, and 6.
|
|
// We pay extra attention when some of the edges overlap. When edges
|
|
// overlap, several of these orderings can be satisfied, and we take
|
|
// the most specific.
|
|
if a0 == b0 && a2 == b2 {
|
|
return WedgeEquals
|
|
}
|
|
|
|
// Cases 1, 2, 5, and 6
|
|
if OrderedCCW(a0, a2, b2, ab1) {
|
|
// The cases with this vertex ordering are 1, 5, and 6,
|
|
if OrderedCCW(b2, b0, a0, ab1) {
|
|
return WedgeProperlyContains
|
|
}
|
|
|
|
// We are in case 5 or 6, or case 2 if a2 == b2.
|
|
if a2 == b2 {
|
|
return WedgeIsProperlyContained
|
|
}
|
|
return WedgeProperlyOverlaps
|
|
|
|
}
|
|
// We are in case 2, 3, or 4.
|
|
if OrderedCCW(a0, b0, b2, ab1) {
|
|
return WedgeIsProperlyContained
|
|
}
|
|
|
|
if OrderedCCW(a0, b0, a2, ab1) {
|
|
return WedgeIsDisjoint
|
|
}
|
|
return WedgeProperlyOverlaps
|
|
}
|
|
|
|
// WedgeContains reports whether non-empty wedge A=(a0, ab1, a2) contains B=(b0, ab1, b2).
|
|
// Equivalent to WedgeRelation == WedgeProperlyContains || WedgeEquals.
|
|
func WedgeContains(a0, ab1, a2, b0, b2 Point) bool {
|
|
// For A to contain B (where each loop interior is defined to be its left
|
|
// side), the CCW edge order around ab1 must be a2 b2 b0 a0. We split
|
|
// this test into two parts that test three vertices each.
|
|
return OrderedCCW(a2, b2, b0, ab1) && OrderedCCW(b0, a0, a2, ab1)
|
|
}
|
|
|
|
// WedgeIntersects reports whether non-empty wedge A=(a0, ab1, a2) intersects B=(b0, ab1, b2).
|
|
// Equivalent to WedgeRelation == WedgeIsDisjoint
|
|
func WedgeIntersects(a0, ab1, a2, b0, b2 Point) bool {
|
|
// For A not to intersect B (where each loop interior is defined to be
|
|
// its left side), the CCW edge order around ab1 must be a0 b2 b0 a2.
|
|
// Note that it's important to write these conditions as negatives
|
|
// (!OrderedCCW(a,b,c,o) rather than Ordered(c,b,a,o)) to get correct
|
|
// results when two vertices are the same.
|
|
return !(OrderedCCW(a0, b2, b0, ab1) && OrderedCCW(b0, a2, a0, ab1))
|
|
}
|
|
|
|
// TODO(roberts): Differences from C++
|
|
// LongitudePruner
|
|
// FaceSegments
|
|
// PointFromExact
|
|
// IntersectionExact
|
|
// intersectionExactError
|