github.com/richardwilkes/toolbox@v1.121.0/xmath/geom/visibility/visibility.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 visibility
    11  
    12  import (
    13  	"cmp"
    14  	"slices"
    15  
    16  	"github.com/richardwilkes/toolbox/collection/quadtree"
    17  	"github.com/richardwilkes/toolbox/xmath"
    18  	"github.com/richardwilkes/toolbox/xmath/geom"
    19  	"github.com/richardwilkes/toolbox/xmath/geom/poly"
    20  	"golang.org/x/exp/constraints"
    21  )
    22  
    23  const epsilon = 0.01
    24  
    25  // Visibility holds state for computing a visibility polygon.
    26  type Visibility[T constraints.Float] struct {
    27  	top      T
    28  	left     T
    29  	bottom   T
    30  	right    T
    31  	segments []Segment[T]
    32  }
    33  
    34  // New creates a Visibility object. If the obstructions do not intersect each other, pass in true for hasNoIntersections
    35  // to eliminate the costly pass to break the segments up into smaller parts.
    36  func New[T constraints.Float](bounds geom.Rect[T], obstructions []Segment[T], hasNoIntersections bool) *Visibility[T] {
    37  	v := &Visibility[T]{
    38  		top:      bounds.Y,
    39  		left:     bounds.X,
    40  		bottom:   bounds.Bottom() - 1,
    41  		right:    bounds.Right() - 1,
    42  		segments: make([]Segment[T], len(obstructions)),
    43  	}
    44  	copy(v.segments, obstructions)
    45  	if !hasNoIntersections {
    46  		v.breakIntersections()
    47  	}
    48  	return v
    49  }
    50  
    51  // SetViewPoint sets a view point and generates a polygon with the unobstructed visible area.
    52  func (v *Visibility[T]) SetViewPoint(viewPt geom.Point[T]) poly.Polygon[T] {
    53  	// If the view point is not within the bounding area, there is no visible area
    54  	if !v.inViewport(viewPt) {
    55  		return nil
    56  	}
    57  
    58  	// Generate a revised segment list by clipping the segments against the viewport and throwing out any that aren't
    59  	// within the viewport.
    60  	segments := make([]Segment[T], 0, len(v.segments)*2)
    61  	viewport := []geom.Point[T]{
    62  		geom.NewPoint(v.left, v.top),
    63  		geom.NewPoint(v.right, v.top),
    64  		geom.NewPoint(v.right, v.bottom),
    65  		geom.NewPoint(v.left, v.bottom),
    66  	}
    67  	for _, si := range v.segments {
    68  		if (si.Start.X < v.left && si.End.X < v.left) ||
    69  			(si.Start.Y < v.top && si.End.Y < v.top) ||
    70  			(si.Start.X > v.right && si.End.X > v.right) ||
    71  			(si.Start.Y > v.bottom && si.End.Y > v.bottom) {
    72  			continue
    73  		}
    74  		intersections := make([]geom.Point[T], 0, len(viewport))
    75  		for j := range viewport {
    76  			k := (j + 1) % len(viewport)
    77  			if hasIntersection(si.Start, si.End, viewport[j], viewport[k]) {
    78  				pt, intersects := intersectLines(si.Start, si.End, viewport[j], viewport[k])
    79  				if intersects && !mostlyEqual(pt, si.Start) && !mostlyEqual(pt, si.End) {
    80  					intersections = append(intersections, pt)
    81  				}
    82  			}
    83  		}
    84  		segments = v.collectSegments(si, intersections, segments, true)
    85  	}
    86  
    87  	// Add the viewport bounds to the segment list
    88  	const eps = 10 * epsilon
    89  	topLeft := geom.Point[T]{X: v.left - eps, Y: v.top - eps}
    90  	topRight := geom.Point[T]{X: v.right + eps, Y: v.top - eps}
    91  	bottomLeft := geom.Point[T]{X: v.left - eps, Y: v.bottom + eps}
    92  	bottomRight := geom.Point[T]{X: v.right + eps, Y: v.bottom + eps}
    93  	segments = append(segments,
    94  		Segment[T]{Start: topLeft, End: topRight},
    95  		Segment[T]{Start: topRight, End: bottomRight},
    96  		Segment[T]{Start: bottomRight, End: bottomLeft},
    97  		Segment[T]{Start: bottomLeft, End: topLeft},
    98  	)
    99  
   100  	return v.computePolygon(viewPt, segments)
   101  }
   102  
   103  func (v *Visibility[T]) computePolygon(viewPt geom.Point[T], segments []Segment[T]) poly.Polygon[T] {
   104  	// Sweep through the points to generate the visibility contour
   105  	sorted := sortPoints(viewPt, segments)
   106  	mapper := &array{data: make([]int, len(segments))}
   107  	for i := range mapper.data {
   108  		mapper.data[i] = -1
   109  	}
   110  	heap := &array{}
   111  	start := geom.Point[T]{X: viewPt.X + 1, Y: viewPt.Y}
   112  	for i := range segments {
   113  		a1 := angle(segments[i].Start, viewPt)
   114  		a2 := angle(segments[i].End, viewPt)
   115  		if (a1 >= -180 && a1 <= 0 && a2 <= 180 && a2 >= 0 && a2-a1 > 180) ||
   116  			(a2 >= -180 && a2 <= 0 && a1 <= 180 && a1 >= 0 && a1-a2 > 180) {
   117  			insert(i, heap, mapper, segments, viewPt, start)
   118  		}
   119  	}
   120  	contour := make(poly.Contour[T], 0, len(sorted)*2)
   121  	i := 0
   122  	for i < len(sorted) {
   123  		extend := false
   124  		shorten := false
   125  		orig := i
   126  		vertex := sorted[i].pt(segments)
   127  		oldSeg := heap.elem(0)
   128  		for {
   129  			if mapper.elem(sorted[i].segmentIndex) != -1 {
   130  				if sorted[i].segmentIndex == oldSeg {
   131  					extend = true
   132  					vertex = sorted[i].pt(segments)
   133  				}
   134  				remove(mapper.elem(sorted[i].segmentIndex), heap, mapper, segments, viewPt, vertex)
   135  			} else {
   136  				insert(sorted[i].segmentIndex, heap, mapper, segments, viewPt, vertex)
   137  				if heap.elem(0) != oldSeg {
   138  					shorten = true
   139  				}
   140  			}
   141  			i++
   142  			if i == len(sorted) || sorted[i].angle >= sorted[orig].angle+epsilon {
   143  				break
   144  			}
   145  		}
   146  		if extend {
   147  			contour = append(contour, geom.Point[T]{X: vertex.X, Y: vertex.Y})
   148  			s := segments[heap.elem(0)]
   149  			if cur, intersects := intersectLines(s.Start, s.End, viewPt, vertex); intersects && !mostlyEqual(cur, vertex) {
   150  				contour = append(contour, geom.Point[T]{X: cur.X, Y: cur.Y})
   151  			}
   152  		} else if shorten {
   153  			s := segments[oldSeg]
   154  			if cur, intersects := intersectLines(s.Start, s.End, viewPt, vertex); intersects {
   155  				contour = append(contour, geom.Point[T]{X: cur.X, Y: cur.Y})
   156  			}
   157  			s = segments[heap.elem(0)]
   158  			if cur, intersects := intersectLines(s.Start, s.End, viewPt, vertex); intersects {
   159  				contour = append(contour, geom.Point[T]{X: cur.X, Y: cur.Y})
   160  			}
   161  		}
   162  	}
   163  	if len(contour) == 0 {
   164  		return nil
   165  	}
   166  	return poly.Polygon[T]{contour}
   167  }
   168  
   169  func (v *Visibility[T]) inViewport(pt geom.Point[T]) bool {
   170  	return pt.X >= v.left-epsilon &&
   171  		pt.Y >= v.top-epsilon &&
   172  		pt.X <= v.right+epsilon &&
   173  		pt.Y <= v.bottom+epsilon
   174  }
   175  
   176  func (v *Visibility[T]) breakIntersections() {
   177  	var qt quadtree.QuadTree[T, Segment[T]]
   178  	for _, si := range v.segments {
   179  		qt.Insert(si)
   180  	}
   181  	segments := make([]Segment[T], 0, len(v.segments)*2)
   182  	for _, si := range v.segments {
   183  		var intersections []geom.Point[T]
   184  		for _, sj := range qt.FindIntersects(si.Bounds()) {
   185  			if si == sj {
   186  				continue
   187  			}
   188  			if hasIntersection(si.Start, si.End, sj.Start, sj.End) {
   189  				pt, intersects := intersectLines(si.Start, si.End, sj.Start, sj.End)
   190  				if intersects && !mostlyEqual(pt, si.Start) && !mostlyEqual(pt, si.End) {
   191  					intersections = append(intersections, pt)
   192  				}
   193  			}
   194  		}
   195  		segments = v.collectSegments(si, intersections, segments, false)
   196  	}
   197  	v.segments = slices.Clip(segments)
   198  }
   199  
   200  func (v *Visibility[T]) collectSegments(s Segment[T], intersections []geom.Point[T], segments []Segment[T], onlyInViewPort bool) []Segment[T] {
   201  	start := s.Start
   202  	for len(intersections) > 0 {
   203  		endIndex := 0
   204  		endDis := distance(start, intersections[0])
   205  		for i := 1; i < len(intersections); i++ {
   206  			if dis := distance(start, intersections[i]); dis < endDis {
   207  				endDis = dis
   208  				endIndex = i
   209  			}
   210  		}
   211  		if !onlyInViewPort || (v.inViewport(start) && v.inViewport(intersections[endIndex])) {
   212  			segments = append(segments, Segment[T]{Start: start, End: intersections[endIndex]})
   213  		}
   214  		start = intersections[endIndex]
   215  		intersections = slices.Delete(intersections, endIndex, endIndex+1)
   216  	}
   217  	if !onlyInViewPort || (v.inViewport(start) && v.inViewport(s.End)) {
   218  		segments = append(segments, Segment[T]{Start: start, End: s.End})
   219  	}
   220  	return segments
   221  }
   222  
   223  func mostlyEqual[T constraints.Float](a, b geom.Point[T]) bool {
   224  	return a.EqualWithin(b, epsilon)
   225  }
   226  
   227  func remove[T constraints.Float](index int, heap, mapper *array, segments []Segment[T], position, destination geom.Point[T]) {
   228  	mapper.set(heap.elem(index), -1)
   229  	if index == heap.size()-1 {
   230  		heap.pop()
   231  		return
   232  	}
   233  	heap.set(index, heap.pop())
   234  	mapper.set(heap.elem(index), index)
   235  	cur := index
   236  	parent := (cur - 1) / 2
   237  	if cur != 0 && lessThan(heap.elem(cur), heap.elem(parent), segments, position, destination) {
   238  		for cur > 0 {
   239  			parent = (cur - 1) / 2
   240  			parentElem := heap.elem(parent)
   241  			curElem := heap.elem(cur)
   242  			if !lessThan(curElem, parentElem, segments, position, destination) {
   243  				break
   244  			}
   245  			mapper.set(parentElem, cur)
   246  			mapper.set(curElem, parent)
   247  			heap.set(cur, parentElem)
   248  			heap.set(parent, curElem)
   249  			cur = parent
   250  		}
   251  		return
   252  	}
   253  loop:
   254  	for {
   255  		left := 2*cur + 1
   256  		right := left + 1
   257  		switch {
   258  		case left < heap.size() && lessThan(heap.elem(left), heap.elem(cur), segments, position, destination) &&
   259  			(right == heap.size() || lessThan(heap.elem(left), heap.elem(right), segments, position, destination)):
   260  			leftElem := heap.elem(left)
   261  			curElem := heap.elem(cur)
   262  			mapper.set(leftElem, cur)
   263  			mapper.set(curElem, left)
   264  			heap.set(left, curElem)
   265  			heap.set(cur, leftElem)
   266  			cur = left
   267  		case right < heap.size() && lessThan(heap.elem(right), heap.elem(cur), segments, position, destination):
   268  			rightElem := heap.elem(right)
   269  			curElem := heap.elem(cur)
   270  			mapper.set(rightElem, cur)
   271  			mapper.set(curElem, right)
   272  			heap.set(right, curElem)
   273  			heap.set(cur, rightElem)
   274  			cur = right
   275  		default:
   276  			break loop
   277  		}
   278  	}
   279  }
   280  
   281  func insert[T constraints.Float](index int, heap, mapper *array, segments []Segment[T], position, destination geom.Point[T]) {
   282  	if _, intersects := intersectLines(segments[index].Start, segments[index].End, position, destination); !intersects {
   283  		return
   284  	}
   285  	cur := heap.size()
   286  	heap.push(index)
   287  	mapper.set(index, cur)
   288  	for cur > 0 {
   289  		parent := (cur - 1) / 2
   290  		parentElem := heap.elem(parent)
   291  		curElem := heap.elem(cur)
   292  		if !lessThan(curElem, parentElem, segments, position, destination) {
   293  			break
   294  		}
   295  		mapper.set(parentElem, cur)
   296  		mapper.set(curElem, parent)
   297  		heap.set(cur, parentElem)
   298  		heap.set(parent, curElem)
   299  		cur = parent
   300  	}
   301  }
   302  
   303  func lessThan[T constraints.Float](index1, index2 int, segments []Segment[T], position, destination geom.Point[T]) bool {
   304  	pt1, intersects1 := intersectLines(segments[index1].Start, segments[index1].End, position, destination)
   305  	if !intersects1 {
   306  		return false
   307  	}
   308  	pt2, intersects2 := intersectLines(segments[index2].Start, segments[index2].End, position, destination)
   309  	if !intersects2 {
   310  		return false
   311  	}
   312  	if !mostlyEqual(pt1, pt2) {
   313  		d1 := distance(pt1, position)
   314  		d2 := distance(pt2, position)
   315  		return d1 < d2
   316  	}
   317  	var a1 T
   318  	if mostlyEqual(pt1, segments[index1].Start) {
   319  		a1 = angle2(segments[index1].End, pt1, position)
   320  	} else {
   321  		a1 = angle2(segments[index1].Start, pt1, position)
   322  	}
   323  	var a2 T
   324  	if mostlyEqual(pt2, segments[index2].Start) {
   325  		a2 = angle2(segments[index2].End, pt2, position)
   326  	} else {
   327  		a2 = angle2(segments[index2].Start, pt2, position)
   328  	}
   329  	if a1 < 180 {
   330  		if a2 > 180 {
   331  			return true
   332  		}
   333  		return a2 < a1
   334  	}
   335  	return a1 < a2
   336  }
   337  
   338  func sortPoints[T constraints.Float](position geom.Point[T], segments []Segment[T]) []endPoint[T] {
   339  	points := make([]endPoint[T], len(segments)*2)
   340  	pos := 0
   341  	for i, s := range segments {
   342  		points[pos].segmentIndex = i
   343  		points[pos].angle = angle(s.Start, position)
   344  		points[pos].start = true
   345  		pos++
   346  		points[pos].segmentIndex = i
   347  		points[pos].angle = angle(s.End, position)
   348  		points[pos].start = false
   349  		pos++
   350  	}
   351  	slices.SortFunc(points, func(a, b endPoint[T]) int {
   352  		if result := cmp.Compare(a.angle, b.angle); result != 0 {
   353  			return result
   354  		}
   355  		if result := cmp.Compare(distance(a.pt(segments), position), distance(b.pt(segments), position)); result != 0 {
   356  			return result
   357  		}
   358  		if a.start == b.start {
   359  			return 0
   360  		}
   361  		if a.start {
   362  			return 1
   363  		}
   364  		return -1
   365  	})
   366  	return points
   367  }
   368  
   369  func angle2[T constraints.Float](a, b, c geom.Point[T]) T {
   370  	a1 := angle(a, b)
   371  	a2 := angle(b, c)
   372  	a3 := a1 - a2
   373  	if a3 < 0 {
   374  		a3 += 360
   375  	}
   376  	if a3 > 360 {
   377  		a3 -= 360
   378  	}
   379  	return a3
   380  }
   381  
   382  func angle[T constraints.Float](a, b geom.Point[T]) T {
   383  	return xmath.Atan2(b.Y-a.Y, b.X-a.X) * 180 / xmath.Pi
   384  }
   385  
   386  func distance[T constraints.Float](a, b geom.Point[T]) T {
   387  	dx := a.X - b.X
   388  	dy := a.Y - b.Y
   389  	return dx*dx + dy*dy
   390  }
   391  
   392  func intersectLines[T constraints.Float](s1, e1, s2, e2 geom.Point[T]) (geom.Point[T], bool) {
   393  	dbx := e2.X - s2.X
   394  	dby := e2.Y - s2.Y
   395  	dax := e1.X - s1.X
   396  	day := e1.Y - s1.Y
   397  	ub := dby*dax - dbx*day
   398  	if ub == 0 {
   399  		return geom.Point[T]{}, false
   400  	}
   401  	ua := (dbx*(s1.Y-s2.Y) - dby*(s1.X-s2.X)) / ub
   402  	return geom.Point[T]{X: s1.X + ua*dax, Y: s1.Y + ua*day}, true
   403  }
   404  
   405  func hasIntersection[T constraints.Float](s1, e1, s2, e2 geom.Point[T]) bool {
   406  	d1 := direction(s2, e2, s1)
   407  	d2 := direction(s2, e2, e1)
   408  	d3 := direction(s1, e1, s2)
   409  	d4 := direction(s1, e1, e2)
   410  	return (((d1 > 0 && d2 < 0) || (d1 < 0 && d2 > 0)) &&
   411  		((d3 > 0 && d4 < 0) || (d3 < 0 && d4 > 0))) ||
   412  		(d1 == 0 && onSegment(s2, e2, s1)) ||
   413  		(d2 == 0 && onSegment(s2, e2, e1)) ||
   414  		(d3 == 0 && onSegment(s1, e1, s2)) ||
   415  		(d4 == 0 && onSegment(s1, e1, e2))
   416  }
   417  
   418  func direction[T constraints.Float](a, b, c geom.Point[T]) int {
   419  	return cmp.Compare((c.X-a.X)*(b.Y-a.Y), (b.X-a.X)*(c.Y-a.Y))
   420  }
   421  
   422  func onSegment[T constraints.Float](a, b, c geom.Point[T]) bool {
   423  	return (a.X <= c.X || b.X <= c.X) &&
   424  		(c.X <= a.X || c.X <= b.X) &&
   425  		(a.Y <= c.Y || b.Y <= c.Y) &&
   426  		(c.Y <= a.Y || c.Y <= b.Y)
   427  }