diff options
Diffstat (limited to 'vendor/github.com/golang/geo/s2/shapeindex.go')
-rw-r--r-- | vendor/github.com/golang/geo/s2/shapeindex.go | 1526 |
1 files changed, 0 insertions, 1526 deletions
diff --git a/vendor/github.com/golang/geo/s2/shapeindex.go b/vendor/github.com/golang/geo/s2/shapeindex.go deleted file mode 100644 index 6efa213ab..000000000 --- a/vendor/github.com/golang/geo/s2/shapeindex.go +++ /dev/null @@ -1,1526 +0,0 @@ -// Copyright 2016 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" - "sort" - "sync" - "sync/atomic" - - "github.com/golang/geo/r1" - "github.com/golang/geo/r2" -) - -// CellRelation describes the possible relationships between a target cell -// and the cells of the ShapeIndex. If the target is an index cell or is -// contained by an index cell, it is Indexed. If the target is subdivided -// into one or more index cells, it is Subdivided. Otherwise it is Disjoint. -type CellRelation int - -// The possible CellRelations for a ShapeIndex. -const ( - Indexed CellRelation = iota - Subdivided - Disjoint -) - -const ( - // cellPadding defines the total error when clipping an edge which comes - // from two sources: - // (1) Clipping the original spherical edge to a cube face (the face edge). - // The maximum error in this step is faceClipErrorUVCoord. - // (2) Clipping the face edge to the u- or v-coordinate of a cell boundary. - // The maximum error in this step is edgeClipErrorUVCoord. - // Finally, since we encounter the same errors when clipping query edges, we - // double the total error so that we only need to pad edges during indexing - // and not at query time. - cellPadding = 2.0 * (faceClipErrorUVCoord + edgeClipErrorUVCoord) - - // cellSizeToLongEdgeRatio defines the cell size relative to the length of an - // edge at which it is first considered to be long. Long edges do not - // contribute toward the decision to subdivide a cell further. For example, - // a value of 2.0 means that the cell must be at least twice the size of the - // edge in order for that edge to be counted. There are two reasons for not - // counting long edges: (1) such edges typically need to be propagated to - // several children, which increases time and memory costs without much benefit, - // and (2) in pathological cases, many long edges close together could force - // subdivision to continue all the way to the leaf cell level. - cellSizeToLongEdgeRatio = 1.0 -) - -// clippedShape represents the part of a shape that intersects a Cell. -// It consists of the set of edge IDs that intersect that cell and a boolean -// indicating whether the center of the cell is inside the shape (for shapes -// that have an interior). -// -// Note that the edges themselves are not clipped; we always use the original -// edges for intersection tests so that the results will be the same as the -// original shape. -type clippedShape struct { - // shapeID is the index of the shape this clipped shape is a part of. - shapeID int32 - - // containsCenter indicates if the center of the CellID this shape has been - // clipped to falls inside this shape. This is false for shapes that do not - // have an interior. - containsCenter bool - - // edges is the ordered set of ShapeIndex original edge IDs. Edges - // are stored in increasing order of edge ID. - edges []int -} - -// newClippedShape returns a new clipped shape for the given shapeID and number of expected edges. -func newClippedShape(id int32, numEdges int) *clippedShape { - return &clippedShape{ - shapeID: id, - edges: make([]int, numEdges), - } -} - -// numEdges returns the number of edges that intersect the CellID of the Cell this was clipped to. -func (c *clippedShape) numEdges() int { - return len(c.edges) -} - -// containsEdge reports if this clipped shape contains the given edge ID. -func (c *clippedShape) containsEdge(id int) bool { - // Linear search is fast because the number of edges per shape is typically - // very small (less than 10). - for _, e := range c.edges { - if e == id { - return true - } - } - return false -} - -// ShapeIndexCell stores the index contents for a particular CellID. -type ShapeIndexCell struct { - shapes []*clippedShape -} - -// NewShapeIndexCell creates a new cell that is sized to hold the given number of shapes. -func NewShapeIndexCell(numShapes int) *ShapeIndexCell { - return &ShapeIndexCell{ - shapes: make([]*clippedShape, numShapes), - } -} - -// numEdges reports the total number of edges in all clipped shapes in this cell. -func (s *ShapeIndexCell) numEdges() int { - var e int - for _, cs := range s.shapes { - e += cs.numEdges() - } - return e -} - -// add adds the given clipped shape to this index cell. -func (s *ShapeIndexCell) add(c *clippedShape) { - // C++ uses a set, so it's ordered and unique. We don't currently catch - // the case when a duplicate value is added. - s.shapes = append(s.shapes, c) -} - -// findByShapeID returns the clipped shape that contains the given shapeID, -// or nil if none of the clipped shapes contain it. -func (s *ShapeIndexCell) findByShapeID(shapeID int32) *clippedShape { - // Linear search is fine because the number of shapes per cell is typically - // very small (most often 1), and is large only for pathological inputs - // (e.g. very deeply nested loops). - for _, clipped := range s.shapes { - if clipped.shapeID == shapeID { - return clipped - } - } - return nil -} - -// faceEdge and clippedEdge store temporary edge data while the index is being -// updated. -// -// While it would be possible to combine all the edge information into one -// structure, there are two good reasons for separating it: -// -// - Memory usage. Separating the two means that we only need to -// store one copy of the per-face data no matter how many times an edge is -// subdivided, and it also lets us delay computing bounding boxes until -// they are needed for processing each face (when the dataset spans -// multiple faces). -// -// - Performance. UpdateEdges is significantly faster on large polygons when -// the data is separated, because it often only needs to access the data in -// clippedEdge and this data is cached more successfully. - -// faceEdge represents an edge that has been projected onto a given face, -type faceEdge struct { - shapeID int32 // The ID of shape that this edge belongs to - edgeID int // Edge ID within that shape - maxLevel int // Not desirable to subdivide this edge beyond this level - hasInterior bool // Belongs to a shape that has a dimension of 2 - a, b r2.Point // The edge endpoints, clipped to a given face - edge Edge // The original edge. -} - -// clippedEdge represents the portion of that edge that has been clipped to a given Cell. -type clippedEdge struct { - faceEdge *faceEdge // The original unclipped edge - bound r2.Rect // Bounding box for the clipped portion -} - -// ShapeIndexIteratorPos defines the set of possible iterator starting positions. By -// default iterators are unpositioned, since this avoids an extra seek in this -// situation where one of the seek methods (such as Locate) is immediately called. -type ShapeIndexIteratorPos int - -const ( - // IteratorBegin specifies the iterator should be positioned at the beginning of the index. - IteratorBegin ShapeIndexIteratorPos = iota - // IteratorEnd specifies the iterator should be positioned at the end of the index. - IteratorEnd -) - -// ShapeIndexIterator is an iterator that provides low-level access to -// the cells of the index. Cells are returned in increasing order of CellID. -// -// for it := index.Iterator(); !it.Done(); it.Next() { -// fmt.Print(it.CellID()) -// } -// -type ShapeIndexIterator struct { - index *ShapeIndex - position int - id CellID - cell *ShapeIndexCell -} - -// NewShapeIndexIterator creates a new iterator for the given index. If a starting -// position is specified, the iterator is positioned at the given spot. -func NewShapeIndexIterator(index *ShapeIndex, pos ...ShapeIndexIteratorPos) *ShapeIndexIterator { - s := &ShapeIndexIterator{ - index: index, - } - - if len(pos) > 0 { - if len(pos) > 1 { - panic("too many ShapeIndexIteratorPos arguments") - } - switch pos[0] { - case IteratorBegin: - s.Begin() - case IteratorEnd: - s.End() - default: - panic("unknown ShapeIndexIteratorPos value") - } - } - - return s -} - -func (s *ShapeIndexIterator) clone() *ShapeIndexIterator { - return &ShapeIndexIterator{ - index: s.index, - position: s.position, - id: s.id, - cell: s.cell, - } -} - -// CellID returns the CellID of the current index cell. -// If s.Done() is true, a value larger than any valid CellID is returned. -func (s *ShapeIndexIterator) CellID() CellID { - return s.id -} - -// IndexCell returns the current index cell. -func (s *ShapeIndexIterator) IndexCell() *ShapeIndexCell { - // TODO(roberts): C++ has this call a virtual method to allow subclasses - // of ShapeIndexIterator to do other work before returning the cell. Do - // we need such a thing? - return s.cell -} - -// Center returns the Point at the center of the current position of the iterator. -func (s *ShapeIndexIterator) Center() Point { - return s.CellID().Point() -} - -// Begin positions the iterator at the beginning of the index. -func (s *ShapeIndexIterator) Begin() { - if !s.index.IsFresh() { - s.index.maybeApplyUpdates() - } - s.position = 0 - s.refresh() -} - -// Next positions the iterator at the next index cell. -func (s *ShapeIndexIterator) Next() { - s.position++ - s.refresh() -} - -// Prev advances the iterator to the previous cell in the index and returns true to -// indicate it was not yet at the beginning of the index. If the iterator is at the -// first cell the call does nothing and returns false. -func (s *ShapeIndexIterator) Prev() bool { - if s.position <= 0 { - return false - } - - s.position-- - s.refresh() - return true -} - -// End positions the iterator at the end of the index. -func (s *ShapeIndexIterator) End() { - s.position = len(s.index.cells) - s.refresh() -} - -// Done reports if the iterator is positioned at or after the last index cell. -func (s *ShapeIndexIterator) Done() bool { - return s.id == SentinelCellID -} - -// refresh updates the stored internal iterator values. -func (s *ShapeIndexIterator) refresh() { - if s.position < len(s.index.cells) { - s.id = s.index.cells[s.position] - s.cell = s.index.cellMap[s.CellID()] - } else { - s.id = SentinelCellID - s.cell = nil - } -} - -// seek positions the iterator at the first cell whose ID >= target, or at the -// end of the index if no such cell exists. -func (s *ShapeIndexIterator) seek(target CellID) { - s.position = sort.Search(len(s.index.cells), func(i int) bool { - return s.index.cells[i] >= target - }) - s.refresh() -} - -// LocatePoint positions the iterator at the cell that contains the given Point. -// If no such cell exists, the iterator position is unspecified, and false is returned. -// The cell at the matched position is guaranteed to contain all edges that might -// intersect the line segment between target and the cell's center. -func (s *ShapeIndexIterator) LocatePoint(p Point) bool { - // Let I = cellMap.LowerBound(T), where T is the leaf cell containing - // point P. Then if T is contained by an index cell, then the - // containing cell is either I or I'. We test for containment by comparing - // the ranges of leaf cells spanned by T, I, and I'. - target := cellIDFromPoint(p) - s.seek(target) - if !s.Done() && s.CellID().RangeMin() <= target { - return true - } - - if s.Prev() && s.CellID().RangeMax() >= target { - return true - } - return false -} - -// LocateCellID attempts to position the iterator at the first matching index cell -// in the index that has some relation to the given CellID. Let T be the target CellID. -// If T is contained by (or equal to) some index cell I, then the iterator is positioned -// at I and returns Indexed. Otherwise if T contains one or more (smaller) index cells, -// then the iterator is positioned at the first such cell I and return Subdivided. -// Otherwise Disjoint is returned and the iterator position is undefined. -func (s *ShapeIndexIterator) LocateCellID(target CellID) CellRelation { - // Let T be the target, let I = cellMap.LowerBound(T.RangeMin()), and - // let I' be the predecessor of I. If T contains any index cells, then T - // contains I. Similarly, if T is contained by an index cell, then the - // containing cell is either I or I'. We test for containment by comparing - // the ranges of leaf cells spanned by T, I, and I'. - s.seek(target.RangeMin()) - if !s.Done() { - if s.CellID() >= target && s.CellID().RangeMin() <= target { - return Indexed - } - if s.CellID() <= target.RangeMax() { - return Subdivided - } - } - if s.Prev() && s.CellID().RangeMax() >= target { - return Indexed - } - return Disjoint -} - -// tracker keeps track of which shapes in a given set contain a particular point -// (the focus). It provides an efficient way to move the focus from one point -// to another and incrementally update the set of shapes which contain it. We use -// this to compute which shapes contain the center of every CellID in the index, -// by advancing the focus from one cell center to the next. -// -// Initially the focus is at the start of the CellID space-filling curve. We then -// visit all the cells that are being added to the ShapeIndex in increasing order -// of CellID. For each cell, we draw two edges: one from the entry vertex to the -// center, and another from the center to the exit vertex (where entry and exit -// refer to the points where the space-filling curve enters and exits the cell). -// By counting edge crossings we can incrementally compute which shapes contain -// the cell center. Note that the same set of shapes will always contain the exit -// point of one cell and the entry point of the next cell in the index, because -// either (a) these two points are actually the same, or (b) the intervening -// cells in CellID order are all empty, and therefore there are no edge crossings -// if we follow this path from one cell to the other. -// -// In C++, this is S2ShapeIndex::InteriorTracker. -type tracker struct { - isActive bool - a Point - b Point - nextCellID CellID - crosser *EdgeCrosser - shapeIDs []int32 - - // Shape ids saved by saveAndClearStateBefore. The state is never saved - // recursively so we don't need to worry about maintaining a stack. - savedIDs []int32 -} - -// newTracker returns a new tracker with the appropriate defaults. -func newTracker() *tracker { - // As shapes are added, we compute which ones contain the start of the - // CellID space-filling curve by drawing an edge from OriginPoint to this - // point and counting how many shape edges cross this edge. - t := &tracker{ - isActive: false, - b: trackerOrigin(), - nextCellID: CellIDFromFace(0).ChildBeginAtLevel(maxLevel), - } - t.drawTo(Point{faceUVToXYZ(0, -1, -1).Normalize()}) // CellID curve start - - return t -} - -// trackerOrigin returns the initial focus point when the tracker is created -// (corresponding to the start of the CellID space-filling curve). -func trackerOrigin() Point { - // The start of the S2CellId space-filling curve. - return Point{faceUVToXYZ(0, -1, -1).Normalize()} -} - -// focus returns the current focus point of the tracker. -func (t *tracker) focus() Point { return t.b } - -// addShape adds a shape whose interior should be tracked. containsOrigin indicates -// whether the current focus point is inside the shape. Alternatively, if -// the focus point is in the process of being moved (via moveTo/drawTo), you -// can also specify containsOrigin at the old focus point and call testEdge -// for every edge of the shape that might cross the current drawTo line. -// This updates the state to correspond to the new focus point. -// -// This requires shape.HasInterior -func (t *tracker) addShape(shapeID int32, containsFocus bool) { - t.isActive = true - if containsFocus { - t.toggleShape(shapeID) - } -} - -// moveTo moves the focus of the tracker to the given point. This method should -// only be used when it is known that there are no edge crossings between the old -// and new focus locations; otherwise use drawTo. -func (t *tracker) moveTo(b Point) { t.b = b } - -// drawTo moves the focus of the tracker to the given point. After this method is -// called, testEdge should be called with all edges that may cross the line -// segment between the old and new focus locations. -func (t *tracker) drawTo(b Point) { - t.a = t.b - t.b = b - // TODO: the edge crosser may need an in-place Init method if this gets expensive - t.crosser = NewEdgeCrosser(t.a, t.b) -} - -// testEdge checks if the given edge crosses the current edge, and if so, then -// toggle the state of the given shapeID. -// This requires shape to have an interior. -func (t *tracker) testEdge(shapeID int32, edge Edge) { - if t.crosser.EdgeOrVertexCrossing(edge.V0, edge.V1) { - t.toggleShape(shapeID) - } -} - -// setNextCellID is used to indicate that the last argument to moveTo or drawTo -// was the entry vertex of the given CellID, i.e. the tracker is positioned at the -// start of this cell. By using this method together with atCellID, the caller -// can avoid calling moveTo in cases where the exit vertex of the previous cell -// is the same as the entry vertex of the current cell. -func (t *tracker) setNextCellID(nextCellID CellID) { - t.nextCellID = nextCellID.RangeMin() -} - -// atCellID reports if the focus is already at the entry vertex of the given -// CellID (provided that the caller calls setNextCellID as each cell is processed). -func (t *tracker) atCellID(cellid CellID) bool { - return cellid.RangeMin() == t.nextCellID -} - -// toggleShape adds or removes the given shapeID from the set of IDs it is tracking. -func (t *tracker) toggleShape(shapeID int32) { - // Most shapeIDs slices are small, so special case the common steps. - - // If there is nothing here, add it. - if len(t.shapeIDs) == 0 { - t.shapeIDs = append(t.shapeIDs, shapeID) - return - } - - // If it's the first element, drop it from the slice. - if t.shapeIDs[0] == shapeID { - t.shapeIDs = t.shapeIDs[1:] - return - } - - for i, s := range t.shapeIDs { - if s < shapeID { - continue - } - - // If it's in the set, cut it out. - if s == shapeID { - copy(t.shapeIDs[i:], t.shapeIDs[i+1:]) // overwrite the ith element - t.shapeIDs = t.shapeIDs[:len(t.shapeIDs)-1] - return - } - - // We've got to a point in the slice where we should be inserted. - // (the given shapeID is now less than the current positions id.) - t.shapeIDs = append(t.shapeIDs[0:i], - append([]int32{shapeID}, t.shapeIDs[i:len(t.shapeIDs)]...)...) - return - } - - // We got to the end and didn't find it, so add it to the list. - t.shapeIDs = append(t.shapeIDs, shapeID) -} - -// saveAndClearStateBefore makes an internal copy of the state for shape ids below -// the given limit, and then clear the state for those shapes. This is used during -// incremental updates to track the state of added and removed shapes separately. -func (t *tracker) saveAndClearStateBefore(limitShapeID int32) { - limit := t.lowerBound(limitShapeID) - t.savedIDs = append([]int32(nil), t.shapeIDs[:limit]...) - t.shapeIDs = t.shapeIDs[limit:] -} - -// restoreStateBefore restores the state previously saved by saveAndClearStateBefore. -// This only affects the state for shapeIDs below "limitShapeID". -func (t *tracker) restoreStateBefore(limitShapeID int32) { - limit := t.lowerBound(limitShapeID) - t.shapeIDs = append(append([]int32(nil), t.savedIDs...), t.shapeIDs[limit:]...) - t.savedIDs = nil -} - -// lowerBound returns the shapeID of the first entry x where x >= shapeID. -func (t *tracker) lowerBound(shapeID int32) int32 { - panic("not implemented") -} - -// removedShape represents a set of edges from the given shape that is queued for removal. -type removedShape struct { - shapeID int32 - hasInterior bool - containsTrackerOrigin bool - edges []Edge -} - -// There are three basic states the index can be in. -const ( - stale int32 = iota // There are pending updates. - updating // Updates are currently being applied. - fresh // There are no pending updates. -) - -// ShapeIndex indexes a set of Shapes, where a Shape is some collection of edges -// that optionally defines an interior. It can be used to represent a set of -// points, a set of polylines, or a set of polygons. For Shapes that have -// interiors, the index makes it very fast to determine which Shape(s) contain -// a given point or region. -// -// The index can be updated incrementally by adding or removing shapes. It is -// designed to handle up to hundreds of millions of edges. All data structures -// are designed to be small, so the index is compact; generally it is smaller -// than the underlying data being indexed. The index is also fast to construct. -// -// Polygon, Loop, and Polyline implement Shape which allows these objects to -// be indexed easily. You can find useful query methods in CrossingEdgeQuery -// and ClosestEdgeQuery (Not yet implemented in Go). -// -// Example showing how to build an index of Polylines: -// -// index := NewShapeIndex() -// for _, polyline := range polylines { -// index.Add(polyline); -// } -// // Now you can use a CrossingEdgeQuery or ClosestEdgeQuery here. -// -type ShapeIndex struct { - // shapes is a map of shape ID to shape. - shapes map[int32]Shape - - // The maximum number of edges per cell. - // TODO(roberts): Update the comments when the usage of this is implemented. - maxEdgesPerCell int - - // nextID tracks the next ID to hand out. IDs are not reused when shapes - // are removed from the index. - nextID int32 - - // cellMap is a map from CellID to the set of clipped shapes that intersect that - // cell. The cell IDs cover a set of non-overlapping regions on the sphere. - // In C++, this is a BTree, so the cells are ordered naturally by the data structure. - cellMap map[CellID]*ShapeIndexCell - // Track the ordered list of cell IDs. - cells []CellID - - // The current status of the index; accessed atomically. - status int32 - - // Additions and removals are queued and processed on the first subsequent - // query. There are several reasons to do this: - // - // - It is significantly more efficient to process updates in batches if - // the amount of entities added grows. - // - Often the index will never be queried, in which case we can save both - // the time and memory required to build it. Examples: - // + Loops that are created simply to pass to an Polygon. (We don't - // need the Loop index, because Polygon builds its own index.) - // + Applications that load a database of geometry and then query only - // a small fraction of it. - // - // The main drawback is that we need to go to some extra work to ensure that - // some methods are still thread-safe. Note that the goal is *not* to - // make this thread-safe in general, but simply to hide the fact that - // we defer some of the indexing work until query time. - // - // This mutex protects all of following fields in the index. - mu sync.RWMutex - - // pendingAdditionsPos is the index of the first entry that has not been processed - // via applyUpdatesInternal. - pendingAdditionsPos int32 - - // The set of shapes that have been queued for removal but not processed yet by - // applyUpdatesInternal. - pendingRemovals []*removedShape -} - -// NewShapeIndex creates a new ShapeIndex. -func NewShapeIndex() *ShapeIndex { - return &ShapeIndex{ - maxEdgesPerCell: 10, - shapes: make(map[int32]Shape), - cellMap: make(map[CellID]*ShapeIndexCell), - cells: nil, - status: fresh, - } -} - -// Iterator returns an iterator for this index. -func (s *ShapeIndex) Iterator() *ShapeIndexIterator { - s.maybeApplyUpdates() - return NewShapeIndexIterator(s, IteratorBegin) -} - -// Begin positions the iterator at the first cell in the index. -func (s *ShapeIndex) Begin() *ShapeIndexIterator { - s.maybeApplyUpdates() - return NewShapeIndexIterator(s, IteratorBegin) -} - -// End positions the iterator at the last cell in the index. -func (s *ShapeIndex) End() *ShapeIndexIterator { - // TODO(roberts): It's possible that updates could happen to the index between - // the time this is called and the time the iterators position is used and this - // will be invalid or not the end. For now, things will be undefined if this - // happens. See about referencing the IsFresh to guard for this in the future. - s.maybeApplyUpdates() - return NewShapeIndexIterator(s, IteratorEnd) -} - -// Len reports the number of Shapes in this index. -func (s *ShapeIndex) Len() int { - return len(s.shapes) -} - -// Reset resets the index to its original state. -func (s *ShapeIndex) Reset() { - s.shapes = make(map[int32]Shape) - s.nextID = 0 - s.cellMap = make(map[CellID]*ShapeIndexCell) - s.cells = nil - atomic.StoreInt32(&s.status, fresh) -} - -// NumEdges returns the number of edges in this index. -func (s *ShapeIndex) NumEdges() int { - numEdges := 0 - for _, shape := range s.shapes { - numEdges += shape.NumEdges() - } - return numEdges -} - -// NumEdgesUpTo returns the number of edges in the given index, up to the given -// limit. If the limit is encountered, the current running total is returned, -// which may be more than the limit. -func (s *ShapeIndex) NumEdgesUpTo(limit int) int { - var numEdges int - // We choose to iterate over the shapes in order to match the counting - // up behavior in C++ and for test compatibility instead of using a - // more idiomatic range over the shape map. - for i := int32(0); i <= s.nextID; i++ { - s := s.Shape(i) - if s == nil { - continue - } - numEdges += s.NumEdges() - if numEdges >= limit { - break - } - } - - return numEdges -} - -// Shape returns the shape with the given ID, or nil if the shape has been removed from the index. -func (s *ShapeIndex) Shape(id int32) Shape { return s.shapes[id] } - -// idForShape returns the id of the given shape in this index, or -1 if it is -// not in the index. -// -// TODO(roberts): Need to figure out an appropriate way to expose this on a Shape. -// C++ allows a given S2 type (Loop, Polygon, etc) to be part of multiple indexes. -// By having each type extend S2Shape which has an id element, they all inherit their -// own id field rather than having to track it themselves. -func (s *ShapeIndex) idForShape(shape Shape) int32 { - for k, v := range s.shapes { - if v == shape { - return k - } - } - return -1 -} - -// Add adds the given shape to the index and returns the assigned ID.. -func (s *ShapeIndex) Add(shape Shape) int32 { - s.shapes[s.nextID] = shape - s.nextID++ - atomic.StoreInt32(&s.status, stale) - return s.nextID - 1 -} - -// Remove removes the given shape from the index. -func (s *ShapeIndex) Remove(shape Shape) { - // The index updates itself lazily because it is much more efficient to - // process additions and removals in batches. - id := s.idForShape(shape) - - // If the shape wasn't found, it's already been removed or was not in the index. - if s.shapes[id] == nil { - return - } - - // Remove the shape from the shapes map. - delete(s.shapes, id) - - // We are removing a shape that has not yet been added to the index, - // so there is nothing else to do. - if id >= s.pendingAdditionsPos { - return - } - - numEdges := shape.NumEdges() - removed := &removedShape{ - shapeID: id, - hasInterior: shape.Dimension() == 2, - containsTrackerOrigin: shape.ReferencePoint().Contained, - edges: make([]Edge, numEdges), - } - - for e := 0; e < numEdges; e++ { - removed.edges[e] = shape.Edge(e) - } - - s.pendingRemovals = append(s.pendingRemovals, removed) - atomic.StoreInt32(&s.status, stale) -} - -// Build triggers the update of the index. Calls to Add and Release are normally -// queued and processed on the first subsequent query. This has many advantages, -// the most important of which is that sometimes there *is* no subsequent -// query, which lets us avoid building the index completely. -// -// This method forces any pending updates to be applied immediately. -func (s *ShapeIndex) Build() { - s.maybeApplyUpdates() -} - -// IsFresh reports if there are no pending updates that need to be applied. -// This can be useful to avoid building the index unnecessarily, or for -// choosing between two different algorithms depending on whether the index -// is available. -// -// The returned index status may be slightly out of date if the index was -// built in a different thread. This is fine for the intended use (as an -// efficiency hint), but it should not be used by internal methods. -func (s *ShapeIndex) IsFresh() bool { - return atomic.LoadInt32(&s.status) == fresh -} - -// isFirstUpdate reports if this is the first update to the index. -func (s *ShapeIndex) isFirstUpdate() bool { - // Note that it is not sufficient to check whether cellMap is empty, since - // entries are added to it during the update process. - return s.pendingAdditionsPos == 0 -} - -// isShapeBeingRemoved reports if the shape with the given ID is currently slated for removal. -func (s *ShapeIndex) isShapeBeingRemoved(shapeID int32) bool { - // All shape ids being removed fall below the index position of shapes being added. - return shapeID < s.pendingAdditionsPos -} - -// maybeApplyUpdates checks if the index pieces have changed, and if so, applies pending updates. -func (s *ShapeIndex) maybeApplyUpdates() { - // TODO(roberts): To avoid acquiring and releasing the mutex on every - // query, we should use atomic operations when testing whether the status - // is fresh and when updating the status to be fresh. This guarantees - // that any thread that sees a status of fresh will also see the - // corresponding index updates. - if atomic.LoadInt32(&s.status) != fresh { - s.mu.Lock() - s.applyUpdatesInternal() - atomic.StoreInt32(&s.status, fresh) - s.mu.Unlock() - } -} - -// applyUpdatesInternal does the actual work of updating the index by applying all -// pending additions and removals. It does *not* update the indexes status. -func (s *ShapeIndex) applyUpdatesInternal() { - // TODO(roberts): Building the index can use up to 20x as much memory per - // edge as the final index memory size. If this causes issues, add in - // batched updating to limit the amount of items per batch to a - // configurable memory footprint overhead. - t := newTracker() - - // allEdges maps a Face to a collection of faceEdges. - allEdges := make([][]faceEdge, 6) - - for _, p := range s.pendingRemovals { - s.removeShapeInternal(p, allEdges, t) - } - - for id := s.pendingAdditionsPos; id < int32(len(s.shapes)); id++ { - s.addShapeInternal(id, allEdges, t) - } - - for face := 0; face < 6; face++ { - s.updateFaceEdges(face, allEdges[face], t) - } - - s.pendingRemovals = s.pendingRemovals[:0] - s.pendingAdditionsPos = int32(len(s.shapes)) - // It is the caller's responsibility to update the index status. -} - -// addShapeInternal clips all edges of the given shape to the six cube faces, -// adds the clipped edges to the set of allEdges, and starts tracking its -// interior if necessary. -func (s *ShapeIndex) addShapeInternal(shapeID int32, allEdges [][]faceEdge, t *tracker) { - shape, ok := s.shapes[shapeID] - if !ok { - // This shape has already been removed. - return - } - - faceEdge := faceEdge{ - shapeID: shapeID, - hasInterior: shape.Dimension() == 2, - } - - if faceEdge.hasInterior { - t.addShape(shapeID, containsBruteForce(shape, t.focus())) - } - - numEdges := shape.NumEdges() - for e := 0; e < numEdges; e++ { - edge := shape.Edge(e) - - faceEdge.edgeID = e - faceEdge.edge = edge - faceEdge.maxLevel = maxLevelForEdge(edge) - s.addFaceEdge(faceEdge, allEdges) - } -} - -// addFaceEdge adds the given faceEdge into the collection of all edges. -func (s *ShapeIndex) addFaceEdge(fe faceEdge, allEdges [][]faceEdge) { - aFace := face(fe.edge.V0.Vector) - // See if both endpoints are on the same face, and are far enough from - // the edge of the face that they don't intersect any (padded) adjacent face. - if aFace == face(fe.edge.V1.Vector) { - x, y := validFaceXYZToUV(aFace, fe.edge.V0.Vector) - fe.a = r2.Point{x, y} - x, y = validFaceXYZToUV(aFace, fe.edge.V1.Vector) - fe.b = r2.Point{x, y} - - maxUV := 1 - cellPadding - if math.Abs(fe.a.X) <= maxUV && math.Abs(fe.a.Y) <= maxUV && - math.Abs(fe.b.X) <= maxUV && math.Abs(fe.b.Y) <= maxUV { - allEdges[aFace] = append(allEdges[aFace], fe) - return - } - } - - // Otherwise, we simply clip the edge to all six faces. - for face := 0; face < 6; face++ { - if aClip, bClip, intersects := ClipToPaddedFace(fe.edge.V0, fe.edge.V1, face, cellPadding); intersects { - fe.a = aClip - fe.b = bClip - allEdges[face] = append(allEdges[face], fe) - } - } -} - -// updateFaceEdges adds or removes the various edges from the index. -// An edge is added if shapes[id] is not nil, and removed otherwise. -func (s *ShapeIndex) updateFaceEdges(face int, faceEdges []faceEdge, t *tracker) { - numEdges := len(faceEdges) - if numEdges == 0 && len(t.shapeIDs) == 0 { - return - } - - // Create the initial clippedEdge for each faceEdge. Additional clipped - // edges are created when edges are split between child cells. We create - // two arrays, one containing the edge data and another containing pointers - // to those edges, so that during the recursion we only need to copy - // pointers in order to propagate an edge to the correct child. - clippedEdges := make([]*clippedEdge, numEdges) - bound := r2.EmptyRect() - for e := 0; e < numEdges; e++ { - clipped := &clippedEdge{ - faceEdge: &faceEdges[e], - } - clipped.bound = r2.RectFromPoints(faceEdges[e].a, faceEdges[e].b) - clippedEdges[e] = clipped - bound = bound.AddRect(clipped.bound) - } - - // Construct the initial face cell containing all the edges, and then update - // all the edges in the index recursively. - faceID := CellIDFromFace(face) - pcell := PaddedCellFromCellID(faceID, cellPadding) - - disjointFromIndex := s.isFirstUpdate() - if numEdges > 0 { - shrunkID := s.shrinkToFit(pcell, bound) - if shrunkID != pcell.id { - // All the edges are contained by some descendant of the face cell. We - // can save a lot of work by starting directly with that cell, but if we - // are in the interior of at least one shape then we need to create - // index entries for the cells we are skipping over. - s.skipCellRange(faceID.RangeMin(), shrunkID.RangeMin(), t, disjointFromIndex) - pcell = PaddedCellFromCellID(shrunkID, cellPadding) - s.updateEdges(pcell, clippedEdges, t, disjointFromIndex) - s.skipCellRange(shrunkID.RangeMax().Next(), faceID.RangeMax().Next(), t, disjointFromIndex) - return - } - } - - // Otherwise (no edges, or no shrinking is possible), subdivide normally. - s.updateEdges(pcell, clippedEdges, t, disjointFromIndex) -} - -// shrinkToFit shrinks the PaddedCell to fit within the given bounds. -func (s *ShapeIndex) shrinkToFit(pcell *PaddedCell, bound r2.Rect) CellID { - shrunkID := pcell.ShrinkToFit(bound) - - if !s.isFirstUpdate() && shrunkID != pcell.CellID() { - // Don't shrink any smaller than the existing index cells, since we need - // to combine the new edges with those cells. - iter := s.Iterator() - if iter.LocateCellID(shrunkID) == Indexed { - shrunkID = iter.CellID() - } - } - return shrunkID -} - -// skipCellRange skips over the cells in the given range, creating index cells if we are -// currently in the interior of at least one shape. -func (s *ShapeIndex) skipCellRange(begin, end CellID, t *tracker, disjointFromIndex bool) { - // If we aren't in the interior of a shape, then skipping over cells is easy. - if len(t.shapeIDs) == 0 { - return - } - - // Otherwise generate the list of cell ids that we need to visit, and create - // an index entry for each one. - skipped := CellUnionFromRange(begin, end) - for _, cell := range skipped { - var clippedEdges []*clippedEdge - s.updateEdges(PaddedCellFromCellID(cell, cellPadding), clippedEdges, t, disjointFromIndex) - } -} - -// updateEdges adds or removes the given edges whose bounding boxes intersect a -// given cell. disjointFromIndex is an optimization hint indicating that cellMap -// does not contain any entries that overlap the given cell. -func (s *ShapeIndex) updateEdges(pcell *PaddedCell, edges []*clippedEdge, t *tracker, disjointFromIndex bool) { - // This function is recursive with a maximum recursion depth of 30 (maxLevel). - - // Incremental updates are handled as follows. All edges being added or - // removed are combined together in edges, and all shapes with interiors - // are tracked using tracker. We subdivide recursively as usual until we - // encounter an existing index cell. At this point we absorb the index - // cell as follows: - // - // - Edges and shapes that are being removed are deleted from edges and - // tracker. - // - All remaining edges and shapes from the index cell are added to - // edges and tracker. - // - Continue subdividing recursively, creating new index cells as needed. - // - When the recursion gets back to the cell that was absorbed, we - // restore edges and tracker to their previous state. - // - // Note that the only reason that we include removed shapes in the recursive - // subdivision process is so that we can find all of the index cells that - // contain those shapes efficiently, without maintaining an explicit list of - // index cells for each shape (which would be expensive in terms of memory). - indexCellAbsorbed := false - if !disjointFromIndex { - // There may be existing index cells contained inside pcell. If we - // encounter such a cell, we need to combine the edges being updated with - // the existing cell contents by absorbing the cell. - iter := s.Iterator() - r := iter.LocateCellID(pcell.id) - if r == Disjoint { - disjointFromIndex = true - } else if r == Indexed { - // Absorb the index cell by transferring its contents to edges and - // deleting it. We also start tracking the interior of any new shapes. - s.absorbIndexCell(pcell, iter, edges, t) - indexCellAbsorbed = true - disjointFromIndex = true - } else { - // DCHECK_EQ(SUBDIVIDED, r) - } - } - - // If there are existing index cells below us, then we need to keep - // subdividing so that we can merge with those cells. Otherwise, - // makeIndexCell checks if the number of edges is small enough, and creates - // an index cell if possible (returning true when it does so). - if !disjointFromIndex || !s.makeIndexCell(pcell, edges, t) { - // TODO(roberts): If it turns out to have memory problems when there - // are 10M+ edges in the index, look into pre-allocating space so we - // are not always appending. - childEdges := [2][2][]*clippedEdge{} // [i][j] - - // Compute the middle of the padded cell, defined as the rectangle in - // (u,v)-space that belongs to all four (padded) children. By comparing - // against the four boundaries of middle we can determine which children - // each edge needs to be propagated to. - middle := pcell.Middle() - - // Build up a vector edges to be passed to each child cell. The (i,j) - // directions are left (i=0), right (i=1), lower (j=0), and upper (j=1). - // Note that the vast majority of edges are propagated to a single child. - for _, edge := range edges { - if edge.bound.X.Hi <= middle.X.Lo { - // Edge is entirely contained in the two left children. - a, b := s.clipVAxis(edge, middle.Y) - if a != nil { - childEdges[0][0] = append(childEdges[0][0], a) - } - if b != nil { - childEdges[0][1] = append(childEdges[0][1], b) - } - } else if edge.bound.X.Lo >= middle.X.Hi { - // Edge is entirely contained in the two right children. - a, b := s.clipVAxis(edge, middle.Y) - if a != nil { - childEdges[1][0] = append(childEdges[1][0], a) - } - if b != nil { - childEdges[1][1] = append(childEdges[1][1], b) - } - } else if edge.bound.Y.Hi <= middle.Y.Lo { - // Edge is entirely contained in the two lower children. - if a := s.clipUBound(edge, 1, middle.X.Hi); a != nil { - childEdges[0][0] = append(childEdges[0][0], a) - } - if b := s.clipUBound(edge, 0, middle.X.Lo); b != nil { - childEdges[1][0] = append(childEdges[1][0], b) - } - } else if edge.bound.Y.Lo >= middle.Y.Hi { - // Edge is entirely contained in the two upper children. - if a := s.clipUBound(edge, 1, middle.X.Hi); a != nil { - childEdges[0][1] = append(childEdges[0][1], a) - } - if b := s.clipUBound(edge, 0, middle.X.Lo); b != nil { - childEdges[1][1] = append(childEdges[1][1], b) - } - } else { - // The edge bound spans all four children. The edge - // itself intersects either three or four padded children. - left := s.clipUBound(edge, 1, middle.X.Hi) - a, b := s.clipVAxis(left, middle.Y) - if a != nil { - childEdges[0][0] = append(childEdges[0][0], a) - } - if b != nil { - childEdges[0][1] = append(childEdges[0][1], b) - } - right := s.clipUBound(edge, 0, middle.X.Lo) - a, b = s.clipVAxis(right, middle.Y) - if a != nil { - childEdges[1][0] = append(childEdges[1][0], a) - } - if b != nil { - childEdges[1][1] = append(childEdges[1][1], b) - } - } - } - - // Now recursively update the edges in each child. We call the children in - // increasing order of CellID so that when the index is first constructed, - // all insertions into cellMap are at the end (which is much faster). - for pos := 0; pos < 4; pos++ { - i, j := pcell.ChildIJ(pos) - if len(childEdges[i][j]) > 0 || len(t.shapeIDs) > 0 { - s.updateEdges(PaddedCellFromParentIJ(pcell, i, j), childEdges[i][j], - t, disjointFromIndex) - } - } - } - - if indexCellAbsorbed { - // Restore the state for any edges being removed that we are tracking. - t.restoreStateBefore(s.pendingAdditionsPos) - } -} - -// makeIndexCell builds an indexCell from the given padded cell and set of edges and adds -// it to the index. If the cell or edges are empty, no cell is added. -func (s *ShapeIndex) makeIndexCell(p *PaddedCell, edges []*clippedEdge, t *tracker) bool { - // If the cell is empty, no index cell is needed. (In most cases this - // situation is detected before we get to this point, but this can happen - // when all shapes in a cell are removed.) - if len(edges) == 0 && len(t.shapeIDs) == 0 { - return true - } - - // Count the number of edges that have not reached their maximum level yet. - // Return false if there are too many such edges. - count := 0 - for _, ce := range edges { - if p.Level() < ce.faceEdge.maxLevel { - count++ - } - - if count > s.maxEdgesPerCell { - return false - } - } - - // Possible optimization: Continue subdividing as long as exactly one child - // of the padded cell intersects the given edges. This can be done by finding - // the bounding box of all the edges and calling ShrinkToFit: - // - // cellID = p.ShrinkToFit(RectBound(edges)); - // - // Currently this is not beneficial; it slows down construction by 4-25% - // (mainly computing the union of the bounding rectangles) and also slows - // down queries (since more recursive clipping is required to get down to - // the level of a spatial index cell). But it may be worth trying again - // once containsCenter is computed and all algorithms are modified to - // take advantage of it. - - // We update the InteriorTracker as follows. For every Cell in the index - // we construct two edges: one edge from entry vertex of the cell to its - // center, and one from the cell center to its exit vertex. Here entry - // and exit refer the CellID ordering, i.e. the order in which points - // are encountered along the 2 space-filling curve. The exit vertex then - // becomes the entry vertex for the next cell in the index, unless there are - // one or more empty intervening cells, in which case the InteriorTracker - // state is unchanged because the intervening cells have no edges. - - // Shift the InteriorTracker focus point to the center of the current cell. - if t.isActive && len(edges) != 0 { - if !t.atCellID(p.id) { - t.moveTo(p.EntryVertex()) - } - t.drawTo(p.Center()) - s.testAllEdges(edges, t) - } - - // Allocate and fill a new index cell. To get the total number of shapes we - // need to merge the shapes associated with the intersecting edges together - // with the shapes that happen to contain the cell center. - cshapeIDs := t.shapeIDs - numShapes := s.countShapes(edges, cshapeIDs) - cell := NewShapeIndexCell(numShapes) - - // To fill the index cell we merge the two sources of shapes: edge shapes - // (those that have at least one edge that intersects this cell), and - // containing shapes (those that contain the cell center). We keep track - // of the index of the next intersecting edge and the next containing shape - // as we go along. Both sets of shape ids are already sorted. - eNext := 0 - cNextIdx := 0 - for i := 0; i < numShapes; i++ { - var clipped *clippedShape - // advance to next value base + i - eshapeID := int32(s.Len()) - cshapeID := eshapeID // Sentinels - - if eNext != len(edges) { - eshapeID = edges[eNext].faceEdge.shapeID - } - if cNextIdx < len(cshapeIDs) { - cshapeID = cshapeIDs[cNextIdx] - } - eBegin := eNext - if cshapeID < eshapeID { - // The entire cell is in the shape interior. - clipped = newClippedShape(cshapeID, 0) - clipped.containsCenter = true - cNextIdx++ - } else { - // Count the number of edges for this shape and allocate space for them. - for eNext < len(edges) && edges[eNext].faceEdge.shapeID == eshapeID { - eNext++ - } - clipped = newClippedShape(eshapeID, eNext-eBegin) - for e := eBegin; e < eNext; e++ { - clipped.edges[e-eBegin] = edges[e].faceEdge.edgeID - } - if cshapeID == eshapeID { - clipped.containsCenter = true - cNextIdx++ - } - } - cell.shapes[i] = clipped - } - - // Add this cell to the map. - s.cellMap[p.id] = cell - s.cells = append(s.cells, p.id) - - // Shift the tracker focus point to the exit vertex of this cell. - if t.isActive && len(edges) != 0 { - t.drawTo(p.ExitVertex()) - s.testAllEdges(edges, t) - t.setNextCellID(p.id.Next()) - } - return true -} - -// updateBound updates the specified endpoint of the given clipped edge and returns the -// resulting clipped edge. -func (s *ShapeIndex) updateBound(edge *clippedEdge, uEnd int, u float64, vEnd int, v float64) *clippedEdge { - c := &clippedEdge{faceEdge: edge.faceEdge} - if uEnd == 0 { - c.bound.X.Lo = u - c.bound.X.Hi = edge.bound.X.Hi - } else { - c.bound.X.Lo = edge.bound.X.Lo - c.bound.X.Hi = u - } - - if vEnd == 0 { - c.bound.Y.Lo = v - c.bound.Y.Hi = edge.bound.Y.Hi - } else { - c.bound.Y.Lo = edge.bound.Y.Lo - c.bound.Y.Hi = v - } - - return c -} - -// clipUBound clips the given endpoint (lo=0, hi=1) of the u-axis so that -// it does not extend past the given value of the given edge. -func (s *ShapeIndex) clipUBound(edge *clippedEdge, uEnd int, u float64) *clippedEdge { - // First check whether the edge actually requires any clipping. (Sometimes - // this method is called when clipping is not necessary, e.g. when one edge - // endpoint is in the overlap area between two padded child cells.) - if uEnd == 0 { - if edge.bound.X.Lo >= u { - return edge - } - } else { - if edge.bound.X.Hi <= u { - return edge - } - } - // We interpolate the new v-value from the endpoints of the original edge. - // This has two advantages: (1) we don't need to store the clipped endpoints - // at all, just their bounding box; and (2) it avoids the accumulation of - // roundoff errors due to repeated interpolations. The result needs to be - // clamped to ensure that it is in the appropriate range. - e := edge.faceEdge - v := edge.bound.Y.ClampPoint(interpolateFloat64(u, e.a.X, e.b.X, e.a.Y, e.b.Y)) - - // Determine which endpoint of the v-axis bound to update. If the edge - // slope is positive we update the same endpoint, otherwise we update the - // opposite endpoint. - var vEnd int - positiveSlope := (e.a.X > e.b.X) == (e.a.Y > e.b.Y) - if (uEnd == 1) == positiveSlope { - vEnd = 1 - } - return s.updateBound(edge, uEnd, u, vEnd, v) -} - -// clipVBound clips the given endpoint (lo=0, hi=1) of the v-axis so that -// it does not extend past the given value of the given edge. -func (s *ShapeIndex) clipVBound(edge *clippedEdge, vEnd int, v float64) *clippedEdge { - if vEnd == 0 { - if edge.bound.Y.Lo >= v { - return edge - } - } else { - if edge.bound.Y.Hi <= v { - return edge - } - } - - // We interpolate the new v-value from the endpoints of the original edge. - // This has two advantages: (1) we don't need to store the clipped endpoints - // at all, just their bounding box; and (2) it avoids the accumulation of - // roundoff errors due to repeated interpolations. The result needs to be - // clamped to ensure that it is in the appropriate range. - e := edge.faceEdge - u := edge.bound.X.ClampPoint(interpolateFloat64(v, e.a.Y, e.b.Y, e.a.X, e.b.X)) - - // Determine which endpoint of the v-axis bound to update. If the edge - // slope is positive we update the same endpoint, otherwise we update the - // opposite endpoint. - var uEnd int - positiveSlope := (e.a.X > e.b.X) == (e.a.Y > e.b.Y) - if (vEnd == 1) == positiveSlope { - uEnd = 1 - } - return s.updateBound(edge, uEnd, u, vEnd, v) -} - -// cliupVAxis returns the given edge clipped to within the boundaries of the middle -// interval along the v-axis, and adds the result to its children. -func (s *ShapeIndex) clipVAxis(edge *clippedEdge, middle r1.Interval) (a, b *clippedEdge) { - if edge.bound.Y.Hi <= middle.Lo { - // Edge is entirely contained in the lower child. - return edge, nil - } else if edge.bound.Y.Lo >= middle.Hi { - // Edge is entirely contained in the upper child. - return nil, edge - } - // The edge bound spans both children. - return s.clipVBound(edge, 1, middle.Hi), s.clipVBound(edge, 0, middle.Lo) -} - -// absorbIndexCell absorbs an index cell by transferring its contents to edges -// and/or "tracker", and then delete this cell from the index. If edges includes -// any edges that are being removed, this method also updates their -// InteriorTracker state to correspond to the exit vertex of this cell. -func (s *ShapeIndex) absorbIndexCell(p *PaddedCell, iter *ShapeIndexIterator, edges []*clippedEdge, t *tracker) { - // When we absorb a cell, we erase all the edges that are being removed. - // However when we are finished with this cell, we want to restore the state - // of those edges (since that is how we find all the index cells that need - // to be updated). The edges themselves are restored automatically when - // UpdateEdges returns from its recursive call, but the InteriorTracker - // state needs to be restored explicitly. - // - // Here we first update the InteriorTracker state for removed edges to - // correspond to the exit vertex of this cell, and then save the - // InteriorTracker state. This state will be restored by UpdateEdges when - // it is finished processing the contents of this cell. - if t.isActive && len(edges) != 0 && s.isShapeBeingRemoved(edges[0].faceEdge.shapeID) { - // We probably need to update the tracker. ("Probably" because - // it's possible that all shapes being removed do not have interiors.) - if !t.atCellID(p.id) { - t.moveTo(p.EntryVertex()) - } - t.drawTo(p.ExitVertex()) - t.setNextCellID(p.id.Next()) - for _, edge := range edges { - fe := edge.faceEdge - if !s.isShapeBeingRemoved(fe.shapeID) { - break // All shapes being removed come first. - } - if fe.hasInterior { - t.testEdge(fe.shapeID, fe.edge) - } - } - } - - // Save the state of the edges being removed, so that it can be restored - // when we are finished processing this cell and its children. We don't - // need to save the state of the edges being added because they aren't being - // removed from "edges" and will therefore be updated normally as we visit - // this cell and its children. - t.saveAndClearStateBefore(s.pendingAdditionsPos) - - // Create a faceEdge for each edge in this cell that isn't being removed. - var faceEdges []*faceEdge - trackerMoved := false - - cell := iter.IndexCell() - for _, clipped := range cell.shapes { - shapeID := clipped.shapeID - shape := s.Shape(shapeID) - if shape == nil { - continue // This shape is being removed. - } - - numClipped := clipped.numEdges() - - // If this shape has an interior, start tracking whether we are inside the - // shape. updateEdges wants to know whether the entry vertex of this - // cell is inside the shape, but we only know whether the center of the - // cell is inside the shape, so we need to test all the edges against the - // line segment from the cell center to the entry vertex. - edge := &faceEdge{ - shapeID: shapeID, - hasInterior: shape.Dimension() == 2, - } - - if edge.hasInterior { - t.addShape(shapeID, clipped.containsCenter) - // There might not be any edges in this entire cell (i.e., it might be - // in the interior of all shapes), so we delay updating the tracker - // until we see the first edge. - if !trackerMoved && numClipped > 0 { - t.moveTo(p.Center()) - t.drawTo(p.EntryVertex()) - t.setNextCellID(p.id) - trackerMoved = true - } - } - for i := 0; i < numClipped; i++ { - edgeID := clipped.edges[i] - edge.edgeID = edgeID - edge.edge = shape.Edge(edgeID) - edge.maxLevel = maxLevelForEdge(edge.edge) - if edge.hasInterior { - t.testEdge(shapeID, edge.edge) - } - var ok bool - edge.a, edge.b, ok = ClipToPaddedFace(edge.edge.V0, edge.edge.V1, p.id.Face(), cellPadding) - if !ok { - panic("invariant failure in ShapeIndex") - } - faceEdges = append(faceEdges, edge) - } - } - // Now create a clippedEdge for each faceEdge, and put them in "new_edges". - var newEdges []*clippedEdge - for _, faceEdge := range faceEdges { - clipped := &clippedEdge{ - faceEdge: faceEdge, - bound: clippedEdgeBound(faceEdge.a, faceEdge.b, p.bound), - } - newEdges = append(newEdges, clipped) - } - - // Discard any edges from "edges" that are being removed, and append the - // remainder to "newEdges" (This keeps the edges sorted by shape id.) - for i, clipped := range edges { - if !s.isShapeBeingRemoved(clipped.faceEdge.shapeID) { - newEdges = append(newEdges, edges[i:]...) - break - } - } - - // Update the edge list and delete this cell from the index. - edges, newEdges = newEdges, edges - delete(s.cellMap, p.id) - // TODO(roberts): delete from s.Cells -} - -// testAllEdges calls the trackers testEdge on all edges from shapes that have interiors. -func (s *ShapeIndex) testAllEdges(edges []*clippedEdge, t *tracker) { - for _, edge := range edges { - if edge.faceEdge.hasInterior { - t.testEdge(edge.faceEdge.shapeID, edge.faceEdge.edge) - } - } -} - -// countShapes reports the number of distinct shapes that are either associated with the -// given edges, or that are currently stored in the InteriorTracker. -func (s *ShapeIndex) countShapes(edges []*clippedEdge, shapeIDs []int32) int { - count := 0 - lastShapeID := int32(-1) - - // next clipped shape id in the shapeIDs list. - clippedNext := int32(0) - // index of the current element in the shapeIDs list. - shapeIDidx := 0 - for _, edge := range edges { - if edge.faceEdge.shapeID == lastShapeID { - continue - } - - count++ - lastShapeID = edge.faceEdge.shapeID - - // Skip over any containing shapes up to and including this one, - // updating count as appropriate. - for ; shapeIDidx < len(shapeIDs); shapeIDidx++ { - clippedNext = shapeIDs[shapeIDidx] - if clippedNext > lastShapeID { - break - } - if clippedNext < lastShapeID { - count++ - } - } - } - - // Count any remaining containing shapes. - count += len(shapeIDs) - shapeIDidx - return count -} - -// maxLevelForEdge reports the maximum level for a given edge. -func maxLevelForEdge(edge Edge) int { - // Compute the maximum cell size for which this edge is considered long. - // The calculation does not need to be perfectly accurate, so we use Norm - // rather than Angle for speed. - cellSize := edge.V0.Sub(edge.V1.Vector).Norm() * cellSizeToLongEdgeRatio - // Now return the first level encountered during subdivision where the - // average cell size is at most cellSize. - return AvgEdgeMetric.MinLevel(cellSize) -} - -// removeShapeInternal does the actual work for removing a given shape from the index. -func (s *ShapeIndex) removeShapeInternal(removed *removedShape, allEdges [][]faceEdge, t *tracker) { - // TODO(roberts): finish the implementation of this. -} |