mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-11-30 15:42:46 +00:00
191 lines
6.2 KiB
Go
191 lines
6.2 KiB
Go
|
// Copyright 2018 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
|
||
|
|
||
|
// VertexModel defines whether shapes are considered to contain their vertices.
|
||
|
// Note that these definitions differ from the ones used by BooleanOperation.
|
||
|
//
|
||
|
// Note that points other than vertices are never contained by polylines.
|
||
|
// If you want need this behavior, use ClosestEdgeQuery's IsDistanceLess
|
||
|
// with a suitable distance threshold instead.
|
||
|
type VertexModel int
|
||
|
|
||
|
const (
|
||
|
// VertexModelOpen means no shapes contain their vertices (not even
|
||
|
// points). Therefore Contains(Point) returns true if and only if the
|
||
|
// point is in the interior of some polygon.
|
||
|
VertexModelOpen VertexModel = iota
|
||
|
|
||
|
// VertexModelSemiOpen means that polygon point containment is defined
|
||
|
// such that if several polygons tile the region around a vertex, then
|
||
|
// exactly one of those polygons contains that vertex. Points and
|
||
|
// polylines still do not contain any vertices.
|
||
|
VertexModelSemiOpen
|
||
|
|
||
|
// VertexModelClosed means all shapes contain their vertices (including
|
||
|
// points and polylines).
|
||
|
VertexModelClosed
|
||
|
)
|
||
|
|
||
|
// ContainsPointQuery determines whether one or more shapes in a ShapeIndex
|
||
|
// contain a given Point. The ShapeIndex may contain any number of points,
|
||
|
// polylines, and/or polygons (possibly overlapping). Shape boundaries may be
|
||
|
// modeled as Open, SemiOpen, or Closed (this affects whether or not shapes are
|
||
|
// considered to contain their vertices).
|
||
|
//
|
||
|
// This type is not safe for concurrent use.
|
||
|
//
|
||
|
// However, note that if you need to do a large number of point containment
|
||
|
// tests, it is more efficient to re-use the query rather than creating a new
|
||
|
// one each time.
|
||
|
type ContainsPointQuery struct {
|
||
|
model VertexModel
|
||
|
index *ShapeIndex
|
||
|
iter *ShapeIndexIterator
|
||
|
}
|
||
|
|
||
|
// NewContainsPointQuery creates a new instance of the ContainsPointQuery for the index
|
||
|
// and given vertex model choice.
|
||
|
func NewContainsPointQuery(index *ShapeIndex, model VertexModel) *ContainsPointQuery {
|
||
|
return &ContainsPointQuery{
|
||
|
index: index,
|
||
|
model: model,
|
||
|
iter: index.Iterator(),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Contains reports whether any shape in the queries index contains the point p
|
||
|
// under the queries vertex model (Open, SemiOpen, or Closed).
|
||
|
func (q *ContainsPointQuery) Contains(p Point) bool {
|
||
|
if !q.iter.LocatePoint(p) {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
cell := q.iter.IndexCell()
|
||
|
for _, clipped := range cell.shapes {
|
||
|
if q.shapeContains(clipped, q.iter.Center(), p) {
|
||
|
return true
|
||
|
}
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
// shapeContains reports whether the clippedShape from the iterator's center position contains
|
||
|
// the given point.
|
||
|
func (q *ContainsPointQuery) shapeContains(clipped *clippedShape, center, p Point) bool {
|
||
|
inside := clipped.containsCenter
|
||
|
numEdges := clipped.numEdges()
|
||
|
if numEdges <= 0 {
|
||
|
return inside
|
||
|
}
|
||
|
|
||
|
shape := q.index.Shape(clipped.shapeID)
|
||
|
if shape.Dimension() != 2 {
|
||
|
// Points and polylines can be ignored unless the vertex model is Closed.
|
||
|
if q.model != VertexModelClosed {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
// Otherwise, the point is contained if and only if it matches a vertex.
|
||
|
for _, edgeID := range clipped.edges {
|
||
|
edge := shape.Edge(edgeID)
|
||
|
if edge.V0 == p || edge.V1 == p {
|
||
|
return true
|
||
|
}
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
// Test containment by drawing a line segment from the cell center to the
|
||
|
// given point and counting edge crossings.
|
||
|
crosser := NewEdgeCrosser(center, p)
|
||
|
for _, edgeID := range clipped.edges {
|
||
|
edge := shape.Edge(edgeID)
|
||
|
sign := crosser.CrossingSign(edge.V0, edge.V1)
|
||
|
if sign == DoNotCross {
|
||
|
continue
|
||
|
}
|
||
|
if sign == MaybeCross {
|
||
|
// For the Open and Closed models, check whether p is a vertex.
|
||
|
if q.model != VertexModelSemiOpen && (edge.V0 == p || edge.V1 == p) {
|
||
|
return (q.model == VertexModelClosed)
|
||
|
}
|
||
|
// C++ plays fast and loose with the int <-> bool conversions here.
|
||
|
if VertexCrossing(crosser.a, crosser.b, edge.V0, edge.V1) {
|
||
|
sign = Cross
|
||
|
} else {
|
||
|
sign = DoNotCross
|
||
|
}
|
||
|
}
|
||
|
inside = inside != (sign == Cross)
|
||
|
}
|
||
|
|
||
|
return inside
|
||
|
}
|
||
|
|
||
|
// ShapeContains reports whether the given shape contains the point under this
|
||
|
// queries vertex model (Open, SemiOpen, or Closed).
|
||
|
//
|
||
|
// This requires the shape belongs to this queries index.
|
||
|
func (q *ContainsPointQuery) ShapeContains(shape Shape, p Point) bool {
|
||
|
if !q.iter.LocatePoint(p) {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
clipped := q.iter.IndexCell().findByShapeID(q.index.idForShape(shape))
|
||
|
if clipped == nil {
|
||
|
return false
|
||
|
}
|
||
|
return q.shapeContains(clipped, q.iter.Center(), p)
|
||
|
}
|
||
|
|
||
|
// shapeVisitorFunc is a type of function that can be called against shaped in an index.
|
||
|
type shapeVisitorFunc func(shape Shape) bool
|
||
|
|
||
|
// visitContainingShapes visits all shapes in the given index that contain the
|
||
|
// given point p, terminating early if the given visitor function returns false,
|
||
|
// in which case visitContainingShapes returns false. Each shape is
|
||
|
// visited at most once.
|
||
|
func (q *ContainsPointQuery) visitContainingShapes(p Point, f shapeVisitorFunc) bool {
|
||
|
// This function returns false only if the algorithm terminates early
|
||
|
// because the visitor function returned false.
|
||
|
if !q.iter.LocatePoint(p) {
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
cell := q.iter.IndexCell()
|
||
|
for _, clipped := range cell.shapes {
|
||
|
if q.shapeContains(clipped, q.iter.Center(), p) &&
|
||
|
!f(q.index.Shape(clipped.shapeID)) {
|
||
|
return false
|
||
|
}
|
||
|
}
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
// ContainingShapes returns a slice of all shapes that contain the given point.
|
||
|
func (q *ContainsPointQuery) ContainingShapes(p Point) []Shape {
|
||
|
var shapes []Shape
|
||
|
q.visitContainingShapes(p, func(shape Shape) bool {
|
||
|
shapes = append(shapes, shape)
|
||
|
return true
|
||
|
})
|
||
|
return shapes
|
||
|
}
|
||
|
|
||
|
// TODO(roberts): Remaining methods from C++
|
||
|
// type edgeVisitorFunc func(shape ShapeEdge) bool
|
||
|
// func (q *ContainsPointQuery) visitIncidentEdges(p Point, v edgeVisitorFunc) bool
|