Skip to content
mikenye edited this page Jan 2, 2025 · 10 revisions

geom2d

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.

Coordinate System

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.

Core Geometric Types

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.

Support for Generics

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.

Precision Control with Epsilon

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.

Relationships Between Geometric Types

This library provides methods to compute relationships between geometric types using a standardized set of relationships: RelationshipDisjoint, RelationshipIntersection, RelationshipContainedBy, RelationshipContains, and RelationshipEqual.

Acknowledgments

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.

Index

func EnsureClockwise

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)
}

Output

[Point[(4, 4)] Point[(4, 0)] Point[(0, 0)]]

func EnsureCounterClockwise

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)
}

Output

[Point[(4, 0)] Point[(4, 4)] Point[(0, 0)]]

func IsWellFormedPolygon

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:

  1. It has at least 3 points.
  2. It has a non-zero area.
  3. 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)
	}

}

Output

The polygon is well-formed.

func RelativeAngle

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)
}

Output

90

func RelativeCosineOfAngle

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:

  1. 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).
  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).

  1. 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.
  1. 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).
  1. 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)
}

Output

0

func SignedArea2X

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)
}

Output

12

type BooleanOperation

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 (*BooleanOperation) String

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())

}

Output

BooleanUnion
BooleanIntersection
BooleanSubtraction

type Circle

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

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)
}

Output

Circle[center=(3, 4), radius=5]

func (Circle[T]) Area

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)
}

Output

78.54

func (Circle[T]) AsFloat32

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 (Circle[T]) AsFloat64

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)
}

Output

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 (Circle[T]) AsInt

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)
}

Output

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 (Circle[T]) AsIntRounded

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)
}

Output

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 (Circle[T]) BoundingBox

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)
}

Output

Bounding box: Rectangle[(5, 5), (15, 5), (15, 15), (5, 15)]

func (Circle[int]) Bresenham

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
	})

}

Output

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 (Circle[T]) Center

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)
}

Output

Point[(3, 4)]

func (Circle[T]) Circumference

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)
}

Output

31.42

func (Circle[T]) Eq

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)
}

Output

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)
}

Output

true

func (Circle[T]) Radius

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)
}

Output

5

func (Circle[T]) RelationshipToCircle

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:

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))
}

Output

RelationshipEqual
RelationshipContains
RelationshipContainedBy
RelationshipDisjoint

func (Circle[T]) RelationshipToLineSegment

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:

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))
}

Output

RelationshipDisjoint
RelationshipIntersection
RelationshipContains

func (Circle[T]) RelationshipToPoint

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:

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))
}

Output

RelationshipDisjoint
RelationshipIntersection
RelationshipContains

func (Circle[T]) RelationshipToPolyTree

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])
}

Output

Circle's relationship to root polygon is: RelationshipContainedBy

func (Circle[T]) RelationshipToRectangle

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:

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)
}

Output

Relationship: RelationshipContainedBy

func (Circle[T]) Rotate

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)
}

Output

Circle[center=(-1, 3), radius=5]

func (Circle[T]) Scale

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)
}

Output

Circle[center=(0, 0), radius=10]

func (Circle[T]) String

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())
}

Output

Circle[center=(3, 4), radius=5]

func (Circle[T]) Translate

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)
}

Output

Circle[center=(5, 7), radius=5]

type LineSegment

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

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())
}

Output

LineSegment[(0, 0) -> (3, 4)]

func (LineSegment[T]) AddLineSegment

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)
}

Output

LineSegment[(3, 4) -> (5, 7)]

func (LineSegment[T]) AsFloat32

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 (LineSegment[T]) AsFloat64

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)
}

Output

Integer LineSegment: LineSegment[(1, 2) -> (3, 4)]
Float64 LineSegment: LineSegment[(1, 2) -> (3, 4)]

func (LineSegment[T]) AsInt

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)
}

Output

LineSegment[(1, 2) -> (3, 4)]

func (LineSegment[T]) AsIntRounded

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)
}

Output

LineSegment[(2, 3) -> (4, 4)]

func (LineSegment[T]) BoundingBox

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)
}

Output

Rectangle[(3, 1), (6, 1), (6, 4), (3, 4)]

func (LineSegment[int]) Bresenham

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
	})

}

Output

Point[(0, 0)]
Point[(1, 1)]
Point[(2, 1)]
Point[(3, 2)]
Point[(4, 2)]
Point[(5, 3)]

func (LineSegment[T]) Center

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)
}

Output

Center: Point[(5, 5)]

func (LineSegment[T]) ContainsPoint

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:

  • p (Point[T]): The Point to test against the LineSegment

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))
}

Output

Point on segment: true
Point off segment: false

func (LineSegment[T]) DistanceToLineSegment

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)
}

Output

1.414

func (LineSegment[T]) DistanceToPoint

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)
}

Output

Distance from point to line segment: 5.00

func (LineSegment[T]) End

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())
}

Output

Point[(3, 4)]

func (LineSegment[T]) Eq

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))
}

Output

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)))
}

Output

true

func (LineSegment[T]) IntersectionGeometry

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 (LineSegment[T]) IntersectsLineSegment

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

}

Output

Line1 intersects Line2: true
Line1 intersects Line3: false
Line1 intersects Line4: true
Line1 intersects Line5: true

func (LineSegment[T]) Length

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())
}

Output

5

func (LineSegment[T]) Normalize

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())

}

Output

Original Line Segment: LineSegment[(3, 5) -> (1, 2)]
Normalized Line Segment: LineSegment[(1, 2) -> (3, 5)]

func (LineSegment[T]) Points

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])
}

Output

Start Point: Point[(1, 2)]
End Point: Point[(3, 4)]

func (LineSegment[T]) Reflect

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:

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)
}

Output

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 (LineSegment[T]) RelationshipToCircle

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:

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))
}

Output

Disjoint: RelationshipDisjoint
Intersecting: RelationshipIntersection
Contained: RelationshipContainedBy

func (LineSegment[T]) RelationshipToLineSegment

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:

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))
}

Output

Line1 vs Line2: RelationshipIntersection
Line1 vs Line3: RelationshipDisjoint
Line1 vs Line4: RelationshipEqual

func (LineSegment[T]) RelationshipToPoint

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:

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))
}

Output

Point1 vs Line Segment: RelationshipIntersection
Point2 vs Line Segment: RelationshipDisjoint
Point3 vs Line Segment: RelationshipIntersection

func (LineSegment[T]) RelationshipToPolyTree

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.

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])
}

Output

Root polygon relationship: RelationshipContainedBy
Hole polygon relationship: RelationshipIntersection

func (LineSegment[T]) RelationshipToRectangle

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:

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))
}

Output

Line1 vs Rectangle: RelationshipIntersection
Line2 vs Rectangle: RelationshipContainedBy
Line3 vs Rectangle: RelationshipDisjoint

func (LineSegment[T]) Rotate

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])
}

Output

Rotated Line Start: Point[(-3, 2)]
Rotated Line End: Point[(-6, 4)]

func (LineSegment[T]) RoundToEpsilon

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(),
	)
}

Output

LineSegment[(1.2000, 4.6000) -> (7.9000, 3.2000)]

func (LineSegment[T]) Scale

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])
}

Output

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 (LineSegment[T]) Slope

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)
}

Output

Slope: 2.000000, Valid: true

func (LineSegment[T]) Start

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())
}

Output

Point[(1, 2)]

func (LineSegment[T]) String

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())
}

Output

LineSegment[(1, 1) -> (4, 5)]

func (LineSegment[T]) SubLineSegment

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])
}

Output

Resulting Line Start: Point[(5, 5)]
Resulting Line End: Point[(5, 5)]

func (LineSegment[T]) Translate

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())
}

Output

Translated Line Segment:
Start Point: Point[(3, 4)]
End Point: Point[(6, 7)]

func (LineSegment[T]) XAtY

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")
	}

}

Output

X at Y=3 is 4.00

func (LineSegment[T]) YAtX

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")
	}

}

Output

Y at X=4 is 3.00

type LineSegmentIntersectionResult

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

type LineSegmentIntersectionType uint8

const (
    IntersectionNone LineSegmentIntersectionType = iota
    IntersectionPoint
    IntersectionSegment
)

type NewPolyTreeOption

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())

}

Output

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

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:

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())
}

Output

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

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:

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())
	}

}

Output

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)]]

type Option

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

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.

type Point

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

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)
	}

}

Output

Convex Hull:
Point[(1, 1)]
Point[(3, 1)]
Point[(5, 3)]
Point[(5, 5)]
Point[(2, 5)]

func NewPoint

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())
}

Output

Integer Point: (10, 20)
Floating-Point Point: (10.50, 20.25)
Inferred Point: (10, 20)

func NewPointFromImagePoint

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:

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)
}

Output

Image Point: (10,20)
Geometry Point: Point[(10, 20)]

func RoundPointToEpsilon

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())

}

Output

Original point: Point[(1.234, 5.678)]
Rounded point: (1.2000, 5.7000)

func (Point[T]) AsFloat32

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 (Point[T]) AsFloat64

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)
}

Output

Point[(3, 4)] is type geom2d.Point[float64]

func (Point[T]) AsInt

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)
}

Output

Point[(3, 4)] is type geom2d.Point[int]

func (Point[T]) AsIntRounded

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)
}

Output

Point[(4, 4)] is type geom2d.Point[int]

func (Point[T]) CrossProduct

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.")
	}

}

Output

Cross product: -3
The points indicate a clockwise turn (right turn).

func (Point[T]) DistanceSquaredToPoint

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)
}

Output

The squared distance between Point[(3, 4)] and Point[(6, 8)] is 25.

func (Point[T]) DistanceToLineSegment

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)
}

Output

The shortest distance from Point[(3, 4)] to LineSegment[(0, 0) -> (6, 0)] is 4.00.

func (Point[T]) DistanceToPoint

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)
}

Output

The Euclidean distance between Point[(3, 4)] and Point[(0, 0)] is 5.00.

func (Point[T]) DotProduct

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)
}

Output

The dot product of vector Point[(3, 4)] and vector Point[(1, 2)] is 11.00.

func (Point[T]) Eq

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)
}

Output

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 (Point[T]) Negate

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)
}

Output

Original Point: Point[(3, -4)]
Negated Point: Point[(-3, 4)]

func (Point[T]) ProjectOntoLineSegment

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)
}

Output

Point[(3, 7)] projects onto the LineSegment[(0, 0) -> (10, 0)] at Point[(3, 0)].

func (Point[T]) Reflect

func (p Point[T]) Reflect(axis ReflectionAxis, line ...LineSegment[float64]) Point[float64]

Reflect reflects the point across the specified axis or custom line.

Parameters:

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)
}

Output

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 (Point[T]) RelationshipToCircle

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:

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)
}

Output

RelationshipContainedBy

func (Point[T]) RelationshipToLineSegment

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:

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:

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
}

Output

RelationshipIntersection
RelationshipDisjoint

func (Point[T]) RelationshipToPoint

func (p Point[T]) RelationshipToPoint(other Point[T], opts ...Option) Relationship

RelationshipToPoint determines the spatial relationship between the current Point and another Point.

Relationships:

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
}

Output

RelationshipEqual
RelationshipDisjoint

func (Point[T]) RelationshipToPolyTree

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:

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:

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
}

Output

RelationshipContainedBy
RelationshipContainedBy
RelationshipContainedBy
RelationshipDisjoint
RelationshipIntersection
RelationshipDisjoint
RelationshipDisjoint
RelationshipDisjoint

func (Point[T]) RelationshipToRectangle

func (p Point[T]) RelationshipToRectangle(r Rectangle[T], opts ...Option) Relationship

RelationshipToRectangle determines the spatial relationship between the current Point and a Rectangle.

Relationships:

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:

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
}

Output

RelationshipContainedBy
RelationshipIntersection
RelationshipDisjoint

func (Point[T]) Rotate

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)
}

Output

Circle[center=(-1, 3), radius=5]

func (Point[T]) Scale

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)
}

Output

Point[(5, 7)]

func (Point[T]) String

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())
}

Output

Point[(3, 4)]

func (Point[T]) Translate

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)
}

Output

Translated point: Point[(8, 3)]
Point after subtraction: Point[(4, 4)]

func (Point[T]) X

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())
}

Output

3

func (Point[T]) Y

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())
}

Output

4

type PointOrientation

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

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:

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)
}

Output

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 (PointOrientation) String

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:

Panics:

type PolyTree

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

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

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:

NewPolyTreeOption functions:

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)

}

Output

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 (*PolyTree[T]) AddChild

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 (*PolyTree[T]) AddSibling

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 (*PolyTree[T]) Area

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)
}

Output

Area of the square: 100.00

func (*PolyTree[T]) AsFloat32

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())

}

Output

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 (*PolyTree[T]) AsFloat64

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())

}

Output

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 (*PolyTree[T]) AsInt

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())

}

Output

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 (*PolyTree[T]) AsIntRounded

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())

}

Output

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 (*PolyTree[T]) BooleanOperation

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 (*PolyTree[T]) BoundingBox

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)
}

Output

Bounding box: Rectangle[(0, 0), (20, 0), (20, 20), (0, 20)]

func (*PolyTree[T]) Children

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())
	}
}

Output

Number of children: 1
Child 1 area: 2500.00

func (*PolyTree[T]) Contour

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)
	}
}

Output

Contour Points:
Point[(0, 0)]
Point[(100, 0)]
Point[(100, 100)]
Point[(0, 100)]

func (*PolyTree[T]) Edges

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:

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())
	}
}

Output

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 (*PolyTree[T]) Eq

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)

}

Output

Are the PolyTrees equal? true
Mismatch bitmask: 0

func (*PolyTree[T]) Hull

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)
	}
}

Output

Convex Hull:
Point: Point[(0, 0)]
Point: Point[(100, 0)]
Point: Point[(50, 100)]

func (*PolyTree[T]) IsRoot

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())
}

Output

Is the root a root? true
Is the child a root? false

func (*PolyTree[T]) Len

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())

}

Output

Total nodes in root: 3
Total nodes in hole: 2
Total nodes in island: 1

func (*PolyTree[T]) Nodes

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())
	}

}

Output

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 (*PolyTree[T]) Overlaps

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

}

Output

Root1 intersects Root2: true
Root1 intersects Root3: false
Root2 intersects Root3: false

func (*PolyTree[T]) Parent

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.")
	}
}

Output

Child's parent exists.
Root's parent is nil.

func (*PolyTree[T]) Perimeter

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)
}

Output

The perimeter of the triangle is: 34.14

func (*PolyTree[T]) PolygonType

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:

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)
}

Output

PolygonType: PTSolid

func (*PolyTree[T]) RelationshipToCircle

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])
}

Output

Root polygon relationship: RelationshipContains
Hole polygon relationship: RelationshipIntersection

func (*PolyTree[T]) RelationshipToLineSegment

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])
}

Output

Root polygon relationship: RelationshipContains
Hole polygon relationship: RelationshipIntersection

func (*PolyTree[T]) RelationshipToPoint

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])
}

Output

Root polygon relationship: RelationshipContains
Hole polygon relationship: RelationshipContains

func (*PolyTree[T]) RelationshipToPolyTree

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:

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])
}

Output

pt1's relationship to pt2: RelationshipIntersection

func (*PolyTree[T]) RelationshipToRectangle

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])
}

Output

Root polygon relationship: RelationshipContains
Hole polygon relationship: RelationshipContainedBy

func (*PolyTree[T]) Root

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

}

Output

true

func (*PolyTree[T]) Rotate

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())

}

Output

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 (*PolyTree[T]) Scale

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:

  • *PolyTree[T]: A new PolyTree where all the points have been scaled relative to ref.

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)

}

Output

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 (*PolyTree[T]) Siblings

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())
	}
}

Output

[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 (*PolyTree[T]) String

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())
}

Output

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 (*PolyTree[T]) Translate

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)

}

Output

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)]

type PolyTreeMismatch

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
)

type PolygonType

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

Valid values for PolygonType

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 (PolygonType) String

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:

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())
}

Output

PTSolid

type Rectangle

Rectangle represents an axis-aligned rectangle defined by its four corners.

type Rectangle[T SignedNumber] struct {
    // contains filtered or unexported fields
}

func NewRectangle

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)
	}

}

Output

Point[(1, 3)]
Point[(4, 3)]
Point[(4, 1)]
Point[(1, 1)]

func NewRectangleFromImageRect

func NewRectangleFromImageRect(r image.Rectangle) Rectangle[int]

NewRectangleFromImageRect creates a new Rectangle[T] from an image.Rectangle.

Parameters:

Returns:

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)
	}

}

Output

Point[(10, 80)]
Point[(50, 80)]
Point[(50, 20)]
Point[(10, 20)]

func (Rectangle[T]) Area

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)

}

Output

The area of the rectangle is: 50

func (Rectangle[T]) AsFloat32

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)
	}

}

Output

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 (Rectangle[T]) AsFloat64

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)
	}

}

Output

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 (Rectangle[T]) AsInt

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)
	}

}

Output

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 (Rectangle[T]) AsIntRounded

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)
	}

}

Output

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 (Rectangle[T]) ContainsPoint

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:

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))
	}

}

Output

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 (Rectangle[T]) Contour

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)
	}

}

Output

Contour of the rectangle:
Point[(0, 10)]
Point[(10, 10)]
Point[(10, 0)]
Point[(0, 0)]

func (Rectangle[T]) Edges

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())
	}

}

Output

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 (Rectangle[T]) Eq

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))

}

Output

rect1 equals rect2: true
rect1 equals rect3: false

func (Rectangle[T]) Height

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())

}

Output

Height of the rectangle: 10

func (Rectangle[T]) Perimeter

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())

}

Output

Perimeter of the rectangle: 40

func (Rectangle[T]) RelationshipToCircle

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:

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))
}

Output

Circle inside: RelationshipContains
Circle intersecting: RelationshipIntersection
Circle outside: RelationshipDisjoint

func (Rectangle[T]) RelationshipToLineSegment

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:

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))
}

Output

Segment inside: RelationshipContains
Segment intersecting: RelationshipIntersection
Segment outside: RelationshipDisjoint

func (Rectangle[T]) RelationshipToPoint

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:

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))

}

Output

Point inside: RelationshipContains
Point on edge: RelationshipIntersection
Point on vertex: RelationshipIntersection
Point outside: RelationshipDisjoint

func (Rectangle[T]) RelationshipToPolyTree

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 (Rectangle[T]) RelationshipToRectangle

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:

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)
}

Output

Relationship: RelationshipIntersection

func (Rectangle[T]) Scale

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())

}

Output

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 (Rectangle[T]) ScaleHeight

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())

}

Output

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 (Rectangle[T]) ScaleWidth

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())

}

Output

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 (Rectangle[T]) String

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())

}

Output

Rectangle[(0, 0), (10, 0), (10, 10), (0, 10)]

func (Rectangle[int]) ToImageRect

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:

func (Rectangle[T]) Translate

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())

}

Output

Rectangle[(5, -5), (15, -5), (15, 5), (5, 5)]

func (Rectangle[T]) Width

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)

}

Output

The width of the rectangle is: 20

type ReflectionAxis

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
)

type Relationship

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 (Relationship) String

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:

Returns:

Panics:

  • If the Relationship value is not supported, this method panics with an error message.

type SignedNumber

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
}

type SweepLineResult

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

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:

  1. Convert all input line segments to LineSegment[float64].
  2. Initialize a priority queue (event queue) with start and end events for all line segments.
  3. Process each event in the event queue in ascending order of x-coordinate (with ties resolved by y-coordinate).
  4. Maintain a status structure (`sweepLineStatus`) to store active line segments intersecting the current sweep line.
  5. 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())
	}

}

Output

Intersection Points:
(3.3333, 1.3333)
(6.6667, 2.6667)
(7.6316, 0.4737)

Generated by gomarkdoc

Clone this wiki locally