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 }