Skip to documentation
SLOP

tiny.geometry.Bvh

Reference tiny.geometry Bvh

Defined in tiny.geometry.

API (14)

Actions

Public operations.

Types and contracts

Public types and contracts.

Fields and members

Public fields and members.

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

Source

Source: lib/geometry/src/bvh.zig:259

zig
pub const Bvh = struct {    nodes: []const Node,    triangles: []const Triangle,    /// The mesh index of the triangle in each tree slot.    order: []const u32,    /// The depth of the deepest node, with the root at zero.    depth: u32,    /// The largest coordinate magnitude of the root box.    magnitude: f32,    /// The tree over no triangles, which answers every query with nothing.    pub const empty: Bvh = .{ .nodes = &empty_nodes, .triangles = &.{}, .order = &.{}, .depth = 0, .magnitude = 0 };    const Task = struct {        node: u32,        begin: u32,        end: u32,        depth: u32,    };    /// Builds the tree over `mesh` into `storage`, which must fit the mesh's    /// triangle count. The same mesh builds the same tree every time.    pub fn build(mesh: Mesh, storage: Storage) Bvh {        assert(mesh.isWellFormed());        const count = mesh.triangleCount();        assert(storage.fits(count));        const order = storage.order[0..count];        const centroids = storage.centroids[0..count];        for (order, centroids, 0..) |*slot, *centroid, index| {            slot.* = @intCast(index);            centroid.* = mesh.triangle(index).centroid();        }        const builder = Builder{ .mesh = mesh, .centroids = centroids };        var node_count: u32 = 1;        var depth: u32 = 0;        var tasks: [depth_limit]Task = undefined;        var task_count: usize = 1;        tasks[0] = .{ .node = 0, .begin = 0, .end = @intCast(count), .depth = 0 };        while (task_count > 0) {            task_count -= 1;            const task = tasks[task_count];            const range = order[task.begin..task.end];            var box = Aabb.empty;            var centroid_box = Aabb.empty;            for (range) |index| {                box = box.join(mesh.triangle(index).bounds());                centroid_box = centroid_box.joinPoint(centroids[index]);            }            depth = @max(depth, task.depth);            assert(task.depth < depth_limit);            if (range.len <= leaf_capacity) {                storage.nodes[task.node] = .{ .min = box.min, .first = task.begin, .max = box.max, .count = @intCast(range.len) };                continue;            }            assert(task.depth + levelsNeeded(range.len) < depth_limit);            const allow_surface_area = task.depth + levelsNeeded(range.len) + 1 < depth_limit;            const middle = task.begin + @as(u32, @intCast(builder.split(range, centroid_box, allow_surface_area)));            const left = node_count;            node_count += 2;            assert(node_count <= Storage.nodeCapacity(count));            storage.nodes[task.node] = .{ .min = box.min, .first = left, .max = box.max, .count = 0 };            assert(task_count + 2 <= depth_limit);            tasks[task_count] = .{ .node = left + 1, .begin = middle, .end = task.end, .depth = task.depth + 1 };            tasks[task_count + 1] = .{ .node = left, .begin = task.begin, .end = middle, .depth = task.depth + 1 };            task_count += 2;        }        for (storage.triangles[0..count], order) |*triangle, index| triangle.* = mesh.triangle(index);        const root = storage.nodes[0].box();        return .{            .nodes = storage.nodes[0..node_count],            .triangles = storage.triangles[0..count],            .order = order,            .depth = depth,            .magnitude = if (count == 0) 0 else root.magnitude(),        };    }    pub fn triangleCount(bvh: Bvh) usize {        return bvh.triangles.len;    }    /// The nearest triangle the ray meets with `t` in `[t_min, t_max]`, as    /// `Mesh.raycast` answers it.    pub fn raycast(bvh: Bvh, ray: Ray, t_min: f32, t_max: f32, stats: ?*Stats) Error!?RayHit {        assert(t_min <= t_max);        var walk = try Walk.begin(bvh, stats);        if (walk.done) return null;        const slack = tolerance.boxSlack(@max(bvh.magnitude, ray.origin.abs().maxComponent()));        var best: ?RayHit = null;        var best_t = t_max;        const root = intersect.raySlab(ray, bvh.nodes[0].box().inflate(slack), best_t) orelse return null;        try walk.stack.push(.{ .node = 0, .entry = root[0] });        while (walk.next(best_t)) |node| {            if (node.isLeaf()) {                for (node.first..node.first + node.count) |slot| {                    walk.tested();                    const hit = intersect.rayTriangle(ray, bvh.triangles[slot]) orelse continue;                    if (hit.t < t_min or hit.t > best_t) continue;                    const index = bvh.order[slot];                    if (best) |held| {                        if (hit.t == held.t and index > held.triangle) continue;                    }                    best = .{ .t = hit.t, .u = hit.u, .v = hit.v, .triangle = index };                    best_t = hit.t;                }                continue;            }            const near = intersect.raySlab(ray, bvh.nodes[node.first].box().inflate(slack), best_t);            const far = intersect.raySlab(ray, bvh.nodes[node.first + 1].box().inflate(slack), best_t);            try walk.pushOrdered(node.first, near, far);        }        return best;    }    /// Whether the ray meets any triangle with `t` in `[t_min, t_max]`.    pub fn raycastAny(bvh: Bvh, ray: Ray, t_min: f32, t_max: f32, stats: ?*Stats) Error!bool {        assert(t_min <= t_max);        var walk = try Walk.begin(bvh, stats);        if (walk.done) return false;        const slack = tolerance.boxSlack(@max(bvh.magnitude, ray.origin.abs().maxComponent()));        const root = intersect.raySlab(ray, bvh.nodes[0].box().inflate(slack), t_max) orelse return false;        try walk.stack.push(.{ .node = 0, .entry = root[0] });        while (walk.next(t_max)) |node| {            if (node.isLeaf()) {                for (node.first..node.first + node.count) |slot| {                    walk.tested();                    const hit = intersect.rayTriangle(ray, bvh.triangles[slot]) orelse continue;                    if (hit.t >= t_min and hit.t <= t_max) return true;                }                continue;            }            const near = intersect.raySlab(ray, bvh.nodes[node.first].box().inflate(slack), t_max);            const far = intersect.raySlab(ray, bvh.nodes[node.first + 1].box().inflate(slack), t_max);            try walk.pushOrdered(node.first, near, far);        }        return false;    }    /// The triangles the sphere touches, as `Mesh.overlapSphere` answers it.    pub fn overlapSphere(bvh: Bvh, sphere: Sphere, out: []u32, stats: ?*Stats) Error!Overlap {        assert(sphere.radius >= 0);        const reach = sphere.bounds();        const slack = tolerance.boxSlack(@max(bvh.magnitude, reach.magnitude()));        var collector = Collector{ .out = out };        var walk = try Walk.begin(bvh, stats);        if (walk.done) return collector.finish();        try walk.stack.push(.{ .node = 0, .entry = 0 });        while (walk.next(0)) |node| {            if (!intersect.sphereAabb(sphere, node.box().inflate(slack))) continue;            if (node.isLeaf()) {                for (node.first..node.first + node.count) |slot| {                    walk.tested();                    if (intersect.sphereTriangle(sphere, bvh.triangles[slot])) collector.add(bvh.order[slot]);                }                continue;            }            try walk.stack.push(.{ .node = node.first + 1, .entry = 0 });            try walk.stack.push(.{ .node = node.first, .entry = 0 });        }        return collector.finish();    }    /// The triangles the capsule touches, as `Mesh.overlapCapsule` answers    /// it.    pub fn overlapCapsule(bvh: Bvh, capsule: Capsule, out: []u32, stats: ?*Stats) Error!Overlap {        assert(capsule.radius >= 0);        const reach = capsule.bounds();        const slack = tolerance.boxSlack(@max(bvh.magnitude, reach.magnitude()));        var collector = Collector{ .out = out };        var walk = try Walk.begin(bvh, stats);        if (walk.done) return collector.finish();        try walk.stack.push(.{ .node = 0, .entry = 0 });        while (walk.next(0)) |node| {            if (!reach.overlaps(node.box().inflate(slack))) continue;            if (node.isLeaf()) {                for (node.first..node.first + node.count) |slot| {                    walk.tested();                    if (intersect.capsuleTriangle(capsule, bvh.triangles[slot])) collector.add(bvh.order[slot]);                }                continue;            }            try walk.stack.push(.{ .node = node.first + 1, .entry = 0 });            try walk.stack.push(.{ .node = node.first, .entry = 0 });        }        return collector.finish();    }    /// The first triangle the sphere touches as it moves by `displacement`,    /// as `Mesh.sweepSphere` answers it.    pub fn sweepSphere(bvh: Bvh, sphere: Sphere, displacement: Vec3, stats: ?*Stats) Error!?SweepHit {        assert(sphere.radius >= 0);        const moved = Sphere{ .center = sphere.center.add(displacement), .radius = sphere.radius };        const reach = sphere.bounds().join(moved.bounds());        const margin = Swept{            .low = Vec3.splat(-sphere.radius),            .high = Vec3.splat(sphere.radius),            .slack = tolerance.boxSlack(@max(bvh.magnitude, reach.magnitude())),        };        const path = Ray{ .origin = sphere.center, .direction = displacement };        return bvh.sweepWith(path, margin, stats, sphere, sweepSphereTriangle);    }    /// The first triangle the capsule touches as it moves by `displacement`,    /// as `Mesh.sweepCapsule` answers it.    pub fn sweepCapsule(bvh: Bvh, capsule: Capsule, displacement: Vec3, stats: ?*Stats) Error!?SweepHit {        assert(capsule.radius >= 0);        const moved = Capsule{ .a = capsule.a.add(displacement), .b = capsule.b.add(displacement), .radius = capsule.radius };        const reach = capsule.bounds().join(moved.bounds());        const axis = capsule.b.sub(capsule.a);        const margin = Swept{            .low = axis.min(.{}).sub(Vec3.splat(capsule.radius)),            .high = axis.max(.{}).add(Vec3.splat(capsule.radius)),            .slack = tolerance.boxSlack(@max(bvh.magnitude, reach.magnitude())),        };        const path = Ray{ .origin = capsule.a, .direction = displacement };        return bvh.sweepWith(path, margin, stats, capsule, sweepCapsuleTriangle);    }    fn sweepSphereTriangle(sphere: Sphere, displacement: Vec3, triangle: Triangle) ?sweep.Hit {        return sweep.sphereTriangle(sphere, displacement, triangle);    }    fn sweepCapsuleTriangle(capsule: Capsule, displacement: Vec3, triangle: Triangle) ?sweep.Hit {        return sweep.capsuleTriangle(capsule, displacement, triangle);    }    /// Walks the boxes a moving reference point can reach while the shape it    /// carries could touch them. A shape whose points lie at offsets in    /// `[low, high]` from the reference point touches a box only while that    /// point is inside the box grown by `-high` below and `-low` above.    fn sweepWith(        bvh: Bvh,        path: Ray,        margin: Swept,        stats: ?*Stats,        shape: anytype,        comptime test_triangle: fn (@TypeOf(shape), Vec3, Triangle) ?sweep.Hit,    ) Error!?SweepHit {        var walk = try Walk.begin(bvh, stats);        if (walk.done) return null;        var best: ?SweepHit = null;        var best_t: f32 = 1;        const root = intersect.raySlab(path, margin.grow(bvh.nodes[0]), best_t) orelse return null;        try walk.stack.push(.{ .node = 0, .entry = root[0] });        while (walk.next(best_t)) |node| {            if (node.isLeaf()) {                for (node.first..node.first + node.count) |slot| {                    walk.tested();                    const hit = test_triangle(shape, path.direction, bvh.triangles[slot]) orelse continue;                    if (hit.t > best_t) continue;                    const index = bvh.order[slot];                    if (best) |held| {                        if (hit.t == held.t and index > held.triangle) continue;                    }                    best = .{ .t = hit.t, .point = hit.point, .normal = hit.normal, .triangle = index };                    best_t = hit.t;                }                continue;            }            const near = intersect.raySlab(path, margin.grow(bvh.nodes[node.first]), best_t);            const far = intersect.raySlab(path, margin.grow(bvh.nodes[node.first + 1]), best_t);            try walk.pushOrdered(node.first, near, far);        }        return best;    }};

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

zig
pub const Bvh = bvh.Bvh;
Called byCallstest sourcelib.geometry.src.bvhtest: a built tree bounds its node co...test sourcelib.geometry.src.bvhtest: a fixed storage holds a tree si...test sourcelib.geometry.src.bvhtest: a tree too deep for the query s...test sourcelib.geometry.src.bvhtest: an empty mesh builds a tree tha...test sourcelib.geometry.src.bvhtest: an overlap with a small buffer ...+3 moreStoragenodeCapacityprivate sourcelib.geometry.src.bvhlevelsNeededBvhbuild
Static calls · unresolved targets: 2 · external targets: 6.
Called byCallsNo direct callersprivate sourcelib.geometry.src.bvh.WalkbeginintersectcapsuleTriangletoleranceboxSlackBvhoverlapCapsule
Static calls · unresolved targets: 1 · external targets: 9.
Called byCallsNo direct callersprivate sourcelib.geometry.src.bvh.WalkbeginintersectsphereAabbintersectsphereTriangletoleranceboxSlackBvhoverlapSphere
Static calls · unresolved targets: 1 · external targets: 8.
Called byCallsNo direct callersprivate sourcelib.geometry.src.bvh.WalkbeginintersectraySlabintersectrayTriangletoleranceboxSlackBvhraycast
Static calls · unresolved targets: 0 · external targets: 6.
Called byCallsNo direct callersprivate sourcelib.geometry.src.bvh.WalkbeginintersectraySlabintersectrayTriangletoleranceboxSlackBvhraycastAny
Static calls · unresolved targets: 0 · external targets: 6.
Called byCallsNo direct callerstoleranceboxSlackBvhsweepCapsule
Static calls · unresolved targets: 3 · external targets: 7.
Called byCallsNo direct callerstoleranceboxSlackBvhsweepSphere
Static calls · unresolved targets: 2 · external targets: 4.

Complete caller list for Bvh.build

8 direct callers.

Audit

Definitions10
Public names10
Members5
Version26.7.0
Revisiondaab053ee433