diff --git a/geometry.go b/geometry.go index 1e2922d..7e10a69 100644 --- a/geometry.go +++ b/geometry.go @@ -181,6 +181,173 @@ 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 +} + +// 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 { + lenSquared := math.Pow(l.Len(), 2) + + if lenSquared == 0 { + return l.A + } + + t := math.Max(0, math.Min(1, l.A.Sub(v).Dot(l.B.Sub(v))/lenSquared)) + projection := l.A.Add(l.B.Sub(v).Scaled(t)) + return v.To(projection) +} + +// 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 +func (l Line) Formula() (m, b float64) { + 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 math.Abs(lDir.X) == math.Abs(kDir.X) && math.Abs(lDir.Y) == math.Abs(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 := l.Formula() + + // 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 the Line and Circle 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 the Line and Rect 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.Sub(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 +410,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 +508,22 @@ 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) +} + +// Vertices returns a slice of the four corners which make up the rectangle. +func (r Rect) Vertices() [4]Vec { + return [4]Vec{ + r.Min, + r.Max, + V(r.Min.X, r.Max.Y), + V(r.Max.X, r.Min.Y), + } +} + // Circle is a 2D circle. It is defined by two properties: // - Center vector // - Radius float64 @@ -476,6 +671,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.