// testClipToPaddedFace performs a comprehensive set of tests across all faces and // with random padding for the given points. // // We do this by defining an (x,y) coordinate system for the plane containing AB, // and converting points along the great circle AB to angles in the range // [-Pi, Pi]. We then accumulate the angle intervals spanned by each // clipped edge; the union over all 6 faces should approximately equal the // interval covered by the original edge. func testClipToPaddedFace(t *testing.T, a, b Point) { a = Point{a.Normalize()} b = Point{b.Normalize()} if a.Vector == b.Mul(-1) { return } norm := Point{a.PointCross(b).Normalize()} aTan := Point{norm.Cross(a.Vector)} padding := 0.0 if !oneIn(10) { padding = 1e-10 * math.Pow(1e-5, randomFloat64()) } xAxis := a yAxis := aTan // Given the points A and B, we expect all angles generated from the clipping // to fall within this range. expectedAngles := s1.Interval{0, float64(a.Angle(b.Vector))} if expectedAngles.IsInverted() { expectedAngles = s1.Interval{expectedAngles.Hi, expectedAngles.Lo} } maxAngles := expectedAngles.Expanded(faceClipErrorRadians) var actualAngles s1.Interval for face := 0; face < 6; face++ { aUV, bUV, intersects := ClipToPaddedFace(a, b, face, padding) if !intersects { continue } aClip := Point{faceUVToXYZ(face, aUV.X, aUV.Y).Normalize()} bClip := Point{faceUVToXYZ(face, bUV.X, bUV.Y).Normalize()} desc := fmt.Sprintf("on face %d, a=%v, b=%v, aClip=%v, bClip=%v,", face, a, b, aClip, bClip) if got := math.Abs(aClip.Dot(norm.Vector)); got > faceClipErrorRadians { t.Errorf("%s abs(%v.Dot(%v)) = %v, want <= %v", desc, aClip, norm, got, faceClipErrorRadians) } if got := math.Abs(bClip.Dot(norm.Vector)); got > faceClipErrorRadians { t.Errorf("%s abs(%v.Dot(%v)) = %v, want <= %v", desc, bClip, norm, got, faceClipErrorRadians) } if float64(aClip.Angle(a.Vector)) > faceClipErrorRadians { if got := math.Max(math.Abs(aUV.X), math.Abs(aUV.Y)); !float64Eq(got, 1+padding) { t.Errorf("%s the largest component of %v = %v, want %v", desc, aUV, got, 1+padding) } } if float64(bClip.Angle(b.Vector)) > faceClipErrorRadians { if got := math.Max(math.Abs(bUV.X), math.Abs(bUV.Y)); !float64Eq(got, 1+padding) { t.Errorf("%s the largest component of %v = %v, want %v", desc, bUV, got, 1+padding) } } aAngle := math.Atan2(aClip.Dot(yAxis.Vector), aClip.Dot(xAxis.Vector)) bAngle := math.Atan2(bClip.Dot(yAxis.Vector), bClip.Dot(xAxis.Vector)) // Rounding errors may cause bAngle to be slightly less than aAngle. // We handle this by constructing the interval with FromPointPair, // which is okay since the interval length is much less than math.Pi. faceAngles := s1.IntervalFromEndpoints(aAngle, bAngle) if faceAngles.IsInverted() { faceAngles = s1.Interval{faceAngles.Hi, faceAngles.Lo} } if !maxAngles.ContainsInterval(faceAngles) { t.Errorf("%s %v.ContainsInterval(%v) = false, but should have contained this interval", desc, maxAngles, faceAngles) } actualAngles = actualAngles.Union(faceAngles) } if !actualAngles.Expanded(faceClipErrorRadians).ContainsInterval(expectedAngles) { t.Errorf("the union of all angle segments should be larger than the expected angle") } }