Skip to documentation
SLOP

tiny.geometry.closest

Reference tiny.geometry closest

Defined in tiny.geometry.

Closest points between shapes, after Ericson, Real-Time Collision Detection, chapter 5.

API (10)

Actions

Public operations.

Types and contracts

Public types and contracts.

No direct callersNo direct callstiny.geometryclosest
Static calls · unresolved targets: unknown · external targets: unknown.

Source

Called byCallsNo direct callstest sourcelib.geometry.src.closesttest: point to box and plane clamp an...intersectsphereAabbclosestpointAabb
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallsNo direct callstest sourcelib.geometry.src.closesttest: point to box and plane clamp an...intersectsphereObbclosestpointObb
Static calls · unresolved targets: 0 · external targets: 4.
Called byCallsNo direct callstest sourcelib.geometry.src.closesttest: point to box and plane clamp an...closestpointPlane
Static calls · unresolved targets: 0 · external targets: 3.
Called byCallsprivate sourcelib.geometry.src.closestpointDegenerateTriangletest sourcelib.geometry.src.closesttest: point to segment clamps to the ...intersectsphereCapsuleclosestsegmentParameterclosestpointSegment
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsclosestsegmentTriangletest sourcelib.geometry.src.closesttest: a triangle with no area answers...test sourcelib.geometry.src.closesttest: point to triangle answers from ...intersectsphereTrianglesweepsphereTriangleprivate sourcelib.geometry.src.closestpointDegenerateTriangleclosestpointTriangle
Static calls · unresolved targets: 2 · external targets: 8.
Called byCallsclosestpointSegmentprivate sourcelib.geometry.src.closestclamp01closestsegmentParameter
Static calls · unresolved targets: 0 · external targets: 3.
Called byCallsclosestsegmentTriangletest sourcelib.geometry.src.closesttest: segment pairs meet at crossings...intersectcapsuleCapsuleprivate sourcelib.geometry.src.closestclamp01closestsegmentSegment
Static calls · unresolved targets: 0 · external targets: 9.
Called byCallstest sourcelib.geometry.src.closesttest: segment to triangle finds cross...intersectcapsuleTrianglesweepcapsuleTriangleclosestpointTriangleclosestsegmentSegmentintersectrayTriangleclosestsegmentTriangle
Static calls · unresolved targets: 0 · external targets: 5.

Source: lib/geometry/src/closest.zig

zig
//! Closest points between shapes, after Ericson, Real-Time Collision//! Detection, chapter 5. Each query returns points on both shapes, so a caller//! reads the separation as their difference.const std = @import("std");const linear = @import("linear");const primitive = @import("primitive.zig");const intersect = @import("intersect.zig");const tolerance = @import("tolerance.zig");const assert = std.debug.assert;const Aabb = primitive.Aabb;const Obb = primitive.Obb;const Plane = primitive.Plane;const Ray = primitive.Ray;const Segment = primitive.Segment;const Triangle = primitive.Triangle;const Vec3 = linear.Vec3;/// A point on each of two shapes, with each point's parameter on its segment/// where the shape is one.pub const Pair = struct {    first: Vec3,    second: Vec3,    first_t: f32 = 0,    second_t: f32 = 0,    pub fn distanceSq(pair: Pair) f32 {        return pair.first.distanceSq(pair.second);    }};fn clamp01(value: f32) f32 {    return @max(0, @min(1, value));}/// The parameter of the point on `segment` nearest `p`.pub fn segmentParameter(p: Vec3, segment: Segment) f32 {    const ab = segment.b.sub(segment.a);    const length_sq = ab.lengthSq();    if (length_sq <= tolerance.length_sq_floor) return 0;    return clamp01(p.sub(segment.a).dot(ab) / length_sq);}pub fn pointSegment(p: Vec3, segment: Segment) Vec3 {    return segment.at(segmentParameter(p, segment));}pub fn pointAabb(p: Vec3, box: Aabb) Vec3 {    assert(!box.isEmpty());    return p.max(box.min).min(box.max);}pub fn pointObb(p: Vec3, box: Obb) Vec3 {    const local = box.toLocal(p);    return box.fromLocal(local.max(box.half_extents.negate()).min(box.half_extents));}pub fn pointPlane(p: Vec3, plane: Plane) Vec3 {    return p.sub(plane.normal.scale(plane.signedDistance(p)));}/// The point of `triangle` nearest `p`, by Voronoi region. A degenerate/// triangle answers with the nearest point of its three edges.pub fn pointTriangle(p: Vec3, triangle: Triangle) Vec3 {    if (triangle.isDegenerate()) {        return pointDegenerateTriangle(p, triangle);    }    const a = triangle.a;    const b = triangle.b;    const c = triangle.c;    const ab = b.sub(a);    const ac = c.sub(a);    const ap = p.sub(a);    const d1 = ab.dot(ap);    const d2 = ac.dot(ap);    if (d1 <= 0 and d2 <= 0) return a;    const bp = p.sub(b);    const d3 = ab.dot(bp);    const d4 = ac.dot(bp);    if (d3 >= 0 and d4 <= d3) return b;    const vc = d1 * d4 - d3 * d2;    if (vc <= 0 and d1 >= 0 and d3 <= 0) return a.add(ab.scale(d1 / (d1 - d3)));    const cp = p.sub(c);    const d5 = ab.dot(cp);    const d6 = ac.dot(cp);    if (d6 >= 0 and d5 <= d6) return c;    const vb = d5 * d2 - d1 * d6;    if (vb <= 0 and d2 >= 0 and d6 <= 0) return a.add(ac.scale(d2 / (d2 - d6)));    const va = d3 * d6 - d5 * d4;    if (va <= 0 and d4 - d3 >= 0 and d5 - d6 >= 0) {        return b.add(c.sub(b).scale((d4 - d3) / ((d4 - d3) + (d5 - d6))));    }    const inverse = 1 / (va + vb + vc);    return a.add(ab.scale(vb * inverse)).add(ac.scale(vc * inverse));}fn pointDegenerateTriangle(p: Vec3, triangle: Triangle) Vec3 {    var best = pointSegment(p, .{ .a = triangle.a, .b = triangle.b });    for ([_]Segment{        .{ .a = triangle.b, .b = triangle.c },        .{ .a = triangle.c, .b = triangle.a },    }) |edge| {        const candidate = pointSegment(p, edge);        if (candidate.distanceSq(p) < best.distanceSq(p)) best = candidate;    }    return best;}/// The nearest points of two segments. A segment shorter than the length/// floor acts as its first endpoint.pub fn segmentSegment(first: Segment, second: Segment) Pair {    const d1 = first.b.sub(first.a);    const d2 = second.b.sub(second.a);    const r = first.a.sub(second.a);    const a = d1.lengthSq();    const e = d2.lengthSq();    const f = d2.dot(r);    var s: f32 = 0;    var t: f32 = 0;    if (a <= tolerance.length_sq_floor) {        if (e > tolerance.length_sq_floor) t = clamp01(f / e);    } else {        const c = d1.dot(r);        if (e <= tolerance.length_sq_floor) {            s = clamp01(-c / a);        } else {            const b = d1.dot(d2);            const denominator = a * e - b * b;            if (denominator > 0) s = clamp01((b * f - c * e) / denominator);            t = (b * s + f) / e;            if (t < 0) {                t = 0;                s = clamp01(-c / a);            } else if (t > 1) {                t = 1;                s = clamp01((b - c) / a);            }        }    }    return .{ .first = first.at(s), .second = second.at(t), .first_t = s, .second_t = t };}/// The nearest points of a segment and a triangle, with the segment's point/// first. A segment that crosses the triangle answers with the crossing.pub fn segmentTriangle(segment: Segment, triangle: Triangle) Pair {    const ray = Ray{ .origin = segment.a, .direction = segment.b.sub(segment.a) };    if (intersect.rayTriangle(ray, triangle)) |hit| {        if (hit.t >= 0 and hit.t <= 1) {            const crossing = ray.at(hit.t);            return .{ .first = crossing, .second = crossing, .first_t = hit.t };        }    }    var best = Pair{ .first = segment.a, .second = pointTriangle(segment.a, triangle) };    const at_b = Pair{ .first = segment.b, .second = pointTriangle(segment.b, triangle), .first_t = 1 };    if (at_b.distanceSq() < best.distanceSq()) best = at_b;    for ([_]Segment{        .{ .a = triangle.a, .b = triangle.b },        .{ .a = triangle.b, .b = triangle.c },        .{ .a = triangle.c, .b = triangle.a },    }) |edge| {        const candidate = segmentSegment(segment, edge);        if (candidate.distanceSq() < best.distanceSq()) best = candidate;    }    return best;}const testing = std.testing;const unit_triangle = Triangle{ .a = .{}, .b = Vec3.init(1, 0, 0), .c = Vec3.init(0, 1, 0) };test "point to segment clamps to the endpoints" {    const segment = Segment{ .a = .{}, .b = Vec3.init(2, 0, 0) };    try testing.expectEqual(Vec3.init(1, 0, 0), pointSegment(Vec3.init(1, 5, 0), segment));    try testing.expectEqual(Vec3.init(0, 0, 0), pointSegment(Vec3.init(-3, 1, 0), segment));    try testing.expectEqual(Vec3.init(2, 0, 0), pointSegment(Vec3.init(9, 0, 0), segment));    const point = Segment{ .a = Vec3.init(1, 1, 1), .b = Vec3.init(1, 1, 1) };    try testing.expectEqual(Vec3.init(1, 1, 1), pointSegment(.{}, point));}test "point to triangle answers from each Voronoi region" {    try testing.expectEqual(Vec3.init(0.25, 0.25, 0), pointTriangle(Vec3.init(0.25, 0.25, 3), unit_triangle));    try testing.expectEqual(Vec3.init(0, 0, 0), pointTriangle(Vec3.init(-1, -1, 0), unit_triangle));    try testing.expectEqual(Vec3.init(1, 0, 0), pointTriangle(Vec3.init(2, -1, 0), unit_triangle));    try testing.expectEqual(Vec3.init(0, 1, 0), pointTriangle(Vec3.init(-1, 2, 0), unit_triangle));    try testing.expectEqual(Vec3.init(0.5, 0, 0), pointTriangle(Vec3.init(0.5, -2, 1), unit_triangle));    try testing.expectEqual(Vec3.init(0, 0.5, 0), pointTriangle(Vec3.init(-2, 0.5, 0), unit_triangle));    try testing.expectEqual(Vec3.init(0.5, 0.5, 0), pointTriangle(Vec3.init(1, 1, 0), unit_triangle));}test "a triangle with no area answers from its edges" {    const sliver = Triangle{ .a = .{}, .b = Vec3.init(1, 0, 0), .c = Vec3.init(2, 0, 0) };    try testing.expectEqual(Vec3.init(1.5, 0, 0), pointTriangle(Vec3.init(1.5, 1, 0), sliver));}test "point to box and plane clamp and project" {    const box = Aabb{ .min = .{}, .max = Vec3.splat(1) };    try testing.expectEqual(Vec3.init(1, 0.5, 0), pointAabb(Vec3.init(3, 0.5, -2), box));    const plane = Plane.fromPointNormal(Vec3.init(0, 0, 2), Vec3.init(0, 0, 1));    try testing.expectEqual(Vec3.init(4, 5, 2), pointPlane(Vec3.init(4, 5, -7), plane));    const turned = Obb{        .center = .{},        .axes = linear.Mat3.fromCols(Vec3.init(0, 1, 0), Vec3.init(-1, 0, 0), Vec3.init(0, 0, 1)),        .half_extents = Vec3.init(2, 1, 1),    };    try testing.expectEqual(Vec3.init(0, 2, 0), pointObb(Vec3.init(0, 5, 0), turned));    try testing.expectEqual(Vec3.init(-1, 0, 0), pointObb(Vec3.init(-5, 0, 0), turned));}test "segment pairs meet at crossings, endpoints and parallel spans" {    const x_axis = Segment{ .a = Vec3.init(-1, 0, 0), .b = Vec3.init(1, 0, 0) };    const above = Segment{ .a = Vec3.init(0, -1, 2), .b = Vec3.init(0, 1, 2) };    const crossing = segmentSegment(x_axis, above);    try testing.expectEqual(Vec3.init(0, 0, 0), crossing.first);    try testing.expectEqual(Vec3.init(0, 0, 2), crossing.second);    try testing.expectEqual(@as(f32, 4), crossing.distanceSq());    const beyond = Segment{ .a = Vec3.init(3, 0, 0), .b = Vec3.init(5, 0, 0) };    const gap = segmentSegment(x_axis, beyond);    try testing.expectEqual(Vec3.init(1, 0, 0), gap.first);    try testing.expectEqual(Vec3.init(3, 0, 0), gap.second);    const parallel = Segment{ .a = Vec3.init(0.5, 1, 0), .b = Vec3.init(3, 1, 0) };    try testing.expectEqual(@as(f32, 1), segmentSegment(x_axis, parallel).distanceSq());    const point = Segment{ .a = Vec3.init(0, 3, 0), .b = Vec3.init(0, 3, 0) };    try testing.expectEqual(Vec3.init(0, 0, 0), segmentSegment(point, x_axis).second);}test "segment to triangle finds crossings, endpoints and edges" {    const through = Segment{ .a = Vec3.init(0.25, 0.25, 1), .b = Vec3.init(0.25, 0.25, -1) };    const crossing = segmentTriangle(through, unit_triangle);    try testing.expectEqual(@as(f32, 0), crossing.distanceSq());    try testing.expectEqual(Vec3.init(0.25, 0.25, 0), crossing.second);    const hovering = Segment{ .a = Vec3.init(0.2, 0.2, 1), .b = Vec3.init(0.2, 0.2, 3) };    const endpoint = segmentTriangle(hovering, unit_triangle);    try testing.expectEqual(Vec3.init(0.2, 0.2, 1), endpoint.first);    try testing.expect(endpoint.second.distance(Vec3.init(0.2, 0.2, 0)) < 1e-6);    const beside = Segment{ .a = Vec3.init(0.5, -1, -1), .b = Vec3.init(0.5, -1, 1) };    const edge = segmentTriangle(beside, unit_triangle);    try testing.expectEqual(Vec3.init(0.5, 0, 0), edge.second);    try testing.expectEqual(@as(f32, 1), edge.distanceSq());}

Source: lib/geometry/src/root.zig:8

zig
pub const closest = @import("closest.zig");

Audit

Definitions11
Public names11
Members4
Version26.7.0
Revisiondaab053ee433