loc

package module
v0.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 17, 2025 License: MIT Imports: 4 Imported by: 0

README

loc - Locate and Align Points and Rectangles

loc is a Go library providing types and functions for 2D geometry, focusing on points (Point[S]) and rectangles (Rect[S]).

Overview

The library offers:

  • Point[S]: Represents an X, Y coordinate.
  • Rect[S]: Represents a rectangle defined by Min and Max points.
  • Utility functions for creating points and rectangles (e.g., Xy, Xyxy, Xywh).
  • Methods for geometric operations:
    • Point arithmetic (Add, Sub, Mul, Div).
    • Rectangle manipulation (Add, Sub, Inset, Intersect, Union).
    • Alignment (Point.Align, Point.AlignCenter).
    • Cutting and Splitting (Rect.CutX, Rect.CutY, Rect.CutXByRate, Rect.CutYByRate, Rect.SplitX, Rect.SplitY).
    • Repeating (Rect.RepeatX, Rect.RepeatY).
    • Anchoring (Rect.Anchor).

Examples

Basic Point and Rectangle Creation
package main

import (
	"fmt"
	"github.com/eihigh/loc"
)

func main() {
	// Create points
	p1 := loc.Xy(10, 20)
	p2 := loc.Xy(30, 40)
	fmt.Println("Point 1:", p1)
	fmt.Println("Point 2:", p2)

	// Create a rectangle from two points
	rect1 := loc.Xyxy(p1.X, p1.Y, p2.X, p2.Y) // (10,20)-(30,40)
	fmt.Println("Rectangle 1:", rect1)

	// Create a rectangle from position and size
	rect2 := loc.Xywh(0, 0, 100, 50) // (0,0)-(100,50)
	fmt.Println("Rectangle 2:", rect2)
}
Aligning a Rectangle within Another

This example demonstrates aligning a smaller modal rectangle to the center of a larger screen rectangle.

package main

import (
	"fmt"
	"github.com/eihigh/loc"
)

func main() {
	screen := loc.Xyxy(0, 0, 800, 600)
	modal := loc.Xyxy(0, 0, 400, 300)

	// Align 'modal' to the center of 'screen'
	// screen.Center() returns the center Point of the screen.
	// AlignCenter aligns the center of 'modal' to this point.
	alignedModal := screen.Center().AlignCenter(modal)
	fmt.Println("Screen:", screen)
	fmt.Println("Modal (original):", modal)
	fmt.Println("Modal (aligned):", alignedModal)
	// Output: Modal (aligned): (200,150)-(600,450)
}
Cutting a Rectangle

This example shows how to cut a rectangle horizontally by an absolute value and by a rate.

package main

import (
	"fmt"
	"github.com/eihigh/loc"
)

func main() {
	rect := loc.Xyxy(0, 0, 100, 50)

	// Cut by absolute value
	cutAbs, restAbs := rect.CutX(30)
	fmt.Printf("Cut by 30: %s, Rest: %s\n", cutAbs, restAbs)
	// Output: Cut by 30: (0,0)-(30,50), Rest: (30,0)-(100,50)

	// Cut by rate (30% of width)
	cutRate, restRate := rect.CutXByRate(0.3)
	fmt.Printf("Cut by 0.3 rate: %s, Rest: %s\n", cutRate, restRate)
	// Output: Cut by 0.3 rate: (0,0)-(30,50), Rest: (30,0)-(100,50)
}
Repeating a Rectangle

This example demonstrates repeating a rectangle horizontally.

package main

import (
	"fmt"
	"github.com/eihigh/loc"
)

func main() {
	baseRect := loc.Xyxy(0, 0, 20, 30)

	// Repeat 'baseRect' 3 times in X direction with a gap of 5
	repeatedRects, overallRect := baseRect.RepeatX(3, 5)

	fmt.Println("Overall Bounding Box:", overallRect)
	for i, r := range repeatedRects {
		fmt.Printf("Repeated Rect %d: %s\n", i, r)
	}
	// Output:
	// Overall Bounding Box: (0,0)-(70,30)
	// Repeated Rect 0: (0,0)-(20,30)
	// Repeated Rect 1: (25,0)-(45,30)
	// Repeated Rect 2: (50,0)-(70,30)
}

For more detailed examples, please refer to the example_test.go file.

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Point

type Point[S ng.Scalar] struct {
	X, Y S
}

A Point is an X, Y coordinate pair. The axes increase right and down.

func Xy

func Xy[S ng.Scalar](x, y S) Point[S]

Xy is shorthand for Point{X, Y}.

func (Point[S]) Add

func (p Point[S]) Add(q Point[S]) Point[S]

Add returns the vector p+q.

func (Point[S]) Align

func (p Point[S]) Align(r Rect[S], rx, ry float64) Rect[S]

Align returns a new rectangle with the same size as r, where the point p is at the relative position (rx, ry) within the new rectangle.

Example
package main

import (
	"fmt"

	"github.com/eihigh/loc"
)

func main() {
	screen := loc.Xyxy(0, 0, 800, 600)
	box := loc.Xyxy(0, 0, 100, 100)

	fmt.Println("top left:", screen.Anchor(0, 0).Align(box, 0, 0))
	fmt.Println("top right:", screen.Anchor(1, 0).Align(box, 1, 0))
	fmt.Println("bottom left:", screen.Anchor(0, 1).Align(box, 0, 1))
	fmt.Println("bottom right:", screen.Anchor(1, 1).Align(box, 1, 1))

}
Output:

top left: (0,0)-(100,100)
top right: (700,0)-(800,100)
bottom left: (0,500)-(100,600)
bottom right: (700,500)-(800,600)

func (Point[S]) AlignCenter

func (p Point[S]) AlignCenter(r Rect[S]) Rect[S]

AlignCenter returns a new rectangle with the same size as r, where the point p is at the center of the new rectangle.

Example
package main

import (
	"fmt"

	"github.com/eihigh/loc"
)

func main() {
	screen := loc.Xyxy(0, 0, 800, 600)
	modal := loc.Xyxy(0, 0, 400, 300)
	modal = screen.Center().AlignCenter(modal)
	fmt.Println(modal.String())

}
Output:

(200,150)-(600,450)

func (Point[S]) AsSize

func (p Point[S]) AsSize() Rect[S]

AsSize converts the x, y coordinates to a rectangle with the corresponding width and height.

func (Point[S]) Div

func (p Point[S]) Div(k S) Point[S]

Div returns the vector p/k.

func (Point[S]) DivPoint added in v0.0.2

func (p Point[S]) DivPoint(q Point[S]) Point[S]

func (Point[S]) Eq

func (p Point[S]) Eq(q Point[S]) bool

Eq reports whether p and q are equal.

func (Point[S]) Float32

func (p Point[S]) Float32() Point[float32]

Float32 returns the point as a float32 point.

func (Point[S]) Float64

func (p Point[S]) Float64() Point[float64]

Float64 returns the point as a float64 point.

func (Point[S]) Image

func (p Point[S]) Image() image.Point

Image returns the point as an image.Point.

func (Point[S]) In

func (p Point[S]) In(r Rect[S]) bool

In reports whether p is in r.

func (Point[S]) Int

func (p Point[S]) Int() Point[int]

Int returns the point as an int point.

func (Point[S]) Mul

func (p Point[S]) Mul(k S) Point[S]

Mul returns the vector p*k.

func (Point[S]) MulPoint added in v0.0.2

func (p Point[S]) MulPoint(q Point[S]) Point[S]

func (Point[S]) String

func (p Point[S]) String() string

String returns a string representation of p like "(3,4)".

func (Point[S]) Sub

func (p Point[S]) Sub(q Point[S]) Point[S]

Sub returns the vector p-q.

func (Point[S]) Xy

func (p Point[S]) Xy() (S, S)

type Rect

type Rect[S ng.Scalar] struct {
	Min, Max Point[S]
}

A Rectangle contains the points with Min.X <= X < Max.X, Min.Y <= Y < Max.Y. It is well-formed if Min.X <= Max.X and likewise for Y. Points are always well-formed. A rectangle's methods always return well-formed outputs for well-formed inputs.

func MinMax

func MinMax[S ng.Scalar, P ng.Vec2like[S]](min, max P) Rect[S]

MinMax creates a rectangle from two points, min and max.

func PosSize

func PosSize[S ng.Scalar, P ng.Vec2like[S]](pos, size P) Rect[S]

PosSize creates a rectangle from a position and a size.

func Xywh

func Xywh[S ng.Scalar](x, y, w, h S) Rect[S]

Xywh creates a rectangle from a point (x, y) and a size (w, h).

func Xyxy

func Xyxy[S ng.Scalar](x0, y0, x1, y1 S) Rect[S]

Xyxy creates a rectangle from two points, (x0, y0) and (x1, y1).

func (Rect[S]) Add

func (r Rect[S]) Add(p Point[S]) Rect[S]

Add returns the rectangle r translated by p.

func (Rect[S]) Anchor

func (r Rect[S]) Anchor(rx, ry float64) Point[S]

Anchor returns a point within r, scaled by rx and ry. rx=0, ry=0 is r.Min; rx=1, ry=1 is r.Max.

Example
package main

import (
	"fmt"

	"github.com/eihigh/loc"
)

func main() {
	rect := loc.Xyxy(10, 20, 110, 120) // Dx=100, Dy=100
	p1 := rect.Anchor(0, 0)
	p2 := rect.Anchor(0.5, 0.5)
	p3 := rect.Anchor(1, 1)
	p4 := rect.Anchor(0.25, 0.75)
	fmt.Println(p1, p2, p3, p4)

}
Output:

(10,20) (60,70) (110,120) (35,95)

func (Rect[S]) Canon

func (r Rect[S]) Canon() Rect[S]

Canon returns the canonical version of r. The returned rectangle has minimum and maximum coordinates swapped if necessary so that it is well-formed.

func (Rect[S]) Center

func (r Rect[S]) Center() Point[S]

Center returns the center point of r.

Example
package main

import (
	"fmt"

	"github.com/eihigh/loc"
)

func main() {
	rect := loc.Xyxy(10, 20, 110, 220) // Dx=100, Dy=200
	center := rect.Center()
	fmt.Println(center)

}
Output:

(60,120)

func (Rect[S]) CutX

func (r Rect[S]) CutX(w S) (got, rest Rect[S])

CutX cuts r into two rectangles at x = r.Min.X + w. It returns the left part (got) and the right part (rest). If w is negative, got is empty and rest is r. If w is larger than r.Dx(), got is r and rest is empty.

Example
package main

import (
	"fmt"

	"github.com/eihigh/loc"
)

func main() {
	rect := loc.Xyxy(0, 0, 100, 50)
	cut1, rest1 := rect.CutX(30)
	fmt.Printf("Cut1: %s, Rest1: %s\n", cut1, rest1)

}
Output:

Cut1: (0,0)-(30,50), Rest1: (30,0)-(100,50)

func (Rect[S]) CutXRate

func (r Rect[S]) CutXRate(rate float64) (Rect[S], Rect[S])

CutXRate cuts the rectangle by a rate of its width. It returns two rectangles: the cut part and the rest. If rate < 0, the cut part has zero width. If rate > 1, the cut part has the original width.

Example
package main

import (
	"fmt"

	"github.com/eihigh/loc"
)

func main() {
	rect := loc.Xyxy(0, 0, 100, 50)
	cut, rest := rect.CutXRate(0.3) // Cut 30% of width
	fmt.Printf("Cut: %s, Rest: %s\n", cut, rest)

	cutNegative, restNegative := rect.CutXRate(-0.1) // Cut -10% (should be 0)
	fmt.Printf("CutNegative: %s, RestNegative: %s\n", cutNegative, restNegative)

	cutOver, restOver := rect.CutXRate(1.2) // Cut 120% (should be 100%)
	fmt.Printf("CutOver: %s, RestOver: %s\n", cutOver, restOver)
}
Output:

Cut: (0,0)-(30,50), Rest: (30,0)-(100,50)
CutNegative: (0,0)-(0,50), RestNegative: (0,0)-(100,50)
CutOver: (0,0)-(100,50), RestOver: (100,0)-(100,50)

func (Rect[S]) CutY

func (r Rect[S]) CutY(h S) (got, rest Rect[S])

CutY cuts r into two rectangles at y = r.Min.Y + h. It returns the top part (got) and the bottom part (rest). If h is negative, got is empty and rest is r. If h is larger than r.Dy(), got is r and rest is empty.

Example
package main

import (
	"fmt"

	"github.com/eihigh/loc"
)

func main() {
	rect := loc.Xyxy(0, 0, 100, 50)
	cut1, rest1 := rect.CutY(20)
	fmt.Printf("Cut1: %s, Rest1: %s\n", cut1, rest1)

}
Output:

Cut1: (0,0)-(100,20), Rest1: (0,20)-(100,50)

func (Rect[S]) CutYRate

func (r Rect[S]) CutYRate(rate float64) (Rect[S], Rect[S])

CutYRate cuts the rectangle by a rate of its height. It returns two rectangles: the cut part and the rest. If rate < 0, the cut part has zero height. If rate > 1, the cut part has the original height.

Example
package main

import (
	"fmt"

	"github.com/eihigh/loc"
)

func main() {
	rect := loc.Xyxy(0, 0, 100, 50)
	cut, rest := rect.CutYRate(0.4) // Cut 40% of height (20)
	fmt.Printf("Cut: %s, Rest: %s\n", cut, rest)

	cutNegative, restNegative := rect.CutYRate(-0.2) // Cut -20% (should be 0)
	fmt.Printf("CutNegative: %s, RestNegative: %s\n", cutNegative, restNegative)

	cutOver, restOver := rect.CutYRate(1.5) // Cut 150% (should be 100%)
	fmt.Printf("CutOver: %s, RestOver: %s\n", cutOver, restOver)
}
Output:

Cut: (0,0)-(100,20), Rest: (0,20)-(100,50)
CutNegative: (0,0)-(100,0), RestNegative: (0,0)-(100,50)
CutOver: (0,0)-(100,50), RestOver: (0,50)-(100,50)

func (Rect[S]) Dx

func (r Rect[S]) Dx() S

Dx returns r's width.

func (Rect[S]) Dy

func (r Rect[S]) Dy() S

Dy returns r's height.

func (Rect[S]) Empty

func (r Rect[S]) Empty() bool

Empty reports whether the rectangle contains no points.

func (Rect[S]) Eq

func (r Rect[S]) Eq(s Rect[S]) bool

Eq reports whether r and s contain the same set of points. All empty rectangles are considered equal.

func (Rect[S]) Image

func (r Rect[S]) Image() image.Rectangle

Image returns the rectangle as an image.Rectangle.

func (Rect[S]) In

func (r Rect[S]) In(s Rect[S]) bool

In reports whether every point in r is in s.

func (Rect[S]) Inset

func (r Rect[S]) Inset(n S) Rect[S]

Inset returns the rectangle r inset by n, which may be negative. If either of r's dimensions is less than 2*n then an empty rectangle near the center of r will be returned.

func (Rect[S]) Inset2

func (r Rect[S]) Inset2(x, y S) Rect[S]

Inset2 returns the rectangle r inset by n in both dimensions. If either of r's dimensions is less than 2*n then an empty rectangle near the center of r will be returned.

func (Rect[S]) Inset4

func (r Rect[S]) Inset4(left, top, right, bottom S) Rect[S]

Inset4 returns the rectangle r inset by left, top, right, and bottom. If either of r's dimensions is less than left+right or top+bottom then an empty rectangle near the center of r will be returned.

func (Rect[S]) Intersect

func (r Rect[S]) Intersect(s Rect[S]) Rect[S]

Intersect returns the largest rectangle contained by both r and s. If the two rectangles do not overlap then the zero rectangle will be returned.

func (Rect[S]) Overlaps

func (r Rect[S]) Overlaps(s Rect[S]) bool

Overlaps reports whether r and s have a non-empty intersection.

func (Rect[S]) Points

func (r Rect[S]) Points() iter.Seq[Point[S]]

Points returns a sequence of points in the rectangle.

func (Rect[S]) RepeatX

func (r Rect[S]) RepeatX(n int, gap S) ([]Rect[S], Rect[S])

RepeatX repeats the rectangle n times in the X direction with a given gap. It returns a slice of the repeated rectangles and the bounding box of all repeated rectangles. If n <= 0, it returns nil and an empty rectangle.

Example
package main

import (
	"fmt"

	"github.com/eihigh/loc"
)

func main() {
	rect := loc.Xyxy(0, 0, 20, 30)
	repeatedRects, overallRect := rect.RepeatX(3, 5) // Repeat 3 times with gap 5

	fmt.Println("Overall:", overallRect)
	for i, r := range repeatedRects {
		fmt.Printf("Part %d: %s\n", i, r)
	}

}
Output:

Overall: (0,0)-(70,30)
Part 0: (0,0)-(20,30)
Part 1: (25,0)-(45,30)
Part 2: (50,0)-(70,30)

func (Rect[S]) RepeatY

func (r Rect[S]) RepeatY(n int, gap S) ([]Rect[S], Rect[S])

RepeatY repeats the rectangle n times in the Y direction with a given gap. It returns a slice of the repeated rectangles and the bounding box of all repeated rectangles. If n <= 0, it returns nil and an empty rectangle.

Example
package main

import (
	"fmt"

	"github.com/eihigh/loc"
)

func main() {
	rect := loc.Xyxy(0, 0, 20, 30)
	repeatedRects, overallRect := rect.RepeatY(2, 10) // Repeat 2 times with gap 10

	fmt.Println("Overall:", overallRect)
	for i, r := range repeatedRects {
		fmt.Printf("Part %d: %s\n", i, r)
	}

}
Output:

Overall: (0,0)-(20,70)
Part 0: (0,0)-(20,30)
Part 1: (0,40)-(20,70)

func (Rect[S]) Size

func (r Rect[S]) Size() Point[S]

Size returns r's width and height.

func (Rect[S]) SplitX

func (r Rect[S]) SplitX(n int, gap S) []Rect[S]

SplitX splits r into n rectangles of (mostly) equal width, with a specified gap between them. If n <= 0, returns nil. If n == 1, returns r. Gap is space between items. Assumed to be non-negative; negative gap leads to overlap.

Example
package main

import (
	"fmt"

	"github.com/eihigh/loc"
)

func main() {
	rect := loc.Xyxy(0, 0, 100, 50)

	// Gap = 0
	parts3NoGap := rect.SplitX(3, 0)
	for i, p := range parts3NoGap {
		fmt.Printf("3 parts, no gap, part %d: %s\n", i, p)
	}

	parts4NoGap := rect.SplitX(4, 0) // 100 / 4 = 25
	for i, p := range parts4NoGap {
		fmt.Printf("4 parts, no gap, part %d: %s\n", i, p)
	}

	parts1NoGap := rect.SplitX(1, 0)
	fmt.Printf("1 part, no gap, part 0: %s\n", parts1NoGap[0])

	// With positive gap
	parts3WithGap5 := rect.SplitX(3, 5) // 100, n=3, gap=5. totalItemWidth = 100 - 2*5 = 90. singleItemWidth = 90/3 = 30.
	for i, p := range parts3WithGap5 {
		fmt.Printf("3 parts, gap 5, part %d: %s\n", i, p)
	}

}
Output:

3 parts, no gap, part 0: (0,0)-(33,50)
3 parts, no gap, part 1: (33,0)-(66,50)
3 parts, no gap, part 2: (66,0)-(100,50)
4 parts, no gap, part 0: (0,0)-(25,50)
4 parts, no gap, part 1: (25,0)-(50,50)
4 parts, no gap, part 2: (50,0)-(75,50)
4 parts, no gap, part 3: (75,0)-(100,50)
1 part, no gap, part 0: (0,0)-(100,50)
3 parts, gap 5, part 0: (0,0)-(30,50)
3 parts, gap 5, part 1: (35,0)-(65,50)
3 parts, gap 5, part 2: (70,0)-(100,50)

func (Rect[S]) SplitY

func (r Rect[S]) SplitY(n int, gap S) []Rect[S]

SplitY splits r into n rectangles of (mostly) equal height, with a specified gap between them. If n <= 0, returns nil. If n == 1, returns r. Gap is space between items. Assumed to be non-negative; negative gap leads to overlap.

Example
package main

import (
	"fmt"

	"github.com/eihigh/loc"
)

func main() {
	rect := loc.Xyxy(0, 0, 50, 100)

	// Gap = 0
	parts3NoGap := rect.SplitY(3, 0)
	for i, p := range parts3NoGap {
		fmt.Printf("3 parts, no gap, part %d: %s\n", i, p)
	}

	parts4NoGap := rect.SplitY(4, 0) // 100 / 4 = 25
	for i, p := range parts4NoGap {
		fmt.Printf("4 parts, no gap, part %d: %s\n", i, p)
	}

	parts1NoGap := rect.SplitY(1, 0)
	fmt.Printf("1 part, no gap, part 0: %s\n", parts1NoGap[0])

	// With positive gap
	parts3WithGap5 := rect.SplitY(3, 5) // 100, n=3, gap=5. totalItemHeight = 100 - 2*5 = 90. singleItemHeight = 90/3 = 30.
	for i, p := range parts3WithGap5 {
		fmt.Printf("3 parts, gap 5, part %d: %s\n", i, p)
	}

}
Output:

3 parts, no gap, part 0: (0,0)-(50,33)
3 parts, no gap, part 1: (0,33)-(50,66)
3 parts, no gap, part 2: (0,66)-(50,100)
4 parts, no gap, part 0: (0,0)-(50,25)
4 parts, no gap, part 1: (0,25)-(50,50)
4 parts, no gap, part 2: (0,50)-(50,75)
4 parts, no gap, part 3: (0,75)-(50,100)
1 part, no gap, part 0: (0,0)-(50,100)
3 parts, gap 5, part 0: (0,0)-(50,30)
3 parts, gap 5, part 1: (0,35)-(50,65)
3 parts, gap 5, part 2: (0,70)-(50,100)

func (Rect[S]) String

func (r Rect[S]) String() string

String returns a string representation of r like "(3,4)-(6,5)".

func (Rect[S]) Sub

func (r Rect[S]) Sub(p Point[S]) Rect[S]

Sub returns the rectangle r translated by -p.

func (Rect[S]) Union

func (r Rect[S]) Union(s Rect[S]) Rect[S]

Union returns the smallest rectangle that contains both r and s.

func (Rect[S]) Within added in v0.0.2

func (r Rect[S]) Within(s Rect[S], rx, ry float64) Rect[S]

Within in an alias for s.Anchor(rx, ry).Align(r, rx, ry).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL