geometry
stable2D geometry primitives for games and graphics. Compute distances, angles, intersections, polygon areas, convex hulls, and collision tests between points, rectangles, circles, and polygons.
use plugin geometry Plugin snippets can be edited in the Playground, but native capabilities are executed locally with the Zolo CLI.
Functions (26)
- distance Euclidean distance between two points
- angle Angle in radians from point A to point B
- midpoint Midpoint between two points
- lerp Linearly interpolate between two points
- normalize Normalize a 2D vector to unit length
- dot Dot product of two 2D vectors
- cross2d 2D cross product (scalar)
- vector_length Length of a 2D vector
- rotate_point Rotate a point around a centre
- reflect_point Reflect a point through a centre
- point_in_rect Test if point is inside a rectangle
- rects_overlap Test if two axis-aligned rectangles overlap
- circle_contains Test if circle contains a point
- circles_overlap Test if two circles overlap
- point_in_polygon Ray-cast point-in-polygon test
- line_intersection Intersection of two line segments
- polygon_area Area of a polygon (shoelace formula)
- polygon_centroid Centroid of a polygon
- polygon_perimeter Perimeter of a polygon
- convex_hull Convex hull of a point set (Graham scan)
- bounding_box Axis-aligned bounding box of a point set
- triangle_area Area of a triangle from three points
- rect_area Area of a rectangle from width and height
- rect_perimeter Perimeter of a rectangle
- circle_area Area of a circle from radius
- circle_circumference Circumference of a circle from radius
From import to result
- 01Import only what you use
- 02Copy the smallest example
- 03Run it with the Zolo CLI
Overview¶
geometry is a stateless toolkit of 2D geometry primitives aimed at games,
simulations, and graphics. There are no handles or objects to manage: every
function takes plain numbers (coordinates, widths, radii, angles in radians) and
returns either a number, a boolean, or a small {x, y} table — so a point is
just an ordinary map you can build, inspect, and pass around freely. Polygon and
point-set functions accept an array of {x, y} tables and read both "x"/"y"
keys and positional 1/2 entries.
Use it for the building blocks of 2D math: distances and angles, vector operations (dot, cross, normalize, length), point transforms (rotate, reflect, lerp, midpoint), area and perimeter formulas, and the collision and containment tests that drive hit detection — point-in-rect, point-in-polygon, circle and rectangle overlap, line intersection, convex hull, and bounding box.
Common patterns¶
Detect whether a moving entity collides with a circular hazard, using a vector toward it to decide a knockback direction:
use plugin geometry::{circle_contains, angle, normalize}
let hazard_x = 100.0
let hazard_y = 100.0
let radius = 20.0
if circle_contains(hazard_x, hazard_y, radius, 110.0, 105.0) {
let dir = angle(hazard_x, hazard_y, 110.0, 105.0)
let push = normalize(110.0 - hazard_x, 105.0 - hazard_y)
print("hit at angle {dir}, push ({push["x"]}, {push["y"]})")
}Build a polygon, then measure its area, perimeter, and centroid together:
use plugin geometry::{polygon_area, polygon_perimeter, polygon_centroid}
let room = [
#{"x": 0.0, "y": 0.0},
#{"x": 8.0, "y": 0.0},
#{"x": 8.0, "y": 6.0},
#{"x": 0.0, "y": 6.0}
]
let c = polygon_centroid(room)
print("area {polygon_area(room)}, perimeter {polygon_perimeter(room)}")
print("centre at ({c["x"]}, {c["y"]})")Reduce a scattered point cloud to its convex hull and frame it with a bounding box:
use plugin geometry::{convex_hull, bounding_box}
let cloud = [
#{"x": 1.0, "y": 1.0},
#{"x": 9.0, "y": 2.0},
#{"x": 5.0, "y": 5.0},
#{"x": 8.0, "y": 9.0},
#{"x": 2.0, "y": 8.0}
]
let hull = convex_hull(cloud)
let bb = bounding_box(hull)
print("from ({bb["min_x"]},{bb["min_y"]}) to ({bb["max_x"]},{bb["max_y"]})")Euclidean distance between two points
Returns the straight-line distance between two points.
use plugin geometry::{distance}
print(distance(0.0, 0.0, 3.0, 4.0)) // 5.0Use it to range-check one entity against another before reacting:
use plugin geometry::{distance}
let d = distance(2.0, 2.0, 10.0, 8.0)
if d < 5.0 {
print("in range ({d})")
} else {
print("too far ({d})")
}Angle in radians from point A to point B
Returns the angle in radians from point (x1, y1) toward point (x2, y2), measured from the positive X axis. Range is (-PI, PI].
use plugin geometry::{angle}
let a = angle(0.0, 0.0, 1.0, 0.0) // 0.0 (east)
let b = angle(0.0, 0.0, 0.0, 1.0) // ~1.57 (north)
print(a)
print(b)Midpoint between two points
Returns the midpoint {x, y} between two points.
use plugin geometry::{midpoint}
let m = midpoint(0.0, 0.0, 10.0, 4.0)
print(m["x"]) // 5.0
print(m["y"]) // 2.0Linearly interpolate between two points
Linearly interpolates between (x1,y1) and (x2,y2) by factor t. Returns {x, y}. t=0 gives the first point, t=1 gives the second.
use plugin geometry::{lerp}
let p = lerp(0.0, 0.0, 10.0, 10.0, 0.25)
print(p["x"]) // 2.5
print(p["y"]) // 2.5Normalize a 2D vector to unit length
Returns a unit vector {x, y} pointing in the same direction. Returns {0, 0} for zero-length input.
use plugin geometry::{normalize, vector_length}
let v = normalize(3.0, 4.0)
print(vector_length(v["x"], v["y"])) // 1.0Dot product of two 2D vectors
Returns the dot product of two 2D vectors. A result of 0 means the vectors are perpendicular.
use plugin geometry::{dot}
print(dot(1.0, 0.0, 0.0, 1.0)) // 0.0
print(dot(1.0, 0.0, 1.0, 0.0)) // 1.02D cross product (scalar)
Returns the Z component of the 3D cross product of two 2D vectors. Positive means counter-clockwise turn, negative means clockwise.
use plugin geometry::{cross2d}
print(cross2d(1.0, 0.0, 0.0, 1.0)) // 1.0 (CCW)
print(cross2d(1.0, 0.0, 0.0, -1.0)) // -1.0 (CW)Length of a 2D vector
Returns the Euclidean length of a 2D vector.
use plugin geometry::{vector_length}
print(vector_length(3.0, 4.0)) // 5.0Rotate a point around a centre
Rotates point (px, py) around centre (cx, cy) by angle radians. Returns {x, y}.
use plugin geometry::{rotate_point}
let p = rotate_point(1.0, 0.0, 0.0, 0.0, 1.5708) // ~90 degrees
print(p["x"]) // ~0.0
print(p["y"]) // ~1.0Reflect a point through a centre
Reflects point (px, py) through centre (cx, cy). Returns {x, y}.
use plugin geometry::{reflect_point}
let r = reflect_point(1.0, 2.0, 0.0, 0.0)
print(r["x"]) // -1.0
print(r["y"]) // -2.0Test if point is inside a rectangle
Returns true if point (px, py) is inside the axis-aligned rectangle at (rx, ry) with width rw and height rh (inclusive bounds).
use plugin geometry::{point_in_rect}
print(point_in_rect(5.0, 5.0, 0.0, 0.0, 10.0, 10.0)) // true
print(point_in_rect(15.0, 5.0, 0.0, 0.0, 10.0, 10.0)) // falseTest if two axis-aligned rectangles overlap
Returns true if two axis-aligned rectangles overlap. Touching edges count as overlapping.
use plugin geometry::{rects_overlap}
print(rects_overlap(0.0, 0.0, 5.0, 5.0, 3.0, 3.0, 5.0, 5.0)) // true
print(rects_overlap(0.0, 0.0, 2.0, 2.0, 5.0, 5.0, 2.0, 2.0)) // falseTest if circle contains a point
Returns true if point (px, py) lies inside or on the circle centred at (cx, cy) with radius r.
use plugin geometry::{circle_contains}
print(circle_contains(0.0, 0.0, 5.0, 3.0, 4.0)) // true (dist=5)
print(circle_contains(0.0, 0.0, 5.0, 4.0, 4.0)) // false (dist>5)Test if two circles overlap
Returns true if two circles overlap or touch.
use plugin geometry::{circles_overlap}
print(circles_overlap(0.0, 0.0, 3.0, 5.0, 0.0, 3.0)) // true (dist=5, sum=6)
print(circles_overlap(0.0, 0.0, 1.0, 5.0, 0.0, 1.0)) // false (dist=5, sum=2)Ray-cast point-in-polygon test
Ray-casting algorithm: returns true if (px, py) is inside the polygon. polygon is a table of {x, y} point tables. Requires at least 3 vertices.
use plugin geometry::{point_in_polygon}
let poly = [
#{"x": 0.0, "y": 0.0},
#{"x": 10.0, "y": 0.0},
#{"x": 10.0, "y": 10.0},
#{"x": 0.0, "y": 10.0}
]
print(point_in_polygon(5.0, 5.0, poly)) // true
print(point_in_polygon(15.0, 5.0, poly)) // falseIntersection of two line segments
Returns the intersection point {x, y} of segments (x1,y1)-(x2,y2) and (x3,y3)-(x4,y4), or nil if they do not intersect within their lengths.
use plugin geometry::{line_intersection}
let p = line_intersection(0.0, 0.0, 10.0, 10.0, 0.0, 10.0, 10.0, 0.0)
print(p["x"]) // 5.0
print(p["y"]) // 5.0
let none = line_intersection(0.0, 0.0, 1.0, 0.0, 5.0, 0.0, 6.0, 0.0)
print(none) // nil (parallel, no overlap)Because a miss returns nil, you can branch directly on the result:
use plugin geometry::{line_intersection}
let hit = line_intersection(0.0, 5.0, 10.0, 5.0, 4.0, 0.0, 4.0, 10.0)
if hit != nil {
print("crosses at ({hit["x"]}, {hit["y"]})") // (4.0, 5.0)
} else {
print("no crossing")
}Area of a polygon (shoelace formula)
Returns the area of a polygon using the shoelace formula. points is a table of {x, y} tables. Requires at least 3 vertices.
use plugin geometry::{polygon_area}
let square = [
#{"x": 0.0, "y": 0.0},
#{"x": 4.0, "y": 0.0},
#{"x": 4.0, "y": 4.0},
#{"x": 0.0, "y": 4.0}
]
print(polygon_area(square)) // 16.0Centroid of a polygon
Returns the centroid {x, y} of a polygon. Falls back to the arithmetic mean of vertices for degenerate (zero-area) polygons.
use plugin geometry::{polygon_centroid}
let tri = [
#{"x": 0.0, "y": 0.0},
#{"x": 6.0, "y": 0.0},
#{"x": 3.0, "y": 6.0}
]
let c = polygon_centroid(tri)
print(c["x"]) // 3.0Perimeter of a polygon
Returns the total perimeter length of a closed polygon (last vertex connects back to first).
use plugin geometry::{polygon_perimeter}
let square = [
#{"x": 0.0, "y": 0.0},
#{"x": 5.0, "y": 0.0},
#{"x": 5.0, "y": 5.0},
#{"x": 0.0, "y": 5.0}
]
print(polygon_perimeter(square)) // 20.0Convex hull of a point set (Graham scan)
Returns the convex hull of a point set as an ordered table of {x, y} vertices using the Graham scan algorithm.
use plugin geometry::{convex_hull, polygon_area}
let pts = [
#{"x": 0.0, "y": 0.0},
#{"x": 5.0, "y": 0.0},
#{"x": 2.5, "y": 2.5},
#{"x": 5.0, "y": 5.0},
#{"x": 0.0, "y": 5.0}
]
let hull = convex_hull(pts)
print(polygon_area(hull))Axis-aligned bounding box of a point set
Returns {min_x, min_y, max_x, max_y} for the axis-aligned bounding box of a point set.
use plugin geometry::{bounding_box}
let pts = [
#{"x": 1.0, "y": 3.0},
#{"x": 7.0, "y": 2.0},
#{"x": 4.0, "y": 8.0}
]
let bb = bounding_box(pts)
print("{bb["min_x"]},{bb["min_y"]} to {bb["max_x"]},{bb["max_y"]}")Area of a triangle from three points
Returns the area of a triangle from three coordinate pairs using the cross-product formula.
use plugin geometry::{triangle_area}
print(triangle_area(0.0, 0.0, 4.0, 0.0, 0.0, 3.0)) // 6.0Area of a rectangle from width and height
Returns the absolute area |w * h| of a rectangle from its width and height.
use plugin geometry::{rect_area}
print(rect_area(6.0, 4.0)) // 24.0
print(rect_area(-3.0, 5.0)) // 15.0 (magnitudes)Perimeter of a rectangle
Returns the perimeter 2 * (|w| + |h|) of a rectangle from its width and height.
use plugin geometry::{rect_perimeter}
print(rect_perimeter(6.0, 4.0)) // 20.0Area of a circle from radius
Returns the area PI * r^2 of a circle from its radius.
use plugin geometry::{circle_area}
print(circle_area(5.0)) // ~78.54
print(circle_area(1.0)) // ~3.14159Circumference of a circle from radius
Returns the circumference 2 * PI * r of a circle from its radius.
use plugin geometry::{circle_circumference}
print(circle_circumference(5.0)) // ~31.42