#ky678/renky/system/graph — coordinate systems for a renderer (MoonBit, zero dependencies)

    Only the coordinate-system layer of a renderer: every space, every transform between them, and the conventions that glue them together. No third-party packages (only moonbitlang/core).

    #Conventions (one place, no surprises)

    ItemChoice
    Handedness / axesRight-handed, Y-up; camera looks down -Z, +X right, +Y up
    Scalars / anglesDouble, radians (Float only for GPU upload helpers)
    Matricescolumn vectors v' = M v, fields mRC, Mat4::to_col_major is GL/WebGPU/Vulkan layout
    QuaternionHamilton, a * b applies b first
    Eulerall 12 orders, intrinsic R = R_A(a) R_B(b) R_C(c), canonical branch on extraction
    Clip / NDC depthDepthMode: NegOneToOne (OpenGL), ZeroToOne (D3D/Vulkan/Metal/WebGPU), ReversedZ; finite or infinite far plane
    Viewport / pixelsorigin top-left, y down, pixel centres at +0.5
    Cube mapfaces +X,-X,+Y,-Y,+Z,-Z (OpenGL table), v down; equirect u = 0.5 + atan2(x,-z)/2π
    Sphericalazimuth 0 = -Z, +π/2 = -X; elevation from the horizon
    GeodesyWGS-84; ECEF → local ENU/NED → render frame (east=+X, up=+Y, north=-Z)

    #Spaces and what converts between them

    model ──Rigid/Mat4 TRS──▶ world ──view (Camera)──▶ view ──Projection──▶ clip ──÷w──▶ NDC ──Viewport──▶ screen ▲ ▲ │ └─ tangent / TBN └─ convention (Z-up, Y-down, left-handed … ⇄ native) ◀── pick_ray / unproject ┘ geodetic (lat,lon,h) ⇄ ECEF ⇄ ENU/NED ⇄ render world Web-Mercator ⇄ tile / pixel / quadkey direction ⇄ cube face (u,v) ⇄ equirect ⇄ octahedral texel ⇄ uv, wrap modes, bilinear footprint cartesian ⇄ spherical / cylindrical / polar camera-relative (floating-origin) view matrix

    Files: vec*.mbt quat.mbt mat3.mbt mat4.mbt euler.mbt transform.mbt (algebra) · projection.mbt viewport.mbt camera.mbt geometry.mbt (pipeline, rays, planes, frusta, AABB) · geodetic.mbt mercator.mbt curvilinear.mbt (earth & curvilinear systems) · texture.mbt tangent.mbt (UV, cube, octahedral, TBN, barycentrics) · convention.mbt (other engines' frames).

    #Run

    From the module root (renky/):

    moon test -p ky678/renky/system/graph # 75 tests; also --target wasm | js | native python3 system/graph/tools/mutation_check.py # injects 71 bugs; every one must be caught moon run system/graph/cmd/demo --target native > demo.ppm # software-rendered scene (docs/demo.png) moon run system/graph/cmd/benchmark --target native --release # micro-benchmarks

    Use it: moon add ky678/renky, then import { "ky678/renky/system/graph" } and refer to it as @graph.

    #How the tests avoid being decorative

    • External oracles (tools/gen_golden.py → golden_data_test.mbt): numpy textbook pipelines (gluPerspective, XMMatrixPerspectiveFovRH, glOrtho, reversed-Z, D3D viewport), scipy (Rotation, Euler, rotvec), PROJ/pyproj (EPSG:4979→4978, EPSG:4326→3857, topocentric ENU), PROJ Geod for metres/degree, OSM tile formulas, Bing quadkey example.
    • Independent re-derivations: a reference triangle rasteriser is compared pixel-by-pixel with analytic ray casting through pick_ray; frustum culling vs clip-space inequalities; closed-form inverses vs general inverse.
    • Mutation testing: tools/mutation_check.py (71 mutants — sign flips, swapped operands, dropped terms) — all killed. It found and led to fixing three real gaps (no tests for AABB transform, ray-plane, frustum near/far).

    Aabb

    pub(all) struct Aabb {
    min : Vec3
    max : Vec3
    } derive(Eq,
    Debug
    )

    Axis-aligned bounding box.

    Aabb::center

    fn Aabb::center(self : Aabb) -> Vec3

    Aabb::contains_point

    fn Aabb::contains_point(self : Aabb, p : Vec3) -> Bool

    Aabb::corner

    fn Aabb::corner(self : Aabb, i : Int) -> Vec3

    Corner i in 0..7 (bit0 -> x, bit1 -> y, bit2 -> z select max).

    Aabb::expand

    fn Aabb::expand(self : Aabb, p : Vec3) -> Aabb

    Aabb::from_center_half

    fn Aabb::from_center_half(center : Vec3, half : Vec3) -> Aabb

    Aabb::half_extents

    fn Aabb::half_extents(self : Aabb) -> Vec3

    Aabb::new

    fn Aabb::new(min : Vec3, max : Vec3) -> Aabb

    Aabb::transform

    fn Aabb::transform(self : Aabb, m : Mat4) -> Aabb

    Exact world-space AABB of this box under an affine matrix (Arvo): cost of one 3x3 multiply, no corner enumeration.

    Aabb::union

    fn Aabb::union(self : Aabb, o : Aabb) -> Aabb

    Axis

    pub(all) enum Axis {
    PosX
    NegX
    PosY
    NegY
    PosZ
    NegZ
    } derive(Eq,
    Debug
    )

    Axis::to_vec3

    fn Axis::to_vec3(self : Axis) -> Vec3

    Bilinear

    pub(all) struct Bilinear {
    x0 : Int
    y0 : Int
    x1 : Int
    y1 : Int
    fx : Double
    fy : Double
    } derive(Eq,
    Debug
    )

    The 2x2 texel neighbourhood and weights of a bilinear fetch at uv. Result texel = (1-fx)(1-fy) T[x0,y0] + fx(1-fy) T[x1,y0] + (1-fx)fy T[x0,y1] + fx fy T[x1,y1].

    Camera

    pub(all) struct Camera {
    pose : Rigid
    projection : Projection
    viewport : Viewport
    } derive(Eq,
    Debug
    )

    Camera::eye

    fn Camera::eye(self : Camera) -> Vec3

    Camera::forward

    fn Camera::forward(self : Camera) -> Vec3

    Camera::frustum

    fn Camera::frustum(self : Camera) -> Frustum

    Camera::look_at

    fn Camera::look_at(eye : Vec3, target : Vec3, up : Vec3, projection : Projection, viewport : Viewport) -> Camera

    Camera::new

    fn Camera::new(pose : Rigid, projection : Projection, viewport : Viewport) -> Camera

    Camera::orbit

    fn Camera::orbit(target : Vec3, distance : Double, yaw : Double, pitch : Double, projection : Projection, viewport : Viewport) -> Camera

    Orbit / FPS style: yaw, pitch give the viewing direction as a Spherical direction (yaw 0, pitch 0 looks down -Z; +yaw turns left about +Y; +pitch looks up). The eye sits distance behind target along that direction.

    Camera::pick_ray

    fn Camera::pick_ray(self : Camera, sx : Double, sy : Double) -> Ray

    World-space picking ray through a window position (unit direction).

    Camera::proj_matrix

    fn Camera::proj_matrix(self : Camera) -> Mat4

    Camera::resize

    fn Camera::resize(self : Camera, width : Double, height : Double) -> Camera

    Change the viewport and keep the picture undistorted by updating the projection aspect.

    Camera::right

    fn Camera::right(self : Camera) -> Vec3

    Camera::screen_to_world

    fn Camera::screen_to_world(self : Camera, sx : Double, sy : Double, depth : Double) -> Vec3

    Window position + window depth (as read back from the depth buffer) -> world.

    Camera::screen_to_world_at_distance

    fn Camera::screen_to_world_at_distance(self : Camera, sx : Double, sy : Double, distance : Double) -> Vec3

    World point under window position (sx, sy) at distance in front of the camera plane.

    Camera::up

    fn Camera::up(self : Camera) -> Vec3

    Camera::view_matrix

    fn Camera::view_matrix(self : Camera) -> Mat4

    World -> view matrix.

    Camera::view_proj

    fn Camera::view_proj(self : Camera) -> Mat4

    World -> clip matrix.

    Camera::view_proj_rte

    fn Camera::view_proj_rte(self : Camera) -> Mat4

    World -> clip with the eye moved to the origin ("camera-relative / floating origin" rendering). Pair with model matrices whose translation has eye subtracted in DOUBLE precision (Mat4::relative_to) before the single rounding to float: this removes the jitter of huge world coordinates.

    Camera::view_to_world

    fn Camera::view_to_world(self : Camera, p : Vec3) -> Vec3

    Camera::world_to_clip

    fn Camera::world_to_clip(self : Camera, p : Vec3) -> Vec4

    Camera::world_to_ndc

    fn Camera::world_to_ndc(self : Camera, p : Vec3) -> Vec3?

    World -> NDC. None if behind the eye plane (perspective).

    Camera::world_to_screen

    fn Camera::world_to_screen(self : Camera, p : Vec3) -> Vec3?

    World -> window: (x px, y px, window depth). None if behind the camera.

    Camera::world_to_view

    fn Camera::world_to_view(self : Camera, p : Vec3) -> Vec3

    Containment

    pub(all) enum Containment {
    Outside
    Intersecting
    Inside
    } derive(Eq,
    Debug
    )

    Convention

    pub(all) struct Convention {
    right : Axis
    up : Axis
    forward : Axis
    } derive(Eq,
    Debug
    )

    Which world axes mean "right", "up" and "forward" (the direction a camera / character looks).

    Convention::basis_change

    fn Convention::basis_change(self : Convention, to : Convention) -> Mat3

    Signed permutation matrix M such that v_to = M * v_from: right stays right, up stays up, forward stays forward. det(M) = -1 when the two conventions differ in handedness (a mirror); remember to flip triangle winding in that case.

    Convention::convert_mat4

    fn Convention::convert_mat4(self : Convention, to : Convention, t : Mat4) -> Mat4

    Re-express a 4x4 transform: M4 * T * M4^-1 (M4 is its own inverse transpose; orthogonal).

    Convention::convert_quat

    fn Convention::convert_quat(self : Convention, to : Convention, q : Quat) -> Quat

    Re-express an orientation: the same physical rotation, described in the other axes. Axis -> M * axis * det(M), angle unchanged (a mirror reverses the sense of rotation).

    Convention::convert_vec3

    fn Convention::convert_vec3(self : Convention, to : Convention, v : Vec3) -> Vec3

    Re-express a point / direction given in self convention in to convention.

    Convention::is_right_handed

    fn Convention::is_right_handed(self : Convention) -> Bool

    Handedness of the world: right-handed when right x up points BACKWARD (= -forward).

    Convention::is_valid

    fn Convention::is_valid(self : Convention) -> Bool

    Valid when right/up/forward use three different coordinate axes.

    Convention::native

    fn Convention::native() -> Convention

    This library / OpenGL / glTF / three.js / Maya: right +X, up +Y, forward -Z (right-handed).

    Convention::ros

    fn Convention::ros() -> Convention

    ROS REP-103 / Source engine style: forward +X, left +Y (right -Y), up +Z (right-handed).

    Convention::unreal

    fn Convention::unreal() -> Convention

    Unreal Engine: forward +X, right +Y, up +Z (left-handed).

    Convention::y_up_left_handed

    fn Convention::y_up_left_handed() -> Convention

    Unity, Direct3D-style left-handed world: right +X, up +Y, forward +Z.

    Convention::z_up_right_handed

    fn Convention::z_up_right_handed() -> Convention

    Blender, 3ds Max, SketchUp, most CAD: right +X, up +Z, forward +Y (right-handed).

    CubeCoord

    pub(all) struct CubeCoord {
    face : Int
    u : Double
    v : Double
    } derive(Eq,
    Debug
    )

    Face + coordinates inside that face image (u right, v down, both in [0, 1]).

    Cylindrical

    pub(all) struct Cylindrical {
    radius : Double
    azimuth : Double
    height : Double
    } derive(Eq,
    Debug
    )

    Cylindrical coordinates about the +Y axis.

    Cylindrical::from_cartesian

    fn Cylindrical::from_cartesian(p : Vec3) -> Cylindrical

    Cylindrical::new

    fn Cylindrical::new(radius : Double, azimuth : Double, height : Double) -> Cylindrical

    Cylindrical::to_cartesian

    fn Cylindrical::to_cartesian(self : Cylindrical) -> Vec3

    DepthMode

    pub(all) enum DepthMode {
    ZeroToOne
    NegOneToOne
    ReversedZ
    } derive(Eq,
    Debug
    )

    How NDC depth is laid out.
    • ZeroToOne: near -> 0, far -> 1 (Vulkan / D3D / Metal / WebGPU native)
    • NegOneToOne: near -> -1, far -> 1 (OpenGL default)
    • ReversedZ: near -> 1, far -> 0 (best float precision; use with a GREATER depth test and clear depth to 0). With an infinite far plane this is the recommended modern setup.

    DepthMode::from_unit

    fn DepthMode::from_unit(self : DepthMode, unit_z : Double) -> Double

    Inverse of to_unit.

    DepthMode::to_unit

    fn DepthMode::to_unit(self : DepthMode, ndc_z : Double) -> Double

    Convert an NDC z in any mode to the [0, 1] "window" range (0 = viewport.min_depth).

    Ellipsoid

    pub(all) struct Ellipsoid {
    a : Double
    f : Double
    } derive(Eq,
    Debug
    )

    Ellipsoid::b

    fn Ellipsoid::b(self : Ellipsoid) -> Double

    semi-minor (polar) radius

    Ellipsoid::e2

    fn Ellipsoid::e2(self : Ellipsoid) -> Double

    first eccentricity squared

    Ellipsoid::from_ecef

    fn Ellipsoid::from_ecef(self : Ellipsoid, p : Vec3) -> Geodetic

    ECEF -> Geodetic (Bowring start + fixed-point iteration on latitude; ~1e-6 m or better for any point from deep underground to beyond geostationary orbit; poles and the equator are handled). The Earth's exact centre has no unique latitude: (0, 0, -a) is returned.

    Ellipsoid::geocentric_latitude

    fn Ellipsoid::geocentric_latitude(self : Ellipsoid, lat : Double) -> Double

    Geocentric latitude (angle from the equatorial plane to the position vector) for a point ON the ellipsoid surface at geodetic latitude lat.

    Ellipsoid::geodetic_latitude

    fn Ellipsoid::geodetic_latitude(self : Ellipsoid, geocentric : Double) -> Double

    Inverse of geocentric_latitude.

    Ellipsoid::grs80

    fn Ellipsoid::grs80() -> Ellipsoid

    GRS 80 (NAD83, ETRS89, CGCS2000 differs slightly in f).

    Ellipsoid::meridional_radius

    fn Ellipsoid::meridional_radius(self : Ellipsoid, lat : Double) -> Double

    Meridional radius of curvature M(lat) (north-south): metres per radian of latitude.

    Ellipsoid::prime_vertical_radius

    fn Ellipsoid::prime_vertical_radius(self : Ellipsoid, lat : Double) -> Double

    Radius of curvature in the prime vertical, N(lat): distance from the surface to the polar axis along the normal.

    Ellipsoid::sphere

    fn Ellipsoid::sphere(radius : Double) -> Ellipsoid

    A perfect sphere (f = 0) -- the model used by simple globes and Web Mercator.

    Ellipsoid::to_ecef

    fn Ellipsoid::to_ecef(self : Ellipsoid, g : Geodetic) -> Vec3

    Geodetic -> ECEF (closed form).

    Ellipsoid::wgs84

    fn Ellipsoid::wgs84() -> Ellipsoid

    WGS 84 (GPS, Web maps, glTF/3D-Tiles ECEF).

    EulerOrder

    pub(all) enum EulerOrder {
    XYZ
    XZY
    YXZ
    YZX
    ZXY
    ZYX
    XYX
    XZX
    YXY
    YZY
    ZXZ
    ZYZ
    } derive(Eq,
    Debug
    )

    EulerOrder::axes

    fn EulerOrder::axes(self : EulerOrder) -> (Int, Int, Int)

    Axis indices (0=X, 1=Y, 2=Z) of the three rotations in order.

    EulerOrder::is_proper

    fn EulerOrder::is_proper(self : EulerOrder) -> Bool

    Frame

    pub(all) struct Frame {
    tangent : Vec3
    bitangent : Vec3
    normal : Vec3
    } derive(Eq,
    Debug
    )

    Orthonormal frame: tangent (T), bitangent (B), normal (N). Right-handed when cross(T, B) == N, left-handed (mirrored UVs) when cross(T, B) == -N.

    Frame::from_normal

    fn Frame::from_normal(n : Vec3) -> Frame

    Right-handed frame whose normal is n (normalised internally); T, B arbitrary but stable.

    Frame::handedness

    fn Frame::handedness(self : Frame) -> Double

    +1 for a right-handed frame, -1 for mirrored (the glTF tangent.w value).

    Frame::to_local

    fn Frame::to_local(self : Frame, v : Vec3) -> Vec3

    Parent space -> local (T, B, N) coordinates (the frame is orthonormal: transpose = inverse).

    Frame::to_mat3

    fn Frame::to_mat3(self : Frame) -> Mat3

    Matrix with columns T, B, N (local -> parent).

    Frame::to_world

    fn Frame::to_world(self : Frame, loc : Vec3) -> Vec3

    Local (T, B, N) coordinates -> parent space.

    Frustum

    pub(all) struct Frustum {
    left : Plane
    right : Plane
    bottom : Plane
    top : Plane
    near : Plane
    far : Plane
    } derive(Eq,
    Debug
    )

    The six planes of a view frustum with normals pointing INSIDE.

    Frustum::classify_aabb

    fn Frustum::classify_aabb(self : Frustum, b : Aabb) -> Containment

    Box classification. Outside is exact-safe (box is certainly invisible); Inside is certain; Intersecting may include a few boxes that are actually just outside a corner.

    Frustum::contains_point

    fn Frustum::contains_point(self : Frustum, p : Vec3) -> Bool

    Frustum::from_view_proj

    fn Frustum::from_view_proj(m : Mat4, depth : DepthMode) -> Frustum

    Extract frustum planes from clip = view_proj * world; handles every DepthMode. The planes are expressed in the space that view_proj takes as INPUT (world space when view_proj = P * V; model space when P * V * M).

    Frustum::intersects_sphere

    fn Frustum::intersects_sphere(self : Frustum, c : Vec3, r : Double) -> Bool

    Conservative sphere test (never a false negative; rare false positives near frustum edges).

    Geodetic

    pub(all) struct Geodetic {
    lat : Double
    lon : Double
    height : Double
    } derive(Eq,
    Debug
    )

    Geodetic position: latitude / longitude in RADIANS, height above the ellipsoid in metres.

    Geodetic::east_ecef

    fn Geodetic::east_ecef(self : Geodetic) -> Vec3

    Geodetic::from_degrees

    fn Geodetic::from_degrees(lat_deg : Double, lon_deg : Double, height : Double) -> Geodetic

    Geodetic::new

    fn Geodetic::new(lat : Double, lon : Double, height : Double) -> Geodetic

    Geodetic::north_ecef

    fn Geodetic::north_ecef(self : Geodetic) -> Vec3

    Geodetic::up_ecef

    fn Geodetic::up_ecef(self : Geodetic) -> Vec3

    Unit ellipsoid normal ("up") in ECEF at a geodetic position (independent of height).

    LocalFrame

    pub(all) struct LocalFrame {
    origin : Vec3
    axes : Mat3
    } derive(Eq,
    Debug
    )

    A local Cartesian frame anchored at origin (ECEF metres) whose orthonormal axes (columns of axes, expressed in ECEF) define the local coordinates.

    LocalFrame::dir_to_local

    fn LocalFrame::dir_to_local(self : LocalFrame, ecef_dir : Vec3) -> Vec3

    Rotate a free vector (e.g. velocity, direction) from ECEF to local axes.

    LocalFrame::ecef_to_render_enu

    fn LocalFrame::ecef_to_render_enu(self : LocalFrame) -> Rigid

    Rigid transform ECEF -> RENDER space for a scene centred on an ENU frame: east -> +X, up -> +Y, north -> -Z (the library's Y-up, -Z-forward world). Use the returned pose's to_mat4() as a view/world matrix for geospatial content, or inverse() to go back.

    LocalFrame::enu

    East-North-Up tangent frame at geodetic position g (local x = east, y = north, z = up).

    LocalFrame::ned

    North-East-Down tangent frame (aerospace convention: x = north, y = east, z = down).

    LocalFrame::to_ecef

    fn LocalFrame::to_ecef(self : LocalFrame, loc : Vec3) -> Vec3

    Local frame coordinates -> ECEF.

    LocalFrame::to_local

    fn LocalFrame::to_local(self : LocalFrame, ecef : Vec3) -> Vec3

    ECEF -> local frame coordinates.

    LonLat

    pub(all) struct LonLat {
    lon : Double
    lat : Double
    } derive(Eq,
    Debug
    )

    LonLat::from_degrees

    fn LonLat::from_degrees(lon_deg : Double, lat_deg : Double) -> LonLat

    LonLat::new

    fn LonLat::new(lon : Double, lat : Double) -> LonLat

    Mat3

    pub(all) struct Mat3 {
    m00 : Double
    m01 : Double
    m02 : Double
    m10 : Double
    m11 : Double
    m12 : Double
    m20 : Double
    m21 : Double
    m22 : Double
    } derive(Eq,
    Debug
    )

    impl Mul for Mat3

    Mat3::approx_eq

    fn Mat3::approx_eq(self : Mat3, o : Mat3, eps : Double) -> Bool

    Mat3::col

    fn Mat3::col(self : Mat3, i : Int) -> Vec3

    Mat3::determinant

    fn Mat3::determinant(self : Mat3) -> Double

    Mat3::diagonal

    fn Mat3::diagonal(x : Double, y : Double, z : Double) -> Mat3

    Mat3::from_axis_angle

    fn Mat3::from_axis_angle(axis : Vec3, angle : Double) -> Mat3

    Rodrigues rotation about axis (normalised internally).

    Mat3::from_cols

    fn Mat3::from_cols(c0 : Vec3, c1 : Vec3, c2 : Vec3) -> Mat3

    Mat3::from_euler

    fn Mat3::from_euler(order : EulerOrder, a : Double, b : Double, c : Double) -> Mat3

    Matrix for R_A(a) * R_B(b) * R_C(c).

    Mat3::from_quat

    fn Mat3::from_quat(q : Quat) -> Mat3

    Mat3::from_rows

    fn Mat3::from_rows(r0 : Vec3, r1 : Vec3, r2 : Vec3) -> Mat3

    Mat3::from_trs2d

    fn Mat3::from_trs2d(t : Vec2, angle : Double, s : Vec2) -> Mat3

    Translate * Rotate * Scale (scale applied first).

    Mat3::identity

    fn Mat3::identity() -> Mat3

    Mat3::inverse

    fn Mat3::inverse(self : Mat3) -> Mat3?

    General inverse; None when singular (det == 0) or not finite.

    Mat3::look_rotation

    fn Mat3::look_rotation(forward : Vec3, up : Vec3) -> Mat3

    Orientation matrix (columns = right, up, back) whose local -Z is forward and whose local +Y is closest to up. Degenerate inputs fall back to a valid orthonormal basis.

    Mat3::mul_dir2

    fn Mat3::mul_dir2(self : Mat3, d : Vec2) -> Vec2

    Direction transform (ignores translation).

    Mat3::mul_point2

    fn Mat3::mul_point2(self : Mat3, p : Vec2) -> Vec2

    Affine point transform (assumes last row = 0 0 1).

    Mat3::mul_vec3

    fn Mat3::mul_vec3(self : Mat3, v : Vec3) -> Vec3

    Mat3::normal_matrix

    fn Mat3::normal_matrix(self : Mat3) -> Mat3?

    (M^-1)^T, the matrix that transforms surface normals. None when singular.

    Mat3::project_point2

    fn Mat3::project_point2(self : Mat3, p : Vec2) -> Vec2?

    Projective (homography) point transform with divide by w. None if w == 0.

    Mat3::rotation2d

    fn Mat3::rotation2d(angle : Double) -> Mat3

    Counter-clockwise rotation about the origin (y-up frame).

    Mat3::rotation_x

    fn Mat3::rotation_x(a : Double) -> Mat3

    Mat3::rotation_y

    fn Mat3::rotation_y(a : Double) -> Mat3

    Mat3::rotation_z

    fn Mat3::rotation_z(a : Double) -> Mat3

    Mat3::row

    fn Mat3::row(self : Mat3, i : Int) -> Vec3

    Mat3::scaling2d

    fn Mat3::scaling2d(sx : Double, sy : Double) -> Mat3

    Mat3::to_col_major

    fn Mat3::to_col_major(self : Mat3) -> FixedArray[Double]

    Column-major 9 values (GLSL mat3 / WGSL mat3x3 memory order, tightly packed).

    Mat3::to_euler

    fn Mat3::to_euler(self : Mat3, order : EulerOrder) -> (Double, Double, Double)

    Recover (a, b, c) such that Mat3::from_euler(order, a, b, c) reproduces m. Ranges: Tait-Bryan: a, c in [-PI, PI], b in [-PI/2, PI/2]; proper: a, c in [-PI, PI], b in [0, PI] (a ~ in (-PI, PI] up to the sign convention). At gimbal lock the decomposition is not unique; one valid solution is returned (the matrix still round-trips).

    Mat3::translation2d

    fn Mat3::translation2d(tx : Double, ty : Double) -> Mat3

    Mat3::transpose

    fn Mat3::transpose(self : Mat3) -> Mat3

    Mat4

    pub(all) struct Mat4 {
    m00 : Double
    m01 : Double
    m02 : Double
    m03 : Double
    m10 : Double
    m11 : Double
    m12 : Double
    m13 : Double
    m20 : Double
    m21 : Double
    m22 : Double
    m23 : Double
    m30 : Double
    m31 : Double
    m32 : Double
    m33 : Double
    } derive(Eq,
    Debug
    )

    impl Mul for Mat4

    Mat4::affine_inverse

    fn Mat4::affine_inverse(self : Mat4) -> Mat4?

    Inverse of an affine matrix (last row 0 0 0 1) via the 3x3 block: ~3x cheaper than inverse. None if the 3x3 block is singular. The last row is NOT checked.

    Mat4::approx_eq

    fn Mat4::approx_eq(self : Mat4, o : Mat4, eps : Double) -> Bool

    Mat4::col

    fn Mat4::col(self : Mat4, i : Int) -> Vec4

    Mat4::decompose

    fn Mat4::decompose(self : Mat4) -> Transform?

    Split an affine TRS matrix into (translation, rotation, scale). A negative determinant (mirroring) is reported as a negative X scale. Returns None for projective matrices, zero scale, or when shear is present (columns not mutually orthogonal).

    Mat4::determinant

    fn Mat4::determinant(self : Mat4) -> Double

    Mat4::from_col_major

    fn Mat4::from_col_major(a : FixedArray[Double]) -> Mat4

    Mat4::from_cols

    fn Mat4::from_cols(c0 : Vec4, c1 : Vec4, c2 : Vec4, c3 : Vec4) -> Mat4

    Mat4::from_mat3

    fn Mat4::from_mat3(r : Mat3) -> Mat4

    Mat4::from_trs

    fn Mat4::from_trs(t : Vec3, r : Quat, s : Vec3) -> Mat4

    Translate * Rotate * Scale (scale applied first). The standard model-matrix recipe.

    Mat4::get

    fn Mat4::get(self : Mat4, r : Int, c : Int) -> Double

    Element (row, col).

    Mat4::identity

    fn Mat4::identity() -> Mat4

    Mat4::inverse

    fn Mat4::inverse(self : Mat4) -> Mat4?

    General 4x4 inverse (Laplace expansion on 2x2 minors). None if singular / not finite. Prefer affine_inverse / rigid_inverse when the matrix structure is known.

    Mat4::is_affine

    fn Mat4::is_affine(self : Mat4) -> Bool

    True when the last row is exactly (0, 0, 0, 1).

    Mat4::look_at

    fn Mat4::look_at(eye : Vec3, target : Vec3, up : Vec3) -> Mat4

    View matrix (world -> view) of an eye at eye looking at target, up = approximate up. Right-handed, camera looks down -Z, +Y up, +X right.

    Mat4::mul_dir

    fn Mat4::mul_dir(self : Mat4, d : Vec3) -> Vec3

    Transform a direction (w = 0): translation ignored. For normals use normal_matrix.

    Mat4::mul_point

    fn Mat4::mul_point(self : Mat4, p : Vec3) -> Vec3

    Transform a point assuming an AFFINE matrix (last row 0 0 0 1): w stays 1, no divide. Fastest path for model/view matrices.

    Mat4::mul_vec4

    fn Mat4::mul_vec4(self : Mat4, v : Vec4) -> Vec4

    Mat4::normal_matrix

    fn Mat4::normal_matrix(self : Mat4) -> Mat3?

    Matrix for transforming surface normals of a model matrix: inverse-transpose of the 3x3 block. Correct under non-uniform scale and mirroring. None if singular. Result is NOT normalised; normalise the transformed normal.

    Mat4::project_point

    fn Mat4::project_point(self : Mat4, p : Vec3) -> Vec3?

    Full homogeneous transform of a point (w = 1) followed by the perspective divide. None if the resulting w == 0. NOTE: points behind the eye (w < 0) are returned mirrored -- use Camera::world_to_ndc for visibility-aware projection.

    Mat4::relative_to

    fn Mat4::relative_to(self : Mat4, origin : Vec3) -> Mat4

    Model matrix expressed relative to origin (translation reduced in double precision). Equivalent to translation(-origin) * m but without a full matrix multiply.

    Mat4::rigid_inverse

    fn Mat4::rigid_inverse(self : Mat4) -> Mat4

    Inverse of a rigid transform (rotation + translation only): R^T, -R^T t. Cheapest and exact; the caller guarantees orthonormality (e.g. camera pose, bone transforms).

    Mat4::rotation

    fn Mat4::rotation(q : Quat) -> Mat4

    Mat4::row

    fn Mat4::row(self : Mat4, i : Int) -> Vec4

    Mat4::scaling

    fn Mat4::scaling(s : Vec3) -> Mat4

    Mat4::to_col_major

    fn Mat4::to_col_major(self : Mat4) -> FixedArray[Double]

    16 values in column-major order (GLSL / WGSL / Metal / Vulkan upload order).

    Mat4::to_col_major_f32

    fn Mat4::to_col_major_f32(self : Mat4) -> FixedArray[Float]

    Column-major single-precision values for GPU upload.

    Mat4::to_mat3

    fn Mat4::to_mat3(self : Mat4) -> Mat3

    Upper-left 3x3 block (rotation * scale for an affine matrix).

    Mat4::transform_points

    fn Mat4::transform_points(self : Mat4, src : FixedArray[Double], dst : FixedArray[Double], count : Int) -> Unit

    Transform count points stored as packed xyz triples in src (affine matrix, w = 1) writing to dst (may alias src). No per-point allocation.

    Mat4::transform_points_f32

    fn Mat4::transform_points_f32(self : Mat4, src : FixedArray[Float], dst : FixedArray[Float], count : Int) -> Unit

    Single-precision variant of transform_points, math done in double then rounded once.

    Mat4::translation

    fn Mat4::translation(t : Vec3) -> Mat4

    Mat4::translation_part

    fn Mat4::translation_part(self : Mat4) -> Vec3

    Mat4::transpose

    fn Mat4::transpose(self : Mat4) -> Mat4

    Plane

    pub(all) struct Plane {
    normal : Vec3
    d : Double
    } derive(Eq,
    Debug
    )

    Plane dot(normal, p) + d = 0; normal should be unit for signed_distance to be metric.

    Plane::from_point_normal

    fn Plane::from_point_normal(point : Vec3, normal : Vec3) -> Plane

    Plane::from_points

    fn Plane::from_points(a : Vec3, b : Vec3, c : Vec3) -> Plane

    Plane through three points (normal = (b-a) x (c-a), counter-clockwise => facing viewer).

    Plane::signed_distance

    fn Plane::signed_distance(self : Plane, p : Vec3) -> Double

    Polar

    pub(all) struct Polar {
    radius : Double
    angle : Double
    } derive(Eq,
    Debug
    )

    2D polar coordinates: angle counter-clockwise from +X (y-up frame).

    Polar::from_cartesian

    fn Polar::from_cartesian(p : Vec2) -> Polar

    Radius >= 0, angle in (-PI, PI]; the origin maps to angle 0.

    Polar::new

    fn Polar::new(radius : Double, angle : Double) -> Polar

    Polar::to_cartesian

    fn Polar::to_cartesian(self : Polar) -> Vec2

    ProjKind

    pub(all) enum ProjKind {
    Perspective
    Orthographic
    } derive(Eq,
    Debug
    )

    Projection

    pub(all) struct Projection {
    kind : ProjKind
    left : Double
    right : Double
    bottom : Double
    top : Double
    near : Double
    far : Double
    depth : DepthMode
    } derive(Eq,
    Debug
    )

    A projection volume. left/right/bottom/top are the extents in VIEW-space units measured at the NEAR plane (perspective) or of the box (orthographic). far may be @double.infinity for perspective projections.

    Projection::aspect

    fn Projection::aspect(self : Projection) -> Double

    Width / height of the projection window.

    Projection::fov_y

    fn Projection::fov_y(self : Projection) -> Double

    Vertical field of view (radians) of a perspective projection (0 for orthographic).

    Projection::frustum

    fn Projection::frustum(left : Double, right : Double, bottom : Double, top : Double, near : Double, far : Double, depth : DepthMode) -> Projection

    Off-centre perspective frustum (glFrustum): extents on the near plane. Used for asymmetric FOV (VR eyes), tiled/sub-rect rendering, lens shift, portals.

    Projection::inverse_mat4

    fn Projection::inverse_mat4(self : Projection) -> Mat4

    Closed-form inverse (clip -> view), much cheaper and more accurate than a general 4x4 inverse. The result of inverse * clip has a w component that must be divided out for perspective (use Mat4::project_point).

    Projection::is_infinite_far

    fn Projection::is_infinite_far(self : Projection) -> Bool

    Projection::is_perspective

    fn Projection::is_perspective(self : Projection) -> Bool

    Projection::linear_depth

    fn Projection::linear_depth(self : Projection, ndc_z : Double) -> Double

    Distance in front of the camera plane (positive), from an NDC depth value. This is the "linearized depth" used for fog, SSAO, soft particles, etc.

    Projection::ndc_depth

    fn Projection::ndc_depth(self : Projection, view_z : Double) -> Double

    NDC depth of a view-space z (negative in front of the camera).

    Projection::orthographic

    fn Projection::orthographic(left : Double, right : Double, bottom : Double, top : Double, near : Double, far : Double, depth : DepthMode) -> Projection

    Projection::orthographic_centered

    fn Projection::orthographic_centered(width : Double, height : Double, near : Double, far : Double, depth : DepthMode) -> Projection

    Centered orthographic box of the given width / height.

    Projection::perspective

    fn Projection::perspective(fov_y : Double, aspect : Double, near : Double, far : Double, depth : DepthMode) -> Projection

    Symmetric perspective from vertical field of view (radians) and width/height aspect. far may be @double.infinity.

    Projection::perspective_fov

    fn Projection::perspective_fov(angle_left : Double, angle_right : Double, angle_down : Double, angle_up : Double, near : Double, far : Double, depth : DepthMode) -> Projection

    Asymmetric perspective from four half-angles (radians, all measured outward from the view axis and normally positive): the VR/OpenXR "field of view" form.

    Projection::project_view

    fn Projection::project_view(self : Projection, p : Vec3) -> Vec3?

    View-space point -> NDC. None for perspective points on/behind the eye plane (z_view >= 0), where the projection is not defined for rendering.

    Projection::to_mat4

    fn Projection::to_mat4(self : Projection) -> Mat4

    The 4x4 projection matrix (column vectors, v_clip = P * v_view).

    Projection::unproject_ndc

    fn Projection::unproject_ndc(self : Projection, ndc : Vec3) -> Vec3

    NDC -> view-space point (exact inverse of project_view).

    Projection::view_ray

    fn Projection::view_ray(self : Projection, ndc_x : Double, ndc_y : Double) -> (Vec3, Vec3)

    View-space ray through an NDC xy position: (origin, unit direction).
    • perspective: origin = eye (0,0,0)
    • orthographic: origin lies on the near plane, direction = -Z

    Projection::view_z_from_ndc

    fn Projection::view_z_from_ndc(self : Projection, ndc_z : Double) -> Double

    View-space z (negative in front) from an NDC depth value.

    Projection::with_aspect

    fn Projection::with_aspect(self : Projection, aspect : Double) -> Projection

    Same vertical extent, new aspect ratio (keeps relative horizontal lens shift).

    Projection::with_depth

    fn Projection::with_depth(self : Projection, depth : DepthMode) -> Projection

    Quat

    pub(all) struct Quat {
    x : Double
    y : Double
    z : Double
    w : Double
    } derive(Eq,
    Debug
    )

    impl Mul for Quat

    Quat::angle_to

    fn Quat::angle_to(self : Quat, o : Quat) -> Double

    Smallest rotation angle (radians, [0, PI]) between two orientations.

    Quat::approx_eq

    fn Quat::approx_eq(self : Quat, o : Quat, eps : Double) -> Bool

    True when both quaternions represent the same rotation (q and -q are equal).

    Quat::conjugate

    fn Quat::conjugate(self : Quat) -> Quat

    Quat::dot

    fn Quat::dot(self : Quat, o : Quat) -> Double

    Quat::from_axis_angle

    fn Quat::from_axis_angle(axis : Vec3, angle : Double) -> Quat

    axis need not be unit length; a zero axis gives identity.

    Quat::from_euler

    fn Quat::from_euler(order : EulerOrder, a : Double, b : Double, c : Double) -> Quat

    Quaternion for R_A(a) * R_B(b) * R_C(c) with (A,B,C) = order.

    Quat::from_mat3

    fn Quat::from_mat3(m : Mat3) -> Quat

    Quaternion from a proper rotation matrix (Shepperd's method, numerically stable).

    Quat::from_rotation_vector

    fn Quat::from_rotation_vector(v : Vec3) -> Quat

    exp-map: rotation vector (axis * angle) -> quaternion. Stable for tiny angles.

    Quat::from_to

    fn Quat::from_to(a : Vec3, b : Vec3) -> Quat

    Shortest-arc rotation taking direction a onto direction b (inputs need not be unit).

    Quat::from_x

    fn Quat::from_x(angle : Double) -> Quat

    Quat::from_y

    fn Quat::from_y(angle : Double) -> Quat

    Quat::from_z

    fn Quat::from_z(angle : Double) -> Quat

    Quat::identity

    fn Quat::identity() -> Quat

    Quat::integrate

    fn Quat::integrate(self : Quat, omega : Vec3, dt : Double) -> Quat

    Advance orientation by a WORLD-space angular velocity omega (rad/s) over dt seconds.

    Quat::inverse

    fn Quat::inverse(self : Quat) -> Quat

    Inverse of a unit quaternion (= conjugate).

    Quat::length

    fn Quat::length(self : Quat) -> Double

    Quat::look_rotation

    fn Quat::look_rotation(forward : Vec3, up : Vec3) -> Quat

    Orientation whose local -Z axis points along forward and whose local +Y is as close as possible to up (the camera / OpenGL "look" convention). Degenerate up is handled.

    Quat::negate

    fn Quat::negate(self : Quat) -> Quat

    Quat::new

    fn Quat::new(x : Double, y : Double, z : Double, w : Double) -> Quat

    Quat::nlerp

    fn Quat::nlerp(self : Quat, o : Quat, t : Double) -> Quat

    Normalised linear interpolation along the shortest arc.

    Quat::normalize

    fn Quat::normalize(self : Quat) -> Quat

    Re-normalise (call after many accumulated multiplications). Zero -> identity.

    Quat::rotate

    fn Quat::rotate(self : Quat, v : Vec3) -> Vec3

    Rotate a vector (direction or point about the origin).

    Quat::slerp

    fn Quat::slerp(self : Quat, o : Quat, t : Double) -> Quat

    Spherical interpolation along the shortest arc (constant angular speed).

    Quat::to_axis_angle

    fn Quat::to_axis_angle(self : Quat) -> (Vec3, Double)

    (unit axis, angle in [0, PI]). Identity -> (+X, 0).

    Quat::to_euler

    fn Quat::to_euler(self : Quat, order : EulerOrder) -> (Double, Double, Double)

    Quat::to_mat3

    fn Quat::to_mat3(self : Quat) -> Mat3

    Quat::to_rotation_vector

    fn Quat::to_rotation_vector(self : Quat) -> Vec3

    log-map: quaternion -> rotation vector with angle in [0, PI] (shortest representation).

    Ray

    pub(all) struct Ray {
    origin : Vec3
    dir : Vec3
    } derive(Eq,
    Debug
    )

    Ray::at

    fn Ray::at(self : Ray, t : Double) -> Vec3

    Ray::intersect_aabb

    fn Ray::intersect_aabb(self : Ray, b : Aabb) -> (Double, Double)?

    Slab test. Returns (t_enter, t_exit) with t_exit >= max(t_enter, 0); t_enter is clamped to 0 when the origin is inside. None on a miss.

    Ray::intersect_plane

    fn Ray::intersect_plane(self : Ray, pl : Plane) -> Double?

    Distance t >= 0 along the ray to the plane; None if parallel or behind the origin.

    Ray::intersect_sphere

    fn Ray::intersect_sphere(self : Ray, center : Vec3, radius : Double) -> Double?

    Nearest distance t >= 0 to a sphere surface hit (entry, or exit if starting inside).

    Ray::new

    fn Ray::new(origin : Vec3, dir : Vec3) -> Ray

    Ray::transform

    fn Ray::transform(self : Ray, m : Mat4) -> Ray

    Apply a rigid / affine matrix to the ray (dir re-normalised; distances t scale by the returned factor if the matrix scales).

    Rigid

    pub(all) struct Rigid {
    rotation : Quat
    translation : Vec3
    } derive(Eq,
    Debug
    )

    Rigid body pose: rotation + translation only (no scale). Forms a group: composition and inverse are exact. Use for camera poses, bones, physics bodies, frames.
    impl Mul for Rigid

    Rigid::from_mat4

    fn Rigid::from_mat4(m : Mat4) -> Rigid

    Extract from a rigid matrix (orthonormal 3x3, affine). Not validated.

    Rigid::identity

    fn Rigid::identity() -> Rigid

    Rigid::inverse

    fn Rigid::inverse(self : Rigid) -> Rigid

    Rigid::lerp

    fn Rigid::lerp(self : Rigid, o : Rigid, t : Double) -> Rigid

    Rigid::look_at

    fn Rigid::look_at(eye : Vec3, target : Vec3, up : Vec3) -> Rigid

    Pose (local -> world) of an object at eye whose local -Z looks at target with +Y toward up. For a camera this is the camera-to-world transform.

    Rigid::mul_dir

    fn Rigid::mul_dir(self : Rigid, d : Vec3) -> Vec3

    Rigid::mul_point

    fn Rigid::mul_point(self : Rigid, p : Vec3) -> Vec3

    Rigid::new

    fn Rigid::new(rotation : Quat, translation : Vec3) -> Rigid

    Rigid::to_mat4

    fn Rigid::to_mat4(self : Rigid) -> Mat4

    Spherical

    pub(all) struct Spherical {
    radius : Double
    azimuth : Double
    elevation : Double
    } derive(Eq,
    Debug
    )

    Spherical coordinates (radius, azimuth, elevation) -- see the module comment.

    Spherical::from_cartesian

    fn Spherical::from_cartesian(p : Vec3) -> Spherical

    Radius >= 0, azimuth in (-PI, PI], elevation in [-PI/2, PI/2]. At the poles (and the origin) azimuth is reported as 0; at the origin elevation is also 0.

    Spherical::new

    fn Spherical::new(radius : Double, azimuth : Double, elevation : Double) -> Spherical

    Spherical::to_cartesian

    fn Spherical::to_cartesian(self : Spherical) -> Vec3

    Transform

    pub(all) struct Transform {
    translation : Vec3
    rotation : Quat
    scale : Vec3
    } derive(Eq,
    Debug
    )

    Translation * Rotation * Scale. to_mat4 gives the model matrix. A negative scale component represents mirroring.

    Transform::identity

    fn Transform::identity() -> Transform

    Transform::mul_dir

    fn Transform::mul_dir(self : Transform, d : Vec3) -> Vec3

    Transform::mul_point

    fn Transform::mul_point(self : Transform, p : Vec3) -> Vec3

    local -> parent

    Transform::new

    fn Transform::new(t : Vec3, r : Quat, s : Vec3) -> Transform

    Transform::to_mat4

    fn Transform::to_mat4(self : Transform) -> Mat4

    Vec2

    pub(all) struct Vec2 {
    x : Double
    y : Double
    } derive(Eq,
    Debug
    )

    impl Add for Vec2
    impl Mul for Vec2
    impl Neg for Vec2
    impl Sub for Vec2

    Vec2::abs

    fn Vec2::abs(self : Vec2) -> Vec2

    Vec2::angle

    fn Vec2::angle(self : Vec2) -> Double

    Angle of the vector from +X, counter-clockwise, in (-PI, PI].

    Vec2::approx_eq

    fn Vec2::approx_eq(self : Vec2, o : Vec2, eps : Double) -> Bool

    Vec2::cross

    fn Vec2::cross(self : Vec2, o : Vec2) -> Double

    z-component of the 3D cross product (signed parallelogram area). Positive when o is counter-clockwise from self (in a y-up frame).

    Vec2::distance

    fn Vec2::distance(self : Vec2, o : Vec2) -> Double

    Vec2::dot

    fn Vec2::dot(self : Vec2, o : Vec2) -> Double

    Vec2::is_finite

    fn Vec2::is_finite(self : Vec2) -> Bool

    Vec2::length

    fn Vec2::length(self : Vec2) -> Double

    Vec2::length_sq

    fn Vec2::length_sq(self : Vec2) -> Double

    Vec2::lerp

    fn Vec2::lerp(self : Vec2, o : Vec2, t : Double) -> Vec2

    Vec2::max

    fn Vec2::max(self : Vec2, o : Vec2) -> Vec2

    Vec2::min

    fn Vec2::min(self : Vec2, o : Vec2) -> Vec2

    Vec2::new

    fn Vec2::new(x : Double, y : Double) -> Vec2

    Vec2::normalize

    fn Vec2::normalize(self : Vec2) -> Vec2

    Unit vector; the zero vector is returned unchanged.

    Vec2::perp

    fn Vec2::perp(self : Vec2) -> Vec2

    90 degrees counter-clockwise (y-up frame).

    Vec2::rotate

    fn Vec2::rotate(self : Vec2, angle : Double) -> Vec2

    Rotate counter-clockwise by angle radians.

    Vec2::scale

    fn Vec2::scale(self : Vec2, s : Double) -> Vec2

    Vec2::splat

    fn Vec2::splat(v : Double) -> Vec2

    Vec2::try_normalize

    fn Vec2::try_normalize(self : Vec2) -> Vec2?

    Unit vector, or None when the length is zero / not finite.

    Vec2::zero

    fn Vec2::zero() -> Vec2

    Vec3

    pub(all) struct Vec3 {
    x : Double
    y : Double
    z : Double
    } derive(Eq,
    Debug
    )

    impl Add for Vec3
    impl Mul for Vec3
    impl Neg for Vec3
    impl Sub for Vec3

    Vec3::abs

    fn Vec3::abs(self : Vec3) -> Vec3

    Vec3::angle_to

    fn Vec3::angle_to(self : Vec3, o : Vec3) -> Double

    Unsigned angle in [0, PI] between two vectors (robust for tiny angles).

    Vec3::any_perpendicular

    fn Vec3::any_perpendicular(self : Vec3) -> Vec3

    A vector perpendicular to self (unit length when self is non-zero).

    Vec3::approx_eq

    fn Vec3::approx_eq(self : Vec3, o : Vec3, eps : Double) -> Bool

    Vec3::cross

    fn Vec3::cross(self : Vec3, o : Vec3) -> Vec3

    Right-handed cross product.

    Vec3::distance

    fn Vec3::distance(self : Vec3, o : Vec3) -> Double

    Vec3::dot

    fn Vec3::dot(self : Vec3, o : Vec3) -> Double

    Vec3::from_yaw_pitch

    fn Vec3::from_yaw_pitch(yaw : Double, pitch : Double) -> Vec3

    Unit direction for yaw (azimuth) and pitch (elevation).

    Vec3::get

    fn Vec3::get(self : Vec3, i : Int) -> Double

    Component by index 0..2 (aborts otherwise).

    Vec3::is_finite

    fn Vec3::is_finite(self : Vec3) -> Bool

    Vec3::length

    fn Vec3::length(self : Vec3) -> Double

    Vec3::length_sq

    fn Vec3::length_sq(self : Vec3) -> Double

    Vec3::lerp

    fn Vec3::lerp(self : Vec3, o : Vec3, t : Double) -> Vec3

    Vec3::max

    fn Vec3::max(self : Vec3, o : Vec3) -> Vec3

    Vec3::min

    fn Vec3::min(self : Vec3, o : Vec3) -> Vec3

    Vec3::new

    fn Vec3::new(x : Double, y : Double, z : Double) -> Vec3

    Vec3::normalize

    fn Vec3::normalize(self : Vec3) -> Vec3

    Unit vector; the zero vector is returned unchanged.

    Vec3::project_onto

    fn Vec3::project_onto(self : Vec3, onto : Vec3) -> Vec3

    Component of self along onto (need not be unit). Zero if onto is zero.

    Vec3::reflect

    fn Vec3::reflect(self : Vec3, n : Vec3) -> Vec3

    Reflect self about unit normal n.

    Vec3::scale

    fn Vec3::scale(self : Vec3, s : Double) -> Vec3

    Vec3::splat

    fn Vec3::splat(v : Double) -> Vec3

    Vec3::to_vec4

    fn Vec3::to_vec4(self : Vec3, w : Double) -> Vec4

    Vec3::to_yaw_pitch

    fn Vec3::to_yaw_pitch(self : Vec3) -> (Double, Double)

    (yaw, pitch) of a direction (need not be unit).

    Vec3::try_normalize

    fn Vec3::try_normalize(self : Vec3) -> Vec3?

    Unit vector, or None when the length is zero / not finite.

    Vec3::unit_x

    fn Vec3::unit_x() -> Vec3

    Vec3::unit_y

    fn Vec3::unit_y() -> Vec3

    Vec3::unit_z

    fn Vec3::unit_z() -> Vec3

    Vec3::xy

    fn Vec3::xy(self : Vec3) -> Vec2

    Vec3::zero

    fn Vec3::zero() -> Vec3

    Vec4

    pub(all) struct Vec4 {
    x : Double
    y : Double
    z : Double
    w : Double
    } derive(Eq,
    Debug
    )

    impl Add for Vec4
    impl Neg for Vec4
    impl Sub for Vec4

    Vec4::approx_eq

    fn Vec4::approx_eq(self : Vec4, o : Vec4, eps : Double) -> Bool

    Vec4::dot

    fn Vec4::dot(self : Vec4, o : Vec4) -> Double

    Vec4::get

    fn Vec4::get(self : Vec4, i : Int) -> Double

    Vec4::lerp

    fn Vec4::lerp(self : Vec4, o : Vec4, t : Double) -> Vec4

    Vec4::new

    fn Vec4::new(x : Double, y : Double, z : Double, w : Double) -> Vec4

    Vec4::perspective_divide

    fn Vec4::perspective_divide(self : Vec4) -> Vec3?

    Perspective divide: (x/w, y/w, z/w). None when w == 0 / non-finite result.

    Vec4::scale

    fn Vec4::scale(self : Vec4, s : Double) -> Vec4

    Vec4::xyz

    fn Vec4::xyz(self : Vec4) -> Vec3

    Vec4::zero

    fn Vec4::zero() -> Vec4

    Viewport

    pub(all) struct Viewport {
    x : Double
    y : Double
    width : Double
    height : Double
    min_depth : Double
    max_depth : Double
    } derive(Eq,
    Debug
    )

    Viewport::aspect

    fn Viewport::aspect(self : Viewport) -> Double

    Viewport::contains

    fn Viewport::contains(self : Viewport, p : Vec2) -> Bool

    Does the window position fall inside the viewport rectangle?

    Viewport::from_logical

    fn Viewport::from_logical(logical_width : Double, logical_height : Double, scale : Double) -> Viewport

    Viewport for a window given in LOGICAL (DPI-independent) units; the pixel size is the logical size times scale, rounded to whole device pixels.

    Viewport::from_uv

    fn Viewport::from_uv(self : Viewport, uv : Vec2) -> Vec2

    Viewport::full

    fn Viewport::full(width : Double, height : Double) -> Viewport

    Viewport::ndc_to_screen

    fn Viewport::ndc_to_screen(self : Viewport, ndc : Vec3, mode : DepthMode) -> Vec3

    NDC (x, y, z) -> window coordinates (x px, y px, depth). mode tells how ndc z maps to [0,1] before min/max depth are applied.

    Viewport::ndc_to_screen_xy

    fn Viewport::ndc_to_screen_xy(self : Viewport, ndc : Vec2) -> Vec2

    Viewport::new

    fn Viewport::new(x : Double, y : Double, width : Double, height : Double) -> Viewport

    Viewport::screen_to_ndc

    fn Viewport::screen_to_ndc(self : Viewport, sx : Double, sy : Double, depth : Double, mode : DepthMode) -> Vec3

    Window coordinates + window depth -> NDC.

    Viewport::screen_to_ndc_xy

    fn Viewport::screen_to_ndc_xy(self : Viewport, p : Vec2) -> Vec2

    Window xy -> NDC xy only.

    Viewport::to_uv

    fn Viewport::to_uv(self : Viewport, p : Vec2) -> Vec2

    Window position -> normalised [0,1]^2 coordinates inside the viewport (origin top-left; the same layout as texture UVs of a full-screen image).

    WrapMode

    pub(all) enum WrapMode {
    Clamp
    Repeat
    MirroredRepeat
    } derive(Eq,
    Debug
    )

    CUBE_NEG_X

    let CUBE_NEG_X : Int

    CUBE_NEG_Y

    let CUBE_NEG_Y : Int

    CUBE_NEG_Z

    let CUBE_NEG_Z : Int

    CUBE_POS_X

    let CUBE_POS_X : Int

    Cube face indices (same order as OpenGL / D3D / Vulkan array layers).

    CUBE_POS_Y

    let CUBE_POS_Y : Int

    CUBE_POS_Z

    let CUBE_POS_Z : Int

    DEG2RAD

    let DEG2RAD : Double

    HALF_PI

    let HALF_PI : Double

    MERCATOR_MAX_LAT

    let MERCATOR_MAX_LAT : Double

    Latitude beyond which Web Mercator is clipped: atan(sinh(PI)) = 85.0511287798 deg.

    MERCATOR_RADIUS

    let MERCATOR_RADIUS : Double

    Radius of the sphere used by EPSG:3857 (WGS84 semi-major axis), metres.
    let PI : Double

    RAD2DEG

    let RAD2DEG : Double

    TAU

    let TAU : Double

    approx

    fn approx(a : Double, b : Double, eps : Double) -> Bool

    |a - b| <= eps (absolute).

    bary_inside

    fn bary_inside(b : Vec3, eps : Double) -> Bool

    True when all three barycentric weights are >= -eps.

    barycentric_2d

    fn barycentric_2d(p : Vec2, a : Vec2, b : Vec2, c : Vec2) -> Vec3?

    Barycentric coordinates (wa, wb, wc) of p in triangle (a, b, c); sum is 1; all >= 0 iff p is inside. None for a degenerate triangle.

    barycentric_3d

    fn barycentric_3d(p : Vec3, a : Vec3, b : Vec3, c : Vec3) -> Vec3?

    Barycentric coordinates of the projection of p onto the plane of triangle (a, b, c).

    bilinear_footprint

    fn bilinear_footprint(uv : Vec2, width : Int, height : Int, wrap_u : WrapMode, wrap_v : WrapMode) -> Bilinear

    clamp

    fn clamp(x : Double, lo : Double, hi : Double) -> Double

    cube_from_direction

    fn cube_from_direction(dir : Vec3) -> CubeCoord

    Direction -> (face, u, v) following the OpenGL cube-map selection table (also used by D3D and Vulkan). dir need not be unit; zero gives face 0 centre. Ties between equal major axes resolve X, then Y, then Z.

    cube_to_direction

    fn cube_to_direction(face : Int, u : Double, v : Double) -> Vec3

    (face, u, v) -> UNIT direction through that point of the face (exact inverse of cube_from_direction).

    deg_to_rad

    fn deg_to_rad(d : Double) -> Double

    ecef_to_yup

    fn ecef_to_yup(p : Vec3) -> Vec3

    Globe-centred render axes: ECEF (x, y, z) -> render (x, z, -y), i.e. north pole = +Y. A proper rotation (determinant +1).

    edge_function

    fn edge_function(a : Vec2, b : Vec2, p : Vec2) -> Double

    Edge function: > 0 when p is to the LEFT of the directed edge a -> b (Y-up frame).

    equirect_from_direction

    fn equirect_from_direction(dir : Vec3) -> Vec2

    Direction -> equirectangular UV. The image centre (0.5, 0.5) looks down -Z (forward); u increases toward +X (turning right), v increases DOWNWARD (v = 0 is straight up).

    equirect_to_direction

    fn equirect_to_direction(uv : Vec2) -> Vec3

    flip_v

    fn flip_v(uv : Vec2) -> Vec2

    Switch between top-left (v down) and bottom-left (v up) conventions.

    ground_resolution

    fn ground_resolution(lat : Double, zoom : Double, tile_size : Double) -> Double

    Ground resolution: metres per pixel at a latitude (true ground distance, not Mercator units).

    interpolate1

    fn interpolate1(b : Vec3, a0 : Double, a1 : Double, a2 : Double) -> Double

    interpolate2

    fn interpolate2(b : Vec3, a0 : Vec2, a1 : Vec2, a2 : Vec2) -> Vec2

    interpolate3

    fn interpolate3(b : Vec3, a0 : Vec3, a1 : Vec3, a2 : Vec3) -> Vec3

    lerp

    fn lerp(a : Double, b : Double, t : Double) -> Double

    logical_to_physical

    fn logical_to_physical(p : Vec2, scale : Double) -> Vec2

    lonlat_to_tile

    fn lonlat_to_tile(p : LonLat, zoom : Int, tile_size : Double) -> (Int, Int)

    Integer tile (x, y) containing a position at integer zoom (clamped to the pyramid).

    lonlat_to_world_pixel

    fn lonlat_to_world_pixel(p : LonLat, zoom : Double, tile_size : Double) -> Vec2

    lon/lat -> world pixel coordinates (origin NW corner, y down).

    mercator_to_world_pixel

    fn mercator_to_world_pixel(m : Vec2, zoom : Double, tile_size : Double) -> Vec2

    EPSG:3857 metres -> world pixels (and back); handy for mixing meter-based and tile-based math.

    mip_extent

    fn mip_extent(size : Int, level : Int) -> Int

    Size of a mip level (>= 1).

    mip_level_count

    fn mip_level_count(width : Int, height : Int) -> Int

    Number of mip levels of a full chain down to 1x1.

    mip_lod

    fn mip_lod(duv_dx : Vec2, duv_dy : Vec2, width : Int, height : Int) -> Double

    Level of detail from screen-space UV derivatives (what textureSample computes): lod = log2(max(|d(uv * size)/dx|, |d(uv * size)/dy|)). May be negative (magnification).

    mod_floor

    fn mod_floor(a : Int, n : Int) -> Int

    Floor division remainder that is always in [0, n) for n > 0.

    oct_decode

    fn oct_decode(e : Vec2) -> Vec3

    oct_encode

    fn oct_encode(n : Vec3) -> Vec2

    oct_to_uv

    fn oct_to_uv(e : Vec2) -> Vec2

    [-1,1]^2 octahedral coordinate -> [0,1]^2 texture UV.

    orthonormal_basis

    fn orthonormal_basis(n : Vec3) -> (Vec3, Vec3)

    Branchless orthonormal basis from a UNIT vector (Duff et al. 2017, "Building an Orthonormal Basis, Revisited"): continuous except at n = (0, 0, -1) and free of singularities for every other direction.

    perspective_correct

    fn perspective_correct(screen_bary : Vec3, w0 : Double, w1 : Double, w2 : Double) -> Vec3

    Perspective-correct barycentrics. screen_bary are the weights measured in 2D window/NDC space; w0, w1, w2 are the clip-space w of the three vertices (= view-space depth for a perspective projection). The result are the weights in the 3D triangle, to be used to interpolate any attribute (UV, normal, colour, view-space position).

    physical_to_logical

    fn physical_to_logical(p : Vec2, scale : Double) -> Vec2

    pixel_center

    fn pixel_center(ix : Int, iy : Int) -> Vec2

    Centre of pixel (ix, iy) in window coordinates.

    pixel_of

    fn pixel_of(p : Vec2) -> (Int, Int)

    Integer pixel containing a window position.

    quadkey_to_tile

    fn quadkey_to_tile(key : String) -> (Int, Int, Int)?

    Parse a quadkey back into (x, y, zoom). None for any character other than 0-3.

    rad_to_deg

    fn rad_to_deg(r : Double) -> Double

    safe_acos

    fn safe_acos(x : Double) -> Double

    acos that tolerates |x| slightly above 1 from rounding error.

    safe_asin

    fn safe_asin(x : Double) -> Double

    asin that tolerates |x| slightly above 1 from rounding error.

    saturate

    fn saturate(x : Double) -> Double

    sign_nz

    fn sign_nz(x : Double) -> Double

    +1.0 for x >= 0 (including +0 and -0), -1.0 otherwise. Never returns 0.

    tangent_frame

    fn tangent_frame(p0 : Vec3, p1 : Vec3, p2 : Vec3, uv0 : Vec2, uv1 : Vec2, uv2 : Vec2, n : Vec3) -> Frame?

    Tangent frame of a triangle from its positions and UVs, for normal mapping. T points along increasing u (dP/du), B along increasing v (dP/dv, which is DOWN in the top-left UV convention -- negate B, or flip_v the UVs, for a Y-up normal map). T and B are orthonormalised against the supplied vertex normal n; the handedness of the UV mapping is preserved in B. None for degenerate (zero-area UV) triangles.

    texel_center_uv

    fn texel_center_uv(i : Int, j : Int, width : Int, height : Int) -> Vec2

    UV of the CENTRE of texel (i, j).

    texel_to_uv

    fn texel_to_uv(t : Vec2, width : Int, height : Int) -> Vec2

    tile_bounds

    fn tile_bounds(x : Int, y : Int, zoom : Int) -> (LonLat, LonLat)

    (west, south, east, north) lon/lat bounds of a tile.

    tile_to_lonlat

    fn tile_to_lonlat(x : Double, y : Double, zoom : Int) -> LonLat

    North-west corner of tile (x, y) at zoom (x, y may be fractional for sub-tile positions).

    tile_to_quadkey

    fn tile_to_quadkey(x : Int, y : Int, zoom : Int) -> String

    Bing-style quadkey of a tile ("" at zoom 0).

    triangle_area2

    fn triangle_area2(a : Vec2, b : Vec2, c : Vec2) -> Double

    Twice the signed area of triangle (a, b, c): positive when counter-clockwise in a Y-UP frame (NDC). In window space (Y down) the same triangle is negative.

    uv_to_oct

    fn uv_to_oct(uv : Vec2) -> Vec2

    uv_to_texel

    fn uv_to_texel(uv : Vec2, width : Int, height : Int) -> Vec2

    UV -> continuous texel coordinates (texel i spans [i, i+1)). Pixel centre = i + 0.5.

    web_mercator_forward

    fn web_mercator_forward(p : LonLat) -> Vec2

    lon/lat -> EPSG:3857 metres (x east, y NORTH). Latitude is clamped to +-MERCATOR_MAX_LAT.

    web_mercator_inverse

    fn web_mercator_inverse(m : Vec2) -> LonLat

    EPSG:3857 metres -> lon/lat.

    world_pixel_to_lonlat

    fn world_pixel_to_lonlat(px : Vec2, zoom : Double, tile_size : Double) -> LonLat

    world_pixel_to_mercator

    fn world_pixel_to_mercator(px : Vec2, zoom : Double, tile_size : Double) -> Vec2

    world_size_px

    fn world_size_px(zoom : Double, tile_size : Double) -> Double

    Side of the whole-world square in pixels at (possibly fractional) zoom.

    wrap_coord

    fn wrap_coord(u : Double, mode : WrapMode) -> Double

    Wrap a normalised coordinate into [0, 1].

    wrap_pi

    fn wrap_pi(a : Double) -> Double

    Wrap an angle into (-PI, PI].

    wrap_tau

    fn wrap_tau(a : Double) -> Double

    Wrap an angle into [0, TAU).

    wrap_texel

    fn wrap_texel(i : Int, n : Int, mode : WrapMode) -> Int

    Wrap an integer texel index into [0, n).

    yup_to_ecef

    fn yup_to_ecef(p : Vec3) -> Vec3