Skip to content

create.geometry

Shapes and curves, the traits that make a type drawable, and the collision tests: overlaps, and each shape's contains and intersects.

Rectangle

struct Rectangle

A rectangle, upright unless turned by rotation: center, area, bounds, left/right/bottom/top, closest_point, contains, move_to, translate.

Locations are Point2D and displacements Vector2D, throughout this module; extents are plain scalars, like Circle's radius. Width and height are two independent lengths rather than a coordinate pair, so they are not grouped: read them as w/h and pair them however the caller needs.

position is the centre, not a corner -- consistent with every shape in this module and with canvas.rectangle. w/h are full width and height, so left()/right()/bottom()/top() are +-w/2/+-h/2 from the centre. Coordinates follow world space: y grows upward, so top() is position.y + h/2 and bottom() is position.y - h/2.

contains and closest_point treat the boundary as inside -- a point exactly on an edge is contained, and closest_point returns it unchanged. A zero w/h collapses the rectangle to a segment or a point; contains/overlaps remain exact for it rather than reporting a false containment or overlap. w/h are assumed non-negative.

corner_radius rounds all four corners, as canvas.rectangle draws them, and every test here sees the rounded shape: a point in a sharp corner's cut-away is outside. It is clamped to half the shorter side. left()/right()/bottom()/top() stay the bounding box's.

rotation turns the rectangle about position, in radians counter-clockwise (y is up), as canvas.transform(rotate(...)) turns what it draws; w and h stay its own width and height. Every test sees the turned shape, exactly, and left()/right()/bottom()/ top() and bounds() are its upright bounding box's. Drawn, it is the upright rectangle turned, so a gradient fill, the rounding and the stroke turn with it, while a shadow's offset stays screen-fixed unless shadow_follows_transform is set, as for any turned shape.

Implements Copyable, Deinitable, Drawable, Equatable, ImplicitlyCopyable, Movable, Region, Shape, Writable, _Collider.

Fields

  • position: Point2D
  • w: Float64
  • h: Float64
  • corner_radius: Float64: How far each corner is rounded, in the units of w/h; 0 is sharp.
  • rotation: Float64: How far the rectangle is turned about position, in radians counter-clockwise; 0 is upright.

Constructors

@staticmethod
def __init__(
    out self,
    position: Point2D,
    w: Float64,
    h: Float64,
    *,
    corner_radius: _OptionalFloat64 = SIMD(0),
    rotation: _OptionalFloat64 = SIMD(0),
)
@staticmethod
def __init__(
    out self,
    position: Point2D,
    w: Int,
    h: Int,
    *,
    corner_radius: _OptionalFloat64 = SIMD(0),
    rotation: _OptionalFloat64 = SIMD(0),
)

__eq__

def __eq__(self, other: Self) -> Bool

__ne__

def __ne__(self, other: Self) -> Bool

center

def center(self) -> Point2D

area

def area(self) -> Float64

bounds

def bounds(self) -> Self

The tightest axis-aligned rectangle around this one: upright, itself, sharp, since rounding never pulls in an edge's middle; turned, as far as its rounded corners reach along each axis.

closest_point

def closest_point(self, p: Point2D) -> Point2D

left

def left(self) -> Float64

right

def right(self) -> Float64

bottom

def bottom(self) -> Float64

top

def top(self) -> Float64

contains

def contains(self, p: Point2D) -> Bool
def contains(self, other: Self) -> Bool
def contains(self, c: Circle) -> Bool
def contains(self, t: Triangle) -> Bool
def contains(self, l: Line) -> Bool
def contains(self, c: Capsule) -> Bool
def contains[C: Curve](self, curve: C) -> Bool

Whether the whole of curve (an Arc, Bezier or Spline) is inside: every segment of it flattened to within 1e-4 of its size, as intersects follows it, so a curve grazing the edge from inside is decided only that closely.

def contains(self, s: Sector) -> Bool
def contains(self, p: Polygon) -> Bool
def contains(self, e: Ellipse) -> Bool

draw_to

def draw_to[T: DrawTarget](self, mut target: T)

move_to

def move_to(self, position: Point2D) -> Self

translate

def translate(self, delta: Vector2D) -> Self

Circle

struct Circle

A circle: center, area, diameter, closest_point, contains, move_to, translate.

position is the centre and r the radius -- there is no orientation, so unlike Rectangle/Triangle there is nothing y-up affects beyond the centre's own coordinates.

contains treats the boundary as inside (distance <= r), and closest_point returns the query point itself when it is already inside or exactly at the centre, rather than an arbitrary point on the circumference. A zero r collapses the circle to a point; contains then holds only for that exact point, and overlaps/intersects remain exact rather than always-false. r is assumed non-negative.

Implements Copyable, Deinitable, Drawable, Equatable, ImplicitlyCopyable, Movable, Region, Shape, Writable, _Collider.

Fields

  • position: Point2D
  • r: Float64

Constructors

@staticmethod
def __init__(out self, position: Point2D, r: Float64)
@staticmethod
def __init__(out self, position: Point2D, r: Int)

__eq__

def __eq__(self, other: Self) -> Bool

__ne__

def __ne__(self, other: Self) -> Bool

center

def center(self) -> Point2D

area

def area(self) -> Float64

diameter

def diameter(self) -> Float64

bounds

def bounds(self) -> Rectangle

The tightest axis-aligned rectangle around the circle.

closest_point

def closest_point(self, p: Point2D) -> Point2D

contains

def contains(self, p: Point2D) -> Bool
def contains(self, other: Self) -> Bool
def contains(self, r: Rectangle) -> Bool
def contains(self, t: Triangle) -> Bool
def contains(self, l: Line) -> Bool
def contains(self, c: Capsule) -> Bool
def contains[C: Curve](self, curve: C) -> Bool

Whether the whole of curve (an Arc, Bezier or Spline) is inside: every segment of it flattened to within 1e-4 of its size, as intersects follows it, so a curve grazing the edge from inside is decided only that closely.

def contains(self, s: Sector) -> Bool
def contains(self, p: Polygon) -> Bool
def contains(self, e: Ellipse) -> Bool

draw_to

def draw_to[T: DrawTarget](self, mut target: T)

move_to

def move_to(self, position: Point2D) -> Self

translate

def translate(self, delta: Vector2D) -> Self

Ellipse

struct Ellipse

An axis-aligned ellipse: center, area, bounds, closest_point, contains, move_to, translate.

position is the centre and w/h the full width and height, as for Rectangle, whose bounds() it fills; a turned ellipse is drawn through canvas.transform. With w == h it is the circle of diameter w, and every test agrees with that Circle's.

contains treats the boundary as inside, and closest_point returns the query point itself when it is inside. A zero w or h collapses the ellipse to the segment (or point) its bounds() is, and every test treats it as that zero-size Rectangle, exactly. w/h are assumed non-negative.

An ellipse is a circle stretched along its axes, so a test against a shape that stretching keeps a shape of its kind -- a point, a Line, a sharp Rectangle, Triangle or Polygon -- runs exactly on the unit circle with the other shape mapped there. Against a Circle or another Ellipse it is exact too, through the closest point on the ellipse, accurate to rounding. Against a Sector and its arc, the ellipse stands in as its outline polygon, within 1e-4 of its larger side; a curve is followed as closely, and a rounded shape is tested as its own outline, as everywhere.

Implements Copyable, Deinitable, Drawable, Equatable, ImplicitlyCopyable, Movable, Region, Shape, Writable.

Fields

  • position: Point2D
  • w: Float64
  • h: Float64

Constructors

@staticmethod
def __init__(out self, position: Point2D, w: Float64, h: Float64)
@staticmethod
def __init__(out self, position: Point2D, w: Int, h: Int)

__eq__

def __eq__(self, other: Self) -> Bool

__ne__

def __ne__(self, other: Self) -> Bool

center

def center(self) -> Point2D

area

def area(self) -> Float64

bounds

def bounds(self) -> Rectangle

The tightest axis-aligned rectangle around the ellipse.

closest_point

def closest_point(self, p: Point2D) -> Point2D

contains

def contains(self, p: Point2D) -> Bool
def contains(self, r: Rectangle) -> Bool
def contains(self, c: Circle) -> Bool

Whether c is inside: its centre is, and no nearer the curve than its radius.

def contains(self, t: Triangle) -> Bool
def contains(self, l: Line) -> Bool
def contains[C: Curve](self, curve: C) -> Bool

Whether the whole of curve (an Arc, Bezier or Spline) is inside: every segment of it flattened to within 1e-4 of its size, as intersects follows it, so a curve grazing the edge from inside is decided only that closely.

def contains(self, s: Sector) -> Bool

Whether s is inside: its tip and its arc are, since every point of a sector lies between the two and the ellipse is convex.

def contains(self, p: Polygon) -> Bool
def contains(self, other: Self) -> Bool

Whether other is inside: with other mapped onto the unit circle, this one, mapped alike, still an ellipse, holds that circle.

def contains(self, c: Capsule) -> Bool

draw_to

def draw_to[T: DrawTarget](self, mut target: T)

move_to

def move_to(self, position: Point2D) -> Self

translate

def translate(self, delta: Vector2D) -> Self

Capsule

struct Capsule

A capsule: every point within r of the segment from start to end -- a rectangle along the segment, capped by a half disc at each end. center, area, bounds, closest_point, contains, move_to, translate.

Like a Line, it lies along any direction and is placed by its ends, so center() is the segment's midpoint and move_to moves that. A stadium is also Rectangle(position, w, h, corner_radius=h / 2, rotation=angle); a capsule is the one placed by its ends, the usual round collider for a moving body. With start == end it is the Circle of radius r there, and a zero r collapses it to the segment.

contains treats the boundary as inside, and closest_point returns the query point itself when it is inside. Every test is a distance from the segment, so they are exact: against a point, a line, a circle, another capsule, an arc, an ellipse and every straight-edged shape, rounded rectangles and triangles included. A shape inside it only needs its corners (or its ends' discs) inside, since a capsule is convex. Holding a Sector's arc or an Ellipse, it follows them as a curve is followed, within 1e-4 of their size, and a rounded polygon or sector is tested as its outline, as everywhere. r is assumed non-negative.

Implements Copyable, Deinitable, Drawable, Equatable, ImplicitlyCopyable, Movable, Region, Shape, Writable, _Collider.

Fields

  • start: Point2D
  • end: Point2D
  • r: Float64

Constructors

@staticmethod
def __init__(out self, start: Point2D, end: Point2D, r: Float64)
@staticmethod
def __init__(out self, start: Point2D, end: Point2D, r: Int)

__eq__

def __eq__(self, other: Self) -> Bool

__ne__

def __ne__(self, other: Self) -> Bool

center

def center(self) -> Point2D

area

def area(self) -> Float64

bounds

def bounds(self) -> Rectangle

The tightest axis-aligned rectangle around the capsule: the segment's, grown by r all round.

closest_point

def closest_point(self, p: Point2D) -> Point2D

contains

def contains(self, p: Point2D) -> Bool
def contains(self, l: Line) -> Bool
def contains(self, r: Rectangle) -> Bool
def contains(self, c: Circle) -> Bool
def contains(self, t: Triangle) -> Bool
def contains(self, other: Self) -> Bool
def contains[C: Curve](self, curve: C) -> Bool

Whether the whole of curve (an Arc, Bezier or Spline) is inside: every segment of it flattened to within 1e-4 of its size, as intersects follows it, so a curve grazing the edge from inside is decided only that closely.

def contains(self, s: Sector) -> Bool

Whether s is inside: its tip and its arc are, since every point of a sector lies between the two and the capsule is convex.

def contains(self, p: Polygon) -> Bool
def contains(self, e: Ellipse) -> Bool

Whether e is inside, its curve followed by its outline polygon.

draw_to

def draw_to[T: DrawTarget](self, mut target: T)

move_to

def move_to(self, position: Point2D) -> Self

translate

def translate(self, delta: Vector2D) -> Self

Line

struct Line

A line segment from start to end: length, at, tangent, at_distance, split, midpoint, bounds, flatten, beziers, closest_point, intersects, move_to, translate.

A Curve, like Arc, Bezier and Spline: a straight one, as in mathematics, with t proportional to distance. It has no interior, so unlike the Regions it has no center(), area() or overlaps, and is the subject of l.intersects(x) -- see the comment above intersects for why that method exists only on curves.

intersects and _point_on_segment-based checks treat the endpoints and any touching/collinear-overlapping point as inclusive. A zero-length line (start == end) is a degenerate point segment: intersects and closest_point remain exact for it (a point can still "intersect" the collapsed line if it coincides with it).

Implements Copyable, Curve, Deinitable, Drawable, Equatable, ImplicitlyCopyable, Movable, Shape, Writable, _Collider.

Fields

  • start: Point2D
  • end: Point2D

Constructors

@staticmethod
def __init__(out self, start: Point2D, end: Point2D)

__eq__

def __eq__(self, other: Self) -> Bool

__ne__

def __ne__(self, other: Self) -> Bool

length

def length(self) -> Float64

at

def at(self, t: Float64) -> Point2D

The point at parameter t: start at 0, end at 1, and proportional to distance in between. Not clamped: outside 0..1 it carries on along the line, as lerp extrapolates.

tangent

def tangent(self, t: Float64) -> Vector2D

The derivative at t: end - start, the same everywhere — the direction of travel, with the line's length as its magnitude. Zero for a zero-length line.

at_distance

def at_distance(self, distance: Float64) -> Point2D

The point distance along the line from start, clamped to the line's ends. Exact, and as cheap as at.

split

def split(self, t: Float64) -> Tuple[Self, Self]

The line cut at t into two, together tracing exactly the original: the first runs start to at(t), the second at(t) to end.

bounds

def bounds(self) -> Rectangle

The tightest axis-aligned rectangle around the line: the box its two ends span, zero-width or zero-height when it is axis-aligned.

flatten

def flatten(self, tolerance: Float64) -> List[Point2D]

start and end: a line is already straight, whatever the tolerance.

beziers

def beziers(self) -> List[Bezier]

The line as one cubic Bézier, its controls at the thirds, so it traces the line exactly and at the same speed.

intersects

def intersects(self, other: Self) -> Bool
def intersects(self, p: Point2D) -> Bool
def intersects(self, a: Arc) -> Bool
def intersects(self, b: Bezier) -> Bool
def intersects(self, s: Spline) -> Bool
def intersects(self, s: Sector) -> Bool
def intersects(self, p: Polygon) -> Bool
def intersects(self, c: Circle) -> Bool
def intersects(self, c: Capsule) -> Bool
def intersects(self, e: Ellipse) -> Bool
def intersects(self, r: Rectangle) -> Bool
def intersects(self, t: Triangle) -> Bool

closest_point

def closest_point(self, p: Point2D) -> Point2D

midpoint

def midpoint(self) -> Point2D

draw_to

def draw_to[T: DrawTarget](self, mut target: T)

move_to

def move_to(self, position: Point2D) -> Self

translate

def translate(self, delta: Vector2D) -> Self

Triangle

struct Triangle

A triangle defined by its three vertices a, b, c: center, area, closest_point, contains, move_to, translate.

Unlike Rectangle/Circle, a Triangle is not centre-positioned in its fields -- center() (the centroid) is derived, and move_to translates all three vertices so the centroid lands on the given point. Vertex winding (clockwise or counter-clockwise) does not matter to any method here; contains and area both work from unsigned or sign-normalized quantities.

contains treats every edge as inside (boundary-inclusive), matching Rectangle/Circle. When the three vertices are collinear (including all three coincident), the hull has zero area and collapses to the longest edge as a segment; contains/overlaps/area all remain exact for this degenerate case rather than reporting a false containment, overlap, or a divide-by-zero.

corner_radius rounds all three corners by one radius, as canvas.triangle draws them, and every test here sees the rounded shape. It is clamped so no corner's rounding overruns half of either edge beside it; one thin corner limits all three.

Implements Copyable, Deinitable, Drawable, Equatable, ImplicitlyCopyable, Movable, Region, Shape, Writable, _Collider.

Fields

  • a: Point2D
  • b: Point2D
  • c: Point2D
  • corner_radius: Float64: How far each corner is rounded; 0 is sharp.

Constructors

@staticmethod
def __init__(
    out self,
    a: Point2D,
    b: Point2D,
    c: Point2D,
    *,
    corner_radius: _OptionalFloat64 = SIMD(0),
)

__eq__

def __eq__(self, other: Self) -> Bool

__ne__

def __ne__(self, other: Self) -> Bool

center

def center(self) -> Point2D

area

def area(self) -> Float64

bounds

def bounds(self) -> Rectangle

The tightest axis-aligned rectangle around the triangle. Rounded, it is the inner triangle's grown by the radius, so a sharp tip's rounding pulls it in.

closest_point

def closest_point(self, p: Point2D) -> Point2D

contains

def contains(self, p: Point2D) -> Bool
def contains(self, other: Self) -> Bool
def contains(self, r: Rectangle) -> Bool
def contains(self, c: Circle) -> Bool
def contains(self, l: Line) -> Bool
def contains(self, c: Capsule) -> Bool
def contains[C: Curve](self, curve: C) -> Bool

Whether the whole of curve (an Arc, Bezier or Spline) is inside: every segment of it flattened to within 1e-4 of its size, as intersects follows it, so a curve grazing the edge from inside is decided only that closely.

def contains(self, s: Sector) -> Bool
def contains(self, p: Polygon) -> Bool
def contains(self, e: Ellipse) -> Bool

draw_to

def draw_to[T: DrawTarget](self, mut target: T)

move_to

def move_to(self, position: Point2D) -> Self

translate

def translate(self, delta: Vector2D) -> Self

Arc

struct Arc

A circular arc: part of the circle around position with radius r, from start_angle round by sweep_angle. end_angle, at, tangent, length, at_distance, split, bounds, flatten, beziers, closest_point, intersects, move_to, translate.

Angles are radians, counted counter-clockwise from the +x axis: y is up, so this is the direction rotate turns. The sweep is signed -- positive runs counter-clockwise, negative clockwise -- so every arc has one spelling and reversing one is negating its sweep. A sweep of a full turn (TAU) or more is the whole circle. The fields keep what was given; the methods read the sweep clamped to a turn.

Like Line and Bezier, an arc is a curve with no interior: there is no area, contains or overlaps for it. position is the circle's centre, which the arc passes through only when r is zero, and move_to moves that centre. A zero r or sweep collapses the arc to a point.

Implements Copyable, Curve, Deinitable, Drawable, Equatable, ImplicitlyCopyable, Movable, Shape, Writable.

Fields

  • position: Point2D
  • r: Float64
  • start_angle: Float64
  • sweep_angle: Float64

Constructors

@staticmethod
def __init__(
    out self,
    position: Point2D,
    r: Float64,
    start_angle: Float64,
    sweep_angle: Float64,
)
@staticmethod
def __init__(
    out self,
    position: Point2D,
    r: Int,
    start_angle: Float64,
    sweep_angle: Float64,
)

__eq__

def __eq__(self, other: Self) -> Bool

__ne__

def __ne__(self, other: Self) -> Bool

end_angle

def end_angle(self) -> Float64

start_angle + sweep_angle: where the arc ends, as an angle.

at

def at(self, t: Float64) -> Point2D

The point at parameter t: 0 at the start, 1 at the end, and proportional to distance along the arc in between. Not clamped: outside 0..1 it carries on round the circle.

tangent

def tangent(self, t: Float64) -> Vector2D

The derivative at t: the direction of travel, with a magnitude that is the arc's length (the speed per unit of t, the same everywhere). Zero for a collapsed arc.

length

def length(self) -> Float64

The distance along the arc from start to end. Exact.

at_distance

def at_distance(self, distance: Float64) -> Point2D

The point distance along the arc from its start, clamped to the arc's ends. An arc has constant speed, so this is at with t scaled: exact, and as cheap as at.

split

def split(self, t: Float64) -> Tuple[Self, Self]

The arc cut at t into two on the same circle, together tracing exactly the original: the first runs from the start to at(t), the second from at(t) to the end. A sweep past a full turn is clamped to one first, as everywhere else.

bounds

def bounds(self) -> Rectangle

The tightest axis-aligned rectangle around the arc: its two ends, widened to each of the circle's four extremes the arc passes.

flatten

def flatten(self, tolerance: Float64) -> List[Point2D]

Points along the arc, from start to end inclusive, such that the straight segments between them stray from the arc by at most tolerance.

The points are evenly spaced, as few as the tolerance allows, and capped at 1024 segments. A collapsed arc still gives two points.

beziers

def beziers(self) -> List[Bezier]

The arc as cubic Béziers, each ending where the next starts, in pieces of at most 45°. An approximation -- no cubic is exactly a circle -- but within about 4e-6 of the radius. A full circle ends exactly where it starts. A collapsed arc gives no curves.

closest_point

def closest_point(self, p: Point2D) -> Point2D

The point on the arc nearest p: straight out from the centre when p lies within the sweep, otherwise the nearer end. From the centre itself every point is as near, and the start is returned.

intersects

def intersects(self, p: Point2D) -> Bool
def intersects(self, l: Line) -> Bool
def intersects(self, other: Self) -> Bool
def intersects(self, c: Circle) -> Bool
def intersects(self, c: Capsule) -> Bool
def intersects(self, e: Ellipse) -> Bool
def intersects(self, r: Rectangle) -> Bool
def intersects(self, t: Triangle) -> Bool
def intersects(self, s: Sector) -> Bool
def intersects(self, p: Polygon) -> Bool
def intersects(self, b: Bezier) -> Bool
def intersects(self, s: Spline) -> Bool

draw_to

def draw_to[T: DrawTarget](self, mut target: T)

move_to

def move_to(self, position: Point2D) -> Self

translate

def translate(self, delta: Vector2D) -> Self

Sector

struct Sector

A sector -- a slice of pie: the region between two radii of the circle around position with radius r, from start_angle round by sweep_angle. end_angle, center, area, arc, bounds, closest_point, contains, move_to, translate.

The angles are an Arc's: radians, counter-clockwise from the +x axis, and a signed sweep, so a negative one runs clockwise and a full turn or more is the whole circle. The fields keep what was given.

center() is position, the tip of the slice, as for Circle: not the centroid. contains treats the boundary as inside, within the slack Arc.intersects allows, so points found on the sector's own arc count; closest_point returns the query point itself when it is inside. A sweep wider than a half turn is not convex. A zero r collapses the sector to its tip, and a zero sweep to the one radius at start_angle. r is assumed non-negative.

corner_radius rounds the tip and both ends of the arc, as canvas.sector draws them, and every test here sees the rounded shape. An end rounds against the arc, tangent to it from inside. Each corner is clamped to half of what lies beside it, as a polygon's is; past a half turn the tip is concave and its rounding fills the notch. A whole disc has no corners. The tests use the rounded outline with each curve flattened, so a point within about 0.1% of the radii of the boundary may fall either way.

Implements Copyable, Deinitable, Drawable, Equatable, ImplicitlyCopyable, Movable, Region, Shape, Writable.

Fields

  • position: Point2D
  • r: Float64
  • start_angle: Float64
  • sweep_angle: Float64
  • corner_radius: Float64: How far each corner is rounded; 0 is sharp.

Constructors

@staticmethod
def __init__(
    out self,
    position: Point2D,
    r: Float64,
    start_angle: Float64,
    sweep_angle: Float64,
    *,
    corner_radius: _OptionalFloat64 = SIMD(0),
)
@staticmethod
def __init__(
    out self,
    position: Point2D,
    r: Int,
    start_angle: Float64,
    sweep_angle: Float64,
    *,
    corner_radius: _OptionalFloat64 = SIMD(0),
)

__eq__

def __eq__(self, other: Self) -> Bool

__ne__

def __ne__(self, other: Self) -> Bool

end_angle

def end_angle(self) -> Float64

start_angle + sweep_angle: where the sector ends, as an angle.

center

def center(self) -> Point2D

area

def area(self) -> Float64

arc

def arc(self) -> Arc

The curved edge of the sector, running the same way round.

bounds

def bounds(self) -> Rectangle

The tightest axis-aligned rectangle around the sector: its arc's, widened to take in the tip.

closest_point

def closest_point(self, p: Point2D) -> Point2D

contains

def contains(self, p: Point2D) -> Bool
def contains(self, r: Rectangle) -> Bool
def contains(self, t: Triangle) -> Bool
def contains(self, c: Circle) -> Bool
def contains(self, other: Self) -> Bool
def contains(self, l: Line) -> Bool
def contains[C: Curve](self, curve: C) -> Bool

Whether the whole of curve (an Arc, Bezier or Spline) is inside: every segment of it flattened to within 1e-4 of its size, as intersects follows it, so a curve grazing the edge from inside is decided only that closely.

def contains(self, p: Polygon) -> Bool
def contains(self, e: Ellipse) -> Bool
def contains(self, c: Capsule) -> Bool

draw_to

def draw_to[T: DrawTarget](self, mut target: T)

move_to

def move_to(self, position: Point2D) -> Self

translate

def translate(self, delta: Vector2D) -> Self

Polygon

struct Polygon

A polygon through vertices, in order, closed back to the first: regular, star, center, area, bounds, closest_point, contains, move_to, translate.

Any vertices make a polygon, concave or crossing itself. What is inside follows the nonzero rule: a point is inside when the edges wind round it, either way, so a pentagram given as five crossing vertices is solid through its middle. Vertex winding does not matter, and the boundary counts as inside. With fewer than three vertices, or all of them in line, the polygon collapses to its edges.

It is a region: in every overlaps pair, contains both ways, and met by Line.intersects and Arc.intersects.

Like a Triangle's, the fields are the vertices and center() (the centroid) is derived; move_to moves the centroid. The centroid and area are those of a simple polygon -- one whose edges do not cross.

corner_radius rounds every corner, as canvas.polygon draws it, and every test here sees the rounded shape. Each corner is clamped on its own so its rounding takes at most half of either edge beside it: a sharp tip rounds less without holding back the rest. A concave corner rounds too, filling its notch. The tests use the rounded outline with each fillet flattened, so a point within about 0.1% of the radius of a fillet may fall either way.

Implements Copyable, Deinitable, Drawable, Equatable, Movable, Region, Shape, Writable.

Fields

  • vertices: List[Point2D]
  • corner_radius: Float64: How far each corner is rounded; 0 is sharp.

Constructors

@staticmethod
def __init__(out self, *vertices: Point2D, *, corner_radius: _OptionalFloat64 = SIMD(0))
@staticmethod
def __init__(
    out self,
    var vertices: List[Point2D],
    *,
    corner_radius: _OptionalFloat64 = SIMD(0),
)

__eq__

def __eq__(self, other: Self) -> Bool

__ne__

def __ne__(self, other: Self) -> Bool

regular

@staticmethod
def regular(
    position: Point2D,
    r: Float64,
    sides: Int,
    start_angle: Float64 = 1.5707963267948966,
    *,
    corner_radius: _OptionalFloat64 = SIMD(0),
) -> Self

A regular polygon of sides vertices on the circle around position with radius r, the first at start_angle (radians counter-clockwise from +x; straight up by default), the rest following counter-clockwise.

@staticmethod
def regular(
    position: Point2D,
    r: Int,
    sides: Int,
    start_angle: Float64 = 1.5707963267948966,
    *,
    corner_radius: _OptionalFloat64 = SIMD(0),
) -> Self

star

@staticmethod
def star(
    position: Point2D,
    r: Float64,
    inner_radius: Float64,
    tips: Int,
    start_angle: Float64 = 1.5707963267948966,
    *,
    corner_radius: _OptionalFloat64 = SIMD(0),
) -> Self

A star of tips points about position: its tips on radius r, the first at start_angle (straight up by default), and between each two tips a vertex on radius inner_radius, halfway round. The outline does not cross itself, so it is a simple polygon.

@staticmethod
def star(
    position: Point2D,
    r: Int,
    inner_radius: Int,
    tips: Int,
    start_angle: Float64 = 1.5707963267948966,
    *,
    corner_radius: _OptionalFloat64 = SIMD(0),
) -> Self

center

def center(self) -> Point2D

The centroid of the enclosed area; with no area, the mean of the vertices (the origin for none).

area

def area(self) -> Float64

bounds

def bounds(self) -> Rectangle

The tightest axis-aligned rectangle around the polygon; a zero-size one at the origin for none.

closest_point

def closest_point(self, p: Point2D) -> Point2D

p itself when inside, else the nearest point on an edge. A polygon with no vertices has no points, and returns p.

contains

def contains(self, p: Point2D) -> Bool
def contains(self, l: Line) -> Bool
def contains[C: Curve](self, curve: C) -> Bool

Whether the whole of curve (an Arc, Bezier or Spline) is inside: every segment of it flattened to within 1e-4 of its size, as intersects follows it, so a curve grazing the edge from inside is decided only that closely.

def contains(self, r: Rectangle) -> Bool
def contains(self, t: Triangle) -> Bool
def contains(self, c: Circle) -> Bool
def contains(self, s: Sector) -> Bool
def contains(self, other: Self) -> Bool
def contains(self, e: Ellipse) -> Bool
def contains(self, c: Capsule) -> Bool

draw_to

def draw_to[T: DrawTarget](self, mut target: T)

move_to

def move_to(self, position: Point2D) -> Self

translate

def translate(self, delta: Vector2D) -> Self

overlaps

def overlaps(a: Circle, b: Circle) -> Bool
def overlaps(a: Circle, b: Capsule) -> Bool
def overlaps(a: Circle, b: Rectangle) -> Bool
def overlaps(a: Circle, b: Triangle) -> Bool
def overlaps(a: Capsule, b: Circle) -> Bool
def overlaps(a: Capsule, b: Capsule) -> Bool
def overlaps(a: Capsule, b: Rectangle) -> Bool
def overlaps(a: Capsule, b: Triangle) -> Bool
def overlaps(a: Rectangle, b: Circle) -> Bool
def overlaps(a: Rectangle, b: Capsule) -> Bool
def overlaps(a: Rectangle, b: Rectangle) -> Bool
def overlaps(a: Rectangle, b: Triangle) -> Bool
def overlaps(a: Triangle, b: Circle) -> Bool
def overlaps(a: Triangle, b: Capsule) -> Bool
def overlaps(a: Triangle, b: Rectangle) -> Bool
def overlaps(a: Triangle, b: Triangle) -> Bool
def overlaps(a: Sector, b: Circle) -> Bool
def overlaps(a: Circle, b: Sector) -> Bool
def overlaps(a: Sector, b: Rectangle) -> Bool
def overlaps(a: Rectangle, b: Sector) -> Bool
def overlaps(a: Sector, b: Triangle) -> Bool
def overlaps(a: Triangle, b: Sector) -> Bool
def overlaps(a: Sector, b: Sector) -> Bool
def overlaps(a: Polygon, b: Circle) -> Bool
def overlaps(a: Circle, b: Polygon) -> Bool
def overlaps(a: Polygon, b: Rectangle) -> Bool
def overlaps(a: Rectangle, b: Polygon) -> Bool
def overlaps(a: Polygon, b: Triangle) -> Bool
def overlaps(a: Triangle, b: Polygon) -> Bool
def overlaps(a: Polygon, b: Sector) -> Bool
def overlaps(a: Sector, b: Polygon) -> Bool
def overlaps(a: Polygon, b: Polygon) -> Bool
def overlaps(a: Ellipse, b: Circle) -> Bool
def overlaps(a: Circle, b: Ellipse) -> Bool
def overlaps(a: Ellipse, b: Rectangle) -> Bool
def overlaps(a: Rectangle, b: Ellipse) -> Bool
def overlaps(a: Ellipse, b: Triangle) -> Bool
def overlaps(a: Triangle, b: Ellipse) -> Bool
def overlaps(a: Ellipse, b: Sector) -> Bool
def overlaps(a: Sector, b: Ellipse) -> Bool
def overlaps(a: Ellipse, b: Polygon) -> Bool
def overlaps(a: Polygon, b: Ellipse) -> Bool
def overlaps(a: Ellipse, b: Ellipse) -> Bool
def overlaps(a: Capsule, b: Ellipse) -> Bool
def overlaps(a: Ellipse, b: Capsule) -> Bool
def overlaps(a: Capsule, b: Sector) -> Bool
def overlaps(a: Sector, b: Capsule) -> Bool
def overlaps(a: Capsule, b: Polygon) -> Bool
def overlaps(a: Polygon, b: Capsule) -> Bool

Bezier

struct Bezier

A cubic Bézier curve from start to end, shaped by control1 and control2: at, tangent, split, bounds, flatten, beziers, closest_point, intersects, translate.

The curve passes through start and end only; the controls pull it towards themselves and set the direction it leaves start (control1 - start) and arrives at end (end - control2). It is the curve SVG paths, fonts and vector editors use. For a smooth curve through given points instead, see Spline.

Like Line, a curve has no interior and is not a region: there is no area, contains or overlaps for it.

t runs from 0 at start to 1 at end, but it is not proportional to distance along the curve — equal steps in t bunch up where the controls do.

Implements Copyable, Curve, Deinitable, Drawable, Equatable, ImplicitlyCopyable, Movable, Shape, Writable.

Fields

  • start: Point2D
  • control1: Point2D
  • control2: Point2D
  • end: Point2D

Constructors

@staticmethod
def __init__(
    out self,
    start: Point2D,
    control1: Point2D,
    control2: Point2D,
    end: Point2D,
)

__eq__

def __eq__(self, other: Self) -> Bool

__ne__

def __ne__(self, other: Self) -> Bool

draw_to

def draw_to[T: DrawTarget](self, mut target: T)

at

def at(self, t: Float64) -> Point2D

The point at parameter t. Not clamped: outside 0..1 the cubic continues past its endpoints, as lerp extrapolates.

tangent

def tangent(self, t: Float64) -> Vector2D

The derivative at t: the direction of travel, with a magnitude that is the curve's speed per unit of t. Not normalised — it is zero where the curve stops, e.g. at a cusp or where a control sits on its endpoint.

closest_point

def closest_point(self, p: Point2D) -> Point2D

The point on the curve nearest p.

Samples the curve at even steps in t, then refines every sample nearer than its neighbours with Newton's method on the distance's derivative, kept within the curve, and keeps the nearest result. Exact to rounding for any curve that fits on a screen, though a search, not a formula.

intersects

def intersects(self, p: Point2D) -> Bool
def intersects(self, l: Line) -> Bool
def intersects(self, a: Arc) -> Bool
def intersects(self, other: Self) -> Bool
def intersects(self, s: Spline) -> Bool
def intersects(self, c: Circle) -> Bool
def intersects(self, c: Capsule) -> Bool
def intersects(self, e: Ellipse) -> Bool
def intersects(self, r: Rectangle) -> Bool
def intersects(self, t: Triangle) -> Bool
def intersects(self, s: Sector) -> Bool
def intersects(self, p: Polygon) -> Bool

split

def split(self, t: Float64) -> Tuple[Self, Self]

The curve cut at t into two, together tracing exactly the original: the first runs start to at(t), the second at(t) to end (de Casteljau).

bounds

def bounds(self) -> Rectangle

The tightest axis-aligned rectangle around the curve — around the curve itself, not its control points, which usually reach further.

flatten

def flatten(self, tolerance: Float64) -> List[Point2D]

Points along the curve, from start to end inclusive, such that the straight segments between them stray from the curve by at most tolerance.

The points are evenly spaced in t, with the count from Wang's formula, and capped at 1024 segments. A curve collapsed to one point still gives two points.

beziers

def beziers(self) -> List[Self]

Itself, alone: the form Arc.beziers, Line.beziers and Spline.beziers share, so every curve converts the same way.

length

def length(self) -> Float64

The distance along the curve from start to end.

Gauss–Legendre quadrature (8 points on each of 16 equal steps in t) of the curve's speed — accurate to far below a pixel for any curve that fits on a screen, but an approximation, not exact.

at_distance

def at_distance(self, distance: Float64) -> Point2D

The point distance along the curve from start, clamped to the curve's ends.

Unlike at, equal steps in distance are equal steps along the curve, so this is what moves something along it at a steady speed. Each call inverts length numerically; sample once per frame per entity, not in a tight loop.

translate

def translate(self, delta: Vector2D) -> Self

Spline

struct Spline

A smooth curve through every one of points, in order: at, tangent, length, at_distance, bounds, flatten, beziers, closest_point, intersects, translate.

It is a Catmull-Rom spline: each stretch between two neighbouring points is a cubic whose direction at each point is set by that point's neighbours, so the curve turns smoothly through every point without the off-curve controls a Bezier needs. The first and last points have one neighbour each; the curve leaves and arrives there heading along the missing neighbour's mirror image. With closed, the curve runs on from the last point back to the first and the ends have neighbours like any other point.

alpha sets how the spacing of the points shapes the curve: 0 is uniform, which can loop or cusp where points bunch up; 0.5, centripetal, the default, never does within a stretch; 1 is chordal, which hugs the points more loosely still. Between 0 and 1 is useful.

t runs from 0 at the first point to 1 at the last (back at the first when closed), each stretch taking an equal share whatever its length, so equal steps in t are not equal distances; at_distance is.

Fewer than two distinct points make no curve: then at and at_distance give the one point (the origin if there is none), length is 0 and flatten gives no points.

Like Bezier, a spline has no interior and is not a region.

Implements Copyable, Curve, Deinitable, Drawable, Equatable, Movable, Shape, Writable.

Fields

  • points: List[Point2D]
  • alpha: Float64
  • closed: Bool

Constructors

@staticmethod
def __init__(
    out self,
    var points: List[Point2D],
    alpha: Float64 = 0.5,
    closed: Bool = False,
)

__eq__

def __eq__(self, other: Self) -> Bool

__ne__

def __ne__(self, other: Self) -> Bool

draw_to

def draw_to[T: DrawTarget](self, mut target: T)

beziers

def beziers(self) -> List[Bezier]

The spline as cubic Béziers, one per stretch between neighbouring points, each ending where the next starts. Exact, not an approximation: every stretch is a cubic, and any cubic is a Bézier.

An open spline of n points gives n - 1 curves; a closed one gives n, the last ending at the first point. A point repeating the one before it is skipped, as is a closed spline's last point repeating its first; fewer than two distinct points give no curves.

at

def at(self, t: Float64) -> Point2D

The point at parameter t, clamped to 0..1: a spline has no natural continuation past its ends.

tangent

def tangent(self, t: Float64) -> Vector2D

The derivative with respect to t at t, clamped to 0..1: the direction of travel, with a magnitude that is the speed t moves at. A joint belongs to the stretch that starts there. Zero when there is no curve.

length

def length(self) -> Float64

The distance along the curve from the first point to the last, as each stretch's Bezier.length summed.

at_distance

def at_distance(self, distance: Float64) -> Point2D

The point distance along the curve from the first point, clamped to the curve's ends. Equal steps in distance are equal steps along the curve. Measures every stretch per call; sample once per frame per entity, not in a tight loop.

bounds

def bounds(self) -> Rectangle

The tightest axis-aligned rectangle around the curve. A spline with no curve gives a rectangle of no size at its one point.

flatten

def flatten(self, tolerance: Float64) -> List[Point2D]

Points along the curve, from the first point to the last inclusive, straying from it by at most tolerance between them: each stretch's Bezier.flatten, joined without repeating the point where one stretch meets the next.

closest_point

def closest_point(self, p: Point2D) -> Point2D

The point on the curve nearest p: the nearest of each stretch's Bezier.closest_point. A spline with no curve gives its one point.

intersects

def intersects(self, p: Point2D) -> Bool
def intersects(self, l: Line) -> Bool
def intersects(self, a: Arc) -> Bool
def intersects(self, b: Bezier) -> Bool
def intersects(self, c: Circle) -> Bool
def intersects(self, c: Capsule) -> Bool
def intersects(self, e: Ellipse) -> Bool
def intersects(self, r: Rectangle) -> Bool
def intersects(self, t: Triangle) -> Bool
def intersects(self, s: Sector) -> Bool
def intersects(self, p: Polygon) -> Bool
def intersects(self, other: Self) -> Bool

translate

def translate(self, delta: Vector2D) -> Self

Curve

trait Curve

A shape with no inside, traced from one end to the other: Line, Arc, Bezier and Spline. A straight line is a curve too, as in mathematics.

t runs from 0 at the start to 1 at the end; only on a Line and an Arc is it proportional to distance, which at_distance always is.

Implements Copyable, Deinitable, Drawable, Movable, Shape.

at

def at(self, t: Float64) -> Point2D

The point at parameter t.

tangent

def tangent(self, t: Float64) -> Vector2D

The derivative at t: the direction of travel, with the speed per unit of t as its magnitude.

length

def length(self) -> Float64

The distance along the curve from start to end.

at_distance

def at_distance(self, distance: Float64) -> Point2D

The point distance along the curve from its start, clamped to its ends.

flatten

def flatten(self, tolerance: Float64) -> List[Point2D]

Points along the curve, start to end, whose straight segments stray from it by at most tolerance.

beziers

def beziers(self) -> List[Bezier]

The curve as cubic Béziers, each ending where the next starts.

Drawable

trait Drawable

Something a DrawTarget can draw: a shape, a curve, or a program's own type made of them.

draw_to

def draw_to[T: DrawTarget](self, mut target: T)

Draw this onto target, through its draw or, for a shape, the primitive for its kind.

DrawTarget

trait DrawTarget

Somewhere shapes are drawn: Canvas. Its primitives, one per kind of shape, are the library's own; a program draws through draw.

draw

def draw[D: Drawable](mut self, shape: D)

Draw shape in the current style and transform.

Region

trait Region

A shape with an inside: Rectangle, Circle, Ellipse, Capsule, Triangle, Sector and Polygon, the region types of overlaps and contains, and what canvas.clip takes. A curve has no inside, so it is not one.

Implements Copyable, Deinitable, Drawable, Movable, Shape.

center

def center(self) -> Point2D

area

def area(self) -> Float64

contains

def contains(self, p: Point2D) -> Bool

Whether p is inside, its boundary included.

move_to

def move_to(self, position: Point2D) -> Self

The region moved so its position (or, without one, its center()) is at position; this one is unchanged.

Shape

trait Shape

A geometric shape: a Region or a Curve. What every one of them answers, whichever it is, and a plain value generic code can keep, copy and drop. Tests between two shapes (contains, intersects, overlaps) are overloads per pair, not part of this.

Implements Copyable, Deinitable, Drawable, Movable.

bounds

def bounds(self) -> Rectangle

The tightest axis-aligned rectangle around the shape.

closest_point

def closest_point(self, p: Point2D) -> Point2D

The point of the shape nearest p: p itself inside a region.

translate

def translate(self, delta: Vector2D) -> Self

The shape moved by delta; this one is unchanged.