github.com/richardwilkes/toolbox@v1.121.0/xmath/geom/poly/local_minima_table.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  	"github.com/richardwilkes/toolbox/xmath/geom"
    14  	"golang.org/x/exp/constraints"
    15  )
    16  
    17  type localMinimaNode[T constraints.Float] struct {
    18  	y          T
    19  	firstBound *edgeNode[T]
    20  	next       *localMinimaNode[T]
    21  }
    22  
    23  func buildLocalMinimaTable[T constraints.Float](lmt *localMinimaNode[T], sbTree *scanBeamTree[T], p Polygon[T], nc []bool, which int, op clipOp) *localMinimaNode[T] {
    24  	if len(p) == 0 {
    25  		return lmt
    26  	}
    27  	count := 0
    28  	for ci := range p {
    29  		if !nc[ci] {
    30  			for v := range p[ci] {
    31  				if optimal(p[ci], v, len(p[ci])) {
    32  					count++
    33  				}
    34  			}
    35  		}
    36  	}
    37  	edges := make([]edgeNode[T], count)
    38  	edgeIndex := 0
    39  	for ci := range p {
    40  		if !nc[ci] {
    41  			// Perform contour optimization
    42  			count = 0
    43  			for v := range p[ci] {
    44  				if optimal(p[ci], v, len(p[ci])) {
    45  					edges[count].vertex = p[ci][v]
    46  					sbTree.add(edges[count].vertex.Y)
    47  					count++
    48  				}
    49  			}
    50  
    51  			// Do the contour forward pass
    52  			for minimum := 0; minimum < count; minimum++ {
    53  				if edges[previousIndex(minimum, count)].vertex.Y < edges[minimum].vertex.Y ||
    54  					edges[nextIndex(minimum, count)].vertex.Y <= edges[minimum].vertex.Y {
    55  					continue
    56  				}
    57  
    58  				// Search for the next local maximum
    59  				edgeCount := 1
    60  				maximum := nextIndex(minimum, count)
    61  				for edges[nextIndex(maximum, count)].vertex.Y > edges[maximum].vertex.Y {
    62  					edgeCount++
    63  					maximum = nextIndex(maximum, count)
    64  				}
    65  
    66  				// Build the next edge list
    67  				e := &edges[edgeIndex]
    68  				e.belowState = unbundled
    69  				e.bundleBelow[clipping] = false
    70  				e.bundleBelow[subject] = false
    71  				vi := minimum
    72  				for i := 0; i < edgeCount; i++ {
    73  					e = &edges[edgeIndex+i]
    74  					v := &edges[vi]
    75  					e.xb = v.vertex.X
    76  					e.bot = v.vertex
    77  					vi = nextIndex(vi, count)
    78  					v = &edges[vi]
    79  					e.top = v.vertex
    80  					e.dx = (v.vertex.X - e.bot.X) / (e.top.Y - e.bot.Y)
    81  					e.which = which
    82  					e.outAbove = nil
    83  					e.outBelow = nil
    84  					e.next = nil
    85  					e.prev = nil
    86  					if edgeCount > 1 && i < edgeCount-1 {
    87  						e.successor = &edges[edgeIndex+i+1]
    88  					} else {
    89  						e.successor = nil
    90  					}
    91  					if edgeCount > 1 && i > 0 {
    92  						e.pred = &edges[edgeIndex+i-1]
    93  					} else {
    94  						e.pred = nil
    95  					}
    96  					e.nextBound = nil
    97  					e.clipSide = op == subtractOp
    98  					e.subjectSide = false
    99  				}
   100  				lmt = lmt.insertBound(edges[minimum].vertex.Y, &edges[edgeIndex])
   101  				edgeIndex += edgeCount
   102  			}
   103  
   104  			// Do the contour reverse pass
   105  			for minimum := 0; minimum < count; minimum++ {
   106  				if edges[previousIndex(minimum, count)].vertex.Y <= edges[minimum].vertex.Y ||
   107  					edges[nextIndex(minimum, count)].vertex.Y < edges[minimum].vertex.Y {
   108  					continue
   109  				}
   110  				// Search for the previous local maximum
   111  				edgeCount := 1
   112  				maximum := previousIndex(minimum, count)
   113  				for edges[previousIndex(maximum, count)].vertex.Y > edges[maximum].vertex.Y {
   114  					edgeCount++
   115  					maximum = previousIndex(maximum, count)
   116  				}
   117  
   118  				// Build the previous edge list
   119  				e := &edges[edgeIndex]
   120  				e.belowState = unbundled
   121  				e.bundleBelow[clipping] = false
   122  				e.bundleBelow[subject] = false
   123  				vi := minimum
   124  				for i := 0; i < edgeCount; i++ {
   125  					e = &edges[edgeIndex+i]
   126  					v := &edges[vi]
   127  					e.xb = v.vertex.X
   128  					e.bot = v.vertex
   129  					vi = previousIndex(vi, count)
   130  					v = &edges[vi]
   131  					e.top = v.vertex
   132  					e.dx = (v.vertex.X - e.bot.X) / (e.top.Y - e.bot.Y)
   133  					e.which = which
   134  					e.outAbove = nil
   135  					e.outBelow = nil
   136  					e.next = nil
   137  					e.prev = nil
   138  					if edgeCount > 1 && i < edgeCount-1 {
   139  						e.successor = &edges[edgeIndex+i+1]
   140  					} else {
   141  						e.successor = nil
   142  					}
   143  					if edgeCount > 1 && i > 0 {
   144  						e.pred = &edges[edgeIndex+i-1]
   145  					} else {
   146  						e.pred = nil
   147  					}
   148  					e.nextBound = nil
   149  					e.clipSide = op == subtractOp
   150  					e.subjectSide = false
   151  				}
   152  				lmt = lmt.insertBound(edges[minimum].vertex.Y, &edges[edgeIndex])
   153  				edgeIndex += edgeCount
   154  			}
   155  		}
   156  	}
   157  	return lmt
   158  }
   159  
   160  func (n *localMinimaNode[T]) insertBound(y T, e *edgeNode[T]) *localMinimaNode[T] {
   161  	lmn, en := n.boundList(y)
   162  	e.insertInto(en)
   163  	return lmn
   164  }
   165  
   166  func (n *localMinimaNode[T]) boundList(y T) (lmn *localMinimaNode[T], en **edgeNode[T]) {
   167  	switch {
   168  	case n == nil:
   169  		lmn = &localMinimaNode[T]{y: y}
   170  		return lmn, &lmn.firstBound
   171  	case y < n.y:
   172  		lmn = &localMinimaNode[T]{
   173  			y:    y,
   174  			next: n,
   175  		}
   176  		return lmn, &lmn.firstBound
   177  	case y > n.y:
   178  		n.next, en = n.next.boundList(y)
   179  		return n, en
   180  	default:
   181  		return n, &n.firstBound
   182  	}
   183  }
   184  
   185  func optimal[T constraints.Float](v []geom.Point[T], i, n int) bool {
   186  	return v[previousIndex(i, n)].Y != v[i].Y || v[nextIndex(i, n)].Y != v[i].Y
   187  }
   188  
   189  func previousIndex(i, n int) int {
   190  	return (i - 1 + n) % n
   191  }
   192  
   193  func nextIndex(i, n int) int {
   194  	return (i + 1) % n
   195  }