Derek McQuay
71f3aede25
this will allow users to make sure that the rectangles they define really are rectangles
202 lines
5.1 KiB
Go
202 lines
5.1 KiB
Go
package rect
|
|
|
|
import (
|
|
"math"
|
|
"sort"
|
|
)
|
|
|
|
// Rectangle struct defines a plane figure with four straight sides
|
|
// and four right angles, which contains 4 vertixes points, P1 through P4
|
|
type Rectangle struct {
|
|
P1, P2, P3, P4 Point
|
|
}
|
|
|
|
// maximum error used for floating point math
|
|
var ɛ = 0.00001
|
|
|
|
// IsRect determins if the rectangle provided really is a rectangle, which
|
|
// by definition means a plane figure with four straight sides and four
|
|
// right angles.
|
|
func (r Rectangle) IsRect() bool {
|
|
// make sure they aren't all just the same point
|
|
if (r.P1.X == r.P2.X && r.P1.X == r.P3.X && r.P1.X == r.P4.X) &&
|
|
(r.P1.Y == r.P2.Y && r.P1.Y == r.P3.Y && r.P1.Y == r.P4.Y) {
|
|
return false
|
|
}
|
|
|
|
cx := (r.P1.X + r.P2.X + r.P3.X + r.P4.X) / 4.0
|
|
cy := (r.P1.Y + r.P2.Y + r.P3.Y + r.P4.Y) / 4.0
|
|
|
|
dd1 := math.Sqrt(math.Abs(cx-r.P1.X)) + math.Sqrt(math.Abs(cy-r.P1.Y))
|
|
dd2 := math.Sqrt(math.Abs(cx-r.P2.X)) + math.Sqrt(math.Abs(cy-r.P2.Y))
|
|
dd3 := math.Sqrt(math.Abs(cx-r.P3.X)) + math.Sqrt(math.Abs(cy-r.P3.Y))
|
|
dd4 := math.Sqrt(math.Abs(cx-r.P4.X)) + math.Sqrt(math.Abs(cy-r.P4.Y))
|
|
return math.Abs(dd1-dd2) < ɛ && math.Abs(dd1-dd3) < ɛ && math.Abs(dd1-dd4) < ɛ
|
|
}
|
|
|
|
// neighbors returns the two points that form the line which creates the right
|
|
// angle of the point passed in as a parameter.
|
|
func (r Rectangle) neighbors(p Point) (Point, Point) {
|
|
keys := []float64{distance(r.P1, p), distance(r.P2, p), distance(r.P3, p), distance(r.P4, p)}
|
|
sort.Float64s(keys)
|
|
n := []Point{}
|
|
d := distance(r.P1, p)
|
|
if math.Abs(keys[1]-d) < ɛ || math.Abs(keys[2]-d) < ɛ {
|
|
n = append(n, r.P1)
|
|
}
|
|
d = distance(r.P2, p)
|
|
if math.Abs(keys[1]-d) < ɛ || math.Abs(keys[2]-d) < ɛ {
|
|
n = append(n, r.P2)
|
|
}
|
|
d = distance(r.P3, p)
|
|
if math.Abs(keys[1]-d) < ɛ || math.Abs(keys[2]-d) < ɛ {
|
|
n = append(n, r.P3)
|
|
}
|
|
d = distance(r.P4, p)
|
|
if math.Abs(keys[1]-d) < ɛ || math.Abs(keys[2]-d) < ɛ {
|
|
n = append(n, r.P4)
|
|
}
|
|
return n[0], n[1]
|
|
}
|
|
|
|
// size returns the size of the Rectangle
|
|
func (r Rectangle) size() float64 {
|
|
n1, n2 := r.neighbors(r.P1)
|
|
return distance(r.P1, n1) * distance(r.P1, n2)
|
|
}
|
|
|
|
// inOrder returns a []Point, let us call pts, in which the four sides of the
|
|
// Rectangle can be defined as pts[0] -- pts[1], pts[0] -- pts[2],
|
|
// pts[3] -- pts[1], and pts[3] -- pts[2]
|
|
func (r Rectangle) inOrder() []Point {
|
|
n1, n2 := r.neighbors(r.P1)
|
|
across := &Point{}
|
|
if r.P2 != n1 || r.P2 != n2 {
|
|
across = &r.P2
|
|
}
|
|
if r.P3 != n1 || r.P3 != n2 {
|
|
across = &r.P3
|
|
}
|
|
if r.P4 != n1 || r.P4 != n2 {
|
|
across = &r.P4
|
|
}
|
|
return []Point{r.P1, n1, n2, *across}
|
|
}
|
|
|
|
// Adjacency detects whether two rectangles, r1, and r2, are adjacent.
|
|
// Adjacency is defined as the sharing of a side
|
|
func Adjacency(r1, r2 Rectangle) bool {
|
|
order1 := r1.inOrder()
|
|
order2 := r2.inOrder()
|
|
|
|
sides1 := []line{
|
|
line{order1[0], order1[1]},
|
|
line{order1[0], order1[2]},
|
|
line{order1[3], order1[1]},
|
|
line{order1[3], order1[2]},
|
|
}
|
|
sides2 := []line{
|
|
line{order2[0], order2[1]},
|
|
line{order2[0], order2[2]},
|
|
line{order2[3], order2[1]},
|
|
line{order2[3], order2[2]},
|
|
}
|
|
|
|
for _, i := range sides1 {
|
|
for _, j := range sides2 {
|
|
if lineOnLine(i, j) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func removeDuplicates(xs *[]Point) {
|
|
found := make(map[Point]bool)
|
|
j := 0
|
|
for i, x := range *xs {
|
|
if !found[x] {
|
|
found[x] = true
|
|
(*xs)[j] = (*xs)[i]
|
|
j++
|
|
}
|
|
}
|
|
*xs = (*xs)[:j]
|
|
}
|
|
|
|
// Intersection determine whether two rectangles, r1 and r2, have one or more
|
|
// intersecting lines and produce a result, []Point, identifying the points
|
|
// of intersection
|
|
func Intersection(r1, r2 Rectangle) []Point {
|
|
order1 := r1.inOrder()
|
|
order2 := r2.inOrder()
|
|
|
|
sides1 := []line{
|
|
line{order1[0], order1[1]},
|
|
line{order1[0], order1[2]},
|
|
line{order1[3], order1[1]},
|
|
line{order1[3], order1[2]},
|
|
}
|
|
sides2 := []line{
|
|
line{order2[0], order2[1]},
|
|
line{order2[0], order2[2]},
|
|
line{order2[3], order2[1]},
|
|
line{order2[3], order2[2]},
|
|
}
|
|
|
|
pts := []Point{}
|
|
for _, i := range sides1 {
|
|
for _, j := range sides2 {
|
|
p, err := lineIntersection(i, j)
|
|
if err == nil {
|
|
pts = append(pts, p)
|
|
}
|
|
}
|
|
}
|
|
|
|
removeDuplicates(&pts)
|
|
return pts
|
|
}
|
|
|
|
// sumOfTri determines if the sum of the areas of the triangles created from
|
|
// point p to two neighboring vertices of a side of the Rectangle, r, add up
|
|
// to the same size as the Rectangle, r. This is one way to determine if
|
|
// a point is inside of a Rectangle.
|
|
func sumOfTri(r Rectangle, p Point) bool {
|
|
n1, n2 := r.neighbors(r.P1)
|
|
x1, x2 := Point{}, Point{}
|
|
across := &Point{}
|
|
if r.P2 != n1 || r.P2 != n2 {
|
|
across = &r.P2
|
|
x1, x2 = r.neighbors(r.P2)
|
|
}
|
|
if r.P3 != n1 || r.P3 != n2 {
|
|
across = &r.P3
|
|
x1, x2 = r.neighbors(r.P3)
|
|
}
|
|
if r.P4 != n1 || r.P4 != n2 {
|
|
across = &r.P4
|
|
x1, x2 = r.neighbors(r.P4)
|
|
}
|
|
sumTri := sizeTriangle(r.P1, n1, p) +
|
|
sizeTriangle(r.P1, n2, p) +
|
|
sizeTriangle(*across, x1, p) +
|
|
sizeTriangle(*across, x2, p)
|
|
if math.Abs(r.size()-sumTri) < ɛ {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Containment returns whether r2 is contained inside of r1
|
|
func Containment(r1, r2 Rectangle) bool {
|
|
if r1.size() <= r2.size() {
|
|
return false
|
|
}
|
|
if sumOfTri(r1, r2.P1) && sumOfTri(r1, r2.P2) && sumOfTri(r1, r2.P3) && sumOfTri(r1, r2.P4) {
|
|
return true
|
|
}
|
|
return false
|
|
}
|