stdlib.geom¶
2D/3D Geometry Library (vectors, matrices, quaternions, primitives)
Generated from
v0.65.7. 14 source files, 460 documented symbols.
collision.xi¶
type CollisionAabb¶
Axis-aligned bounding box defined by min and max corners.
| Field | Type |
|---|---|
min |
Vec[Float64] |
max |
Vec[Float64] |
type CollisionSphere¶
CollisionSphere primitive defined by centre point and radius.
| Field | Type |
|---|---|
center |
Vec[Float64] |
radius |
Float64 |
type CollisionRay¶
CollisionRay primitive: infinite line from origin along dir.
| Field | Type |
|---|---|
origin |
Vec[Float64] |
dir |
Vec[Float64] |
fn aabb_new(min: &Vec[Float64], max: &Vec[Float64]) -> CollisionAabb¶
Construct an AABB from min and max corners (3 components each). O(1).
- Postcondition:
result.min.len() == min.len() && result.max.len() == max.len()
fn aabb_contains(a: CollisionAabb, p: &Vec[Float64]) -> Bool¶
True iff p lies inside the AABB (inclusive). A point with fewer than 3 components is not inside. O(1).
- Postcondition:
(p.len() < 3 || a.min.len() < 3 || a.max.len() < 3) => result == false
fn aabb_intersects(a: CollisionAabb, b: CollisionAabb) -> Bool¶
True iff the two AABBs overlap or touch. O(1).
- Postcondition:
(a.min.len() < 3 || a.max.len() < 3 || b.min.len() < 3 || b.max.len() < 3) => result == false
fn sphere_new(center: &Vec[Float64], radius: Float64) -> CollisionSphere¶
Construct a sphere from center and radius. O(1).
- Postcondition:
result.center.len() == center.len() && result.radius == radius
fn sphere_contains(s: CollisionSphere, p: &Vec[Float64]) -> Bool¶
True iff p lies inside the sphere (inclusive). O(1).
- Postcondition:
(p.len() < 3 || s.center.len() < 3) => result == false
fn sphere_intersects(a: CollisionSphere, b: CollisionSphere) -> Bool¶
True iff the two spheres overlap or touch. O(1).
- Postcondition:
(a.center.len() < 3 || b.center.len() < 3) => result == false
fn ray_new(origin: &Vec[Float64], dir: &Vec[Float64]) -> CollisionRay¶
Construct a ray from origin and direction. O(1).
- Postcondition:
result.origin.len() == origin.len() && result.dir.len() == dir.len()
fn ray_sphere_intersect(r: CollisionRay, s: CollisionSphere) -> Option[Float64]¶
CollisionRay-sphere intersection: nearest positive t; None on miss. O(1).
- Postcondition:
(r.origin.len() < 3 || r.dir.len() < 3 || s.center.len() < 3) => result.is_some == false
fn ray_aabb_intersect(r: CollisionRay, a: CollisionAabb) -> Option[Float64]¶
CollisionRay-AABB intersection via the slab method: nearest positive t; None on miss. O(1).
- Postcondition:
(r.origin.len() < 3 || r.dir.len() < 3 || a.min.len() < 3 || a.max.len() < 3) => result.is_some == false
fn ray_plane_intersect(r: CollisionRay, plane: &Vec[Float64]) -> Option[Float64]¶
CollisionRay-plane intersection against the plane (n, d) given as the 4-element vector [nx, ny, nz, d] with n.p = d. None when parallel or behind. O(1).
- Postcondition:
(r.origin.len() < 3 || r.dir.len() < 3 || plane.len() < 4) => result.is_some == false
fn point_in_triangle(p: &Vec[Float64], a: &Vec[Float64], b: &Vec[Float64], c: &Vec[Float64]) -> Bool¶
True iff p is inside (or on) the triangle (a, b, c) using same-side tests on the 2D-projected coordinate with the dominant axis removed. O(1).
- Postcondition:
(p.len() < 3 || a.len() < 3 || b.len() < 3 || c.len() < 3) => result == false
fn segment_intersect(p1: &Vec[Float64], p2: &Vec[Float64], p3: &Vec[Float64], p4: &Vec[Float64]) -> Option[Vec[Float64]]¶
Intersection point of segments p1p2 and p3p4; None if disjoint or parallel. The result is a 3-component point. O(1).
- Postcondition:
(p1.len() < 2 || p2.len() < 2 || p3.len() < 2 || p4.len() < 2) => result.is_some == false
curves.xi¶
fn bezier_quad(p0: &Vec[Float64], p1: &Vec[Float64], p2: &Vec[Float64], t: Float64) -> Vec[Float64]¶
Point on a quadratic Bezier curve at parameter t. O(1).
- Postcondition:
result.len() == 0 || result.len() == p0.len()
fn bezier_cubic(p0: &Vec[Float64], p1: &Vec[Float64], p2: &Vec[Float64], p3: &Vec[Float64], t: Float64) -> Vec[Float64]¶
Point on a cubic Bezier curve at parameter t. O(1).
- Postcondition:
result.len() == 0 || result.len() == p0.len()
fn bezier_derivative(points: &Vec[Vec[Float64]], t: Float64) -> Vec[Float64]¶
Tangent vector of a Bezier curve at t: the derivative of the de Casteljau ladder. O(k^2).
- Postcondition:
result.len() == 0 || points.len() >= 2
fn catmull_rom(p0: &Vec[Float64], p1: &Vec[Float64], p2: &Vec[Float64], p3: &Vec[Float64], t: Float64) -> Vec[Float64]¶
Catmull-Rom spline point over [p1, p2] at parameter t in [0, 1]. O(1).
- Postcondition:
result.len() == 0 || result.len() == p0.len()
fn b_spline(points: &Vec[Vec[Float64]], t: Float64) -> Vec[Float64]¶
Uniform cubic B-spline point at parameter t in [0, 1] over the four control points. O(1).
- Postcondition:
result.len() == 0 || points.len() >= 4
fn hermite_curve(p0: &Vec[Float64], t0: &Vec[Float64], p1: &Vec[Float64], t1: &Vec[Float64], t: Float64) -> Vec[Float64]¶
Hermite interpolation with endpoint tangents t0 and t1. O(1).
- Postcondition:
result.len() == 0 || result.len() == p0.len()
fn curve_length(samples: fn(Float64) -> Vec[Float64], a: Float64, b: Float64, n: Int) -> Float64¶
Arc length of a sampled curve over [a, b] by piecewise-linear integration with n segments. O(n * cost(f)).
- Postcondition:
(result >= 0) || (result != result)
geom.xi¶
type Vec2¶
2-component vector (x, y) -- used for 2D positions, directions, UVs.
| Field | Type |
|---|---|
x |
Float64 |
y |
Float64 |
type Vec3¶
3-component vector (x, y, z) -- core 3D math type.
| Field | Type |
|---|---|
x |
Float64 |
y |
Float64 |
z |
Float64 |
type Vec4¶
4-component vector (x, y, z, w) -- homogeneous coords, RGBA colours.
| Field | Type |
|---|---|
x |
Float64 |
y |
Float64 |
z |
Float64 |
w |
Float64 |
type Quaternion¶
Quaternion (x, y, z, w) -- rotation representation; w is the scalar part.
| Field | Type |
|---|---|
x |
Float64 |
y |
Float64 |
z |
Float64 |
w |
Float64 |
type Mat2¶
2x2 column-major matrix.
| Field | Type |
|---|---|
m00 |
Float64 |
m01 |
Float64 |
m10 |
Float64 |
m11 |
Float64 |
type Mat3¶
3x3 column-major matrix.
| Field | Type |
|---|---|
m00 |
Float64 |
m01 |
Float64 |
m02 |
Float64 |
m10 |
Float64 |
m11 |
Float64 |
m12 |
Float64 |
m20 |
Float64 |
m21 |
Float64 |
m22 |
Float64 |
type Mat4¶
4x4 column-major matrix -- core 3D transform type.
| Field | Type |
|---|---|
m00 |
Float64 |
m01 |
Float64 |
m02 |
Float64 |
m03 |
Float64 |
m10 |
Float64 |
m11 |
Float64 |
m12 |
Float64 |
m13 |
Float64 |
m20 |
Float64 |
m21 |
Float64 |
m22 |
Float64 |
m23 |
Float64 |
m30 |
Float64 |
m31 |
Float64 |
m32 |
Float64 |
m33 |
Float64 |
type Aabb¶
Axis-aligned bounding box defined by min and max corners.
| Field | Type |
|---|---|
min |
Vec3 |
max |
Vec3 |
type Sphere¶
Sphere primitive defined by centre point and radius.
| Field | Type |
|---|---|
center |
Vec3 |
radius |
Float64 |
type Ray¶
Ray primitive: infinite line from origin along dir. dir should be normalised for consistent t values.
| Field | Type |
|---|---|
origin |
Vec3 |
dir |
Vec3 |
fn vec2_new(x: Float64, y: Float64) -> Vec2¶
Create a new 2D vector.
- Postcondition:
((result.x == x) || (result.x != result.x)) && ((result.y == y) || (result.y != result.y))
fn vec2_add(a: Vec2, b: Vec2) -> Vec2¶
Add two 2D vectors component-wise. O(1).
- Postcondition:
result.x == a.x + b.x && result.y == a.y + b.y
fn vec2_sub(a: Vec2, b: Vec2) -> Vec2¶
Subtract b from a component-wise. O(1).
- Postcondition:
result.x == a.x - b.x && result.y == a.y - b.y
fn vec2_mul(a: Vec2, b: Vec2) -> Vec2¶
Multiply two 2D vectors component-wise. O(1).
- Postcondition:
result.x == a.x * b.x && result.y == a.y * b.y
fn vec2_div(a: Vec2, b: Vec2) -> Vec2¶
Divide a by b component-wise. O(1).
- Postcondition:
result.x == a.x / b.x && result.y == a.y / b.y
fn vec2_add_scalar(v: Vec2, s: Float64) -> Vec2¶
Add scalar s to each component of v. O(1).
- Postcondition:
result.x == v.x + s && result.y == v.y + s
fn vec2_sub_scalar(v: Vec2, s: Float64) -> Vec2¶
Subtract scalar s from each component of v. O(1).
- Postcondition:
result.x == v.x - s && result.y == v.y - s
fn vec2_mul_scalar(v: Vec2, s: Float64) -> Vec2¶
Multiply each component of v by scalar s. O(1).
- Postcondition:
result.x == v.x * s && result.y == v.y * s
fn vec2_div_scalar(v: Vec2, s: Float64) -> Vec2¶
Divide each component of v by scalar s. O(1).
- Postcondition:
result.x == v.x / s && result.y == v.y / s
fn vec2_dot(a: Vec2, b: Vec2) -> Float64¶
Dot product of two 2D vectors. O(1).
- Postcondition:
result == a.x * b.x + a.y * b.y
fn vec2_cross(a: Vec2, b: Vec2) -> Float64¶
2D cross product (scalar): a.x * b.y - a.y * b.x. O(1). This is the signed area of the parallelogram spanned by a and b.
- Postcondition:
result == a.x * b.y - a.y * b.x
fn vec2_length(v: Vec2) -> Float64¶
Euclidean length (magnitude) of v. O(1).
- Postcondition:
result >= 0
fn vec2_normalize(v: Vec2) -> Vec2¶
Normalise v to unit length. Returns zero vector if length is zero. O(1).
- Postcondition:
(result.x == 0 && result.y == 0) || (v.x != 0 || v.y != 0)
fn vec2_distance(a: Vec2, b: Vec2) -> Float64¶
Euclidean distance between two 2D points. O(1).
- Postcondition:
result >= 0
fn vec2_lerp(a: Vec2, b: Vec2, t: Float64) -> Float64¶
Linearly interpolate between a and b by t. t=0 -> a, t=1 -> b. O(1).
- Postcondition:
result == math.lerp(a.x, b.x, t)
fn vec3_new(x: Float64, y: Float64, z: Float64) -> Vec3¶
Create a new 3D vector.
- Postcondition:
((result.x == x) || (result.x != result.x)) && ((result.y == y) || (result.y != result.y)) && ((result.z == z) || (result.z != result.z))
fn vec3_add(a: Vec3, b: Vec3) -> Vec3¶
Add two 3D vectors component-wise. O(1).
- Postcondition:
result.x == a.x + b.x && result.y == a.y + b.y && result.z == a.z + b.z
fn vec3_sub(a: Vec3, b: Vec3) -> Vec3¶
Subtract b from a component-wise. O(1).
- Postcondition:
result.x == a.x - b.x && result.y == a.y - b.y && result.z == a.z - b.z
fn vec3_mul(a: Vec3, b: Vec3) -> Vec3¶
Multiply two 3D vectors component-wise (Hadamard product). O(1).
- Postcondition:
result.x == a.x * b.x && result.y == a.y * b.y && result.z == a.z * b.z
fn vec3_div(a: Vec3, b: Vec3) -> Vec3¶
Divide a by b component-wise. O(1).
- Postcondition:
result.x == a.x / b.x && result.y == a.y / b.y && result.z == a.z / b.z
fn vec3_add_scalar(v: Vec3, s: Float64) -> Vec3¶
Add scalar s to each component of v. O(1).
- Postcondition:
result.x == v.x + s && result.y == v.y + s && result.z == v.z + s
fn vec3_sub_scalar(v: Vec3, s: Float64) -> Vec3¶
Subtract scalar s from each component of v. O(1).
- Postcondition:
result.x == v.x - s && result.y == v.y - s && result.z == v.z - s
fn vec3_mul_scalar(v: Vec3, s: Float64) -> Vec3¶
Multiply each component of v by scalar s. O(1).
- Postcondition:
result.x == v.x * s && result.y == v.y * s && result.z == v.z * s
fn vec3_div_scalar(v: Vec3, s: Float64) -> Vec3¶
Divide each component of v by scalar s. O(1).
- Postcondition:
result.x == v.x / s && result.y == v.y / s && result.z == v.z / s
fn vec3_dot(a: Vec3, b: Vec3) -> Float64¶
Dot product of two 3D vectors. O(1).
- Postcondition:
result == a.x * b.x + a.y * b.y + a.z * b.z
fn vec3_cross(a: Vec3, b: Vec3) -> Vec3¶
3D cross product: a x b (right-handed). O(1).
- Postcondition:
result.x == a.y * b.z - a.z * b.y && result.y == a.z * b.x - a.x * b.z && result.z == a.x * b.y - a.y * b.x
fn vec3_length(v: Vec3) -> Float64¶
Euclidean length (magnitude) of v. O(1).
- Postcondition:
result >= 0
fn vec3_normalize(v: Vec3) -> Vec3¶
Normalise v to unit length. Returns zero vector if length is zero. O(1).
- Postcondition:
(result.x == 0 && result.y == 0 && result.z == 0) || (v.x != 0 || v.y != 0 || v.z != 0)
fn vec3_distance(a: Vec3, b: Vec3) -> Float64¶
Euclidean distance between two 3D points. O(1).
- Postcondition:
result >= 0
fn vec3_lerp(a: Vec3, b: Vec3, t: Float64) -> Vec3¶
Linearly interpolate each component between a and b by t. O(1).
- Postcondition:
(!((t >= 0) && (t <= 1))) || (((result.x == a.x + (b.x - a.x) * t) || (result.x != result.x)) && ((result.y == a.y + (b.y - a.y) * t) || (result.y != result.y)) && ((result.z == a.z + (b.z - a.z) * t) || (result.z != result.z)))
fn vec4_new(x: Float64, y: Float64, z: Float64, w: Float64) -> Vec4¶
Create a new 4D vector.
- Postcondition:
((result.x == x) || (result.x != result.x)) && ((result.y == y) || (result.y != result.y)) && ((result.z == z) || (result.z != result.z)) && ((result.w == w) || (result.w != result.w))
fn quat_identity() -> Quaternion¶
Identity quaternion (no rotation). O(1).
- Postcondition:
result.x == 0 && result.y == 0 && result.z == 0 && result.w == 1
fn quat_new(axis: Vec3, angle: Float64) -> Quaternion¶
Create a quaternion from an axis (must be normalised) and an angle (radians). Rotation is right-handed around the axis. O(1).
- Postcondition:
((result.x == axis.x * math.sin(angle * 0.5)) || (result.x != result.x)) && ((result.y == axis.y * math.sin(angle * 0.5)) || (result.y != result.y)) && ((result.z == axis.z * math.sin(angle * 0.5)) || (result.z != result.z)) && ((result.w == math.cos(angle * 0.5)) || (result.w != result.w))
fn quat_mul(a: Quaternion, b: Quaternion) -> Quaternion¶
Multiply two quaternions q1 * q2 (compose rotations, q2 applied first). O(1). Hamilton product: (w1w2 - v1-v2, w1v2 + w2v1 + v1xv2)
- Postcondition:
((result.x == a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y) || (result.x != result.x)) && ((result.y == a.w * b.y - a.x * b.z + a.y * b.w + a.z * b.x) || (result.y != result.y)) && ((result.z == a.w * b.z + a.x * b.y - a.y * b.x + a.z * b.w) || (result.z != result.z)) && ((result.w == a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z) || (result.w != result.w))
fn quat_normalize(q: Quaternion) -> Quaternion¶
Normalise a quaternion to unit length. If length is zero, returns identity. O(1).
- Postcondition:
(result.x == 0 && result.y == 0 && result.z == 0 && result.w == 1) || (q.x != 0 || q.y != 0 || q.z != 0 || q.w != 0)
fn quat_conjugate(q: Quaternion) -> Quaternion¶
Conjugate of a quaternion. For unit quaternions this is the inverse. O(1).
- Postcondition:
((result.x == -q.x) || (result.x != result.x)) && ((result.y == -q.y) || (result.y != result.y)) && ((result.z == -q.z) || (result.z != result.z)) && ((result.w == q.w) || (result.w != result.w))
fn quat_rotate_vec3(q: Quaternion, v: Vec3) -> Vec3¶
Rotate a 3D vector by quaternion q (q must be normalised). O(1). Returns: v + 2.0 * q.xyz x (q.xyz x v + q.w * v)
- Postcondition:
(((result.x == v.x) || (result.x != result.x)) && ((result.y == v.y) || (result.y != result.y)) && ((result.z == v.z) || (result.z != result.z))) || (q.x != 0 || q.y != 0 || q.z != 0 || q.w != 1)
fn quat_from_euler(yaw: Float64, pitch: Float64, roll: Float64) -> Quaternion¶
Create a quaternion from Euler angles (ZYX intrinsic = yaw-pitch-roll in radians). yaw: rotation around Z, pitch: around Y, roll: around X.
- Postcondition:
((result.x >= -2) && (result.x <= 2)) || (result.x != result.x) - Postcondition:
((result.y >= -2) && (result.y <= 2)) || (result.y != result.y) - Postcondition:
((result.z >= -2) && (result.z <= 2)) || (result.z != result.z) - Postcondition:
((result.w >= -2) && (result.w <= 2)) || (result.w != result.w)
fn mat4_identity() -> Mat4¶
4x4 identity matrix. O(1).
- Postcondition:
result.m00 == 1 && result.m11 == 1 && result.m22 == 1 && result.m33 == 1 && result.m01 == 0 && result.m02 == 0 && result.m03 == 0 && result.m10 == 0 && result.m12 == 0 && result.m13 == 0 && result.m20 == 0 && result.m21 == 0 && result.m23 == 0 && result.m30 == 0 && result.m31 == 0 && result.m32 == 0
fn mat4_mul(a: Mat4, b: Mat4) -> Mat4¶
Multiply two 4x4 matrices: a * b. Row x column dot products. O(64 ops).
- Postcondition:
((result.m00 == a.m00 * b.m00 + a.m01 * b.m10 + a.m02 * b.m20 + a.m03 * b.m30) || (result.m00 != result.m00)) && ((result.m01 == a.m00 * b.m01 + a.m01 * b.m11 + a.m02 * b.m21 + a.m03 * b.m31) || (result.m01 != result.m01)) && ((result.m02 == a.m00 * b.m02 + a.m01 * b.m12 + a.m02 * b.m22 + a.m03 * b.m32) || (result.m02 != result.m02)) && ((result.m03 == a.m00 * b.m03 + a.m01 * b.m13 + a.m02 * b.m23 + a.m03 * b.m33) || (result.m03 != result.m03)) && ((result.m10 == a.m10 * b.m00 + a.m11 * b.m10 + a.m12 * b.m20 + a.m13 * b.m30) || (result.m10 != result.m10)) && ((result.m11 == a.m10 * b.m01 + a.m11 * b.m11 + a.m12 * b.m21 + a.m13 * b.m31) || (result.m11 != result.m11)) && ((result.m12 == a.m10 * b.m02 + a.m11 * b.m12 + a.m12 * b.m22 + a.m13 * b.m32) || (result.m12 != result.m12)) && ((result.m13 == a.m10 * b.m03 + a.m11 * b.m13 + a.m12 * b.m23 + a.m13 * b.m33) || (result.m13 != result.m13)) && ((result.m20 == a.m20 * b.m00 + a.m21 * b.m10 + a.m22 * b.m20 + a.m23 * b.m30) || (result.m20 != result.m20)) && ((result.m21 == a.m20 * b.m01 + a.m21 * b.m11 + a.m22 * b.m21 + a.m23 * b.m31) || (result.m21 != result.m21)) && ((result.m22 == a.m20 * b.m02 + a.m21 * b.m12 + a.m22 * b.m22 + a.m23 * b.m32) || (result.m22 != result.m22)) && ((result.m23 == a.m20 * b.m03 + a.m21 * b.m13 + a.m22 * b.m23 + a.m23 * b.m33) || (result.m23 != result.m23)) && ((result.m30 == a.m30 * b.m00 + a.m31 * b.m10 + a.m32 * b.m20 + a.m33 * b.m30) || (result.m30 != result.m30)) && ((result.m31 == a.m30 * b.m01 + a.m31 * b.m11 + a.m32 * b.m21 + a.m33 * b.m31) || (result.m31 != result.m31)) && ((result.m32 == a.m30 * b.m02 + a.m31 * b.m12 + a.m32 * b.m22 + a.m33 * b.m32) || (result.m32 != result.m32)) && ((result.m33 == a.m30 * b.m03 + a.m31 * b.m13 + a.m32 * b.m23 + a.m33 * b.m33) || (result.m33 != result.m33))
fn mat4_translate(tx: Float64, ty: Float64, tz: Float64) -> Mat4¶
Translation matrix. O(1).
- Postcondition:
result.m00 == 1 && result.m01 == 0 && result.m02 == 0 && ((result.m03 == tx) || (result.m03 != result.m03)) && result.m10 == 0 && result.m11 == 1 && result.m12 == 0 && ((result.m13 == ty) || (result.m13 != result.m13)) && result.m20 == 0 && result.m21 == 0 && result.m22 == 1 && ((result.m23 == tz) || (result.m23 != result.m23)) && result.m30 == 0 && result.m31 == 0 && result.m32 == 0 && result.m33 == 1
fn mat4_scale(sx: Float64, sy: Float64, sz: Float64) -> Mat4¶
Scale matrix (non-uniform). O(1).
- Postcondition:
((result.m00 == sx) || (result.m00 != result.m00)) && result.m01 == 0 && result.m02 == 0 && result.m03 == 0 && result.m10 == 0 && ((result.m11 == sy) || (result.m11 != result.m11)) && result.m12 == 0 && result.m13 == 0 && result.m20 == 0 && result.m21 == 0 && ((result.m22 == sz) || (result.m22 != result.m22)) && result.m23 == 0 && result.m30 == 0 && result.m31 == 0 && result.m32 == 0 && result.m33 == 1
fn mat4_rotate_x(angle: Float64) -> Mat4¶
Rotation around X axis by angle radians (right-handed). O(1).
- Postcondition:
result.m00 == 1 && result.m01 == 0 && result.m02 == 0 && result.m03 == 0 && result.m10 == 0 && ((result.m11 == math.cos(angle)) || (result.m11 != result.m11)) && ((result.m12 == -math.sin(angle)) || (result.m12 != result.m12)) && result.m13 == 0 && result.m20 == 0 && ((result.m21 == math.sin(angle)) || (result.m21 != result.m21)) && ((result.m22 == math.cos(angle)) || (result.m22 != result.m22)) && result.m23 == 0 && result.m30 == 0 && result.m31 == 0 && result.m32 == 0 && result.m33 == 1
fn mat4_rotate_y(angle: Float64) -> Mat4¶
Rotation around Y axis by angle radians (right-handed). O(1).
- Postcondition:
((result.m00 == math.cos(angle)) || (result.m00 != result.m00)) && result.m01 == 0 && ((result.m02 == math.sin(angle)) || (result.m02 != result.m02)) && result.m03 == 0 && result.m10 == 0 && result.m11 == 1 && result.m12 == 0 && result.m13 == 0 && ((result.m20 == -math.sin(angle)) || (result.m20 != result.m20)) && result.m21 == 0 && ((result.m22 == math.cos(angle)) || (result.m22 != result.m22)) && result.m23 == 0 && result.m30 == 0 && result.m31 == 0 && result.m32 == 0 && result.m33 == 1
fn mat4_rotate_z(angle: Float64) -> Mat4¶
Rotation around Z axis by angle radians (right-handed). O(1).
- Postcondition:
((result.m00 == math.cos(angle)) || (result.m00 != result.m00)) && ((result.m01 == -math.sin(angle)) || (result.m01 != result.m01)) && result.m02 == 0 && result.m03 == 0 && ((result.m10 == math.sin(angle)) || (result.m10 != result.m10)) && ((result.m11 == math.cos(angle)) || (result.m11 != result.m11)) && result.m12 == 0 && result.m13 == 0 && result.m20 == 0 && result.m21 == 0 && result.m22 == 1 && result.m23 == 0 && result.m30 == 0 && result.m31 == 0 && result.m32 == 0 && result.m33 == 1
fn mat4_perspective(fov: Float64, aspect: Float64, near: Float64, far: Float64) -> Mat4¶
Perspective projection matrix (right-handed, reverse Z [-1,1] NDC). fov: vertical field of view in radians, aspect: width/height, near/far: clipping planes. O(1).
- Postcondition:
((result.m00 == (1 / math.tan(fov * 0.5)) / aspect) || (result.m00 != result.m00)) && result.m01 == 0 && result.m02 == 0 && result.m03 == 0 && result.m10 == 0 && ((result.m11 == 1 / math.tan(fov * 0.5)) || (result.m11 != result.m11)) && result.m12 == 0 && result.m13 == 0 && result.m20 == 0 && result.m21 == 0 && ((result.m22 == (far + near) * (1 / (near - far))) || (result.m22 != result.m22)) && ((result.m23 == (2 * far * near) * (1 / (near - far))) || (result.m23 != result.m23)) && result.m30 == 0 && result.m31 == 0 && result.m32 == -1 && result.m33 == 0
fn mat4_look_at(eye: Vec3, target: Vec3, up: Vec3) -> Mat4¶
Look-at view matrix: camera at eye, looking at target, with up vector. Right-handed coordinate system. O(1).
- Postcondition:
result.m30 == 0 && result.m31 == 0 && result.m32 == 0 && result.m33 == 1
fn mat4_transform_vec3(m: Mat4, v: Vec3) -> Vec3¶
Transform a Vec3 point by a 4x4 matrix (x,y,z,1 homogeneous). O(16 ops).
- Postcondition:
((result.x == (m.m00 * v.x + m.m01 * v.y + m.m02 * v.z + m.m03) / (m.m30 * v.x + m.m31 * v.y + m.m32 * v.z + m.m33)) || (result.x != result.x) || (m.m30 * v.x + m.m31 * v.y + m.m32 * v.z + m.m33 == 0)) && ((result.y == (m.m10 * v.x + m.m11 * v.y + m.m12 * v.z + m.m13) / (m.m30 * v.x + m.m31 * v.y + m.m32 * v.z + m.m33)) || (result.y != result.y) || (m.m30 * v.x + m.m31 * v.y + m.m32 * v.z + m.m33 == 0)) && ((result.z == (m.m20 * v.x + m.m21 * v.y + m.m22 * v.z + m.m23) / (m.m30 * v.x + m.m31 * v.y + m.m32 * v.z + m.m33)) || (result.z != result.z) || (m.m30 * v.x + m.m31 * v.y + m.m32 * v.z + m.m33 == 0))
fn mat3_identity() -> Mat3¶
3x3 identity matrix. O(1).
- Postcondition:
result.m00 == 1 && result.m01 == 0 && result.m02 == 0 && result.m10 == 0 && result.m11 == 1 && result.m12 == 0 && result.m20 == 0 && result.m21 == 0 && result.m22 == 1
fn mat3_mul(a: Mat3, b: Mat3) -> Mat3¶
Multiply two 3x3 matrices: a * b. O(27 ops).
- Postcondition:
((result.m00 == a.m00 * b.m00 + a.m01 * b.m10 + a.m02 * b.m20) || (result.m00 != result.m00)) && ((result.m01 == a.m00 * b.m01 + a.m01 * b.m11 + a.m02 * b.m21) || (result.m01 != result.m01)) && ((result.m02 == a.m00 * b.m02 + a.m01 * b.m12 + a.m02 * b.m22) || (result.m02 != result.m02)) && ((result.m10 == a.m10 * b.m00 + a.m11 * b.m10 + a.m12 * b.m20) || (result.m10 != result.m10)) && ((result.m11 == a.m10 * b.m01 + a.m11 * b.m11 + a.m12 * b.m21) || (result.m11 != result.m11)) && ((result.m12 == a.m10 * b.m02 + a.m11 * b.m12 + a.m12 * b.m22) || (result.m12 != result.m12)) && ((result.m20 == a.m20 * b.m00 + a.m21 * b.m10 + a.m22 * b.m20) || (result.m20 != result.m20)) && ((result.m21 == a.m20 * b.m01 + a.m21 * b.m11 + a.m22 * b.m21) || (result.m21 != result.m21)) && ((result.m22 == a.m20 * b.m02 + a.m21 * b.m12 + a.m22 * b.m22) || (result.m22 != result.m22))
fn aabb_new(min: Vec3, max: Vec3) -> Aabb¶
Create an AABB from min and max corners.
- Postcondition:
((result.min.x == min.x) || (result.min.x != result.min.x)) && ((result.min.y == min.y) || (result.min.y != result.min.y)) && ((result.min.z == min.z) || (result.min.z != result.min.z)) && ((result.max.x == max.x) || (result.max.x != result.max.x)) && ((result.max.y == max.y) || (result.max.y != result.max.y)) && ((result.max.z == max.z) || (result.max.z != result.max.z))
fn aabb_contains_point(box: Aabb, point: Vec3) -> Bool¶
Test whether a point is inside the AABB (inclusive). O(1).
- Postcondition:
(result == true) => (point.x >= box.min.x && point.x <= box.max.x && point.y >= box.min.y && point.y <= box.max.y && point.z >= box.min.z && point.z <= box.max.z)
fn aabb_intersects_aabb(a: Aabb, b: Aabb) -> Bool¶
Test whether two AABBs intersect. O(1).
- Postcondition:
(result == true) => (!(a.max.x < b.min.x) && !(a.min.x > b.max.x) && !(a.max.y < b.min.y) && !(a.min.y > b.max.y) && !(a.max.z < b.min.z) && !(a.min.z > b.max.z))
fn sphere_new(center: Vec3, radius: Float64) -> Sphere¶
Create a sphere from centre and radius.
- Postcondition:
((result.center.x == center.x) || (result.center.x != result.center.x)) && ((result.center.y == center.y) || (result.center.y != result.center.y)) && ((result.center.z == center.z) || (result.center.z != result.center.z)) && ((result.radius == radius) || (result.radius != result.radius))
fn sphere_contains_point(s: Sphere, point: Vec3) -> Bool¶
Test whether a point is inside the sphere (inclusive). O(1).
- Postcondition:
(result == true) => (vec3_distance(s.center, point) <= s.radius)
fn ray_new(origin: Vec3, dir: Vec3) -> Ray¶
Create a ray from origin and direction.
- Postcondition:
((result.origin.x == origin.x) || (result.origin.x != result.origin.x)) && ((result.origin.y == origin.y) || (result.origin.y != result.origin.y)) && ((result.origin.z == origin.z) || (result.origin.z != result.origin.z)) && ((result.dir.x == dir.x) || (result.dir.x != result.dir.x)) && ((result.dir.y == dir.y) || (result.dir.y != result.dir.y)) && ((result.dir.z == dir.z) || (result.dir.z != result.dir.z))
fn ray_intersect_sphere(r: Ray, s: Sphere) -> Option[Float64]¶
Ray-sphere intersection. Returns Some(t) for the nearest hit, or None. t is the distance from origin along dir to the intersection point. Uses quadratic formula; only returns the smaller positive t. O(1).
- Postcondition:
(result.is_some == false) || ((2 * vec3_dot(vec3_sub(r.origin, s.center), r.dir)) * (2 * vec3_dot(vec3_sub(r.origin, s.center), r.dir)) - 4 * vec3_dot(r.dir, r.dir) * (vec3_dot(vec3_sub(r.origin, s.center), vec3_sub(r.origin, s.center)) - s.radius * s.radius) >= 0) || ((2 * vec3_dot(vec3_sub(r.origin, s.center), r.dir)) * (2 * vec3_dot(vec3_sub(r.origin, s.center), r.dir)) - 4 * vec3_dot(r.dir, r.dir) * (vec3_dot(vec3_sub(r.origin, s.center), vec3_sub(r.origin, s.center)) - s.radius * s.radius) != (2 * vec3_dot(vec3_sub(r.origin, s.center), r.dir)) * (2 * vec3_dot(vec3_sub(r.origin, s.center), r.dir)) - 4 * vec3_dot(r.dir, r.dir) * (vec3_dot(vec3_sub(r.origin, s.center), vec3_sub(r.origin, s.center)) - s.radius * s.radius))
fn ray_intersect_aabb(r: Ray, box: Aabb) -> Option[Float64]¶
Ray-AABB intersection (slab method). Returns Some(t_near) for intersection, or None if the ray misses the box. O(1). See: "An Efficient and Robust Ray-Box Intersection Algorithm" by Williams et al.
- Postcondition:
(result.is_some == false) || (r.dir.x == r.dir.x || r.dir.y == r.dir.y || r.dir.z == r.dir.z || r.origin.x == r.origin.x || r.origin.y == r.origin.y || r.origin.z == r.origin.z || box.min.x == box.min.x || box.min.y == box.min.y || box.min.z == box.min.z || box.max.x == box.max.x || box.max.y == box.max.y || box.max.z == box.max.z)
fn vec2_neg(v: Vec2) -> Vec2¶
Negate a 2D vector (component-wise -v). O(1).
- Postcondition:
result.x == -v.x && result.y == -v.y
fn vec2_reflect(incident: Vec2, normal: Vec2) -> Vec2¶
Reflect a 2D incident vector about a surface normal (normal must be unit). Formula: i - 2 * dot(i, n) * n. Degenerate (zero) normal returns incident. O(1).
- Postcondition:
(((result.x == incident.x) || (result.x != result.x)) && ((result.y == incident.y) || (result.y != result.y))) || (vec2_length(normal) != 0)
fn vec2_refract(incident: Vec2, normal: Vec2, eta: Float64) -> Option[Vec2]¶
Refract a 2D vector across an interface with relative index eta. Returns None on total internal reflection (k < 0). Both vectors should be unit length. Formula: etai - (etadot(i,n) + sqrt(k)) * n, k = 1 - eta2(1 - dot2). O(1).
- Postcondition:
(result.is_some == false) || !(1 - eta * eta * (1 - vec2_dot(incident, normal) * vec2_dot(incident, normal)) < 0)
fn vec2_project(a: Vec2, b: Vec2) -> Vec2¶
Project a onto b: b * dot(a,b) / dot(b,b). Returns zero if b is degenerate. O(1).
- Postcondition:
(result.x == 0 && result.y == 0) || (b.x != 0 || b.y != 0)
fn vec2_reject(a: Vec2, b: Vec2) -> Vec2¶
Reject a from b: a - project(a,b), the component of a perpendicular to b. O(1).
- Postcondition:
(((result.x == a.x) || (result.x != result.x)) && ((result.y == a.y) || (result.y != result.y))) || (b.x != 0 || b.y != 0)
fn vec2_angle_between(a: Vec2, b: Vec2) -> Float64¶
Angle (radians) between two 2D vectors in [0, PI]. Returns 0 if either is zero. Uses acos of the clamped dot product of the normalised vectors. O(1).
- Postcondition:
((result >= 0) && (result < 4)) || (result != result)
fn vec2_distance_squared(a: Vec2, b: Vec2) -> Float64¶
Squared Euclidean distance between two 2D points (avoids sqrt). O(1).
- Postcondition:
result >= 0
fn vec2_lerp_unclamped(a: Vec2, b: Vec2, t: Float64) -> Vec2¶
Unclamped linear interpolation between a and b by t (t may leave [0,1]). O(1).
- Postcondition:
result.x == a.x + (b.x - a.x) * t && result.y == a.y + (b.y - a.y) * t
fn vec2_nlerp(a: Vec2, b: Vec2, t: Float64) -> Vec2¶
Normalised linear interpolation (nlerp): lerp then normalise the result. O(1). Cheaper than slerp; not constant angular velocity.
- Postcondition:
(result.x == 0 && result.y == 0) || (a.x + (b.x - a.x) * t != 0 || a.y + (b.y - a.y) * t != 0)
fn vec2_rotate(v: Vec2, angle: Float64) -> Vec2¶
Rotate a 2D vector counter-clockwise by angle (radians) about the origin. Formula: (xcos - ysin, xsin + ycos). O(1).
- Postcondition:
((result.x == v.x * math.cos(angle) - v.y * math.sin(angle)) || (result.x != result.x)) && ((result.y == v.x * math.sin(angle) + v.y * math.cos(angle)) || (result.y != result.y))
fn vec2_rotate_around(v: Vec2, center: Vec2, angle: Float64) -> Vec2¶
Rotate v around an arbitrary center point by angle (radians). O(1). Translates to the origin, rotates, then translates back.
- Postcondition:
((result.x == ((v.x - center.x) * math.cos(angle) - (v.y - center.y) * math.sin(angle)) + center.x) || (result.x != result.x)) && ((result.y == ((v.x - center.x) * math.sin(angle) + (v.y - center.y) * math.cos(angle)) + center.y) || (result.y != result.y))
fn vec2_perpendicular(v: Vec2) -> Vec2¶
Return a vector perpendicular to v: (-y, x). This is v rotated by +90 degrees. O(1).
- Postcondition:
((result.x == -v.y) || (result.x != result.x)) && ((result.y == v.x) || (result.y != result.y))
fn vec2_from_angle(angle: Float64) -> Vec2¶
Unit vector from an angle (radians): (cos(angle), sin(angle)). O(1).
- Postcondition:
((result.x == math.cos(angle)) || (result.x != result.x)) && ((result.y == math.sin(angle)) || (result.y != result.y))
fn vec2_is_unit(v: Vec2, epsilon: Float64) -> Bool¶
True if the length of v is within epsilon of 1.0. O(1).
- Postcondition:
(result == true) => ((vec2_length(v) >= (1 - epsilon)) && (vec2_length(v) <= (1 + epsilon)))
fn vec2_is_zero(v: Vec2) -> Bool¶
True if every component of v is exactly zero. O(1).
- Postcondition:
(v.x == 0 && v.y == 0) => (result == true)
fn vec2_approx_eq(a: Vec2, b: Vec2, epsilon: Float64) -> Bool¶
True if every corresponding component of a and b differs by at most epsilon. O(1).
- Postcondition:
(result == true) => (((a.x - b.x) <= epsilon) && ((b.x - a.x) <= epsilon) && ((a.y - b.y) <= epsilon) && ((b.y - a.y) <= epsilon))
fn vec2_min_component(v: Vec2) -> Float64¶
Smallest component of a 2D vector. O(1).
- Postcondition:
result <= v.x && result <= v.y
fn vec2_max_component(v: Vec2) -> Float64¶
Largest component of a 2D vector. O(1).
- Postcondition:
result >= v.x && result >= v.y
fn vec2_abs(v: Vec2) -> Vec2¶
Component-wise absolute value of a 2D vector. O(1).
- Postcondition:
result.x >= 0 && result.y >= 0
fn vec2_clamp_length(v: Vec2, max_len: Float64) -> Vec2¶
Clamp the length of v to max_len. Vectors shorter than max_len are unchanged. If max_len <= 0 the zero vector is returned. O(1).
- Postcondition:
(((result.x == v.x) || (result.x != result.x)) && ((result.y == v.y) || (result.y != result.y))) || (vec2_length(v) > max_len)
fn vec3_neg(v: Vec3) -> Vec3¶
Negate a 3D vector (component-wise -v). O(1).
- Postcondition:
result.x == -v.x && result.y == -v.y && result.z == -v.z
fn vec3_reflect(incident: Vec3, normal: Vec3) -> Vec3¶
Reflect a 3D incident vector about a surface normal (normal must be unit). Formula: i - 2 * dot(i, n) * n. Degenerate (zero) normal returns incident. O(1).
- Postcondition:
(((result.x == incident.x) || (result.x != result.x)) && ((result.y == incident.y) || (result.y != result.y)) && ((result.z == incident.z) || (result.z != result.z))) || (vec3_length(normal) != 0)
fn vec3_refract(incident: Vec3, normal: Vec3, eta: Float64) -> Option[Vec3]¶
Refract a 3D vector across an interface with relative index eta. Returns None on total internal reflection (k < 0). Both vectors should be unit length. Formula: etai - (etadot(i,n) + sqrt(k)) * n, k = 1 - eta2(1 - dot2). O(1).
- Postcondition:
(result.is_some == false) || !(1 - eta * eta * (1 - vec3_dot(incident, normal) * vec3_dot(incident, normal)) < 0)
fn vec3_project(a: Vec3, b: Vec3) -> Vec3¶
Project a onto b: b * dot(a,b) / dot(b,b). Returns zero if b is degenerate. O(1).
- Postcondition:
(result.x == 0 && result.y == 0 && result.z == 0) || (b.x != 0 || b.y != 0 || b.z != 0)
fn vec3_reject(a: Vec3, b: Vec3) -> Vec3¶
Reject a from b: a - project(a,b), the component of a perpendicular to b. O(1).
- Postcondition:
(((result.x == a.x) || (result.x != result.x)) && ((result.y == a.y) || (result.y != result.y)) && ((result.z == a.z) || (result.z != result.z))) || (b.x != 0 || b.y != 0 || b.z != 0)
fn vec3_angle_between(a: Vec3, b: Vec3) -> Float64¶
Angle (radians) between two 3D vectors in [0, PI]. Returns 0 if either is zero. Uses acos of the clamped dot product of the normalised vectors. O(1).
- Postcondition:
((result >= 0) && (result < 4)) || (result != result)
fn vec3_distance_squared(a: Vec3, b: Vec3) -> Float64¶
Squared Euclidean distance between two 3D points (avoids sqrt). O(1).
- Postcondition:
result >= 0
fn vec3_lerp_unclamped(a: Vec3, b: Vec3, t: Float64) -> Vec3¶
Unclamped linear interpolation between a and b by t (t may leave [0,1]). O(1). Contrast with vec3_lerp which clamps t into [0,1].
- Postcondition:
result.x == a.x + (b.x - a.x) * t && result.y == a.y + (b.y - a.y) * t && result.z == a.z + (b.z - a.z) * t
fn vec3_nlerp(a: Vec3, b: Vec3, t: Float64) -> Vec3¶
Normalised linear interpolation (nlerp): lerp then normalise the result. O(1). Cheaper than slerp; not constant angular velocity.
- Postcondition:
(result.x == 0 && result.y == 0 && result.z == 0) || (a.x + (b.x - a.x) * t != 0 || a.y + (b.y - a.y) * t != 0 || a.z + (b.z - a.z) * t != 0)
fn vec3_orthogonal(v: Vec3) -> Vec3¶
Return any vector perpendicular to v (unit length), using the smallest-absolute-component zero method to avoid cancellation. Returns the zero vector when v is zero. O(1).
- Postcondition:
(result.x == 0 && result.y == 0 && result.z == 0) || (v.x != 0 || v.y != 0 || v.z != 0)
fn vec3_is_unit(v: Vec3, epsilon: Float64) -> Bool¶
True if the length of v is within epsilon of 1.0. O(1).
- Postcondition:
(result == true) => ((vec3_length(v) >= (1 - epsilon)) && (vec3_length(v) <= (1 + epsilon)))
fn vec3_is_zero(v: Vec3) -> Bool¶
True if every component of v is exactly zero. O(1).
- Postcondition:
(v.x == 0 && v.y == 0 && v.z == 0) => (result == true)
fn vec3_approx_eq(a: Vec3, b: Vec3, epsilon: Float64) -> Bool¶
True if every corresponding component of a and b differs by at most epsilon. O(1).
- Postcondition:
(result == true) => (((a.x - b.x) <= epsilon) && ((b.x - a.x) <= epsilon) && ((a.y - b.y) <= epsilon) && ((b.y - a.y) <= epsilon) && ((a.z - b.z) <= epsilon) && ((b.z - a.z) <= epsilon))
fn vec3_min_component(v: Vec3) -> Float64¶
Smallest component of a 3D vector. O(1).
- Postcondition:
result <= v.x && result <= v.y && result <= v.z
fn vec3_max_component(v: Vec3) -> Float64¶
Largest component of a 3D vector. O(1).
- Postcondition:
result >= v.x && result >= v.y && result >= v.z
fn vec3_abs(v: Vec3) -> Vec3¶
Component-wise absolute value of a 3D vector. O(1).
- Postcondition:
result.x >= 0 && result.y >= 0 && result.z >= 0
fn vec3_clamp_length(v: Vec3, max_len: Float64) -> Vec3¶
Clamp the length of v to max_len. Vectors shorter than max_len are unchanged. If max_len <= 0 the zero vector is returned. O(1).
- Postcondition:
(((result.x == v.x) || (result.x != result.x)) && ((result.y == v.y) || (result.y != result.y)) && ((result.z == v.z) || (result.z != result.z))) || (vec3_length(v) > max_len)
fn vec4_mul_component(a: Vec4, b: Vec4) -> Vec4¶
Multiply two 4D vectors component-wise (Hadamard product). O(1).
- Postcondition:
result.x == a.x * b.x && result.y == a.y * b.y && result.z == a.z * b.z && result.w == a.w * b.w
fn vec4_div_component(a: Vec4, b: Vec4) -> Vec4¶
Divide a by b component-wise. O(1).
- Postcondition:
result.x == a.x / b.x && result.y == a.y / b.y && result.z == a.z / b.z && result.w == a.w / b.w
fn vec4_scale(v: Vec4, s: Float64) -> Vec4¶
Multiply each component of v by scalar s. O(1).
- Postcondition:
result.x == v.x * s && result.y == v.y * s && result.z == v.z * s && result.w == v.w * s
fn vec4_neg(v: Vec4) -> Vec4¶
Negate a 4D vector (component-wise -v). O(1).
- Postcondition:
result.x == -v.x && result.y == -v.y && result.z == -v.z && result.w == -v.w
fn vec4_approx_eq(a: Vec4, b: Vec4, epsilon: Float64) -> Bool¶
True if every corresponding component of a and b differs by at most epsilon. O(1).
- Postcondition:
(result == true) => (((a.x - b.x) <= epsilon) && ((b.x - a.x) <= epsilon) && ((a.y - b.y) <= epsilon) && ((b.y - a.y) <= epsilon) && ((a.z - b.z) <= epsilon) && ((b.z - a.z) <= epsilon) && ((a.w - b.w) <= epsilon) && ((b.w - a.w) <= epsilon))
fn vec4_min_component(v: Vec4) -> Float64¶
Smallest component of a 4D vector. O(1).
- Postcondition:
result <= v.x && result <= v.y && result <= v.z && result <= v.w
fn vec4_max_component(v: Vec4) -> Float64¶
Largest component of a 4D vector. O(1).
- Postcondition:
result >= v.x && result >= v.y && result >= v.z && result >= v.w
fn vec4_abs(v: Vec4) -> Vec4¶
Component-wise absolute value of a 4D vector. O(1).
- Postcondition:
result.x >= 0 && result.y >= 0 && result.z >= 0 && result.w >= 0
fn vec3_from_vec4(v: Vec4) -> Vec3¶
Drop the w component of a 4D vector to produce a 3D vector. O(1).
- Postcondition:
((result.x == v.x) || (result.x != result.x)) && ((result.y == v.y) || (result.y != result.y)) && ((result.z == v.z) || (result.z != result.z))
fn vec4_from_vec3(v: Vec3, w: Float64) -> Vec4¶
Build a 4D vector from a 3D vector plus an explicit w component. O(1).
- Postcondition:
((result.x == v.x) || (result.x != result.x)) && ((result.y == v.y) || (result.y != result.y)) && ((result.z == v.z) || (result.z != result.z)) && ((result.w == w) || (result.w != result.w))
fn mat2_identity() -> Mat2¶
2x2 identity matrix. O(1).
- Postcondition:
result.m00 == 1 && result.m01 == 0 && result.m10 == 0 && result.m11 == 1
fn mat2_mul(a: Mat2, b: Mat2) -> Mat2¶
Multiply two 2x2 matrices: a * b. O(8 ops).
- Postcondition:
((result.m00 == a.m00 * b.m00 + a.m01 * b.m10) || (result.m00 != result.m00)) && ((result.m01 == a.m00 * b.m01 + a.m01 * b.m11) || (result.m01 != result.m01)) && ((result.m10 == a.m10 * b.m00 + a.m11 * b.m10) || (result.m10 != result.m10)) && ((result.m11 == a.m10 * b.m01 + a.m11 * b.m11) || (result.m11 != result.m11))
fn mat2_transpose(m: Mat2) -> Mat2¶
Transpose a 2x2 matrix in place. O(1).
- Postcondition:
((result.m00 == m.m00) || (result.m00 != result.m00)) && ((result.m01 == m.m10) || (result.m01 != result.m01)) && ((result.m10 == m.m01) || (result.m10 != result.m10)) && ((result.m11 == m.m11) || (result.m11 != result.m11))
fn mat2_determinant(m: Mat2) -> Float64¶
Determinant of a 2x2 matrix: m00m11 - m01m10. O(1).
- Postcondition:
(result == m.m00 * m.m11 - m.m01 * m.m10) || (result != result)
fn mat2_inverse(m: Mat2) -> Option[Mat2]¶
Inverse of a 2x2 matrix via the adjugate / determinant formula. Returns None when the determinant is (near) zero, so the matrix is singular. O(1).
- Postcondition:
(result.is_some == false) || !(math.abs_float(m.m00 * m.m11 - m.m01 * m.m10) < 0.000001 * 0.000001)
fn mat2_scale(s: Float64) -> Mat2¶
Uniform 2x2 scale matrix with factor s. O(1).
- Postcondition:
((result.m00 == s) || (result.m00 != result.m00)) && result.m01 == 0 && result.m10 == 0 && ((result.m11 == s) || (result.m11 != result.m11))
fn mat2_rotation(angle: Float64) -> Mat2¶
2x2 rotation matrix by angle radians (counter-clockwise). O(1).
- Postcondition:
((result.m00 == math.cos(angle)) || (result.m00 != result.m00)) && ((result.m01 == -math.sin(angle)) || (result.m01 != result.m01)) && ((result.m10 == math.sin(angle)) || (result.m10 != result.m10)) && ((result.m11 == math.cos(angle)) || (result.m11 != result.m11))
fn mat2_transform_vec2(m: Mat2, v: Vec2) -> Vec2¶
Transform a 2D vector by a 2x2 matrix: M * v. O(4 ops).
- Postcondition:
((result.x == m.m00 * v.x + m.m01 * v.y) || (result.x != result.x)) && ((result.y == m.m10 * v.x + m.m11 * v.y) || (result.y != result.y))
fn mat3_transpose(m: Mat3) -> Mat3¶
Transpose a 3x3 matrix. O(1).
- Postcondition:
((result.m00 == m.m00) || (result.m00 != result.m00)) && ((result.m01 == m.m10) || (result.m01 != result.m01)) && ((result.m02 == m.m20) || (result.m02 != result.m02)) && ((result.m10 == m.m01) || (result.m10 != result.m10)) && ((result.m11 == m.m11) || (result.m11 != result.m11)) && ((result.m12 == m.m21) || (result.m12 != result.m12)) && ((result.m20 == m.m02) || (result.m20 != result.m20)) && ((result.m21 == m.m12) || (result.m21 != result.m21)) && ((result.m22 == m.m22) || (result.m22 != result.m22))
fn mat3_determinant(m: Mat3) -> Float64¶
Determinant of a 3x3 matrix by cofactor expansion along the first row. O(9 ops).
- Postcondition:
(result == m.m00 * (m.m11 * m.m22 - m.m12 * m.m21) - m.m01 * (m.m10 * m.m22 - m.m12 * m.m20) + m.m02 * (m.m10 * m.m21 - m.m11 * m.m20)) || (result != result)
fn mat3_inverse(m: Mat3) -> Option[Mat3]¶
Inverse of a 3x3 matrix via the adjugate / determinant formula. Returns None when the determinant is (near) zero, so the matrix is singular. O(27 ops).
- Postcondition:
(result.is_some == false) || !(math.abs_float(m.m00 * (m.m11 * m.m22 - m.m12 * m.m21) - m.m01 * (m.m10 * m.m22 - m.m12 * m.m20) + m.m02 * (m.m10 * m.m21 - m.m11 * m.m20)) < 0.000001 * 0.000001)
fn mat3_transform_vec3(m: Mat3, v: Vec3) -> Vec3¶
Transform a 3D vector by a 3x3 matrix: M * v. O(9 ops).
- Postcondition:
((result.x == m.m00 * v.x + m.m01 * v.y + m.m02 * v.z) || (result.x != result.x)) && ((result.y == m.m10 * v.x + m.m11 * v.y + m.m12 * v.z) || (result.y != result.y)) && ((result.z == m.m20 * v.x + m.m21 * v.y + m.m22 * v.z) || (result.z != result.z))
fn mat3_scale(s: Float64) -> Mat3¶
Uniform 3x3 scale matrix with factor s. O(1).
- Postcondition:
((result.m00 == s) || (result.m00 != result.m00)) && result.m01 == 0 && result.m02 == 0 && result.m10 == 0 && ((result.m11 == s) || (result.m11 != result.m11)) && result.m12 == 0 && result.m20 == 0 && result.m21 == 0 && ((result.m22 == s) || (result.m22 != result.m22))
fn mat3_scale_xyz(x: Float64, y: Float64, z: Float64) -> Mat3¶
Non-uniform 3x3 scale matrix with per-axis factors. O(1).
- Postcondition:
((result.m00 == x) || (result.m00 != result.m00)) && result.m01 == 0 && result.m02 == 0 && result.m10 == 0 && ((result.m11 == y) || (result.m11 != result.m11)) && result.m12 == 0 && result.m20 == 0 && result.m21 == 0 && ((result.m22 == z) || (result.m22 != result.m22))
fn mat3_rotation_x(angle: Float64) -> Mat3¶
3x3 rotation around the X axis by angle radians (right-handed). O(1).
- Postcondition:
result.m00 == 1 && result.m01 == 0 && result.m02 == 0 && result.m10 == 0 && ((result.m11 == math.cos(angle)) || (result.m11 != result.m11)) && ((result.m12 == -math.sin(angle)) || (result.m12 != result.m12)) && result.m20 == 0 && ((result.m21 == math.sin(angle)) || (result.m21 != result.m21)) && ((result.m22 == math.cos(angle)) || (result.m22 != result.m22))
fn mat3_rotation_y(angle: Float64) -> Mat3¶
3x3 rotation around the Y axis by angle radians (right-handed). O(1).
- Postcondition:
((result.m00 == math.cos(angle)) || (result.m00 != result.m00)) && result.m01 == 0 && ((result.m02 == math.sin(angle)) || (result.m02 != result.m02)) && result.m10 == 0 && result.m11 == 1 && result.m12 == 0 && ((result.m20 == -math.sin(angle)) || (result.m20 != result.m20)) && result.m21 == 0 && ((result.m22 == math.cos(angle)) || (result.m22 != result.m22))
fn mat3_rotation_z(angle: Float64) -> Mat3¶
3x3 rotation around the Z axis by angle radians (right-handed). O(1).
- Postcondition:
((result.m00 == math.cos(angle)) || (result.m00 != result.m00)) && ((result.m01 == -math.sin(angle)) || (result.m01 != result.m01)) && result.m02 == 0 && ((result.m10 == math.sin(angle)) || (result.m10 != result.m10)) && ((result.m11 == math.cos(angle)) || (result.m11 != result.m11)) && result.m12 == 0 && result.m20 == 0 && result.m21 == 0 && result.m22 == 1
fn mat3_from_quat(q: Quaternion) -> Mat3¶
Rotation matrix from a (unit) quaternion. The quaternion is normalised first. Formula: the standard 3x3 rotation matrix derived from q. O(27 ops).
- Postcondition:
(q.x == 0 && q.y == 0 && q.z == 0 && q.w == 0) => (result.m00 == 1 && result.m01 == 0 && result.m02 == 0 && result.m10 == 0 && result.m11 == 1 && result.m12 == 0 && result.m20 == 0 && result.m21 == 0 && result.m22 == 1)
fn mat4_transpose(m: Mat4) -> Mat4¶
Transpose a 4x4 matrix. O(1).
- Postcondition:
((result.m00 == m.m00) || (result.m00 != result.m00)) && ((result.m01 == m.m10) || (result.m01 != result.m01)) && ((result.m02 == m.m20) || (result.m02 != result.m02)) && ((result.m03 == m.m30) || (result.m03 != result.m03)) && ((result.m10 == m.m01) || (result.m10 != result.m10)) && ((result.m11 == m.m11) || (result.m11 != result.m11)) && ((result.m12 == m.m21) || (result.m12 != result.m12)) && ((result.m13 == m.m31) || (result.m13 != result.m13)) && ((result.m20 == m.m02) || (result.m20 != result.m20)) && ((result.m21 == m.m12) || (result.m21 != result.m21)) && ((result.m22 == m.m22) || (result.m22 != result.m22)) && ((result.m23 == m.m32) || (result.m23 != result.m23)) && ((result.m30 == m.m03) || (result.m30 != result.m30)) && ((result.m31 == m.m13) || (result.m31 != result.m31)) && ((result.m32 == m.m23) || (result.m32 != result.m32)) && ((result.m33 == m.m33) || (result.m33 != result.m33))
fn mat4_determinant(m: Mat4) -> Float64¶
Determinant of a 4x4 matrix by cofactor expansion along the first row. O(48 ops). Uses 3x3 sub-determinants of the three lower rows.
- Postcondition:
(result == m.m00 * (m.m11 * (m.m22 * m.m33 - m.m23 * m.m32) - m.m12 * (m.m21 * m.m33 - m.m23 * m.m31) + m.m13 * (m.m21 * m.m32 - m.m22 * m.m31)) - m.m01 * (m.m10 * (m.m22 * m.m33 - m.m23 * m.m32) - m.m12 * (m.m20 * m.m33 - m.m23 * m.m30) + m.m13 * (m.m20 * m.m32 - m.m22 * m.m30)) + m.m02 * (m.m10 * (m.m21 * m.m33 - m.m23 * m.m31) - m.m11 * (m.m20 * m.m33 - m.m23 * m.m30) + m.m13 * (m.m20 * m.m31 - m.m21 * m.m30)) - m.m03 * (m.m10 * (m.m21 * m.m32 - m.m22 * m.m31) - m.m11 * (m.m20 * m.m32 - m.m22 * m.m30) + m.m12 * (m.m20 * m.m31 - m.m21 * m.m30))) || (result != result)
fn mat4_inverse(m: Mat4) -> Option[Mat4]¶
Inverse of a 4x4 matrix via the adjugate method (cofactor transpose / det). Returns None when |det| < 1e-12, so the matrix is singular. O(150 ops).
- Postcondition:
(result.is_some == false) || !(math.abs_float(m.m00 * (m.m11 * (m.m22 * m.m33 - m.m23 * m.m32) - m.m12 * (m.m21 * m.m33 - m.m23 * m.m31) + m.m13 * (m.m21 * m.m32 - m.m22 * m.m31)) - m.m01 * (m.m10 * (m.m22 * m.m33 - m.m23 * m.m32) - m.m12 * (m.m20 * m.m33 - m.m23 * m.m30) + m.m13 * (m.m20 * m.m32 - m.m22 * m.m30)) + m.m02 * (m.m10 * (m.m21 * m.m33 - m.m23 * m.m31) - m.m11 * (m.m20 * m.m33 - m.m23 * m.m30) + m.m13 * (m.m20 * m.m31 - m.m21 * m.m30)) - m.m03 * (m.m10 * (m.m21 * m.m32 - m.m22 * m.m31) - m.m11 * (m.m20 * m.m32 - m.m22 * m.m30) + m.m12 * (m.m20 * m.m31 - m.m21 * m.m30))) < 0.000001 * 0.000001)
fn mat4_transform_vec4(m: Mat4, v: Vec4) -> Vec4¶
Transform a Vec4 (homogeneous) by a 4x4 matrix: M * v, no perspective divide. O(16 ops).
- Postcondition:
((result.x == m.m00 * v.x + m.m01 * v.y + m.m02 * v.z + m.m03 * v.w) || (result.x != result.x)) && ((result.y == m.m10 * v.x + m.m11 * v.y + m.m12 * v.z + m.m13 * v.w) || (result.y != result.y)) && ((result.z == m.m20 * v.x + m.m21 * v.y + m.m22 * v.z + m.m23 * v.w) || (result.z != result.z)) && ((result.w == m.m30 * v.x + m.m31 * v.y + m.m32 * v.z + m.m33 * v.w) || (result.w != result.w))
fn mat4_transform_point(m: Mat4, p: Vec3) -> Vec3¶
Transform a point (w=1) by a 4x4 matrix, including perspective divide. If the transformed w is zero, returns the zero vector. O(16 ops).
- Postcondition:
((result.x == (m.m00 * p.x + m.m01 * p.y + m.m02 * p.z + m.m03) / (m.m30 * p.x + m.m31 * p.y + m.m32 * p.z + m.m33)) || (result.x != result.x) || (m.m30 * p.x + m.m31 * p.y + m.m32 * p.z + m.m33 == 0)) && ((result.y == (m.m10 * p.x + m.m11 * p.y + m.m12 * p.z + m.m13) / (m.m30 * p.x + m.m31 * p.y + m.m32 * p.z + m.m33)) || (result.y != result.y) || (m.m30 * p.x + m.m31 * p.y + m.m32 * p.z + m.m33 == 0)) && ((result.z == (m.m20 * p.x + m.m21 * p.y + m.m22 * p.z + m.m23) / (m.m30 * p.x + m.m31 * p.y + m.m32 * p.z + m.m33)) || (result.z != result.z) || (m.m30 * p.x + m.m31 * p.y + m.m32 * p.z + m.m33 == 0))
fn mat4_transform_direction(m: Mat4, d: Vec3) -> Vec3¶
Transform a direction (w=0) by a 4x4 matrix: rotation/scale only, translation is ignored and no perspective divide is applied. O(9 ops).
- Postcondition:
((result.x == m.m00 * d.x + m.m01 * d.y + m.m02 * d.z) || (result.x != result.x)) && ((result.y == m.m10 * d.x + m.m11 * d.y + m.m12 * d.z) || (result.y != result.y)) && ((result.z == m.m20 * d.x + m.m21 * d.y + m.m22 * d.z) || (result.z != result.z))
fn mat4_from_scale(x: Float64, y: Float64, z: Float64) -> Mat4¶
Scale matrix (non-uniform) from three axis factors. Same as mat4_scale. O(1).
- Postcondition:
((result.m00 == x) || (result.m00 != result.m00)) && result.m01 == 0 && result.m02 == 0 && result.m03 == 0 && result.m10 == 0 && ((result.m11 == y) || (result.m11 != result.m11)) && result.m12 == 0 && result.m13 == 0 && result.m20 == 0 && result.m21 == 0 && ((result.m22 == z) || (result.m22 != result.m22)) && result.m23 == 0 && result.m30 == 0 && result.m31 == 0 && result.m32 == 0 && result.m33 == 1
fn mat4_from_translation(t: Vec3) -> Mat4¶
Translation matrix from a Vec3 offset. O(1).
- Postcondition:
result.m00 == 1 && result.m01 == 0 && result.m02 == 0 && ((result.m03 == t.x) || (result.m03 != result.m03)) && result.m10 == 0 && result.m11 == 1 && result.m12 == 0 && ((result.m13 == t.y) || (result.m13 != result.m13)) && result.m20 == 0 && result.m21 == 0 && result.m22 == 1 && ((result.m23 == t.z) || (result.m23 != result.m23)) && result.m30 == 0 && result.m31 == 0 && result.m32 == 0 && result.m33 == 1
fn mat4_translation_xyz(x: Float64, y: Float64, z: Float64) -> Mat4¶
Translation matrix from three components. Same as mat4_translate. O(1).
- Postcondition:
result.m00 == 1 && result.m01 == 0 && result.m02 == 0 && ((result.m03 == x) || (result.m03 != result.m03)) && result.m10 == 0 && result.m11 == 1 && result.m12 == 0 && ((result.m13 == y) || (result.m13 != result.m13)) && result.m20 == 0 && result.m21 == 0 && result.m22 == 1 && ((result.m23 == z) || (result.m23 != result.m23)) && result.m30 == 0 && result.m31 == 0 && result.m32 == 0 && result.m33 == 1
fn mat4_from_rotation_x(angle: Float64) -> Mat4¶
Rotation around the X axis. Same as mat4_rotate_x. O(1).
- Postcondition:
result.m00 == 1 && result.m01 == 0 && result.m02 == 0 && result.m03 == 0 && result.m10 == 0 && ((result.m11 == math.cos(angle)) || (result.m11 != result.m11)) && ((result.m12 == -math.sin(angle)) || (result.m12 != result.m12)) && result.m13 == 0 && result.m20 == 0 && ((result.m21 == math.sin(angle)) || (result.m21 != result.m21)) && ((result.m22 == math.cos(angle)) || (result.m22 != result.m22)) && result.m23 == 0 && result.m30 == 0 && result.m31 == 0 && result.m32 == 0 && result.m33 == 1
fn mat4_from_rotation_y(angle: Float64) -> Mat4¶
Rotation around the Y axis. Same as mat4_rotate_y. O(1).
- Postcondition:
((result.m00 == math.cos(angle)) || (result.m00 != result.m00)) && result.m01 == 0 && ((result.m02 == math.sin(angle)) || (result.m02 != result.m02)) && result.m03 == 0 && result.m10 == 0 && result.m11 == 1 && result.m12 == 0 && result.m13 == 0 && ((result.m20 == -math.sin(angle)) || (result.m20 != result.m20)) && result.m21 == 0 && ((result.m22 == math.cos(angle)) || (result.m22 != result.m22)) && result.m23 == 0 && result.m30 == 0 && result.m31 == 0 && result.m32 == 0 && result.m33 == 1
fn mat4_from_rotation_z(angle: Float64) -> Mat4¶
Rotation around the Z axis. Same as mat4_rotate_z. O(1).
- Postcondition:
((result.m00 == math.cos(angle)) || (result.m00 != result.m00)) && ((result.m01 == -math.sin(angle)) || (result.m01 != result.m01)) && result.m02 == 0 && result.m03 == 0 && ((result.m10 == math.sin(angle)) || (result.m10 != result.m10)) && ((result.m11 == math.cos(angle)) || (result.m11 != result.m11)) && result.m12 == 0 && result.m13 == 0 && result.m20 == 0 && result.m21 == 0 && result.m22 == 1 && result.m23 == 0 && result.m30 == 0 && result.m31 == 0 && result.m32 == 0 && result.m33 == 1
fn mat4_from_quat(q: Quaternion) -> Mat4¶
Rotation matrix from a (unit) quaternion. The quaternion is normalised first. Formula: the standard 4x4 rotation matrix derived from q. O(27 ops).
- Postcondition:
result.m03 == 0 && result.m13 == 0 && result.m23 == 0 && result.m30 == 0 && result.m31 == 0 && result.m32 == 0 && result.m33 == 1
fn mat4_rotation_axis_angle(axis: Vec3, angle: Float64) -> Mat4¶
Rotation matrix about an arbitrary axis (unit length) by angle radians. Uses the Rodrigues formula. The axis is normalised first. O(30 ops).
- Postcondition:
result.m03 == 0 && result.m13 == 0 && result.m23 == 0 && result.m30 == 0 && result.m31 == 0 && result.m32 == 0 && result.m33 == 1
fn mat4_orthographic(l: Float64, r: Float64, b: Float64, t: Float64, n: Float64, f: Float64) -> Mat4¶
Orthographic projection matrix (right-handed, standard OpenGL mapping). Maps [l,r]x[b,t]x[n,f] to NDC [-1,1]^3. l != r, b != t, n != f required. O(1).
- Postcondition:
((result.m00 == 2 / (r - l)) || (result.m00 != result.m00)) && result.m01 == 0 && result.m02 == 0 && ((result.m03 == -(r + l) / (r - l)) || (result.m03 != result.m03)) && result.m10 == 0 && ((result.m11 == 2 / (t - b)) || (result.m11 != result.m11)) && result.m12 == 0 && ((result.m13 == -(t + b) / (t - b)) || (result.m13 != result.m13)) && result.m20 == 0 && result.m21 == 0 && ((result.m22 == -2 / (f - n)) || (result.m22 != result.m22)) && ((result.m23 == -(f + n) / (f - n)) || (result.m23 != result.m23)) && result.m30 == 0 && result.m31 == 0 && result.m32 == 0 && result.m33 == 1
fn mat4_is_identity(m: Mat4, eps: Float64) -> Bool¶
True if every element of m is within epsilon of the identity matrix. O(16 ops).
- Postcondition:
(result == true) => ((m.m00 >= (1 - eps)) && (m.m00 <= (1 + eps)) && (m.m11 >= (1 - eps)) && (m.m11 <= (1 + eps)) && (m.m22 >= (1 - eps)) && (m.m22 <= (1 + eps)) && (m.m33 >= (1 - eps)) && (m.m33 <= (1 + eps)))
fn mat4_approx_eq(a: Mat4, b: Mat4, eps: Float64) -> Bool¶
True if every corresponding element of a and b differs by at most epsilon. O(16 ops).
- Postcondition:
(result == true) => (((a.m00 - b.m00) <= eps) && ((b.m00 - a.m00) <= eps) && ((a.m01 - b.m01) <= eps) && ((b.m01 - a.m01) <= eps) && ((a.m02 - b.m02) <= eps) && ((b.m02 - a.m02) <= eps) && ((a.m03 - b.m03) <= eps) && ((b.m03 - a.m03) <= eps) && ((a.m10 - b.m10) <= eps) && ((b.m10 - a.m10) <= eps) && ((a.m11 - b.m11) <= eps) && ((b.m11 - a.m11) <= eps) && ((a.m12 - b.m12) <= eps) && ((b.m12 - a.m12) <= eps) && ((a.m13 - b.m13) <= eps) && ((b.m13 - a.m13) <= eps) && ((a.m20 - b.m20) <= eps) && ((b.m20 - a.m20) <= eps) && ((a.m21 - b.m21) <= eps) && ((b.m21 - a.m21) <= eps) && ((a.m22 - b.m22) <= eps) && ((b.m22 - a.m22) <= eps) && ((a.m23 - b.m23) <= eps) && ((b.m23 - a.m23) <= eps) && ((a.m30 - b.m30) <= eps) && ((b.m30 - a.m30) <= eps) && ((a.m31 - b.m31) <= eps) && ((b.m31 - a.m31) <= eps) && ((a.m32 - b.m32) <= eps) && ((b.m32 - a.m32) <= eps) && ((a.m33 - b.m33) <= eps) && ((b.m33 - a.m33) <= eps))
fn quat_from_axis_angle(axis: Vec3, angle: Float64) -> Quaternion¶
Create a quaternion from an axis and angle (radians). The axis is normalised first, so any (non-zero) axis is accepted. Rotation is right-handed. O(1).
- Postcondition:
((result.x == vec3_normalize(axis).x * math.sin(angle * 0.5)) || (result.x != result.x)) && ((result.y == vec3_normalize(axis).y * math.sin(angle * 0.5)) || (result.y != result.y)) && ((result.z == vec3_normalize(axis).z * math.sin(angle * 0.5)) || (result.z != result.z)) && ((result.w == math.cos(angle * 0.5)) || (result.w != result.w))
fn quat_mul_vec3(q: Quaternion, v: Vec3) -> Vec3¶
Rotate a 3D vector by a quaternion: q * v * q^-1 (q must be unit length). Same as quat_rotate_vec3, provided under the mul_vec3 name. O(1).
- Postcondition:
(((result.x == v.x) || (result.x != result.x)) && ((result.y == v.y) || (result.y != result.y)) && ((result.z == v.z) || (result.z != result.z))) || (q.x != 0 || q.y != 0 || q.z != 0 || q.w != 1)
fn quat_inverse(q: Quaternion) -> Quaternion¶
Inverse of a quaternion: the conjugate of the normalised quaternion. For a unit quaternion the conjugate is exactly the inverse. O(1).
- Postcondition:
(result.x == 0 && result.y == 0 && result.z == 0 && result.w == 1) || (q.x != 0 || q.y != 0 || q.z != 0 || q.w != 0)
fn quat_dot(a: Quaternion, b: Quaternion) -> Float64¶
Dot product of two quaternions (4-vector dot). O(4 ops).
- Postcondition:
(result == a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w) || (result != result)
fn quat_length(q: Quaternion) -> Float64¶
Length (magnitude) of a quaternion. O(4 ops + sqrt).
- Postcondition:
(result >= 0) || (result != result)
fn quat_is_unit(q: Quaternion, eps: Float64) -> Bool¶
True if the length of q is within epsilon of 1.0. O(1).
- Postcondition:
(result == true) => ((quat_length(q) >= (1 - eps)) && (quat_length(q) <= (1 + eps)))
fn quat_slerp(a: Quaternion, b: Quaternion, t: Float64) -> Quaternion¶
Spherical linear interpolation between two quaternions by t in [0,1]. Handles the shortest path by negating b when dot(a,b) < 0, clamps the dot to [-1,1], and falls back to nlerp when a and b are nearly parallel. O(1).
- Postcondition:
(((result.x == a.x) || (result.x != result.x)) && ((result.y == a.y) || (result.y != result.y)) && ((result.z == a.z) || (result.z != result.z)) && ((result.w == a.w) || (result.w != result.w))) || (((result.x == b.x) || (result.x != result.x)) && ((result.y == b.y) || (result.y != result.y)) && ((result.z == b.z) || (result.z != result.z)) && ((result.w == b.w) || (result.w != result.w))) || (!(t <= 0) && !(t >= 1))
fn quat_nlerp(a: Quaternion, b: Quaternion, t: Float64) -> Quaternion¶
Normalised linear interpolation between two quaternions (fast, not constant angular velocity). t is clamped into [0,1]. O(1).
- Postcondition:
(result.x == 0 && result.y == 0 && result.z == 0 && result.w == 1) || (a.x != 0 || a.y != 0 || a.z != 0 || a.w != 0 || b.x != 0 || b.y != 0 || b.z != 0 || b.w != 0)
fn quat_from_mat4(m: &Mat4) -> Quaternion¶
Extract the quaternion from a rotation matrix using the standard trace method. Handles all three largest-diagonal cases to avoid degenerate sqrt. O(1).
- Postcondition:
(m.m00 == 1 && m.m01 == 0 && m.m02 == 0 && m.m03 == 0 && m.m10 == 0 && m.m11 == 1 && m.m12 == 0 && m.m13 == 0 && m.m20 == 0 && m.m21 == 0 && m.m22 == 1 && m.m23 == 0 && m.m30 == 0 && m.m31 == 0 && m.m32 == 0 && m.m33 == 1) => (result.x == 0 && result.y == 0 && result.z == 0 && result.w == 1)
fn quat_to_mat4(q: Quaternion) -> Mat4¶
4x4 rotation matrix from a quaternion. Same result as mat4_from_quat. O(1).
- Postcondition:
result.m03 == 0 && result.m13 == 0 && result.m23 == 0 && result.m30 == 0 && result.m31 == 0 && result.m32 == 0 && result.m33 == 1
fn quat_to_mat3(q: Quaternion) -> Mat3¶
3x3 rotation matrix from a quaternion. Same result as mat3_from_quat. O(1).
- Postcondition:
(q.x == 0 && q.y == 0 && q.z == 0 && q.w == 0) => (result.m00 == 1 && result.m01 == 0 && result.m02 == 0 && result.m10 == 0 && result.m11 == 1 && result.m12 == 0 && result.m20 == 0 && result.m21 == 0 && result.m22 == 1)
fn quat_roll(q: Quaternion) -> Float64¶
Roll (rotation around X, radians) extracted from a quaternion. Conventions match quat_from_euler (ZYX intrinsic). O(1).
- Postcondition:
((result >= -3.15) && (result <= 3.15)) || (result != result)
fn quat_pitch(q: Quaternion) -> Float64¶
Pitch (rotation around Y, radians) extracted from a quaternion. Conventions match quat_from_euler (ZYX intrinsic). Input to asin is clamped. O(1).
- Postcondition:
((result >= -1.6) && (result <= 1.6)) || (result != result)
fn quat_yaw(q: Quaternion) -> Float64¶
Yaw (rotation around Z, radians) extracted from a quaternion. Conventions match quat_from_euler (ZYX intrinsic). O(1).
- Postcondition:
((result >= -3.15) && (result <= 3.15)) || (result != result)
fn quat_angle_between(a: Quaternion, b: Quaternion) -> Float64¶
Angle (radians) between two rotation quaternions in [0, 2PI]. Returns 2acos(clamped dot) over the shortest arc. O(1).
- Postcondition:
((result >= 0) && (result <= 6.3)) || (result != result)
fn aabb_from_min_max(min: Vec3, max: Vec3) -> Aabb¶
Create an AABB from min and max corners. Same as aabb_new. O(1).
- Postcondition:
((result.min.x == min.x) || (result.min.x != result.min.x)) && ((result.min.y == min.y) || (result.min.y != result.min.y)) && ((result.min.z == min.z) || (result.min.z != result.min.z)) && ((result.max.x == max.x) || (result.max.x != result.max.x)) && ((result.max.y == max.y) || (result.max.y != result.max.y)) && ((result.max.z == max.z) || (result.max.z != result.max.z))
fn aabb_center(box: Aabb) -> Vec3¶
Centre point of an AABB: (min + max) / 2. O(1).
- Postcondition:
((result.x == (box.min.x + box.max.x) * 0.5) || (result.x != result.x)) && ((result.y == (box.min.y + box.max.y) * 0.5) || (result.y != result.y)) && ((result.z == (box.min.z + box.max.z) * 0.5) || (result.z != result.z))
fn aabb_size(box: Aabb) -> Vec3¶
Size (extent per axis) of an AABB: max - min. O(1).
- Postcondition:
((result.x == box.max.x - box.min.x) || (result.x != result.x)) && ((result.y == box.max.y - box.min.y) || (result.y != result.y)) && ((result.z == box.max.z - box.min.z) || (result.z != result.z))
fn aabb_half_extents(box: Aabb) -> Vec3¶
Half-extents of an AABB: size / 2. O(1).
- Postcondition:
((result.x == (box.max.x - box.min.x) * 0.5) || (result.x != result.x)) && ((result.y == (box.max.y - box.min.y) * 0.5) || (result.y != result.y)) && ((result.z == (box.max.z - box.min.z) * 0.5) || (result.z != result.z))
fn aabb_intersects_sphere(box: Aabb, s: Sphere) -> Bool¶
True if the sphere intersects the AABB. Uses the closest-point test: the squared distance from the sphere centre to the box must not exceed r2. O(1).
- Postcondition:
(result == true) => (vec3_distance_squared(aabb_closest_point(box, s.center), s.center) <= s.radius * s.radius)
fn aabb_closest_point(box: Aabb, p: Vec3) -> Vec3¶
Closest point on (or inside) the AABB to p: p clamped into [min, max]. O(1).
- Postcondition:
((result.x == math.clamp(p.x, box.min.x, box.max.x)) || (result.x != result.x)) && ((result.y == math.clamp(p.y, box.min.y, box.max.y)) || (result.y != result.y)) && ((result.z == math.clamp(p.z, box.min.z, box.max.z)) || (result.z != result.z))
fn aabb_surface_area(box: Aabb) -> Float64¶
Surface area of an AABB: 2(wh + hd + wd). O(1).
- Postcondition:
(result == 2 * ((box.max.x - box.min.x) * (box.max.y - box.min.y) + (box.max.y - box.min.y) * (box.max.z - box.min.z) + (box.max.x - box.min.x) * (box.max.z - box.min.z))) || (result != result)
fn aabb_volume(box: Aabb) -> Float64¶
Volume of an AABB: whd. O(1).
- Postcondition:
(result == (box.max.x - box.min.x) * (box.max.y - box.min.y) * (box.max.z - box.min.z)) || (result != result)
fn aabb_expand(box: Aabb, p: Vec3) -> Aabb¶
Expand the AABB to include point p (grow min/max component-wise). O(1).
- Postcondition:
((result.min.x == math.min_float(box.min.x, p.x)) || (result.min.x != result.min.x)) && ((result.min.y == math.min_float(box.min.y, p.y)) || (result.min.y != result.min.y)) && ((result.min.z == math.min_float(box.min.z, p.z)) || (result.min.z != result.min.z)) && ((result.max.x == math.max_float(box.max.x, p.x)) || (result.max.x != result.max.x)) && ((result.max.y == math.max_float(box.max.y, p.y)) || (result.max.y != result.max.y)) && ((result.max.z == math.max_float(box.max.z, p.z)) || (result.max.z != result.max.z))
fn aabb_union(a: Aabb, b: Aabb) -> Aabb¶
Smallest AABB that contains both a and b (component-wise min/max). O(1).
- Postcondition:
((result.min.x == math.min_float(a.min.x, b.min.x)) || (result.min.x != result.min.x)) && ((result.min.y == math.min_float(a.min.y, b.min.y)) || (result.min.y != result.min.y)) && ((result.min.z == math.min_float(a.min.z, b.min.z)) || (result.min.z != result.min.z)) && ((result.max.x == math.max_float(a.max.x, b.max.x)) || (result.max.x != result.max.x)) && ((result.max.y == math.max_float(a.max.y, b.max.y)) || (result.max.y != result.max.y)) && ((result.max.z == math.max_float(a.max.z, b.max.z)) || (result.max.z != result.max.z))
fn aabb_intersection(a: Aabb, b: Aabb) -> Option[Aabb]¶
Overlap of two AABBs. Returns None when the boxes do not intersect. The result is the intersection volume between them. O(1).
- Postcondition:
(result.is_some == false) || aabb_intersects_aabb(a, b)
fn sphere_intersects_sphere(a: Sphere, b: Sphere) -> Bool¶
True if two spheres intersect (or touch): distance <= r_a + r_b. O(1).
- Postcondition:
(result == true) => (vec3_distance_squared(a.center, b.center) <= (a.radius + b.radius) * (a.radius + b.radius))
fn sphere_intersects_aabb(s: Sphere, box: Aabb) -> Bool¶
True if a sphere intersects an AABB. Delegates to aabb_intersects_sphere. O(1).
- Postcondition:
(result == true) => (vec3_distance_squared(aabb_closest_point(box, s.center), s.center) <= s.radius * s.radius)
fn sphere_closest_point(s: Sphere, p: Vec3) -> Vec3¶
Closest point on the sphere surface to p. If p equals the centre, the centre (an arbitrary surface direction) is returned. O(1).
- Postcondition:
(((result.x == s.center.x) || (result.x != result.x)) && ((result.y == s.center.y) || (result.y != result.y)) && ((result.z == s.center.z) || (result.z != result.z))) || (vec3_length(vec3_sub(p, s.center)) != 0)
fn sphere_surface_area(s: Sphere) -> Float64¶
Surface area of a sphere: 4PIr2. O(1).
- Postcondition:
(result == 4 * math.PI * s.radius * s.radius) || (result != result)
fn sphere_volume(s: Sphere) -> Float64¶
Volume of a sphere: (4/3)PIr3. O(1).
- Postcondition:
(result == 4 / 3 * math.PI * s.radius * s.radius * s.radius) || (result != result)
fn sphere_expand(s: Sphere, p: Vec3) -> Sphere¶
Expand the sphere so it contains point p. If p is already inside, the sphere is returned unchanged. O(1).
- Postcondition:
((((result.center.x == s.center.x) || (result.center.x != result.center.x)) && ((result.center.y == s.center.y) || (result.center.y != result.center.y)) && ((result.center.z == s.center.z) || (result.center.z != result.center.z)) && ((result.radius == s.radius) || (result.radius != result.radius)))) || (vec3_distance(s.center, p) > s.radius)
fn ray_at(r: Ray, t: Float64) -> Vec3¶
Point on the ray at parameter t: origin + dir * t. O(1).
- Postcondition:
((result.x == r.origin.x + r.dir.x * t) || (result.x != result.x)) && ((result.y == r.origin.y + r.dir.y * t) || (result.y != result.y)) && ((result.z == r.origin.z + r.dir.z * t) || (result.z != result.z))
fn ray_origin(r: Ray) -> Vec3¶
Origin of the ray. O(1).
- Postcondition:
((result.x == r.origin.x) || (result.x != result.x)) && ((result.y == r.origin.y) || (result.y != result.y)) && ((result.z == r.origin.z) || (result.z != result.z))
fn ray_dir(r: Ray) -> Vec3¶
Direction of the ray. O(1).
- Postcondition:
((result.x == r.dir.x) || (result.x != result.x)) && ((result.y == r.dir.y) || (result.y != result.y)) && ((result.z == r.dir.z) || (result.z != result.z))
fn ray_intersect_plane(r: Ray, plane_point: Vec3, plane_normal: Vec3) -> Option[Float64]¶
Ray-plane intersection. Returns Some(t) where t is the ray parameter of the hit, or None if the ray is parallel to the plane or the hit lies behind the origin. plane_normal need not be unit. O(1).
- Postcondition:
(result.is_some == false) || !(math.abs_float(vec3_dot(r.dir, plane_normal)) < 0.000001 * 0.000001)
fn ray_distance_to_point(r: Ray, p: Vec3) -> Float64¶
Distance from a point p to the ray line (not the segment). Uses the 3D cross-product formula |(p - o) x d| / |d|. Returns 0 if the direction is degenerate. O(1).
- Postcondition:
(result >= 0) || (result != result)
type Plane¶
Plane defined by a point on the plane and its normal direction. The normal need not be unit length; signed distances then scale accordingly.
| Field | Type |
|---|---|
point |
Vec3 |
normal |
Vec3 |
fn plane_new(point: Vec3, normal: Vec3) -> Plane¶
Create a plane from a point on it and a normal direction. O(1).
- Postcondition:
((result.point.x == point.x) || (result.point.x != result.point.x)) && ((result.point.y == point.y) || (result.point.y != result.point.y)) && ((result.point.z == point.z) || (result.point.z != result.point.z)) && ((result.normal.x == normal.x) || (result.normal.x != result.normal.x)) && ((result.normal.y == normal.y) || (result.normal.y != result.normal.y)) && ((result.normal.z == normal.z) || (result.normal.z != result.normal.z))
fn plane_signed_distance(p: &Plane, pt: Vec3) -> Float64¶
Signed distance from a point to the plane (positive on the normal side). Assumes the plane normal is unit length. O(1).
- Postcondition:
(result == vec3_dot(vec3_sub(pt, p.point), p.normal)) || (result != result)
fn plane_distance_to_point(p: &Plane, pt: Vec3) -> Float64¶
Absolute distance from a point to the plane. Assumes a unit normal. O(1).
- Postcondition:
(result >= 0) || (result != result)
fn plane_intersect_ray(p: &Plane, r: Ray) -> Option[Float64]¶
Ray-plane intersection against a Plane. Returns Some(t) or None. This is an alias of ray_intersect_plane using the plane's fields. O(1).
- Postcondition:
(result.is_some == false) || !(math.abs_float(vec3_dot(r.dir, p.normal)) < 0.000001 * 0.000001)
fn f64_approx_eq(a: Float64, b: Float64, eps: Float64) -> Bool¶
True if |a - b| <= eps. The canonical epsilon comparison. O(1).
- Postcondition:
(result == true) => (((a - b) <= eps) && ((b - a) <= eps))
fn f64_deg_to_rad(d: Float64) -> Float64¶
Convert degrees to radians: d * PI / 180. O(1).
- Postcondition:
(result == d * math.PI / 180) || (result != result)
fn f64_rad_to_deg(r: Float64) -> Float64¶
Convert radians to degrees: r * 180 / PI. O(1).
- Postcondition:
(result == r * 180 / math.PI) || (result != result)
fn f32_deg_to_rad(d: Float64) -> Float64¶
Convert degrees to radians (Float32 API: same formula, Float64 arithmetic). O(1).
- Postcondition:
(result == d * math.PI / 180) || (result != result)
fn f32_rad_to_deg(r: Float64) -> Float64¶
Convert radians to degrees (Float32 API: same formula, Float64 arithmetic). O(1).
- Postcondition:
(result == r * 180 / math.PI) || (result != result)
geometry_2d.xi¶
type Point2¶
2D point.
| Field | Type |
|---|---|
x |
Float64 |
y |
Float64 |
type Line2¶
Infinite line ax + by + c = 0.
| Field | Type |
|---|---|
a |
Float64 |
b |
Float64 |
c |
Float64 |
type Ray2¶
Ray: origin and (not necessarily unit) direction.
| Field | Type |
|---|---|
origin |
Point2 |
dir |
Vec2 |
type Segment2¶
Segment between two points.
| Field | Type |
|---|---|
a |
Point2 |
b |
Point2 |
type Circle¶
Circle with center and radius.
| Field | Type |
|---|---|
center |
Point2 |
radius |
Float64 |
type Rect¶
Axis-aligned rectangle defined by min and max corners.
| Field | Type |
|---|---|
min |
Point2 |
max |
Point2 |
type Triangle2¶
Triangle with three vertices.
| Field | Type |
|---|---|
a |
Point2 |
b |
Point2 |
c |
Point2 |
type Polygon2¶
Polygon: vertex list in boundary order.
| Field | Type |
|---|---|
vertices |
Vec[Point2] |
fn point_distance(a: Point2, b: Point2) -> Float64¶
Euclidean distance between two points. O(1).
- Postcondition:
(result >= 0) || (result != result)
fn point_in_circle(p: Point2, c: Circle) -> Bool¶
True if p lies inside (or on) the circle. O(1).
- Postcondition:
(c.radius == c.radius) && p.x == c.center.x && p.y == c.center.y => result == true
fn point_in_rect(p: Point2, r: Rect) -> Bool¶
True if p lies inside (or on) the axis-aligned rectangle. O(1).
- Postcondition:
p.x < r.min.x => result == false - Postcondition:
p.x > r.max.x => result == false
fn point_in_triangle(p: Point2, t: Triangle2) -> Bool¶
True if p lies inside (or on) the triangle (same-side test). O(1).
- Postcondition:
p.x == t.a.x && p.y == t.a.y => result == true
fn point_in_polygon(p: Point2, poly: Polygon2) -> Bool¶
True if p lies inside the polygon (ray-casting test; boundary counts as inside). O(n).
- Postcondition:
poly.vertices.len() < 2 => result == false
fn line_intersection(l1: Line2, l2: Line2) -> Option[Point2]¶
Intersection of two infinite lines; None when they are parallel. O(1).
- Postcondition:
l1.a * l2.b == l2.a * l1.b => result.is_some == false
fn segment_intersection(s1: Segment2, s2: Segment2) -> Option[Point2]¶
Intersection of two segments; None when they do not meet. O(1).
- Postcondition:
(s1.a.y - s1.b.y) * (s2.b.x - s2.a.x) == (s2.a.y - s2.b.y) * (s1.b.x - s1.a.x) => result.is_some == false
fn segment_point_distance(s: Segment2, p: Point2) -> Float64¶
Shortest distance from p to the segment s. O(1).
- Postcondition:
(result >= 0) || (result != result)
fn line_point_distance(l: Line2, p: Point2) -> Float64¶
Perpendicular distance from p to the infinite line l. O(1).
- Postcondition:
(result >= 0) || (result != result)
fn circle_intersection(c: Circle, l: Line2) -> Option[Vec[Point2]]¶
Intersection points of the circle and the line; None when they do not meet or the line is degenerate. O(1).
- Postcondition:
(l.a == 0 && l.b == 0) => result.is_some == false
fn circle_line_intersection(c: Circle, l: Line2) -> Option[Vec[Point2]]¶
Alias of circle_intersection. O(1).
- Postcondition:
(l.a == 0 && l.b == 0) => result.is_some == false
fn circle_circle_intersection(c1: Circle, c2: Circle) -> Option[Vec[Point2]]¶
Intersection points of two circles; None when they do not intersect (or are concentric). O(1).
- Postcondition:
c1.center.x == c2.center.x && c1.center.y == c2.center.y => result.is_some == false
fn area_triangle(t: Triangle2) -> Float64¶
Signed area of the triangle (positive for counter-clockwise vertices). O(1).
- Postcondition:
t.b.x == t.a.x && t.b.y == t.a.y => result == 0
fn area_polygon(poly: Polygon2) -> Float64¶
Signed area of the polygon via the shoelace formula. O(n).
- Postcondition:
poly.vertices.len() < 3 => result == 0
fn centroid(poly: Polygon2) -> Point2¶
Area centroid of the polygon. Returns the zero point for a degenerate polygon. O(n).
- Postcondition:
poly.vertices.len() == 0 => result.x == 0 && result.y == 0
fn convex_hull(points: &Vec[Point2]) -> Polygon2¶
Convex hull of the points via the monotone chain algorithm (Andrew). The hull is counter-clockwise without a duplicated closing vertex. O(n log n).
- Postcondition:
points.len() == 0 => result.vertices.len() == 0
fn is_convex(poly: Polygon2) -> Bool¶
True if every interior angle of the polygon is at most 180 degrees (collinear edges allowed). O(n).
- Postcondition:
poly.vertices.len() < 3 => result == false
fn polygon_contains(poly: Polygon2, p: Point2) -> Bool¶
Containment test for p in poly. Same as point_in_polygon. O(n).
- Postcondition:
poly.vertices.len() < 2 => result == false
fn polygon_intersection(a: Polygon2, b: Polygon2) -> Option[Polygon2]¶
Intersection polygon of a and b via Sutherland-Hodgman clipping of a against the edges of b (exact when b is convex). None when the result is empty. O(n*m).
- Postcondition:
a.vertices.len() == 0 => result.is_some == false
fn polygon_union(a: Polygon2, b: Polygon2) -> Option[Polygon2]¶
Boolean union polygon of a and b. Implemented as the convex hull of both vertex sets: exact when the union is convex (e.g. overlapping convex polygons), otherwise an enclosing convex approximation (documented). O(n log n).
- Postcondition:
a.vertices.len() == 0 && b.vertices.len() == 0 => result.is_some == false
fn polygon_difference(a: Polygon2, b: Polygon2) -> Option[Polygon2]¶
Boolean difference a minus b. Exact for the reliable inputs: empty-a -> None, empty-b -> Some(a), and disjoint polygons -> Some(a) (a minus b is a; disjointness is decided by the intersection test, so this is exact whenever polygon_intersection is exact -- b convex). A fully contained a returns None (a\b is empty). Overlapping and containment inputs otherwise remain a conservative approximation -- a real polygon-clipping implementation (single ring with holes/slits) is a queue follow-up; do not rely on overlap results. O(n*m).
- Postcondition:
a.vertices.len() == 0 => result.is_some == false
fn polygon_circumference(poly: Polygon2) -> Float64¶
Perimeter of the polygon. O(n).
- Postcondition:
(result >= 0) || (result != result)
geometry_3d.xi¶
type Point3¶
3D point.
| Field | Type |
|---|---|
x |
Float64 |
y |
Float64 |
z |
Float64 |
type Line3¶
Infinite line: point plus direction.
| Field | Type |
|---|---|
point |
Point3 |
dir |
Vec3 |
type Ray3¶
Ray: origin plus (not necessarily unit) direction.
| Field | Type |
|---|---|
origin |
Point3 |
dir |
Vec3 |
type Segment3¶
Segment between two points.
| Field | Type |
|---|---|
a |
Point3 |
b |
Point3 |
type Plane3d¶
Plane3d normal . p = d.
| Field | Type |
|---|---|
normal |
Vec3 |
d |
Float64 |
type Sphere3d¶
Sphere3d with center and radius.
| Field | Type |
|---|---|
center |
Point3 |
radius |
Float64 |
type Capsule¶
Capsule: segment (a, b) with radius.
| Field | Type |
|---|---|
a |
Point3 |
b |
Point3 |
radius |
Float64 |
type Cylinder¶
Cylinder: axis segment plus radius.
| Field | Type |
|---|---|
axis |
Segment3 |
radius |
Float64 |
type Cone¶
Cone: apex, axis direction, and half angle in radians.
| Field | Type |
|---|---|
apex |
Point3 |
axis |
Vec3 |
half_angle |
Float64 |
type Box¶
Axis-aligned box defined by min and max corners.
| Field | Type |
|---|---|
min |
Point3 |
max |
Point3 |
type OBB¶
Oriented box: center, orthonormal axes, half extents.
| Field | Type |
|---|---|
center |
Point3 |
axes |
Vec[Vec3] |
half_extents |
Vec[Float64] |
type Triangle3¶
Triangle with three vertices.
| Field | Type |
|---|---|
a |
Point3 |
b |
Point3 |
c |
Point3 |
type Polygon3¶
Polygon: vertex list in boundary order.
| Field | Type |
|---|---|
vertices |
Vec[Point3] |
type Mesh¶
Triangle mesh: vertices plus triangle index list (3 indices per triangle).
| Field | Type |
|---|---|
vertices |
Vec[Point3] |
indices |
Vec[Int] |
fn point_distance(a: Point3, b: Point3) -> Float64¶
Euclidean distance between two points. O(1).
- Postcondition:
(result >= 0) || (result != result)
fn point_sphere_distance(p: Point3, s: Sphere3d) -> Float64¶
Distance from p to the sphere surface (0 when p is inside). O(1).
- Postcondition:
(result >= 0) || (result != result)
fn point_plane_distance(p: Point3, pl: Plane3d) -> Float64¶
Signed distance from p to the plane (positive on the normal side). O(1).
- Postcondition:
pl.normal.x == 0 && pl.normal.y == 0 && pl.normal.z == 0 => result == 0
fn plane_point_distance(pl: Plane3d, p: Point3) -> Float64¶
Absolute distance from p to the plane. Alias of point_plane_distance. O(1).
- Postcondition:
(result >= 0) || (result != result)
fn line_point_distance(l: Line3, p: Point3) -> Float64¶
Shortest distance from p to the infinite line l. O(1).
- Postcondition:
(result >= 0) || (result != result)
fn segment_point_distance(s: Segment3, p: Point3) -> Float64¶
Shortest distance from p to the segment s. O(1).
- Postcondition:
(result >= 0) || (result != result)
fn ray_plane_intersection(r: Ray3, pl: Plane3d) -> Option[Float64]¶
Ray-plane intersection: parameter t along the ray; None when parallel or behind the origin. O(1).
- Postcondition:
result.is_some == true || result.is_some == false
fn ray_triangle_intersection(r: Ray3, t: Triangle3) -> Option[Float64]¶
Ray-triangle intersection via the Moller-Trumbore algorithm. Returns the nearest positive t; None on miss or behind the origin. O(1).
- Postcondition:
result.is_some == true || result.is_some == false
fn ray_sphere_intersection(r: Ray3, s: Sphere3d) -> Option[Float64]¶
Ray-sphere intersection: nearest positive t; None on miss. O(1).
- Postcondition:
(r.dir.x == 0 && r.dir.y == 0 && r.dir.z == 0) => result.is_some == false
fn ray_box_intersection(r: Ray3, b: Box) -> Option[Float64]¶
Ray-box intersection via the slab method: nearest positive t; None on miss. O(1).
- Postcondition:
result.is_some == true || result.is_some == false
fn plane_plane_intersection(p1: Plane3d, p2: Plane3d) -> Option[Line3]¶
Line of intersection of two planes; None when parallel. O(1).
- Postcondition:
p1.normal.x == p2.normal.x && p1.normal.y == p2.normal.y && p1.normal.z == p2.normal.z => result.is_some == false
fn sphere_sphere_intersection(a: Sphere3d, b: Sphere3d) -> Bool¶
True if the two spheres overlap or touch. O(1).
- Postcondition:
a.radius == 0 && b.radius == 0 => result == (a.center.x == b.center.x && a.center.y == b.center.y && a.center.z == b.center.z)
fn aabb_intersection(a: Box, b: Box) -> Bool¶
True if the two axis-aligned boxes overlap or touch. O(1).
- Postcondition:
a.max.x < b.min.x => result == false
fn aabb_contains(b: Box, p: Point3) -> Bool¶
True if p lies inside (or on) the box. O(1).
- Postcondition:
p.x < b.min.x => result == false
fn closest_point_on_segment(s: Segment3, p: Point3) -> Point3¶
Closest point on the segment s to p. O(1).
- Postcondition:
s.b.x == s.a.x && s.b.y == s.a.y && s.b.z == s.a.z => result.x == s.a.x && result.y == s.a.y && result.z == s.a.z
fn closest_point_on_plane(pl: Plane3d, p: Point3) -> Point3¶
Orthogonal projection of p onto the plane. O(1).
- Postcondition:
pl.normal.x == 0 && pl.normal.y == 0 && pl.normal.z == 0 && p.x == p.x && p.y == p.y && p.z == p.z => result.x == p.x && result.y == p.y && result.z == p.z
fn triangle_normal(t: Triangle3) -> Vec3¶
Unit normal of the triangle (right-handed, b-a cross c-a). Returns the zero vector for a degenerate triangle. O(1).
- Postcondition:
t.b.x == t.a.x && t.b.y == t.a.y && t.b.z == t.a.z && t.c.x == t.a.x && t.c.y == t.a.y && t.c.z == t.a.z => result.x == 0 && result.y == 0 && result.z == 0
fn mesh_volume(m: Mesh) -> Float64¶
Signed volume of a closed mesh via the divergence theorem (sum of signed tetrahedron volumes about the origin). O(n).
- Postcondition:
m.indices.len() < 3 => result == 0
fn mesh_surface_area(m: Mesh) -> Float64¶
Total surface area of a mesh (sum of triangle areas). O(n).
- Postcondition:
(result >= 0) || (result != result)
fn mesh_centroid(m: Mesh) -> Point3¶
Volume-weighted centroid of a closed mesh. Returns the zero point for a degenerate mesh. O(n).
- Postcondition:
m.indices.len() < 3 => result.x == 0 && result.y == 0 && result.z == 0
fn convex_hull_3d(points: &Vec[Point3]) -> Mesh¶
Convex hull of a point cloud as a triangle mesh. Every oriented face is a triangle (i, j, k) such that all other points lie on (or behind) the plane of that triangle. O(n^4); exact for small point sets.
- Postcondition:
points.len() < 4 => result.indices.len() == 0
geometry_extended.xi¶
fn voronoi(sites: &Vec[Point2], bounds: Rect) -> Vec[Polygon2]¶
Bounded Voronoi diagram of sites clipped to bounds: one cell per site, each cell the intersection of the half-planes defined by the perpendicular bisectors with every other site. O(k^2 * n).
- Postcondition:
result.len() == sites.len()
fn delaunay(points: &Vec[Point2]) -> Vec[Triangle2]¶
Delaunay triangulation of points via the Bowyer-Watson algorithm (bounded by a super-triangle). Returns a non-empty triangle list for >= 3 points. O(n^2) typical.
- Postcondition:
result.len() == 0 || points.len() >= 3
fn bezier_curve(controls: &Vec[Vec2], t: Float64) -> Vec2¶
Point on a Bezier curve with de Casteljau's algorithm. O(k^2).
- Postcondition:
controls.len() == 0 => result.x == 0 && result.y == 0
fn b_spline(controls: &Vec[Vec2], knots: &Vec[Float64], t: Float64) -> Vec2¶
Uniform open (clamped) quadratic B-spline evaluation with de Boor's algorithm. Knots must satisfy knots.len() == controls.len() + 3 and be clamped (non-decreasing, endpoints repeated); t is clamped to [0, 1]. Returns the zero vector on inconsistent input. O(1).
- Postcondition:
(controls.len() < 3 || knots.len() != controls.len() + 3) => result.x == 0 && result.y == 0
fn nurbs(controls: &Vec[Vec2], weights: &Vec[Float64], knots: &Vec[Float64], t: Float64) -> Vec2¶
NURBS evaluation: weighted rational B-spline with de Boor's algorithm. weights.len() must equal controls.len() and knots must satisfy the clamped B-spline sizing. O(1).
- Postcondition:
(controls.len() < 3 || weights.len() != controls.len() || knots.len() != controls.len() + 3) => result.x == 0 && result.y == 0
fn subdivision(mesh: Mesh, iterations: Int) -> Mesh¶
Midpoint subdivision surface refinement: every triangle is split into four by inserting edge midpoints (no shared-edge deduplication). The triangle count quadruples per iteration. O(iterations * n).
- Postcondition:
iterations <= 0 => result.vertices.len() == mesh.vertices.len() && result.indices.len() == mesh.indices.len()
fn mesh_processing(mesh: Mesh) -> Mesh¶
Mesh cleanup pipeline: removes duplicate vertices (exact position match) and rewrites the index list accordingly. Returns the cleaned mesh. O(n^2).
- Postcondition:
mesh.vertices.len() == 0 && mesh.indices.len() == 0 => result.vertices.len() == 0 && result.indices.len() == 0
fn projective_geometry(points: &Vec[Vec3]) -> Vec[Vec3]¶
Apply a projective transform to the points: each point (x, y, z) is mapped to (x/w, y/w, z/w) with the perspective weight w = 1 + x + y + z. Points whose weight is zero are left unchanged. O(n).
- Postcondition:
result.len() == points.len()
fn hyperbolic_geometry(a: &Vec[Float64], b: &Vec[Float64]) -> Float64¶
Hyperbolic distance in the Poincare ball model: 2 * atanh(|a - b| / |1 - a.b|). Returns 0 for identical points. O(1).
- Postcondition:
(a.len() != b.len() || a.len() == 0) => result != result - Postcondition:
(result >= 0) || (result != result)
fn elliptic_geometry(a: &Vec[Float64], b: &Vec[Float64]) -> Float64¶
Elliptic (spherical) distance between two unit vectors: acos(a.b) in [0, PI]. Returns 0 for identical unit vectors. O(n).
- Postcondition:
(a.len() != b.len() || a.len() == 0) => result != result - Postcondition:
((result >= 0) && (result <= 4)) || (result != result)
fn non_euclidean(a: &Vec[Float64], b: &Vec[Float64]) -> Float64¶
Generic non-Euclidean metric: the Poincare hyperbolic distance (see hyperbolic_geometry). O(n).
- Postcondition:
(a.len() != b.len() || a.len() == 0) => result != result - Postcondition:
(result >= 0) || (result != result)
fn incidence_geometry(points: &Vec[Point2], lines: &Vec[Line2]) -> Bool¶
Incidence predicate: true iff every point lies on at least one of the lines. O(p * l).
- Postcondition:
points.len() == 0 => result == true
fn convex_geometry(points: &Vec[Vec2]) -> Polygon2¶
Convex decomposition/combination of a point set: returns the convex hull of the points as a Polygon2. O(n log n).
- Postcondition:
points.len() == 0 => result.vertices.len() == 0
fn computational_geometry(points: &Vec[Vec2]) -> Vec[Polygon2]¶
General computational geometry entry point: returns the convex hull of the points as a single-cell polygon list. O(n log n).
- Postcondition:
result.len() == 0 || result.len() == 1
linear.xi¶
fn gram_schmidt(vectors: &Vec[Vec[Float64]]) -> Vec[Vec[Float64]]¶
Orthonormalise a set of vectors (each row is a vector) via the modified Gram-Schmidt process. Linearly dependent vectors collapse to the zero vector. O(k^2 * n).
- Postcondition:
result.len() == 0 || result.len() == vectors.len()
fn orthogonalize(vectors: &Vec[Vec[Float64]]) -> Vec[Vec[Float64]]¶
Orthogonalise a set of vectors (each row is a vector) without normalising the output. Linearly dependent vectors collapse to the zero vector. O(k^2 * n).
- Postcondition:
result.len() == 0 || result.len() == vectors.len()
fn normalize_columns(m: &Vec[Vec[Float64]]) -> Vec[Vec[Float64]]¶
Normalise every column of m to unit length. Columns with zero length are left as-is. Empty matrix for a ragged input. O(n*m).
- Postcondition:
result.len() == 0 || m.len() > 0
fn normalize_rows(m: &Vec[Vec[Float64]]) -> Vec[Vec[Float64]]¶
Normalise every row of m to unit length. Rows with zero length are left as-is. Empty matrix for a ragged input. O(n*m).
- Postcondition:
result.len() == m.len()
fn is_orthogonal(m: &Vec[Vec[Float64]]) -> Bool¶
True iff m is orthogonal: m * m^T is the identity (within 1e-9). Empty or non-square matrices are false. O(n^3).
- Postcondition:
m.len() == 0 => result == false
fn is_symmetric(m: &Vec[Vec[Float64]]) -> Bool¶
True iff m equals its transpose (exact component equality). O(n^2).
- Postcondition:
m.len() == 0 => result == true
fn is_skew_symmetric(m: &Vec[Vec[Float64]]) -> Bool¶
True iff m equals minus its transpose (exact component equality, diagonal must be zero). O(n^2).
- Postcondition:
m.len() == 0 => result == true
fn is_positive_definite(m: &Vec[Vec[Float64]]) -> Bool¶
True iff m is symmetric positive definite: symmetric and every leading principal minor is positive (checked via a Cholesky-style sweep). O(n^2).
- Postcondition:
m.len() == 0 => result == false
fn is_diagonal_dominant(m: &Vec[Vec[Float64]]) -> Bool¶
True iff m is diagonally dominant: |m[i][i]| >= sum of the absolute values of the off-diagonal entries in the same row, for every row. O(n^2).
- Postcondition:
m.len() == 0 => result == true
fn matrix_exponential(m: &Vec[Vec[Float64]]) -> Vec[Vec[Float64]]¶
Matrix exponential of m via the Taylor series exp(M) = sum M^k / k!, iterated until the added term is negligible (or 60 terms). Empty matrix on non-square input. O(n^3 * terms).
- Postcondition:
result.len() == 0 || result.len() == m.len()
fn matrix_logarithm(m: &Vec[Vec[Float64]]) -> Vec[Vec[Float64]]¶
Principal matrix logarithm of m via the series log(M) = sum (-1)^(k+1) (M-I)^k / k, which converges when M is close to the identity. Returns the zero matrix for the identity and an empty matrix for non-square input. O(n^3 * terms).
- Postcondition:
result.len() == 0 || result.len() == m.len()
fn matrix_sqrt(m: &Vec[Vec[Float64]]) -> Vec[Vec[Float64]]¶
Principal matrix square root of m via Newton iteration X_{k+1} = (X_k + M * X_k^-1) / 2 (converges for well-conditioned SPD-like inputs). Empty matrix on non-square input. O(n^3 * iterations).
- Postcondition:
result.len() == 0 || result.len() == m.len()
fn matrix_power(m: &Vec[Vec[Float64]], p: Int) -> Vec[Vec[Float64]]¶
Integer matrix power m^p. p == 0 yields the identity, p < 0 inverts first, and repeated squaring keeps it to O(log |p|) multiplications. Empty matrix on non-square or singular input. O(n^3 * log |p|).
- Postcondition:
result.len() == 0 || result.len() == m.len()
fn vec_to_skew(v: &Vec[Float64]) -> Vec[Vec[Float64]]¶
Skew-symmetric matrix [v]_x of a 3-vector v. Empty matrix unless v has exactly 3 elements. O(1).
- Postcondition:
result.len() == 0 || result.len() == 3
fn skew_to_vec(m: &Vec[Vec[Float64]]) -> Vec[Float64]¶
The 3-vector v such that vec_to_skew(v) == m, extracted from a skew-symmetric matrix. Empty vector unless m is 3x3. O(1).
- Postcondition:
result.len() == 0 || result.len() == 3
mat.xi¶
fn mat_identity(n: Int) -> Vec[Vec[Float64]]¶
n x n identity matrix. O(n^2).
- Postcondition:
result.len() == 0 || result.len() == n
fn mat_mul(a: &Vec[Vec[Float64]], b: &Vec[Vec[Float64]]) -> Vec[Vec[Float64]]¶
Matrix product a * b. Returns an empty matrix when the inner dimensions do not match. O(n^3).
- Postcondition:
result.len() == 0 || result.len() == a.len()
fn mat_det(m: &Vec[Vec[Float64]]) -> Float64¶
Determinant of a square matrix via Gaussian elimination with partial pivoting. Returns NaN for a non-square matrix and 0 for a singular one. O(n^3).
- Postcondition:
(result != result) || (result == 0) || (m.len() > 0)
fn mat_inv(m: &Vec[Vec[Float64]]) -> Option[Vec[Vec[Float64]]]¶
Inverse of a square matrix via Gauss-Jordan elimination with partial pivoting. None when the matrix is singular or non-square. O(n^3).
- Postcondition:
(result.is_some == false) || (m.len() > 0)
fn mat_transpose(m: &Vec[Vec[Float64]]) -> Vec[Vec[Float64]]¶
Transpose of a matrix. Empty matrix for a ragged input. O(n*m).
- Postcondition:
result.len() == 0 || m.len() > 0
fn mat_translate(m: &Vec[Vec[Float64]], x: Float64, y: Float64, z: Float64) -> Vec[Vec[Float64]]¶
Compose the 4x4 matrix m with a translation (x, y, z): returns T * m where T is the translation matrix, so the translation is applied after m. Empty matrix unless m is 4x4. O(64).
- Postcondition:
result.len() == 0 || (result.len() == 4 && m.len() == 4)
fn mat_rotate(m: &Vec[Vec[Float64]], angle: Float64, axis: &Vec[Float64]) -> Vec[Vec[Float64]]¶
Compose the 4x4 matrix m with a rotation of angle (radians) about the axis direction: returns R * m where R is the Rodrigues rotation matrix (the axis is normalised first). Empty matrix unless m is 4x4. O(64).
- Postcondition:
result.len() == 0 || (result.len() == 4 && m.len() == 4)
fn mat_scale(m: &Vec[Vec[Float64]], x: Float64, y: Float64, z: Float64) -> Vec[Vec[Float64]]¶
Compose the 4x4 matrix m with a scale (x, y, z): returns S * m where S is the scale matrix, applied after m. Empty matrix unless m is 4x4. O(64).
- Postcondition:
result.len() == 0 || (result.len() == 4 && m.len() == 4)
fn mat_look_at(eye: &Vec[Float64], target: &Vec[Float64], up: &Vec[Float64]) -> Vec[Vec[Float64]]¶
Right-handed look-at view matrix: camera at eye looking at target with up direction. Column-major 4x4. O(1).
- Postcondition:
result.len() == 4
fn mat_perspective(fovy: Float64, aspect: Float64, near: Float64, far: Float64) -> Vec[Vec[Float64]]¶
Perspective projection matrix (right-handed, standard OpenGL mapping). fovy is the vertical field of view in radians; near/far must differ. 4x4. O(1).
- Postcondition:
result.len() == 4
fn mat_ortho(left: Float64, right: Float64, bottom: Float64, top: Float64, near: Float64, far: Float64) -> Vec[Vec[Float64]]¶
Orthographic projection matrix mapping [left,right] x [bottom,top] x [near,far] to NDC [-1,1]^3. 4x4. O(1).
- Postcondition:
result.len() == 4
fn mat_transform_point(m: &Vec[Vec[Float64]], p: &Vec[Float64]) -> Vec[Float64]¶
Transform the point p (3 or 4 components, w defaults to 1) by the 4x4 matrix m including the perspective divide. Returns the zero vector when the transformed w is zero. Empty vector for a non-4x4 matrix. O(16).
- Postcondition:
result.len() == 0 || result.len() == 3
matrix.xi¶
type Mat2¶
2x2 column-major matrix.
| Field | Type |
|---|---|
m00 |
Float64 |
m01 |
Float64 |
m10 |
Float64 |
m11 |
Float64 |
type Mat3¶
3x3 column-major matrix.
| Field | Type |
|---|---|
m00 |
Float64 |
m01 |
Float64 |
m02 |
Float64 |
m10 |
Float64 |
m11 |
Float64 |
m12 |
Float64 |
m20 |
Float64 |
m21 |
Float64 |
m22 |
Float64 |
type Mat4¶
4x4 column-major matrix.
| Field | Type |
|---|---|
m00 |
Float64 |
m01 |
Float64 |
m02 |
Float64 |
m03 |
Float64 |
m10 |
Float64 |
m11 |
Float64 |
m12 |
Float64 |
m13 |
Float64 |
m20 |
Float64 |
m21 |
Float64 |
m22 |
Float64 |
m23 |
Float64 |
m30 |
Float64 |
m31 |
Float64 |
m32 |
Float64 |
m33 |
Float64 |
type MatMN¶
Dynamic m x n matrix stored row-major.
| Field | Type |
|---|---|
rows |
Int |
cols |
Int |
data |
Vec[Float64] |
fn mat2_new(a: Float64, b: Float64, c: Float64, d: Float64) -> Mat2¶
Construct a 2x2 matrix. O(1).
- Postcondition:
result.m00 == a && result.m01 == b && result.m10 == c && result.m11 == d
fn mat3_new(a: Float64, b: Float64, c: Float64, d: Float64, e: Float64, f: Float64, g: Float64, h: Float64, i: Float64) -> Mat3¶
Construct a 3x3 matrix. O(1).
- Postcondition:
result.m00 == a && result.m01 == b && result.m02 == c - Postcondition:
result.m10 == d && result.m11 == e && result.m12 == f - Postcondition:
result.m20 == g && result.m21 == h && result.m22 == i
fn mat4_new(a: Float64, b: Float64, c: Float64, d: Float64, e: Float64, f: Float64, g: Float64, h: Float64, i: Float64, j: Float64, k: Float64, l: Float64, m: Float64, n: Float64, o: Float64, p: Float64) -> Mat4¶
Construct a 4x4 matrix. O(1).
- Postcondition:
result.m00 == a && result.m01 == b && result.m02 == c && result.m03 == d - Postcondition:
result.m10 == e && result.m11 == f && result.m12 == g && result.m13 == h - Postcondition:
result.m20 == i && result.m21 == j && result.m22 == k && result.m23 == l - Postcondition:
result.m30 == m && result.m31 == n && result.m32 == o && result.m33 == p
fn identity(n: Int) -> Vec[Vec[Float64]]¶
n x n identity matrix. O(n^2).
- Postcondition:
result.len() == 0 || result.len() == n
fn zero(rows: Int, cols: Int) -> Vec[Vec[Float64]]¶
rows x cols zero matrix. O(n*m).
- Postcondition:
result.len() == 0 || result.len() == rows
fn one(rows: Int, cols: Int) -> Vec[Vec[Float64]]¶
rows x cols all-ones matrix. O(n*m).
- Postcondition:
result.len() == 0 || result.len() == rows
fn add(a: &Vec[Vec[Float64]], b: &Vec[Vec[Float64]]) -> Vec[Vec[Float64]]¶
Element-wise addition of two same-shape matrices. Empty matrix on shape mismatch. O(n*m).
- Postcondition:
result.len() == 0 || result.len() == a.len()
fn sub(a: &Vec[Vec[Float64]], b: &Vec[Vec[Float64]]) -> Vec[Vec[Float64]]¶
Element-wise subtraction of two same-shape matrices. Empty matrix on shape mismatch. O(n*m).
- Postcondition:
result.len() == 0 || result.len() == a.len()
fn mul(a: &Vec[Vec[Float64]], b: &Vec[Vec[Float64]]) -> Vec[Vec[Float64]]¶
Matrix product a * b. Empty matrix on inner-dimension mismatch. O(n^3).
- Postcondition:
result.len() == 0 || result.len() == a.len()
fn scalar_mul(a: &Vec[Vec[Float64]], s: Float64) -> Vec[Vec[Float64]]¶
Scale every element of a by s. O(n*m).
- Postcondition:
result.len() == a.len()
fn transpose(a: &Vec[Vec[Float64]]) -> Vec[Vec[Float64]]¶
Matrix transpose. Empty matrix for a ragged input. O(n*m).
- Postcondition:
result.len() == 0 || a.len() > 0
fn det(a: &Vec[Vec[Float64]]) -> Float64¶
Determinant of a square matrix via Gaussian elimination with partial pivoting. NaN for non-square input. O(n^3).
- Postcondition:
(result != result) || (result == 0) || (a.len() > 0)
fn inverse(a: &Vec[Vec[Float64]]) -> Option[Vec[Vec[Float64]]]¶
Inverse of a square matrix via Gauss-Jordan elimination with partial pivoting. None when singular or non-square. O(n^3).
- Postcondition:
(result.is_some == false) || (a.len() > 0)
fn minor(a: &Vec[Vec[Float64]], row: Int, col: Int) -> Float64¶
Determinant of the submatrix obtained by deleting row and col. NaN for non-square input or out-of-range indices. O(n^3).
- Postcondition:
(result != result) || (result == 0) || (a.len() > 0)
fn cofactor(a: &Vec[Vec[Float64]], row: Int, col: Int) -> Float64¶
Signed minor (cofactor) at (row, col): (-1)^(row+col) * det of the deleted submatrix. NaN for non-square or out-of-range input. O(n^3).
- Postcondition:
(result != result) || (result == 0) || (a.len() > 0)
fn adjugate(a: &Vec[Vec[Float64]]) -> Vec[Vec[Float64]]¶
Adjugate (classical) matrix: the transpose of the cofactor matrix. Empty matrix for non-square input. O(n^4).
- Postcondition:
result.len() == 0 || a.len() > 0
fn trace(a: &Vec[Vec[Float64]]) -> Float64¶
Trace (sum of the main diagonal) of a square matrix. NaN for non-square. O(n).
- Postcondition:
(result != result) || (result == 0) || (a.len() > 0)
fn rank(a: &Vec[Vec[Float64]]) -> Int¶
Rank of a matrix via Gaussian elimination with partial pivoting. O(n^3).
- Postcondition:
(result >= 0) && (result <= a.len())
fn nullity(a: &Vec[Vec[Float64]]) -> Int¶
Nullity of a matrix: number of columns minus the rank. O(n^3).
- Postcondition:
result >= 0
fn eigenvalues(a: &Vec[Vec[Float64]]) -> Vec[Float64]¶
Eigenvalues of a square matrix. Implemented analytically for 2x2 matrices; returns the empty vector for any other size (documented). O(1).
- Postcondition:
result.len() == 0 || result.len() == 2
fn eigenvectors(a: &Vec[Vec[Float64]]) -> Vec[Vec[Float64]]¶
Eigenvectors of a square matrix (each row is a unit eigenvector, in the same order as eigenvalues). Implemented analytically for 2x2 matrices; returns the empty matrix for any other size (documented). O(1).
- Postcondition:
result.len() == 0 || result.len() == 2
fn diagonal(a: &Vec[Vec[Float64]]) -> Vec[Float64]¶
Main diagonal entries of a matrix. Empty vector for a ragged input. O(n).
- Postcondition:
result.len() <= a.len()
fn diag_mul(a: &Vec[Vec[Float64]], d: &Vec[Float64]) -> Vec[Vec[Float64]]¶
Right-multiply a by the diagonal matrix diag(d): result[i][j] = a[i][j]d[j]. Empty matrix on size mismatch. O(nm).
- Postcondition:
result.len() == 0 || result.len() == a.len()
fn hadamard(a: &Vec[Vec[Float64]], b: &Vec[Vec[Float64]]) -> Vec[Vec[Float64]]¶
Element-wise (Hadamard) product of two same-shape matrices. Empty matrix on shape mismatch. O(n*m).
- Postcondition:
result.len() == 0 || result.len() == a.len()
fn kronecker(a: &Vec[Vec[Float64]], b: &Vec[Vec[Float64]]) -> Vec[Vec[Float64]]¶
Kronecker product of a (r x c) and b (p x q): an (rp) x (cq) matrix. Empty matrix for empty input. O(rpc*q).
- Postcondition:
result.len() == 0 || result.len() >= a.len()
fn lu_decompose(a: &Vec[Vec[Float64]]) -> (Vec[Vec[Float64]], Vec[Vec[Float64]])¶
LU factorization of a square matrix (Doolittle, no pivoting): (L, U) with L unit lower triangular and A = L*U. Returns empty matrices on failure (non-square or zero pivot). O(n^3).
- Postcondition:
result._0.len() == 0 || (result._0.len() == a.len() && result._1.len() == a.len())
fn qr_decompose(a: &Vec[Vec[Float64]]) -> (Vec[Vec[Float64]], Vec[Vec[Float64]])¶
QR factorization of a square matrix via Gram-Schmidt on the columns: (Q, R) with Q orthogonal and A = Q*R. Empty matrices on failure. O(n^3).
- Postcondition:
result._0.len() == 0 || (result._0.len() == a.len() && result._1.len() == a.len())
fn svd_decompose(a: &Vec[Vec[Float64]]) -> (Vec[Vec[Float64]], Vec[Float64], Vec[Vec[Float64]])¶
SVD of a 2x2 symmetric matrix (U, s, V) via its eigendecomposition with s holding the eigenvalues in descending order. Empty values for any other input (documented). O(1).
- Postcondition:
result._0.len() == 0 || (result._0.len() == 2 && result._1.len() == 2 && result._2.len() == 2)
fn cholesky(a: &Vec[Vec[Float64]]) -> Option[Vec[Vec[Float64]]]¶
Cholesky factor L (lower triangular) of a symmetric positive definite matrix such that A = L*L^T. None when not SPD or non-square. O(n^3).
- Postcondition:
(result.is_some == false) || (a.len() > 0)
fn solve_linear(a: &Vec[Vec[Float64]], b: &Vec[Float64]) -> Vec[Float64]¶
Solve the square linear system A*x = b via Gauss-Jordan elimination with partial pivoting. Returns the empty vector when A is singular or the shapes do not match. O(n^3).
- Postcondition:
result.len() == 0 || result.len() == a.len()
fn least_squares(a: &Vec[Vec[Float64]], b: &Vec[Float64]) -> Vec[Float64]¶
Least-squares solution of the overdetermined system Ax = b via the normal equations A^TAx = A^Tb. Returns the empty vector on shape mismatch or a singular normal matrix. O(m*n^2 + n^3).
- Postcondition:
result.len() == 0 || a.len() > 0
fn condition_number(a: &Vec[Vec[Float64]]) -> Float64¶
Condition number of a 2x2 matrix: the ratio of the largest to the smallest singular value (singular values of A^T*A square-rooted). Returns NaN for non-2x2 input or a singular matrix (documented). O(1).
- Postcondition:
(result >= 0) || (result != result)
polyhedra.xi¶
fn cube_vertices(size: Float64) -> Vec[Vec[Float64]]¶
Vertices of a cube centered at the origin with the given side length. Returns 8 vertices, each (x, y, z). O(1).
- Postcondition:
result.len() == 8
fn cube_faces() -> Vec[Vec[Int]]¶
Face index list for cube_vertices: 12 triangles (3 indices each). O(1).
- Postcondition:
result.len() == 12
fn sphere_vertices(radius: Float64, slices: Int, stacks: Int) -> Vec[Vec[Float64]]¶
UV-sphere vertex grid: (slices + 1) x (stacks + 1) vertices of the form (x, y, z). O(slices * stacks).
- Postcondition:
(slices < 1 || stacks < 1) => result.len() == 0
fn icosahedron_vertices() -> Vec[Vec[Float64]]¶
Unit icosahedron vertices (12) in the standard layout: (+-1, +-phi, 0), (0, +-1, +-phi), (+-phi, 0, +-1). O(1).
- Postcondition:
result.len() == 12
fn icosahedron_faces() -> Vec[Vec[Int]]¶
Icosahedron faces: 20 triangles referencing icosahedron_vertices. O(1).
- Postcondition:
result.len() == 20
fn tetrahedron_vertices() -> Vec[Vec[Float64]]¶
Unit tetrahedron vertices (4). O(1).
- Postcondition:
result.len() == 4
fn octahedron_vertices() -> Vec[Vec[Float64]]¶
Unit octahedron vertices (6). O(1).
- Postcondition:
result.len() == 6
fn dodecahedron_vertices() -> Vec[Vec[Float64]]¶
Unit dodecahedron vertices (20). O(1).
- Postcondition:
result.len() == 20
fn convex_hull_2d(points: &Vec[Vec[Float64]]) -> Vec[Vec[Float64]]¶
Convex hull polygon of 2D points (monotone chain). The hull is returned without a duplicated closing vertex. O(n log n).
- Postcondition:
points.len() == 0 => result.len() == 0
fn convex_hull_3d(points: &Vec[Vec[Float64]]) -> Vec[Vec[Float64]]¶
Convex hull vertices of a 3D point cloud. Every oriented triangle (i, j, k) with all other points on (or behind) its plane is emitted as a hull face. O(n^4); exact for small point sets.
- Postcondition:
points.len() == 0 => result.len() == 0
quat.xi¶
type Quat¶
Quaternion (x, y, z, w); w is the scalar part.
| Field | Type |
|---|---|
x |
Float64 |
y |
Float64 |
z |
Float64 |
w |
Float64 |
fn quat_new(x: Float64, y: Float64, z: Float64, w: Float64) -> Quat¶
Construct a quaternion from components. Implemented locally (name collision with geom.quat_new which takes axis/angle). O(1).
- Postcondition:
result.x == x && result.y == y && result.z == z && result.w == w
fn quat_identity() -> Quat¶
Identity quaternion (no rotation). Implemented locally (name collision). O(1).
- Postcondition:
result.x == 0 && result.y == 0 && result.z == 0 && result.w == 1
fn quat_mul(a: Quat, b: Quat) -> Quat¶
Hamilton product a * b (compose rotations; b applied first). Implemented locally (name collision with geom.quat_mul). O(1).
- Postcondition:
((result.x == a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y) || (result.x != result.x)) && ((result.y == a.w * b.y - a.x * b.z + a.y * b.w + a.z * b.x) || (result.y != result.y)) && ((result.z == a.w * b.z + a.x * b.y - a.y * b.x + a.z * b.w) || (result.z != result.z)) && ((result.w == a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z) || (result.w != result.w))
fn quat_conjugate(q: Quat) -> Quat¶
Conjugate of a quaternion: negate the vector part. Implemented locally (name collision with geom.quat_conjugate). O(1).
- Postcondition:
((result.x == -q.x) || (result.x != result.x)) && ((result.y == -q.y) || (result.y != result.y)) && ((result.z == -q.z) || (result.z != result.z)) && ((result.w == q.w) || (result.w != result.w))
fn quat_inv(q: Quat) -> Quat¶
Inverse of a unit quaternion (the conjugate). Delegates to geom.quat_inverse through the canonical Quaternion type. O(1).
- Postcondition:
(result.x == 0 && result.y == 0 && result.z == 0 && result.w == 1) || (q.x != 0 || q.y != 0 || q.z != 0 || q.w != 0)
fn quat_norm(q: Quat) -> Float64¶
Euclidean length of a quaternion. Delegates to geom.quat_length. O(1).
- Postcondition:
(result >= 0) || (result != result)
fn quat_normalize(q: Quat) -> Quat¶
Unit quaternion; identity if the length is zero. Implemented locally (name collision with geom.quat_normalize). O(1).
- Postcondition:
(result.x == 0 && result.y == 0 && result.z == 0 && result.w == 1) || (q.x != 0 || q.y != 0 || q.z != 0 || q.w != 0)
fn quat_from_axis_angle(axis: &Vec[Float64], angle: Float64) -> Quat¶
Quaternion rotating angle (radians) about the (non-zero) axis direction. The axis is normalised first. Implemented locally (name collision). O(1).
- Postcondition:
(result.x == 0 && result.y == 0 && result.z == 0 && result.w == 1) || (axis.len() > 0)
fn quat_to_euler(q: Quat) -> (Float64, Float64, Float64)¶
Extract (yaw, pitch, roll) in radians, matching geom.quat_from_euler (ZYX intrinsic). Implemented locally: module-qualified results inside a tuple literal mis-type as Int in this compiler (BUG). O(1).
- Postcondition:
((result._0 >= -4 && result._0 <= 4) || (result._0 != result._0)) && ((result._1 >= -2 && result._1 <= 2) || (result._1 != result._1)) && ((result._2 >= -4 && result._2 <= 4) || (result._2 != result._2))
fn quat_slerp(a: Quat, b: Quat, t: Float64) -> Quat¶
Spherical linear interpolation between a and b by t in [0,1] along the shortest arc. Implemented locally (name collision with geom.quat_slerp). O(1).
- Postcondition:
(((result.x == a.x) || (result.x != result.x)) && ((result.y == a.y) || (result.y != result.y)) && ((result.z == a.z) || (result.z != result.z)) && ((result.w == a.w) || (result.w != result.w))) || (((result.x == b.x) || (result.x != result.x)) && ((result.y == b.y) || (result.y != result.y)) && ((result.z == b.z) || (result.z != result.z)) && ((result.w == b.w) || (result.w != result.w))) || (!(t <= 0) && !(t >= 1))
fn quat_rotate(q: Quat, v: &Vec[Float64]) -> Vec[Float64]¶
Rotate the 3D vector v by quaternion q. Delegates to geom.quat_rotate_vec3 through the canonical types. O(1).
- Postcondition:
result.len() == 0 || result.len() == 3
quaternion.xi¶
type Quat¶
Quaternion (x, y, z, w); w is the scalar part.
| Field | Type |
|---|---|
x |
Float64 |
y |
Float64 |
z |
Float64 |
w |
Float64 |
fn quat_new(x: Float64, y: Float64, z: Float64, w: Float64) -> Quat¶
Construct a quaternion from components. Implemented locally (name collision with geom.quat_new which takes axis/angle). O(1).
- Postcondition:
result.x == x && result.y == y && result.z == z && result.w == w
fn quat_identity() -> Quat¶
Identity quaternion (no rotation). Implemented locally (name collision). O(1).
- Postcondition:
result.x == 0 && result.y == 0 && result.z == 0 && result.w == 1
fn quat_from_axis_angle(axis: &Vec[Float64], angle: Float64) -> Quat¶
Quaternion rotating angle (radians) about the (non-zero) axis direction. The axis is normalised first. Implemented locally (name collision). O(1).
- Postcondition:
(result.x == 0 && result.y == 0 && result.z == 0 && result.w == 1) || (axis.len() > 0)
fn quat_from_euler(yaw: Float64, pitch: Float64, roll: Float64) -> Quat¶
Quaternion from ZYX intrinsic Euler angles (yaw around Z, pitch around Y, roll around X), in radians. Implemented locally (name collision with geom.quat_from_euler). O(1).
- Postcondition:
((result.x >= -2) && (result.x <= 2)) || (result.x != result.x) - Postcondition:
((result.y >= -2) && (result.y <= 2)) || (result.y != result.y) - Postcondition:
((result.z >= -2) && (result.z <= 2)) || (result.z != result.z) - Postcondition:
((result.w >= -2) && (result.w <= 2)) || (result.w != result.w)
fn quat_from_rotation_matrix(m: &Vec[Vec[Float64]]) -> Quat¶
Quaternion equivalent of a 3x3 rotation matrix (trace method). The matrix is copied locally before element access. Returns the identity quaternion for a non-3x3 input. O(1).
- Postcondition:
(result.x == 0 && result.y == 0 && result.z == 0 && result.w == 1) || (m.len() == 3)
fn quat_to_matrix(q: Quat) -> Vec[Vec[Float64]]¶
3x3 rotation matrix (row-major Vec[Vec[Float64]]) from a quaternion. The quaternion is normalised first. O(1).
- Postcondition:
result.len() == 3
fn quat_to_euler(q: Quat) -> (Float64, Float64, Float64)¶
Extract (yaw, pitch, roll) in radians, matching quat_from_euler (ZYX intrinsic). Implemented locally: module-qualified results inside a tuple literal mis-type as Int in this compiler (BUG). O(1).
- Postcondition:
((result._0 >= -4 && result._0 <= 4) || (result._0 != result._0)) - Postcondition:
((result._1 >= -2 && result._1 <= 2) || (result._1 != result._1)) - Postcondition:
((result._2 >= -4 && result._2 <= 4) || (result._2 != result._2))
fn quat_mul(a: Quat, b: Quat) -> Quat¶
Hamilton product a * b (compose rotations; b applied first). Implemented locally (name collision with geom.quat_mul). O(1).
- Postcondition:
((result.x == a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y) || (result.x != result.x)) && ((result.y == a.w * b.y - a.x * b.z + a.y * b.w + a.z * b.x) || (result.y != result.y)) && ((result.z == a.w * b.z + a.x * b.y - a.y * b.x + a.z * b.w) || (result.z != result.z)) && ((result.w == a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z) || (result.w != result.w))
fn quat_conj(q: Quat) -> Quat¶
Conjugate of a quaternion: negate the vector part. Delegates to geom.quat_conjugate (name differs). O(1).
- Postcondition:
((result.x == -q.x) || (result.x != result.x)) && ((result.y == -q.y) || (result.y != result.y)) && ((result.z == -q.z) || (result.z != result.z)) && ((result.w == q.w) || (result.w != result.w))
fn quat_inv(q: Quat) -> Quat¶
Inverse of a unit quaternion (the conjugate). Delegates to geom.quat_inverse (name differs). O(1).
- Postcondition:
(result.x == 0 && result.y == 0 && result.z == 0 && result.w == 1) || (q.x != 0 || q.y != 0 || q.z != 0 || q.w != 0)
fn quat_norm(q: Quat) -> Float64¶
Euclidean length of a quaternion. Delegates to geom.quat_length (name differs). O(1).
- Postcondition:
(result >= 0) || (result != result)
fn quat_normalize(q: Quat) -> Quat¶
Unit quaternion; identity if the length is zero. Implemented locally (name collision with geom.quat_normalize). O(1).
- Postcondition:
(result.x == 0 && result.y == 0 && result.z == 0 && result.w == 1) || (q.x != 0 || q.y != 0 || q.z != 0 || q.w != 0)
fn quat_rotate(q: Quat, v: &Vec[Float64]) -> Vec[Float64]¶
Rotate the 3D vector v by quaternion q. Delegates to geom.quat_rotate_vec3 through the canonical types. O(1).
- Postcondition:
result.len() == 0 || result.len() == 3
fn quat_slerp(a: Quat, b: Quat, t: Float64) -> Quat¶
Spherical linear interpolation between a and b by t in [0,1] along the shortest arc. Implemented locally (name collision with geom.quat_slerp). O(1).
- Postcondition:
(((result.x == a.x) || (result.x != result.x)) && ((result.y == a.y) || (result.y != result.y)) && ((result.z == a.z) || (result.z != result.z)) && ((result.w == a.w) || (result.w != result.w))) || (((result.x == b.x) || (result.x != result.x)) && ((result.y == b.y) || (result.y != result.y)) && ((result.z == b.z) || (result.z != result.z)) && ((result.w == b.w) || (result.w != result.w))) || (!(t <= 0) && !(t >= 1))
fn quat_nlerp(a: Quat, b: Quat, t: Float64) -> Quat¶
Normalised linear interpolation between a and b by t (t clamped to [0,1]). Implemented locally (name collision with geom.quat_nlerp). O(1).
- Postcondition:
(result.x == 0 && result.y == 0 && result.z == 0 && result.w == 1) || (a.x != 0 || a.y != 0 || a.z != 0 || a.w != 0 || b.x != 0 || b.y != 0 || b.z != 0 || b.w != 0)
fn quat_angle(q: Quat) -> Float64¶
Rotation angle of q in radians, in [0, PI]. 0 for the identity. O(1).
- Postcondition:
((result >= 0) && (result <= 7)) || (result != result)
fn quat_axis(q: Quat) -> Vec[Float64]¶
Unit rotation axis of q (direction of the vector part). Returns the zero vector when q represents no rotation. O(1).
- Postcondition:
result.len() == 3
fn quat_look_at(eye: &Vec[Float64], target: &Vec[Float64], up: &Vec[Float64]) -> Quat¶
Orientation quaternion looking from eye towards target with the given up direction (right-handed). Returns the identity for a degenerate look. O(1).
- Postcondition:
((result.x >= -2) && (result.x <= 2)) || (result.x != result.x) - Postcondition:
((result.y >= -2) && (result.y <= 2)) || (result.y != result.y) - Postcondition:
((result.z >= -2) && (result.z <= 2)) || (result.z != result.z) - Postcondition:
((result.w >= -2) && (result.w <= 2)) || (result.w != result.w)
fn quat_between(a: &Vec[Float64], b: &Vec[Float64]) -> Quat¶
Shortest rotation quaternion mapping the unit direction a onto the unit direction b. Returns the identity when a or b is degenerate. O(1).
- Postcondition:
((result.x >= -2) && (result.x <= 2)) || (result.x != result.x) - Postcondition:
((result.y >= -2) && (result.y <= 2)) || (result.y != result.y) - Postcondition:
((result.z >= -2) && (result.z <= 2)) || (result.z != result.z) - Postcondition:
((result.w >= -2) && (result.w <= 2)) || (result.w != result.w)
vec.xi¶
fn vec2(x: Float64, y: Float64) -> Vec2¶
Construct a 2D vector. Delegates to geom.vec2_new. O(1).
- Postcondition:
result.x == x && result.y == y
fn vec2_add(a: Vec2, b: Vec2) -> Vec2¶
Add two 2D vectors component-wise. Implemented locally (same name as the canonical geom.vec2_add; same-name delegation is avoided). O(1).
- Postcondition:
((result.x == a.x + b.x) || (result.x != result.x)) && ((result.y == a.y + b.y) || (result.y != result.y))
fn vec2_sub(a: Vec2, b: Vec2) -> Vec2¶
Subtract b from a component-wise. Implemented locally (name collision). O(1).
- Postcondition:
((result.x == a.x - b.x) || (result.x != result.x)) && ((result.y == a.y - b.y) || (result.y != result.y))
fn vec2_scale(v: Vec2, s: Float64) -> Vec2¶
Multiply each component of v by scalar s. Delegates to geom.vec2_mul_scalar. O(1).
- Postcondition:
((result.x == v.x * s) || (result.x != result.x)) && ((result.y == v.y * s) || (result.y != result.y))
fn vec2_dot(a: Vec2, b: Vec2) -> Float64¶
Dot product of two 2D vectors. Implemented locally (name collision). O(1).
- Postcondition:
(result == a.x * b.x + a.y * b.y) || (result != result)
fn vec2_cross(a: Vec2, b: Vec2) -> Float64¶
2D cross product (scalar, signed area). Implemented locally (name collision). O(1).
- Postcondition:
(result == a.x * b.y - a.y * b.x) || (result != result)
fn vec2_len(v: Vec2) -> Float64¶
Euclidean length of a 2D vector. Delegates to geom.vec2_length. O(1).
- Postcondition:
(result >= 0) || (result != result)
fn vec2_norm(v: Vec2) -> Vec2¶
Unit vector of v; zero vector when the length is zero. Delegates to geom.vec2_normalize. O(1).
- Postcondition:
(result.x == 0 && result.y == 0) || (v.x != 0 || v.y != 0)
fn vec2_dist(a: Vec2, b: Vec2) -> Float64¶
Euclidean distance between two 2D points. Delegates to geom.vec2_distance. O(1).
- Postcondition:
(result >= 0) || (result != result)
fn vec2_lerp(a: Vec2, b: Vec2, t: Float64) -> Vec2¶
Linear interpolation between a and b by t (t outside [0,1] extrapolates). Implemented locally: the canonical geom.vec2_lerp returns a scalar, so a full Vec2 result requires a dedicated implementation. O(1).
- Postcondition:
((result.x == a.x + (b.x - a.x) * t) || (result.x != result.x)) && ((result.y == a.y + (b.y - a.y) * t) || (result.y != result.y))
fn vec3_new(x: Float64, y: Float64, z: Float64) -> Vec3¶
Construct a 3D vector. Implemented locally (name collision). O(1).
- Postcondition:
result.x == x && result.y == y && result.z == z
fn vec3_add(a: Vec3, b: Vec3) -> Vec3¶
Add two 3D vectors component-wise. Implemented locally (name collision). O(1).
- Postcondition:
((result.x == a.x + b.x) || (result.x != result.x)) && ((result.y == a.y + b.y) || (result.y != result.y)) && ((result.z == a.z + b.z) || (result.z != result.z))
fn vec3_sub(a: Vec3, b: Vec3) -> Vec3¶
Subtract b from a component-wise. Implemented locally (name collision). O(1).
- Postcondition:
((result.x == a.x - b.x) || (result.x != result.x)) && ((result.y == a.y - b.y) || (result.y != result.y)) && ((result.z == a.z - b.z) || (result.z != result.z))
fn vec3_scale(v: Vec3, s: Float64) -> Vec3¶
Multiply each component of v by scalar s. Delegates to geom.vec3_mul_scalar. O(1).
- Postcondition:
((result.x == v.x * s) || (result.x != result.x)) && ((result.y == v.y * s) || (result.y != result.y)) && ((result.z == v.z * s) || (result.z != result.z))
fn vec3_dot(a: Vec3, b: Vec3) -> Float64¶
Dot product of two 3D vectors. Implemented locally (name collision). O(1).
- Postcondition:
(result == a.x * b.x + a.y * b.y + a.z * b.z) || (result != result)
fn vec3_cross(a: Vec3, b: Vec3) -> Vec3¶
Right-handed cross product a x b. Implemented locally (name collision). O(1).
- Postcondition:
((result.x == a.y * b.z - a.z * b.y) || (result.x != result.x)) && ((result.y == a.z * b.x - a.x * b.z) || (result.y != result.y)) && ((result.z == a.x * b.y - a.y * b.x) || (result.z != result.z))
fn vec3_len(v: Vec3) -> Float64¶
Euclidean length of a 3D vector. Delegates to geom.vec3_length. O(1).
- Postcondition:
(result >= 0) || (result != result)
fn vec3_norm(v: Vec3) -> Vec3¶
Unit vector of v; zero vector when the length is zero. Delegates to geom.vec3_normalize. O(1).
- Postcondition:
(result.x == 0 && result.y == 0 && result.z == 0) || (v.x != 0 || v.y != 0 || v.z != 0)
fn vec3_dist(a: Vec3, b: Vec3) -> Float64¶
Euclidean distance between two 3D points. Delegates to geom.vec3_distance. O(1).
- Postcondition:
(result >= 0) || (result != result)
fn vec4_new(x: Float64, y: Float64, z: Float64, w: Float64) -> Vec4¶
Construct a 4D vector. Implemented locally (name collision). O(1).
- Postcondition:
result.x == x && result.y == y && result.z == z && result.w == w
fn vec4_add(a: Vec4, b: Vec4) -> Vec4¶
Add two 4D vectors component-wise. No canonical dynamic equivalent; local. O(1).
- Postcondition:
((result.x == a.x + b.x) || (result.x != result.x)) && ((result.y == a.y + b.y) || (result.y != result.y)) && ((result.z == a.z + b.z) || (result.z != result.z)) && ((result.w == a.w + b.w) || (result.w != result.w))
fn vec4_sub(a: Vec4, b: Vec4) -> Vec4¶
Subtract b from a component-wise. No canonical dynamic equivalent; local. O(1).
- Postcondition:
((result.x == a.x - b.x) || (result.x != result.x)) && ((result.y == a.y - b.y) || (result.y != result.y)) && ((result.z == a.z - b.z) || (result.z != result.z)) && ((result.w == a.w - b.w) || (result.w != result.w))
fn vec4_scale(v: Vec4, s: Float64) -> Vec4¶
Multiply each component of v by scalar s. Implemented locally (name collision). O(1).
- Postcondition:
((result.x == v.x * s) || (result.x != result.x)) && ((result.y == v.y * s) || (result.y != result.y)) && ((result.z == v.z * s) || (result.z != result.z)) && ((result.w == v.w * s) || (result.w != result.w))
fn vec4_dot(a: Vec4, b: Vec4) -> Float64¶
Dot product of two 4D vectors. No canonical equivalent; local. O(1).
- Postcondition:
(result == a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w) || (result != result)
fn vec4_len(v: Vec4) -> Float64¶
Euclidean length of a 4D vector. No canonical equivalent; local. O(4).
- Postcondition:
(result >= 0) || (result != result)
fn vec4_norm(v: Vec4) -> Vec4¶
Unit vector of v; zero vector when the length is zero. Local (no canonical equivalent for the vec4 length/normalise pair). O(4).
- Postcondition:
(result.x == 0 && result.y == 0 && result.z == 0 && result.w == 0) || (v.x != 0 || v.y != 0 || v.z != 0 || v.w != 0)
fn vec_reflect(v: &Vec[Float64], n: &Vec[Float64]) -> Vec[Float64]¶
Reflect the dynamic vector v about a surface normal n: v - 2dot(v,n)n. The normal is normalised first (any non-zero direction is accepted); a degenerate normal returns a copy of v. Dynamic-domain operation with no canonical Vec[Float64] equivalent in geom.xi. O(n).
- Postcondition:
result.len() == 0 || result.len() == v.len()
fn vec_project(a: &Vec[Float64], b: &Vec[Float64]) -> Float64¶
Scalar projection of a onto b: dot(a, b) / |b|. Zero when b is degenerate. Dynamic-domain operation with no canonical Vec[Float64] equivalent. O(n).
- Postcondition:
(result == 0) || (result != result) || (a.len() == b.len() && b.len() > 0)
fn vec_angle(a: &Vec[Float64], b: &Vec[Float64]) -> Float64¶
Angle in radians between two equal-length non-zero vectors, in [0, PI]. Returns 0 when either vector is degenerate. Dynamic-domain operation. O(n).
- Postcondition:
(result >= 0 && result < 4) || (result != result)
vector.xi¶
type Vec2¶
2-component vector (x, y).
| Field | Type |
|---|---|
x |
Float64 |
y |
Float64 |
type Vec3¶
3-component vector (x, y, z).
| Field | Type |
|---|---|
x |
Float64 |
y |
Float64 |
z |
Float64 |
type Vec4¶
4-component vector (x, y, z, w).
| Field | Type |
|---|---|
x |
Float64 |
y |
Float64 |
z |
Float64 |
w |
Float64 |
type VecN¶
Dynamic N-component vector.
| Field | Type |
|---|---|
data |
Vec[Float64] |
fn v2_new(x: Float64, y: Float64) -> Vec2¶
Construct a 2D vector. O(1).
- Postcondition:
result.x == x && result.y == y
fn v3_new(x: Float64, y: Float64, z: Float64) -> Vec3¶
Construct a 3D vector. O(1).
- Postcondition:
result.x == x && result.y == y && result.z == z
fn v4_new(x: Float64, y: Float64, z: Float64, w: Float64) -> Vec4¶
Construct a 4D vector. O(1).
- Postcondition:
result.x == x && result.y == y && result.z == z && result.w == w
fn dot(a: &Vec[Float64], b: &Vec[Float64]) -> Float64¶
Dot product of two equal-length dynamic vectors. Returns NaN (0.0/0.0) when the lengths differ or either is empty (documented; no silent garbage). O(n).
- Postcondition:
a.len() != b.len() => result != result
fn cross(a: &Vec[Float64], b: &Vec[Float64]) -> Vec[Float64]¶
3D cross product of two 3-element dynamic vectors. Returns an empty vector when either input is not exactly length 3 (documented). O(3).
- Postcondition:
result.len() == 0 || result.len() == 3
fn cross2(a: Vec2, b: Vec2) -> Float64¶
2D cross product (signed area) of two 2D vectors. O(1).
- Postcondition:
(result == a.x * b.y - a.y * b.x) || (result != result)
fn outer(a: &Vec[Float64], b: &Vec[Float64]) -> Vec[Vec[Float64]]¶
Outer product matrix a (x) b: row i, col j holds a[i] * b[j]. O(n*m).
- Postcondition:
result.len() == a.len()
fn norm(v: &Vec[Float64]) -> Float64¶
Euclidean length of a dynamic vector. O(n).
- Postcondition:
(result >= 0) || (result != result)
fn norm_sq(v: &Vec[Float64]) -> Float64¶
Squared Euclidean length of a dynamic vector (avoids sqrt). O(n).
- Postcondition:
(result >= 0) || (result != result)
fn normalize(v: &Vec[Float64]) -> Vec[Float64]¶
Unit vector of v. Returns the zero vector when the length is zero (documented). O(n).
- Postcondition:
result.len() == 0 || result.len() == v.len()
fn unit(v: &Vec[Float64]) -> Vec[Float64]¶
Alias of normalize. O(n).
- Postcondition:
result.len() == 0 || result.len() == v.len()
fn distance(a: &Vec[Float64], b: &Vec[Float64]) -> Float64¶
Euclidean distance between two equal-length vectors. Returns NaN when the lengths differ (documented). O(n).
- Postcondition:
a.len() != b.len() => result != result - Postcondition:
(result >= 0) || (result != result)
fn distance_sq(a: &Vec[Float64], b: &Vec[Float64]) -> Float64¶
Squared Euclidean distance between two equal-length vectors. Returns NaN when the lengths differ (documented). O(n).
- Postcondition:
a.len() != b.len() => result != result - Postcondition:
(result >= 0) || (result != result)
fn angle(a: &Vec[Float64], b: &Vec[Float64]) -> Float64¶
Angle in radians between two equal-length non-zero vectors, in [0, PI]. Returns 0 when either vector is degenerate. O(n).
- Postcondition:
((result >= 0) && (result <= 4)) || (result != result)
fn project(a: &Vec[Float64], b: &Vec[Float64]) -> Vec[Float64]¶
Projection of a onto b: b * dot(a,b) / dot(b,b). Returns the zero vector when b is degenerate. O(n).
- Postcondition:
result.len() == 0 || result.len() == b.len()
fn reject(a: &Vec[Float64], b: &Vec[Float64]) -> Vec[Float64]¶
Reject a from b: a - project(a,b), the component of a perpendicular to b. Requires equal-length inputs; empty vector otherwise. O(n).
- Postcondition:
result.len() == 0 || result.len() == a.len()
fn lerp(a: &Vec[Float64], b: &Vec[Float64], t: Float64) -> Vec[Float64]¶
Linear interpolation between a and b by t (t outside [0,1] extrapolates). Requires equal-length inputs; empty vector otherwise. O(n).
- Postcondition:
result.len() == 0 || result.len() == a.len()
fn slerp(a: &Vec[Float64], b: &Vec[Float64], t: Float64) -> Vec[Float64]¶
Spherical linear interpolation between two equal-length non-zero vectors at parameter t in [0,1], with constant angular velocity. Falls back to lerp for near-parallel inputs; returns the empty vector for degenerate inputs. O(n).
- Postcondition:
result.len() == 0 || result.len() == a.len()
fn reflect(v: &Vec[Float64], normal: &Vec[Float64]) -> Vec[Float64]¶
Reflect v about the (not necessarily unit) normal: v - 2dot(v,n)/dot(n,n)n. Empty vector when the normal is degenerate or lengths differ. O(n).
- Postcondition:
result.len() == 0 || result.len() == v.len()
fn refract(v: &Vec[Float64], normal: &Vec[Float64], eta: Float64) -> Option[Vec[Float64]]¶
Refract v across an interface with relative index eta (both inputs unit). Returns None on total internal reflection (k < 0) or length mismatch. O(n).
- Postcondition:
(v.len() != normal.len() || v.len() == 0) => result.is_some == false - Postcondition:
result.is_some => v.len() == normal.len()
fn clamp(v: &Vec[Float64], lo: Float64, hi: Float64) -> Vec[Float64]¶
Clamp each component of v into [lo, hi]. O(n).
- Postcondition:
result.len() == v.len()
fn component_min(v: &Vec[Float64]) -> Float64¶
Smallest component of v. Returns NaN for an empty vector (documented). O(n).
- Postcondition:
v.len() == 0 => result != result
fn component_max(v: &Vec[Float64]) -> Float64¶
Largest component of v. Returns NaN for an empty vector (documented). O(n).
- Postcondition:
v.len() == 0 => result != result
fn hadamard(a: &Vec[Float64], b: &Vec[Float64]) -> Vec[Float64]¶
Component-wise product (Hadamard) of two equal-length vectors. Empty vector on length mismatch (documented). O(n).
- Postcondition:
result.len() == 0 || result.len() == a.len()