tiny.geometry.intersect
Defined in tiny.geometry.
Ray casts and overlap tests between shapes.
API (16)
Actions
Public operations.
aabbAabbcapsuleCapsulecapsuleTrianglelineSphereRoots: The two roots of|origin + direction t - center|^2 = radius^2, or null when the line stays outside the sphere or the direction has no length.rayAabbrayObbrayPlaneraySlab: The span oftover which a ray lies inside a box, clipped to[0, t_max], or null when the ray misses within that span.raySphererayTriangle: Möller–Trumbore over the whole line, from both sides.sphereAabbsphereCapsulesphereObbsphereSpheresphereTriangle
Types and contracts
Public types and contracts.
TriangleHit: Where a ray meets a triangle: the ray parameter and the barycentric weights of the second and third corners.
Source
Source: lib/geometry/src/intersect.zig
zig
//! Ray casts and overlap tests between shapes.//!//! Ray queries report the parameter `t` of the first point on or inside the//! shape, in units of the ray direction, and ignore the part of the ray behind//! its origin. A ray starting inside a solid reports `t = 0`. Triangles are//! two-sided.const std = @import("std");const linear = @import("linear");const closest = @import("closest.zig");const primitive = @import("primitive.zig");const tolerance = @import("tolerance.zig");const Aabb = primitive.Aabb;const Capsule = primitive.Capsule;const Obb = primitive.Obb;const Plane = primitive.Plane;const Ray = primitive.Ray;const Sphere = primitive.Sphere;const Triangle = primitive.Triangle;const Vec3 = linear.Vec3;/// Where a ray meets a triangle: the ray parameter and the barycentric/// weights of the second and third corners.pub const TriangleHit = struct { t: f32, u: f32, v: f32,};/// Möller–Trumbore over the whole line, from both sides. The caller filters/// `t` to its span. A ray that grazes the triangle plane, or meets a/// degenerate triangle, misses, because its determinant is parallel noise.pub fn rayTriangle(ray: Ray, triangle: Triangle) ?TriangleHit { const edge1 = triangle.b.sub(triangle.a); const edge2 = triangle.c.sub(triangle.a); const pvec = ray.direction.cross(edge2); const determinant = edge1.dot(pvec); const factors_sq = edge1.lengthSq() * edge2.lengthSq() * ray.direction.lengthSq(); if (tolerance.isParallel(determinant * determinant, factors_sq)) return null; const inverse = 1.0 / determinant; const tvec = ray.origin.sub(triangle.a); const u = tvec.dot(pvec) * inverse; if (u < 0.0 or u > 1.0) return null; const qvec = tvec.cross(edge1); const v = ray.direction.dot(qvec) * inverse; if (v < 0.0 or u + v > 1.0) return null; return .{ .t = edge2.dot(qvec) * inverse, .u = u, .v = v };}/// The two roots of `|origin + direction t - center|^2 = radius^2`, or null/// when the line stays outside the sphere or the direction has no length.pub fn lineSphereRoots(origin: Vec3, direction: Vec3, center: Vec3, radius: f32) ?[2]f32 { const m = origin.sub(center); const a = direction.lengthSq(); if (a <= tolerance.closing_sq_floor) return null; const b = m.dot(direction); const c = m.lengthSq() - radius * radius; const discriminant = b * b - a * c; if (discriminant < 0) return null; const root = @sqrt(discriminant); return .{ (-b - root) / a, (-b + root) / a };}pub fn raySphere(ray: Ray, sphere: Sphere) ?f32 { const m = ray.origin.sub(sphere.center); if (m.lengthSq() <= sphere.radius * sphere.radius) return 0; const roots = lineSphereRoots(ray.origin, ray.direction, sphere.center, sphere.radius) orelse return null; if (roots[1] < 0) return null; return @max(roots[0], 0);}/// The span of `t` over which a ray lies inside a box, clipped to `[0, t_max]`,/// or null when the ray misses within that span.pub fn raySlab(ray: Ray, box: Aabb, t_max: f32) ?[2]f32 { const origin = ray.origin.toArray(); const direction = ray.direction.toArray(); const low = box.min.toArray(); const high = box.max.toArray(); var near: f32 = 0; var far: f32 = t_max; for (0..3) |axis| { if (@abs(direction[axis]) <= tolerance.direction_component_floor) { if (origin[axis] < low[axis] or origin[axis] > high[axis]) return null; continue; } const inverse = 1.0 / direction[axis]; const first = (low[axis] - origin[axis]) * inverse; const second = (high[axis] - origin[axis]) * inverse; near = @max(near, @min(first, second)); far = @min(far, @max(first, second)); if (near > far) return null; } return .{ near, far };}pub fn rayAabb(ray: Ray, box: Aabb) ?f32 { const span = raySlab(ray, box, std.math.inf(f32)) orelse return null; return span[0];}pub fn rayObb(ray: Ray, box: Obb) ?f32 { const local = Ray{ .origin = box.toLocal(ray.origin), .direction = .{ .x = ray.direction.dot(box.axes.cols[0]), .y = ray.direction.dot(box.axes.cols[1]), .z = ray.direction.dot(box.axes.cols[2]), }, }; return rayAabb(local, .{ .min = box.half_extents.negate(), .max = box.half_extents });}pub fn rayPlane(ray: Ray, plane: Plane) ?f32 { const closing = plane.normal.dot(ray.direction); if (tolerance.isParallel(closing * closing, ray.direction.lengthSq())) return null; const t = -plane.signedDistance(ray.origin) / closing; if (t < 0) return null; return t;}pub fn sphereSphere(a: Sphere, b: Sphere) bool { const reach = a.radius + b.radius; return a.center.distanceSq(b.center) <= reach * reach;}pub fn sphereAabb(sphere: Sphere, box: Aabb) bool { return closest.pointAabb(sphere.center, box).distanceSq(sphere.center) <= sphere.radius * sphere.radius;}pub fn sphereObb(sphere: Sphere, box: Obb) bool { return closest.pointObb(sphere.center, box).distanceSq(sphere.center) <= sphere.radius * sphere.radius;}pub fn sphereTriangle(sphere: Sphere, triangle: Triangle) bool { const nearest = closest.pointTriangle(sphere.center, triangle); return nearest.distanceSq(sphere.center) <= sphere.radius * sphere.radius;}pub fn sphereCapsule(sphere: Sphere, capsule: Capsule) bool { const reach = sphere.radius + capsule.radius; return closest.pointSegment(sphere.center, capsule.axis()).distanceSq(sphere.center) <= reach * reach;}pub fn capsuleCapsule(a: Capsule, b: Capsule) bool { const reach = a.radius + b.radius; return closest.segmentSegment(a.axis(), b.axis()).distanceSq() <= reach * reach;}pub fn capsuleTriangle(capsule: Capsule, triangle: Triangle) bool { return closest.segmentTriangle(capsule.axis(), triangle).distanceSq() <= capsule.radius * capsule.radius;}pub fn aabbAabb(a: Aabb, b: Aabb) bool { return a.overlaps(b);}const testing = std.testing;const unit_triangle = Triangle{ .a = .{}, .b = Vec3.init(1, 0, 0), .c = Vec3.init(0, 1, 0) };const down = Vec3.init(0, 0, -1);test "a ray meets a triangle from either side with barycentric weights" { const hit = rayTriangle(.{ .origin = Vec3.init(0.25, 0.5, 3), .direction = down }, unit_triangle).?; try testing.expectEqual(@as(f32, 3), hit.t); try testing.expectEqual(@as(f32, 0.25), hit.u); try testing.expectEqual(@as(f32, 0.5), hit.v); const below = rayTriangle(.{ .origin = Vec3.init(0.25, 0.25, -2), .direction = down.negate() }, unit_triangle).?; try testing.expectEqual(@as(f32, 2), below.t); try testing.expectEqual(@as(?TriangleHit, null), rayTriangle(.{ .origin = Vec3.init(0.9, 0.9, 1), .direction = down }, unit_triangle)); const grazing = Ray{ .origin = Vec3.init(-1, 0.25, 0), .direction = Vec3.init(1, 0, 0) }; try testing.expectEqual(@as(?TriangleHit, null), rayTriangle(grazing, unit_triangle));}test "a ray enters a sphere, starts inside it, or misses" { const sphere = Sphere{ .center = Vec3.init(0, 0, -5), .radius = 1 }; try testing.expectEqual(@as(?f32, 4), raySphere(.{ .origin = .{}, .direction = down }, sphere)); try testing.expectEqual(@as(?f32, 0), raySphere(.{ .origin = Vec3.init(0, 0, -5), .direction = down }, sphere)); try testing.expectEqual(@as(?f32, null), raySphere(.{ .origin = .{}, .direction = down.negate() }, sphere)); try testing.expectEqual(@as(?f32, null), raySphere(.{ .origin = Vec3.init(3, 0, 0), .direction = down }, sphere));}test "a ray enters a box through the nearest slab" { const box = Aabb{ .min = Vec3.init(-1, -1, -1), .max = Vec3.init(1, 1, 1) }; try testing.expectEqual(@as(?f32, 4), rayAabb(.{ .origin = Vec3.init(0, 0, 5), .direction = down }, box)); try testing.expectEqual(@as(?f32, 0), rayAabb(.{ .origin = .{}, .direction = down }, box)); try testing.expectEqual(@as(?f32, null), rayAabb(.{ .origin = Vec3.init(2, 0, 5), .direction = down }, box)); const span = raySlab(.{ .origin = Vec3.init(0, 0, 5), .direction = down }, box, 5).?; try testing.expectEqual([2]f32{ 4, 5 }, span); try testing.expectEqual(@as(?[2]f32, null), raySlab(.{ .origin = Vec3.init(0, 0, 5), .direction = down }, box, 3));}test "a ray enters a turned box in its own frame" { const turned = Obb{ .center = Vec3.init(0, 0, -5), .axes = linear.Mat3.fromCols(Vec3.init(0, 1, 0), Vec3.init(-1, 0, 0), Vec3.init(0, 0, 1)), .half_extents = Vec3.init(3, 0.5, 1), }; try testing.expectEqual(@as(?f32, 4), rayObb(.{ .origin = Vec3.init(0, 2, 0), .direction = down }, turned)); try testing.expectEqual(@as(?f32, null), rayObb(.{ .origin = Vec3.init(2, 0, 0), .direction = down }, turned));}test "a ray meets a plane ahead of it" { const floor = Plane.fromPointNormal(.{}, Vec3.init(0, 0, 1)); try testing.expectEqual(@as(?f32, 2), rayPlane(.{ .origin = Vec3.init(1, 1, 2), .direction = down }, floor)); try testing.expectEqual(@as(?f32, null), rayPlane(.{ .origin = Vec3.init(1, 1, 2), .direction = down.negate() }, floor)); try testing.expectEqual(@as(?f32, null), rayPlane(.{ .origin = Vec3.init(1, 1, 2), .direction = Vec3.init(1, 0, 0) }, floor));}test "overlaps hold at contact and fail past it" { const ball = Sphere{ .center = Vec3.init(0.25, 0.25, 1), .radius = 1 }; try testing.expect(sphereTriangle(ball, unit_triangle)); try testing.expect(!sphereTriangle(.{ .center = ball.center, .radius = 0.99 }, unit_triangle)); try testing.expect(sphereSphere(ball, .{ .center = Vec3.init(0.25, 0.25, 3), .radius = 1 })); try testing.expect(!sphereSphere(ball, .{ .center = Vec3.init(0.25, 0.25, 3.5), .radius = 1 })); const box = Aabb{ .min = Vec3.init(2, 0, 0), .max = Vec3.init(3, 1, 1) }; try testing.expect(sphereAabb(.{ .center = Vec3.init(1, 0.5, 0.5), .radius = 1 }, box)); try testing.expect(!sphereAabb(.{ .center = Vec3.init(0.5, 0.5, 0.5), .radius = 1 }, box)); try testing.expect(aabbAabb(box, .{ .min = Vec3.init(3, 1, 1), .max = Vec3.init(4, 2, 2) })); try testing.expect(!aabbAabb(box, .{ .min = Vec3.init(3.5, 0, 0), .max = Vec3.init(4, 1, 1) })); const upright = Capsule{ .a = Vec3.init(0.25, 0.25, 0.5), .b = Vec3.init(0.25, 0.25, 2), .radius = 0.5 }; try testing.expect(capsuleTriangle(upright, unit_triangle)); try testing.expect(!capsuleTriangle(.{ .a = upright.a, .b = upright.b, .radius = 0.25 }, unit_triangle)); const other = Capsule{ .a = Vec3.init(-1, 0, 3), .b = Vec3.init(1, 0, 3), .radius = 0.5 }; const lying = Capsule{ .a = Vec3.init(0, -1, 2), .b = Vec3.init(0, 1, 2), .radius = 0.5 }; try testing.expect(capsuleCapsule(other, lying)); try testing.expect(!capsuleCapsule(other, .{ .a = lying.a, .b = lying.b, .radius = 0.25 })); try testing.expect(sphereCapsule(.{ .center = Vec3.init(0, 0, 1), .radius = 0.5 }, lying)); try testing.expect(!sphereCapsule(.{ .center = Vec3.init(0, 0, 0.5), .radius = 0.5 }, lying)); const turned = Obb{ .center = .{}, .half_extents = Vec3.init(1, 2, 3) }; try testing.expect(sphereObb(.{ .center = Vec3.init(0, 0, 3.5), .radius = 0.5 }, turned)); try testing.expect(!sphereObb(.{ .center = Vec3.init(1.5, 2.5, 0), .radius = 0.5 }, turned));}Source: lib/geometry/src/root.zig:9
zig
pub const intersect = @import("intersect.zig");Audit
| Definitions | 17 |
|---|---|
| Public names | 17 |
| Members | 3 |
| Version | 26.7.0 |
| Revision | daab053ee433 |