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  }