-
Notifications
You must be signed in to change notification settings - Fork 0
Home
import "github.com/mikenye/geom2d"
Package geom2d provides a comprehensive set of tools for computational geometry in two dimensions.
The geom2d package is built around core types like Point, LineSegment, Circle, Rectangle, and PolyTree, supporting a wide range of operations including transformations, boolean geometry, and spatial relationships.
Designed for both performance and clarity, geom2d leverages Go generics to handle various numeric types and provides intuitive APIs for working with 2D geometric data.
This library assumes a standard Cartesian coordinate system where the x-axis increases to the right and the y-axis increases upward. This system is commonly referred to as a right-handed Cartesian coordinate system. All geometric operations and relationships (e.g., clockwise or counterclockwise points) are based on this convention.
The library includes support for the following 2D geometric types:
- Point: Represents a single coordinate in 2D space.
- LineSegment: Represents a straight line segment defined by two endpoints.
- Rectangle: Represents an axis-aligned rectangle, defined by its corners.
- Circle: Represents a circle defined by a center point and radius.
- PolyTree: Represents a hierarchical structure of polygons, supporting sibling polygons, nested holes and nested islands.
geom2d leverages Go’s generics, allowing you to use the library with different numeric types (int, float32, float64, etc.). This flexibility ensures the library can adapt to various applications, from integer-based grids to floating-point precision computations.
geom2d incorporates an epsilon parameter in many of its relationship methods to handle floating-point precision issues. This allows you to control the tolerance for comparisons, making the library robust for real-world applications where precision errors can occur.
This library provides methods to compute relationships between geometric types using a standardized set of relationships: RelationshipDisjoint, RelationshipIntersection, RelationshipContainedBy, RelationshipContains, and RelationshipEqual.
geom2d builds upon the work of others and is grateful for the foundations they have laid. Specifically:
- Martínez et al.: Their paper on Boolean operations on polygons has been instrumental in the implementation of the Martínez algorithm in this library. See A simple algorithm for Boolean operations on polygons.
- Tom Wright: The inspiration for starting this library came from Tom Wright’s repository Provably Correct Polygon Algorithms and his accompanying paper. While geom2d follows its own approach, certain ideas have been influenced by his work.
- Jack Bresenham: The Bresenham's Line Algorithm and Bresenham's Circle Algorithm implemented in this library are inspired by Jack Bresenham's pioneering work. These algorithms are efficient methods for rasterizing lines and circles in computer graphics. For more details, see Bresenham's original paper "Algorithm for computer control of a digital plotter." IBM Systems Journal, 1965.
- This project is a collaborative effort, with assistance from OpenAI's Assistant for brainstorming, debugging, and refining implementations.
- func EnsureClockwise[T SignedNumber](points []Point[T])
- func EnsureCounterClockwise[T SignedNumber](points []Point[T])
- func IsWellFormedPolygon[T SignedNumber](points []Point[T]) (bool, error)
- func RelativeAngle[T SignedNumber](A, B Point[T], O ...Point[T]) (radians float64)
- func RelativeCosineOfAngle[T SignedNumber](A, B Point[T], O ...Point[T]) float64
- func SignedArea2X[T SignedNumber](points []Point[T]) T
- type BooleanOperation
-
type Circle
- func NewCircle[T SignedNumber](center Point[T], radius T) Circle[T]
- func (c Circle[T]) Area() float64
- func (c Circle[T]) AsFloat32() Circle[float32]
- func (c Circle[T]) AsFloat64() Circle[float64]
- func (c Circle[T]) AsInt() Circle[int]
- func (c Circle[T]) AsIntRounded() Circle[int]
- func (c Circle[T]) BoundingBox() Rectangle[T]
- func (c Circle[int]) Bresenham(yield func(Point[int]) bool)
- func (c Circle[T]) Center() Point[T]
- func (c Circle[T]) Circumference() float64
- func (c Circle[T]) Eq(other Circle[T], opts ...Option) bool
- func (c Circle[T]) Radius() T
- func (c Circle[T]) RelationshipToCircle(other Circle[T], opts ...Option) Relationship
- func (c Circle[T]) RelationshipToLineSegment(l LineSegment[T], opts ...Option) Relationship
- func (c Circle[T]) RelationshipToPoint(p Point[T], opts ...Option) Relationship
- func (c Circle[T]) RelationshipToPolyTree(pt *PolyTree[T], opts ...Option) map[*PolyTree[T]]Relationship
- func (c Circle[T]) RelationshipToRectangle(r Rectangle[T], opts ...Option) Relationship
- func (c Circle[T]) Rotate(pivot Point[T], radians float64, opts ...Option) Circle[float64]
- func (c Circle[T]) Scale(factor T) Circle[T]
- func (c Circle[T]) String() string
- func (c Circle[T]) Translate(v Point[T]) Circle[T]
-
type LineSegment
- func NewLineSegment[T SignedNumber](start, end Point[T]) LineSegment[T]
- func (l LineSegment[T]) AddLineSegment(other LineSegment[T]) LineSegment[T]
- func (l LineSegment[T]) AsFloat32() LineSegment[float32]
- func (l LineSegment[T]) AsFloat64() LineSegment[float64]
- func (l LineSegment[T]) AsInt() LineSegment[int]
- func (l LineSegment[T]) AsIntRounded() LineSegment[int]
- func (l LineSegment[T]) BoundingBox() Rectangle[T]
- func (l LineSegment[int]) Bresenham(yield func(Point[int]) bool)
- func (l LineSegment[T]) Center(opts ...Option) Point[float64]
- func (l LineSegment[T]) ContainsPoint(p Point[T]) bool
- func (l LineSegment[T]) DistanceToLineSegment(other LineSegment[T], opts ...Option) float64
- func (l LineSegment[T]) DistanceToPoint(p Point[T], opts ...Option) float64
- func (l LineSegment[T]) End() Point[T]
- func (l LineSegment[T]) Eq(other LineSegment[T], opts ...Option) bool
- func (l LineSegment[T]) IntersectionGeometry(other LineSegment[T], opts ...Option) LineSegmentIntersectionResult[float64]
- func (l LineSegment[T]) IntersectsLineSegment(other LineSegment[T]) bool
- func (l LineSegment[T]) Length(opts ...Option) float64
- func (l LineSegment[T]) Normalize() LineSegment[T]
- func (l LineSegment[T]) Points() []Point[T]
- func (l LineSegment[T]) Reflect(axis ReflectionAxis, line ...LineSegment[float64]) LineSegment[float64]
- func (l LineSegment[T]) RelationshipToCircle(c Circle[T], opts ...Option) Relationship
- func (l LineSegment[T]) RelationshipToLineSegment(other LineSegment[T], opts ...Option) Relationship
- func (l LineSegment[T]) RelationshipToPoint(p Point[T], opts ...Option) Relationship
- func (l LineSegment[T]) RelationshipToPolyTree(pt *PolyTree[T], opts ...Option) map[*PolyTree[T]]Relationship
- func (l LineSegment[T]) RelationshipToRectangle(r Rectangle[T], opts ...Option) Relationship
- func (l LineSegment[T]) Rotate(pivot Point[T], radians float64, opts ...Option) LineSegment[float64]
- func (l LineSegment[T]) RoundToEpsilon(epsilon float64) LineSegment[float64]
- func (l LineSegment[T]) Scale(ref Point[T], factor T) LineSegment[T]
- func (l LineSegment[T]) Slope() (float64, bool)
- func (l LineSegment[T]) Start() Point[T]
- func (l LineSegment[T]) String() string
- func (l LineSegment[T]) SubLineSegment(other LineSegment[T]) LineSegment[T]
- func (l LineSegment[T]) Translate(delta Point[T]) LineSegment[T]
- func (l LineSegment[T]) XAtY(y float64) (float64, bool)
- func (l LineSegment[T]) YAtX(x float64) (float64, bool)
- type LineSegmentIntersectionResult
- type LineSegmentIntersectionType
- type NewPolyTreeOption
- type Option
-
type Point
- func ConvexHull[T SignedNumber](points []Point[T]) []Point[T]
- func NewPoint[T SignedNumber](x, y T) Point[T]
- func NewPointFromImagePoint(q image.Point) Point[int]
- func RoundPointToEpsilon(point Point[float64], epsilon float64) Point[float64]
- func (p Point[T]) AsFloat32() Point[float32]
- func (p Point[T]) AsFloat64() Point[float64]
- func (p Point[T]) AsInt() Point[int]
- func (p Point[T]) AsIntRounded() Point[int]
- func (p Point[T]) CrossProduct(q Point[T]) T
- func (p Point[T]) DistanceSquaredToPoint(q Point[T]) T
- func (p Point[T]) DistanceToLineSegment(l LineSegment[T], opts ...Option) float64
- func (p Point[T]) DistanceToPoint(q Point[T], opts ...Option) float64
- func (p Point[T]) DotProduct(q Point[T]) T
- func (p Point[T]) Eq(q Point[T], opts ...Option) bool
- func (p Point[T]) Negate() Point[T]
- func (p Point[T]) ProjectOntoLineSegment(l LineSegment[T]) Point[float64]
- func (p Point[T]) Reflect(axis ReflectionAxis, line ...LineSegment[float64]) Point[float64]
- func (p Point[T]) RelationshipToCircle(c Circle[T], opts ...Option) Relationship
- func (p Point[T]) RelationshipToLineSegment(l LineSegment[T], opts ...Option) Relationship
- func (p Point[T]) RelationshipToPoint(other Point[T], opts ...Option) Relationship
- func (p Point[T]) RelationshipToPolyTree(pt *PolyTree[T], opts ...Option) map[*PolyTree[T]]Relationship
- func (p Point[T]) RelationshipToRectangle(r Rectangle[T], opts ...Option) Relationship
- func (p Point[T]) Rotate(pivot Point[T], radians float64, opts ...Option) Point[float64]
- func (p Point[T]) Scale(ref Point[T], k T) Point[T]
- func (p Point[T]) String() string
- func (p Point[T]) Translate(delta Point[T]) Point[T]
- func (p Point[T]) X() T
- func (p Point[T]) Y() T
- type PointOrientation
-
type PolyTree
- func ApplyPointTransform[T, N SignedNumber](pt *PolyTree[T], transformFunc func(p Point[T]) (Point[N], error)) (*PolyTree[N], error)
- func NewPolyTree[T SignedNumber](points []Point[T], t PolygonType, opts ...NewPolyTreeOption[T]) (*PolyTree[T], error)
- func (pt *PolyTree[T]) AddChild(child *PolyTree[T]) error
- func (pt *PolyTree[T]) AddSibling(sibling *PolyTree[T]) error
- func (pt *PolyTree[T]) Area() float64
- func (pt *PolyTree[T]) AsFloat32() *PolyTree[float32]
- func (pt *PolyTree[T]) AsFloat64() *PolyTree[float64]
- func (pt *PolyTree[T]) AsInt() *PolyTree[int]
- func (pt *PolyTree[T]) AsIntRounded() *PolyTree[int]
- func (pt *PolyTree[T]) BooleanOperation(other *PolyTree[T], operation BooleanOperation) (*PolyTree[T], error)
- func (pt *PolyTree[T]) BoundingBox() Rectangle[T]
- func (pt *PolyTree[T]) Children() []*PolyTree[T]
- func (pt *PolyTree[T]) Contour() []Point[T]
- func (pt *PolyTree[T]) Edges() []LineSegment[T]
- func (pt *PolyTree[T]) Eq(other *PolyTree[T], opts ...polyTreeEqOption[T]) (bool, PolyTreeMismatch)
- func (pt *PolyTree[T]) Hull() []Point[T]
- func (pt *PolyTree[T]) IsRoot() bool
- func (pt *PolyTree[T]) Len() int
- func (pt *PolyTree[T]) Nodes(yield func(*PolyTree[T]) bool)
- func (pt *PolyTree[T]) Overlaps(other *PolyTree[T]) bool
- func (pt *PolyTree[T]) Parent() *PolyTree[T]
- func (pt *PolyTree[T]) Perimeter(opts ...Option) float64
- func (pt *PolyTree[T]) PolygonType() PolygonType
- func (pt *PolyTree[T]) RelationshipToCircle(c Circle[T], opts ...Option) map[*PolyTree[T]]Relationship
- func (pt *PolyTree[T]) RelationshipToLineSegment(l LineSegment[T], opts ...Option) map[*PolyTree[T]]Relationship
- func (pt *PolyTree[T]) RelationshipToPoint(p Point[T], opts ...Option) map[*PolyTree[T]]Relationship
- func (pt *PolyTree[T]) RelationshipToPolyTree(other *PolyTree[T], opts ...Option) map[*PolyTree[T]]map[*PolyTree[T]]Relationship
- func (pt *PolyTree[T]) RelationshipToRectangle(r Rectangle[T], opts ...Option) map[*PolyTree[T]]Relationship
- func (pt *PolyTree[T]) Root() *PolyTree[T]
- func (pt *PolyTree[T]) Rotate(pivot Point[T], radians float64, opts ...Option) *PolyTree[float64]
- func (pt *PolyTree[T]) Scale(ref Point[T], k T) *PolyTree[T]
- func (pt *PolyTree[T]) Siblings() []*PolyTree[T]
- func (pt *PolyTree[T]) String() string
- func (pt *PolyTree[T]) Translate(delta Point[T]) *PolyTree[T]
- type PolyTreeMismatch
- type PolygonType
-
type Rectangle
- func NewRectangle[T SignedNumber](points []Point[T]) Rectangle[T]
- func NewRectangleFromImageRect(r image.Rectangle) Rectangle[int]
- func (r Rectangle[T]) Area() T
- func (r Rectangle[T]) AsFloat32() Rectangle[float32]
- func (r Rectangle[T]) AsFloat64() Rectangle[float64]
- func (r Rectangle[T]) AsInt() Rectangle[int]
- func (r Rectangle[T]) AsIntRounded() Rectangle[int]
- func (r Rectangle[T]) ContainsPoint(p Point[T]) bool
- func (r Rectangle[T]) Contour() []Point[T]
- func (r Rectangle[T]) Edges() []LineSegment[T]
- func (r Rectangle[T]) Eq(other Rectangle[T]) bool
- func (r Rectangle[T]) Height() T
- func (r Rectangle[T]) Perimeter() T
- func (r Rectangle[T]) RelationshipToCircle(c Circle[T], opts ...Option) Relationship
- func (r Rectangle[T]) RelationshipToLineSegment(l LineSegment[T], opts ...Option) Relationship
- func (r Rectangle[T]) RelationshipToPoint(p Point[T], opts ...Option) Relationship
- func (r Rectangle[T]) RelationshipToPolyTree(pt *PolyTree[T], opts ...Option) map[*PolyTree[T]]Relationship
- func (r Rectangle[T]) RelationshipToRectangle(other Rectangle[T], opts ...Option) Relationship
- func (r Rectangle[T]) Scale(ref Point[T], k T) Rectangle[T]
- func (r Rectangle[T]) ScaleHeight(factor float64) Rectangle[float64]
- func (r Rectangle[T]) ScaleWidth(factor float64) Rectangle[float64]
- func (r Rectangle[T]) String() string
- func (r Rectangle[int]) ToImageRect() image.Rectangle
- func (r Rectangle[T]) Translate(p Point[T]) Rectangle[T]
- func (r Rectangle[T]) Width() T
- type ReflectionAxis
- type Relationship
- type SignedNumber
- type SweepLineResult
func EnsureClockwise[T SignedNumber](points []Point[T])
EnsureClockwise ensures that a slice of points representing a polygon is ordered in a clockwise direction.
This function checks the orientation of the points based on the signed area of the polygon. If the signed area is positive, indicating a counterclockwise orientation, the function reverses the order of the points (in-place) to make them clockwise. If the points are already clockwise, no changes are made.
Parameters:
- points ([]Point[T]): A slice of points representing the vertices of a polygon. The points are assumed to form a closed loop or define a valid polygon.
Behavior:
- Calculates the signed area of the polygon using SignedArea2X.
- If the signed area is positive (counterclockwise orientation), reverses the order of the points.
- If the signed area is negative or zero (already clockwise or degenerate polygon), does nothing.
Notes:
- This function modifies the input slice of points in place.
- A zero area polygon is considered already clockwise and is left unchanged.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Define points in counter-clockwise order
points := []geom2d.Point[int]{
geom2d.NewPoint(0, 0),
geom2d.NewPoint(4, 0),
geom2d.NewPoint(4, 4),
}
// Re-order points in clockwise order
geom2d.EnsureClockwise(points)
// Print re-ordered points
fmt.Println(points)
}
[Point[(4, 4)] Point[(4, 0)] Point[(0, 0)]]
func EnsureCounterClockwise[T SignedNumber](points []Point[T])
EnsureCounterClockwise ensures that a slice of points representing a polygon is ordered in a counterclockwise direction.
This function checks the orientation of the points based on the signed area of the polygon. If the signed area is negative, indicating a clockwise orientation, the function reverses the order of the points (in-place) to make them counterclockwise. If the points are already counterclockwise, no changes are made.
Parameters:
- points ([]Point[T]): A slice of points representing the vertices of a polygon. The points are assumed to form a closed loop or define a valid polygon.
Behavior:
- Calculates the signed area of the polygon using SignedArea2X.
- If the signed area is negative (clockwise orientation), reverses the order of the points.
- If the signed area is positive or zero (already counterclockwise or degenerate polygon), does nothing.
Notes:
- This function modifies the input slice of points in place.
- A zero area polygon is considered already counterclockwise and is left unchanged.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Define points in clockwise order
points := []geom2d.Point[int]{
geom2d.NewPoint(0, 0),
geom2d.NewPoint(4, 4),
geom2d.NewPoint(4, 0),
}
// Re-order points in counter-clockwise order
geom2d.EnsureCounterClockwise(points)
// Print re-ordered points
fmt.Println(points)
}
[Point[(4, 0)] Point[(4, 4)] Point[(0, 0)]]
func IsWellFormedPolygon[T SignedNumber](points []Point[T]) (bool, error)
IsWellFormedPolygon checks whether a given set of points defines a well-formed polygon. A polygon is considered well-formed if:
- It has at least 3 points.
- It has a non-zero area.
- It does not contain any self-intersecting edges.
Parameters:
- points ([]Point[T]): A slice of Point[T] representing the vertices of the polygon.
Returns:
- A boolean indicating whether the polygon is well-formed.
- An error providing details if the polygon is not well-formed.
Example Errors:
- "polygon must have at least 3 points"
- "polygon has zero area"
- "polygon has self-intersecting edges: edge1 and edge2"
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
points := []geom2d.Point[int]{
geom2d.NewPoint(0, 0),
geom2d.NewPoint(4, 0),
geom2d.NewPoint(4, 3),
geom2d.NewPoint(0, 3),
}
wellFormed, err := geom2d.IsWellFormedPolygon(points)
if wellFormed {
fmt.Println("The polygon is well-formed.")
} else {
fmt.Printf("The polygon is not well-formed: %v\n", err)
}
}
The polygon is well-formed.
func RelativeAngle[T SignedNumber](A, B Point[T], O ...Point[T]) (radians float64)
RelativeAngle calculates the angle in radians between two points, A and B, relative to an optional origin Point O. This angle is measured from the origin to the line segments connecting A and B. If no origin is provided, the function defaults to using the point (0, 0) as the origin.
Parameters:
- A: The first Point forming one side of the angle.
- B: The second Point forming the other side of the angle.
- O: An optional origin Point. If not provided, the origin defaults to (0, 0).
Returns:
- radians (float64): The angle between points A and B relative to the origin, in radians.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
"math"
)
func main() {
A := geom2d.NewPoint[int](1, 0)
B := geom2d.NewPoint[int](0, 1)
radians := geom2d.RelativeAngle(A, B) // π/2 radians
degrees := radians * 180 / math.Pi // convert to degrees
fmt.Println(degrees)
}
90
func RelativeCosineOfAngle[T SignedNumber](A, B Point[T], O ...Point[T]) float64
RelativeCosineOfAngle calculates the cosine of the angle between two points, A and B, relative to an optional origin Point O. This function returns the cosine of the angle directly, avoiding the costly math.Acos calculation, which can improve performance in applications where only the cosine value is needed.
If no origin is provided, the function defaults to using the point (0, 0) as the origin.
Parameters:
- A (Point[T]): The first Point forming one side of the angle.
- B (Point[T]): The second Point forming the other side of the angle.
- O (Point[T]): An optional origin Point. If not provided, the origin defaults to (0, 0).
Returns:
- float64: The cosine of the angle between points A and B relative to the origin.
Note:
- This function does not currently handle division by zero errors. If either vector OA or OB has zero length, a division by zero could occur. Consider adding validation if needed in such cases.
Why Would Anyone Just Need The Cosine?
By using the cosine of the angle, you can determine not just the relative angle but also its directionality and magnitude in terms of alignment. Here's why someone might want this:
- Efficient Angle Comparison
Instead of calculating the actual angle using trigonometric functions (which are computationally expensive), you can compare the cosine of angles directly. Since the cosine function is monotonic between 0 and π, you can use the cosine values to determine:
- If the vectors are pointing in the same direction (cos(θ) ≈ 1).
- If the vectors are orthogonal (cos(θ) ≈ 0).
- If the vectors are pointing in opposite directions (cos(θ) ≈ -1).
- Avoiding Floating-Point Inaccuracies
Computing the cosine of the angle avoids potential inaccuracies associated with computing the angle itself (e.g., due to limited precision when converting radians to degrees or vice versa).
- Applications in Sorting
If you are sorting points or vectors relative to a reference direction, you can use RelativeCosineOfAngle to order them based on their angular relationship. This is particularly useful in computational geometry tasks like:
- Constructing a convex hull.
- Ordering vertices for polygon operations.
- Determining Vector Orientation
You can use the cosine value to determine the degree of alignment between two vectors, which is helpful in:
- Physics simulations (e.g., checking if a force vector aligns with a velocity vector).
- Rendering graphics (e.g., checking the alignment of a surface normal with a light source).
- Geometric Insight
In geometry, understanding the relative cosine helps in:
- Classifying angles (acute, obtuse, or right) without explicitly calculating them.
- Performing dot product-based calculations indirectly, as cos(θ) is derived from the dot product normalized by the vectors' magnitudes.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
A := geom2d.NewPoint(1, 0)
B := geom2d.NewPoint(0, 1)
cosine := geom2d.RelativeCosineOfAngle(A, B) // cosine is 0 for a 90-degree angle
fmt.Println(cosine)
}
0
func SignedArea2X[T SignedNumber](points []Point[T]) T
SignedArea2X computes twice the signed area of a polygon defined by the given points.
The function calculates the signed area of the polygon using the Shoelace Formula, adapted to sum the areas of triangles formed by consecutive points. The result is twice the actual signed area, which avoids introducing fractional values and simplifies calculations with integer-based coordinate types.
A positive signed area indicates that the points are ordered counterclockwise, while a negative signed area indicates clockwise order. This function is commonly used to determine the orientation of a polygon or to compute its area efficiently.
Parameters:
- points ([]Point[T]): A slice of Point values representing the vertices of the polygon in order. The polygon is assumed to be closed, meaning the first Point connects to the last Point.
Returns:
- The signed area multiplied by 2 (hence "2X"). Returns 0 if the number of points is fewer than 3, as a valid polygon cannot be formed.
Notes:
- The function assumes the input points form a simple polygon (no self-intersections).
- If the points are not in order, the result may not represent the true orientation or area of the intended polygon.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
points := []geom2d.Point[int]{
geom2d.NewPoint(0, 0),
geom2d.NewPoint(4, 0),
geom2d.NewPoint(4, 3),
}
signedArea := geom2d.SignedArea2X(points)
fmt.Println(signedArea)
}
12
BooleanOperation defines the types of Boolean operations that can be performed on polygons. These operations are fundamental in computational geometry for combining or modifying shapes.
type BooleanOperation uint8
Valid values for BooleanOperation
const (
// BooleanUnion represents the union operation, which combines two polygons into a single polygon
// that encompasses the areas of both input polygons. Overlapping regions are merged.
BooleanUnion BooleanOperation = iota
// BooleanIntersection represents the intersection operation, which computes the region(s)
// where two polygons overlap. The result is one or more polygons that covers only the shared area.
BooleanIntersection
// BooleanSubtraction represents the subtraction operation, which removes the area of one polygon
// from another. The result is one or more polygons representing the area of the first polygon excluding
// the overlapping region with the second polygon.
BooleanSubtraction
)
func (o *BooleanOperation) String() string
String returns the string representation of a BooleanOperation. This is useful for debugging and logging purposes.
Supported operations: - BooleanUnion: "BooleanUnion" - BooleanIntersection: "BooleanIntersection" - BooleanSubtraction: "BooleanSubtraction"
Panics if an unsupported BooleanOperation is encountered.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
op := geom2d.BooleanUnion
fmt.Println(op.String())
op = geom2d.BooleanIntersection
fmt.Println(op.String())
op = geom2d.BooleanSubtraction
fmt.Println(op.String())
}
BooleanUnion
BooleanIntersection
BooleanSubtraction
Circle represents a circle in 2D space with a center point and a radius.
The Circle type provides methods for calculating its circumference and area, determining if a point lies within the circle, checking if a line segment intersects the circle, and checking the relationship between the circle and other geometric shapes.
type Circle[T SignedNumber] struct {
// contains filtered or unexported fields
}
func NewCircle[T SignedNumber](center Point[T], radius T) Circle[T]
NewCircle creates a new Circle with the specified center point and radius.
Parameters:
- center (Point[T]): The center Point of the Circle.
- radius (T): The radius of the circle, of generic type T, which must satisfy the SignedNumber constraint.
Returns:
- Circle[T]: A new Circle with the specified center and radius.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
center := geom2d.NewPoint(3, 4) // Note type inference as int not explicitly specified
radius := 5
circle := geom2d.NewCircle(center, radius) // Creates a Circle with center (3, 4) and radius 5
fmt.Println(circle)
}
Circle[center=(3, 4), radius=5]
func (c Circle[T]) Area() float64
Area calculates the area of the circle.
Returns:
- float64: The area of the circle, computed as π * radius^2.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
"math"
)
func main() {
circle := geom2d.NewCircle(geom2d.NewPoint(3, 4), 5) // Creates a Circle with center (3, 4) and radius 5
area := circle.Area() // area = π*5*5 = 25π
areaRounded := math.Round(area*100) / 100 // Round to two digits
fmt.Println(areaRounded)
}
78.54
func (c Circle[T]) AsFloat32() Circle[float32]
AsFloat32 converts the Circle's center coordinates and radius to the float32 type, returning a new Circle[float32]. This method is useful for cases where higher precision or floating-point arithmetic is required.
Returns:
- Circle[float32]: A new Circle with the center coordinates and radius converted to float64.
func (c Circle[T]) AsFloat64() Circle[float64]
AsFloat64 converts the Circle's center coordinates and radius to the float64 type, returning a new Circle[float64]. This method is useful for cases where higher precision or floating-point arithmetic is required.
Returns:
- Circle[float64]: A new Circle with the center coordinates and radius converted to float64.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
intCircle := geom2d.NewCircle(geom2d.NewPoint(3, 4), 5)
fltCircle := intCircle.AsFloat64()
fmt.Printf("intCircle is %v of type: %T\n", intCircle, intCircle)
fmt.Printf("fltCircle is %v of type: %T\n", fltCircle, fltCircle)
}
intCircle is Circle[center=(3, 4), radius=5] of type: geom2d.Circle[int]
fltCircle is Circle[center=(3, 4), radius=5] of type: geom2d.Circle[float64]
func (c Circle[T]) AsInt() Circle[int]
AsInt converts the Circle's center coordinates and radius to the int type by truncating any decimal values. This method is useful when integer values are needed, such as for pixel-based or grid-based calculations.
Returns:
- Circle[int]: A new Circle with center coordinates and radius converted to int by truncating any decimal portion.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
fltCircle := geom2d.NewCircle(geom2d.NewPoint(3.7, 4.9), 5.6)
intCircle := fltCircle.AsInt()
fmt.Printf("fltCircle is %v of type: %T\n", fltCircle, fltCircle)
fmt.Printf("intCircle is %v of type: %T\n", intCircle, intCircle)
}
fltCircle is Circle[center=(3.7, 4.9), radius=5.6] of type: geom2d.Circle[float64]
intCircle is Circle[center=(3, 4), radius=5] of type: geom2d.Circle[int]
func (c Circle[T]) AsIntRounded() Circle[int]
AsIntRounded converts the Circle's center coordinates and radius to the int type by rounding to the nearest integer. This method is useful when integer values are needed and rounding provides a more accurate representation compared to truncation.
Returns:
- Circle[int]: A new Circle with center coordinates and radius converted to int by rounding to the nearest integer.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
fltCircle := geom2d.NewCircle(geom2d.NewPoint(3.7, 4.2), 5.6)
intCircle := fltCircle.AsIntRounded()
fmt.Printf("fltCircle is %v of type: %T\n", fltCircle, fltCircle)
fmt.Printf("intCircle is %v of type: %T\n", intCircle, intCircle)
}
fltCircle is Circle[center=(3.7, 4.2), radius=5.6] of type: geom2d.Circle[float64]
intCircle is Circle[center=(4, 4), radius=6] of type: geom2d.Circle[int]
func (c Circle[T]) BoundingBox() Rectangle[T]
BoundingBox calculates the axis-aligned bounding box (AABB) of the circle.
The bounding box is the smallest rectangle, aligned with the coordinate axes, that completely encloses the circle. This is useful for collision detection, spatial partitioning, and other geometric operations.
Returns:
- Rectangle[T]: The axis-aligned bounding box that encloses the circle.
Notes:
- The bounding box is a rectangle defined by the four corner points derived from the circle's center and radius.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create a circle centered at (10, 10) with a radius of 5
circle := geom2d.NewCircle(geom2d.NewPoint(10, 10), 5)
// Calculate its bounding box
boundingBox := circle.BoundingBox()
// Output the bounding box
fmt.Println("Bounding box:", boundingBox)
}
Bounding box: Rectangle[(5, 5), (15, 5), (15, 15), (5, 15)]
func (c Circle[int]) Bresenham(yield func(Point[int]) bool)
Bresenham generates all points on the perimeter of a circle using Bresenham's circle-drawing algorithm.
This method is typically used for rasterized circle rendering.
The function is designed to be used with a for-loop, and thus takes a callback yield that processes each point. If the callback returns false at any point (if the calling for-loop is terminated, for example), the function halts further generation.
This algorithm utilizes integer arithmetic to efficiently calculate the points on the circle, making it suitable for rendering or other grid-based operations.
Parameters:
- yield (func(Point[int]) bool): A function that processes each generated point. Returning false will stop further point generation.
Note: This implementation requires the circle is defined with integer-type coordinates.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Define a circle with center (5, 5) and radius 3.
c := geom2d.NewCircle(geom2d.NewPoint(5, 5), 3)
// Generate the points on the circle's perimeter.
c.Bresenham(func(p geom2d.Point[int]) bool {
fmt.Println(p)
return true // Continue generating points
})
}
Point[(5, 8)]
Point[(5, 8)]
Point[(5, 2)]
Point[(5, 2)]
Point[(8, 5)]
Point[(2, 5)]
Point[(8, 5)]
Point[(2, 5)]
Point[(6, 8)]
Point[(4, 8)]
Point[(6, 2)]
Point[(4, 2)]
Point[(8, 6)]
Point[(2, 6)]
Point[(8, 4)]
Point[(2, 4)]
Point[(7, 7)]
Point[(3, 7)]
Point[(7, 3)]
Point[(3, 3)]
Point[(7, 7)]
Point[(3, 7)]
Point[(7, 3)]
Point[(3, 3)]
func (c Circle[T]) Center() Point[T]
Center returns the center Point of the Circle.
Returns:
- Point[T]: The center Point of the Circle.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
circle := geom2d.NewCircle(geom2d.NewPoint(3, 4), 5)
center := circle.Center()
fmt.Println(center)
}
Point[(3, 4)]
func (c Circle[T]) Circumference() float64
Circumference calculates the circumference of the circle.
Returns:
- float64: The circumference of the circle, computed as 2 * π * radius.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
"math"
)
func main() {
circle := geom2d.NewCircle(geom2d.NewPoint(3, 4), 5)
circumference := circle.Circumference()
circumferenceRounded := math.Round(circumference*100) / 100
fmt.Println(circumferenceRounded)
}
31.42
func (c Circle[T]) Eq(other Circle[T], opts ...Option) bool
Eq determines whether the calling Circle (c) is equal to another Circle (other), either exactly (default) or approximately using an epsilon threshold.
Parameters
- other: The Circle to compare with the calling Circle.
- opts: A variadic slice of Option functions to customize the equality check. WithEpsilon(epsilon float64): Specifies a tolerance for comparing the center coordinates and radii of the circles, allowing for robust handling of floating-point precision errors.
Behavior
- The function checks whether the center points of the two circles are equal (using the Point.Eq method of the Point type) and whether their radii are equal.
- If WithEpsilon is provided, the comparison allows for small differences in the radius values and center coordinates within the epsilon threshold.
Returns
- bool: true if the center coordinates and radius of the two circles are equal (or approximately equal within epsilon); otherwise, false.
Notes:
- Approximate equality is particularly useful when comparing circles with floating-point coordinates or radii, where small precision errors might otherwise cause inequality.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
c1 := geom2d.NewCircle(geom2d.NewPoint(3, 4), 5)
c2 := geom2d.NewCircle(geom2d.NewPoint(3, 4), 5)
isEqual := c1.Eq(c2)
fmt.Println(isEqual)
}
true
Example (Epsilon)
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
c1 := geom2d.NewCircle(geom2d.NewPoint[float64](3, 4), 5)
c2 := geom2d.NewCircle(geom2d.NewPoint(3.0001, 4.0001), 5.0001)
isApproximatelyEqual := c1.Eq(c2, geom2d.WithEpsilon(1e-3))
fmt.Println(isApproximatelyEqual)
}
true
func (c Circle[T]) Radius() T
Radius returns the radius of the Circle.
Returns:
- T: The radius of the Circle.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
circle := geom2d.NewCircle(geom2d.NewPoint(3, 4), 5)
radius := circle.Radius()
fmt.Println(radius)
}
5
func (c Circle[T]) RelationshipToCircle(other Circle[T], opts ...Option) Relationship
RelationshipToCircle determines the spatial relationship between two circles.
This function evaluates the relationship between the current circle and another circle by comparing their center points and radii. The possible relationships include:
- RelationshipEqual: The circles are identical.
- RelationshipContainedBy: The current circle is completely contained within the other circle.
- RelationshipContains: The current circle completely contains the other circle.
- RelationshipIntersection: The circles overlap, including tangency.
- RelationshipDisjoint: The circles do not overlap.
Parameters:
- other (Circle[T]): The circle to compare against the current circle.
- opts: A variadic slice of Option functions to customize the equality check. WithEpsilon(epsilon float64): Specifies a tolerance for comparing the center coordinates and radii of the circles, allowing for robust handling of floating-point precision errors.
Returns:
- Relationship: A constant representing the relationship between the circles.
Behavior:
- The function first checks for equality by comparing center points and radii.
- It then checks for containment by comparing the distance between centers and radii.
- Intersection is detected if the distance between centers is less than or equal to the sum of the radii.
- If no other relationship is found, the circles are considered disjoint.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
circle1 := geom2d.NewCircle(geom2d.NewPoint(0, 0), 10)
circle2 := geom2d.NewCircle(geom2d.NewPoint(0, 0), 10)
circle3 := geom2d.NewCircle(geom2d.NewPoint(3, 0), 5)
circle4 := geom2d.NewCircle(geom2d.NewPoint(20, 0), 5)
fmt.Println(circle1.RelationshipToCircle(circle2))
fmt.Println(circle1.RelationshipToCircle(circle3))
fmt.Println(circle3.RelationshipToCircle(circle1))
fmt.Println(circle1.RelationshipToCircle(circle4))
}
RelationshipEqual
RelationshipContains
RelationshipContainedBy
RelationshipDisjoint
func (c Circle[T]) RelationshipToLineSegment(l LineSegment[T], opts ...Option) Relationship
RelationshipToLineSegment determines the spatial relationship between the current Circle and a given LineSegment.
This function evaluates the relationship between the circle and the line segment, which can be one of the following:
- RelationshipDisjoint: The line segment lies entirely outside the circle.
- RelationshipIntersection: The line segment intersects the circle's boundary.
- RelationshipContains: The line segment is fully contained within the circle.
This method internally calls LineSegment.RelationshipToCircle, flipping the containment direction to align with the perspective of the circle.
Parameters:
- l (LineSegment[T]): The line segment to compare against the circle.
- opts: A variadic slice of Option functions to customize the equality check. WithEpsilon(epsilon float64): Specifies a tolerance for comparing the center coordinates and radii of the circles, allowing for robust handling of floating-point precision errors.
Returns:
- Relationship: A constant representing the relationship between the circle and the line segment.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
circle := geom2d.NewCircle(geom2d.NewPoint(0, 0), 10)
line1 := geom2d.NewLineSegment(geom2d.NewPoint(15, 0), geom2d.NewPoint(20, 0)) // Outside
line2 := geom2d.NewLineSegment(geom2d.NewPoint(0, -15), geom2d.NewPoint(0, 15)) // Intersects
line3 := geom2d.NewLineSegment(geom2d.NewPoint(5, 5), geom2d.NewPoint(-5, -5)) // Fully contained
fmt.Println(circle.RelationshipToLineSegment(line1))
fmt.Println(circle.RelationshipToLineSegment(line2))
fmt.Println(circle.RelationshipToLineSegment(line3))
}
RelationshipDisjoint
RelationshipIntersection
RelationshipContains
func (c Circle[T]) RelationshipToPoint(p Point[T], opts ...Option) Relationship
RelationshipToPoint determines the spatial relationship between the current Circle and a given Point.
This function evaluates the relationship between the circle and the point, which can be one of the following:
- RelationshipDisjoint: The point lies outside the circle.
- RelationshipIntersection: The point lies exactly on the circle's boundary.
- RelationshipContains: The point lies inside the circle.
This method internally calls Point.RelationshipToCircle, flipping the containment direction to align with the perspective of the circle.
Parameters:
- p (Point[T]): The point to compare against the circle.
- opts: A variadic slice of Option functions to customize the equality check. WithEpsilon(epsilon float64): Specifies a tolerance for comparing the center coordinates and radii of the circles, allowing for robust handling of floating-point precision errors.
Returns:
- Relationship: A constant representing the relationship between the circle and the point.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
circle := geom2d.NewCircle(geom2d.NewPoint(0, 0), 10)
point1 := geom2d.NewPoint(15, 0) // Outside the circle
point2 := geom2d.NewPoint(10, 0) // On the circle boundary
point3 := geom2d.NewPoint(5, 0) // Inside the circle
fmt.Println(circle.RelationshipToPoint(point1))
fmt.Println(circle.RelationshipToPoint(point2))
fmt.Println(circle.RelationshipToPoint(point3))
}
RelationshipDisjoint
RelationshipIntersection
RelationshipContains
func (c Circle[T]) RelationshipToPolyTree(pt *PolyTree[T], opts ...Option) map[*PolyTree[T]]Relationship
RelationshipToPolyTree determines the spatial relationships between the Circle and the polygons in the given PolyTree.
This method evaluates whether the circle intersects, contains, or is contained by each polygon in the PolyTree. It uses a doubled representation of the Circle to align with the doubled points in the PolyTree for robust computations.
Parameters:
- pt (*PolyTree[T]): The PolyTree whose polygons will be compared to the Circle.
- opts: A variadic slice of Option functions to customize the equality check. WithEpsilon(epsilon float64): Specifies a tolerance for comparing the center coordinates and radii of the circles, allowing for robust handling of floating-point precision errors.
Behavior:
- For each polygon in the PolyTree, the function iterates through its edges and checks the relationship to the Circle.
- Intersection is checked first; if any edge of the polygon intersects the circle, the relationship is marked as RelationshipIntersection.
- If all edges of the polygon lie within the circle's radius, the relationship is marked as RelationshipContains.
- If the circle's center lies within the polygon and its minimum distance to any edge is greater than its radius, the relationship is marked as RelationshipContainedBy.
- If none of the above conditions are satisfied, the relationship is marked as RelationshipDisjoint.
Returns:
- map[*PolyTree[T]]Relationship: A map where each key is a pointer to a polygon in the PolyTree, and the value is the relationship between the Circle and that polygon.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create a polygon as a PolyTree
root, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(0, 0),
geom2d.NewPoint(0, 100),
geom2d.NewPoint(100, 100),
geom2d.NewPoint(100, 0),
}, geom2d.PTSolid)
// Note: While errors are ignored in this example for simplicity, it is important to handle errors properly in
// production code to ensure robustness and reliability.
// Create a circle
circle := geom2d.NewCircle(geom2d.NewPoint(50, 50), 10)
// Check relationships
relationships := circle.RelationshipToPolyTree(root)
// Output the relationship
fmt.Printf("Circle's relationship to root polygon is: %v\n", relationships[root])
}
Circle's relationship to root polygon is: RelationshipContainedBy
func (c Circle[T]) RelationshipToRectangle(r Rectangle[T], opts ...Option) Relationship
RelationshipToRectangle determines the spatial relationship between the circle and the rectangle.
This function evaluates whether the circle is:
- Disjoint from the rectangle (no overlap or touching),
- Intersects with the rectangle (crosses its boundary),
- Fully contains the rectangle (encloses it entirely),
- Fully contained by the rectangle (is completely inside the rectangle).
Parameters:
- r (Rectangle[T]): The rectangle to compare with the circle.
- opts: A variadic slice of Option functions to customize the behavior of the relationship check. WithEpsilon(epsilon float64): Specifies a tolerance for floating-point precision.
Behavior:
- The function checks each edge of the rectangle for potential intersections with the circle.
- If all edges of the rectangle are fully contained within the circle, it returns RelationshipContains.
- If the rectangle fully contains the circle (the circle’s center is inside the rectangle, and the circle does not extend beyond any edge), it returns RelationshipContainedBy.
- If none of these conditions are met, it determines whether the circle and rectangle are disjoint.
Returns:
Relationship: One of the following constants:
- RelationshipDisjoint: The circle and rectangle are entirely separate.
- RelationshipIntersection: The circle intersects with one or more edges of the rectangle.
- RelationshipContains: The circle completely encloses the rectangle.
- RelationshipContainedBy: The circle is fully contained within the rectangle.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Define a rectangle
rect := geom2d.NewRectangle([]geom2d.Point[int]{
geom2d.NewPoint(0, 0),
geom2d.NewPoint(100, 0),
geom2d.NewPoint(100, 100),
geom2d.NewPoint(0, 100),
})
// Define a circle
circle := geom2d.NewCircle(geom2d.NewPoint(50, 50), 30)
// Determine the relationship
rel := circle.RelationshipToRectangle(rect)
// Print the result
fmt.Println("Relationship:", rel)
}
Relationship: RelationshipContainedBy
func (c Circle[T]) Rotate(pivot Point[T], radians float64, opts ...Option) Circle[float64]
Rotate rotates the Circle's center around a specified pivot Point by a given angle in radians counterclockwise, while keeping the radius unchanged. Optionally, an epsilon threshold can be applied to adjust the precision of the resulting coordinates.
Parameters:
- pivot (Point[T]): The Point around which to rotate the circle's center.
- radians: The rotation angle in radians (counterclockwise).
- opts: A variadic slice of Option functions to customize the behavior of the rotation. WithEpsilon(epsilon float64): Specifies a tolerance for snapping the resulting center coordinates to cleaner values, improving robustness in floating-point calculations.
Returns:
- Circle[float64]: A new Circle with the center rotated around the pivot Point by the specified angle, and with the radius unchanged.
Behavior:
- The function rotates the circle's center point around the given pivot by the specified angle using the Point.Rotate method.
- The rotation is performed in a counterclockwise direction relative to the pivot point.
- The radius remains unchanged in the resulting Circle.
- If WithEpsilon is provided, epsilon adjustments are applied to the rotated coordinates to handle floating-point precision errors.
Notes:
- Epsilon adjustment is particularly useful when the rotation involves floating-point calculations that could result in minor inaccuracies.
- The returned Circle always has a center with float64 coordinates, ensuring precision regardless of the coordinate type of the original Circle.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
"math"
)
func main() {
pivot := geom2d.NewPoint(1, 1)
circle := geom2d.NewCircle(geom2d.NewPoint(3, 3), 5)
angle := math.Pi / 2 // Rotate 90 degrees
rotated := circle.Rotate(pivot, angle, geom2d.WithEpsilon(1e-10)).AsInt()
fmt.Println(rotated)
}
Circle[center=(-1, 3), radius=5]
func (c Circle[T]) Scale(factor T) Circle[T]
Scale scales the radius of the circle by a scalar factor.
Parameters:
- factor (T): The factor by which to scale the radius.
Returns:
- Circle[T]: A new circle with the radius scaled by the specified factor.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
circle := geom2d.NewCircle(geom2d.NewPoint(0, 0), 5)
scaled := circle.Scale(2).AsInt()
fmt.Println(scaled)
}
Circle[center=(0, 0), radius=10]
func (c Circle[T]) String() string
String returns a string representation of the Circle, including its center coordinates and radius. This is useful for debugging and logging.
Returns:
- string: A string representation of the Circle in the format "Circle[center=(x, y), radius=r]".
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
circle := geom2d.NewCircle(geom2d.NewPoint(3, 4), 5)
fmt.Println(circle.String())
}
Circle[center=(3, 4), radius=5]
func (c Circle[T]) Translate(v Point[T]) Circle[T]
Translate moves the circle by a specified vector (given as a Point).
This method shifts the circle's center by the given vector v, effectively translating the circle's position in the 2D plane. The radius of the circle remains unchanged.
Parameters:
- v (Point[T]): The vector by which to translate the circle's center.
Returns:
- Circle[T]: A new Circle translated by the specified vector.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
circle := geom2d.NewCircle(geom2d.NewPoint(3, 4), 5)
translationVector := geom2d.NewPoint(2, 3)
translatedCircle := circle.Translate(translationVector)
fmt.Println(translatedCircle)
}
Circle[center=(5, 7), radius=5]
LineSegment represents a line segment in a 2D space, defined by two endpoints, the start Point and end Point.
The generic type parameter T must satisfy the SignedNumber constraint, allowing the segment to use various numeric types such as int or float64 for its coordinates.
type LineSegment[T SignedNumber] struct {
// contains filtered or unexported fields
}
func NewLineSegment[T SignedNumber](start, end Point[T]) LineSegment[T]
NewLineSegment creates a new line segment from two endpoints, a start Point and end Point.
This constructor function initializes a LineSegment with the specified starting and ending points. The generic type parameter "T" must satisfy the SignedNumber constraint, allowing various numeric types (such as int or float64) to be used for the segment’s coordinates.
Parameters:
- start (Point[T]): The starting Point of the LineSegment.
- end (Point[T]): The ending Point of the LineSegment.
Returns:
- LineSegment[T] - A new line segment defined by the start and end points.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
segment := geom2d.NewLineSegment(geom2d.NewPoint(0, 0), geom2d.NewPoint(3, 4))
fmt.Println(segment.String())
}
LineSegment[(0, 0) -> (3, 4)]
func (l LineSegment[T]) AddLineSegment(other LineSegment[T]) LineSegment[T]
AddLineSegment adds the start and end points of another line segment to this one.
This method performs an element-wise addition, where the start and end points of the other line segment are added to the corresponding start and end points of the current line segment.
Parameters:
- other (LineSegment[T]): The line segment to add to the current one.
Returns:
- LineSegment[T] - A new line segment where each endpoint is the sum of the corresponding endpoints.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
segment1 := geom2d.NewLineSegment(geom2d.NewPoint(1, 1), geom2d.NewPoint(4, 5))
segment2 := geom2d.NewLineSegment(geom2d.NewPoint(2, 3), geom2d.NewPoint(1, 2))
result := segment1.AddLineSegment(segment2)
fmt.Println(result)
}
LineSegment[(3, 4) -> (5, 7)]
func (l LineSegment[T]) AsFloat32() LineSegment[float32]
AsFloat32 converts the line segment to a LineSegment[float32] type.
This function converts both endpoints of the LineSegment l to Point[float32] values, creating a new line segment with floating-point coordinates. It is useful for precise calculations where floating-point accuracy is needed.
Returns:
- LineSegment[float32] - The line segment with both endpoints converted to float32.
func (l LineSegment[T]) AsFloat64() LineSegment[float64]
AsFloat64 converts the line segment to a LineSegment[float64] type.
This function converts both endpoints of the LineSegment l to Point[float64] values, creating a new line segment with floating-point coordinates. It is useful for precise calculations where floating-point accuracy is needed.
Returns:
- LineSegment[float64] - The line segment with both endpoints converted to float64.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create a LineSegment with integer coordinates
intSegment := geom2d.NewLineSegment(
geom2d.NewPoint(1, 2), // Start point
geom2d.NewPoint(3, 4), // End point
)
// Convert the LineSegment to float64
floatSegment := intSegment.AsFloat64()
// Print the converted LineSegment
fmt.Println("Integer LineSegment:", intSegment)
fmt.Println("Float64 LineSegment:", floatSegment)
}
Integer LineSegment: LineSegment[(1, 2) -> (3, 4)]
Float64 LineSegment: LineSegment[(1, 2) -> (3, 4)]
func (l LineSegment[T]) AsInt() LineSegment[int]
AsInt converts the line segment to a LineSegment[int] type.
This function converts both endpoints of the line segment l to Point[int] by truncating any decimal places. It is useful for converting a floating-point line segment to integer coordinates without rounding.
Returns:
- LineSegment[int] - The line segment with both endpoints converted to integer coordinates by truncation.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create a LineSegment with floating-point coordinates
line := geom2d.NewLineSegment(
geom2d.NewPoint(1.5, 2.7),
geom2d.NewPoint(3.9, 4.2),
)
// Convert the LineSegment to integer coordinates
intLine := line.AsInt()
// Output the integer LineSegment
fmt.Println(intLine)
}
LineSegment[(1, 2) -> (3, 4)]
func (l LineSegment[T]) AsIntRounded() LineSegment[int]
AsIntRounded converts the line segment to a LineSegment[int] type with rounded coordinates.
This function converts both endpoints of the line segment l to Point[int] by rounding each coordinate to the nearest integer. It is useful when you need to approximate the segment’s position with integer coordinates while minimizing the rounding error.
Returns:
- LineSegment[int] - The line segment with both endpoints converted to integer coordinates by rounding.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create a LineSegment with floating-point coordinates
line := geom2d.NewLineSegment(
geom2d.NewPoint(1.5, 2.7),
geom2d.NewPoint(3.9, 4.2),
)
// Convert the LineSegment to integer coordinates with rounding
roundedIntLine := line.AsIntRounded()
// Output the rounded integer LineSegment
fmt.Println(roundedIntLine)
}
LineSegment[(2, 3) -> (4, 4)]
func (l LineSegment[T]) BoundingBox() Rectangle[T]
BoundingBox computes the smallest axis-aligned Rectangle that fully contains the LineSegment.
Returns:
Notes:
- This method is useful for spatial queries, collision detection, or visual rendering.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create a LineSegment
line := geom2d.NewLineSegment(
geom2d.NewPoint(3, 1),
geom2d.NewPoint(6, 4),
)
// Compute the bounding box of the LineSegment
boundingBox := line.BoundingBox()
// Output the bounding box
fmt.Println(boundingBox)
}
Rectangle[(3, 1), (6, 1), (6, 4), (3, 4)]
func (l LineSegment[int]) Bresenham(yield func(Point[int]) bool)
Bresenham generates all the integer points along the line segment using Bresenham's line algorithm. It is an efficient way to rasterize a line in a grid or pixel-based system.
The function is designed to be used with a for-loop, and thus takes a callback yield that processes each point. If the callback returns false at any point (if the calling for-loop is terminated, for example), the function halts further generation.
Example use cases include: - Rendering lines in graphics applications. - Generating grid points for pathfinding.
Parameters:
- yield (func(Point[int]) bool): A function that processes each generated point. Returning false will stop further point generation.
Note: This method requires integer-type coordinates for the line segment.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Define a line segment
line := geom2d.NewLineSegment(geom2d.NewPoint(0, 0), geom2d.NewPoint(5, 3))
// Print the points generated by Bresenham's algorithm
line.Bresenham(func(p geom2d.Point[int]) bool {
fmt.Println(p)
return true
})
}
Point[(0, 0)]
Point[(1, 1)]
Point[(2, 1)]
Point[(3, 2)]
Point[(4, 2)]
Point[(5, 3)]
func (l LineSegment[T]) Center(opts ...Option) Point[float64]
Center calculates the midpoint of the line segment, optionally applying an epsilon threshold to adjust the precision of the result.
Parameters:
- opts: A variadic slice of Option functions to customize the behavior of the calculation. WithEpsilon(epsilon float64): Specifies a tolerance for snapping near-integer or near-zero results to cleaner values, improving robustness in floating-point calculations.
Behavior:
- The midpoint is calculated by averaging the x and y coordinates of the start and end points of the line segment.
- If WithEpsilon is provided, the resulting midpoint coordinates are adjusted such that small deviations due to floating-point precision errors are corrected.
Returns:
- Point[float64]: The midpoint of the line segment as a point with floating-point coordinates, optionally adjusted based on epsilon.
Notes:
- Epsilon adjustment is particularly useful when working with floating-point coordinates where minor imprecision could affect the midpoint calculation.
- The midpoint is always returned as Point[float64], ensuring precision regardless of the coordinate type of the original line segment.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
lineSegment := geom2d.NewLineSegment(geom2d.NewPoint(0, 0), geom2d.NewPoint(10, 10))
center := lineSegment.Center()
fmt.Printf("Center: %v\n", center)
}
Center: Point[(5, 5)]
func (l LineSegment[T]) ContainsPoint(p Point[T]) bool
ContainsPoint determines whether the given Point lies on the LineSegment.
This method first checks if the Point is collinear with the endpoints of the LineSegment using an Orientation. If the Point is not collinear, it cannot be on the segment. If the Point is collinear, the function then verifies if the Point lies within the bounding box defined by the segment's endpoints.
Parameters:
Returns:
- bool: true if the Point lies on the LineSegment, false otherwise.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
lineSegment := geom2d.NewLineSegment(geom2d.NewPoint(0, 0), geom2d.NewPoint(10, 10))
pointOnSegment := geom2d.NewPoint(5, 5)
pointOffSegment := geom2d.NewPoint(7, 8)
fmt.Printf("Point on segment: %v\n", lineSegment.ContainsPoint(pointOnSegment))
fmt.Printf("Point off segment: %v\n", lineSegment.ContainsPoint(pointOffSegment))
}
Point on segment: true
Point off segment: false
func (l LineSegment[T]) DistanceToLineSegment(other LineSegment[T], opts ...Option) float64
DistanceToLineSegment calculates the minimum distance between two line segments, l and other.
If the segments intersect or touch at any point, the function returns 0, as the distance is effectively zero. Otherwise, it calculates the shortest distance by considering distances between segment endpoints and the perpendicular projections of each endpoint onto the opposite segment.
Parameters:
- other (LineSegment[T]): The line segment to compare with l.
- opts: A variadic slice of Option functions to customize the behavior of the calculation. WithEpsilon(epsilon float64): Specifies a tolerance for snapping near-zero results to zero or for handling small floating-point deviations in distance calculations.
Behavior:
- The function checks whether the segments intersect or touch. If so, the distance is immediately returned as 0.
- For non-intersecting segments, the function calculates the shortest distance using the following steps: 1. Compute direct distances between the endpoints of l and other. 2. Compute distances to the perpendicular projections of each endpoint onto the opposite segment. 3. Track the minimum distance among all calculations and return this value.
- If WithEpsilon is provided, epsilon adjustments are applied to the calculated distances and projections to ensure robustness against floating-point precision errors.
Returns:
- float64: The minimum distance between the two segments. If the segments intersect or touch, this value is 0.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
"math"
)
func main() {
segmentAB := geom2d.NewLineSegment(geom2d.NewPoint(0, 0), geom2d.NewPoint(2, 2))
segmentCD := geom2d.NewLineSegment(geom2d.NewPoint(3, 3), geom2d.NewPoint(5, 5))
// Default behavior (no epsilon adjustment)
distance := segmentAB.DistanceToLineSegment(segmentCD)
distanceRounded := math.Round(distance*1000) / 1000
fmt.Println(distanceRounded)
}
1.414
func (l LineSegment[T]) DistanceToPoint(p Point[T], opts ...Option) float64
DistanceToPoint calculates the orthogonal (shortest) distance from a specified Point p to the LineSegment l. This distance is determined by projecting the Point p onto the LineSegment and measuring the distance from p to the projected Point.
Parameters:
- p (Point[T]): The Point for which the distance to the LineSegment l is calculated.
- opts: A variadic slice of Option functions to customize the behavior of the calculation. WithEpsilon(epsilon float64): Specifies a tolerance for snapping near-zero results to zero or handling small floating-point deviations in distance calculations.
Behavior:
- The function computes the projection of p onto the given LineSegment l. This is the closest point on l to p, whether it lies within the segment bounds or at an endpoint.
- The orthogonal distance is then calculated as the Euclidean distance between p and the projected point.
- If WithEpsilon is provided, epsilon adjustments are applied to the calculated distance to ensure robustness against floating-point precision errors.
Returns:
- float64: The shortest distance between the Point p and the LineSegment l, optionally adjusted based on epsilon.
Notes:
- This function leverages the Point.DistanceToLineSegment method to perform the calculation, ensuring precision and consistency across related operations.
- Epsilon adjustment is particularly useful for applications involving floating-point data, where small deviations can affect the accuracy of results.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
lineSegment := geom2d.NewLineSegment(geom2d.NewPoint(0, 0), geom2d.NewPoint(10, 0))
point := geom2d.NewPoint(5, 5)
distance := lineSegment.DistanceToPoint(point)
fmt.Printf("Distance from point to line segment: %.2f\n", distance)
}
Distance from point to line segment: 5.00
func (l LineSegment[T]) End() Point[T]
End returns the ending Point of the line segment.
This function provides access to the ending Point of the line segment l, typically representing the endpoint of the segment.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
lineSegment := geom2d.NewLineSegment(geom2d.NewPoint(1, 2), geom2d.NewPoint(3, 4))
fmt.Println(lineSegment.End())
}
Point[(3, 4)]
func (l LineSegment[T]) Eq(other LineSegment[T], opts ...Option) bool
Eq checks if two line segments are equal by comparing their start and end points. Equality can be evaluated either exactly (default) or approximately using an epsilon threshold.
Parameters:
- other (LineSegment[T]): The line segment to compare with the current line segment.
- opts: A variadic slice of Option functions to customize the equality check. WithEpsilon(epsilon float64): Specifies a tolerance for comparing the start and end points of the line segments. If the absolute difference between the coordinates of the points is less than epsilon, they are considered equal.
Behavior:
- By default, the function performs an exact equality check, returning true only if both the start and end points of l and other are identical.
- If WithEpsilon is provided, the function performs an approximate equality check, considering the points equal if their coordinate differences are within the specified epsilon threshold.
Returns:
- bool: Returns true if both line segments have identical (or approximately equal with epsilon) start and end points; otherwise, false.
Notes:
- Approximate equality is useful when comparing line segments with floating-point coordinates, where small precision errors might otherwise cause inequality.
- This function relies on the Point.Eq method, which supports epsilon adjustments.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
segment1 := geom2d.NewLineSegment(geom2d.NewPoint(1.0, 1.0), geom2d.NewPoint(4.0, 5.0))
segment2 := geom2d.NewLineSegment(geom2d.NewPoint(1.0, 1.0), geom2d.NewPoint(4.0, 5.0))
fmt.Println(segment1.Eq(segment2))
}
true
Example (Epsilon)
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Approximate equality with epsilon
segment1 := geom2d.NewLineSegment(geom2d.NewPoint(1.0, 1.0), geom2d.NewPoint(4.0, 5.0))
segment3 := geom2d.NewLineSegment(geom2d.NewPoint(1.00001, 1.00001), geom2d.NewPoint(4.00001, 5.00001))
fmt.Println(segment1.Eq(segment3, geom2d.WithEpsilon(1e-4)))
}
true
func (l LineSegment[T]) IntersectionGeometry(other LineSegment[T], opts ...Option) LineSegmentIntersectionResult[float64]
IntersectionPoint calculates the intersection Point between two line segments, if one exists.
This method checks if the LineSegment l and LineSegment other intersect within their boundaries and, if so, calculates and returns the intersection point. If the segments do not intersect or are parallel, it returns a zero-value Point and false.
It uses the parametric form of the line segments to solve for intersection parameters t and u. If t and u are both in the range [0, 1], the intersection point lies within the bounds of both segments.
Parameters:
- other (LineSegment[T]): The second line segment to check for an intersection.
Returns:
- Point[T]: The intersection point.
- bool: If true, the first element is the intersection point. If false, there is no intersection within the segments’ bounds or the segments are parallel.
todo: update doc comments and reorder
func (l LineSegment[T]) IntersectsLineSegment(other LineSegment[T]) bool
IntersectsLineSegment checks whether there is any intersection or overlap between LineSegment l and LineSegment other.
This function returns true if segments l and other have an intersecting spatial relationship, such as intersection, overlap, containment, or endpoint coincidence.
Parameters:
- other (LineSegment[T]): The line segment to compare with l.
Returns:
- bool: Returns true if l and other intersect, overlap, or share any form of intersecting relationship, and false if they are completely disjoint.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Define two line segments
line1 := geom2d.NewLineSegment(geom2d.NewPoint(0, 0), geom2d.NewPoint(10, 10)) // Line segment from (0, 0) to (10, 10)
line2 := geom2d.NewLineSegment(geom2d.NewPoint(5, 5), geom2d.NewPoint(15, 15)) // Line segment from (5, 5) to (15, 15)
line3 := geom2d.NewLineSegment(geom2d.NewPoint(20, 20), geom2d.NewPoint(30, 30)) // Line segment far from line1 and line2
// Check for intersection
fmt.Printf("Line1 intersects Line2: %v\n", line1.IntersectsLineSegment(line2)) // Expected: true
fmt.Printf("Line1 intersects Line3: %v\n", line1.IntersectsLineSegment(line3)) // Expected: false
// Overlapping case
line4 := geom2d.NewLineSegment(geom2d.NewPoint(0, 0), geom2d.NewPoint(5, 5)) // Line segment overlaps partially with Line1
fmt.Printf("Line1 intersects Line4: %v\n", line1.IntersectsLineSegment(line4)) // Expected: true
// Endpoint touching case
line5 := geom2d.NewLineSegment(geom2d.NewPoint(10, 10), geom2d.NewPoint(20, 20)) // Shares an endpoint with Line1
fmt.Printf("Line1 intersects Line5: %v\n", line1.IntersectsLineSegment(line5)) // Expected: true
}
Line1 intersects Line2: true
Line1 intersects Line3: false
Line1 intersects Line4: true
Line1 intersects Line5: true
func (l LineSegment[T]) Length(opts ...Option) float64
Length calculates the length of the line segment, optionally using an epsilon threshold to adjust the precision of the calculation.
Parameters:
- opts: A variadic slice of Option functions to customize the behavior of the calculation. WithEpsilon(epsilon float64): Specifies a tolerance for snapping small floating-point deviations in the calculated length to cleaner values, improving robustness.
Behavior:
- The function computes the Euclidean distance between the start and end points of the line segment using the LineSegment.DistanceToPoint method.
- If WithEpsilon is provided, the resulting length is adjusted such that small deviations due to floating-point precision errors are corrected.
Returns:
- float64: The length of the line segment, optionally adjusted based on epsilon.
Notes:
- This function relies on LineSegment.DistanceToPoint, which supports epsilon adjustments for distance calculations. Epsilon is particularly useful for floating-point coordinates where minor imprecision might affect the result.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
segment := geom2d.NewLineSegment(geom2d.NewPoint(0, 0), geom2d.NewPoint(3, 4))
fmt.Println(segment.Length())
}
5
func (l LineSegment[T]) Normalize() LineSegment[T]
Normalize returns a "normalized" version of the line segment where the start point is guaranteed to be the leftmost-lowest point. This ensures that: - The point with the smallest X-coordinate is the start point. - If the X-coordinates are equal, the point with the smallest Y-coordinate is the start point.
This normalization is useful for algorithms that require consistent ordering of line segments, such as the Bentley-Ottmann sweep line algorithm or Boolean operations on polygons.
Returns a new LineSegment with the points swapped if necessary.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create a line segment where the start point is not leftmost-lowest
original := geom2d.NewLineSegment(geom2d.NewPoint(3, 5), geom2d.NewPoint(1, 2))
// Normalize the segment
normalized := original.Normalize()
// Print the result
fmt.Printf("Original Line Segment: %s\n", original.String())
fmt.Printf("Normalized Line Segment: %s\n", normalized.String())
}
Original Line Segment: LineSegment[(3, 5) -> (1, 2)]
Normalized Line Segment: LineSegment[(1, 2) -> (3, 5)]
func (l LineSegment[T]) Points() []Point[T]
Points returns the two endpoints of the line segment as a slice of Points. The order of the points is [start, end].
Returns:
- []Point[T]: A slice containing the start and end points of the line segment.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create a line segment with two endpoints
line := geom2d.NewLineSegment(geom2d.NewPoint(1, 2), geom2d.NewPoint(3, 4))
// Get the points as a slice
points := line.Points()
// Output the points
fmt.Printf("Start Point: %v\n", points[0])
fmt.Printf("End Point: %v\n", points[1])
}
Start Point: Point[(1, 2)]
End Point: Point[(3, 4)]
func (l LineSegment[T]) Reflect(axis ReflectionAxis, line ...LineSegment[float64]) LineSegment[float64]
Reflect reflects the line segment across the specified axis or custom line.
Parameters:
- axis (ReflectionAxis): The axis or line to reflect across (ReflectAcrossXAxis, ReflectAcrossYAxis, or ReflectAcrossCustomLine).
- line (LineSegment[float64]): Optional. The line segment for ReflectAcrossCustomLine reflection.
Returns:
- LineSegment[float64] - A new line segment where both endpoints are reflected accordingly.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create a line segment
line := geom2d.NewLineSegment(
geom2d.NewPoint[float64](1, 2),
geom2d.NewPoint[float64](3, 4),
)
// Reflect across the X-axis
reflectedX := line.Reflect(geom2d.ReflectAcrossXAxis)
// Reflect across the Y-axis
reflectedY := line.Reflect(geom2d.ReflectAcrossYAxis)
// Reflect across a custom line (e.g., y = x, represented as LineSegment[(0, 0), (1, 1)])
customLine := geom2d.NewLineSegment(
geom2d.NewPoint[float64](0, 0),
geom2d.NewPoint[float64](1, 1),
)
reflectedCustom := line.Reflect(geom2d.ReflectAcrossCustomLine, customLine)
// Output the results
fmt.Printf("Original Line: %v\n", line)
fmt.Printf("Reflected across X-axis: %v\n", reflectedX)
fmt.Printf("Reflected across Y-axis: %v\n", reflectedY)
fmt.Printf("Reflected across custom line (y = x): %v\n", reflectedCustom)
}
Original Line: LineSegment[(1, 2) -> (3, 4)]
Reflected across X-axis: LineSegment[(1, -2) -> (3, -4)]
Reflected across Y-axis: LineSegment[(-1, 2) -> (-3, 4)]
Reflected across custom line (y = x): LineSegment[(2, 1) -> (4, 3)]
func (l LineSegment[T]) RelationshipToCircle(c Circle[T], opts ...Option) Relationship
RelationshipToCircle determines the spatial relationship between the current LineSegment and a Circle.
This function evaluates whether the LineSegment:
- Intersects the circle at any point.
- Lies entirely within the circle.
- Lies entirely outside the circle.
Parameters:
- c (Circle[T]): The circle to compare with the line segment.
- opts: A variadic slice of Option functions to customize the behavior of the relationship check. WithEpsilon(epsilon float64): Specifies a tolerance for comparing distances, improving robustness against floating-point precision errors.
Behavior:
- The function calculates the distance between the circle's center and the endpoints of the line segment, as well as the shortest distance between the circle's center and the line segment itself.
- If any of these distances are exactly equal to the circle's radius, the function determines that the line segment intersects the circle.
- If both endpoints of the line segment lie within the circle, the function determines that the line segment is contained within the circle.
- Otherwise, the function determines that the line segment lies entirely outside the circle.
Returns:
Relationship: A constant indicating the relationship of the line segment to the circle. Possible values are:
- RelationshipDisjoint: The line segment lies entirely outside the circle.
- RelationshipIntersection: The line segment intersects the circle at one or more points.
- RelationshipContainedBy: The line segment lies entirely within the circle.
Notes:
- Epsilon adjustment is particularly useful when working with floating-point coordinates, where minor precision errors could otherwise lead to incorrect results.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Define a circle with center (5, 5) and radius 5
circle := geom2d.NewCircle(geom2d.NewPoint(5, 5), 5.0)
// Define various line segments
lineDisjoint := geom2d.NewLineSegment(
geom2d.NewPoint(0, 0),
geom2d.NewPoint(-2, -2),
)
lineIntersecting := geom2d.NewLineSegment(
geom2d.NewPoint(0, 0),
geom2d.NewPoint(10, 10),
)
lineContained := geom2d.NewLineSegment(
geom2d.NewPoint(5, 6),
geom2d.NewPoint(5, 4),
)
// Evaluate relationships
fmt.Println("Disjoint:", lineDisjoint.RelationshipToCircle(circle))
fmt.Println("Intersecting:", lineIntersecting.RelationshipToCircle(circle))
fmt.Println("Contained:", lineContained.RelationshipToCircle(circle))
}
Disjoint: RelationshipDisjoint
Intersecting: RelationshipIntersection
Contained: RelationshipContainedBy
func (l LineSegment[T]) RelationshipToLineSegment(other LineSegment[T], opts ...Option) Relationship
RelationshipToLineSegment determines the high-level spatial relationship between two line segments.
It categorizes the relationship as:
- Disjoint (no intersection or overlap).
- Intersection (the segments intersect at one or more points).
- Equal (both segments are collinear and share the same endpoints).
Parameters:
- other (LineSegment[T]): The line segment to compare against the current one.
- opts: A variadic slice of Option functions to customize the behavior of the relationship check. WithEpsilon(epsilon float64): Specifies a tolerance for comparing points and collinearity calculations, allowing for robust handling of floating-point precision errors.
Behavior:
The detailed relationship mapped to a Relationship constant:
- RelationshipDisjoint: If the segments are collinear but disjoint, or if they miss entirely.
- RelationshipIntersection: The segments intersect at one or more points.
- RelationshipEqual: If the segments are collinear and exactly equal (share the same endpoints).
Returns:
- Relationship: A constant describing the high-level relationship between the two line segments.
Notes:
- The use of epsilon adjustments ensures robustness against floating-point imprecision.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Define two line segments
line1 := geom2d.NewLineSegment(geom2d.NewPoint(0, 0), geom2d.NewPoint(10, 10))
line2 := geom2d.NewLineSegment(geom2d.NewPoint(5, 5), geom2d.NewPoint(15, 15))
line3 := geom2d.NewLineSegment(geom2d.NewPoint(20, 20), geom2d.NewPoint(30, 30))
line4 := geom2d.NewLineSegment(geom2d.NewPoint(0, 0), geom2d.NewPoint(10, 10))
// Evaluate relationships
fmt.Println("Line1 vs Line2:", line1.RelationshipToLineSegment(line2))
fmt.Println("Line1 vs Line3:", line1.RelationshipToLineSegment(line3))
fmt.Println("Line1 vs Line4:", line1.RelationshipToLineSegment(line4))
}
Line1 vs Line2: RelationshipIntersection
Line1 vs Line3: RelationshipDisjoint
Line1 vs Line4: RelationshipEqual
func (l LineSegment[T]) RelationshipToPoint(p Point[T], opts ...Option) Relationship
RelationshipToPoint determines the high-level spatial relationship between a line segment and a point.
This function evaluates how a line segment relates to a point by delegating the computation to the Point.RelationshipToLineSegment method. The relationship is determined based on whether the point lies on the segment, coincides with an endpoint, or is disjoint from the segment.
Parameters:
- p (Point[T]): The point to compare against the line segment.
- opts: A variadic slice of Option functions to customize the behavior of the relationship check. WithEpsilon(epsilon float64): Specifies a tolerance for comparing distances and collinearity calculations, ensuring robust handling of floating-point precision errors.
Behavior:
- The function calls the Point.RelationshipToLineSegment method for computation.
- The returned relationship constant describes whether the point is on the line segment (RelationshipIntersection), or disjoint from the line segment (RelationshipDisjoint).
Returns:
- Relationship: A constant describing the relationship between the line segment and the point.
Notes:
- Epsilon adjustment ensures robustness against floating-point imprecision.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Define a line segment
segment := geom2d.NewLineSegment(
geom2d.NewPoint[float64](0, 0),
geom2d.NewPoint[float64](10, 10),
)
// Define some points
point1 := geom2d.NewPoint[float64](5, 5) // On the segment
point2 := geom2d.NewPoint[float64](10, 0) // Disjoint
point3 := geom2d.NewPoint[float64](0, 0) // Coincides with an endpoint
// Evaluate relationships
fmt.Println("Point1 vs Line Segment:", segment.RelationshipToPoint(point1))
fmt.Println("Point2 vs Line Segment:", segment.RelationshipToPoint(point2))
fmt.Println("Point3 vs Line Segment:", segment.RelationshipToPoint(point3))
}
Point1 vs Line Segment: RelationshipIntersection
Point2 vs Line Segment: RelationshipDisjoint
Point3 vs Line Segment: RelationshipIntersection
func (l LineSegment[T]) RelationshipToPolyTree(pt *PolyTree[T], opts ...Option) map[*PolyTree[T]]Relationship
RelationshipToPolyTree determines the relationship between a line segment and each polygon in a PolyTree.
This function evaluates how a line segment relates to the polygons in the tree, computing whether the segment is disjoint, intersects any edge, or is fully contained within a polygon. The result is returned as a map, where each key is a pointer to a polygon in the PolyTree, and the value is the relationship between the line segment and that polygon.
Parameters:
- pt (*PolyTree[T]): The PolyTree to analyze for relationships.
- opts: A variadic slice of Option functions to customize the behavior of the relationship check. WithEpsilon(epsilon float64): Specifies a tolerance for comparing distances and collinearity calculations, ensuring robust handling of floating-point precision errors.
Behavior:
The function first checks if the line segment intersects any edge of each polygon.
- If an intersection or equality is found, the relationship is set to RelationshipIntersection.
- If the segment's start and end points are both contained within a polygon, the relationship is set to RelationshipContainedBy.
- If neither of the above conditions is satisfied, the relationship defaults to RelationshipDisjoint.
Returns:
- map[*PolyTree[T]]Relationship: A map where the keys are polygons in the PolyTree and the values are their relationships with the line segment.
Notes:
- Epsilon adjustment ensures robustness against floating-point imprecision.
- The function assumes polygons in the PolyTree have doubled points for accurate containment checks.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Define a PolyTree with a root polygon and a hole
root, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(0, 0),
geom2d.NewPoint(10, 0),
geom2d.NewPoint(10, 10),
geom2d.NewPoint(0, 10),
}, geom2d.PTSolid)
hole, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(4, 4),
geom2d.NewPoint(6, 4),
geom2d.NewPoint(6, 6),
geom2d.NewPoint(4, 6),
}, geom2d.PTHole)
_ = root.AddChild(hole)
// Note: While errors are ignored in this example for simplicity, it is important to handle errors properly in
// production code to ensure robustness and reliability.
// Define a line segment
segment := geom2d.NewLineSegment(
geom2d.NewPoint(2, 2),
geom2d.NewPoint(8, 8),
)
// Evaluate relationships
relationships := segment.RelationshipToPolyTree(root)
// Print results
fmt.Printf("Root polygon relationship: %v\n", relationships[root])
fmt.Printf("Hole polygon relationship: %v\n", relationships[hole])
}
Root polygon relationship: RelationshipContainedBy
Hole polygon relationship: RelationshipIntersection
func (l LineSegment[T]) RelationshipToRectangle(r Rectangle[T], opts ...Option) Relationship
RelationshipToRectangle determines the high-level spatial relationship between a line segment and a rectangle.
This function evaluates how a line segment relates to a rectangle, considering possible intersections, containment, or disjoint relationships. The function iterates through the rectangle's edges to check for intersections with the line segment and verifies whether the line segment is entirely contained within the rectangle.
Parameters:
- r (Rectangle[T]): The rectangle to compare against the line segment.
- opts: A variadic slice of Option functions to customize the behavior of the relationship check. WithEpsilon(epsilon float64): Specifies a tolerance for comparing points and collinearity calculations, ensuring robust handling of floating-point precision errors.
Behavior:
The function checks each edge of the rectangle against the line segment:
- If any edge intersects or is equal to the line segment, the function returns RelationshipIntersection.
- If both endpoints of the line segment are contained within the rectangle, the function returns RelationshipContainedBy.
- If no intersection or containment is found, the function returns RelationshipDisjoint.
Returns:
- Relationship: A constant describing the relationship between the line segment and the rectangle.
Notes:
- Epsilon adjustment ensures robustness against floating-point imprecision.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Define a rectangle
rect := geom2d.NewRectangle([]geom2d.Point[int]{
geom2d.NewPoint(0, 0),
geom2d.NewPoint(10, 0),
geom2d.NewPoint(10, 10),
geom2d.NewPoint(0, 10),
})
// Define some line segments
line1 := geom2d.NewLineSegment(geom2d.NewPoint(5, 5), geom2d.NewPoint(15, 15)) // Intersects
line2 := geom2d.NewLineSegment(geom2d.NewPoint(1, 1), geom2d.NewPoint(9, 9)) // Contained
line3 := geom2d.NewLineSegment(geom2d.NewPoint(20, 20), geom2d.NewPoint(30, 30)) // Disjoint
// Evaluate relationships
fmt.Println("Line1 vs Rectangle:", line1.RelationshipToRectangle(rect))
fmt.Println("Line2 vs Rectangle:", line2.RelationshipToRectangle(rect))
fmt.Println("Line3 vs Rectangle:", line3.RelationshipToRectangle(rect))
}
Line1 vs Rectangle: RelationshipIntersection
Line2 vs Rectangle: RelationshipContainedBy
Line3 vs Rectangle: RelationshipDisjoint
func (l LineSegment[T]) Rotate(pivot Point[T], radians float64, opts ...Option) LineSegment[float64]
Rotate rotates the LineSegment around a given pivot Point by a specified angle in radians counterclockwise. Optionally, an epsilon threshold can be applied to adjust the precision of the resulting coordinates.
Parameters:
- pivot (Point[T]): The point around which to rotate the line segment.
- radians (float64): The rotation angle in radians.
- opts: A variadic slice of Option functions to customize the behavior of the rotation. WithEpsilon(epsilon float64): Specifies a tolerance for snapping near-zero or near-integer values in the resulting coordinates to cleaner values, improving robustness.
Behavior:
- The function rotates the start and end points of the line segment around the given pivot point by the specified angle using the Point.Rotate method.
- If WithEpsilon is provided, epsilon adjustments are applied to the rotated coordinates to handle floating-point precision errors.
Returns:
- LineSegment[float64]: A new line segment representing the rotated position, with floating-point coordinates.
Notes:
- Epsilon adjustment is particularly useful when the rotation involves floating-point calculations that could result in minor inaccuracies.
- The returned line segment always has float64 coordinates, ensuring precision regardless of the coordinate type of the original line segment.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
"math"
)
func main() {
// Define a line segment from (2, 3) to (4, 6)
line := geom2d.NewLineSegment(
geom2d.NewPoint[int](2, 3),
geom2d.NewPoint[int](4, 6),
)
// Define a pivot point at the origin (0, 0)
pivot := geom2d.NewPoint[int](0, 0)
// Rotate the line segment by 90 degrees (π/2 radians) counterclockwise
rotatedLine := line.Rotate(pivot, math.Pi/2)
// Print the rotated line segment's start and end points
fmt.Printf("Rotated Line Start: %v\n", rotatedLine.Points()[0])
fmt.Printf("Rotated Line End: %v\n", rotatedLine.Points()[1])
}
Rotated Line Start: Point[(-3, 2)]
Rotated Line End: Point[(-6, 4)]
func (l LineSegment[T]) RoundToEpsilon(epsilon float64) LineSegment[float64]
RoundToEpsilon returns a new LineSegment where the coordinates of both the start and end points are rounded to the nearest multiple of the given epsilon.
Parameters:
- epsilon: The value to which the coordinates should be rounded.
Returns:
A new LineSegment with rounded coordinates.
Notes:
- The epsilon value must be greater than 0. If it is 0 or negative, the function will panic.
Example
ExampleLineSegment_RoundToEpsilon demonstrates the use of the RoundToEpsilon method to round the coordinates of a LineSegment to the nearest multiple of the given epsilon.
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create a line segment
ls := geom2d.NewLineSegment(geom2d.NewPoint[float64](1.2345, 4.5678), geom2d.NewPoint[float64](7.8912, 3.2109))
// Round the coordinates to the nearest 0.1
rounded := ls.RoundToEpsilon(0.1)
// Print the rounded line segment
fmt.Printf("LineSegment[(%.4f, %.4f) -> (%.4f, %.4f)]",
rounded.Start().X(),
rounded.Start().Y(),
rounded.End().X(),
rounded.End().Y(),
)
}
LineSegment[(1.2000, 4.6000) -> (7.9000, 3.2000)]
func (l LineSegment[T]) Scale(ref Point[T], factor T) LineSegment[T]
Scale scales the line segment by a given factor from a specified reference point.
Parameters:
- ref (Point[T]): The reference point from which the scaling is applied. Using the origin point (0, 0) scales the segment relative to the coordinate system's origin, while specifying a custom reference point scales the segment relative to that point.
- factor ([T]): The scaling factor, where a value greater than 1 expands the segment, and a value between 0 and 1 shrinks it.
Behavior:
- The function scales both endpoints of the line segment relative to the specified reference point using the Point.Scale method.
- The scaling operation preserves the relative orientation of the segment.
Returns:
- LineSegment[T]: A new line segment, scaled relative to the specified reference point.
Notes:
- Scaling by a factor of 1 will return a line segment identical to the original.
- Negative scaling factors will mirror the segment across the reference point and scale its length accordingly.
- If the user wishes to shrink the segment (factor < 1), we recommend ensuring the line segment's type is floating-point to avoid precision loss. Use the LineSegment.AsFloat64 method to safely convert the segment to floating-point type before scaling.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Define a line segment from (2, 3) to (4, 6)
line := geom2d.NewLineSegment(
geom2d.NewPoint[int](2, 3),
geom2d.NewPoint[int](4, 6),
)
// Define a reference point for scaling
ref := geom2d.NewPoint[int](0, 0)
// Scale the line segment by a factor of 2 relative to the origin (0, 0)
scaledLine := line.Scale(ref, 2)
// Print the scaled line segment's start and end points
fmt.Printf("Scaled Line Start: %v\n", scaledLine.Points()[0])
fmt.Printf("Scaled Line End: %v\n", scaledLine.Points()[1])
// Scale the line segment by a shrinking factor of 0.5, converting to floating-point type
lineFloat := line.AsFloat64()
shrunkLine := lineFloat.Scale(ref.AsFloat64(), 0.5)
// Print the shrunk line segment's start and end points
fmt.Printf("Shrunk Line Start: %v\n", shrunkLine.Points()[0])
fmt.Printf("Shrunk Line End: %v\n", shrunkLine.Points()[1])
// Scale the line segment relative to a custom point (3, 3)
customRef := geom2d.NewPoint[int](3, 3)
customScaledLine := line.Scale(customRef, 2)
// Print the line segment scaled relative to the custom reference point
fmt.Printf("Custom Scaled Line Start: %v\n", customScaledLine.Points()[0])
fmt.Printf("Custom Scaled Line End: %v\n", customScaledLine.Points()[1])
}
Scaled Line Start: Point[(4, 6)]
Scaled Line End: Point[(8, 12)]
Shrunk Line Start: Point[(1, 1.5)]
Shrunk Line End: Point[(2, 3)]
Custom Scaled Line Start: Point[(1, 3)]
Custom Scaled Line End: Point[(5, 9)]
func (l LineSegment[T]) Slope() (float64, bool)
Slope calculates the slope of the line segment.
The slope is calculated as the change in y-coordinates (dy) divided by the change in x-coordinates (dx) of the line segment. This function returns the slope as a float64 and a boolean indicating whether the slope is defined.
Returns:
- (float64, true): The calculated slope if the line segment is not vertical.
- (0, false): Indicates the slope is undefined (the line segment is vertical).
Example
ExampleLineSegment_Slope demonstrates the use of the Slope method to calculate the slope of a line segment.
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create a line segment
ls := geom2d.NewLineSegment(geom2d.NewPoint[float64](1, 1), geom2d.NewPoint[float64](3, 5))
// Calculate the slope
slope, ok := ls.Slope()
// Print the slope and whether it is valid
fmt.Printf("Slope: %.6f, Valid: %t\n", slope, ok)
}
Slope: 2.000000, Valid: true
func (l LineSegment[T]) Start() Point[T]
Start returns the starting point of the line segment.
This function provides access to the starting point of the LineSegment l, typically representing the beginning of the segment.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
lineSegment := geom2d.NewLineSegment(geom2d.NewPoint(1, 2), geom2d.NewPoint(3, 4))
fmt.Println(lineSegment.Start())
}
Point[(1, 2)]
func (l LineSegment[T]) String() string
String returns a formatted string representation of the line segment for debugging and logging purposes.
The string representation includes the coordinates of the start and end points in the format: "LineSegment[(x1, y1) -> (x2, y2)]", where (x1, y1) are the coordinates of the start point, and (x2, y2) are the coordinates of the end point.
Returns:
- string: A string representing the line segment's start and end coordinates.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
segment := geom2d.NewLineSegment(geom2d.NewPoint(1, 1), geom2d.NewPoint(4, 5))
fmt.Println(segment.String())
}
LineSegment[(1, 1) -> (4, 5)]
func (l LineSegment[T]) SubLineSegment(other LineSegment[T]) LineSegment[T]
SubLineSegment subtracts the start and end points of another line segment from this one.
This function performs an element-wise subtraction, where the start and end points of the other line segment are subtracted from the corresponding start and end points of the current line segment.
Parameters:
- other (LineSegment[T]): The line segment to subtract from the current one.
Returns:
- LineSegment[T] - A new line segment where each endpoint is the result of the element-wise subtraction.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Define two line segments
AB := geom2d.NewLineSegment(
geom2d.NewPoint[int](10, 10),
geom2d.NewPoint[int](20, 20),
)
CD := geom2d.NewLineSegment(
geom2d.NewPoint[int](5, 5),
geom2d.NewPoint[int](15, 15),
)
// Subtract CD from AB
result := AB.SubLineSegment(CD)
// Print the resulting line segment's start and end points
fmt.Printf("Resulting Line Start: %v\n", result.Points()[0])
fmt.Printf("Resulting Line End: %v\n", result.Points()[1])
}
Resulting Line Start: Point[(5, 5)]
Resulting Line End: Point[(5, 5)]
func (l LineSegment[T]) Translate(delta Point[T]) LineSegment[T]
Translate moves the line segment by a specified vector.
This method shifts the line segment's position in the 2D plane by translating both its start and end points by the given vector delta. The relative orientation and length of the line segment remain unchanged.
Parameters:
- delta (Point[T]): The vector by which to translate the line segment.
Returns:
- LineSegment[T]: A new line segment translated by the specified vector.
Notes:
- Translating the line segment effectively adds the delta vector to both the start and end points of the segment.
- This operation is equivalent to a uniform shift, maintaining the segment's shape and size while moving it to a new position.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Define a line segment AB
AB := geom2d.NewLineSegment(
geom2d.NewPoint(1, 1),
geom2d.NewPoint(4, 4),
)
// Define the translation vector
delta := geom2d.NewPoint(2, 3)
// Translate the line segment by the vector
translatedAB := AB.Translate(delta)
// Output the translated line segment
fmt.Println("Translated Line Segment:")
fmt.Println("Start Point:", translatedAB.Start())
fmt.Println("End Point:", translatedAB.End())
}
Translated Line Segment:
Start Point: Point[(3, 4)]
End Point: Point[(6, 7)]
func (l LineSegment[T]) XAtY(y float64) (float64, bool)
XAtY calculates the X-coordinate of the line segment at the given Y-coordinate.
The function determines the X-coordinate of the intersection point between the line segment and the horizontal line at the specified Y. It handles vertical, horizontal, and diagonal line segments.
Parameters:
- y (float64): The Y-coordinate for which the corresponding X-coordinate is to be calculated.
Returns:
- float64: The X-coordinate corresponding to the given Y, if it lies within the bounds of the segment.
- bool: A boolean indicating whether the given Y is within the vertical range of the segment.
Behavior:
- If the line segment is vertical (constant X), the function returns the constant X-coordinate if the Y-coordinate is within the segment's range, and false otherwise.
- If the line segment is horizontal (constant Y), the function returns false unless the Y-coordinate matches the segment's Y-coordinate.
- For diagonal line segments, the function computes the X-coordinate using the line equation and returns true if the calculated X lies within the segment's bounds.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
segment := geom2d.NewLineSegment(geom2d.NewPoint(2, 2), geom2d.NewPoint(8, 5))
x, ok := segment.XAtY(3) // Y = 3
if ok {
fmt.Printf("X at Y=3 is %.2f\n", x)
} else {
fmt.Println("Y=3 is out of bounds for the segment")
}
}
X at Y=3 is 4.00
func (l LineSegment[T]) YAtX(x float64) (float64, bool)
YAtX calculates the Y-coordinate of the line segment at the given X-coordinate.
The function determines the Y-coordinate of the intersection point between the line segment and the vertical line at the specified X. It handles vertical, horizontal, and diagonal line segments.
Parameters:
- x (float64): The X-coordinate for which the corresponding Y-coordinate is to be calculated.
Returns:
- float64: The Y-coordinate corresponding to the given X, if it lies within the bounds of the segment.
- bool: A boolean indicating whether the given X is within the horizontal range of the segment.
Behavior:
- If the line segment is vertical (constant X), the function returns the Y-coordinate of the line segment if the X-coordinate matches the segment's X-coordinate, and false otherwise.
- If the line segment is horizontal (constant Y), the function returns the constant Y-coordinate if the X-coordinate is within the segment's range, and false otherwise.
- For diagonal line segments, the function computes the Y-coordinate using the line equation and returns true if the calculated Y lies within the segment's bounds.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
segment := geom2d.NewLineSegment(geom2d.NewPoint(2, 2), geom2d.NewPoint(8, 5))
y, ok := segment.YAtX(4) // X = 4
if ok {
fmt.Printf("Y at X=4 is %.2f\n", y)
} else {
fmt.Println("X=4 is out of bounds for the segment")
}
}
Y at X=4 is 3.00
type LineSegmentIntersectionResult[T SignedNumber] struct {
IntersectionType LineSegmentIntersectionType // Type of intersection
IntersectionPoint Point[T] // Valid if Type == IntersectionPoint
IntersectionSegment LineSegment[T] // Valid if Type == IntersectionSegment
}
type LineSegmentIntersectionType uint8
const (
IntersectionNone LineSegmentIntersectionType = iota
IntersectionPoint
IntersectionSegment
)
NewPolyTreeOption defines a functional option type for configuring a new PolyTree during creation.
This type allows for flexible and extensible initialization of PolyTree objects by applying optional configurations after the core properties have been set.
Parameters:
- T: The numeric type of the coordinates in the PolyTree, constrained by the SignedNumber interface.
This pattern makes it easy to add optional properties to a PolyTree without requiring an extensive list of parameters in the NewPolyTree function.
type NewPolyTreeOption[T SignedNumber] func(*PolyTree[T]) error
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create a new PolyTree with a child
child, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(3, 3),
geom2d.NewPoint(7, 3),
geom2d.NewPoint(7, 7),
geom2d.NewPoint(3, 7),
}, geom2d.PTHole)
parent, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(0, 0),
geom2d.NewPoint(10, 0),
geom2d.NewPoint(10, 10),
geom2d.NewPoint(0, 10),
}, geom2d.PTSolid, geom2d.WithChildren(child))
fmt.Println(parent.String())
}
PolyTree: PTSolid
Contour Points: [(0, 0), (10, 0), (10, 10), (0, 10)]
PolyTree: PTHole
Contour Points: [(3, 3), (3, 7), (7, 7), (7, 3)]
func WithChildren[T SignedNumber](children ...*PolyTree[T]) NewPolyTreeOption[T]
WithChildren is an option for the NewPolyTree function that assigns child polygons to the created PolyTree. It also sets up parent-child relationships and orders the children for consistency.
Parameters:
- children: A variadic list of pointers to PolyTree objects representing the child polygons.
Behavior:
- The function assigns the provided children to the PolyTree being created.
- It establishes the parent-child relationship by setting the parent of each child to the newly created PolyTree.
Returns:
- A NewPolyTreeOption that can be passed to the NewPolyTree function.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create a new PolyTree with a child
child, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(3, 3),
geom2d.NewPoint(7, 3),
geom2d.NewPoint(7, 7),
geom2d.NewPoint(3, 7),
}, geom2d.PTHole)
parent, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(0, 0),
geom2d.NewPoint(10, 0),
geom2d.NewPoint(10, 10),
geom2d.NewPoint(0, 10),
}, geom2d.PTSolid, geom2d.WithChildren(child))
fmt.Println(parent.String())
}
PolyTree: PTSolid
Contour Points: [(0, 0), (10, 0), (10, 10), (0, 10)]
PolyTree: PTHole
Contour Points: [(3, 3), (3, 7), (7, 7), (7, 3)]
func WithSiblings[T SignedNumber](siblings ...*PolyTree[T]) NewPolyTreeOption[T]
WithSiblings is an option for the NewPolyTree function that assigns sibling polygons to the created PolyTree.
Parameters:
- siblings: A variadic list of pointers to PolyTree objects representing the sibling polygons.
Behavior:
- The function assigns the provided children to the PolyTree being created.
Returns:
- A NewPolyTreeOption that can be passed to the NewPolyTree function.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create the first PolyTree
root1, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(0, 0),
geom2d.NewPoint(100, 0),
geom2d.NewPoint(100, 100),
geom2d.NewPoint(0, 100),
}, geom2d.PTSolid)
// Create the second PolyTree
root2, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(200, 200),
geom2d.NewPoint(300, 200),
geom2d.NewPoint(300, 300),
geom2d.NewPoint(200, 300),
}, geom2d.PTSolid)
// Create a third PolyTree and assign siblings
root3, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(400, 400),
geom2d.NewPoint(500, 400),
geom2d.NewPoint(500, 500),
geom2d.NewPoint(400, 500),
}, geom2d.PTSolid, geom2d.WithSiblings(root1, root2))
// Check siblings of root3
fmt.Println("Siblings of root3:")
for _, sibling := range root3.Siblings() {
fmt.Println(sibling.Contour())
}
}
Siblings of root3:
[Point[(0, 0)] Point[(100, 0)] Point[(100, 100)] Point[(0, 100)]]
[Point[(200, 200)] Point[(300, 200)] Point[(300, 300)] Point[(200, 300)]]
Option is a functional option type used to configure optional parameters in geometric operations. Functions that accept an Option parameter allow users to customize behavior without modifying the primary function signature.
Option functions take a pointer to an geomOptions struct and modify its fields to apply specific configurations.
type Option func(*geomOptions)
func WithEpsilon(epsilon float64) Option
WithEpsilon returns an Option that sets the Epsilon value for functions that support it. Epsilon is a small positive value used to adjust for floating-point precision errors, ensuring numerical stability in geometric calculations.
Parameters:
- epsilon: A small positive value specifying the tolerance range. Values within [-epsilon, epsilon] are treated as zero.
Behavior:
- When this option is applied, functions will use the specified Epsilon value to handle near-zero results caused by floating-point arithmetic.
- If a negative epsilon is provided, it will default to 0 (no adjustment).
- If not set (default), Epsilon remains 0, and no adjustment is applied.
Returns:
- An Option function that modifies the Epsilon field in the geomOptions struct.
Point represents a point in two-dimensional space with x and y coordinates of a generic numeric type T. The Point struct provides methods for common vector operations such as addition, subtraction, and distance calculations, making it versatile for computational geometry and graphics applications.
Type Parameter:
- T: The numeric type for the coordinates, constrained to signed number types by the SignedNumber interface.
type Point[T SignedNumber] struct {
// contains filtered or unexported fields
}
func ConvexHull[T SignedNumber](points []Point[T]) []Point[T]
ConvexHull computes the convex hull of a finite set of points using the Graham Scan algorithm. The convex hull is the smallest convex polygon that encloses all points in the input set. This function is particularly useful in computational geometry applications where the outer boundary of a set of points is needed.
This implementation finds the point with the lowest y-coordinate to serve as a reference for sorting points by their angle relative to this point. Starting from this reference, it orders the points counterclockwise, removing any points that cause a clockwise turn to ensure a convex boundary.
Parameters:
- points (Point[T]): A variable number of instances representing the set of points for which the convex hull is to be computed.
Returns:
- []Point[T]: A slice of points representing the vertices of the convex hull in counterclockwise order. The returned slice includes only the points that form the outer boundary of the input set.
Note:
- If the points parameter is empty or has fewer than three points, the function returns the input points unchanged, as a convex hull cannot be formed.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Define a set of points
points := []geom2d.Point[int]{
geom2d.NewPoint(1, 1),
geom2d.NewPoint(2, 5),
geom2d.NewPoint(3, 3),
geom2d.NewPoint(5, 3),
geom2d.NewPoint(3, 1),
geom2d.NewPoint(4, 4),
geom2d.NewPoint(5, 5),
}
// Compute the convex hull
hull := geom2d.ConvexHull(points)
// Print the points that form the convex hull
fmt.Println("Convex Hull:")
for _, point := range hull {
fmt.Println(point)
}
}
Convex Hull:
Point[(1, 1)]
Point[(3, 1)]
Point[(5, 3)]
Point[(5, 5)]
Point[(2, 5)]
func NewPoint[T SignedNumber](x, y T) Point[T]
NewPoint creates a new Point with the specified x and y coordinates.
This function is generic and requires the x and y values to satisfy the SignedNumber constraint.
Parameters:
- x (T): The x-coordinate of the point.
- y (T): The y-coordinate of the point.
Returns:
- Point[T]: A new Point instance with the given coordinates.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create a new point with integer coordinates
pointInt := geom2d.NewPoint[int](10, 20)
fmt.Printf("Integer Point: (%d, %d)\n", pointInt.X(), pointInt.Y())
// Create a new point with floating-point coordinates
pointFloat := geom2d.NewPoint[float64](10.5, 20.25)
fmt.Printf("Floating-Point Point: (%.2f, %.2f)\n", pointFloat.X(), pointFloat.Y())
// Create a new point with type inference.
// As x and y are given as integer values, pointInferred will be of type Point[int].
pointInferred := geom2d.NewPoint(10, 20)
fmt.Printf("Inferred Point: (%v, %v)\n", pointInferred.X(), pointInferred.Y())
}
Integer Point: (10, 20)
Floating-Point Point: (10.50, 20.25)
Inferred Point: (10, 20)
func NewPointFromImagePoint(q image.Point) Point[int]
NewPointFromImagePoint creates and returns a new Point with integer x and y coordinates based on an image.Point. This function is useful for converting between graphics and computational geometry representations of points.
Parameters:
- q (image.Point): An image.Point representing the source coordinates for the new point.
Returns:
- Point[int]: A new Point with coordinates corresponding to the x and y values of the provided image.Point.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
"image"
)
func main() {
// Define an image.Point
imgPoint := image.Point{X: 10, Y: 20}
// Convert the image.Point to a geom2d.Point[int]
geomPoint := geom2d.NewPointFromImagePoint(imgPoint)
// Print the result
fmt.Printf("Image Point: %v\n", imgPoint)
fmt.Printf("Geometry Point: %v\n", geomPoint)
}
Image Point: (10,20)
Geometry Point: Point[(10, 20)]
func RoundPointToEpsilon(point Point[float64], epsilon float64) Point[float64]
RoundPointToEpsilon rounds the coordinates of a point to the nearest multiple of a given epsilon value. This function is useful for reducing numerical precision issues in geometric computations.
Parameters: - point (Point[float64]): The input Point[float64] whose coordinates need to be rounded. - epsilon (float64): The precision value to which the coordinates should be rounded.
Returns: - A new Point[float64] where each coordinate is rounded to the nearest multiple of epsilon.
Notes: - The epsilon value must be greater than zero or the function will panic.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
point := geom2d.NewPoint[float64](1.234, 5.678)
epsilon := 0.1
roundedPoint := geom2d.RoundPointToEpsilon(point, epsilon)
fmt.Printf("Original point: %v\n", point)
fmt.Printf("Rounded point: (%.4f, %.4f)\n", roundedPoint.X(), roundedPoint.Y())
}
Original point: Point[(1.234, 5.678)]
Rounded point: (1.2000, 5.7000)
func (p Point[T]) AsFloat32() Point[float32]
AsFloat32 converts the Point's x and y coordinates to the float32 type, returning a new Point[float32]. This method is useful when higher precision or floating-point arithmetic is needed on the coordinates.
Returns:
- Point[float32]: A new Point with x and y coordinates converted to float32.
func (p Point[T]) AsFloat64() Point[float64]
AsFloat64 converts the Point's x and y coordinates to the float64 type, returning a new Point[float64]. This method is useful when higher precision or floating-point arithmetic is needed on the coordinates.
Returns:
- Point[float64]: A new Point with x and y coordinates converted to float64.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
p := geom2d.NewPoint(3, 4)
floatPoint := p.AsFloat64()
fmt.Printf("%s is type %T", floatPoint, floatPoint)
}
Point[(3, 4)] is type geom2d.Point[float64]
func (p Point[T]) AsInt() Point[int]
AsInt converts the Point's x and y coordinates to the int type by truncating any decimal values. This method is useful when integer coordinates are needed for operations that require whole numbers, such as pixel-based calculations.
Returns:
- Point[int]: A new Point with x and y coordinates converted to int by truncating any decimal portion.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
p := geom2d.NewPoint(3.7, 4.9)
intPoint := p.AsInt()
fmt.Printf("%s is type %T", intPoint, intPoint)
}
Point[(3, 4)] is type geom2d.Point[int]
func (p Point[T]) AsIntRounded() Point[int]
AsIntRounded converts the Point's x and y coordinates to the int type by rounding to the nearest integer. This method is useful when integer coordinates are required and rounding provides more accurate results compared to truncation.
Returns:
- Point[int]: A new Point with x and y coordinates converted to int by rounding to the nearest integer.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
p := geom2d.NewPoint(3.7, 4.2)
roundedPoint := p.AsIntRounded()
fmt.Printf("%s is type %T", roundedPoint, roundedPoint)
}
Point[(4, 4)] is type geom2d.Point[int]
func (p Point[T]) CrossProduct(q Point[T]) T
CrossProduct calculates the cross product of the vector from the origin to Point p and the vector from the origin to Point q. The result is useful in determining the relative orientation of two vectors:
- A positive result indicates a counterclockwise turn (left turn),
- A negative result indicates a clockwise turn (right turn),
- A result of zero indicates that the points are collinear.
Parameters:
- q (Point[T]): The Point with which to compute the cross product relative to the calling Point.
Returns:
- T: The cross product of the vectors from the origin to p and q, indicating their relative orientation.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Define two points relative to the origin
p := geom2d.NewPoint[int](1, 2) // Point p is at (1, 2)
q := geom2d.NewPoint[int](2, 1) // Point q is at (2, 1)
// Calculate the cross product of vectors from the origin to p and q
crossProduct := p.CrossProduct(q)
// Analyze the result
fmt.Printf("Cross product: %d\n", crossProduct)
if crossProduct > 0 {
fmt.Println("The points indicate a counterclockwise turn (left turn).")
} else if crossProduct < 0 {
fmt.Println("The points indicate a clockwise turn (right turn).")
} else {
fmt.Println("The points are collinear.")
}
}
Cross product: -3
The points indicate a clockwise turn (right turn).
func (p Point[T]) DistanceSquaredToPoint(q Point[T]) T
DistanceSquaredToPoint calculates the squared Euclidean distance between Point p and another Point q. This method returns the squared distance, which avoids the computational cost of a square root calculation and is useful in cases where only distance comparisons are needed.
Parameters:
- q (Point[T]): The Point to which the squared distance is calculated from the calling Point.
Returns:
- T: The squared Euclidean distance between p and q.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Define two points
p := geom2d.NewPoint[int](3, 4)
q := geom2d.NewPoint[int](6, 8)
// Calculate the squared Euclidean distance between the points
distanceSquared := p.DistanceSquaredToPoint(q)
// Display the result
fmt.Printf("The squared distance between %v and %v is %d.\n", p, q, distanceSquared)
}
The squared distance between Point[(3, 4)] and Point[(6, 8)] is 25.
func (p Point[T]) DistanceToLineSegment(l LineSegment[T], opts ...Option) float64
DistanceToLineSegment calculates the orthogonal (shortest) distance from the current Point p to a specified LineSegment l. This distance is the length of the perpendicular line segment from p to the closest point on l.
Parameters:
- l (LineSegment[T]): The LineSegment to which the distance is calculated.
- opts: A variadic slice of Option functions to customize the calculation behavior. WithEpsilon(epsilon float64): Adjusts the result by snapping small floating-point deviations to cleaner values based on the specified epsilon threshold.
Behavior:
- The function first computes the projection of p onto the given LineSegment l. This is the closest point on l to p, whether it falls within the line segment or on one of its endpoints.
- The distance is then calculated as the Euclidean distance from p to the projected point, using the Point.DistanceToPoint method for precision.
Returns:
- float64: The shortest distance between the point p and the line segment l, optionally adjusted based on epsilon if provided.
Notes:
- If the point p lies exactly on the line segment, the distance will be zero (or adjusted to zero if within epsilon).
- This method ensures precision by converting points to float64 before performing calculations.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Define a point
p := geom2d.NewPoint[float64](3, 4)
// Define a line segment
lineSegment := geom2d.NewLineSegment(
geom2d.NewPoint[float64](0, 0),
geom2d.NewPoint[float64](6, 0),
)
// Calculate the shortest distance from the point to the line segment
distance := p.DistanceToLineSegment(lineSegment, geom2d.WithEpsilon(1e-10))
// Display the result
fmt.Printf("The shortest distance from %v to %v is %.2f.\n", p, lineSegment, distance)
}
The shortest distance from Point[(3, 4)] to LineSegment[(0, 0) -> (6, 0)] is 4.00.
func (p Point[T]) DistanceToPoint(q Point[T], opts ...Option) float64
DistanceToPoint calculates the Euclidean (straight-line) distance between the current Point p and another Point q. The result is returned as a float64, providing precise measurement of the straight-line separation between the two points.
Parameters:
- q (Point[T]): The Point to which the distance is calculated.
- opts: A variadic slice of Option functions to customize the behavior of the calculation. WithEpsilon(epsilon float64): Adjusts the result by snapping near-zero distances (or distances close to clean values like integers) to a more precise value based on the specified epsilon threshold.
Behavior:
- The function computes the Euclidean distance using the formula: distance = sqrt((p.x - q.x)^2 + (p.y - q.y)^2)
- If the WithEpsilon option is provided, the computed distance is adjusted such that deviations within the epsilon range are snapped to a clean value.
Returns:
- float64: The Euclidean distance between the two points, optionally adjusted based on epsilon.
Notes:
- Epsilon adjustment is useful for snapping results to clean values, particularly when small floating-point errors could propagate in further calculations.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Define two points
p1 := geom2d.NewPoint[float64](3, 4)
p2 := geom2d.NewPoint[float64](0, 0)
// Calculate the Euclidean distance between the points
distance := p1.DistanceToPoint(p2, geom2d.WithEpsilon(1e-10))
// Display the result
fmt.Printf("The Euclidean distance between %v and %v is %.2f.\n", p1, p2, distance)
}
The Euclidean distance between Point[(3, 4)] and Point[(0, 0)] is 5.00.
func (p Point[T]) DotProduct(q Point[T]) T
DotProduct calculates the dot product of the vector represented by Point p with the vector represented by Point q. The dot product is defined as p.x*q.x + p.y*q.y and is widely used in geometry for angle calculations, projection operations, and determining the relationship between two vectors.
Parameters:
- q (Point[T]): The Point with which to calculate the dot product relative to the calling Point.
Returns:
- T: The dot product of the vectors represented by p and q.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Define two vectors as points
p1 := geom2d.NewPoint[float64](3, 4)
p2 := geom2d.NewPoint[float64](1, 2)
// Calculate the dot product of the vectors
dotProduct := p1.DotProduct(p2)
// Display the result
fmt.Printf("The dot product of vector %v and vector %v is %.2f.\n", p1, p2, dotProduct)
}
The dot product of vector Point[(3, 4)] and vector Point[(1, 2)] is 11.00.
func (p Point[T]) Eq(q Point[T], opts ...Option) bool
Eq determines whether the calling Point p is equal to another Point q. Equality can be evaluated either exactly (default) or approximately using an epsilon threshold.
Parameters:
- q (Point[T]): The Point to compare with the calling Point.
- opts: A variadic slice of Option functions to customize the equality check. WithEpsilon(epsilon float64): Specifies a tolerance for comparing the coordinates of p and q. If the absolute difference between the coordinates of p and q is less than epsilon, they are considered equal.
Behavior:
- By default, the function performs an exact equality check, returning true only if the x and y coordinates of p and q are identical.
- If the WithEpsilon option is provided, the function performs an approximate equality check, considering p and q equal if their coordinate differences are within the specified epsilon threshold.
Returns:
- bool: True if p and q are equal based on the specified comparison mode; otherwise, false.
Notes:
- Approximate equality is particularly useful when comparing points with floating-point coordinates, where small precision errors may result in slightly different values.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
p := geom2d.NewPoint(3.0, 4.0)
q := geom2d.NewPoint(3.0, 4.0)
// Exact equality
isEqual := p.Eq(q)
fmt.Printf("Are %s and %s equal? %t.\n", p, q, isEqual)
// Approximate equality with epsilon
r := geom2d.NewPoint(3.000001, 4.000001)
isApproximatelyEqual := p.Eq(r, geom2d.WithEpsilon(1e-5))
fmt.Printf("Are %s and %s equal within 5 decimal places? %t.\n", p, r, isApproximatelyEqual)
}
Are Point[(3, 4)] and Point[(3, 4)] equal? true.
Are Point[(3, 4)] and Point[(3.000001, 4.000001)] equal within 5 decimal places? true.
func (p Point[T]) Negate() Point[T]
Negate returns a new Point with both x and y coordinates negated. This operation is equivalent to reflecting the point across the origin and is useful in reversing the direction of a vector or preparing a point for subtraction via translation.
Returns:
- Point[T]: A new Point with negated x and y coordinates.
Notes:
- The returned point has the same type as the calling point.
- This method does not modify the original point but returns a new instance.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Define a point
p := geom2d.NewPoint(3, -4)
// Negate the point
negated := p.Negate()
// Output the result
fmt.Println("Original Point:", p)
fmt.Println("Negated Point:", negated)
}
Original Point: Point[(3, -4)]
Negated Point: Point[(-3, 4)]
func (p Point[T]) ProjectOntoLineSegment(l LineSegment[T]) Point[float64]
ProjectOntoLineSegment projects the Point p onto a given LineSegment l.
The function calculates the closest point on the LineSegment defined by the endpoints l.Start() and l.End() to the Point p. It utilizes vector mathematics to determine the projection of Point p onto the infinite line represented by the LineSegment. If the projected point falls beyond the ends of the LineSegment, the function returns the closest endpoint of the segment.
Parameters:
- l (LineSegment[T]): The line segment onto which the point is projected. It consists of two endpoints: Start() and End().
Returns:
- A Point[float64] representing the coordinates of the projected point. If the LineSegment is degenerate (both endpoints are the same), the function returns the coordinates of the Start() Point of the LineSegment.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Define a point to be projected
point := geom2d.NewPoint[float64](3, 7)
// Define a line segment
lineSegment := geom2d.NewLineSegment(
geom2d.NewPoint[float64](0, 0),
geom2d.NewPoint[float64](10, 0),
)
// Project the point onto the line segment
projectedPoint := point.ProjectOntoLineSegment(lineSegment)
// Display the result
fmt.Printf("%v projects onto the %v at %v.\n", point, lineSegment, projectedPoint)
}
Point[(3, 7)] projects onto the LineSegment[(0, 0) -> (10, 0)] at Point[(3, 0)].
func (p Point[T]) Reflect(axis ReflectionAxis, line ...LineSegment[float64]) Point[float64]
Reflect reflects the point across the specified axis or custom line.
Parameters:
- axis (ReflectionAxis): Axis - The axis of reflection (ReflectAcrossXAxis, ReflectAcrossYAxis, or ReflectAcrossCustomLine).
- line (LineSegment[T]): Optional. The line for ReflectAcrossCustomLine reflection.
Returns:
- Point[float64] - A new point representing the reflection of the original point.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Define the point to be reflected
point := geom2d.NewPoint[float64](3, 4)
// Reflect across the X-axis
reflectedX := point.Reflect(geom2d.ReflectAcrossXAxis)
// Reflect across the Y-axis
reflectedY := point.Reflect(geom2d.ReflectAcrossYAxis)
// Define a custom line for reflection
line := geom2d.NewLineSegment(
geom2d.NewPoint[float64](0, 0),
geom2d.NewPoint[float64](10, 10),
)
// Reflect across the custom line
reflectedLine := point.Reflect(geom2d.ReflectAcrossCustomLine, line)
// Print the results
fmt.Printf("Original Point: %v\n", point)
fmt.Printf("Reflected across X-axis: %v\n", reflectedX)
fmt.Printf("Reflected across Y-axis: %v\n", reflectedY)
fmt.Printf("Reflected across custom %v: %v\n", line, reflectedLine)
}
Original Point: Point[(3, 4)]
Reflected across X-axis: Point[(3, -4)]
Reflected across Y-axis: Point[(-3, 4)]
Reflected across custom LineSegment[(0, 0) -> (10, 10)]: Point[(4, 3)]
func (p Point[T]) RelationshipToCircle(c Circle[T], opts ...Option) Relationship
RelationshipToCircle determines the spatial relationship between the current Point and a given Circle.
This function evaluates whether the point lies outside, on the boundary of, or inside the given circle. The possible relationships are:
- RelationshipDisjoint: The point lies outside the circle.
- RelationshipIntersection: The point lies exactly on the circle's boundary.
- RelationshipContainedBy: The point is inside the circle.
Parameters:
- c (Circle[T]): The circle to compare with the current point.
- opts: A variadic slice of Option functions to customize the equality check. WithEpsilon(epsilon float64): Specifies a tolerance for comparing distances to handle floating-point precision errors.
Returns:
- Relationship: The relationship of the point to the circle, indicating whether the point is disjoint from, on the boundary of, or contained within the circle.
Behavior:
- The function computes the Euclidean distance between the point and the circle's center.
- It compares this distance to the circle's radius (converted to float64 for precision).
- If the distance equals the radius, the relationship is RelationshipIntersection.
- If the distance is less than the radius, the relationship is RelationshipContainedBy.
- Otherwise, the relationship is RelationshipDisjoint.
Notes:
- Epsilon adjustments can be used to account for floating-point precision issues when comparing the distance to the circle's radius.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Define a point
point := geom2d.NewPoint(1, 1)
// Define a circle with center (0, 0) and radius 5
circle := geom2d.NewCircle(geom2d.NewPoint(0, 0), 5)
// Calculate the relationship between the point and the circle
relationship := point.RelationshipToCircle(circle, geom2d.WithEpsilon(1e-10))
// Output the relationship
fmt.Println(relationship)
}
RelationshipContainedBy
func (p Point[T]) RelationshipToLineSegment(l LineSegment[T], opts ...Option) Relationship
RelationshipToLineSegment determines the spatial relationship of the current Point to a given LineSegment.
The function calculates the orthogonal (shortest) distance from the point to the line segment and determines the relationship based on this distance.
Relationships:
- RelationshipIntersection: The point lies on the line segment.
- RelationshipDisjoint: The point does not lie on the line segment.
Parameters:
- l (LineSegment[T]): The line segment to analyze the relationship with.
- opts: A variadic slice of Option functions to customize the calculation. WithEpsilon(epsilon float64): Adjusts the precision for distance comparisons, enabling robust handling of floating-point errors.
Returns:
- Relationship: The spatial relationship of the point to the line segment.
Behavior:
- If the shortest distance between the point and the line segment is zero (or within the epsilon threshold), the function returns RelationshipIntersection.
- Otherwise, it returns RelationshipDisjoint.
Notes:
- This method is useful for determining if a point lies on a line segment, including endpoints and interior points.
- Epsilon adjustment is particularly useful for floating-point coordinates to avoid precision errors.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Define a line segment from (0, 0) to (10, 0)
segment := geom2d.NewLineSegment(
geom2d.NewPoint(0, 0),
geom2d.NewPoint(10, 0),
)
// Define points to test
pointOnSegment := geom2d.NewPoint(5, 0) // Lies on the segment
pointAbove := geom2d.NewPoint(5, 2) // Lies above the segment
// Analyze relationships
fmt.Println(pointOnSegment.RelationshipToLineSegment(segment)) // Intersection
fmt.Println(pointAbove.RelationshipToLineSegment(segment)) // Disjoint
}
RelationshipIntersection
RelationshipDisjoint
func (p Point[T]) RelationshipToPoint(other Point[T], opts ...Option) Relationship
RelationshipToPoint determines the spatial relationship between the current Point and another Point.
Relationships:
- RelationshipEqual: The two points are equal.
- RelationshipDisjoint: The two points are not equal.
Parameters:
- other (Point[T]): The other point to analyze the relationship with.
- opts: A variadic slice of Option functions to customize the behavior of the comparison. WithEpsilon(epsilon float64): Specifies a tolerance for comparing the coordinates of the two points, allowing for robust handling of floating-point precision errors.
Returns:
- Relationship: The spatial relationship between the two points.
Behavior:
- If the current point and the other point are equal (or approximately equal within the epsilon threshold), the function returns RelationshipEqual.
- Otherwise, it returns RelationshipDisjoint.
Notes:
- Epsilon adjustment is particularly useful for floating-point coordinates to avoid precision errors when comparing points.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Define two points
pointA := geom2d.NewPoint(5, 5)
pointB := geom2d.NewPoint(5, 5)
pointC := geom2d.NewPoint(10, 10)
// Analyze relationships
fmt.Println(pointA.RelationshipToPoint(pointB)) // Equal
fmt.Println(pointA.RelationshipToPoint(pointC)) // Disjoint
}
RelationshipEqual
RelationshipDisjoint
func (p Point[T]) RelationshipToPolyTree(pt *PolyTree[T], opts ...Option) map[*PolyTree[T]]Relationship
RelationshipToPolyTree determines the spatial relationship between the current Point and each polygon in a PolyTree.
This method returns a map, where the keys are pointers to the polygons in the PolyTree, and the values are Relationship constants indicating the relationship of the point to each polygon.
Relationships:
- RelationshipContainedBy: The point is inside the polygon but not on its boundary.
- RelationshipIntersection: The point lies on an edge or vertex of the polygon.
- RelationshipDisjoint: The point lies entirely outside the polygon.
Parameters:
- pt (*PolyTree[T]): The PolyTree to analyze.
- opts: A variadic slice of Option functions to customize the behavior of the calculation. WithEpsilon(epsilon float64): Specifies a tolerance for comparing the point's location relative to the polygons, improving robustness in floating-point calculations.
Returns:
- map[*PolyTree[T]]Relationship: A map where each polygon in the PolyTree is associated with its relationship to the point.
Behavior:
For each polygon in the PolyTree, the function checks whether the point is:
- Contained within the polygon.
- On an edge or vertex of the polygon.
- Outside the polygon entirely.
The relationship for each polygon is stored in the output map.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Define a PolyTree with a root polygon and a hole
root, _ := geom2d.NewPolyTree(
[]geom2d.Point[int]{
geom2d.NewPoint(0, 0),
geom2d.NewPoint(0, 100),
geom2d.NewPoint(100, 100),
geom2d.NewPoint(100, 0),
},
geom2d.PTSolid,
)
hole, _ := geom2d.NewPolyTree(
[]geom2d.Point[int]{
geom2d.NewPoint(20, 20),
geom2d.NewPoint(20, 80),
geom2d.NewPoint(80, 80),
geom2d.NewPoint(80, 20),
},
geom2d.PTHole,
)
_ = root.AddChild(hole)
// Note: While errors are ignored in this example for simplicity, it is important to handle errors properly in
// production code to ensure robustness and reliability.
// Define points to test
pointA := geom2d.NewPoint(50, 50) // Inside both the hole and the root poly
pointB := geom2d.NewPoint(5, 5) // Inside the root polygon only
pointC := geom2d.NewPoint(0, 10) // On the root polygon's edge
pointD := geom2d.NewPoint(-15, -15) // Outside the entire PolyTree
// Analyze relationships
relationships := pointA.RelationshipToPolyTree(root)
fmt.Println(relationships[root]) // RelationshipContainedBy
fmt.Println(relationships[hole]) // RelationshipContainedBy
relationships = pointB.RelationshipToPolyTree(root)
fmt.Println(relationships[root]) // RelationshipContainedBy
fmt.Println(relationships[hole]) // RelationshipDisjoint
relationships = pointC.RelationshipToPolyTree(root)
fmt.Println(relationships[root]) // RelationshipIntersection
fmt.Println(relationships[hole]) // RelationshipDisjoint
relationships = pointD.RelationshipToPolyTree(root)
fmt.Println(relationships[root]) // RelationshipDisjoint
fmt.Println(relationships[hole]) // RelationshipDisjoint
}
RelationshipContainedBy
RelationshipContainedBy
RelationshipContainedBy
RelationshipDisjoint
RelationshipIntersection
RelationshipDisjoint
RelationshipDisjoint
RelationshipDisjoint
func (p Point[T]) RelationshipToRectangle(r Rectangle[T], opts ...Option) Relationship
RelationshipToRectangle determines the spatial relationship between the current Point and a Rectangle.
Relationships:
- RelationshipIntersection: The point lies on one of the rectangle's edges.
- RelationshipContainedBy: The point is inside the rectangle but not on its boundary.
- RelationshipDisjoint: The point lies entirely outside the rectangle.
Parameters:
- r (Rectangle[T]): The rectangle to analyze the relationship with.
- opts: A variadic slice of Option functions to customize the behavior of the calculation. WithEpsilon(epsilon float64): Specifies a tolerance for comparing the point's location relative to the rectangle, improving robustness in floating-point calculations.
Returns:
- Relationship: The spatial relationship between the point and the rectangle.
Behavior:
- The function checks if the point lies on any of the rectangle's edges. If so, it returns RelationshipIntersection.
- If the point is not on an edge but is inside the rectangle, it returns RelationshipContainedBy.
- If the point is neither on an edge nor inside the rectangle, it returns RelationshipDisjoint.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Define a rectangle
rect := geom2d.NewRectangle([]geom2d.Point[int]{
geom2d.NewPoint(0, 0),
geom2d.NewPoint(10, 0),
geom2d.NewPoint(10, 10),
geom2d.NewPoint(0, 10),
})
// Define points
pointA := geom2d.NewPoint(5, 5) // Inside
pointB := geom2d.NewPoint(10, 5) // On edge
pointC := geom2d.NewPoint(15, 5) // Outside
// Analyze relationships
fmt.Println(pointA.RelationshipToRectangle(rect)) // Contained
fmt.Println(pointB.RelationshipToRectangle(rect)) // Intersection
fmt.Println(pointC.RelationshipToRectangle(rect)) // Disjoint
}
RelationshipContainedBy
RelationshipIntersection
RelationshipDisjoint
func (p Point[T]) Rotate(pivot Point[T], radians float64, opts ...Option) Point[float64]
Rotate rotates the point by a specified angle (in radians), counter-clockwise, around a given pivot point.
Parameters:
- pivot (Point[T]): The point around which the rotation is performed.
- radians (float64): The angle of rotation, specified in radians.
- opts: A variadic slice of Option functions to customize the behavior of the relationship check. WithEpsilon(epsilon float64): Specifies a tolerance for floating-point comparisons, improving robustness against precision errors.
Behavior:
- The method first translates the point to the origin (relative to the pivot), applies the rotation matrix, and then translates the point back to its original position.
- If the WithEpsilon option is provided, small numerical deviations in the rotated coordinates will be adjusted based on the specified epsilon.
Returns:
- Point[float64]: A new point representing the rotated position.
Notes:
- If no options are provided, the default behavior applies, and no epsilon adjustment is made.
- The return type is always Point[float64] to ensure precision in the rotated result.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
"math"
)
func main() {
pivot := geom2d.NewPoint(1.0, 1.0)
circle := geom2d.NewCircle(geom2d.NewPoint(3.0, 3.0), 5.0)
// Rotates the circle 90 degrees around (1.0, 1.0)
rotatedCircle := circle.Rotate(pivot, math.Pi/2, geom2d.WithEpsilon(1e-10))
fmt.Println(rotatedCircle)
}
Circle[center=(-1, 3), radius=5]
func (p Point[T]) Scale(ref Point[T], k T) Point[T]
Scale scales the point by a factor k relative to a reference point ref.
Parameters:
- ref (Point[float64]): The reference point from which scaling originates.
- k (float64): The scaling factor.
Returns:
- Point[float64] - A new point scaled relative to the reference point.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
p := geom2d.NewPoint(3, 4)
ref := geom2d.NewPoint(1, 1)
scaled := p.Scale(ref, 2)
fmt.Println(scaled)
}
Point[(5, 7)]
func (p Point[T]) String() string
String returns a string representation of the Point p in the format "Point[(x, y)]". This provides a readable format for the point’s coordinates, useful for debugging and displaying points in logs or output.
Returns:
- string: A string representation of the Point in the format "Point[(x, y)]".
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
p := geom2d.NewPoint(3, 4)
fmt.Println(p.String())
}
Point[(3, 4)]
func (p Point[T]) Translate(delta Point[T]) Point[T]
Translate moves the Point by a given displacement vector.
Parameters:
- delta (Point[T]): The displacement vector to apply.
Returns:
- Point[T]: A new Point resulting from the translation.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create a point
p := geom2d.NewPoint(5, 5)
// Translate the point by a positive delta
delta := geom2d.NewPoint(3, -2)
pTranslated := p.Translate(delta)
fmt.Println("Translated point:", pTranslated)
// Subtract a vector by negating it and then using Translate
subtractVector := geom2d.NewPoint(1, 1).Negate()
pSubtracted := p.Translate(subtractVector)
fmt.Println("Point after subtraction:", pSubtracted)
}
Translated point: Point[(8, 3)]
Point after subtraction: Point[(4, 4)]
func (p Point[T]) X() T
X returns the x-coordinate of the Point p. This accessor provides read-only access to the x-coordinate.
Returns:
- T: The x-coordinate of the Point.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
p := geom2d.NewPoint(3, 4)
fmt.Println(p.X())
}
3
func (p Point[T]) Y() T
Y returns the y-coordinate of the Point p. This accessor provides read-only access to the y-coordinate.
Returns:
- T: The y-coordinate of the Point.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
p := geom2d.NewPoint(3, 4)
fmt.Println(p.Y())
}
4
PointOrientation represents the relative orientation of three points in a two-dimensional plane. It describes whether the points are collinear, form a clockwise turn, or form a counterclockwise turn. This type is commonly used in computational geometry algorithms to determine the spatial relationship between points in relation to each other.
type PointOrientation uint8
Valid values for PointOrientation.
const (
// PointsCollinear indicates that the points are collinear, meaning they lie on a single straight line.
PointsCollinear PointOrientation = iota
// PointsClockwise indicates that the points are arranged in a clockwise orientation.
PointsClockwise
// PointsCounterClockwise indicates that the points are arranged in a counterclockwise orientation.
PointsCounterClockwise
)
func Orientation[T SignedNumber](p0, p1, p2 Point[T]) PointOrientation
Orientation determines the relative orientation of three points: p0, p1, and p2. It calculates the signed area of the triangle formed by these points to determine if the points make a counterclockwise turn, a clockwise turn, or are collinear. This method is widely used in computational geometry to classify point arrangements in polygon and convex hull algorithms.
Parameters:
- p0, p1, p2: The three points for which the orientation is determined.
Returns:
PointOrientation: A constant indicating the relative orientation of the points:
- PointsCounterClockwise if the points form a counterclockwise turn,
- PointsClockwise if the points form a clockwise turn,
- PointsCollinear if the points are collinear (lie on a single line).
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Define three sets of points to test different orientations
// Counterclockwise orientation
p0 := geom2d.NewPoint(0, 0)
p1 := geom2d.NewPoint(1, 1)
p2 := geom2d.NewPoint(2, 0)
orientation1 := geom2d.Orientation(p0, p1, p2)
fmt.Printf("Orientation of p0: %v, p1: %v, p2: %v: %v\n", p0, p1, p2, orientation1)
// Clockwise orientation
p3 := geom2d.NewPoint(0, 0)
p4 := geom2d.NewPoint(2, 0)
p5 := geom2d.NewPoint(1, 1)
orientation2 := geom2d.Orientation(p3, p4, p5)
fmt.Printf("Orientation of p3: %v, p4: %v, p5: %v: %v\n", p3, p4, p5, orientation2)
// Collinear orientation
p6 := geom2d.NewPoint(0, 0)
p7 := geom2d.NewPoint(1, 1)
p8 := geom2d.NewPoint(2, 2)
orientation3 := geom2d.Orientation(p6, p7, p8)
fmt.Printf("Orientation of points p6%v, p7%v, p8%v: %v\n", p6, p7, p8, orientation3)
}
Orientation of p0: Point[(0, 0)], p1: Point[(1, 1)], p2: Point[(2, 0)]: PointsClockwise
Orientation of p3: Point[(0, 0)], p4: Point[(2, 0)], p5: Point[(1, 1)]: PointsCounterClockwise
Orientation of points p6Point[(0, 0)], p7Point[(1, 1)], p8Point[(2, 2)]: PointsCollinear
func (o PointOrientation) String() string
String converts a PointOrientation constant into its string representation.
This method is used to provide a human-readable description of the PointOrientation value. It is particularly useful for debugging and logging, as it outputs the specific orientation type (e.g., PointsCollinear, PointsClockwise, or PointsCounterClockwise).
Behavior:
- If the value corresponds to a defined PointOrientation constant, the method returns its name.
- If the value is unsupported or invalid, the method panics with an error.
Returns:
- string: The string representation of the PointOrientation value.
Panics:
- If the PointOrientation value is invalid or not one of the defined constants (PointsCollinear, PointsClockwise, PointsCounterClockwise), the function panics with a descriptive error message.
PolyTree represents a polygon that can be part of a hierarchical structure. It supports complex polygons with holes and islands and enables operations like unions, intersections, and subtractions.
A PolyTree has the following characteristics:
- A contour: The outer boundary of the polygon, represented as a sequence of vertices. This includes all the original points, intersection points, and added midpoints as needed for polygon operations.
- A PolygonType: Indicates whether the polygon is a solid region (PTSolid) or a hole (PTHole). This classification is essential for understanding the relationship between the polygon and its children.
- A parent: a pointer to the parent polygon in the hierarchy. For example, a hole's parent would be the solid polygon that contains it. If a polygon is the root polygon in the PolyTree, its parent is nil.
- Zero or more siblings: A list of sibling polygons that are not nested within each other but share the same parent. Siblings must be of the same PolygonType.
- Zero or more children: A list of child polygons nested within this polygon. If the PolygonType is PTSolid, the children are holes (PTHole). If the PolygonType is PTHole, the children are solid islands (PTSolid).
Hierarchy Rules:
- A solid polygon (PTSolid) can contain holes (PTHole) as its children.
- A hole (PTHole) can contain solid polygons (PTSolid) as its children.
- Siblings are polygons of the same PolygonType that do not overlap.
These relationships form a tree structure, where each node is a PolyTree, allowing for complex geometric modeling and operations.
Note: Internal optimizations, such as convex hull caching and point indexing, are abstracted away from the user.
type PolyTree[T SignedNumber] struct {
// contains filtered or unexported fields
}
func ApplyPointTransform[T, N SignedNumber](pt *PolyTree[T], transformFunc func(p Point[T]) (Point[N], error)) (*PolyTree[N], error)
ApplyPointTransform applies a transformation function to each point in a PolyTree and produces a new PolyTree with points of a different type. It supports transformation between numeric types in the SignedNumber constraint.
This function allows users to convert the points in a PolyTree using a custom transformation function, for example, rounding float64 coordinates to int or scaling coordinates to another type.
Parameters:
- pt (*PolyTree[T]): The input PolyTree with points of type T.
- transformFunc (func(Point[T]) (Point[N], error)): A user-defined function that transforms a Point[T] to a Point[N]. It can return an error if the transformation fails.
Returns:
- *PolyTree[N]: A new PolyTree where all points have been transformed to type N.
- error: Returns an error if any point transformation fails or if the new PolyTree cannot be created.
func NewPolyTree[T SignedNumber](points []Point[T], t PolygonType, opts ...NewPolyTreeOption[T]) (*PolyTree[T], error)
NewPolyTree creates a new PolyTree object from a set of points defining the polygon's contour. The function also allows for optional configuration using NewPolyTreeOption.
Parameters:
- points ([]Point[T]): A slice of Point[T] representing the vertices of the polygon.
- t (PolygonType): The type of polygon, either PTSolid or PTHole.
- opts: A variadic slice of NewPolyTreeOption functions, see below.
NewPolyTreeOption functions:
- WithChildren: is an option for the NewPolyTree function that assigns child polygons to the created PolyTree. This can also be done later with the PolyTree.AddChild method.
- WithSiblings: is an option for the NewPolyTree function that assigns sibling polygons to the created PolyTree. This can also be done later with the PolyTree.AddSibling method.
Returns:
- A pointer to the newly created PolyTree.
- An error if the input points are invalid (e.g., less than three points or zero area).
Notes:
- The function ensures that the polygon's points are oriented correctly based on its type.
- Contour are doubled internally to avoid integer division/precision issues during midpoint calculations.
- The polygon's convex hull is computed and stored for potential optimisations.
- Child polygons must have the opposite PolygonType (e.g., holes for a solid polygon and solids for a hole polygon).
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create root/parent polygon - large square
root, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(0, 0),
geom2d.NewPoint(100, 0),
geom2d.NewPoint(100, 100),
geom2d.NewPoint(0, 100),
}, geom2d.PTSolid)
// Create hole polygon - slightly smaller square
hole, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(20, 20),
geom2d.NewPoint(80, 20),
geom2d.NewPoint(80, 80),
geom2d.NewPoint(20, 80),
}, geom2d.PTHole)
// Create island polygon - even slightly smaller square
island, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(40, 40),
geom2d.NewPoint(60, 40),
geom2d.NewPoint(60, 60),
geom2d.NewPoint(40, 60),
}, geom2d.PTSolid)
// Set up polygon relationships
_ = hole.AddChild(island)
_ = root.AddChild(hole)
// Print polytree
fmt.Println(root)
}
PolyTree: PTSolid
Contour Points: [(0, 0), (100, 0), (100, 100), (0, 100)]
PolyTree: PTHole
Contour Points: [(20, 20), (20, 80), (80, 80), (80, 20)]
PolyTree: PTSolid
Contour Points: [(40, 40), (60, 40), (60, 60), (40, 60)]
func (pt *PolyTree[T]) AddChild(child *PolyTree[T]) error
AddChild adds a child PolyTree to the current PolyTree.
Parameters:
- child (*PolyTree[T]): A pointer to the PolyTree to be added as a child.
Returns:
error: An error if the operation fails. Possible error scenarios include:
- The child is nil.
- The child has the same PolygonType as the parent.
- The child polygon does not fit entirely within the parent's contour.
- The child polygon overlaps with existing children.
func (pt *PolyTree[T]) AddSibling(sibling *PolyTree[T]) error
AddSibling adds a sibling PolyTree to the current PolyTree.
Parameters:
- sibling (*PolyTree[T]): A pointer to the PolyTree to be added as a sibling.
Returns:
error: An error if the operation fails. Possible error scenarios include:
- The sibling is nil.
- The sibling has a different PolygonType than the current PolyTree.
- The sibling polygon overlaps with existing siblings.
func (pt *PolyTree[T]) Area() float64
Area calculates the area of the polygon represented by the PolyTree. This method computes the absolute area of the PolyTree's contour, ensuring that the result is always positive, regardless of the orientation of the contour.
Returns:
- float64: The absolute area of the PolyTree.
Notes:
- The method operates only on the contour of the PolyTree and does not account for any children polygons.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create a square PolyTree
contour := []geom2d.Point[int]{
geom2d.NewPoint(0, 0),
geom2d.NewPoint(0, 10),
geom2d.NewPoint(10, 10),
geom2d.NewPoint(10, 0),
}
poly, err := geom2d.NewPolyTree(contour, geom2d.PTSolid)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
// Calculate the area
area := poly.Area()
fmt.Printf("Area of the square: %.2f\n", area)
}
Area of the square: 100.00
func (pt *PolyTree[T]) AsFloat32() *PolyTree[float32]
AsFloat32 converts a PolyTree with generic numeric type T to a new PolyTree with points of type float32.
This method iterates over the contours and nodes of the current PolyTree, converting all points to float32 using the Point.AsFloat32 method. It then rebuilds the PolyTree with the converted points.
Returns:
- *PolyTree[float32]: A new PolyTree instance where all points are of type float32.
Panics:
- If an error occurs during the nesting of contours into the new PolyTree, the function panics.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create an example PolyTree[int]
points := []geom2d.Point[int]{
geom2d.NewPoint(0, 0),
geom2d.NewPoint(10, 0),
geom2d.NewPoint(10, 10),
geom2d.NewPoint(0, 10),
}
polyTree, _ := geom2d.NewPolyTree(points, geom2d.PTSolid)
fmt.Println("Original:")
fmt.Println(polyTree)
fmt.Println("AsFloat32:")
fmt.Println(polyTree.AsFloat32())
}
Original:
PolyTree: PTSolid
Contour Points: [(0, 0), (10, 0), (10, 10), (0, 10)]
AsFloat32:
PolyTree: PTSolid
Contour Points: [(0, 0), (10, 0), (10, 10), (0, 10)]
func (pt *PolyTree[T]) AsFloat64() *PolyTree[float64]
AsFloat64 converts a PolyTree with generic numeric type T to a new PolyTree with points of type float64.
This method iterates over the contours and nodes of the current PolyTree, converting all points to float64 using the Point.AsFloat64 method. It then rebuilds the PolyTree with the converted points.
Returns:
- *PolyTree[float64]: A new PolyTree instance where all points are of type float64.
Panics:
- If an error occurs during the nesting of contours into the new PolyTree, the function panics.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create an example PolyTree[int]
points := []geom2d.Point[int]{
geom2d.NewPoint(0, 0),
geom2d.NewPoint(10, 0),
geom2d.NewPoint(10, 10),
geom2d.NewPoint(0, 10),
}
polyTree, _ := geom2d.NewPolyTree(points, geom2d.PTSolid)
fmt.Println("Original:")
fmt.Println(polyTree)
fmt.Println("AsFloat64:")
fmt.Println(polyTree.AsFloat64())
}
Original:
PolyTree: PTSolid
Contour Points: [(0, 0), (10, 0), (10, 10), (0, 10)]
AsFloat64:
PolyTree: PTSolid
Contour Points: [(0, 0), (10, 0), (10, 10), (0, 10)]
func (pt *PolyTree[T]) AsInt() *PolyTree[int]
AsInt converts a PolyTree with generic numeric type T to a new PolyTree with points of type int.
This method iterates over the contours and nodes of the current PolyTree, converting all points to int using the Point.AsInt method. It then rebuilds the PolyTree with the converted points.
Returns:
- *PolyTree[int]: A new PolyTree instance where all points are of type int.
Panics:
- If an error occurs during the nesting of contours into the new PolyTree, the function panics.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create an example PolyTree[float64]
points := []geom2d.Point[float64]{
geom2d.NewPoint[float64](0, 0),
geom2d.NewPoint[float64](10.5, 0),
geom2d.NewPoint[float64](10.5, 10.5),
geom2d.NewPoint[float64](0, 10.5),
}
polyTree, _ := geom2d.NewPolyTree(points, geom2d.PTSolid)
fmt.Println("Original:")
fmt.Println(polyTree)
fmt.Println("AsInt:")
fmt.Println(polyTree.AsInt())
}
Original:
PolyTree: PTSolid
Contour Points: [(0, 0), (10.5, 0), (10.5, 10.5), (0, 10.5)]
AsInt:
PolyTree: PTSolid
Contour Points: [(0, 0), (10, 0), (10, 10), (0, 10)]
func (pt *PolyTree[T]) AsIntRounded() *PolyTree[int]
AsIntRounded converts a PolyTree with generic numeric type T to a new PolyTree with points of type int, rounding the coordinates of each point to the nearest integer.
This method iterates over the contours and nodes of the current PolyTree, converting all points to int using the Point.AsIntRounded method. It then rebuilds the PolyTree with the rounded points.
Returns:
- *PolyTree[int]: A new PolyTree instance where all points are of type int, with coordinates rounded.
Panics:
- If an error occurs during the nesting of contours into the new PolyTree, the function panics.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create an example PolyTree[float64]
points := []geom2d.Point[float64]{
geom2d.NewPoint[float64](0, 0),
geom2d.NewPoint[float64](10.5, 0),
geom2d.NewPoint[float64](10.5, 10.5),
geom2d.NewPoint[float64](0, 10.5),
}
polyTree, _ := geom2d.NewPolyTree(points, geom2d.PTSolid)
fmt.Println("Original:")
fmt.Println(polyTree)
fmt.Println("AsIntRounded:")
fmt.Println(polyTree.AsIntRounded())
}
Original:
PolyTree: PTSolid
Contour Points: [(0, 0), (10.5, 0), (10.5, 10.5), (0, 10.5)]
AsIntRounded:
PolyTree: PTSolid
Contour Points: [(0, 0), (11, 0), (11, 11), (0, 11)]
func (pt *PolyTree[T]) BooleanOperation(other *PolyTree[T], operation BooleanOperation) (*PolyTree[T], error)
BooleanOperation performs a Boolean operation (union, intersection, or subtraction) between the current polygon (p) and another polygon (other). The result is returned as a new PolyTree, or an error is returned if the operation fails.
Parameters:
- other (*PolyTree[T]): The polygon to perform the operation with.
- operation (BooleanOperation): The type of Boolean operation to perform (e.g., union, intersection, subtraction).
Returns:
- A new PolyTree resulting from the operation, or an error if the operation fails.
Supported Operations:
- BooleanIntersection returns the result of the intersection operation, combining two polytrees into a single polytree that encompasses the areas of both input polytrees. Overlapping regions are merged.
- BooleanSubtraction returns the result of the subtraction operation, which removes the area of polytree "other" from the calling PolyTree. The result is a polytree containing one or more polygons representing the area of the calling polygon excluding the overlapping region with the "other" polygon.
- BooleanUnion returns the result of the union operation, which computes the region(s) where two polygons overlap. The result is a polytree with overlapping regions merged.
Behavior:
For non-intersecting polygons:
- Union: Adds the other polygon as a sibling of p.
- Intersection: Returns nil as there is no overlapping area.
- Subtraction: Returns p unchanged, as other does not overlap with p.
For intersecting polygons:
- Computes intersection points and marks entry/exit points for traversal.
- Traverses the polygons to construct the result of the operation.
- Returns a nested PolyTree structure representing the operation result.
func (pt *PolyTree[T]) BoundingBox() Rectangle[T]
BoundingBox calculates the axis-aligned bounding box (AABB) of the PolyTree.
The bounding box is the smallest rectangle, aligned with the coordinate axes, that completely encloses all polygons in the PolyTree. The calculation uses the convex hull of each polygon in the tree, ensuring efficiency and accuracy.
Returns:
- Rectangle[T]: The axis-aligned bounding box that encloses all polygons in the PolyTree.
Notes:
- The bounding box is computed by iterating through the convex hull points of all polygons in the PolyTree.
- If the PolyTree is empty, the behavior is undefined and should be handled externally by the caller.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create a PolyTree with a single polygon
polyTree, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(0, 0),
geom2d.NewPoint(0, 20),
geom2d.NewPoint(20, 20),
geom2d.NewPoint(20, 0),
}, geom2d.PTSolid)
// While errors are ignored in this example, please handle them appropriately in production code.
// Calculate the bounding box of the PolyTree
boundingBox := polyTree.BoundingBox()
// Output the bounding box
fmt.Println("Bounding box:", boundingBox)
}
Bounding box: Rectangle[(0, 0), (20, 0), (20, 20), (0, 20)]
func (pt *PolyTree[T]) Children() []*PolyTree[T]
Children returns the immediate child polygons of the current PolyTree node.
The children are represented as a slice of pointers to PolyTree instances. Each child polygon is nested directly within the current PolyTree node.
Returns:
- []*PolyTree[T]: A slice containing the children of the current PolyTree.
Notes:
- The method does not include grandchildren or deeper descendants in the returned slice.
- If the current PolyTree node has no children, an empty slice is returned.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create a root PolyTree
rootContour := []geom2d.Point[int]{
geom2d.NewPoint(0, 0),
geom2d.NewPoint(0, 100),
geom2d.NewPoint(100, 100),
geom2d.NewPoint(100, 0),
}
root, err := geom2d.NewPolyTree(rootContour, geom2d.PTSolid)
if err != nil {
fmt.Printf("Error creating root: %v\n", err)
return
}
// Create a child PolyTree
childContour := []geom2d.Point[int]{
geom2d.NewPoint(25, 25),
geom2d.NewPoint(25, 75),
geom2d.NewPoint(75, 75),
geom2d.NewPoint(75, 25),
}
child, err := geom2d.NewPolyTree(childContour, geom2d.PTHole)
if err != nil {
fmt.Printf("Error creating child: %v\n", err)
return
}
// Add the child to the root
if err := root.AddChild(child); err != nil {
fmt.Printf("Error adding child: %v\n", err)
return
}
// Retrieve and print the children of the root
children := root.Children()
fmt.Printf("Number of children: %d\n", len(children))
for i, c := range children {
fmt.Printf("Child %d area: %.2f\n", i+1, c.Area())
}
}
Number of children: 1
Child 1 area: 2500.00
func (pt *PolyTree[T]) Contour() []Point[T]
Contour returns a slice of points that make up the external contour of the current PolyTree node. The contour represents the boundary of the polygon associated with the node.
The last Point in the slice is assumed to connect to the first Point, forming a closed contour.
Returns:
- []Point[T]: A slice of Point representing the vertexes of the PolyTree node's contour.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create a PolyTree representing a square
pt, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(0, 0),
geom2d.NewPoint(0, 100),
geom2d.NewPoint(100, 100),
geom2d.NewPoint(100, 0),
}, geom2d.PTSolid)
// Get the contour of the PolyTree
contour := pt.Contour()
// Print the contour points
fmt.Println("Contour Points:")
for _, point := range contour {
fmt.Println(point)
}
}
Contour Points:
Point[(0, 0)]
Point[(100, 0)]
Point[(100, 100)]
Point[(0, 100)]
func (pt *PolyTree[T]) Edges() []LineSegment[T]
Edges returns the edges of the current PolyTree node as a slice of LineSegment.
Each edge represents a segment of the current PolyTree node's contour.
Returns:
- []LineSegment[T]: A slice of LineSegment representing the edges of the PolyTree node's contour.
Notes:
- If the PolyTree node has no contour, an empty slice is returned.
- This method does not include edges from child polygons or sibling polygons.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create a PolyTree
contour := []geom2d.Point[int]{
geom2d.NewPoint(0, 0),
geom2d.NewPoint(0, 100),
geom2d.NewPoint(100, 100),
geom2d.NewPoint(100, 0),
}
poly, err := geom2d.NewPolyTree(contour, geom2d.PTSolid)
if err != nil {
fmt.Printf("Error creating PolyTree: %v\n", err)
return
}
// Retrieve and print edges
edges := poly.Edges()
fmt.Printf("Number of edges: %d\n", len(edges))
for i, edge := range edges {
fmt.Printf("Edge %d: Start %v, End %v\n", i+1, edge.Start(), edge.End())
}
}
Number of edges: 4
Edge 1: Start Point[(0, 0)], End Point[(100, 0)]
Edge 2: Start Point[(100, 0)], End Point[(100, 100)]
Edge 3: Start Point[(100, 100)], End Point[(0, 100)]
Edge 4: Start Point[(0, 100)], End Point[(0, 0)]
func (pt *PolyTree[T]) Eq(other *PolyTree[T], opts ...polyTreeEqOption[T]) (bool, PolyTreeMismatch)
Eq compares two PolyTree objects (pt and other) for structural and content equality. It identifies mismatches in contours, siblings, and children while avoiding infinite recursion by tracking visited nodes. The comparison results are represented as a boolean and a bitmask.
Parameters:
- other (*PolyTree[T]): The PolyTree to compare with the current PolyTree.
- opts: A variadic slice of Option functions to customize the equality check. WithEpsilon(epsilon float64): Specifies a tolerance for comparing the start and end points of the line segments. If the absolute difference between the coordinates of the points is less than epsilon, they are considered equal.
Returns:
- A boolean indicating whether the two PolyTree objects are equal.
- A PolyTreeMismatch bitmask that specifies which aspects (if any) differ.
Behavior:
- Handles nil cases for the "pt" and "other" PolyTree objects.
- Recursively compares contours, siblings, and children, considering different sibling/child orders.
- Uses the visited map to prevent infinite recursion in cyclic structures.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create the first PolyTree (root with one child and one sibling)
root1, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(0, 0),
geom2d.NewPoint(100, 0),
geom2d.NewPoint(100, 100),
geom2d.NewPoint(0, 100),
}, geom2d.PTSolid)
child1, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(20, 20),
geom2d.NewPoint(80, 20),
geom2d.NewPoint(80, 80),
geom2d.NewPoint(20, 80),
}, geom2d.PTHole)
sibling1, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(200, 200),
geom2d.NewPoint(300, 200),
geom2d.NewPoint(300, 300),
geom2d.NewPoint(200, 300),
}, geom2d.PTSolid)
_ = root1.AddChild(child1)
_ = root1.AddSibling(sibling1)
// Create the second PolyTree with identical structure and geometry
root2, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(0, 0),
geom2d.NewPoint(100, 0),
geom2d.NewPoint(100, 100),
geom2d.NewPoint(0, 100),
}, geom2d.PTSolid)
child2, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(20, 20),
geom2d.NewPoint(80, 20),
geom2d.NewPoint(80, 80),
geom2d.NewPoint(20, 80),
}, geom2d.PTHole)
sibling2, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(200, 200),
geom2d.NewPoint(300, 200),
geom2d.NewPoint(300, 300),
geom2d.NewPoint(200, 300),
}, geom2d.PTSolid)
_ = root2.AddChild(child2)
_ = root2.AddSibling(sibling2)
// Compare the two PolyTrees
equal, mismatches := root1.Eq(root2)
// Print the results
fmt.Printf("Are the PolyTrees equal? %v\n", equal)
fmt.Printf("Mismatch bitmask: %v\n", mismatches)
}
Are the PolyTrees equal? true
Mismatch bitmask: 0
func (pt *PolyTree[T]) Hull() []Point[T]
Hull returns the convex hull of the PolyTree node's contour as a slice of Point.
The convex hull represents the smallest convex polygon that can enclose the points of the PolyTree node's contour. This is useful for optimizations in geometric operations.
Returns:
- []Point[T]: A slice of points representing the convex hull of the PolyTree node.
Notes:
- If the PolyTree node does not have a contour, an empty slice is returned.
- The points in the hull are ordered counterclockwise.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create a PolyTree with a triangular contour
contour := []geom2d.Point[int]{
geom2d.NewPoint(0, 0),
geom2d.NewPoint(50, 100),
geom2d.NewPoint(100, 0),
geom2d.NewPoint(50, 50),
}
poly, err := geom2d.NewPolyTree(contour, geom2d.PTSolid)
if err != nil {
fmt.Printf("Error creating PolyTree: %v\n", err)
return
}
// Retrieve and print the hull
hull := poly.Hull()
fmt.Printf("Convex Hull:\n")
for _, point := range hull {
fmt.Printf("Point: %v\n", point)
}
}
Convex Hull:
Point: Point[(0, 0)]
Point: Point[(100, 0)]
Point: Point[(50, 100)]
func (pt *PolyTree[T]) IsRoot() bool
IsRoot determines if the current PolyTree node is the root of the tree.
A PolyTree node is considered root if it does not have a parent.
Returns:
- bool: true if the PolyTree node is the root (has no parent), false otherwise.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create a root PolyTree
rootContour := []geom2d.Point[int]{
geom2d.NewPoint(0, 0),
geom2d.NewPoint(0, 100),
geom2d.NewPoint(100, 100),
geom2d.NewPoint(100, 0),
}
root, err := geom2d.NewPolyTree(rootContour, geom2d.PTSolid)
if err != nil {
fmt.Printf("Error creating root PolyTree: %v\n", err)
return
}
// Create a child PolyTree
childContour := []geom2d.Point[int]{
geom2d.NewPoint(20, 20),
geom2d.NewPoint(20, 80),
geom2d.NewPoint(80, 80),
geom2d.NewPoint(80, 20),
}
child, err := geom2d.NewPolyTree(childContour, geom2d.PTHole)
if err != nil {
fmt.Printf("Error creating child PolyTree: %v\n", err)
return
}
err = root.AddChild(child)
if err != nil {
fmt.Printf("Error adding child to root: %v\n", err)
return
}
// Print the root status
fmt.Printf("Is the root a root? %v\n", root.IsRoot())
fmt.Printf("Is the child a root? %v\n", child.IsRoot())
}
Is the root a root? true
Is the child a root? false
func (pt *PolyTree[T]) Len() int
Len returns the total number of PolyTree nodes in the current PolyTree structure, including the root, its siblings, and all nested children.
This method iterates through all nodes in the PolyTree and counts them.
Returns:
- int: The total number of PolyTree nodes.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create root/parent polygon - large square
root, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(0, 0),
geom2d.NewPoint(100, 0),
geom2d.NewPoint(100, 100),
geom2d.NewPoint(0, 100),
}, geom2d.PTSolid)
// Create hole polygon - slightly smaller square
hole, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(20, 20),
geom2d.NewPoint(80, 20),
geom2d.NewPoint(80, 80),
geom2d.NewPoint(20, 80),
}, geom2d.PTHole)
// Create island polygon - even slightly smaller square
island, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(40, 40),
geom2d.NewPoint(60, 40),
geom2d.NewPoint(60, 60),
geom2d.NewPoint(40, 60),
}, geom2d.PTSolid)
// Establish relationships
_ = hole.AddChild(island)
_ = root.AddChild(hole)
// Output the total number of PolyTree nodes
fmt.Printf("Total nodes in root: %d\n", root.Len())
fmt.Printf("Total nodes in hole: %d\n", hole.Len())
fmt.Printf("Total nodes in island: %d\n", island.Len())
}
Total nodes in root: 3
Total nodes in hole: 2
Total nodes in island: 1
func (pt *PolyTree[T]) Nodes(yield func(*PolyTree[T]) bool)
Nodes iterates over this PolyTree and all its nested polygons, including siblings and children at all levels equal to and below the current node's level. It does not traverse above the current level, if this is required, it is suggested to first obtain the root using PolyTree.Root.
This function is an iterator, designed for use in a for loop, for example:
for node := range polytree.Nodes {
// do something
}
Parameters:
- yield: A callback function that receives a pointer to the current PolyTree being iterated over. The iteration stops early if yield returns false.
Behavior:
- Starts with the current polygon (p) and yields it to the yield function.
- Recursively iterates over all siblings and their nested polygons.
- Recursively iterates over all children and their nested polygons.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create root/parent polygon - large square
root, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(0, 0),
geom2d.NewPoint(100, 0),
geom2d.NewPoint(100, 100),
geom2d.NewPoint(0, 100),
}, geom2d.PTSolid)
// Create hole polygon - slightly smaller square
hole, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(20, 20),
geom2d.NewPoint(80, 20),
geom2d.NewPoint(80, 80),
geom2d.NewPoint(20, 80),
}, geom2d.PTHole)
// Create island polygon - even slightly smaller square
island, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(40, 40),
geom2d.NewPoint(60, 40),
geom2d.NewPoint(60, 60),
geom2d.NewPoint(40, 60),
}, geom2d.PTSolid)
// Establish relationships
_ = hole.AddChild(island)
_ = root.AddChild(hole)
// Use Nodes to iterate over all polygons in the PolyTree
fmt.Println("Iterating over all nodes in the PolyTree:")
for node := range root.Nodes {
fmt.Printf("Polygon Type: %s\n", node.PolygonType())
fmt.Printf("Contour: %v\n", node.Contour())
}
}
Iterating over all nodes in the PolyTree:
Polygon Type: PTSolid
Contour: [Point[(0, 0)] Point[(100, 0)] Point[(100, 100)] Point[(0, 100)]]
Polygon Type: PTHole
Contour: [Point[(20, 20)] Point[(20, 80)] Point[(80, 80)] Point[(80, 20)]]
Polygon Type: PTSolid
Contour: [Point[(40, 40)] Point[(60, 40)] Point[(60, 60)] Point[(40, 60)]]
func (pt *PolyTree[T]) Overlaps(other *PolyTree[T]) bool
Overlaps checks whether the current PolyTree intersects with another PolyTree.
Parameters:
- other (*PolyTree[T]): The PolyTree to compare against the current PolyTree.
Returns:
- true if the two PolyTree objects intersect, either by containment or edge overlap.
- false if there is no intersection.
Behavior:
- Checks if any point from one PolyTree lies inside the contour of the other PolyTree.
- Verifies if any edges of the two PolyTree objects intersect.
This method accounts for all potential intersection cases, including:
- One polygon entirely inside the other.
- Overlapping polygons with shared edges.
- Polygons touching at vertices or along edges.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create the first PolyTree - a square
root1, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(0, 0),
geom2d.NewPoint(100, 0),
geom2d.NewPoint(100, 100),
geom2d.NewPoint(0, 100),
}, geom2d.PTSolid)
// Create the second PolyTree - a smaller square inside the first
root2, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(20, 20),
geom2d.NewPoint(80, 20),
geom2d.NewPoint(80, 80),
geom2d.NewPoint(20, 80),
}, geom2d.PTSolid)
// Create a third PolyTree - disjoint from the first two
root3, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(200, 200),
geom2d.NewPoint(300, 200),
geom2d.NewPoint(300, 300),
geom2d.NewPoint(200, 300),
}, geom2d.PTSolid)
// Check intersections
fmt.Printf("Root1 intersects Root2: %v\n", root1.Overlaps(root2)) // Expect true
fmt.Printf("Root1 intersects Root3: %v\n", root1.Overlaps(root3)) // Expect false
fmt.Printf("Root2 intersects Root3: %v\n", root2.Overlaps(root3)) // Expect false
}
Root1 intersects Root2: true
Root1 intersects Root3: false
Root2 intersects Root3: false
func (pt *PolyTree[T]) Parent() *PolyTree[T]
Parent retrieves the parent of the current PolyTree node.
The parent node represents the polygon in the hierarchy that contains the current node. If the current node is the root, this function returns nil.
Returns:
- *PolyTree[T]: The parent node of the current PolyTree, or nil if the current node is the root.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create a root PolyTree
rootContour := []geom2d.Point[int]{
geom2d.NewPoint(0, 0),
geom2d.NewPoint(0, 100),
geom2d.NewPoint(100, 100),
geom2d.NewPoint(100, 0),
}
root, err := geom2d.NewPolyTree(rootContour, geom2d.PTSolid)
if err != nil {
fmt.Printf("Error creating root PolyTree: %v\n", err)
return
}
// Create a child PolyTree
childContour := []geom2d.Point[int]{
geom2d.NewPoint(20, 20),
geom2d.NewPoint(20, 80),
geom2d.NewPoint(80, 80),
geom2d.NewPoint(80, 20),
}
child, err := geom2d.NewPolyTree(childContour, geom2d.PTHole)
if err != nil {
fmt.Printf("Error creating child PolyTree: %v\n", err)
return
}
err = root.AddChild(child)
if err != nil {
fmt.Printf("Error adding child to root: %v\n", err)
return
}
// Print the parent of the child node
if parent := child.Parent(); parent != nil {
fmt.Println("Child's parent exists.")
} else {
fmt.Println("Child's parent is nil.")
}
// Print the parent of the root node
if parent := root.Parent(); parent != nil {
fmt.Println("Root's parent exists.")
} else {
fmt.Println("Root's parent is nil.")
}
}
Child's parent exists.
Root's parent is nil.
func (pt *PolyTree[T]) Perimeter(opts ...Option) float64
Perimeter calculates the total perimeter of the PolyTree contour.
This function computes the sum of the lengths of all edges that make up the PolyTree's contour. The calculation can be customized using the optional parameters.
Parameters:
- opts: A variadic slice of Option functions to customize the behavior of the calculation. For example, you can use WithEpsilon to adjust precision for floating-point comparisons.
Returns:
- float64: The total perimeter of the PolyTree.
Notes:
- The perimeter only considers the contour of the PolyTree itself and does not include any child polygons.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create a PolyTree representing a triangle
triangleContour := []geom2d.Point[int]{
geom2d.NewPoint(0, 0),
geom2d.NewPoint(0, 10),
geom2d.NewPoint(10, 0),
}
triangle, err := geom2d.NewPolyTree(triangleContour, geom2d.PTSolid)
if err != nil {
fmt.Printf("Error creating PolyTree: %v\n", err)
return
}
// Calculate the perimeter
perimeter := triangle.Perimeter()
fmt.Printf("The perimeter of the triangle is: %.2f\n", perimeter)
}
The perimeter of the triangle is: 34.14
func (pt *PolyTree[T]) PolygonType() PolygonType
PolygonType returns the type of the PolyTree's polygon.
This function identifies whether the polygon represented by the PolyTree is a solid polygon or a hole, based on the PolygonType enumeration.
Returns:
- PolygonType: The type of the polygon (e.g., PTSolid or PTHole).
Notes:
- A solid polygon (PTSolid) represents a filled area.
- A hole polygon (PTHole) represents a void inside a parent polygon.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Define a polygon contour
contour := []geom2d.Point[int]{
geom2d.NewPoint(0, 0),
geom2d.NewPoint(0, 10),
geom2d.NewPoint(10, 10),
geom2d.NewPoint(10, 0),
}
// Create a solid polygon
polyTree, err := geom2d.NewPolyTree(contour, geom2d.PTSolid)
if err != nil {
fmt.Printf("Error creating PolyTree: %v\n", err)
return
}
// Get the PolygonType
polygonType := polyTree.PolygonType()
// Print the PolygonType
fmt.Printf("PolygonType: %v\n", polygonType)
}
PolygonType: PTSolid
func (pt *PolyTree[T]) RelationshipToCircle(c Circle[T], opts ...Option) map[*PolyTree[T]]Relationship
RelationshipToCircle determines the spatial relationship between a PolyTree and a Circle.
This method evaluates the relationship between the calling PolyTree (pt) and the specified Circle (c) for each polygon in the PolyTree. The relationships include containment, intersection, and disjoint.
Parameters:
- c (Circle[T]): The Circle to evaluate against the PolyTree.
- opts (Option): A variadic slice of Option functions to customize the behavior of the relationship check. WithEpsilon(epsilon float64): Specifies a tolerance for comparing points and distances, allowing for robust handling of floating-point precision errors.
Behavior:
- For each polygon in the PolyTree, the function determines whether the Circle lies within, intersects, or is disjoint from the polygon.
- The containment relationship is flipped to reflect the PolyTree's relationship to the Circle.
Returns:
- map[*PolyTree[T]]Relationship: A map where each key is a polygon in the PolyTree, and the value is the relationship between the PolyTree and the Circle.
Notes:
- This method assumes that the PolyTree is valid and non-degenerate.
- The flipped containment ensures that the returned relationships describe the PolyTree's relationship to the Circle, rather than the Circle's relationship to the PolyTree.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create a PolyTree
root, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(0, 0),
geom2d.NewPoint(0, 100),
geom2d.NewPoint(100, 100),
geom2d.NewPoint(100, 0),
}, geom2d.PTSolid)
hole, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(20, 20),
geom2d.NewPoint(20, 80),
geom2d.NewPoint(80, 80),
geom2d.NewPoint(80, 20),
}, geom2d.PTHole)
_ = root.AddChild(hole)
// Note: While errors are ignored in this example for simplicity, it is important to handle errors properly in
// production code to ensure robustness and reliability.
// Define a Circle
circle := geom2d.NewCircle(geom2d.NewPoint(50, 50), 40)
// Determine relationships
relationships := root.RelationshipToCircle(circle, geom2d.WithEpsilon(1e-10))
// Output the relationships
fmt.Printf("Root polygon relationship: %v\n", relationships[root])
fmt.Printf("Hole polygon relationship: %v\n", relationships[hole])
}
Root polygon relationship: RelationshipContains
Hole polygon relationship: RelationshipIntersection
func (pt *PolyTree[T]) RelationshipToLineSegment(l LineSegment[T], opts ...Option) map[*PolyTree[T]]Relationship
RelationshipToLineSegment determines the spatial relationship between a PolyTree and a LineSegment.
This method evaluates the relationship between the calling PolyTree (pt) and the specified LineSegment (l) for each polygon in the PolyTree. The relationships include containment, intersection, and disjoint.
Parameters:
- l (LineSegment[T]): The LineSegment to evaluate against the PolyTree.
- opts (Option): A variadic slice of Option functions to customize the behavior of the relationship check. WithEpsilon(epsilon float64): Specifies a tolerance for comparing points and collinearity calculations, allowing for robust handling of floating-point precision errors.
Behavior:
- For each polygon in the PolyTree, the function determines whether the LineSegment lies within, intersects, or is disjoint from the polygon.
- The containment relationship is flipped to reflect the PolyTree's relationship to the LineSegment.
Returns:
- map[*PolyTree[T]]Relationship: A map where each key is a polygon in the PolyTree, and the value is the relationship between the PolyTree and the LineSegment.
Notes:
- This method assumes that the PolyTree is valid and non-degenerate.
- The flipped containment ensures that the returned relationships describe the PolyTree's relationship to the LineSegment, rather than the LineSegment's relationship to the PolyTree.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create a PolyTree
root, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(0, 0),
geom2d.NewPoint(0, 100),
geom2d.NewPoint(100, 100),
geom2d.NewPoint(100, 0),
}, geom2d.PTSolid)
hole, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(20, 20),
geom2d.NewPoint(20, 80),
geom2d.NewPoint(80, 80),
geom2d.NewPoint(80, 20),
}, geom2d.PTHole)
_ = root.AddChild(hole)
// Note: While errors are ignored in this example for simplicity, it is important to handle errors properly in
// production code to ensure robustness and reliability.
// Define a LineSegment
lineSegment := geom2d.NewLineSegment(geom2d.NewPoint(10, 10), geom2d.NewPoint(90, 90))
// Determine relationships
relationships := root.RelationshipToLineSegment(lineSegment, geom2d.WithEpsilon(1e-10))
// Output the relationships
fmt.Printf("Root polygon relationship: %v\n", relationships[root])
fmt.Printf("Hole polygon relationship: %v\n", relationships[hole])
}
Root polygon relationship: RelationshipContains
Hole polygon relationship: RelationshipIntersection
func (pt *PolyTree[T]) RelationshipToPoint(p Point[T], opts ...Option) map[*PolyTree[T]]Relationship
RelationshipToPoint determines the spatial relationship between a PolyTree and a Point.
This method evaluates the relationship between the calling PolyTree (pt) and the specified Point (p), for each polygon in the PolyTree. The relationships include containment, intersection (point on edge or vertex), and disjoint.
Parameters:
- p (Point[T]): The point to evaluate against the PolyTree.
- opts (Option): A variadic slice of Option functions to customize the behavior of the relationship check. WithEpsilon(epsilon float64): Specifies a tolerance for comparing points and collinearity calculations, allowing for robust handling of floating-point precision errors.
Behavior:
- For each polygon in the PolyTree, the function determines whether the point lies within, intersects, or is disjoint from the polygon.
- The containment relationship is flipped so that the relationship reflects the PolyTree's relationship to the point.
Returns:
- map[*PolyTree[T]]Relationship: A map where each key is a polygon in the PolyTree, and the value is the relationship between the PolyTree and the point.
Notes:
- This method assumes that the PolyTree is valid and non-degenerate.
- The flipped containment ensures that the returned relationships describe the PolyTree's relationship to the point, rather than the point's relationship to the PolyTree.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create a PolyTree
root, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(0, 0),
geom2d.NewPoint(0, 100),
geom2d.NewPoint(100, 100),
geom2d.NewPoint(100, 0),
}, geom2d.PTSolid)
hole, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(20, 20),
geom2d.NewPoint(20, 80),
geom2d.NewPoint(80, 80),
geom2d.NewPoint(80, 20),
}, geom2d.PTHole)
_ = root.AddChild(hole)
// Note: While errors are ignored in this example for simplicity, it is important to handle errors properly in
// production code to ensure robustness and reliability.
// Define a point
point := geom2d.NewPoint(50, 50)
// Determine relationships
relationships := root.RelationshipToPoint(point, geom2d.WithEpsilon(1e-10))
// Output the relationships
fmt.Printf("Root polygon relationship: %v\n", relationships[root])
fmt.Printf("Hole polygon relationship: %v\n", relationships[hole])
}
Root polygon relationship: RelationshipContains
Hole polygon relationship: RelationshipContains
func (pt *PolyTree[T]) RelationshipToPolyTree(other *PolyTree[T], opts ...Option) map[*PolyTree[T]]map[*PolyTree[T]]Relationship
RelationshipToPolyTree determines the spatial relationship between the polygons in one PolyTree (pt) and the polygons in another PolyTree (other).
This function evaluates pairwise relationships between all polygons in the two PolyTrees and returns a map. Each key in the outer map corresponds to a polygon from the first PolyTree (pt), and its value is another map where the keys are polygons from the second PolyTree (other) and the values are the relationships between the two polygons.
Relationships include:
- RelationshipEqual: The two polygons are identical.
- RelationshipIntersection: The polygons overlap partially.
- RelationshipContains: A polygon in the first PolyTree completely encloses a polygon in the second PolyTree.
- RelationshipContainedBy: A polygon in the first PolyTree is completely enclosed by a polygon in the second PolyTree.
- RelationshipDisjoint: The two polygons have no overlap or containment.
Parameters:
- other (*PolyTree[T]): The other PolyTree to compare against PolyTree pt.
- opts (Option): A variadic slice of Option functions to customize the behavior of the relationship check. WithEpsilon(epsilon float64): Specifies a tolerance for comparing points and collinearity calculations, allowing for robust handling of floating-point precision errors.
Returns:
- map[*PolyTree[T]]map[*PolyTree[T]]Relationship: A nested map where the first key is a polygon from the first PolyTree (pt), the second key is a polygon from the second PolyTree (other), and the value represents their spatial relationship.
Notes:
- For efficiency, the function first checks for equality and then evaluates intersection and containment.
- The function assumes that the input PolyTrees have properly formed contours and edges.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create the first PolyTree
pt1, err := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(0, 0),
geom2d.NewPoint(0, 10),
geom2d.NewPoint(10, 10),
geom2d.NewPoint(10, 0),
}, geom2d.PTSolid)
if err != nil {
fmt.Println("Error creating PolyTree 1:", err)
return
}
// Create the second PolyTree
pt2, err := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(5, 5),
geom2d.NewPoint(5, 15),
geom2d.NewPoint(15, 15),
geom2d.NewPoint(15, 5),
}, geom2d.PTSolid)
if err != nil {
fmt.Println("Error creating PolyTree 2:", err)
return
}
// Calculate the relationships
relationships := pt1.RelationshipToPolyTree(pt2)
fmt.Printf("pt1's relationship to pt2: %v\n", relationships[pt1][pt2])
}
pt1's relationship to pt2: RelationshipIntersection
func (pt *PolyTree[T]) RelationshipToRectangle(r Rectangle[T], opts ...Option) map[*PolyTree[T]]Relationship
RelationshipToRectangle computes the relationship between the given Rectangle (r) and each polygon in the PolyTree (pt).
The function evaluates whether the rectangle is disjoint from, intersects with, contains, or is contained by each polygon in the PolyTree. It uses Rectangle.RelationshipToPolyTree to determine the relationship and flips the containment relationships so that they are expressed from the PolyTree's perspective.
Parameters:
- r (Rectangle[T]): The rectangle to evaluate against the polygons in the PolyTree.
- opts: A variadic slice of Option functions to customize the behavior of the relationship check. WithEpsilon(epsilon float64): Specifies a tolerance for geometric calculations, improving robustness against floating-point imprecision.
Returns:
- map[*PolyTree[T]]Relationship: A map where the keys are pointers to the polygons in the PolyTree, and the values are the relationships of the rectangle to those polygons, with containment relationships flipped to reflect the PolyTree's perspective.
Notes:
- This method assumes that the PolyTree contains valid and non-overlapping polygons.
- The returned relationships are expressed relative to the PolyTree, meaning containment relationships are flipped.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create a PolyTree with a root polygon and a hole
root, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(0, 0),
geom2d.NewPoint(0, 100),
geom2d.NewPoint(100, 100),
geom2d.NewPoint(100, 0),
}, geom2d.PTSolid)
hole, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(20, 20),
geom2d.NewPoint(20, 80),
geom2d.NewPoint(80, 80),
geom2d.NewPoint(80, 20),
}, geom2d.PTHole)
_ = root.AddChild(hole)
// Note: While errors are ignored in this example for simplicity, it is important to handle errors properly in
// production code to ensure robustness and reliability.
// Define a rectangle
rect := geom2d.NewRectangle(
[]geom2d.Point[int]{
geom2d.NewPoint(10, 10),
geom2d.NewPoint(90, 10),
geom2d.NewPoint(90, 90),
geom2d.NewPoint(10, 90),
},
)
// Calculate relationships
relationships := root.RelationshipToRectangle(rect)
// Output the relationships
fmt.Printf("Root polygon relationship: %v\n", relationships[root])
fmt.Printf("Hole polygon relationship: %v\n", relationships[hole])
}
Root polygon relationship: RelationshipContains
Hole polygon relationship: RelationshipContainedBy
func (pt *PolyTree[T]) Root() *PolyTree[T]
Root returns the topmost parent node of the current PolyTree. This is useful for obtaining the root node of a nested PolyTree structure.
Behavior:
- Starts at the current node and traverses upward through the parent chain until a node with no parent is found.
- If the current node is already the root, it returns the current node.
Returns:
- *PolyTree[T]: The root node of the PolyTree.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create a root/parent polygon
root, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(0, 0),
geom2d.NewPoint(100, 0),
geom2d.NewPoint(100, 100),
geom2d.NewPoint(0, 100),
}, geom2d.PTSolid)
// Create a child polygon
child, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(20, 20),
geom2d.NewPoint(80, 20),
geom2d.NewPoint(80, 80),
geom2d.NewPoint(20, 80),
}, geom2d.PTHole)
// Create a grandchild polygon
grandchild, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(40, 40),
geom2d.NewPoint(60, 40),
geom2d.NewPoint(60, 60),
geom2d.NewPoint(40, 60),
}, geom2d.PTSolid)
// Establish relationships
_ = child.AddChild(grandchild)
_ = root.AddChild(child)
// Retrieve the root node from the grandchild
fmt.Println(grandchild.Root() == root) // true
}
true
func (pt *PolyTree[T]) Rotate(pivot Point[T], radians float64, opts ...Option) *PolyTree[float64]
Rotate rotates all points in the PolyTree around a specified pivot Point by a given angle in radians, in counter-clockwise direction.
This method applies a rotation transformation to all points in the PolyTree, including its contours and nested child polygons. The rotation is performed relative to the specified pivot point and is expressed in radians.
Parameters:
- pivot (Point[T]): The Point around which all points in the PolyTree are rotated.
- radians (float64): The angle of rotation in radians. Positive values indicate counter-clockwise rotation.
- opts: A variadic slice of Option functions to customize the behavior of the relationship check. WithEpsilon(epsilon float64): Specifies a tolerance for geometric calculations, improving robustness against floating-point imprecision.
Returns:
Panics:
- This function panics if the internal point transformation logic fails.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
"math"
)
func main() {
// Create root/parent polygon - large square
root, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(0, 0),
geom2d.NewPoint(100, 0),
geom2d.NewPoint(100, 100),
geom2d.NewPoint(0, 100),
}, geom2d.PTSolid)
// Define pivot point (0, 0) and rotation angle (90° counterclockwise)
pivot := geom2d.NewPoint(0, 0)
angle := math.Pi / 2
// Perform rotation
rotated := root.Rotate(pivot, angle)
// Print before and after rotation
fmt.Println("Before rotation:")
fmt.Println(root)
fmt.Println("After 90° counterclockwise rotation:")
fmt.Println(rotated.AsIntRounded())
}
Before rotation:
PolyTree: PTSolid
Contour Points: [(0, 0), (100, 0), (100, 100), (0, 100)]
After 90° counterclockwise rotation:
PolyTree: PTSolid
Contour Points: [(-100, 0), (0, 0), (0, 100), (-100, 100)]
func (pt *PolyTree[T]) Scale(ref Point[T], k T) *PolyTree[T]
Scale scales all points in the PolyTree relative to a reference point by a given scaling factor.
This method scales all the points of the PolyTree's contours and its children relative to the reference Point ref by the scaling factor k. It maintains the relationships between parent and child polygons and adjusts their points proportionally.
Parameters:
- ref (Point[T]): The reference point used as the centre of scaling.
- k (T): The scaling factor. A value greater than 1 enlarges the PolyTree, a value between 0 and 1 shrinks it, and a negative value mirrors it.
Returns:
Panics:
- This function panics if the internal point transformation logic fails.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create root/parent polygon - large square
root, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(0, 0),
geom2d.NewPoint(100, 0),
geom2d.NewPoint(100, 100),
geom2d.NewPoint(0, 100),
}, geom2d.PTSolid)
// Create hole polygon - slightly smaller square
hole, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(20, 20),
geom2d.NewPoint(80, 20),
geom2d.NewPoint(80, 80),
geom2d.NewPoint(20, 80),
}, geom2d.PTHole)
// Create island polygon - even slightly smaller square
island, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(40, 40),
geom2d.NewPoint(60, 40),
geom2d.NewPoint(60, 60),
geom2d.NewPoint(40, 60),
}, geom2d.PTSolid)
// Set up polygon relationships
_ = hole.AddChild(island)
_ = root.AddChild(hole)
// Scale by a factor of 2 with origin 0,0
scaled := root.Scale(geom2d.NewPoint(0, 0), 2)
// Print output
fmt.Println("Before scaling:")
fmt.Println(root)
fmt.Println("After scaling:")
fmt.Println(scaled)
}
Before scaling:
PolyTree: PTSolid
Contour Points: [(0, 0), (100, 0), (100, 100), (0, 100)]
PolyTree: PTHole
Contour Points: [(20, 20), (20, 80), (80, 80), (80, 20)]
PolyTree: PTSolid
Contour Points: [(40, 40), (60, 40), (60, 60), (40, 60)]
After scaling:
PolyTree: PTSolid
Contour Points: [(0, 0), (200, 0), (200, 200), (0, 200)]
PolyTree: PTHole
Contour Points: [(40, 160), (160, 160), (160, 40), (40, 40)]
PolyTree: PTSolid
Contour Points: [(80, 80), (120, 80), (120, 120), (80, 120)]
func (pt *PolyTree[T]) Siblings() []*PolyTree[T]
Siblings returns a slice of sibling polygons of the current PolyTree.
This function provides access to polygons at the same hierarchical level as the current PolyTree. The siblings are polygons that share the same parent as the current PolyTree.
Returns:
- []*PolyTree[T]: A slice of pointers to sibling PolyTrees.
Notes:
- If the current PolyTree has no siblings, an empty slice is returned.
- The slice does not include the current PolyTree itself.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create a root polygon
root, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(0, 0),
geom2d.NewPoint(0, 100),
geom2d.NewPoint(100, 100),
geom2d.NewPoint(100, 0),
}, geom2d.PTSolid)
// Create sibling polygons
sibling1, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(150, 150),
geom2d.NewPoint(150, 250),
geom2d.NewPoint(250, 250),
geom2d.NewPoint(250, 150),
}, geom2d.PTSolid)
sibling2, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(300, 300),
geom2d.NewPoint(300, 400),
geom2d.NewPoint(400, 400),
geom2d.NewPoint(400, 300),
}, geom2d.PTSolid)
// Add siblings
_ = root.AddSibling(sibling1)
_ = root.AddSibling(sibling2)
// Note: While errors are ignored in this example for simplicity, it is important to handle errors properly in
// production code to ensure robustness and reliability.
// Get siblings of root
siblings := root.Siblings()
for _, sibling := range siblings {
fmt.Println(sibling.Contour())
}
}
[Point[(150, 150)] Point[(250, 150)] Point[(250, 250)] Point[(150, 250)]]
[Point[(300, 300)] Point[(400, 300)] Point[(400, 400)] Point[(300, 400)]]
func (pt *PolyTree[T]) String() string
String returns a string representation of the PolyTree, displaying its hierarchy, polygon type, and contour points.
This method uses the Nodes method to traverse the entire PolyTree and represent each polygon's type, contour points, and relationships.
Returns:
- string: A human-readable representation of the PolyTree.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create a root PolyTree with a solid contour
root, _ := geom2d.NewPolyTree(
[]geom2d.Point[int]{
geom2d.NewPoint(0, 0),
geom2d.NewPoint(10, 0),
geom2d.NewPoint(10, 10),
geom2d.NewPoint(0, 10),
},
geom2d.PTSolid,
)
// Create a hole PolyTree inside the root
hole, _ := geom2d.NewPolyTree(
[]geom2d.Point[int]{
geom2d.NewPoint(3, 3),
geom2d.NewPoint(7, 3),
geom2d.NewPoint(7, 7),
geom2d.NewPoint(3, 7),
},
geom2d.PTHole,
)
// Add the hole as a child of the root
_ = root.AddChild(hole)
// Create an island PolyTree inside the hole
island, _ := geom2d.NewPolyTree(
[]geom2d.Point[int]{
geom2d.NewPoint(4, 4),
geom2d.NewPoint(6, 4),
geom2d.NewPoint(6, 6),
geom2d.NewPoint(4, 6),
},
geom2d.PTSolid,
)
// Add the island as a child of the hole
_ = hole.AddChild(island)
// Print the PolyTree's string representation
fmt.Println(root.String())
}
PolyTree: PTSolid
Contour Points: [(0, 0), (10, 0), (10, 10), (0, 10)]
PolyTree: PTHole
Contour Points: [(3, 3), (3, 7), (7, 7), (7, 3)]
PolyTree: PTSolid
Contour Points: [(4, 4), (6, 4), (6, 6), (4, 6)]
func (pt *PolyTree[T]) Translate(delta Point[T]) *PolyTree[T]
Translate moves all points in the PolyTree by a specified displacement vector (given as a Point).
This method applies the given delta vector to all points in the PolyTree, including its contours and any nested child polygons. The displacement vector is added to each point's coordinates.
Parameters:
- delta (Point[T]): The displacement vector to apply to all points.
Returns:
Panics:
- This function panics if the internal point transformation logic fails.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create root/parent polygon - large square
root, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(0, 0),
geom2d.NewPoint(100, 0),
geom2d.NewPoint(100, 100),
geom2d.NewPoint(0, 100),
}, geom2d.PTSolid)
// Create hole polygon - smaller square
hole, _ := geom2d.NewPolyTree([]geom2d.Point[int]{
geom2d.NewPoint(20, 20),
geom2d.NewPoint(80, 20),
geom2d.NewPoint(80, 80),
geom2d.NewPoint(20, 80),
}, geom2d.PTHole)
// Add hole to root
_ = root.AddChild(hole)
// Translate the entire PolyTree by (10, 10)
translated := root.Translate(geom2d.NewPoint(10, 10))
// Print before and after translation
fmt.Println("Before translation:")
fmt.Println(root)
fmt.Println("After translation:")
fmt.Println(translated)
}
Before translation:
PolyTree: PTSolid
Contour Points: [(0, 0), (100, 0), (100, 100), (0, 100)]
PolyTree: PTHole
Contour Points: [(20, 20), (20, 80), (80, 80), (80, 20)]
After translation:
PolyTree: PTSolid
Contour Points: [(10, 10), (110, 10), (110, 110), (10, 110)]
PolyTree: PTHole
Contour Points: [(30, 90), (90, 90), (90, 30), (30, 30)]
PolyTreeMismatch represents a bitmask of potential mismatches between two PolyTree structures.
type PolyTreeMismatch uint8
Valid values for PolyTreeMismatch
const (
// PTMNoMismatch indicates that there is no mismatch between the compared PolyTree structures.
PTMNoMismatch PolyTreeMismatch = 0 << iota
// PTMNilPolygonMismatch indicates that one of the PolyTree structures is nil while the other is not.
PTMNilPolygonMismatch PolyTreeMismatch = 1
// PTMContourMismatch indicates that the contours of the compared PolyTree structures are not identical.
// This could mean differences in the points, their order, or their overall shape.
PTMContourMismatch PolyTreeMismatch = 2
// PTMSiblingMismatch indicates that the siblings of the compared PolyTree structures do not match.
// This could mean differences in the number of siblings, their contours, or their structure.
PTMSiblingMismatch PolyTreeMismatch = 4
// PTMChildMismatch indicates that the children of the compared PolyTree structures do not match.
// This could mean differences in the number of children, their contours, or their structure.
PTMChildMismatch PolyTreeMismatch = 8
)
PolygonType (PT) defines whether the inside of the contour of a polygon represents either a solid region (island) or a void region (hole). This distinction is essential for operations involving polygons with complex structures, such as those containing holes or nested islands.
type PolygonType uint8
const (
// PTSolid represents a solid region of the polygon, commonly referred to as an "island."
// PTSolid polygons are the primary filled areas, excluding any void regions (holes).
PTSolid PolygonType = iota
// PTHole represents a void region of the polygon, often nested within a solid polygon.
// Holes are not part of the filled area of the polygon and are treated as exclusions.
PTHole
)
func (t PolygonType) String() string
String returns a string representation of the PolygonType.
This method converts the enum value of a PolygonType into a human-readable string, making it useful for debugging, logging, or providing textual representations.
Returns:
- string: A string representation of the PolygonType, such as PTSolid or PTHole.
Panics:
- If the PolygonType value is unsupported, the method will panic with an appropriate error message.
Notes:
- This method assumes that the PolygonType is valid. If new polygon types are added in the future, ensure this method is updated to include them to avoid panics.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
fmt.Println(geom2d.PTSolid.String())
}
PTSolid
Rectangle represents an axis-aligned rectangle defined by its four corners.
type Rectangle[T SignedNumber] struct {
// contains filtered or unexported fields
}
func NewRectangle[T SignedNumber](points []Point[T]) Rectangle[T]
NewRectangle creates a new Rectangle from a slice of four points. The points can be provided in any order, but they must form an axis-aligned rectangle.
Parameters:
- points ([]Point[T]): A slice of four points.
Returns:
Panics:
- If the provided points do not form an axis-aligned rectangle, the function panics.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Define four points that form an axis-aligned rectangle
points := []geom2d.Point[int]{
geom2d.NewPoint(1, 1), // bottom-left
geom2d.NewPoint(4, 1), // bottom-right
geom2d.NewPoint(1, 3), // top-left
geom2d.NewPoint(4, 3), // top-right
}
// Create the rectangle
rect := geom2d.NewRectangle(points)
// Print the corners of the rectangle
for _, point := range rect.Contour() {
fmt.Println(point)
}
}
Point[(1, 3)]
Point[(4, 3)]
Point[(4, 1)]
Point[(1, 1)]
func NewRectangleFromImageRect(r image.Rectangle) Rectangle[int]
NewRectangleFromImageRect creates a new Rectangle[T] from an image.Rectangle.
Parameters:
- r image.Rectangle: The image.Rectangle to convert.
Returns:
- Rectangle[int]: A new rectangle with integer coordinates matching the given image.Rectangle.
Behavior:
- The function maps the minimum point of the image.Rectangle to the top-left corner and the maximum point to the bottom-right corner of the resulting rectangle.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
"image"
)
func main() {
// Define an image.Rectangle
imgRect := image.Rect(10, 20, 50, 80)
// Create a Rectangle[int] from the image.Rectangle
rect := geom2d.NewRectangleFromImageRect(imgRect)
// Print the corners of the rectangle
for _, point := range rect.Contour() {
fmt.Println(point)
}
}
Point[(10, 80)]
Point[(50, 80)]
Point[(50, 20)]
Point[(10, 20)]
func (r Rectangle[T]) Area() T
Area calculates the area of the rectangle.
Returns:
- T: The area of the rectangle, calculated as Width * Height.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create a rectangle
rect := geom2d.NewRectangle([]geom2d.Point[int]{
geom2d.NewPoint(0, 0),
geom2d.NewPoint(10, 0),
geom2d.NewPoint(10, 5),
geom2d.NewPoint(0, 5),
})
// Calculate the area
area := rect.Area()
// Print the area
fmt.Printf("The area of the rectangle is: %d\n", area)
}
The area of the rectangle is: 50
func (r Rectangle[T]) AsFloat32() Rectangle[float32]
AsFloat32 converts the Rectangle's corner points to the float32 type, useful for higher-precision operations.
Returns:
- Rectangle[float32]: A new Rectangle with float32 coordinates.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create an integer-based rectangle
rect := geom2d.NewRectangle([]geom2d.Point[int]{
geom2d.NewPoint(0, 0),
geom2d.NewPoint(10, 0),
geom2d.NewPoint(10, 5),
geom2d.NewPoint(0, 5),
})
// Convert the rectangle to a float32-based rectangle
rectFloat32 := rect.AsFloat32()
// Print the original and converted rectangle
fmt.Println("Original rectangle (int):")
for _, point := range rect.Contour() {
fmt.Println(point)
}
fmt.Println("Converted rectangle (float32):")
for _, point := range rectFloat32.Contour() {
fmt.Println(point)
}
}
Original rectangle (int):
Point[(0, 5)]
Point[(10, 5)]
Point[(10, 0)]
Point[(0, 0)]
Converted rectangle (float32):
Point[(0, 5)]
Point[(10, 5)]
Point[(10, 0)]
Point[(0, 0)]
func (r Rectangle[T]) AsFloat64() Rectangle[float64]
AsFloat64 converts the Rectangle's corner points to the float64 type, useful for higher-precision operations.
Returns:
- Rectangle[float64]: A new Rectangle with float64 coordinates.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create an integer-based rectangle
rect := geom2d.NewRectangle([]geom2d.Point[int]{
geom2d.NewPoint(0, 0),
geom2d.NewPoint(10, 0),
geom2d.NewPoint(10, 5),
geom2d.NewPoint(0, 5),
})
// Convert the rectangle to a float64-based rectangle
rectFloat64 := rect.AsFloat64()
// Print the original and converted rectangle
fmt.Println("Original rectangle (int):")
for _, point := range rect.Contour() {
fmt.Println(point)
}
fmt.Println("Converted rectangle (float64):")
for _, point := range rectFloat64.Contour() {
fmt.Println(point)
}
}
Original rectangle (int):
Point[(0, 5)]
Point[(10, 5)]
Point[(10, 0)]
Point[(0, 0)]
Converted rectangle (float64):
Point[(0, 5)]
Point[(10, 5)]
Point[(10, 0)]
Point[(0, 0)]
func (r Rectangle[T]) AsInt() Rectangle[int]
AsInt converts the Rectangle's corner points to the int type by truncating any decimal values. This method is useful for operations requiring integer coordinates.
Returns:
- Rectangle[int]: A new Rectangle with integer coordinates, truncated from the original values.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create a float64-based rectangle
rect := geom2d.NewRectangle([]geom2d.Point[float64]{
geom2d.NewPoint(0.5, 0.5),
geom2d.NewPoint(10.5, 0.5),
geom2d.NewPoint(10.5, 5.5),
geom2d.NewPoint(0.5, 5.5),
})
// Convert the rectangle to an int-based rectangle
rectInt := rect.AsInt()
// Print the original and converted rectangle
fmt.Println("Original rectangle (float64):")
for _, point := range rect.Contour() {
fmt.Println(point)
}
fmt.Println("Converted rectangle (int):")
for _, point := range rectInt.Contour() {
fmt.Println(point)
}
}
Original rectangle (float64):
Point[(0.5, 5.5)]
Point[(10.5, 5.5)]
Point[(10.5, 0.5)]
Point[(0.5, 0.5)]
Converted rectangle (int):
Point[(0, 5)]
Point[(10, 5)]
Point[(10, 0)]
Point[(0, 0)]
func (r Rectangle[T]) AsIntRounded() Rectangle[int]
AsIntRounded converts the Rectangle's corner points to the int type by rounding to the nearest integer. This method is useful for operations requiring integer coordinates with rounding.
Returns:
- Rectangle[int]: A new Rectangle with integer coordinates, rounded from the original values.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create a float64-based rectangle
rect := geom2d.NewRectangle([]geom2d.Point[float64]{
geom2d.NewPoint(0.5, 0.5),
geom2d.NewPoint(10.7, 0.5),
geom2d.NewPoint(10.7, 5.3),
geom2d.NewPoint(0.5, 5.3),
})
// Convert the rectangle to an int-based rectangle with rounding
rectIntRounded := rect.AsIntRounded()
// Print the original and converted rectangle
fmt.Println("Original rectangle (float64):")
for _, point := range rect.Contour() {
fmt.Println(point)
}
fmt.Println("Converted rectangle (int):")
for _, point := range rectIntRounded.Contour() {
fmt.Println(point)
}
}
Original rectangle (float64):
Point[(0.5, 5.3)]
Point[(10.7, 5.3)]
Point[(10.7, 0.5)]
Point[(0.5, 0.5)]
Converted rectangle (int):
Point[(1, 5)]
Point[(11, 5)]
Point[(11, 1)]
Point[(1, 1)]
func (r Rectangle[T]) ContainsPoint(p Point[T]) bool
ContainsPoint checks if a given Point lies within or on the boundary of the Rectangle.
Parameters:
- p: The Point to check.
Returns:
- bool: Returns true if the point lies inside or on the boundary of the rectangle, false otherwise.
Behavior:
- A point is considered contained if its x-coordinate is between the left and right edges of the Rectangle, and its y-coordinate is between the top and bottom edges of the rectangle.
- The rectangle's boundary is inclusive for both x and y coordinates.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create an integer-based rectangle
rect := geom2d.NewRectangle([]geom2d.Point[int]{
geom2d.NewPoint(0, 10),
geom2d.NewPoint(10, 10),
geom2d.NewPoint(10, 0),
geom2d.NewPoint(0, 0),
})
// Check if the rectangle contains various points
points := []geom2d.Point[int]{
geom2d.NewPoint(5, 5), // Inside
geom2d.NewPoint(0, 10), // On the top-left corner
geom2d.NewPoint(10, 0), // On the bottom-right corner
geom2d.NewPoint(-1, 5), // Outside (left)
geom2d.NewPoint(5, 11), // Outside (above)
}
for _, p := range points {
fmt.Printf("Point %v contained: %v\n", p, rect.ContainsPoint(p))
}
}
Point Point[(5, 5)] contained: true
Point Point[(0, 10)] contained: true
Point Point[(10, 0)] contained: true
Point Point[(-1, 5)] contained: false
Point Point[(5, 11)] contained: false
func (r Rectangle[T]) Contour() []Point[T]
Contour returns the four corner points of the rectangle in the following order: top-left, top-right, bottom-right, and bottom-left.
Returns:
- []Point[T]: A slice containing the four corner points of the rectangle.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create a rectangle
rect := geom2d.NewRectangle([]geom2d.Point[int]{
geom2d.NewPoint(0, 10), // top-left
geom2d.NewPoint(10, 10), // top-right
geom2d.NewPoint(10, 0), // bottom-right
geom2d.NewPoint(0, 0), // bottom-left
})
// Get the points of the rectangle
points := rect.Contour()
// Print the points
fmt.Println("Contour of the rectangle:")
for _, point := range points {
fmt.Println(point)
}
}
Contour of the rectangle:
Point[(0, 10)]
Point[(10, 10)]
Point[(10, 0)]
Point[(0, 0)]
func (r Rectangle[T]) Edges() []LineSegment[T]
Edges returns the edges of the rectangle as a slice of LineSegment[T]. Each edge is represented as a line segment connecting two adjacent corners of the rectangle.
Returns:
- []LineSegment[T]: A slice of line segments representing the edges of the rectangle.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create a rectangle
rect := geom2d.NewRectangle([]geom2d.Point[int]{
geom2d.NewPoint(0, 0),
geom2d.NewPoint(10, 0),
geom2d.NewPoint(10, 5),
geom2d.NewPoint(0, 5),
})
// Get the edges of the rectangle
edges := rect.Edges()
// Print the edges
for i, edge := range edges {
fmt.Printf("Edge %d: Start %v, End %v\n", i+1, edge.Start(), edge.End())
}
}
Edge 1: Start Point[(0, 0)], End Point[(10, 0)]
Edge 2: Start Point[(10, 0)], End Point[(10, 5)]
Edge 3: Start Point[(10, 5)], End Point[(0, 5)]
Edge 4: Start Point[(0, 5)], End Point[(0, 0)]
func (r Rectangle[T]) Eq(other Rectangle[T]) bool
Eq checks if two Rectangle instances are equal.
Parameters:
Returns:
- bool: Returns true if the two rectangles have identical corner points (bottom-left, bottom-right, top-right, and top-left), false otherwise.
Behavior:
- The comparison is based on the exact equality of the corner points.
- Both rectangles must have the same coordinates for all four corners to be considered equal.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create two identical rectangles
rect1 := geom2d.NewRectangle([]geom2d.Point[int]{
geom2d.NewPoint(0, 10),
geom2d.NewPoint(10, 10),
geom2d.NewPoint(10, 0),
geom2d.NewPoint(0, 0),
})
rect2 := geom2d.NewRectangle([]geom2d.Point[int]{
geom2d.NewPoint(0, 10),
geom2d.NewPoint(10, 10),
geom2d.NewPoint(10, 0),
geom2d.NewPoint(0, 0),
})
// Create a different rectangle
rect3 := geom2d.NewRectangle([]geom2d.Point[int]{
geom2d.NewPoint(1, 11),
geom2d.NewPoint(11, 11),
geom2d.NewPoint(11, 1),
geom2d.NewPoint(1, 1),
})
// Compare the rectangles
fmt.Println("rect1 equals rect2:", rect1.Eq(rect2))
fmt.Println("rect1 equals rect3:", rect1.Eq(rect3))
}
rect1 equals rect2: true
rect1 equals rect3: false
func (r Rectangle[T]) Height() T
Height calculates the height of the rectangle.
Returns:
- T: The height of the rectangle, calculated as the absolute difference between the y-coordinates of the top-left and bottom-right corners.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create a rectangle
rect := geom2d.NewRectangle([]geom2d.Point[int]{
geom2d.NewPoint(0, 10), // top-left
geom2d.NewPoint(10, 10), // top-right
geom2d.NewPoint(10, 0), // bottom-right
geom2d.NewPoint(0, 0), // bottom-left
})
// Calculate and print the height of the rectangle
fmt.Println("Height of the rectangle:", rect.Height())
}
Height of the rectangle: 10
func (r Rectangle[T]) Perimeter() T
Perimeter calculates the perimeter of the rectangle.
Returns:
- T: The perimeter of the rectangle, calculated as 2 * (Width + Height).
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create a rectangle
rect := geom2d.NewRectangle([]geom2d.Point[int]{
geom2d.NewPoint(0, 10),
geom2d.NewPoint(10, 10),
geom2d.NewPoint(10, 0),
geom2d.NewPoint(0, 0),
})
// Calculate and print the perimeter of the rectangle
fmt.Println("Perimeter of the rectangle:", rect.Perimeter())
}
Perimeter of the rectangle: 40
func (r Rectangle[T]) RelationshipToCircle(c Circle[T], opts ...Option) Relationship
RelationshipToCircle determines the spatial relationship between a rectangle and a circle.
This function evaluates whether the given Circle is:
- Disjoint from the rectangle (RelationshipDisjoint)
- Intersecting the rectangle's boundary (RelationshipIntersection)
- Fully contained within the rectangle (RelationshipContains)
The function delegates the relationship check to the circle's Circle.RelationshipToRectangle method and flips the containment perspective to represent the rectangle's relationship to the circle.
Parameters:
- c (Circle[T]): The circle to compare with the rectangle.
- opts: A variadic slice of Option functions to customize the equality check. WithEpsilon(epsilon float64): Specifies a tolerance for comparing the center coordinates and radii of the circles, allowing for robust handling of floating-point precision errors.
Returns:
- Relationship: The relationship of the rectangle to the circle.
Notes:
- The returned relationship reflects the rectangle's perspective.
- Use the WithEpsilon option to adjust for floating-point precision errors during the calculations.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Define a rectangle
rect := geom2d.NewRectangle([]geom2d.Point[int]{
geom2d.NewPoint[int](0, 0),
geom2d.NewPoint[int](100, 0),
geom2d.NewPoint[int](100, 100),
geom2d.NewPoint[int](0, 100),
})
// Define circles to test
circleInside := geom2d.NewCircle(geom2d.NewPoint[int](50, 50), 10)
circleIntersecting := geom2d.NewCircle(geom2d.NewPoint[int](50, 50), 60)
circleOutside := geom2d.NewCircle(geom2d.NewPoint[int](200, 200), 20)
// Check relationships
fmt.Println("Circle inside:", rect.RelationshipToCircle(circleInside))
fmt.Println("Circle intersecting:", rect.RelationshipToCircle(circleIntersecting))
fmt.Println("Circle outside:", rect.RelationshipToCircle(circleOutside))
}
Circle inside: RelationshipContains
Circle intersecting: RelationshipIntersection
Circle outside: RelationshipDisjoint
func (r Rectangle[T]) RelationshipToLineSegment(l LineSegment[T], opts ...Option) Relationship
RelationshipToLineSegment determines the spatial relationship between a rectangle and a line segment.
This function checks whether the given LineSegment is:
- Disjoint from the rectangle (RelationshipDisjoint)
- Intersecting the rectangle's boundary (RelationshipIntersection)
- Fully contained within the rectangle (RelationshipContains)
The relationship is determined by delegating the check to the line segment's LineSegment.RelationshipToRectangle method and then flipping the containment perspective to describe the rectangle's relationship to the line segment.
Parameters:
- l (LineSegment[T]): The line segment to compare with the rectangle.
- opts: A variadic slice of Option functions to customize the equality check. WithEpsilon(epsilon float64): Specifies a tolerance for comparing the center coordinates and radii of the circles, allowing for robust handling of floating-point precision errors.
Returns:
- Relationship: The relationship of the rectangle to the line segment.
Notes:
- The returned relationship is flipped to represent the rectangle's perspective.
- The behavior of the function can be customized using the WithEpsilon option to handle floating-point precision.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Define a rectangle
rect := geom2d.NewRectangle([]geom2d.Point[int]{
geom2d.NewPoint[int](0, 0),
geom2d.NewPoint[int](100, 0),
geom2d.NewPoint[int](100, 100),
geom2d.NewPoint[int](0, 100),
})
// Define line segments to check against the rectangle
segmentInside := geom2d.NewLineSegment(geom2d.NewPoint(10, 10), geom2d.NewPoint(90, 90))
segmentIntersecting := geom2d.NewLineSegment(geom2d.NewPoint(-10, 50), geom2d.NewPoint(110, 50))
segmentOutside := geom2d.NewLineSegment(geom2d.NewPoint(200, 200), geom2d.NewPoint(300, 300))
// Check relationships
fmt.Println("Segment inside:", rect.RelationshipToLineSegment(segmentInside))
fmt.Println("Segment intersecting:", rect.RelationshipToLineSegment(segmentIntersecting))
fmt.Println("Segment outside:", rect.RelationshipToLineSegment(segmentOutside))
}
Segment inside: RelationshipContains
Segment intersecting: RelationshipIntersection
Segment outside: RelationshipDisjoint
func (r Rectangle[T]) RelationshipToPoint(p Point[T], opts ...Option) Relationship
RelationshipToPoint determines the spatial relationship between a rectangle and a point.
This function checks whether the given Point is:
- Outside the rectangle (RelationshipDisjoint)
- On the rectangle's edge or vertex (RelationshipIntersection)
- Fully contained within the rectangle (RelationshipContains)
The relationship is determined by delegating the check to the point's Point.RelationshipToRectangle method and then flipping the containment perspective to describe the rectangle's relationship to the point.
Parameters:
- p (Point[T]): The point to compare with the rectangle.
- opts: A variadic slice of Option functions to customize the equality check. WithEpsilon(epsilon float64): Specifies a tolerance for comparing the center coordinates and radii of the circles, allowing for robust handling of floating-point precision errors.
Returns:
- Relationship: The relationship of the rectangle to the point.
Notes:
- The returned relationship is flipped to represent the rectangle's perspective.
- The behavior of the function can be customized using the WithEpsilon option to handle floating-point precision.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Define a rectangle
rect := geom2d.NewRectangle([]geom2d.Point[int]{
geom2d.NewPoint(0, 0),
geom2d.NewPoint(100, 0),
geom2d.NewPoint(100, 100),
geom2d.NewPoint(0, 100),
})
// Define points to check against the rectangle
pointInside := geom2d.NewPoint(50, 50)
pointOnEdge := geom2d.NewPoint(0, 50)
pointOnVertex := geom2d.NewPoint(0, 0)
pointOutside := geom2d.NewPoint(200, 200)
// Check relationships
fmt.Println("Point inside:", rect.RelationshipToPoint(pointInside))
fmt.Println("Point on edge:", rect.RelationshipToPoint(pointOnEdge))
fmt.Println("Point on vertex:", rect.RelationshipToPoint(pointOnVertex))
fmt.Println("Point outside:", rect.RelationshipToPoint(pointOutside))
}
Point inside: RelationshipContains
Point on edge: RelationshipIntersection
Point on vertex: RelationshipIntersection
Point outside: RelationshipDisjoint
func (r Rectangle[T]) RelationshipToPolyTree(pt *PolyTree[T], opts ...Option) map[*PolyTree[T]]Relationship
RelationshipToPolyTree determines the spatial relationship between a rectangle and a PolyTree.
This method evaluates how the calling Rectangle (r) relates to each polygon in the given PolyTree (pt). The relationships include intersection, containment, and disjoint.
Parameters:
- pt: A pointer to the PolyTree to compare with the calling rectangle.
- opts: A variadic slice of Option functions to customize the behavior of the relationship check. WithEpsilon(epsilon float64): Specifies a tolerance for comparing points and collinearity calculations, allowing for robust handling of floating-point precision errors.
Behavior:
- For each polygon in the PolyTree, the function determines whether the rectangle intersects, contains, is contained by, or is disjoint from the polygon.
- Intersection is determined by checking if any edge of the rectangle intersects any edge of the polygon.
- Containment is determined by checking whether all edges of one shape lie within the other.
Returns:
- map[*PolyTree[T]]Relationship: A map where each key is a polygon in the PolyTree, and the value is the relationship between the rectangle and that polygon.
Notes:
- The function assumes that both the rectangle and the polygons in the PolyTree are valid (e.g., non-degenerate).
- Epsilon adjustment is useful for floating-point coordinates, where small precision errors might otherwise cause incorrect results.
func (r Rectangle[T]) RelationshipToRectangle(other Rectangle[T], opts ...Option) Relationship
RelationshipToRectangle determines the spatial relationship between two rectangles.
This method evaluates the relationship between the calling Rectangle (r) and another Rectangle (other). It checks for equality, intersections, containment, and disjoint relationships. The function considers edge and vertex overlap to ensure accurate results.
Parameters:
- other: The Rectangle to compare with the calling Rectangle.
- opts: A variadic slice of Option functions to customize the behavior of the relationship check. WithEpsilon(epsilon float64): Specifies a tolerance for comparing points and collinearity calculations, allowing for robust handling of floating-point precision errors.
Behavior:
- The function first checks if the two rectangles are equal.
- It then evaluates whether the rectangles intersect by checking all edge pairs.
- If no intersections are found, the function checks if one rectangle is fully contained within the other.
- If neither intersects nor is contained, the rectangles are considered disjoint.
Returns:
Relationship: A constant indicating the relationship between the two rectangles, which can be:
- RelationshipEqual: The rectangles are identical in position and size.
- RelationshipIntersection: The rectangles overlap but are not fully contained.
- RelationshipContains: The calling rectangle fully contains the other rectangle.
- RelationshipContainedBy: The calling rectangle is fully contained within the other rectangle.
- RelationshipDisjoint: The rectangles do not overlap or touch.
Notes:
- The function assumes the input rectangles are valid (e.g., non-degenerate).
- Epsilon adjustment is useful for floating-point coordinates, where small precision errors might otherwise cause incorrect results.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Define two rectangles
r1 := geom2d.NewRectangle([]geom2d.Point[int]{
geom2d.NewPoint(0, 0),
geom2d.NewPoint(10, 0),
geom2d.NewPoint(10, 10),
geom2d.NewPoint(0, 10),
})
r2 := geom2d.NewRectangle([]geom2d.Point[int]{
geom2d.NewPoint(5, 5),
geom2d.NewPoint(15, 5),
geom2d.NewPoint(15, 15),
geom2d.NewPoint(5, 15),
})
// Determine the relationship
relationship := r1.RelationshipToRectangle(r2, geom2d.WithEpsilon(1e-10))
// Output the result
fmt.Println("Relationship:", relationship)
}
Relationship: RelationshipIntersection
func (r Rectangle[T]) Scale(ref Point[T], k T) Rectangle[T]
Scale scales the Rectangle relative to a specified reference Point by a given scalar factor.
Each corner of the rectangle is scaled relative to the reference point using the provided factor. The resulting rectangle maintains its axis-aligned orientation.
Parameters:
- ref (Point[T]): The reference point relative to which the rectangle is scaled.
- k (T): The scaling factor. A value > 1 enlarges the rectangle; < 1 shrinks it.
Returns:
- Rectangle[T]: A new rectangle with corners scaled relative to the reference point.
Notes:
- The function delegates the scaling of each corner to the Point.Scale method.
- The rectangle remains axis-aligned after scaling.
- If the scaling factor k is 1, the rectangle remains unchanged.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create a rectangle
rect := geom2d.NewRectangle([]geom2d.Point[int]{
geom2d.NewPoint(0, 10), // top-left
geom2d.NewPoint(10, 10), // top-right
geom2d.NewPoint(0, 0), // bottom-left
geom2d.NewPoint(10, 0), // bottom-right
})
// Scale the rectangle by a factor of 2 relative to the origin
scaled := rect.Scale(geom2d.NewPoint(0, 0), 2)
// Print the original and scaled rectangles
fmt.Println("Original Rectangle Contour:", rect.Contour())
fmt.Println("Scaled Rectangle Contour:", scaled.Contour())
}
Original Rectangle Contour: [Point[(0, 10)] Point[(10, 10)] Point[(10, 0)] Point[(0, 0)]]
Scaled Rectangle Contour: [Point[(0, 20)] Point[(20, 20)] Point[(20, 0)] Point[(0, 0)]]
func (r Rectangle[T]) ScaleHeight(factor float64) Rectangle[float64]
ScaleHeight scales the height of the rectangle by a scalar value.
Parameters:
- factor (float64): The scaling factor for the height. A value > 1 enlarges the height; < 1 shrinks it.
Returns:
- Rectangle[float64]: A new Rectangle with the height scaled by the given factor.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create a rectangle
rect := geom2d.NewRectangle([]geom2d.Point[int]{
geom2d.NewPoint(0, 10), // top-left
geom2d.NewPoint(10, 10), // top-right
geom2d.NewPoint(0, 0), // bottom-left
geom2d.NewPoint(10, 0), // bottom-right
})
// Scale the height of the rectangle by a factor of 1.5
scaled := rect.ScaleHeight(1.5)
// Print the original and scaled rectangles
fmt.Println("Original Rectangle Contour:", rect.Contour())
fmt.Println("Scaled Rectangle Contour:", scaled.Contour())
}
Original Rectangle Contour: [Point[(0, 10)] Point[(10, 10)] Point[(10, 0)] Point[(0, 0)]]
Scaled Rectangle Contour: [Point[(0, 25)] Point[(10, 25)] Point[(10, 10)] Point[(0, 10)]]
func (r Rectangle[T]) ScaleWidth(factor float64) Rectangle[float64]
ScaleWidth scales the width of the rectangle by a scalar value.
Parameters:
- factor (float64): The scaling factor for the width. A value > 1 enlarges the width; < 1 shrinks it.
Returns:
- Rectangle[float64]: A new Rectangle with the width scaled by the given factor.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create a rectangle
rect := geom2d.NewRectangle([]geom2d.Point[int]{
geom2d.NewPoint(0, 10), // top-left
geom2d.NewPoint(10, 10), // top-right
geom2d.NewPoint(0, 0), // bottom-left
geom2d.NewPoint(10, 0), // bottom-right
})
// Scale the width of the rectangle by a factor of 2.0
scaled := rect.ScaleWidth(2.0)
// Print the original and scaled rectangles
fmt.Println("Original Rectangle Contour:", rect.Contour())
fmt.Println("Scaled Rectangle Contour:", scaled.Contour())
}
Original Rectangle Contour: [Point[(0, 10)] Point[(10, 10)] Point[(10, 0)] Point[(0, 0)]]
Scaled Rectangle Contour: [Point[(0, 10)] Point[(20, 10)] Point[(20, 0)] Point[(0, 0)]]
func (r Rectangle[T]) String() string
String returns a string representation of the rectangle. The representation includes the coordinates of the rectangle's corners in counter-clockwise order, in the format: "Rectangle[(bottomLeft), (bottomRight), (topRight), (topLeft)]".
This is primarily useful for debugging and logging.
Returns:
- string: A formatted string showing the coordinates of the rectangle's corners.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create a rectangle
rect := geom2d.NewRectangle([]geom2d.Point[int]{
geom2d.NewPoint(0, 10),
geom2d.NewPoint(10, 10),
geom2d.NewPoint(0, 0),
geom2d.NewPoint(10, 0),
})
// Get the string representation
fmt.Println(rect.String())
}
Rectangle[(0, 0), (10, 0), (10, 10), (0, 10)]
func (r Rectangle[int]) ToImageRect() image.Rectangle
ToImageRect converts the Rectangle[int] to an image.Rectangle. This method is only available for Rectangle[int] as image.Rectangle requires integer coordinates.
Returns:
- image.Rectangle: A new image.Rectangle with coordinates matching the Rectangle.
func (r Rectangle[T]) Translate(p Point[T]) Rectangle[T]
Translate moves the rectangle by a specified vector.
This method shifts the rectangle's position in the 2D plane by translating both its top-left and bottom-right corners by the given vector p. The dimensions of the rectangle remain unchanged.
Parameters:
- p (Point[T]): The vector by which to translate the rectangle.
Returns:
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create a rectangle
rect := geom2d.NewRectangle([]geom2d.Point[int]{
geom2d.NewPoint(0, 10), // top-left
geom2d.NewPoint(10, 10), // top-right
geom2d.NewPoint(0, 0), // bottom-left
geom2d.NewPoint(10, 0), // bottom-right
})
// Translate the rectangle by (5, -5)
translatedRect := rect.Translate(geom2d.NewPoint(5, -5))
// Print the translated rectangle
fmt.Println(translatedRect.String())
}
Rectangle[(5, -5), (15, -5), (15, 5), (5, 5)]
func (r Rectangle[T]) Width() T
Width calculates the width of the rectangle.
Returns:
- T: The width of the rectangle, calculated as the absolute difference between the x-coordinates of the top-left and bottom-right corners.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Create a rectangle
rect := geom2d.NewRectangle([]geom2d.Point[int]{
geom2d.NewPoint(0, 10),
geom2d.NewPoint(20, 10),
geom2d.NewPoint(0, 0),
geom2d.NewPoint(20, 0),
})
// Calculate the width of the rectangle
width := rect.Width()
// Print the width
fmt.Printf("The width of the rectangle is: %d\n", width)
}
The width of the rectangle is: 20
ReflectionAxis specifies the axis or line across which a point or line segment should be reflected.
This type defines the possible axes for reflection, including the standard x-axis and y-axis, as well as an arbitrary line defined by a custom line segment.
type ReflectionAxis int
const (
// ReflectAcrossXAxis reflects a point or line segment across the x-axis, flipping the y-coordinate.
ReflectAcrossXAxis ReflectionAxis = iota
// ReflectAcrossYAxis reflects a point or line segment across the y-axis, flipping the x-coordinate.
ReflectAcrossYAxis
// ReflectAcrossCustomLine reflects a point or line segment across an arbitrary line defined by a LineSegment.
// This line segment can be specified as an additional argument to the Reflect method.
ReflectAcrossCustomLine
)
Relationship defines the spatial or geometric relationship between two shapes or entities in 2D space. This type is used across various functions to represent how one geometric entity relates to another.
The possible relationships include:
- Disjoint: The entities do not intersect, overlap, or touch in any way.
- Intersection: The entities share some boundary or overlap.
- Contained: One entity is fully within the boundary of another.
- ContainedBy: One entity is fully enclosed by another.
- Equal: The entities are identical in shape, size, and position.
Each relationship type is represented as a constant value of the Relationship type. Functions that evaluate relationships between geometric entities typically return one of these constants to describe the spatial relationship between them.
See the individual constant definitions for more details.
type Relationship uint8
Valid values for Relationship:
const (
// RelationshipDisjoint indicates that the two entities are completely separate,
// with no overlap, touching, or intersection.
RelationshipDisjoint Relationship = iota
// RelationshipIntersection indicates that the two entities overlap or share
// a boundary. This includes cases where the entities partially intersect
// or where they touch at one or more points.
RelationshipIntersection
// RelationshipContainedBy indicates that the first entity is fully enclosed
// within the second entity. The boundary of the first entity does not extend
// outside the boundary of the second entity.
RelationshipContainedBy
// RelationshipContains indicates that the first entity fully encloses the
// second entity. The boundary of the second entity does not extend outside
// the boundary of the first entity.
RelationshipContains
// RelationshipEqual indicates that the two entities are identical in shape,
// size, and position. This includes cases where their boundaries coincide exactly.
RelationshipEqual
)
func (r Relationship) String() string
String converts a Relationship value to its string representation.
This method provides a human-readable string corresponding to the Relationship constant, such as RelationshipDisjoint or RelationshipContainedBy. It is useful for debugging and logging purposes.
Supported Relationship values:
- RelationshipDisjoint: The objects are disjoint and do not touch or intersect.
- RelationshipIntersection: The objects intersect or overlap at some point.
- RelationshipContainedBy: The object is fully contained within another object.
- RelationshipContains: The object fully contains another object.
- RelationshipEqual: The objects are identical in size, shape, and position.
Returns:
- string: The string representation of the Relationship.
Panics:
- If the Relationship value is not supported, this method panics with an error message.
SignedNumber is a generic interface representing signed numeric types supported by this package. This interface allows functions and structs to operate generically on various numeric types, including integer and floating-point types, while restricting to signed values only.
Supported types:
- int
- int32
- int64
- float32
- float64
By using SignedNumber, functions can handle multiple numeric types without needing to be rewritten for each specific type, enabling flexible and type-safe operations across different numeric data.
type SignedNumber interface {
// contains filtered or unexported methods
}
SweepLineResult represents the outcome of processing a set of line segments using the sweep line algorithm.
This structure captures the resulting line segments and any intersection points identified during the processing. It is typically returned as the final result of the sweep line algorithm.
type SweepLineResult struct {
// The line segments that were processed.
// These may include the original segments as well as new segments created from splitting
// at intersection points.
LineSegments []LineSegment[float64]
// IntersectionPoints holds the points where intersections occurred between the line segments.
IntersectionPoints []Point[float64]
}
func SweepLine[T SignedNumber](linesegments []LineSegment[T], opts ...Option) SweepLineResult
SweepLine performs the Bentley-Ottmann algorithm to find intersections between a set of line segments.
This function detects all intersection points between the provided line segments efficiently using a sweep line approach. The algorithm operates by sorting and processing events (start points, end points, and intersections) and maintaining a dynamic status of active line segments intersecting the current sweep line.
Parameters:
- linesegments: A slice of LineSegment[T], where T is a SignedNumber type.
- opts: Optional configuration parameters.
Returns:
A SweepLineResult containing:
- The intersection points detected between line segments.
- A copy of the line segments (as float64).
Algorithm Steps:
- Convert all input line segments to LineSegment[float64].
- Initialize a priority queue (event queue) with start and end events for all line segments.
- Process each event in the event queue in ascending order of x-coordinate (with ties resolved by y-coordinate).
- Maintain a status structure (`sweepLineStatus`) to store active line segments intersecting the current sweep line.
- For each event: Start: Insert the segment into the sweep line status and check for intersections with neighbors. End: Remove the segment from the sweep line status and check for new intersections between its former neighbors. Intersection: Record the intersection and swap the two intersecting segments in the sweep line status.
Example
package main
import (
"fmt"
"github.com/mikenye/geom2d"
)
func main() {
// Define a set of line segments.
lineSegments := []geom2d.LineSegment[int]{
geom2d.NewLineSegment(geom2d.NewPoint(0, 0), geom2d.NewPoint(10, 4)),
geom2d.NewLineSegment(geom2d.NewPoint(0, 2), geom2d.NewPoint(10, 0)),
geom2d.NewLineSegment(geom2d.NewPoint(0, 4), geom2d.NewPoint(10, 2)),
geom2d.NewLineSegment(geom2d.NewPoint(1, -1), geom2d.NewPoint(10, 1)),
}
// Call SweepLine to find intersections.
result := geom2d.SweepLine(lineSegments)
// Print the intersection points.
fmt.Println("Intersection Points:")
for _, point := range result.IntersectionPoints {
fmt.Printf("(%.4f, %.4f)\n", point.X(), point.Y())
}
}
Intersection Points:
(3.3333, 1.3333)
(6.6667, 2.6667)
(7.6316, 0.4737)
Generated by gomarkdoc