tiny.geometry.closest
Defined in tiny.geometry.
Closest points between shapes, after Ericson, Real-Time Collision Detection, chapter 5.
API (10)
Actions
Public operations.
Pair.distanceSqpointAabbpointObbpointPlanepointSegmentpointTriangle: The point oftrianglenearestp, by Voronoi region.segmentParameter: The parameter of the point onsegmentnearestp.segmentSegment: The nearest points of two segments.segmentTriangle: The nearest points of a segment and a triangle, with the segment's point first.
Types and contracts
Public types and contracts.
Pair: A point on each of two shapes, with each point's parameter on its segment where the shape is one.
Source
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
| Definitions | 11 |
|---|---|
| Public names | 11 |
| Members | 4 |
| Version | 26.7.0 |
| Revision | daab053ee433 |