github.com/richardwilkes/toolbox@v1.121.0/xmath/geom/poly/polygon.go (about)

     1  // Copyright (c) 2016-2024 by Richard A. Wilkes. All rights reserved.
     2  //
     3  // This Source Code Form is subject to the terms of the Mozilla Public
     4  // License, version 2.0. If a copy of the MPL was not distributed with
     5  // this file, You can obtain one at http://mozilla.org/MPL/2.0/.
     6  //
     7  // This Source Code Form is "Incompatible With Secondary Licenses", as
     8  // defined by the Mozilla Public License, version 2.0.
     9  
    10  package poly
    11  
    12  import (
    13  	"strings"
    14  
    15  	"github.com/richardwilkes/toolbox/xmath/geom"
    16  	"golang.org/x/exp/constraints"
    17  )
    18  
    19  const (
    20  	clipping = iota
    21  	subject
    22  )
    23  
    24  type clipOp int
    25  
    26  const (
    27  	subtractOp clipOp = iota
    28  	intersectOp
    29  	xorOp
    30  	unionOp
    31  )
    32  
    33  // Polygon holds one or more contour lines. The polygon may contain holes and may be self-intersecting.
    34  type Polygon[T constraints.Float] []Contour[T]
    35  
    36  // Clone returns a duplicate of this polygon.
    37  func (p Polygon[T]) Clone() Polygon[T] {
    38  	if len(p) == 0 {
    39  		return nil
    40  	}
    41  	clone := Polygon[T](make([]Contour[T], len(p)))
    42  	for i := range p {
    43  		clone[i] = p[i].Clone()
    44  	}
    45  	return clone
    46  }
    47  
    48  // String implements fmt.Stringer.
    49  func (p Polygon[T]) String() string {
    50  	var buffer strings.Builder
    51  	buffer.WriteByte('{')
    52  	for i, c := range p {
    53  		if i != 0 {
    54  			buffer.WriteByte(',')
    55  		}
    56  		buffer.WriteString(c.String())
    57  	}
    58  	buffer.WriteByte('}')
    59  	return buffer.String()
    60  }
    61  
    62  // Bounds returns the bounding rectangle of this polygon.
    63  func (p Polygon[T]) Bounds() geom.Rect[T] {
    64  	if len(p) == 0 {
    65  		return geom.Rect[T]{}
    66  	}
    67  	b := p[0].Bounds()
    68  	for _, c := range p[1:] {
    69  		b = b.Union(c.Bounds())
    70  	}
    71  	return b
    72  }
    73  
    74  // Contains returns true if the point is contained by this polygon.
    75  func (p Polygon[T]) Contains(pt geom.Point[T]) bool {
    76  	for i := range p {
    77  		if p[i].Contains(pt) {
    78  			return true
    79  		}
    80  	}
    81  	return false
    82  }
    83  
    84  // ContainsEvenOdd returns true if the point is contained by the polygon using the even-odd rule.
    85  // https://en.wikipedia.org/wiki/Even-odd_rule
    86  func (p Polygon[T]) ContainsEvenOdd(pt geom.Point[T]) bool {
    87  	var count int
    88  	for i := range p {
    89  		if p[i].Contains(pt) {
    90  			count++
    91  		}
    92  	}
    93  	return count%2 == 1
    94  }
    95  
    96  // Transform returns the result of transforming this Polygon by the Matrix.
    97  func (p Polygon[T]) Transform(m geom.Matrix[T]) Polygon[T] {
    98  	clone := p.Clone()
    99  	for _, c := range clone {
   100  		for i := range c {
   101  			c[i] = m.TransformPoint(c[i])
   102  		}
   103  	}
   104  	return clone
   105  }
   106  
   107  // Union returns a new Polygon holding the union of both Polygons.
   108  func (p Polygon[T]) Union(other Polygon[T]) Polygon[T] {
   109  	return p.construct(unionOp, other)
   110  }
   111  
   112  // Intersect returns a new Polygon holding the intersection of both Polygons.
   113  func (p Polygon[T]) Intersect(other Polygon[T]) Polygon[T] {
   114  	return p.construct(intersectOp, other)
   115  }
   116  
   117  // Sub returns a new Polygon holding the result of removing the other Polygon from this Polygon.
   118  func (p Polygon[T]) Sub(other Polygon[T]) Polygon[T] {
   119  	return p.construct(subtractOp, other)
   120  }
   121  
   122  // Xor returns a new Polygon holding the result of xor'ing this Polygon with the other Polygon.
   123  func (p Polygon[T]) Xor(other Polygon[T]) Polygon[T] {
   124  	return p.construct(xorOp, other)
   125  }
   126  
   127  func (p Polygon[T]) construct(op clipOp, other Polygon[T]) Polygon[T] {
   128  	var result Polygon[T]
   129  
   130  	// Short-circuit the work if we can trivially determine the result is an empty polygon.
   131  	if (len(p) == 0 && len(other) == 0) ||
   132  		(len(p) == 0 && (op == intersectOp || op == subtractOp)) ||
   133  		(len(other) == 0 && op == intersectOp) {
   134  		return result
   135  	}
   136  
   137  	// Build the local minima table and the scan beam table
   138  	sbTree := &scanBeamTree[T]{}
   139  	subjNonContributing, clipNonContributing := p.identifyNonContributingContours(op, other)
   140  	lmt := buildLocalMinimaTable(nil, sbTree, p, subjNonContributing, subject, op)
   141  	if lmt = buildLocalMinimaTable(lmt, sbTree, other, clipNonContributing, clipping, op); lmt == nil {
   142  		return result
   143  	}
   144  	sbt := sbTree.buildScanBeamTable()
   145  
   146  	// Process each scan beam
   147  	var aet *edgeNode[T]
   148  	var outPoly *polygonNode[T]
   149  	localMin := lmt
   150  	i := 0
   151  	for i < len(sbt) {
   152  
   153  		// Set yb and yt to the bottom and top of the scanbeam
   154  		var yt, dy T
   155  		var bPt geom.Point[T]
   156  		bPt.Y = sbt[i]
   157  		i++
   158  		if i < len(sbt) {
   159  			yt = sbt[i]
   160  			dy = yt - bPt.Y
   161  		}
   162  
   163  		// If LMT node corresponding to bPt.Y exists
   164  		if localMin != nil && localMin.y == bPt.Y {
   165  			// Add edges starting at this local minimum to the AET
   166  			for edge := localMin.firstBound; edge != nil; edge = edge.nextBound {
   167  				aet = edge.addEdgeToActiveEdgeTable(aet, nil)
   168  			}
   169  			localMin = localMin.next
   170  		}
   171  		if aet == nil {
   172  			continue
   173  		}
   174  
   175  		aet.bundleFields(bPt)
   176  		bPt, outPoly = aet.process(op, bPt, outPoly)
   177  		aet = aet.deleteTerminatingEdges(bPt, yt)
   178  
   179  		if i < len(sbt) {
   180  			// Process each node in the intersection table
   181  			for inter := aet.buildIntersections(dy); inter != nil; inter = inter.next {
   182  				outPoly = inter.process(op, bPt, outPoly)
   183  				aet = aet.swapIntersectingEdgeBundles(inter)
   184  			}
   185  			aet = aet.prepareForNextScanBeam(yt)
   186  		}
   187  	}
   188  
   189  	// Generate the resulting polygon
   190  	if outPoly != nil {
   191  		return outPoly.generate()
   192  	}
   193  	return Polygon[T]{}
   194  }
   195  
   196  func (p Polygon[T]) identifyNonContributingContours(op clipOp, clip Polygon[T]) (subjNC, clipNC []bool) {
   197  	subjNC = make([]bool, len(p))
   198  	clipNC = make([]bool, len(clip))
   199  	if (op == intersectOp || op == subtractOp) && len(p) > 0 && len(clip) > 0 {
   200  
   201  		// Check all subject contour bounding boxes against clip boxes
   202  		overlaps := make([]bool, len(p)*len(clip))
   203  		boxes := make([]geom.Rect[T], len(clip))
   204  		for i, c := range clip {
   205  			boxes[i] = c.Bounds()
   206  		}
   207  		for si := range p {
   208  			box := p[si].Bounds()
   209  			for ci := range clip {
   210  				overlaps[ci*len(p)+si] = box.Intersects(boxes[ci])
   211  			}
   212  		}
   213  
   214  		// For each clip contour, search for any subject contour overlaps
   215  		for ci := range clip {
   216  			clipNC[ci] = true
   217  			for si := range p {
   218  				if overlaps[ci*len(p)+si] {
   219  					clipNC[ci] = false
   220  					break
   221  				}
   222  			}
   223  		}
   224  
   225  		if op == intersectOp {
   226  			// For each subject contour, search for any clip contour overlaps
   227  			for si := range p {
   228  				subjNC[si] = true
   229  				for ci := range clip {
   230  					if overlaps[ci*len(p)+si] {
   231  						subjNC[si] = false
   232  						break
   233  					}
   234  				}
   235  			}
   236  		}
   237  	}
   238  	return
   239  }