commit
a6c8b92517
423
geometry.go
423
geometry.go
|
@ -181,6 +181,268 @@ func Lerp(a, b Vec, t float64) Vec {
|
||||||
return a.Scaled(1 - t).Add(b.Scaled(t))
|
return a.Scaled(1 - t).Add(b.Scaled(t))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Line is a 2D line segment, between points A and B.
|
||||||
|
type Line struct {
|
||||||
|
A, B Vec
|
||||||
|
}
|
||||||
|
|
||||||
|
// L creates and returns a new Line.
|
||||||
|
func L(from, to Vec) Line {
|
||||||
|
return Line{
|
||||||
|
A: from,
|
||||||
|
B: to,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bounds returns the lines bounding box. This is in the form of a normalized Rect.
|
||||||
|
func (l Line) Bounds() Rect {
|
||||||
|
return R(l.A.X, l.A.Y, l.B.X, l.B.Y).Norm()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Center will return the point at center of the line; that is, the point equidistant from either end.
|
||||||
|
func (l Line) Center() Vec {
|
||||||
|
return l.A.Add(l.A.To(l.B).Scaled(0.5))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Closest will return the point on the line which is closest to the Vec provided.
|
||||||
|
func (l Line) Closest(v Vec) Vec {
|
||||||
|
// between is a helper function which determines whether x is greater than min(a, b) and less than max(a, b)
|
||||||
|
between := func(a, b, x float64) bool {
|
||||||
|
min := math.Min(a, b)
|
||||||
|
max := math.Max(a, b)
|
||||||
|
return min < x && x < max
|
||||||
|
}
|
||||||
|
|
||||||
|
// Closest point will be on a line which perpendicular to this line.
|
||||||
|
// If and only if the infinite perpendicular line intersects the segment.
|
||||||
|
m, b := l.Formula()
|
||||||
|
|
||||||
|
// Account for horizontal lines
|
||||||
|
if m == 0 {
|
||||||
|
x := v.X
|
||||||
|
y := l.A.Y
|
||||||
|
|
||||||
|
// check if the X coordinate of v is on the line
|
||||||
|
if between(l.A.X, l.B.X, v.X) {
|
||||||
|
return V(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise get the closest endpoint
|
||||||
|
if l.A.To(v).Len() < l.B.To(v).Len() {
|
||||||
|
return l.A
|
||||||
|
}
|
||||||
|
return l.B
|
||||||
|
}
|
||||||
|
|
||||||
|
// Account for vertical lines
|
||||||
|
if math.IsInf(math.Abs(m), 1) {
|
||||||
|
x := l.A.X
|
||||||
|
y := v.Y
|
||||||
|
|
||||||
|
// check if the Y coordinate of v is on the line
|
||||||
|
if between(l.A.Y, l.B.Y, v.Y) {
|
||||||
|
return V(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise get the closest endpoint
|
||||||
|
if l.A.To(v).Len() < l.B.To(v).Len() {
|
||||||
|
return l.A
|
||||||
|
}
|
||||||
|
return l.B
|
||||||
|
}
|
||||||
|
|
||||||
|
perpendicularM := -1 / m
|
||||||
|
perpendicularB := v.Y - (perpendicularM * v.X)
|
||||||
|
|
||||||
|
// Coordinates of intersect (of infinite lines)
|
||||||
|
x := (perpendicularB - b) / (m - perpendicularM)
|
||||||
|
y := m*x + b
|
||||||
|
|
||||||
|
// Check if the point lies between the x and y bounds of the segment
|
||||||
|
if !between(l.A.X, l.B.X, x) && !between(l.A.Y, l.B.Y, y) {
|
||||||
|
// Not within bounding box
|
||||||
|
toStart := v.To(l.A)
|
||||||
|
toEnd := v.To(l.B)
|
||||||
|
|
||||||
|
if toStart.Len() < toEnd.Len() {
|
||||||
|
return l.A
|
||||||
|
}
|
||||||
|
return l.B
|
||||||
|
}
|
||||||
|
|
||||||
|
return V(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contains returns whether the provided Vec lies on the line.
|
||||||
|
func (l Line) Contains(v Vec) bool {
|
||||||
|
return l.Closest(v) == v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Formula will return the values that represent the line in the formula: y = mx + b
|
||||||
|
// This function will return math.Inf+, math.Inf- for a vertical line.
|
||||||
|
func (l Line) Formula() (m, b float64) {
|
||||||
|
// Account for horizontal lines
|
||||||
|
if l.B.Y == l.A.Y {
|
||||||
|
return 0, l.A.Y
|
||||||
|
}
|
||||||
|
|
||||||
|
m = (l.B.Y - l.A.Y) / (l.B.X - l.A.X)
|
||||||
|
b = l.A.Y - (m * l.A.X)
|
||||||
|
|
||||||
|
return m, b
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intersect will return the point of intersection for the two line segments. If the line segments do not intersect,
|
||||||
|
// this function will return the zero-vector and false.
|
||||||
|
func (l Line) Intersect(k Line) (Vec, bool) {
|
||||||
|
// Check if the lines are parallel
|
||||||
|
lDir := l.A.To(l.B)
|
||||||
|
kDir := k.A.To(k.B)
|
||||||
|
if lDir.X == kDir.X && lDir.Y == kDir.Y {
|
||||||
|
return ZV, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// The lines intersect - but potentially not within the line segments.
|
||||||
|
// Get the intersection point for the lines if they were infinitely long, check if the point exists on both of the
|
||||||
|
// segments
|
||||||
|
lm, lb := l.Formula()
|
||||||
|
km, kb := k.Formula()
|
||||||
|
|
||||||
|
// Account for vertical lines
|
||||||
|
if math.IsInf(math.Abs(lm), 1) && math.IsInf(math.Abs(km), 1) {
|
||||||
|
// Both vertical, therefore parallel
|
||||||
|
return ZV, false
|
||||||
|
}
|
||||||
|
|
||||||
|
var x, y float64
|
||||||
|
|
||||||
|
if math.IsInf(math.Abs(lm), 1) || math.IsInf(math.Abs(km), 1) {
|
||||||
|
// One line is vertical
|
||||||
|
intersectM := lm
|
||||||
|
intersectB := lb
|
||||||
|
verticalLine := k
|
||||||
|
|
||||||
|
if math.IsInf(math.Abs(lm), 1) {
|
||||||
|
intersectM = km
|
||||||
|
intersectB = kb
|
||||||
|
verticalLine = l
|
||||||
|
}
|
||||||
|
|
||||||
|
y = intersectM*verticalLine.A.X + intersectB
|
||||||
|
x = verticalLine.A.X
|
||||||
|
} else {
|
||||||
|
// Coordinates of intersect
|
||||||
|
x = (kb - lb) / (lm - km)
|
||||||
|
y = lm*x + lb
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.Contains(V(x, y)) && k.Contains(V(x, y)) {
|
||||||
|
// The intersect point is on both line segments, they intersect.
|
||||||
|
return V(x, y), true
|
||||||
|
}
|
||||||
|
|
||||||
|
return ZV, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntersectCircle will return the shortest Vec such that moving the Line by that Vec will cause the Line and Circle
|
||||||
|
// to no longer intesect. If they do not intersect at all, this function will return a zero-vector.
|
||||||
|
func (l Line) IntersectCircle(c Circle) Vec {
|
||||||
|
// Get the point on the line closest to the center of the circle.
|
||||||
|
closest := l.Closest(c.Center)
|
||||||
|
cirToClosest := c.Center.To(closest)
|
||||||
|
|
||||||
|
if cirToClosest.Len() >= c.Radius {
|
||||||
|
return ZV
|
||||||
|
}
|
||||||
|
|
||||||
|
return cirToClosest.Scaled(cirToClosest.Len() - c.Radius)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntersectRect will return the shortest Vec such that moving the Line by that Vec will cause the Line and Rect to
|
||||||
|
// no longer intesect. If they do not intersect at all, this function will return a zero-vector.
|
||||||
|
func (l Line) IntersectRect(r Rect) Vec {
|
||||||
|
// Check if either end of the line segment are within the rectangle
|
||||||
|
if r.Contains(l.A) || r.Contains(l.B) {
|
||||||
|
// Use the Rect.Intersect to get minimal return value
|
||||||
|
rIntersect := l.Bounds().Intersect(r)
|
||||||
|
if rIntersect.H() > rIntersect.W() {
|
||||||
|
// Go vertical
|
||||||
|
return V(0, rIntersect.H())
|
||||||
|
}
|
||||||
|
return V(rIntersect.W(), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if any of the rectangles' edges intersect with this line.
|
||||||
|
for _, edge := range r.Edges() {
|
||||||
|
if _, ok := l.Intersect(edge); ok {
|
||||||
|
// Get the closest points on the line to each corner, where:
|
||||||
|
// - the point is contained by the rectangle
|
||||||
|
// - the point is not the corner itself
|
||||||
|
corners := r.Vertices()
|
||||||
|
closest := ZV
|
||||||
|
closestCorner := corners[0]
|
||||||
|
for _, c := range corners {
|
||||||
|
cc := l.Closest(c)
|
||||||
|
if closest == ZV || (closest.Len() > cc.Len() && r.Contains(cc)) {
|
||||||
|
closest = cc
|
||||||
|
closestCorner = c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return closest.To(closestCorner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No intersect
|
||||||
|
return ZV
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the length of the line segment.
|
||||||
|
func (l Line) Len() float64 {
|
||||||
|
return l.A.To(l.B).Len()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Moved will return a line moved by the delta Vec provided.
|
||||||
|
func (l Line) Moved(delta Vec) Line {
|
||||||
|
return Line{
|
||||||
|
A: l.A.Add(delta),
|
||||||
|
B: l.B.Add(delta),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rotated will rotate the line around the provided Vec.
|
||||||
|
func (l Line) Rotated(around Vec, angle float64) Line {
|
||||||
|
// Move the line so we can use `Vec.Rotated`
|
||||||
|
lineShifted := l.Moved(around.Scaled(-1))
|
||||||
|
|
||||||
|
lineRotated := Line{
|
||||||
|
A: lineShifted.A.Rotated(angle),
|
||||||
|
B: lineShifted.B.Rotated(angle),
|
||||||
|
}
|
||||||
|
|
||||||
|
return lineRotated.Moved(around)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scaled will return the line scaled around the center point.
|
||||||
|
func (l Line) Scaled(scale float64) Line {
|
||||||
|
return l.ScaledXY(l.Center(), scale)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScaledXY will return the line scaled around the Vec provided.
|
||||||
|
func (l Line) ScaledXY(around Vec, scale float64) Line {
|
||||||
|
toA := around.To(l.A).Scaled(scale)
|
||||||
|
toB := around.To(l.B).Scaled(scale)
|
||||||
|
|
||||||
|
return Line{
|
||||||
|
A: around.Add(toA),
|
||||||
|
B: around.Add(toB),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Line) String() string {
|
||||||
|
return fmt.Sprintf("Line(%v, %v)", l.A, l.B)
|
||||||
|
}
|
||||||
|
|
||||||
// Rect is a 2D rectangle aligned with the axes of the coordinate system. It is defined by two
|
// Rect is a 2D rectangle aligned with the axes of the coordinate system. It is defined by two
|
||||||
// points, Min and Max.
|
// points, Min and Max.
|
||||||
//
|
//
|
||||||
|
@ -243,6 +505,18 @@ func (r Rect) Area() float64 {
|
||||||
return r.W() * r.H()
|
return r.W() * r.H()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Edges will return the four lines which make up the edges of the rectangle.
|
||||||
|
func (r Rect) Edges() [4]Line {
|
||||||
|
corners := r.Vertices()
|
||||||
|
|
||||||
|
return [4]Line{
|
||||||
|
{A: corners[0], B: corners[1]},
|
||||||
|
{A: corners[1], B: corners[2]},
|
||||||
|
{A: corners[2], B: corners[3]},
|
||||||
|
{A: corners[3], B: corners[0]},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Center returns the position of the center of the Rect.
|
// Center returns the position of the center of the Rect.
|
||||||
func (r Rect) Center() Vec {
|
func (r Rect) Center() Vec {
|
||||||
return Lerp(r.Min, r.Max, 0.5)
|
return Lerp(r.Min, r.Max, 0.5)
|
||||||
|
@ -329,6 +603,50 @@ func (r Rect) IntersectCircle(c Circle) Vec {
|
||||||
return c.IntersectRect(r).Scaled(-1)
|
return c.IntersectRect(r).Scaled(-1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IntersectLine will return the shortest Vec such that if the Rect is moved by the Vec returned, the Line and Rect no
|
||||||
|
// longer intersect.
|
||||||
|
func (r Rect) IntersectLine(l Line) Vec {
|
||||||
|
return l.IntersectRect(r).Scaled(-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntersectionPoints returns all the points where the Rect intersects with the line provided. This can be zero, one or
|
||||||
|
// two points, depending on the location of the shapes. The points of intersection will be returned in order of
|
||||||
|
// closest-to-l.A to closest-to-l.B.
|
||||||
|
func (r Rect) IntersectionPoints(l Line) []Vec {
|
||||||
|
// Use map keys to ensure unique points
|
||||||
|
pointMap := make(map[Vec]struct{})
|
||||||
|
|
||||||
|
for _, edge := range r.Edges() {
|
||||||
|
if intersect, ok := l.Intersect(edge); ok {
|
||||||
|
pointMap[intersect] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
points := make([]Vec, 0, len(pointMap))
|
||||||
|
for point := range pointMap {
|
||||||
|
points = append(points, point)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Order the points
|
||||||
|
if len(points) == 2 {
|
||||||
|
if points[1].To(l.A).Len() < points[0].To(l.A).Len() {
|
||||||
|
return []Vec{points[1], points[0]}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return points
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vertices returns a slice of the four corners which make up the rectangle.
|
||||||
|
func (r Rect) Vertices() [4]Vec {
|
||||||
|
return [4]Vec{
|
||||||
|
r.Min,
|
||||||
|
V(r.Min.X, r.Max.Y),
|
||||||
|
r.Max,
|
||||||
|
V(r.Max.X, r.Min.Y),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Circle is a 2D circle. It is defined by two properties:
|
// Circle is a 2D circle. It is defined by two properties:
|
||||||
// - Center vector
|
// - Center vector
|
||||||
// - Radius float64
|
// - Radius float64
|
||||||
|
@ -398,6 +716,12 @@ func (c Circle) Contains(u Vec) bool {
|
||||||
return c.Radius >= toCenter.Len()
|
return c.Radius >= toCenter.Len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Formula returns the values of h and k, for the equation of the circle: (x-h)^2 + (y-k)^2 = r^2
|
||||||
|
// where r is the radius of the circle.
|
||||||
|
func (c Circle) Formula() (h, k float64) {
|
||||||
|
return c.Center.X, c.Center.Y
|
||||||
|
}
|
||||||
|
|
||||||
// maxCircle will return the larger circle based on the radius.
|
// maxCircle will return the larger circle based on the radius.
|
||||||
func maxCircle(c, d Circle) Circle {
|
func maxCircle(c, d Circle) Circle {
|
||||||
if c.Radius < d.Radius {
|
if c.Radius < d.Radius {
|
||||||
|
@ -476,6 +800,12 @@ func (c Circle) Intersect(d Circle) Circle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IntersectLine will return the shortest Vec such that if the Rect is moved by the Vec returned, the Line and Rect no
|
||||||
|
// longer intersect.
|
||||||
|
func (c Circle) IntersectLine(l Line) Vec {
|
||||||
|
return l.IntersectCircle(c).Scaled(-1)
|
||||||
|
}
|
||||||
|
|
||||||
// IntersectRect returns a minimal required Vector, such that moving the circle by that vector would stop the Circle
|
// IntersectRect returns a minimal required Vector, such that moving the circle by that vector would stop the Circle
|
||||||
// and the Rect intersecting. This function returns a zero-vector if the Circle and Rect do not overlap, and if only
|
// and the Rect intersecting. This function returns a zero-vector if the Circle and Rect do not overlap, and if only
|
||||||
// the perimeters touch.
|
// the perimeters touch.
|
||||||
|
@ -552,6 +882,99 @@ func (c Circle) IntersectRect(r Rect) Vec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IntersectionPoints returns all the points where the Circle intersects with the line provided. This can be zero, one or
|
||||||
|
// two points, depending on the location of the shapes. The points of intersection will be returned in order of
|
||||||
|
// closest-to-l.A to closest-to-l.B.
|
||||||
|
func (c Circle) IntersectionPoints(l Line) []Vec {
|
||||||
|
cContainsA := c.Contains(l.A)
|
||||||
|
cContainsB := c.Contains(l.B)
|
||||||
|
|
||||||
|
// Special case for both endpoint being contained within the circle
|
||||||
|
if cContainsA && cContainsB {
|
||||||
|
return []Vec{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get closest point on the line to this circles' center
|
||||||
|
closestToCenter := l.Closest(c.Center)
|
||||||
|
|
||||||
|
// If the distance to the closest point is greater than the radius, there are no points of intersection
|
||||||
|
if closestToCenter.To(c.Center).Len() > c.Radius {
|
||||||
|
return []Vec{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the distance to the closest point is equal to the radius, the line is tangent and the closest point is the
|
||||||
|
// point at which it touches the circle.
|
||||||
|
if closestToCenter.To(c.Center).Len() == c.Radius {
|
||||||
|
return []Vec{closestToCenter}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special case for endpoint being on the circles' center
|
||||||
|
if c.Center == l.A || c.Center == l.B {
|
||||||
|
otherEnd := l.B
|
||||||
|
if c.Center == l.B {
|
||||||
|
otherEnd = l.A
|
||||||
|
}
|
||||||
|
intersect := c.Center.Add(c.Center.To(otherEnd).Unit().Scaled(c.Radius))
|
||||||
|
return []Vec{intersect}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This means the distance to the closest point is less than the radius, so there is at least one intersection,
|
||||||
|
// possibly two.
|
||||||
|
|
||||||
|
// If one of the end points exists within the circle, there is only one intersection
|
||||||
|
if cContainsA || cContainsB {
|
||||||
|
containedPoint := l.A
|
||||||
|
otherEnd := l.B
|
||||||
|
if cContainsB {
|
||||||
|
containedPoint = l.B
|
||||||
|
otherEnd = l.A
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use trigonometry to get the length of the line between the contained point and the intersection point.
|
||||||
|
// The following is used to describe the triangle formed:
|
||||||
|
// - a is the side between contained point and circle center
|
||||||
|
// - b is the side between the center and the intersection point (radius)
|
||||||
|
// - c is the side between the contained point and the intersection point
|
||||||
|
// The captials of these letters are used as the angles opposite the respective sides.
|
||||||
|
// a and b are known
|
||||||
|
a := containedPoint.To(c.Center).Len()
|
||||||
|
b := c.Radius
|
||||||
|
// B can be calculated by subtracting the angle of b (to the x-axis) from the angle of c (to the x-axis)
|
||||||
|
B := containedPoint.To(c.Center).Angle() - containedPoint.To(otherEnd).Angle()
|
||||||
|
// Using the Sin rule we can get A
|
||||||
|
A := math.Asin((a * math.Sin(B)) / b)
|
||||||
|
// Using the rule that there are 180 degrees (or Pi radians) in a triangle, we can now get C
|
||||||
|
C := math.Pi - A + B
|
||||||
|
// If C is zero, the line segment is in-line with the center-intersect line.
|
||||||
|
var c float64
|
||||||
|
if C == 0 {
|
||||||
|
c = b - a
|
||||||
|
} else {
|
||||||
|
// Using the Sine rule again, we can now get c
|
||||||
|
c = (a * math.Sin(C)) / math.Sin(A)
|
||||||
|
}
|
||||||
|
// Travelling from the contained point to the other end by length of a will provide the intersection point.
|
||||||
|
return []Vec{
|
||||||
|
containedPoint.Add(containedPoint.To(otherEnd).Unit().Scaled(c)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise the endpoints exist outside of the circle, and the line segment intersects in two locations.
|
||||||
|
// The vector formed by going from the closest point to the center of the circle will be perpendicular to the line;
|
||||||
|
// this forms a right-angled triangle with the intersection points, with the radius as the hypotenuse.
|
||||||
|
// Calculate the other triangles' sides' length.
|
||||||
|
a := math.Sqrt(math.Pow(c.Radius, 2) - math.Pow(closestToCenter.To(c.Center).Len(), 2))
|
||||||
|
|
||||||
|
// Travelling in both directions from the closest point by length of a will provide the two intersection points.
|
||||||
|
first := closestToCenter.Add(closestToCenter.To(l.A).Unit().Scaled(a))
|
||||||
|
second := closestToCenter.Add(closestToCenter.To(l.B).Unit().Scaled(a))
|
||||||
|
|
||||||
|
if first.To(l.A).Len() < second.To(l.A).Len() {
|
||||||
|
return []Vec{first, second}
|
||||||
|
}
|
||||||
|
return []Vec{second, first}
|
||||||
|
}
|
||||||
|
|
||||||
// Matrix is a 2x3 affine matrix that can be used for all kinds of spatial transforms, such
|
// Matrix is a 2x3 affine matrix that can be used for all kinds of spatial transforms, such
|
||||||
// as movement, scaling and rotations.
|
// as movement, scaling and rotations.
|
||||||
//
|
//
|
||||||
|
|
876
geometry_test.go
876
geometry_test.go
|
@ -10,6 +10,53 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// closeEnough will shift the decimal point by the accuracy required, truncates the results and compares them.
|
||||||
|
// Effectively this compares two floats to a given decimal point.
|
||||||
|
// Example:
|
||||||
|
// closeEnough(100.125342432, 100.125, 2) == true
|
||||||
|
// closeEnough(math.Pi, 3.14, 2) == true
|
||||||
|
// closeEnough(0.1234, 0.1245, 3) == false
|
||||||
|
func closeEnough(got, expected float64, decimalAccuracy int) bool {
|
||||||
|
gotShifted := got * math.Pow10(decimalAccuracy)
|
||||||
|
expectedShifted := expected * math.Pow10(decimalAccuracy)
|
||||||
|
|
||||||
|
return math.Trunc(gotShifted) == math.Trunc(expectedShifted)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRect_Edges(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
Min pixel.Vec
|
||||||
|
Max pixel.Vec
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
want [4]pixel.Line
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Get edges",
|
||||||
|
fields: fields{Min: pixel.V(0, 0), Max: pixel.V(10, 10)},
|
||||||
|
want: [4]pixel.Line{
|
||||||
|
pixel.L(pixel.V(0, 0), pixel.V(0, 10)),
|
||||||
|
pixel.L(pixel.V(0, 10), pixel.V(10, 10)),
|
||||||
|
pixel.L(pixel.V(10, 10), pixel.V(10, 0)),
|
||||||
|
pixel.L(pixel.V(10, 0), pixel.V(0, 0)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
r := pixel.Rect{
|
||||||
|
Min: tt.fields.Min,
|
||||||
|
Max: tt.fields.Max,
|
||||||
|
}
|
||||||
|
if got := r.Edges(); !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("Rect.Edges() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRect_Resize(t *testing.T) {
|
func TestRect_Resize(t *testing.T) {
|
||||||
type rectTestTransform struct {
|
type rectTestTransform struct {
|
||||||
name string
|
name string
|
||||||
|
@ -80,6 +127,40 @@ func TestRect_Resize(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRect_Vertices(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
Min pixel.Vec
|
||||||
|
Max pixel.Vec
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
want [4]pixel.Vec
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Get corners",
|
||||||
|
fields: fields{Min: pixel.V(0, 0), Max: pixel.V(10, 10)},
|
||||||
|
want: [4]pixel.Vec{
|
||||||
|
pixel.V(0, 0),
|
||||||
|
pixel.V(0, 10),
|
||||||
|
pixel.V(10, 10),
|
||||||
|
pixel.V(10, 0),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
r := pixel.Rect{
|
||||||
|
Min: tt.fields.Min,
|
||||||
|
Max: tt.fields.Max,
|
||||||
|
}
|
||||||
|
if got := r.Vertices(); !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("Rect.Vertices() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestMatrix_Unproject(t *testing.T) {
|
func TestMatrix_Unproject(t *testing.T) {
|
||||||
const delta = 1e-15
|
const delta = 1e-15
|
||||||
t.Run("for rotated matrix", func(t *testing.T) {
|
t.Run("for rotated matrix", func(t *testing.T) {
|
||||||
|
@ -541,20 +622,86 @@ func TestCircle_Intersect(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRect_IntersectCircle(t *testing.T) {
|
func TestCircle_IntersectPoints(t *testing.T) {
|
||||||
// closeEnough will shift the decimal point by the accuracy required, truncates the results and compares them.
|
type fields struct {
|
||||||
// Effectively this compares two floats to a given decimal point.
|
Center pixel.Vec
|
||||||
// Example:
|
Radius float64
|
||||||
// closeEnough(100.125342432, 100.125, 2) == true
|
|
||||||
// closeEnough(math.Pi, 3.14, 2) == true
|
|
||||||
// closeEnough(0.1234, 0.1245, 3) == false
|
|
||||||
closeEnough := func(got, expected float64, decimalAccuracy int) bool {
|
|
||||||
gotShifted := got * math.Pow10(decimalAccuracy)
|
|
||||||
expectedShifted := expected * math.Pow10(decimalAccuracy)
|
|
||||||
|
|
||||||
return math.Trunc(gotShifted) == math.Trunc(expectedShifted)
|
|
||||||
}
|
}
|
||||||
|
type args struct {
|
||||||
|
l pixel.Line
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
want []pixel.Vec
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Line intersects circle at two points",
|
||||||
|
fields: fields{Center: pixel.V(2, 2), Radius: 1},
|
||||||
|
args: args{pixel.L(pixel.V(0, 0), pixel.V(10, 10))},
|
||||||
|
want: []pixel.Vec{pixel.V(1.292, 1.292), pixel.V(2.707, 2.707)},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Line intersects circle at one point",
|
||||||
|
fields: fields{Center: pixel.V(-0.5, -0.5), Radius: 1},
|
||||||
|
args: args{pixel.L(pixel.V(0, 0), pixel.V(10, 10))},
|
||||||
|
want: []pixel.Vec{pixel.V(0.207, 0.207)},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Line endpoint is circle center",
|
||||||
|
fields: fields{Center: pixel.V(0, 0), Radius: 1},
|
||||||
|
args: args{pixel.L(pixel.V(0, 0), pixel.V(10, 10))},
|
||||||
|
want: []pixel.Vec{pixel.V(0.707, 0.707)},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Both line endpoints within circle",
|
||||||
|
fields: fields{Center: pixel.V(0, 0), Radius: 1},
|
||||||
|
args: args{pixel.L(pixel.V(0.2, 0.2), pixel.V(0.5, 0.5))},
|
||||||
|
want: []pixel.Vec{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Line does not intersect circle",
|
||||||
|
fields: fields{Center: pixel.V(10, 0), Radius: 1},
|
||||||
|
args: args{pixel.L(pixel.V(0, 0), pixel.V(10, 10))},
|
||||||
|
want: []pixel.Vec{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Horizontal line intersects circle at two points",
|
||||||
|
fields: fields{Center: pixel.V(5, 5), Radius: 1},
|
||||||
|
args: args{pixel.L(pixel.V(0, 5), pixel.V(10, 5))},
|
||||||
|
want: []pixel.Vec{pixel.V(4, 5), pixel.V(6, 5)},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Vertical line intersects circle at two points",
|
||||||
|
fields: fields{Center: pixel.V(5, 5), Radius: 1},
|
||||||
|
args: args{pixel.L(pixel.V(5, 0), pixel.V(5, 10))},
|
||||||
|
want: []pixel.Vec{pixel.V(5, 4), pixel.V(5, 6)},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Left and down line intersects circle at two points",
|
||||||
|
fields: fields{Center: pixel.V(5, 5), Radius: 1},
|
||||||
|
args: args{pixel.L(pixel.V(10, 10), pixel.V(0, 0))},
|
||||||
|
want: []pixel.Vec{pixel.V(5.707, 5.707), pixel.V(4.292, 4.292)},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
c := pixel.Circle{
|
||||||
|
Center: tt.fields.Center,
|
||||||
|
Radius: tt.fields.Radius,
|
||||||
|
}
|
||||||
|
got := c.IntersectionPoints(tt.args.l)
|
||||||
|
for i, v := range got {
|
||||||
|
if !closeEnough(v.X, tt.want[i].X, 2) || !closeEnough(v.Y, tt.want[i].Y, 2) {
|
||||||
|
t.Errorf("Circle.IntersectPoints() = %v, want %v", v, tt.want[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRect_IntersectCircle(t *testing.T) {
|
||||||
type fields struct {
|
type fields struct {
|
||||||
Min pixel.Vec
|
Min pixel.Vec
|
||||||
Max pixel.Vec
|
Max pixel.Vec
|
||||||
|
@ -690,3 +837,708 @@ func TestRect_IntersectCircle(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRect_IntersectionPoints(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
Min pixel.Vec
|
||||||
|
Max pixel.Vec
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
l pixel.Line
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
want []pixel.Vec
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "No intersection points",
|
||||||
|
fields: fields{Min: pixel.V(1, 1), Max: pixel.V(5, 5)},
|
||||||
|
args: args{l: pixel.L(pixel.V(-5, 0), pixel.V(-2, 2))},
|
||||||
|
want: []pixel.Vec{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "One intersection point",
|
||||||
|
fields: fields{Min: pixel.V(1, 1), Max: pixel.V(5, 5)},
|
||||||
|
args: args{l: pixel.L(pixel.V(2, 0), pixel.V(2, 3))},
|
||||||
|
want: []pixel.Vec{pixel.V(2, 1)},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Two intersection points",
|
||||||
|
fields: fields{Min: pixel.V(1, 1), Max: pixel.V(5, 5)},
|
||||||
|
args: args{l: pixel.L(pixel.V(0, 2), pixel.V(6, 2))},
|
||||||
|
want: []pixel.Vec{pixel.V(1, 2), pixel.V(5, 2)},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
r := pixel.Rect{
|
||||||
|
Min: tt.fields.Min,
|
||||||
|
Max: tt.fields.Max,
|
||||||
|
}
|
||||||
|
if got := r.IntersectionPoints(tt.args.l); !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("Rect.IntersectPoints() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLine_Bounds(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
A pixel.Vec
|
||||||
|
B pixel.Vec
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
want pixel.Rect
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Positive slope",
|
||||||
|
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
|
||||||
|
want: pixel.R(0, 0, 10, 10),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Negative slope",
|
||||||
|
fields: fields{A: pixel.V(10, 10), B: pixel.V(0, 0)},
|
||||||
|
want: pixel.R(0, 0, 10, 10),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
l := pixel.Line{
|
||||||
|
A: tt.fields.A,
|
||||||
|
B: tt.fields.B,
|
||||||
|
}
|
||||||
|
if got := l.Bounds(); !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("Line.Bounds() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLine_Center(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
A pixel.Vec
|
||||||
|
B pixel.Vec
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
want pixel.Vec
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Positive slope",
|
||||||
|
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
|
||||||
|
want: pixel.V(5, 5),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Negative slope",
|
||||||
|
fields: fields{A: pixel.V(10, 10), B: pixel.V(0, 0)},
|
||||||
|
want: pixel.V(5, 5),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
l := pixel.Line{
|
||||||
|
A: tt.fields.A,
|
||||||
|
B: tt.fields.B,
|
||||||
|
}
|
||||||
|
if got := l.Center(); !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("Line.Center() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLine_Closest(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
A pixel.Vec
|
||||||
|
B pixel.Vec
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
v pixel.Vec
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
want pixel.Vec
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Point on line",
|
||||||
|
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
|
||||||
|
args: args{v: pixel.V(5, 5)},
|
||||||
|
want: pixel.V(5, 5),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Point on next to line",
|
||||||
|
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
|
||||||
|
args: args{v: pixel.V(0, 10)},
|
||||||
|
want: pixel.V(5, 5),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Point on next to vertical line",
|
||||||
|
fields: fields{A: pixel.V(5, 0), B: pixel.V(5, 10)},
|
||||||
|
args: args{v: pixel.V(6, 5)},
|
||||||
|
want: pixel.V(5, 5),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Point on next to horizontal line",
|
||||||
|
fields: fields{A: pixel.V(0, 5), B: pixel.V(10, 5)},
|
||||||
|
args: args{v: pixel.V(5, 6)},
|
||||||
|
want: pixel.V(5, 5),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Point far from line",
|
||||||
|
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
|
||||||
|
args: args{v: pixel.V(80, -70)},
|
||||||
|
want: pixel.V(5, 5),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Point on inline with line",
|
||||||
|
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
|
||||||
|
args: args{v: pixel.V(20, 20)},
|
||||||
|
want: pixel.V(10, 10),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
l := pixel.Line{
|
||||||
|
A: tt.fields.A,
|
||||||
|
B: tt.fields.B,
|
||||||
|
}
|
||||||
|
if got := l.Closest(tt.args.v); !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("Line.Closest() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLine_Contains(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
A pixel.Vec
|
||||||
|
B pixel.Vec
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
v pixel.Vec
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Point on line",
|
||||||
|
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
|
||||||
|
args: args{v: pixel.V(5, 5)},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Point on negative sloped line",
|
||||||
|
fields: fields{A: pixel.V(0, 10), B: pixel.V(10, 0)},
|
||||||
|
args: args{v: pixel.V(5, 5)},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Point not on line",
|
||||||
|
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
|
||||||
|
args: args{v: pixel.V(0, 10)},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
l := pixel.Line{
|
||||||
|
A: tt.fields.A,
|
||||||
|
B: tt.fields.B,
|
||||||
|
}
|
||||||
|
if got := l.Contains(tt.args.v); got != tt.want {
|
||||||
|
t.Errorf("Line.Contains() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLine_Formula(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
A pixel.Vec
|
||||||
|
B pixel.Vec
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
wantM float64
|
||||||
|
wantB float64
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Getting formula - 45 degs",
|
||||||
|
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
|
||||||
|
wantM: 1,
|
||||||
|
wantB: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Getting formula - 90 degs",
|
||||||
|
fields: fields{A: pixel.V(0, 0), B: pixel.V(0, 10)},
|
||||||
|
wantM: math.Inf(1),
|
||||||
|
wantB: math.NaN(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Getting formula - 0 degs",
|
||||||
|
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 0)},
|
||||||
|
wantM: 0,
|
||||||
|
wantB: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
l := pixel.Line{
|
||||||
|
A: tt.fields.A,
|
||||||
|
B: tt.fields.B,
|
||||||
|
}
|
||||||
|
gotM, gotB := l.Formula()
|
||||||
|
if gotM != tt.wantM {
|
||||||
|
t.Errorf("Line.Formula() gotM = %v, want %v", gotM, tt.wantM)
|
||||||
|
}
|
||||||
|
if gotB != tt.wantB {
|
||||||
|
if math.IsNaN(tt.wantB) && !math.IsNaN(gotB) {
|
||||||
|
t.Errorf("Line.Formula() gotB = %v, want %v", gotB, tt.wantB)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLine_Intersect(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
A pixel.Vec
|
||||||
|
B pixel.Vec
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
k pixel.Line
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
want pixel.Vec
|
||||||
|
want1 bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Lines intersect",
|
||||||
|
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
|
||||||
|
args: args{k: pixel.L(pixel.V(0, 10), pixel.V(10, 0))},
|
||||||
|
want: pixel.V(5, 5),
|
||||||
|
want1: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Lines intersect 2",
|
||||||
|
fields: fields{A: pixel.V(5, 1), B: pixel.V(1, 1)},
|
||||||
|
args: args{k: pixel.L(pixel.V(2, 0), pixel.V(2, 3))},
|
||||||
|
want: pixel.V(2, 1),
|
||||||
|
want1: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Line intersect with vertical",
|
||||||
|
fields: fields{A: pixel.V(5, 0), B: pixel.V(5, 10)},
|
||||||
|
args: args{k: pixel.L(pixel.V(0, 0), pixel.V(10, 10))},
|
||||||
|
want: pixel.V(5, 5),
|
||||||
|
want1: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Line intersect with horizontal",
|
||||||
|
fields: fields{A: pixel.V(0, 5), B: pixel.V(10, 5)},
|
||||||
|
args: args{k: pixel.L(pixel.V(0, 0), pixel.V(10, 10))},
|
||||||
|
want: pixel.V(5, 5),
|
||||||
|
want1: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Lines don't intersect",
|
||||||
|
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
|
||||||
|
args: args{k: pixel.L(pixel.V(0, 10), pixel.V(1, 20))},
|
||||||
|
want: pixel.ZV,
|
||||||
|
want1: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Lines don't intersect 2",
|
||||||
|
fields: fields{A: pixel.V(1, 1), B: pixel.V(1, 5)},
|
||||||
|
args: args{k: pixel.L(pixel.V(-5, 0), pixel.V(-2, 2))},
|
||||||
|
want: pixel.ZV,
|
||||||
|
want1: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Lines don't intersect 3",
|
||||||
|
fields: fields{A: pixel.V(2, 0), B: pixel.V(2, 3)},
|
||||||
|
args: args{k: pixel.L(pixel.V(1, 5), pixel.V(5, 5))},
|
||||||
|
want: pixel.ZV,
|
||||||
|
want1: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Lines parallel",
|
||||||
|
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
|
||||||
|
args: args{k: pixel.L(pixel.V(0, 1), pixel.V(10, 11))},
|
||||||
|
want: pixel.ZV,
|
||||||
|
want1: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
l := pixel.Line{
|
||||||
|
A: tt.fields.A,
|
||||||
|
B: tt.fields.B,
|
||||||
|
}
|
||||||
|
got, got1 := l.Intersect(tt.args.k)
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("Line.Intersect() got = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
if got1 != tt.want1 {
|
||||||
|
t.Errorf("Line.Intersect() got1 = %v, want %v", got1, tt.want1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLine_IntersectCircle(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
A pixel.Vec
|
||||||
|
B pixel.Vec
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
c pixel.Circle
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
want pixel.Vec
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Cirle intersects",
|
||||||
|
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
|
||||||
|
args: args{c: pixel.C(pixel.V(6, 4), 2)},
|
||||||
|
want: pixel.V(0.5857864376269049, -0.5857864376269049),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Cirle doesn't intersects",
|
||||||
|
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
|
||||||
|
args: args{c: pixel.C(pixel.V(0, 5), 1)},
|
||||||
|
want: pixel.ZV,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
l := pixel.Line{
|
||||||
|
A: tt.fields.A,
|
||||||
|
B: tt.fields.B,
|
||||||
|
}
|
||||||
|
if got := l.IntersectCircle(tt.args.c); !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("Line.IntersectCircle() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLine_IntersectRect(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
A pixel.Vec
|
||||||
|
B pixel.Vec
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
r pixel.Rect
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
want pixel.Vec
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Line through rect vertically",
|
||||||
|
fields: fields{A: pixel.V(0, 0), B: pixel.V(0, 10)},
|
||||||
|
args: args{r: pixel.R(-1, 1, 5, 5)},
|
||||||
|
want: pixel.V(-1, 0),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Line through rect horizontally",
|
||||||
|
fields: fields{A: pixel.V(0, 1), B: pixel.V(10, 1)},
|
||||||
|
args: args{r: pixel.R(1, 0, 5, 5)},
|
||||||
|
want: pixel.V(0, -1),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Line through rect diagonally bottom and left edges",
|
||||||
|
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
|
||||||
|
args: args{r: pixel.R(0, 2, 3, 3)},
|
||||||
|
want: pixel.V(-1, 1),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Line through rect diagonally top and right edges",
|
||||||
|
fields: fields{A: pixel.V(10, 0), B: pixel.V(0, 10)},
|
||||||
|
args: args{r: pixel.R(5, 0, 8, 3)},
|
||||||
|
want: pixel.V(-2.5, -2.5),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Line with not rect intersect",
|
||||||
|
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
|
||||||
|
args: args{r: pixel.R(20, 20, 21, 21)},
|
||||||
|
want: pixel.ZV,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
l := pixel.Line{
|
||||||
|
A: tt.fields.A,
|
||||||
|
B: tt.fields.B,
|
||||||
|
}
|
||||||
|
if got := l.IntersectRect(tt.args.r); !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("Line.IntersectRect() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLine_Len(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
A pixel.Vec
|
||||||
|
B pixel.Vec
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
want float64
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "End right-up of start",
|
||||||
|
fields: fields{A: pixel.V(0, 0), B: pixel.V(3, 4)},
|
||||||
|
want: 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "End left-up of start",
|
||||||
|
fields: fields{A: pixel.V(0, 0), B: pixel.V(-3, 4)},
|
||||||
|
want: 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "End right-down of start",
|
||||||
|
fields: fields{A: pixel.V(0, 0), B: pixel.V(3, -4)},
|
||||||
|
want: 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "End left-down of start",
|
||||||
|
fields: fields{A: pixel.V(0, 0), B: pixel.V(-3, -4)},
|
||||||
|
want: 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "End same as start",
|
||||||
|
fields: fields{A: pixel.V(0, 0), B: pixel.V(0, 0)},
|
||||||
|
want: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
l := pixel.Line{
|
||||||
|
A: tt.fields.A,
|
||||||
|
B: tt.fields.B,
|
||||||
|
}
|
||||||
|
if got := l.Len(); got != tt.want {
|
||||||
|
t.Errorf("Line.Len() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLine_Rotated(t *testing.T) {
|
||||||
|
// round returns the nearest integer, rounding ties away from zero.
|
||||||
|
// This is required because `math.Round` wasn't introduced until Go1.10
|
||||||
|
round := func(x float64) float64 {
|
||||||
|
t := math.Trunc(x)
|
||||||
|
if math.Abs(x-t) >= 0.5 {
|
||||||
|
return t + math.Copysign(1, x)
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
type fields struct {
|
||||||
|
A pixel.Vec
|
||||||
|
B pixel.Vec
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
around pixel.Vec
|
||||||
|
angle float64
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
want pixel.Line
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Rotating around line center",
|
||||||
|
fields: fields{A: pixel.V(1, 1), B: pixel.V(3, 3)},
|
||||||
|
args: args{around: pixel.V(2, 2), angle: math.Pi},
|
||||||
|
want: pixel.L(pixel.V(3, 3), pixel.V(1, 1)),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Rotating around x-y origin",
|
||||||
|
fields: fields{A: pixel.V(1, 1), B: pixel.V(3, 3)},
|
||||||
|
args: args{around: pixel.V(0, 0), angle: math.Pi},
|
||||||
|
want: pixel.L(pixel.V(-1, -1), pixel.V(-3, -3)),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Rotating around line end",
|
||||||
|
fields: fields{A: pixel.V(1, 1), B: pixel.V(3, 3)},
|
||||||
|
args: args{around: pixel.V(1, 1), angle: math.Pi},
|
||||||
|
want: pixel.L(pixel.V(1, 1), pixel.V(-1, -1)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
l := pixel.Line{
|
||||||
|
A: tt.fields.A,
|
||||||
|
B: tt.fields.B,
|
||||||
|
}
|
||||||
|
// Have to round the results, due to floating-point in accuracies. Results are correct to approximately
|
||||||
|
// 10 decimal places.
|
||||||
|
got := l.Rotated(tt.args.around, tt.args.angle)
|
||||||
|
if round(got.A.X) != tt.want.A.X ||
|
||||||
|
round(got.B.X) != tt.want.B.X ||
|
||||||
|
round(got.A.Y) != tt.want.A.Y ||
|
||||||
|
round(got.B.Y) != tt.want.B.Y {
|
||||||
|
t.Errorf("Line.Rotated() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLine_Scaled(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
A pixel.Vec
|
||||||
|
B pixel.Vec
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
scale float64
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
want pixel.Line
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Scaling by 1",
|
||||||
|
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
|
||||||
|
args: args{scale: 1},
|
||||||
|
want: pixel.L(pixel.V(0, 0), pixel.V(10, 10)),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Scaling by >1",
|
||||||
|
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
|
||||||
|
args: args{scale: 2},
|
||||||
|
want: pixel.L(pixel.V(-5, -5), pixel.V(15, 15)),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Scaling by <1",
|
||||||
|
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
|
||||||
|
args: args{scale: 0.5},
|
||||||
|
want: pixel.L(pixel.V(2.5, 2.5), pixel.V(7.5, 7.5)),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Scaling by -1",
|
||||||
|
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
|
||||||
|
args: args{scale: -1},
|
||||||
|
want: pixel.L(pixel.V(10, 10), pixel.V(0, 0)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
l := pixel.Line{
|
||||||
|
A: tt.fields.A,
|
||||||
|
B: tt.fields.B,
|
||||||
|
}
|
||||||
|
if got := l.Scaled(tt.args.scale); !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("Line.Scaled() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLine_ScaledXY(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
A pixel.Vec
|
||||||
|
B pixel.Vec
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
around pixel.Vec
|
||||||
|
scale float64
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
want pixel.Line
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Scaling by 1 around origin",
|
||||||
|
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
|
||||||
|
args: args{around: pixel.ZV, scale: 1},
|
||||||
|
want: pixel.L(pixel.V(0, 0), pixel.V(10, 10)),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Scaling by >1 around origin",
|
||||||
|
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
|
||||||
|
args: args{around: pixel.ZV, scale: 2},
|
||||||
|
want: pixel.L(pixel.V(0, 0), pixel.V(20, 20)),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Scaling by <1 around origin",
|
||||||
|
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
|
||||||
|
args: args{around: pixel.ZV, scale: 0.5},
|
||||||
|
want: pixel.L(pixel.V(0, 0), pixel.V(5, 5)),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Scaling by -1 around origin",
|
||||||
|
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
|
||||||
|
args: args{around: pixel.ZV, scale: -1},
|
||||||
|
want: pixel.L(pixel.V(0, 0), pixel.V(-10, -10)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
l := pixel.Line{
|
||||||
|
A: tt.fields.A,
|
||||||
|
B: tt.fields.B,
|
||||||
|
}
|
||||||
|
if got := l.ScaledXY(tt.args.around, tt.args.scale); !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("Line.ScaledXY() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLine_String(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
A pixel.Vec
|
||||||
|
B pixel.Vec
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Getting string",
|
||||||
|
fields: fields{A: pixel.V(0, 0), B: pixel.V(1, 1)},
|
||||||
|
want: "Line(Vec(0, 0), Vec(1, 1))",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
l := pixel.Line{
|
||||||
|
A: tt.fields.A,
|
||||||
|
B: tt.fields.B,
|
||||||
|
}
|
||||||
|
if got := l.String(); got != tt.want {
|
||||||
|
t.Errorf("Line.String() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue