github.com/pavlo67/common@v0.5.3/common/mathlib/plane/chains.go (about) 1 package plane 2 3 import ( 4 "fmt" 5 "math" 6 "slices" 7 8 "github.com/pavlo67/common/common/mathlib/sets" 9 ) 10 11 type PolyChain []Point2 12 13 //type ProjectionPolyChainOnPolyChain struct { 14 // N0 int 15 // ProjectionOnPolyChain 16 //} 17 18 func (pCh PolyChain) Length() float64 { 19 length := 0. 20 for i := 1; i < len(pCh); i++ { 21 length += pCh[i-1].DistanceTo(pCh[i]) 22 } 23 return length 24 } 25 26 func (pCh PolyChain) Reversed() PolyChain { 27 polyChainReversed := make([]Point2, len(pCh)) 28 for i, p2 := range pCh { 29 polyChainReversed[len(pCh)-i-1] = p2 30 } 31 return polyChainReversed 32 } 33 34 func (pCh PolyChain) Direction(deviationMaxIn float64) *Segment { 35 if len(pCh) < 2 { 36 return nil 37 } 38 39 pEnd := pCh[len(pCh)-1] 40 directionLine := Segment{pCh[len(pCh)-2], pEnd} 41 42 for i := len(pCh) - 3; i >= 0; i-- { 43 directionLineNext := Segment{Segment{pCh[i], pCh[i+1]}.Middle(), pEnd} 44 for j := i + 1; j < len(pCh)-1; j++ { 45 if pCh[j].DistanceToLine(directionLineNext) > deviationMaxIn { 46 return &directionLine 47 } 48 } 49 directionLine = directionLineNext 50 51 directionLineNext = Segment{pCh[i], pEnd} 52 for j := i + 1; j < len(pCh)-1; j++ { 53 if pCh[j].DistanceToLine(directionLineNext) > deviationMaxIn { 54 return &directionLine 55 } 56 } 57 directionLine = directionLineNext 58 } 59 60 if directionLine[0] == directionLine[1] { 61 return nil 62 } 63 64 return &directionLine 65 } 66 67 func (p Point2) ProjectionOnLineSegment(ls Segment) (distance, projectionPosition float64) { 68 d0, d1, d := p.DistanceSquare(ls[0]), p.DistanceSquare(ls[1]), ls[0].DistanceSquare(ls[1]) 69 var reversed bool 70 if d1 < d0 { 71 d0, d1 = d1, d0 72 reversed = true 73 } 74 if d0+d < d1 { 75 return math.NaN(), math.NaN() 76 } 77 78 c0 := (d0 + d - d1) / (2 * math.Sqrt(d)) 79 80 if reversed { 81 return math.Sqrt(d0 - c0*c0), math.Sqrt(d) - c0 82 } else { 83 return math.Sqrt(d0 - c0*c0), c0 84 } 85 } 86 87 func (p Point2) ProjectionOnPolyChain(pCh PolyChain) (float64, ProjectionOnPolyChain) { 88 89 if len(pCh) < 1 { 90 return math.NaN(), ProjectionOnPolyChain{N: -1, Position: math.NaN(), Point2: Point2{math.NaN(), math.NaN()}} 91 } else if len(pCh) == 1 { 92 return p.DistanceTo(pCh[0]), ProjectionOnPolyChain{Point2: pCh[0]} 93 } 94 95 minDist := math.Inf(1) 96 pr := ProjectionOnPolyChain{ 97 N: -1, 98 Position: math.NaN(), 99 Point2: Point2{math.NaN(), math.NaN()}, 100 } 101 var n int 102 var pPr Point2 103 104 // POINTS: 105 for i, pI := range pCh[:len(pCh)-1] { 106 dist, position := p.ProjectionOnLineSegment(Segment{pI, pCh[i+1]}) 107 if dist >= minDist || math.IsNaN(dist) { 108 continue 109 } 110 111 if segmentLength := pI.DistanceTo(pCh[i+1]); segmentLength <= 0 { 112 pPr, n, position = pI, i, 0 113 } else if position >= segmentLength { 114 pPr, n, position = pCh[i+1], i+1, 0 115 } else { 116 dx, dy := pCh[i+1].X-pI.X, pCh[i+1].Y-pI.Y 117 pPr, n = Point2{pI.X + dx*position/segmentLength, pI.Y + dy*position/segmentLength}, i 118 } 119 120 minDist, pr = dist, ProjectionOnPolyChain{n, position, pPr} 121 } 122 123 return minDist, pr 124 } 125 126 func (pCh PolyChain) DistanceTo(pCh1 PolyChain, distanceMax float64) (dist float64, pr, pr1 *ProjectionOnPolyChain) { 127 for n0, p0 := range pCh { 128 if dist_, pr_ := p0.DistanceToPolyChain(pCh1); dist_ <= distanceMax { 129 return dist_, &ProjectionOnPolyChain{N: n0, Point2: p0}, &pr_ 130 } 131 } 132 for n1, p1 := range pCh1 { 133 if dist_, pr_ := p1.DistanceToPolyChain(pCh); dist_ <= distanceMax { 134 return dist_, &pr_, &ProjectionOnPolyChain{N: n1, Point2: p1} 135 } 136 } 137 138 return math.NaN(), nil, nil 139 } 140 141 func AveragePolyChains(pCh0, pCh1 PolyChain, distanceMaxIn float64, connectEnds bool) ( 142 ok bool, pCh0Averaged PolyChain, pCh1RestsInitial []PolyChain) { 143 144 // log.Print(distanceMaxIn, pCh0, pCh1) 145 // if distanceMaxIn == 9.87053098413958 { 146 // } 147 148 var p1Averaged []int 149 150 for n0, p0 := range pCh0 { 151 if dist, pr := p0.ProjectionOnPolyChain(pCh1); dist <= distanceMaxIn { 152 pCh0[n0] = Point2{0.5 * (pr.X + p0.X), 0.5 * (pr.Y + p0.Y)} 153 if pr.Position == 0 { 154 pCh1[pr.N] = pCh0[n0] 155 p1Averaged = append(p1Averaged, pr.N) 156 } else { 157 // TODO!!! be careful: appending order is essential here 158 pCh1 = append(pCh1[:pr.N+1], append(PolyChain{pCh0[n0]}, pCh1[pr.N+1:]...)...) 159 160 for i, p := range p1Averaged { 161 if p > pr.N { 162 p1Averaged[i]++ 163 } 164 } 165 p1Averaged = append(p1Averaged, pr.N+1) 166 } 167 } 168 } 169 170 for n1, p1 := range pCh1 { 171 if sets.In(p1Averaged, n1) { 172 continue 173 } 174 175 if dist, pr := p1.ProjectionOnPolyChain(pCh0); dist <= distanceMaxIn { 176 pCh1[n1] = Point2{0.5 * (pr.X + p1.X), 0.5 * (pr.Y + p1.Y)} 177 if pr.Position == 0 { 178 pCh0[pr.N] = pCh1[n1] 179 } else { 180 // TODO!!! be careful: appending order is essential here 181 pCh0 = append(pCh0[:pr.N+1], append(PolyChain{pCh1[n1]}, pCh0[pr.N+1:]...)...) 182 183 } 184 p1Averaged = append(p1Averaged, n1) 185 } 186 } 187 188 slices.Sort(p1Averaged) 189 190 // log.Print(pCh0, "\n", pCh1, "\n", p1Averaged) 191 192 var n1Prev, n1Next int 193 for _, n1 := range p1Averaged { 194 if n1 > n1Next { 195 pCh1RestsInitial = append(pCh1RestsInitial, pCh1[n1Prev:n1+1]) 196 } 197 n1Prev, n1Next = n1, n1+1 198 } 199 if len(pCh1) > n1Next { 200 pCh1RestsInitial = append(pCh1RestsInitial, pCh1[n1Prev:]) 201 } 202 203 ok = len(p1Averaged) > 0 204 205 if ok && connectEnds && len(pCh1RestsInitial) > 0 { 206 var pCh1Rests []PolyChain 207 p00, p01 := pCh0[0], pCh0[len(pCh0)-1] 208 v0 := Point2{p01.X - p00.X, p01.Y - p00.Y} 209 for _, pCh1Rest := range pCh1RestsInitial { 210 p10, p11 := pCh1Rest[0], pCh1Rest[len(pCh1Rest)-1] 211 if math.Abs(v0.AnglesDelta(Point2{p11.X - p10.X, p11.Y - p10.Y})) <= math.Pi/2 { 212 if p01 == p10 { 213 pCh0 = append(pCh0, pCh1Rest[1:]...) 214 } else if p11 == p00 { 215 pCh0 = append(pCh1Rest, pCh0[1:]...) 216 } else { 217 pCh1Rests = append(pCh1Rests, pCh1Rest) 218 } 219 } else { 220 if p01 == p11 { 221 pCh0 = append(pCh0, pCh1Rest.Reversed()[1:]...) 222 } else if p00 == p10 { 223 pCh0 = append(pCh0.Reversed(), pCh1Rest[1:]...) 224 } else { 225 pCh1Rests = append(pCh1Rests, pCh1Rest) 226 } 227 228 } 229 } 230 231 if len(pCh1Rests) != len(pCh1RestsInitial) { 232 // so some non-averaged fragment of pCh1 is added to pCh0 233 // TODO!!! be careful: it looks like this operation "reverses" the original pCh0/pCh1 order because pCh1Rests contains pCh0 as the last fragment 234 return true, nil, append(pCh1Rests, pCh0) 235 } 236 237 return true, pCh0, pCh1Rests 238 } 239 240 // if distanceMaxIn == 9.87053098413958 { 241 // if ok { 242 // log.Print(2222222222222, distanceMaxIn, pCh0, pCh1RestsInitial) 243 // } 244 245 return ok, pCh0, pCh1RestsInitial 246 } 247 248 type PolyChainsIntersection struct { 249 Point2 250 N0, N1 int 251 } 252 253 func PolyChainsIntersectionAny(pCh0, pCh1 PolyChain) *PolyChainsIntersection { 254 for i0 := 1; i0 < len(pCh0); i0++ { 255 s0 := Segment{pCh0[i0-1], pCh0[i0]} 256 for i1 := 1; i1 < len(pCh1); i1++ { 257 if p := SegmentsIntersection(s0, Segment{pCh1[i1-1], pCh1[i1]}); p != nil { 258 return &PolyChainsIntersection{*p, i0 - 1, i1 - 1} 259 } 260 } 261 } 262 263 return nil 264 } 265 266 // it's equal to append(append(... 267 268 //pCh1New := make(PolyChain, len(pCh1)+1) 269 //for i := 0; i <= pr.N; i++ { 270 // pCh1New[i] = pCh1[i] 271 //} 272 //pCh1New[pr.N+1] = pCh0[n0] 273 //for i := pr.N + 1; i < len(pCh1); i++ { 274 // pCh1New[i+1] = pCh1[i] 275 //} 276 //pCh1 = pCh1New 277 278 //pCh0New := make(PolyChain, len(pCh0)+1) 279 //for i := 0; i <= pr.N; i++ { 280 // pCh0New[i] = pCh0[i] 281 //} 282 //pCh0New[pr.N+1] = pCh1[n1] 283 //for i := pr.N + 1; i < len(pCh0); i++ { 284 // pCh0New[i+1] = pCh0[i] 285 //} 286 //pCh0 = pCh0New 287 288 func (pCh PolyChain) Shorten(maxDistanceToBeJoined float64) PolyChain { 289 for i := 0; i <= len(pCh)-3; i++ { 290 for j := len(pCh) - 1; j >= i+2; j-- { 291 if pCh[i].DistanceTo(pCh[j]) <= maxDistanceToBeJoined { 292 pCh = append(pCh[:i+1], pCh[j:]...) 293 break 294 } 295 } 296 } 297 298 return pCh 299 } 300 301 func (pCh PolyChain) Straighten(minDeviation float64) PolyChain { 302 if len(pCh) <= 2 { 303 return pCh 304 } 305 306 straightenedPolyChain := PolyChain{pCh[0]} 307 prevMedium := Segment{pCh[0], pCh[1]}.Middle() 308 var prevMediumAdded bool 309 310 for i := 1; i < len(pCh)-1; i++ { 311 medium := Segment{pCh[i], pCh[i+1]}.Middle() 312 if distance := pCh[i].DistanceToLine(Segment{prevMedium, medium}); distance > minDeviation { 313 straightenedPolyChain = append(straightenedPolyChain, pCh[i]) 314 prevMediumAdded = false 315 } else if prevMediumAdded { 316 straightenedPolyChain = append(straightenedPolyChain, medium) 317 prevMediumAdded = true 318 } else { 319 straightenedPolyChain = append(straightenedPolyChain, prevMedium, medium) 320 prevMediumAdded = true 321 } 322 prevMedium = medium 323 } 324 325 return append(straightenedPolyChain, pCh[len(pCh)-1]) 326 } 327 328 func (pCh PolyChain) Cut(fromEndI int, axis Segment) PolyChain { 329 330 numI := len(pCh) 331 332 if fromEndI < 0 || fromEndI >= numI { 333 return nil 334 } 335 336 p0 := pCh[fromEndI] 337 if p0.DistanceToLine(axis) == 0 { 338 return nil 339 } 340 341 cutted := []Point2{p0} 342 343 divided := false 344 for i := (fromEndI + 1) % numI; i != fromEndI; i = (i + 1) % numI { 345 p1 := pCh[i] 346 if axis.DivideByLine(p0, p1) != nil { 347 divided = true 348 s := Segment{pCh[i-1], p1} 349 p11 := s.LinesIntersection(axis) 350 if p11 != nil { 351 cutted = append(cutted, *p11) 352 } else { 353 // TODO!!! but why??? 354 cutted = append(cutted, s.Middle()) 355 } 356 break 357 } else { 358 cutted = append(cutted, p1) 359 } 360 } 361 362 if divided { 363 364 i := (fromEndI + numI - 1) % numI 365 for { 366 p1 := pCh[i] 367 if axis.DivideByLine(p0, p1) != nil { 368 s := Segment{pCh[(i+1)%numI], p1} 369 p11 := s.LinesIntersection(axis) 370 if p11 != nil { 371 cutted = append(cutted, *p11) 372 } else { 373 // TODO!!! but why??? 374 cutted = append(cutted, s.Middle()) 375 } 376 break 377 } 378 i = (i + numI - 1) % numI 379 } 380 381 i = (i + 1) % numI 382 for ; i != fromEndI; i = (i + 1) % numI { 383 cutted = append(cutted, pCh[i]) 384 } 385 386 } 387 388 return cutted 389 } 390 391 func (pCh PolyChain) Approximate(minDeviation float64) PolyChain { 392 if len(pCh) <= 2 { 393 return pCh 394 } 395 396 lineSegment := Segment{pCh[0], pCh[len(pCh)-1]} 397 segmentLength := pCh[0].DistanceTo(pCh[len(pCh)-1]) 398 segmentLengthSquare := segmentLength * segmentLength 399 maxDistance := minDeviation 400 var distance, ratio, maxRatio float64 401 var maxI int 402 for i := 1; i < len(pCh)-1; i++ { 403 distanceToFirst, distanceToLast := pCh[i].DistanceTo(pCh[0]), pCh[i].DistanceTo(pCh[len(pCh)-1]) 404 if distanceToFirst > distanceToLast { 405 distanceToFirst, distanceToLast = distanceToLast, distanceToFirst 406 } 407 408 if distanceToFirst*distanceToFirst+segmentLengthSquare <= distanceToLast*distanceToLast { 409 distance = distanceToFirst 410 } else { 411 distance = pCh[i].DistanceToLine(lineSegment) 412 } 413 414 if distance > maxDistance { 415 maxDistance, maxI, maxRatio = distance, i, float64(i)/float64(len(pCh)-i-1) 416 } else if distance == maxDistance { 417 if i < len(pCh)-i-1 { 418 ratio = float64(i) / float64(len(pCh)-i-1) 419 } else { 420 ratio = float64(len(pCh)-i-1) / float64(i) 421 } 422 if ratio > maxRatio { 423 maxI, maxRatio = i, float64(i)/float64(len(pCh)-i-1) 424 } 425 } 426 } 427 if maxI <= 0 { 428 return lineSegment[:] 429 } 430 431 return append(append(PolyChain{}, pCh[:maxI+1].Approximate(minDeviation)...), pCh[maxI:].Approximate(minDeviation)[1:]...) 432 } 433 434 func (pCh PolyChain) Filter() PolyChain { 435 if len(pCh) < 2 { 436 return pCh 437 } 438 var pChNew PolyChain 439 440 I: 441 for i := 0; i < len(pCh); { 442 pChNew = append(pChNew, pCh[i]) 443 for j := i + 1; j < len(pCh); j++ { 444 if pCh[j] != pCh[i] { 445 i = j 446 continue I 447 } 448 } 449 break 450 } 451 452 return pChNew 453 } 454 455 func (pCh PolyChain) ShortenWithTheSamePoints(maxDistanceToBeJoined float64) PolyChain { 456 for i := 0; i <= len(pCh)-3; i++ { 457 for j := len(pCh) - 1; j >= i+2; j-- { 458 if pCh[i].DistanceTo(pCh[j]) <= maxDistanceToBeJoined { 459 for k := i + 1; k < j; k++ { 460 pCh[k] = pCh[i] 461 } 462 break 463 } 464 } 465 } 466 467 return pCh 468 } 469 470 func (pCh PolyChain) ShortString() string { 471 if len(pCh) < 1 { 472 return "[]" 473 } 474 var pChStr string 475 for _, p := range pCh { 476 pChStr += fmt.Sprintf(" {%.1f %.1f}", p.X, p.Y) 477 } 478 479 return "[" + pChStr[1:] + "]" 480 }