func (l *Loop) isEmptyOrFull() bool {
	return len(l.vertices) == 1
}
isEmptyOrFull reports true if this loop is either the "empty" or "full" special loops.
package s2
import (
	"fmt"
	"io"
	"math"
	"github.com/golang/geo/r1"
	"github.com/golang/geo/r3"
	"github.com/golang/geo/s1"
)
type Loop struct {
	vertices []Point
	originInside bool
	depth int
	bound Rect
	subregionBound Rect
	index *ShapeIndex
}
func LoopFromPoints(pts []Point) *Loop {
	l := &Loop{
		vertices: pts,
		index:    NewShapeIndex(),
	}
	l.initOriginAndBound()
	return l
}
func LoopFromCell(c Cell) *Loop {
	l := &Loop{
		vertices: []Point{
			c.Vertex(0),
			c.Vertex(1),
			c.Vertex(2),
			c.Vertex(3),
		},
		index: NewShapeIndex(),
	}
	l.initOriginAndBound()
	return l
}
var (
	emptyLoopPoint = Point{r3.Vector{X: 0, Y: 0, Z: 1}}
	fullLoopPoint  = Point{r3.Vector{X: 0, Y: 0, Z: -1}}
)
func EmptyLoop() *Loop {
	return LoopFromPoints([]Point{emptyLoopPoint})
}
func FullLoop() *Loop {
	return LoopFromPoints([]Point{fullLoopPoint})
}
func (l *Loop) initOriginAndBound() {
	if len(l.vertices) < 3 {
		if !l.isEmptyOrFull() {
			l.originInside = false
			return
		}
		l.originInside = l.vertices[0].Z < 0
	} else {
		l.originInside = false
		v1Inside := OrderedCCW(Point{l.vertices[1].Ortho()}, l.vertices[0], l.vertices[2], l.vertices[1])
		if v1Inside != l.ContainsPoint(l.vertices[1]) {
			l.originInside = true
		}
	}
	l.initBound()
	l.index = NewShapeIndex()
	l.index.Add(l)
}
func (l *Loop) initBound() {
	if len(l.vertices) == 0 {
		*l = *EmptyLoop()
		return
	}
	if l.isEmptyOrFull() {
		if l.IsEmpty() {
			l.bound = EmptyRect()
		} else {
			l.bound = FullRect()
		}
		l.subregionBound = l.bound
		return
	}
	bounder := NewRectBounder()
	for i := 0; i <= len(l.vertices); i++ {  
		bounder.AddPoint(l.Vertex(i))
	}
	b := bounder.RectBound()
	if l.ContainsPoint(Point{r3.Vector{0, 0, 1}}) {
		b = Rect{r1.Interval{b.Lat.Lo, math.Pi / 2}, s1.FullInterval()}
	}
	if b.Lng.IsFull() && l.ContainsPoint(Point{r3.Vector{0, 0, -1}}) {
		b.Lat.Lo = -math.Pi / 2
	}
	l.bound = b
	l.subregionBound = ExpandForSubregions(l.bound)
}
func (l *Loop) Validate() error {
	if err := l.findValidationErrorNoIndex(); err != nil {
		return err
	}
	return nil
}
func (l *Loop) findValidationErrorNoIndex() error {
	for i, v := range l.vertices {
		if !v.IsUnit() {
			return fmt.Errorf("vertex %d is not unit length", i)
		}
	}
	if len(l.vertices) < 3 {
		if l.isEmptyOrFull() {
			return nil  
		}
		return fmt.Errorf("non-empty, non-full loops must have at least 3 vertices")
	}
	for i, v := range l.vertices {
		if v == l.Vertex(i+1) {
			return fmt.Errorf("edge %d is degenerate (duplicate vertex)", i)
		}
		if other := (Point{l.Vertex(i + 1).Mul(-1)}); v == other {
			return fmt.Errorf("vertices %d and %d are antipodal", i,
				(i+1)%len(l.vertices))
		}
	}
	return nil
}
func (l *Loop) Contains(o *Loop) bool {
	if !l.subregionBound.Contains(o.bound) {
		return false
	}
	if l.isEmptyOrFull() || o.isEmptyOrFull() {
		return l.IsFull() || o.IsEmpty()
	}
	relation := &containsRelation{}
	if hasCrossingRelation(l, o, relation) {
		return false
	}
	if relation.foundSharedVertex {
		return true
	}
	if !l.ContainsPoint(o.Vertex(0)) {
		return false
	}
	if (o.subregionBound.Contains(l.bound) || o.bound.Union(l.bound).IsFull()) &&
		o.ContainsPoint(l.Vertex(0)) {
		return false
	}
	return true
}
func (l *Loop) Intersects(o *Loop) bool {
	if !l.bound.Intersects(o.bound) {
		return false
	}
	relation := &intersectsRelation{}
	if hasCrossingRelation(l, o, relation) {
		return true
	}
	if relation.foundSharedVertex {
		return false
	}
	if l.subregionBound.Contains(o.bound) || l.bound.Union(o.bound).IsFull() {
		if l.ContainsPoint(o.Vertex(0)) {
			return true
		}
	}
	if o.subregionBound.Contains(l.bound) {
		if o.ContainsPoint(l.Vertex(0)) {
			return true
		}
	}
	return false
}
func (l *Loop) Equal(other *Loop) bool {
	if len(l.vertices) != len(other.vertices) {
		return false
	}
	for i, v := range l.vertices {
		if v != other.Vertex(i) {
			return false
		}
	}
	return true
}
func (l *Loop) BoundaryEqual(o *Loop) bool {
	if len(l.vertices) != len(o.vertices) {
		return false
	}
	if l.isEmptyOrFull() {
		return l.IsEmpty() == o.IsEmpty()
	}
	for offset, vertex := range l.vertices {
		if vertex == o.Vertex(0) {
			for i := 0; i < len(l.vertices); i++ {
				if l.Vertex(i+offset) != o.Vertex(i) {
					return false
				}
			}
			return true
		}
	}
	return false
}
func (l *Loop) compareBoundary(o *Loop) int {
	if !l.bound.Intersects(o.bound) {
		return -1
	}
	if l.IsFull() {
		return 1
	}
	if o.IsFull() {
		return -1
	}
	relation := newCompareBoundaryRelation(o.IsHole())
	if hasCrossingRelation(l, o, relation) {
		return 0
	}
	if relation.foundSharedVertex {
		if relation.containsEdge {
			return 1
		}
		return -1
	}
	if l.ContainsPoint(o.Vertex(0)) {
		return 1
	}
	return -1
}
func (l *Loop) ContainsOrigin() bool {
	return l.originInside
}
func (l *Loop) ReferencePoint() ReferencePoint {
	return OriginReferencePoint(l.originInside)
}
func (l *Loop) NumEdges() int {
	if l.isEmptyOrFull() {
		return 0
	}
	return len(l.vertices)
}
func (l *Loop) Edge(i int) Edge {
	return Edge{l.Vertex(i), l.Vertex(i + 1)}
}
func (l *Loop) NumChains() int {
	if l.IsEmpty() {
		return 0
	}
	return 1
}
func (l *Loop) Chain(chainID int) Chain {
	return Chain{0, l.NumEdges()}
}
func (l *Loop) ChainEdge(chainID, offset int) Edge {
	return Edge{l.Vertex(offset), l.Vertex(offset + 1)}
}
func (l *Loop) ChainPosition(edgeID int) ChainPosition {
	return ChainPosition{0, edgeID}
}
func (l *Loop) Dimension() int { return 2 }
func (l *Loop) typeTag() typeTag { return typeTagNone }
func (l *Loop) privateInterface() {}
func (l *Loop) IsEmpty() bool {
	return l.isEmptyOrFull() && !l.ContainsOrigin()
}
func (l *Loop) IsFull() bool {
	return l.isEmptyOrFull() && l.ContainsOrigin()
}