// Copyright 2017 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" "reflect" "testing" "github.com/golang/geo/s1" ) func perturbAtDistance(distance s1.Angle, a0, b0 Point) Point { x := InterpolateAtDistance(distance, a0, b0) if oneIn(2) { if oneIn(2) { x.X = math.Nextafter(x.X, 1) } else { x.X = math.Nextafter(x.X, -1) } if oneIn(2) { x.Y = math.Nextafter(x.Y, 1) } else { x.Y = math.Nextafter(x.Y, -1) } if oneIn(2) { x.Z = math.Nextafter(x.Z, 1) } else { x.Z = math.Nextafter(x.Z, -1) } x = Point{x.Normalize()} } return x } // generatePerturbedSubEdges generate sub-edges of some given edge (a,b). // The length of the sub-edges is distributed exponentially over a large range, // and the endpoints may be slightly perturbed to one side of (a,b) or the other. func generatePerturbedSubEdges(a, b Point, count int) []Edge { var edges []Edge a = Point{a.Normalize()} b = Point{b.Normalize()} length0 := a.Distance(b) for i := 0; i < count; i++ { length := length0 * s1.Angle(math.Pow(1e-15, randomFloat64())) offset := (length0 - length) * s1.Angle(randomFloat64()) edges = append(edges, Edge{ perturbAtDistance(offset, a, b), perturbAtDistance(offset+length, a, b), }) } return edges } // generateCapEdges creates edges whose center is randomly chosen from the given cap, and // whose length is randomly chosen up to maxLength. func generateCapEdges(centerCap Cap, maxLength s1.Angle, count int) []Edge { var edges []Edge for i := 0; i < count; i++ { center := samplePointFromCap(centerCap) edgeCap := CapFromCenterAngle(center, 0.5*maxLength) p1 := samplePointFromCap(edgeCap) // Compute p1 reflected through center, and normalize for good measure. p2 := Point{(center.Mul(2 * p1.Dot(center.Vector)).Sub(p1.Vector)).Normalize()} edges = append(edges, Edge{p1, p2}) } return edges } func testCrossingEdgeQueryAllCrossings(t *testing.T, edges []Edge) { s := &edgeVectorShape{} for _, edge := range edges { s.Add(edge.V0, edge.V1) } // Force more subdivision than usual to make the test more challenging. index := NewShapeIndex() index.maxEdgesPerCell = 1 index.Add(s) // To check that candidates are being filtered reasonably, we count the // total number of candidates that the total number of edge pairs that // either intersect or are very close to intersecting. var numCandidates, numNearbyPairs int for _, edge := range edges { a := edge.V0 b := edge.V1 query := NewCrossingEdgeQuery(index) candidates := query.candidates(a, b, s) // Verify that the EdgeMap version of candidates returns the same result. edgeMap := query.candidatesEdgeMap(a, b) if len(edgeMap) != 1 { t.Errorf("there should be only one shape in this map, got %d", len(edgeMap)) // Skip the next part of the check since we expect only 1 // value, and you can't get just the first entry in a map. continue } for k, v := range edgeMap { if Shape(s) != k { t.Errorf("element(%v) of edgeMap should be this shape", k) } if !reflect.DeepEqual(candidates, v) { t.Errorf("edgeMap candidates for this shape = %v, want %v", v, candidates) } } if len(candidates) == 0 { t.Errorf("candidates should not be empty") } // Now check the actual candidates. // Check the results are sorted for k, v := range candidates { if k < len(candidates)-1 { if v > candidates[k+1] { t.Errorf("candidates was not sorted. candidates[%d] = %v > candidates[%d] = %v", k, v, k+1, candidates[k+1]) } } } if got := candidates[0]; got < 0 { t.Errorf("the first element should not have a negative edge id") } if got, want := candidates[len(candidates)-1], s.NumEdges(); got >= want { t.Errorf("candidates[%d] = %v, want < %v", len(candidates)-1, got, want) } numCandidates += len(candidates) var expectedCrossings, expectedInteriorCrossings []int var missingCandidates []int for i := 0; i < s.NumEdges(); i++ { edge := s.Edge(i) c := edge.V0 d := edge.V1 sign := CrossingSign(a, b, c, d) if sign != DoNotCross { expectedCrossings = append(expectedCrossings, i) if sign == Cross { expectedInteriorCrossings = append(expectedInteriorCrossings, i) } numNearbyPairs++ // try to find this edge number in the set of candidates. found := false for _, v := range candidates { if v == i { found = true } } // If we couldn't find it, add it to the missing set. if !found { missingCandidates = append(missingCandidates, i) } } else { maxDist := MaxDiagMetric.Value(MaxLevel) if DistanceFromSegment(a, c, d).Radians() < maxDist || DistanceFromSegment(b, c, d).Radians() < maxDist || DistanceFromSegment(c, a, b).Radians() < maxDist || DistanceFromSegment(d, a, b).Radians() < maxDist { numNearbyPairs++ } } } if len(missingCandidates) != 0 { t.Errorf("missingCandidates should have been empty") } // test that Crossings returns only the actual crossing edges. actualCrossings := query.Crossings(a, b, s, CrossingTypeAll) if !reflect.DeepEqual(expectedCrossings, actualCrossings) { t.Errorf("query.Crossings(%v, %v, ...) = %v, want %v", a, b, actualCrossings, expectedCrossings) } // Verify that the edge map version of Crossings returns the same result. if edgeMap := query.CrossingsEdgeMap(a, b, CrossingTypeAll); len(edgeMap) != 0 { if len(edgeMap) != 1 { t.Errorf("query.CrossingsEdgeMap(%v, %v) has %d entried, want 1", a, b, len(edgeMap)) } else { for k, v := range edgeMap { if s != k { t.Errorf("query.CrossingsEdgeMap(%v, %v, ...) shape = %v, want %v", a, b, k, s) } if !reflect.DeepEqual(expectedCrossings, v) { t.Errorf("query.CrossingsEdgeMap(%v, %v) = %v, want %v", a, b, v, expectedCrossings) } } } } // Verify that CrossingTypeInterior returns only the interior crossings. actualCrossings = query.Crossings(a, b, s, CrossingTypeInterior) // TODO(roberts): Move to package "cmp" when s2 can meet the minimum go version. // This will handle the case where one slice is nil and the other is non-nil // but empty as being equivalent more cleanly. if !reflect.DeepEqual(expectedInteriorCrossings, actualCrossings) && (len(actualCrossings) != 0 && len(expectedInteriorCrossings) != 0) { t.Errorf("query.Crossings(%v, %v, CrossingTypeInterior) = %v, want %v", a, b, actualCrossings, expectedInteriorCrossings) } } // There is nothing magical about this particular ratio; this check exists // to catch changes that dramatically increase the number of candidates. if numCandidates > 3*numNearbyPairs { t.Errorf("number of candidates should not be dramatically larger than number of nearby pairs. got %v, want < %v", numCandidates, 3*numNearbyPairs) } } func TestCrossingEdgeQueryCrossingCandidatesPerturbedCubeEdges(t *testing.T) { // Test edges that lie in the plane of one of the S2 cube edges. Such edges // may lie on the boundary between two cube faces, or pass through a cube // vertex, or follow a 45 diagonal across a cube face toward its center. // // This test is sufficient to demonstrate that padding the cell boundaries // is necessary for correctness. (It will fails if ShapeIndexes CellPadding is // set to zero.) for iter := 0; iter < 10; iter++ { face := randomUniformInt(6) scale := math.Pow(1e-15, randomFloat64()) u := scale*2*float64(randomUniformInt(2)) - 1 v := scale*2*float64(randomUniformInt(2)) - 1 a := Point{faceUVToXYZ(face, u, v)} b := Point{a.Sub(unitNorm(face).Mul(2))} // TODO(roberts): This test is currently slow because *every* crossing test // needs to invoke ExpensiveSign. edges := generatePerturbedSubEdges(a, b, 30) testCrossingEdgeQueryAllCrossings(t, edges) } } // Test edges that lie in the plane of one of the S2 cube face axes. These // edges are special because one coordinate is zero, and they lie on the // boundaries between the immediate child cells of the cube face. func TestCrossingEdgeQueryCandidatesPerturbedCubeFaceAxes(t *testing.T) { for iter := 0; iter < 5; iter++ { face := randomUniformInt(6) scale := math.Pow(1e-15, randomFloat64()) axis := uvwAxis(face, randomUniformInt(2)) a := Point{axis.Mul(scale).Add(unitNorm(face).Vector)} b := Point{axis.Mul(scale).Sub(unitNorm(face).Vector)} edges := generatePerturbedSubEdges(a, b, 30) testCrossingEdgeQueryAllCrossings(t, edges) } } func TestCrossingEdgeQueryCandidatesCapEdgesNearCubeVertex(t *testing.T) { // Test a random collection of edges near the S2 cube vertex where the // Hilbert curve starts and ends. edges := generateCapEdges(CapFromCenterAngle(PointFromCoords(-1, -1, 1), s1.Angle(1e-3)), s1.Angle(1e-4), 1000) testCrossingEdgeQueryAllCrossings(t, edges) } func TestCrossingEdgeQueryCandidatesDegenerateEdgeOnCellVertexIsItsOwnCandidate(t *testing.T) { for iter := 0; iter < 100; iter++ { cell := CellFromCellID(randomCellID()) edges := []Edge{{cell.Vertex(0), cell.Vertex(0)}} testCrossingEdgeQueryAllCrossings(t, edges) } } func TestCrossingEdgeQueryCandidatesCollinearEdgesOnCellBoundaries(t *testing.T) { const numEdgeIntervals = 8 // 9*8/2 = 36 edges for level := 0; level <= MaxLevel; level++ { var edges []Edge cell := CellFromCellID(randomCellIDForLevel(level)) i := randomUniformInt(4) p1 := cell.Vertex(i % 4) p2 := cell.Vertex((i + 1) % 4) delta := p2.Sub(p1.Vector).Mul(1 / numEdgeIntervals) for i := 0; i <= numEdgeIntervals; i++ { for j := 0; j < i; j++ { edges = append(edges, Edge{ Point{p1.Add(delta.Mul(float64(i))).Normalize()}, Point{p1.Add(delta.Mul(float64(j))).Normalize()}, }) } } testCrossingEdgeQueryAllCrossings(t, edges) } } func TestCrossingEdgeQueryCrossingsPolylineCrossings(t *testing.T) { index := NewShapeIndex() // Three zig-zag lines near the equator. index.Add(makePolyline("0:0, 2:1, 0:2, 2:3, 0:4, 2:5, 0:6")) index.Add(makePolyline("1:0, 3:1, 1:2, 3:3, 1:4, 3:5, 1:6")) index.Add(makePolyline("2:0, 4:1, 2:2, 4:3, 2:4, 4:5, 2:6")) index.Begin() var tests = []struct { a0, a1 Point }{ {parsePoint("1:0"), parsePoint("1:4")}, {parsePoint("5:5"), parsePoint("6:6")}, } for _, test := range tests { query := NewCrossingEdgeQuery(index) edgeMap := query.CrossingsEdgeMap(test.a0, test.a1, CrossingTypeAll) if len(edgeMap) == 0 { continue } for shape, edges := range edgeMap { polyline := shape.(*Polyline) if len(edges) == 0 { t.Errorf("shapes with no crossings should have been filtered out") } for _, edge := range edges { b0 := (*polyline)[edge] b1 := (*polyline)[edge+1] if got := CrossingSign(test.a0, test.a1, b0, b1); got == DoNotCross { t.Errorf("CrossingSign(%v, %v, %v, %v) = %v, want MaybeCross or Cross", test.a0, test.a1, b0, b1, got) } } } // Also test that no edges are missing. for _, shape := range index.shapes { polyline := shape.(*Polyline) edges := edgeMap[shape] for e := 0; e < len(*polyline)-1; e++ { if got := CrossingSign(test.a0, test.a1, (*polyline)[e], (*polyline)[e+1]); got != DoNotCross { // Need to count occurrences of the current edge to see // if and how many are in the edge map. count := 0 for _, edge := range edges { if edge == e { count++ } } if count != 1 { t.Errorf("edge %v should appear once in the set of edges, got %v", e, count) } } } } } } func TestUniqueInts(t *testing.T) { tests := []struct { have []int want []int }{ { have: nil, want: nil, }, { have: []int{}, want: nil, }, { have: []int{1}, want: []int{1}, }, { have: []int{3, 2, 1}, want: []int{1, 2, 3}, }, { have: []int{4, 4, 4}, want: []int{4}, }, { have: []int{1, 2, 3, 4, 2, 3, 5, 4, 6, 1, 2, 3, 4, 5, 7, 8, 1, 3, 1, 2, 3, 9, 3, 2, 1}, want: []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, }, } for _, test := range tests { if got := uniqueInts(test.have); !reflect.DeepEqual(got, test.want) { t.Errorf("uniqueInts(%v) = %v, want %v", test.have, got, test.want) } } }