GoToSocial/vendor/github.com/golang/geo/s2/crossing_edge_query.go
Tobi Smethurst 98263a7de6
Grand test fixup (#138)
* start fixing up tests

* fix up tests + automate with drone

* fiddle with linting

* messing about with drone.yml

* some more fiddling

* hmmm

* add cache

* add vendor directory

* verbose

* ci updates

* update some little things

* update sig
2021-08-12 21:03:24 +02:00

410 lines
13 KiB
Go

// 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 (
"sort"
"github.com/golang/geo/r2"
)
// CrossingEdgeQuery is used to find the Edge IDs of Shapes that are crossed by
// a given edge(s).
//
// Note that if you need to query many edges, it is more efficient to declare
// a single CrossingEdgeQuery instance and reuse it.
//
// If you want to find *all* the pairs of crossing edges, it is more efficient to
// use the not yet implemented VisitCrossings in shapeutil.
type CrossingEdgeQuery struct {
index *ShapeIndex
// temporary values used while processing a query.
a, b r2.Point
iter *ShapeIndexIterator
// candidate cells generated when finding crossings.
cells []*ShapeIndexCell
}
// NewCrossingEdgeQuery creates a CrossingEdgeQuery for the given index.
func NewCrossingEdgeQuery(index *ShapeIndex) *CrossingEdgeQuery {
c := &CrossingEdgeQuery{
index: index,
iter: index.Iterator(),
}
return c
}
// Crossings returns the set of edge of the shape S that intersect the given edge AB.
// If the CrossingType is Interior, then only intersections at a point interior to both
// edges are reported, while if it is CrossingTypeAll then edges that share a vertex
// are also reported.
func (c *CrossingEdgeQuery) Crossings(a, b Point, shape Shape, crossType CrossingType) []int {
edges := c.candidates(a, b, shape)
if len(edges) == 0 {
return nil
}
crosser := NewEdgeCrosser(a, b)
out := 0
n := len(edges)
for in := 0; in < n; in++ {
b := shape.Edge(edges[in])
sign := crosser.CrossingSign(b.V0, b.V1)
if crossType == CrossingTypeAll && (sign == MaybeCross || sign == Cross) || crossType != CrossingTypeAll && sign == Cross {
edges[out] = edges[in]
out++
}
}
if out < n {
edges = edges[0:out]
}
return edges
}
// EdgeMap stores a sorted set of edge ids for each shape.
type EdgeMap map[Shape][]int
// CrossingsEdgeMap returns the set of all edges in the index that intersect the given
// edge AB. If crossType is CrossingTypeInterior, then only intersections at a
// point interior to both edges are reported, while if it is CrossingTypeAll
// then edges that share a vertex are also reported.
//
// The edges are returned as a mapping from shape to the edges of that shape
// that intersect AB. Every returned shape has at least one crossing edge.
func (c *CrossingEdgeQuery) CrossingsEdgeMap(a, b Point, crossType CrossingType) EdgeMap {
edgeMap := c.candidatesEdgeMap(a, b)
if len(edgeMap) == 0 {
return nil
}
crosser := NewEdgeCrosser(a, b)
for shape, edges := range edgeMap {
out := 0
n := len(edges)
for in := 0; in < n; in++ {
edge := shape.Edge(edges[in])
sign := crosser.CrossingSign(edge.V0, edge.V1)
if (crossType == CrossingTypeAll && (sign == MaybeCross || sign == Cross)) || (crossType != CrossingTypeAll && sign == Cross) {
edgeMap[shape][out] = edges[in]
out++
}
}
if out == 0 {
delete(edgeMap, shape)
} else {
if out < n {
edgeMap[shape] = edgeMap[shape][0:out]
}
}
}
return edgeMap
}
// candidates returns a superset of the edges of the given shape that intersect
// the edge AB.
func (c *CrossingEdgeQuery) candidates(a, b Point, shape Shape) []int {
var edges []int
// For small loops it is faster to use brute force. The threshold below was
// determined using benchmarks.
const maxBruteForceEdges = 27
maxEdges := shape.NumEdges()
if maxEdges <= maxBruteForceEdges {
edges = make([]int, maxEdges)
for i := 0; i < maxEdges; i++ {
edges[i] = i
}
return edges
}
// Compute the set of index cells intersected by the query edge.
c.getCellsForEdge(a, b)
if len(c.cells) == 0 {
return nil
}
// Gather all the edges that intersect those cells and sort them.
// TODO(roberts): Shapes don't track their ID, so we need to range over
// the index to find the ID manually.
var shapeID int32
for k, v := range c.index.shapes {
if v == shape {
shapeID = k
}
}
for _, cell := range c.cells {
if cell == nil {
continue
}
clipped := cell.findByShapeID(shapeID)
if clipped == nil {
continue
}
edges = append(edges, clipped.edges...)
}
if len(c.cells) > 1 {
edges = uniqueInts(edges)
}
return edges
}
// uniqueInts returns the sorted uniqued values from the given input.
func uniqueInts(in []int) []int {
var edges []int
m := make(map[int]bool)
for _, i := range in {
if m[i] {
continue
}
m[i] = true
edges = append(edges, i)
}
sort.Ints(edges)
return edges
}
// candidatesEdgeMap returns a map from shapes to the superse of edges for that
// shape that intersect the edge AB.
//
// CAVEAT: This method may return shapes that have an empty set of candidate edges.
// However the return value is non-empty only if at least one shape has a candidate edge.
func (c *CrossingEdgeQuery) candidatesEdgeMap(a, b Point) EdgeMap {
edgeMap := make(EdgeMap)
// If there are only a few edges then it's faster to use brute force. We
// only bother with this optimization when there is a single shape.
if len(c.index.shapes) == 1 {
// Typically this method is called many times, so it is worth checking
// whether the edge map is empty or already consists of a single entry for
// this shape, and skip clearing edge map in that case.
shape := c.index.Shape(0)
// Note that we leave the edge map non-empty even if there are no candidates
// (i.e., there is a single entry with an empty set of edges).
edgeMap[shape] = c.candidates(a, b, shape)
return edgeMap
}
// Compute the set of index cells intersected by the query edge.
c.getCellsForEdge(a, b)
if len(c.cells) == 0 {
return edgeMap
}
// Gather all the edges that intersect those cells and sort them.
for _, cell := range c.cells {
for _, clipped := range cell.shapes {
s := c.index.Shape(clipped.shapeID)
for j := 0; j < clipped.numEdges(); j++ {
edgeMap[s] = append(edgeMap[s], clipped.edges[j])
}
}
}
if len(c.cells) > 1 {
for s, edges := range edgeMap {
edgeMap[s] = uniqueInts(edges)
}
}
return edgeMap
}
// getCells returns the set of ShapeIndexCells that might contain edges intersecting
// the edge AB in the given cell root. This method is used primarily by loop and shapeutil.
func (c *CrossingEdgeQuery) getCells(a, b Point, root *PaddedCell) []*ShapeIndexCell {
aUV, bUV, ok := ClipToFace(a, b, root.id.Face())
if ok {
c.a = aUV
c.b = bUV
edgeBound := r2.RectFromPoints(c.a, c.b)
if root.Bound().Intersects(edgeBound) {
c.computeCellsIntersected(root, edgeBound)
}
}
if len(c.cells) == 0 {
return nil
}
return c.cells
}
// getCellsForEdge populates the cells field to the set of index cells intersected by an edge AB.
func (c *CrossingEdgeQuery) getCellsForEdge(a, b Point) {
c.cells = nil
segments := FaceSegments(a, b)
for _, segment := range segments {
c.a = segment.a
c.b = segment.b
// Optimization: rather than always starting the recursive subdivision at
// the top level face cell, instead we start at the smallest S2CellId that
// contains the edge (the edge root cell). This typically lets us skip
// quite a few levels of recursion since most edges are short.
edgeBound := r2.RectFromPoints(c.a, c.b)
pcell := PaddedCellFromCellID(CellIDFromFace(segment.face), 0)
edgeRoot := pcell.ShrinkToFit(edgeBound)
// Now we need to determine how the edge root cell is related to the cells
// in the spatial index (cellMap). There are three cases:
//
// 1. edgeRoot is an index cell or is contained within an index cell.
// In this case we only need to look at the contents of that cell.
// 2. edgeRoot is subdivided into one or more index cells. In this case
// we recursively subdivide to find the cells intersected by AB.
// 3. edgeRoot does not intersect any index cells. In this case there
// is nothing to do.
relation := c.iter.LocateCellID(edgeRoot)
if relation == Indexed {
// edgeRoot is an index cell or is contained by an index cell (case 1).
c.cells = append(c.cells, c.iter.IndexCell())
} else if relation == Subdivided {
// edgeRoot is subdivided into one or more index cells (case 2). We
// find the cells intersected by AB using recursive subdivision.
if !edgeRoot.isFace() {
pcell = PaddedCellFromCellID(edgeRoot, 0)
}
c.computeCellsIntersected(pcell, edgeBound)
}
}
}
// computeCellsIntersected computes the index cells intersected by the current
// edge that are descendants of pcell and adds them to this queries set of cells.
func (c *CrossingEdgeQuery) computeCellsIntersected(pcell *PaddedCell, edgeBound r2.Rect) {
c.iter.seek(pcell.id.RangeMin())
if c.iter.Done() || c.iter.CellID() > pcell.id.RangeMax() {
// The index does not contain pcell or any of its descendants.
return
}
if c.iter.CellID() == pcell.id {
// The index contains this cell exactly.
c.cells = append(c.cells, c.iter.IndexCell())
return
}
// Otherwise, split the edge among the four children of pcell.
center := pcell.Middle().Lo()
if edgeBound.X.Hi < center.X {
// Edge is entirely contained in the two left children.
c.clipVAxis(edgeBound, center.Y, 0, pcell)
return
} else if edgeBound.X.Lo >= center.X {
// Edge is entirely contained in the two right children.
c.clipVAxis(edgeBound, center.Y, 1, pcell)
return
}
childBounds := c.splitUBound(edgeBound, center.X)
if edgeBound.Y.Hi < center.Y {
// Edge is entirely contained in the two lower children.
c.computeCellsIntersected(PaddedCellFromParentIJ(pcell, 0, 0), childBounds[0])
c.computeCellsIntersected(PaddedCellFromParentIJ(pcell, 1, 0), childBounds[1])
} else if edgeBound.Y.Lo >= center.Y {
// Edge is entirely contained in the two upper children.
c.computeCellsIntersected(PaddedCellFromParentIJ(pcell, 0, 1), childBounds[0])
c.computeCellsIntersected(PaddedCellFromParentIJ(pcell, 1, 1), childBounds[1])
} else {
// The edge bound spans all four children. The edge itself intersects
// at most three children (since no padding is being used).
c.clipVAxis(childBounds[0], center.Y, 0, pcell)
c.clipVAxis(childBounds[1], center.Y, 1, pcell)
}
}
// clipVAxis computes the intersected cells recursively for a given padded cell.
// Given either the left (i=0) or right (i=1) side of a padded cell pcell,
// determine whether the current edge intersects the lower child, upper child,
// or both children, and call c.computeCellsIntersected recursively on those children.
// The center is the v-coordinate at the center of pcell.
func (c *CrossingEdgeQuery) clipVAxis(edgeBound r2.Rect, center float64, i int, pcell *PaddedCell) {
if edgeBound.Y.Hi < center {
// Edge is entirely contained in the lower child.
c.computeCellsIntersected(PaddedCellFromParentIJ(pcell, i, 0), edgeBound)
} else if edgeBound.Y.Lo >= center {
// Edge is entirely contained in the upper child.
c.computeCellsIntersected(PaddedCellFromParentIJ(pcell, i, 1), edgeBound)
} else {
// The edge intersects both children.
childBounds := c.splitVBound(edgeBound, center)
c.computeCellsIntersected(PaddedCellFromParentIJ(pcell, i, 0), childBounds[0])
c.computeCellsIntersected(PaddedCellFromParentIJ(pcell, i, 1), childBounds[1])
}
}
// splitUBound returns the bound for two children as a result of spliting the
// current edge at the given value U.
func (c *CrossingEdgeQuery) splitUBound(edgeBound r2.Rect, u float64) [2]r2.Rect {
v := edgeBound.Y.ClampPoint(interpolateFloat64(u, c.a.X, c.b.X, c.a.Y, c.b.Y))
// diag indicates which diagonal of the bounding box is spanned by AB:
// it is 0 if AB has positive slope, and 1 if AB has negative slope.
var diag int
if (c.a.X > c.b.X) != (c.a.Y > c.b.Y) {
diag = 1
}
return splitBound(edgeBound, 0, diag, u, v)
}
// splitVBound returns the bound for two children as a result of spliting the
// current edge into two child edges at the given value V.
func (c *CrossingEdgeQuery) splitVBound(edgeBound r2.Rect, v float64) [2]r2.Rect {
u := edgeBound.X.ClampPoint(interpolateFloat64(v, c.a.Y, c.b.Y, c.a.X, c.b.X))
var diag int
if (c.a.X > c.b.X) != (c.a.Y > c.b.Y) {
diag = 1
}
return splitBound(edgeBound, diag, 0, u, v)
}
// splitBound returns the bounds for the two childrenn as a result of spliting
// the current edge into two child edges at the given point (u,v). uEnd and vEnd
// indicate which bound endpoints of the first child will be updated.
func splitBound(edgeBound r2.Rect, uEnd, vEnd int, u, v float64) [2]r2.Rect {
var childBounds = [2]r2.Rect{
edgeBound,
edgeBound,
}
if uEnd == 1 {
childBounds[0].X.Lo = u
childBounds[1].X.Hi = u
} else {
childBounds[0].X.Hi = u
childBounds[1].X.Lo = u
}
if vEnd == 1 {
childBounds[0].Y.Lo = v
childBounds[1].Y.Hi = v
} else {
childBounds[0].Y.Hi = v
childBounds[1].Y.Lo = v
}
return childBounds
}