diff --git a/vector.go b/vector.go deleted file mode 100644 index 42318b0..0000000 --- a/vector.go +++ /dev/null @@ -1,462 +0,0 @@ -package pixel - -import ( - "fmt" - "math" -) - -// Vec is a 2D vector type with X and Y coordinates. -// -// Create vectors with the V constructor: -// -// u := pixel.V(1, 2) -// v := pixel.V(8, -3) -// -// Use various methods to manipulate them: -// -// w := u.Add(v) -// fmt.Println(w) // Vec(9, -1) -// fmt.Println(u.Sub(v)) // Vec(-7, 5) -// u = pixel.V(2, 3) -// v = pixel.V(8, 1) -// if u.X < 0 { -// fmt.Println("this won't happen") -// } -// x := u.Unit().Dot(v.Unit()) -type Vec struct { - X, Y float64 -} - -// ZV is a zero vector. -var ZV = Vec{0, 0} - -// V returns a new 2D vector with the given coordinates. -func V(x, y float64) Vec { - return Vec{x, y} -} - -// nearlyEqual compares two float64s and returns whether they are equal, accounting for rounding errors.At worst, the -// result is correct to 7 significant digits. -func nearlyEqual(a, b float64) bool { - epsilon := 0.000001 - - if a == b { - return true - } - - diff := math.Abs(a - b) - - if a == 0.0 || b == 0.0 || diff < math.SmallestNonzeroFloat64 { - return diff < (epsilon * math.SmallestNonzeroFloat64) - } - - absA := math.Abs(a) - absB := math.Abs(b) - - return diff/math.Min(absA+absB, math.MaxFloat64) < epsilon -} - -// Eq will compare two vectors and return whether they are equal accounting for rounding errors. At worst, the result -// is correct to 7 significant digits. -func (u Vec) Eq(v Vec) bool { - return nearlyEqual(u.X, v.X) && nearlyEqual(u.Y, v.Y) -} - -// Unit returns a vector of length 1 facing the given angle. -func Unit(angle float64) Vec { - return Vec{1, 0}.Rotated(angle) -} - -// String returns the string representation of the vector u. -// -// u := pixel.V(4.5, -1.3) -// u.String() // returns "Vec(4.5, -1.3)" -// fmt.Println(u) // Vec(4.5, -1.3) -func (u Vec) String() string { - return fmt.Sprintf("Vec(%v, %v)", u.X, u.Y) -} - -// XY returns the components of the vector in two return values. -func (u Vec) XY() (x, y float64) { - return u.X, u.Y -} - -// Add returns the sum of vectors u and v. -func (u Vec) Add(v Vec) Vec { - return Vec{ - u.X + v.X, - u.Y + v.Y, - } -} - -// Sub returns the difference betweeen vectors u and v. -func (u Vec) Sub(v Vec) Vec { - return Vec{ - u.X - v.X, - u.Y - v.Y, - } -} - -// Floor converts x and y to their integer equivalents. -func (u Vec) Floor() Vec { - return Vec{ - math.Floor(u.X), - math.Floor(u.Y), - } -} - -// To returns the vector from u to v. Equivalent to v.Sub(u). -func (u Vec) To(v Vec) Vec { - return Vec{ - v.X - u.X, - v.Y - u.Y, - } -} - -// Scaled returns the vector u multiplied by c. -func (u Vec) Scaled(c float64) Vec { - return Vec{u.X * c, u.Y * c} -} - -// ScaledXY returns the vector u multiplied by the vector v component-wise. -func (u Vec) ScaledXY(v Vec) Vec { - return Vec{u.X * v.X, u.Y * v.Y} -} - -// Len returns the length of the vector u. -func (u Vec) Len() float64 { - return math.Hypot(u.X, u.Y) -} - -// SqLen returns the squared length of the vector u (faster to compute than Len). -func (u Vec) SqLen() float64 { - return u.X*u.X + u.Y*u.Y -} - -// Angle returns the angle between the vector u and the x-axis. The result is in range [-Pi, Pi]. -func (u Vec) Angle() float64 { - return math.Atan2(u.Y, u.X) -} - -// Unit returns a vector of length 1 facing the direction of u (has the same angle). -func (u Vec) Unit() Vec { - if u.X == 0 && u.Y == 0 { - return Vec{1, 0} - } - return u.Scaled(1 / u.Len()) -} - -// Rotated returns the vector u rotated by the given angle in radians. -func (u Vec) Rotated(angle float64) Vec { - sin, cos := math.Sincos(angle) - return Vec{ - u.X*cos - u.Y*sin, - u.X*sin + u.Y*cos, - } -} - -// Normal returns a vector normal to u. Equivalent to u.Rotated(math.Pi / 2), but faster. -func (u Vec) Normal() Vec { - return Vec{-u.Y, u.X} -} - -// Dot returns the dot product of vectors u and v. -func (u Vec) Dot(v Vec) float64 { - return u.X*v.X + u.Y*v.Y -} - -// Cross return the cross product of vectors u and v. -func (u Vec) Cross(v Vec) float64 { - return u.X*v.Y - v.X*u.Y -} - -// Project returns a projection (or component) of vector u in the direction of vector v. -// -// Behaviour is undefined if v is a zero vector. -func (u Vec) Project(v Vec) Vec { - len := u.Dot(v) / v.Len() - return v.Unit().Scaled(len) -} - -// Map applies the function f to both x and y components of the vector u and returns the modified -// vector. -// -// u := pixel.V(10.5, -1.5) -// v := u.Map(math.Floor) // v is Vec(10, -2), both components of u floored -func (u Vec) Map(f func(float64) float64) Vec { - return Vec{ - f(u.X), - f(u.Y), - } -} - -// Lerp returns a linear interpolation between vectors a and b. -// -// This function basically returns a point along the line between a and b and t chooses which one. -// If t is 0, then a will be returned, if t is 1, b will be returned. Anything between 0 and 1 will -// return the appropriate point between a and b and so on. -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).Eq(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() - var closest *Vec - closestCorner := corners[0] - for _, c := range corners { - cc := l.Closest(c) - if closest == nil || (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) -}