tile38/vendor/github.com/tidwall/geojson/geometry/segment.go

236 lines
5.1 KiB
Go
Raw Permalink Normal View History

// Copyright 2018 Joshua J Baker. All rights reserved.
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
package geometry
import (
"math"
)
// Segment is a two point line
type Segment struct {
A, B Point
}
// Move a segment by delta
func (seg Segment) Move(deltaX, deltaY float64) Segment {
return Segment{
A: Point{X: seg.A.X + deltaX, Y: seg.A.Y + deltaY},
B: Point{X: seg.B.X + deltaX, Y: seg.B.Y + deltaY},
}
}
// Rect is the outer boundaries of the segment.
func (seg Segment) Rect() Rect {
var rect Rect
rect.Min = seg.A
rect.Max = seg.B
if rect.Min.X > rect.Max.X {
rect.Min.X, rect.Max.X = rect.Max.X, rect.Min.X
}
if rect.Min.Y > rect.Max.Y {
rect.Min.Y, rect.Max.Y = rect.Max.Y, rect.Min.Y
}
return rect
}
// CollinearPoint ...
func (seg Segment) CollinearPoint(point Point) bool {
cmpx, cmpy := point.X-seg.A.X, point.Y-seg.A.Y
rx, ry := seg.B.X-seg.A.X, seg.B.Y-seg.A.Y
cmpxr := cmpx*ry - cmpy*rx
return cmpxr == 0
}
// ContainsPoint ...
func (seg Segment) ContainsPoint(point Point) bool {
return seg.Raycast(point).On
}
// // Angle ...
// func (seg Segment) Angle() float64 {
// return math.Atan2(seg.B.Y-seg.A.Y, seg.B.X-seg.A.X)
// }
// RaycastResult holds the results of the Raycast operation
type RaycastResult struct {
In bool // point on the left
On bool // point is directly on top of
}
// Raycast performs the raycast operation
func (seg Segment) Raycast(point Point) RaycastResult {
p, a, b := point, seg.A, seg.B
// make sure that the point is inside the segment bounds
if a.Y < b.Y && (p.Y < a.Y || p.Y > b.Y) {
return RaycastResult{false, false}
} else if a.Y > b.Y && (p.Y < b.Y || p.Y > a.Y) {
return RaycastResult{false, false}
}
// test if point is in on the segment
if a.Y == b.Y {
if a.X == b.X {
if p == a {
return RaycastResult{false, true}
}
return RaycastResult{false, false}
}
if p.Y == b.Y {
// horizontal segment
// check if the point in on the line
if a.X < b.X {
if p.X >= a.X && p.X <= b.X {
return RaycastResult{false, true}
}
} else {
if p.X >= b.X && p.X <= a.X {
return RaycastResult{false, true}
}
}
}
}
if a.X == b.X && p.X == b.X {
// vertical segment
// check if the point in on the line
if a.Y < b.Y {
if p.Y >= a.Y && p.Y <= b.Y {
return RaycastResult{false, true}
}
} else {
if p.Y >= b.Y && p.Y <= a.Y {
return RaycastResult{false, true}
}
}
}
if (p.X-a.X)/(b.X-a.X) == (p.Y-a.Y)/(b.Y-a.Y) {
return RaycastResult{false, true}
}
// do the actual raycast here.
for p.Y == a.Y || p.Y == b.Y {
p.Y = math.Nextafter(p.Y, math.Inf(1))
}
if a.Y < b.Y {
if p.Y < a.Y || p.Y > b.Y {
return RaycastResult{false, false}
}
} else {
if p.Y < b.Y || p.Y > a.Y {
return RaycastResult{false, false}
}
}
if a.X > b.X {
if p.X >= a.X {
return RaycastResult{false, false}
}
if p.X <= b.X {
return RaycastResult{true, false}
}
} else {
if p.X >= b.X {
return RaycastResult{false, false}
}
if p.X <= a.X {
return RaycastResult{true, false}
}
}
if a.Y < b.Y {
if (p.Y-a.Y)/(p.X-a.X) >= (b.Y-a.Y)/(b.X-a.X) {
return RaycastResult{true, false}
}
} else {
if (p.Y-b.Y)/(p.X-b.X) >= (a.Y-b.Y)/(a.X-b.X) {
return RaycastResult{true, false}
}
}
return RaycastResult{false, false}
}
// IntersectsSegment detects if segment intersects with other segement
func (seg Segment) IntersectsSegment(other Segment) bool {
a, b, c, d := seg.A, seg.B, other.A, other.B
// do the bounding boxes intersect?
if a.Y > b.Y {
if c.Y > d.Y {
if b.Y > c.Y || a.Y < d.Y {
return false
}
} else {
if b.Y > d.Y || a.Y < c.Y {
return false
}
}
} else {
if c.Y > d.Y {
if a.Y > c.Y || b.Y < d.Y {
return false
}
} else {
if a.Y > d.Y || b.Y < c.Y {
return false
}
}
}
if a.X > b.X {
if c.X > d.X {
if b.X > c.X || a.X < d.X {
return false
}
} else {
if b.X > d.X || a.X < c.X {
return false
}
}
} else {
if c.X > d.X {
if a.X > c.X || b.X < d.X {
return false
}
} else {
if a.X > d.X || b.X < c.X {
return false
}
}
}
if seg.A == other.A || seg.A == other.B ||
seg.B == other.A || seg.B == other.B {
return true
}
// the following code is from http://ideone.com/PnPJgb
cmpx, cmpy := c.X-a.X, c.Y-a.Y
rx, ry := b.X-a.X, b.Y-a.Y
cmpxr := cmpx*ry - cmpy*rx
if cmpxr == 0 {
// Lines are collinear, and so intersect if they have any overlap
if !(((c.X-a.X <= 0) != (c.X-b.X <= 0)) ||
((c.Y-a.Y <= 0) != (c.Y-b.Y <= 0))) {
return seg.Raycast(other.A).On || seg.Raycast(other.B).On
//return false
}
return true
}
sx, sy := d.X-c.X, d.Y-c.Y
cmpxs := cmpx*sy - cmpy*sx
rxs := rx*sy - ry*sx
if rxs == 0 {
return false // segments are parallel.
}
rxsr := 1 / rxs
t := cmpxs * rxsr
u := cmpxr * rxsr
if !((t >= 0) && (t <= 1) && (u >= 0) && (u <= 1)) {
return false
}
return true
}
// ContainsSegment returns true if segment contains other segment
func (seg Segment) ContainsSegment(other Segment) bool {
return seg.Raycast(other.A).On && seg.Raycast(other.B).On
}