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))
|
||||
}
|
||||
|
||||
// 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
|
||||
// points, Min and Max.
|
||||
//
|
||||
|
@ -243,6 +505,18 @@ func (r Rect) Area() float64 {
|
|||
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.
|
||||
func (r Rect) Center() Vec {
|
||||
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)
|
||||
}
|
||||
|
||||
// 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:
|
||||
// - Center vector
|
||||
// - Radius float64
|
||||
|
@ -398,6 +716,12 @@ func (c Circle) Contains(u Vec) bool {
|
|||
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.
|
||||
func maxCircle(c, d Circle) Circle {
|
||||
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
|
||||
// and the Rect intersecting. This function returns a zero-vector if the Circle and Rect do not overlap, and if only
|
||||
// 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
|
||||
// as movement, scaling and rotations.
|
||||
//
|
||||
|
|
876
geometry_test.go
876
geometry_test.go
|
@ -10,6 +10,53 @@ import (
|
|||
"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) {
|
||||
type rectTestTransform struct {
|
||||
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) {
|
||||
const delta = 1e-15
|
||||
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) {
|
||||
// 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
|
||||
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)
|
||||
func TestCircle_IntersectPoints(t *testing.T) {
|
||||
type fields struct {
|
||||
Center pixel.Vec
|
||||
Radius float64
|
||||
}
|
||||
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 {
|
||||
Min 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