std/geometry
The shapes of the plane and of space, with the half-open rule that makes a row of rectangles a tiling and the ray tests that answer a distance.
Diese Seite gibt es noch nicht auf Deutsch, deshalb steht sie hier auf Englisch. Zur englischen Seite
Die generierte Referenz von std/geometry zeigt jede öffentliche Deklaration mit Signatur, Doc-Kommentar und Beispielen.
std/geometry is the shapes and what they answer about each other: what contains a point, what two of them share, how
far apart they are, which axis-aligned box holds one, and what a ray hits. In the plane: Rectangle, Circle,
Segment2, Ray2, Triangle2, Polygon. In space: Box, Sphere, Ray3, Plane, Triangle3.
Every shape is generic over its scalar the way std/linear's vectors are: a Rectangle<Int> is the pixel or tile
rectangle and a Rectangle is the continuous one, and the tests that need a square root or an angle live under Real
while everything else works with whole numbers. It is a pure value library and depends on std/linear and nothing else.
Import#
use Rectangle, Circle, Segment2, Ray2, Triangle2, Polygon from "std/geometry"
use Box, Sphere, Ray3, Plane, Triangle3 from "std/geometry"use Vector2 from "std/linear"
use Rectangle from "std/geometry"
const tile = Rectangle Vector2(0, 0), Vector2(8, 8)
print "{tile.contains(Vector2(0, 0))} {tile.contains(Vector2(8, 0))} {tile.area()}"The half-open rule#
The minimum edge of a Rectangle or a Box is inside it and the maximum edge is outside. A point is in a rectangle
when minimum.x <= point.x and point.x < maximum.x, on every axis and for every scalar. That is what makes a row of
them a tiling: Rectangle(Vector2(0, 0), Vector2(8, 8)) and Rectangle(Vector2(8, 0), Vector2(8, 8)) share the column
x == 8 in neither of them, so a pixel belongs to exactly one tile and a hit test never answers twice.
A Circle and a Sphere are closed instead - their boundary is inside - because they do not tile: nothing is lost by
counting a boundary twice, and a great deal is lost by leaving a touching point out of both of two touching circles.
bounds and covering answer the closed hull of what they are given, so a corner of a shape can lie exactly on the
maximum edge of that shape's own bounding rectangle and contains then answers false for it. A bound is a bound; the
half-open rule is about tiling.
Declarations#
Rectangle, Box#
type Rectangle<Scalar: Numeric = Float> {
origin: Vector2<Scalar>
size: Vector2<Scalar>
static fn between(first: Vector2<Scalar>, second: Vector2<Scalar>): Self
fn minimum(): Vector2<Scalar>
fn maximum(): Vector2<Scalar>
fn isEmpty(): Bool
fn area(): Scalar
fn contains(point: Vector2<Scalar>): Bool
fn encloses(other: Self): Bool
fn intersects(other: Self): Bool
fn intersection(other: Self): Self
fn combined(other: Self): Self
fn covering(point: Vector2<Scalar>): Self
fn translated(by: Vector2<Scalar>): Self
fn grown(by: Vector2<Scalar>): Self
fn withSize(value: Vector2<Scalar>): Self
fn closestPoint(to: Vector2<Scalar>): Vector2<Scalar>
fn bounds(): Self
}
extend<Scalar: Signed> Rectangle<Scalar> {
fn manhattanDistanceTo(point: Vector2<Scalar>): Scalar
}
extend<Scalar: Real> Rectangle<Scalar> {
static fn centered(at: Vector2<Scalar>, size: Vector2<Scalar>): Self
fn center(): Vector2<Scalar>
fn distanceTo(point: Vector2<Scalar>): Scalar
fn distanceSquaredTo(point: Vector2<Scalar>): Scalar
}
type Box<Scalar: Numeric = Float> {
origin: Vector3<Scalar>
size: Vector3<Scalar>
static fn between(first: Vector3<Scalar>, second: Vector3<Scalar>): Self
fn minimum(): Vector3<Scalar>
fn maximum(): Vector3<Scalar>
fn isEmpty(): Bool
fn volume(): Scalar
fn contains(point: Vector3<Scalar>): Bool
fn encloses(other: Self): Bool
fn intersects(other: Self): Bool
fn intersection(other: Self): Self
fn combined(other: Self): Self
fn covering(point: Vector3<Scalar>): Self
fn translated(by: Vector3<Scalar>): Self
fn grown(by: Vector3<Scalar>): Self
fn closestPoint(to: Vector3<Scalar>): Vector3<Scalar>
fn bounds(): Self
}
extend<Scalar: Signed> Box<Scalar> {
fn manhattanDistanceTo(point: Vector3<Scalar>): Scalar
}
extend<Scalar: Real> Box<Scalar> {
static fn centered(at: Vector3<Scalar>, size: Vector3<Scalar>): Self
fn center(): Vector3<Scalar>
fn distanceTo(point: Vector3<Scalar>): Scalar
fn distanceSquaredTo(point: Vector3<Scalar>): Scalar
}An axis-aligned box: where it starts (origin, the corner with the smaller coordinates) and how big it is (size). A
size with a negative or zero component describes no points, and isEmpty says so - intersection answers an empty one
rather than None, so that a chain of them stays a rectangle.
between (from two corners), minimum, maximum, area/volume, contains, encloses, intersects,
intersection, combined, covering, translated, grown, withSize, closestPoint, bounds and
manhattanDistanceTo need only Numeric; centered, center, distanceTo and distanceSquaredTo halve or take a
root and therefore need Real.
Nothing here says top or bottom. Whether a viewer sees the minimum corner as the top or the bottom is the program's
own business.
Circle, Sphere#
type Circle<Scalar: Real = Float> {
center: Vector2<Scalar>
radius: Scalar
fn isEmpty(): Bool
fn contains(point: Vector2<Scalar>): Bool
fn encloses(other: Self): Bool
fn intersects(other: Self): Bool
fn intersectsRectangle(other: Rectangle<Scalar>): Bool
fn closestPoint(to: Vector2<Scalar>): Vector2<Scalar>
fn distanceTo(point: Vector2<Scalar>): Scalar
fn translated(by: Vector2<Scalar>): Self
fn grown(by: Scalar): Self
fn bounds(): Rectangle<Scalar>
fn area(): Scalar
fn circumference(): Scalar
}
type Sphere<Scalar: Real = Float> {
center: Vector3<Scalar>
radius: Scalar
static fn around(box: Box<Scalar>): Self
fn isEmpty(): Bool
fn contains(point: Vector3<Scalar>): Bool
fn encloses(other: Self): Bool
fn intersects(other: Self): Bool
fn intersectsBox(other: Box<Scalar>): Bool
fn closestPoint(to: Vector3<Scalar>): Vector3<Scalar>
fn distanceTo(point: Vector3<Scalar>): Scalar
fn translated(by: Vector3<Scalar>): Self
fn grown(by: Scalar): Self
fn bounds(): Box<Scalar>
fn combined(other: Self): Self
}A disc or a ball: a center and a radius, with the boundary inside. contains, encloses, intersects,
intersectsRectangle/intersectsBox (a test against the nearest point of the box), closestPoint, distanceTo,
translated, grown, bounds, combined, and for a circle area and circumference. Sphere.around(box) is the ball
through the corners of a box.
Segment2, Ray2, Ray3#
type Segment2<Scalar: Numeric = Float> {
from: Vector2<Scalar>
to: Vector2<Scalar>
fn step(): Vector2<Scalar>
fn sideOf(point: Vector2<Scalar>): Scalar
fn bounds(): Rectangle<Scalar>
fn intersects(other: Self): Bool
}
extend<Scalar: Signed> Segment2<Scalar> {
fn manhattanLength(): Scalar
}
extend<Scalar: Real> Segment2<Scalar> {
fn length(): Scalar
fn direction(): Vector2<Scalar>
fn center(): Vector2<Scalar>
fn at(factor: Scalar): Vector2<Scalar>
fn closestPoint(to: Vector2<Scalar>): Vector2<Scalar>
fn distanceTo(point: Vector2<Scalar>): Scalar
fn intersection(other: Self): Vector2<Scalar>?
}
type Ray2<Scalar: Real = Float> {
origin: Vector2<Scalar>
direction: Vector2<Scalar>
static fn towards(from: Vector2<Scalar>, to: Vector2<Scalar>): Self
fn at(distance: Scalar): Vector2<Scalar>
fn hits(circle: Circle<Scalar>): Scalar?
fn hitsRectangle(rectangle: Rectangle<Scalar>): Scalar?
fn hitsSegment(segment: Segment2<Scalar>): Scalar?
}
type Ray3<Scalar: Real = Float> {
origin: Vector3<Scalar>
direction: Vector3<Scalar>
static fn towards(from: Vector3<Scalar>, to: Vector3<Scalar>): Self
fn at(distance: Scalar): Vector3<Scalar>
fn hits(sphere: Sphere<Scalar>): Scalar?
fn hitsPlane(plane: Plane<Scalar>): Scalar?
fn hitsBox(box: Box<Scalar>): Scalar?
fn hitsTriangle(triangle: Triangle3<Scalar>): Scalar?
}A Segment2 is the straight piece of line between two points, with both ends belonging to it: step, sideOf, bounds
and intersects under Numeric, and length, direction, center, at, closestPoint, distanceTo and
intersection (the crossing point) under Real.
A ray is a half-line, and every hit test answers how far along the ray the hit is, measured in the length of
direction - not the point. That is what a caller compares to find the nearest of several hits, and ray.at(distance)
turns it back into a point. A ray with a direction of length one therefore answers distances. Ray2 hits a Circle, a
Rectangle (the slab test) and a Segment2; Ray3 hits a Sphere, a Plane, a Box and a Triangle3. A ray that
starts inside a shape answers 0, and a ray with no direction answers None.
Triangle2, Triangle3#
type Triangle2<Scalar: Numeric = Float> {
first: Vector2<Scalar>
second: Vector2<Scalar>
third: Vector2<Scalar>
fn signedArea(): Scalar
fn bounds(): Rectangle<Scalar>
fn edges(): List<Segment2<Scalar>>
fn contains(point: Vector2<Scalar>): Bool
fn translated(by: Vector2<Scalar>): Self
}
extend<Scalar: Signed> Triangle2<Scalar> {
fn doubledArea(): Scalar
}
extend<Scalar: Real> Triangle2<Scalar> {
fn area(): Scalar
fn center(): Vector2<Scalar>
fn closestPoint(to: Vector2<Scalar>): Vector2<Scalar>
fn distanceTo(point: Vector2<Scalar>): Scalar
}
type Triangle3<Scalar: Numeric = Float> {
first: Vector3<Scalar>
second: Vector3<Scalar>
third: Vector3<Scalar>
fn firstEdge(): Vector3<Scalar>
fn secondEdge(): Vector3<Scalar>
fn doubledAreaVector(): Vector3<Scalar>
fn bounds(): Box<Scalar>
fn translated(by: Vector3<Scalar>): Self
}
extend<Scalar: Real> Triangle3<Scalar> {
fn normal(): Vector3<Scalar>
fn area(): Scalar
fn plane(): Plane<Scalar>
fn center(): Vector3<Scalar>
fn weightsOf(point: Vector3<Scalar>): (first: Scalar, second: Scalar, third: Scalar)
fn contains(point: Vector3<Scalar>): Bool
}Three corners. The winding is not fixed: signedArea is positive where the corners run from the first axis towards the
second and negative the other way, and contains works for both. In space the normal follows the right-handed order of
the corners, plane answers the plane they lie in, and weightsOf writes a point as a mix of the three corners - which
is what says whether a crossing of the plane is a crossing of the face.
Polygon#
type Polygon<Scalar: Numeric = Float> {
corners: List<Vector2<Scalar>>
static fn ofTriangle(triangle: Triangle2<Scalar>): Self
static fn ofRectangle(rectangle: Rectangle<Scalar>): Self
fn isEmpty(): Bool
fn edges(): List<Segment2<Scalar>>
fn signedDoubledArea(): Scalar
fn bounds(): Rectangle<Scalar>
fn isConvex(): Bool
fn containsConvex(point: Vector2<Scalar>): Bool
fn contains(point: Vector2<Scalar>): Bool
fn intersectsConvex(other: Self): Bool
fn translated(by: Vector2<Scalar>): Self
}
extend<Scalar: Signed> Polygon<Scalar> {
fn doubledArea(): Scalar
}
extend<Scalar: Real> Polygon<Scalar> {
fn area(): Scalar
}A closed chain of corners, the last joined back to the first. The convex tests are the ones to reach for -
containsConvex is a side test per edge and intersectsConvex is the separating axis test - and isConvex is what a
program establishes once instead of per query. contains works for any polygon, including one with a dent, by counting
the crossings of a ray. Fewer than three corners describe no area, and every test answers false for such a polygon.
Plane#
type Plane<Scalar: Real = Float> {
normal: Vector3<Scalar>
distance: Scalar
static fn through(point: Vector3<Scalar>, normal: Vector3<Scalar>): Self
static fn ofPoints(first: Vector3<Scalar>, second: Vector3<Scalar>, third: Vector3<Scalar>): Self
fn signedDistanceTo(point: Vector3<Scalar>): Scalar
fn distanceTo(point: Vector3<Scalar>): Scalar
fn isInFront(point: Vector3<Scalar>): Bool
fn closestPoint(to: Vector3<Scalar>): Vector3<Scalar>
fn mirrored(point: Vector3<Scalar>): Vector3<Scalar>
fn flipped(): Self
}A flat surface in space, as the normal that points away from its front and the distance along that normal.
signedDistanceTo is positive in front and negative behind, and which of the two a program calls "outside" is the
program's own business. through, ofPoints, distanceTo, isInFront, closestPoint, mirrored and flipped.
Related#
- std/linear - the vectors every shape is made of, and
Fixed, the scalar that makes these tests bit-identical everywhere. - std/number -
NumericandReal, the two bounds the shapes layer along.