437 lines
9.9 KiB
Odin
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package bvh
import "../collision"
import "base:runtime"
import "core:container/queue"
import "core:log"
import "core:math"
import lg "core:math/linalg"
import "core:mem"
import "game:debug"
import rl "libs:raylib"
import "libs:tracy"
_ :: log
_ :: rl
_ :: lg
_ :: math
Vec3 :: [3]f32
AABB :: struct {
min, max: Vec3,
}
// Helper struct to avoid passing verts/indices separately
Mesh :: struct {
vertices: []Vec3,
indices: []u16,
}
BVH :: struct {
nodes: []Node,
// Triangle IDs. first_index = indices[primitive * 3]
primitives: []u16,
nodes_used: i32,
}
destroy_bvh :: proc(bvh: ^BVH) {
delete(bvh.nodes)
delete(bvh.primitives)
}
// Helper struct to store mesh data together with its bvh for convenience
// You don't have to use it
Mesh_BVH :: struct {
bvh: BVH,
mesh: Mesh,
}
Node :: struct {
aabb: AABB,
// Index of the left child, right child is left_child + 1
child_or_prim_start: i32,
prim_len: i32,
}
// uvw
Bary :: [3]f32
is_leaf_node :: #force_inline proc(node: Node) -> bool {
return node.prim_len > 0
}
#assert(size_of(Node) == 32)
build_bvh_from_mesh :: proc(mesh: Mesh, allocator := context.allocator) -> (mesh_bvh: Mesh_BVH) {
tracy.Zone()
vertices, indices := mesh.vertices, mesh.indices
assert(len(indices) % 3 == 0)
bvh := &mesh_bvh.bvh
mesh_bvh.mesh = mesh
num_triangles := len(indices) / 3
// Caller owned, allocator might be temp_allocator so do this before checkpoint below, otherwise we the result accidentally
bvh.nodes, _ = mem.make_aligned([]Node, num_triangles * 2 - 1, size_of(Node), allocator)
bvh.primitives = make([]u16, num_triangles, allocator)
// Clean up after ourselves
temp := runtime.default_temp_allocator_temp_begin()
defer runtime.default_temp_allocator_temp_end(temp)
// Temp stuff
centroids := make([]Vec3, num_triangles, context.temp_allocator)
aabbs := make([]AABB, num_triangles, context.temp_allocator)
// Calculate centroids and aabbs
{
tracy.ZoneN("calculate_centroids_and_aabbs")
for i in 0 ..< num_triangles {
i1, i2, i3 := indices[i * 3], indices[i * 3 + 1], indices[i * 3 + 2]
v1, v2, v3 := vertices[i1], vertices[i2], vertices[i3]
centroids[i] = (v1 + v2 + v3) / 3.0
aabbs[i].min = lg.min_triple(v1, v2, v3)
aabbs[i].max = lg.max_triple(v1, v2, v3)
size := aabbs[i].max - aabbs[i].min
assert(size.x >= 0)
assert(size.y >= 0)
assert(size.z >= 0)
bvh.primitives[i] = u16(i)
}
}
bvh.nodes_used = 1 // root
root := &bvh.nodes[0]
root.child_or_prim_start = 0
root.prim_len = i32(num_triangles)
update_node_bounds(bvh, 0, aabbs)
subdivide(bvh, 0, centroids, aabbs)
return
}
/// Useful for a top level accel structure
build_bvh_from_aabbs :: proc(
aabbs: []AABB,
primitives: []u16,
allocator := context.allocator,
) -> (
bvh: BVH,
) {
tracy.Zone()
bvh.nodes, _ = mem.make_aligned([]Node, len(aabbs) * 2 - 1, size_of(Node), allocator)
bvh.primitives = make([]u16, len(aabbs), allocator)
temp := runtime.default_temp_allocator_temp_begin()
defer runtime.default_temp_allocator_temp_end(temp)
// Temp stuff
centroids := make([]Vec3, len(aabbs), context.temp_allocator)
// Calculate centroids
for i in 0 ..< len(aabbs) {
centroids[i] = (aabbs[i].max + aabbs[i].min) * 0.5
bvh.primitives[i] = primitives[i]
}
bvh.nodes_used = 1
root := &bvh.nodes[0]
root.prim_len = i32(len(primitives))
update_node_bounds(&bvh, 0, aabbs)
subdivide(&bvh, 0, centroids, aabbs)
return
}
update_node_bounds :: proc(bvh: ^BVH, node_idx: i32, prim_aabbs: []AABB) {
tracy.Zone()
node := &bvh.nodes[node_idx]
node.aabb.min = max(f32)
node.aabb.max = min(f32)
for i in node.child_or_prim_start ..< node.child_or_prim_start + node.prim_len {
prim_aabb := prim_aabbs[bvh.primitives[i]]
node.aabb.min = lg.min(node.aabb.min, prim_aabb.min)
node.aabb.max = lg.max(node.aabb.max, prim_aabb.max)
}
}
subdivide :: proc(bvh: ^BVH, node_idx: i32, centroids: []Vec3, aabbs: []AABB) {
tracy.Zone()
node := &bvh.nodes[node_idx]
if node.prim_len <= 2 {
return
}
size := node.aabb.max - node.aabb.min
// Split along longest axis
largest_side := size.x
split_axis := 0
if size.y > largest_side {
split_axis = 1
largest_side = size.y
}
if size.z > largest_side {
split_axis = 2
}
split_pos := node.aabb.min[split_axis] + size[split_axis] * 0.5
// Partition
i := node.child_or_prim_start
j := i + node.prim_len - 1
for i <= j {
prim_i := bvh.primitives[i]
prim_j := bvh.primitives[j]
if centroids[prim_i][split_axis] < split_pos {
i += 1
} else {
bvh.primitives[i] = prim_j
bvh.primitives[j] = prim_i
j -= 1
}
}
left_count := i - node.child_or_prim_start
if left_count == 0 || left_count == node.prim_len {
return
}
left_child := bvh.nodes_used
right_child := bvh.nodes_used + 1
bvh.nodes_used += 2
prim_start := node.child_or_prim_start
node.child_or_prim_start = left_child
bvh.nodes[left_child] = {}
bvh.nodes[right_child] = {}
bvh.nodes[left_child].child_or_prim_start = prim_start
bvh.nodes[left_child].prim_len = left_count
bvh.nodes[right_child].child_or_prim_start = i
bvh.nodes[right_child].prim_len = node.prim_len - left_count
node.prim_len = 0
update_node_bounds(bvh, left_child, aabbs)
update_node_bounds(bvh, right_child, aabbs)
subdivide(bvh, left_child, centroids, aabbs)
subdivide(bvh, right_child, centroids, aabbs)
}
Ray :: struct {
origin, dir: Vec3,
dir_inv: Vec3,
}
Collision :: struct {
hit: bool,
t: f32,
// which primitive we hit
prim: u16,
// Barycentric coords of the hit triangle
bary: Bary,
// jStats
aabb_tests: int,
triangle_tests: int,
}
Iterator_Intersect_Type :: enum {
AABB,
Ray,
}
Iterator_Intersect_Leaf :: struct($T: Iterator_Intersect_Type) {
bvh: ^BVH,
nodes_to_process: queue.Queue(i32),
bounds: AABB,
ray: Ray,
min_t: f32,
}
iterator_intersect_leaf_aabb :: proc(
bvh: ^BVH,
bounds: AABB,
) -> (
it: Iterator_Intersect_Leaf(.AABB),
) {
it.bvh = bvh
it.bounds = bounds
if len(it.bvh.nodes) > 0 {
queue.init(&it.nodes_to_process, queue.DEFAULT_CAPACITY, context.temp_allocator)
queue.push_back(&it.nodes_to_process, 0)
}
return it
}
iterator_intersect_leaf_ray :: proc(
bvh: ^BVH,
ray: Ray,
distance := max(f32),
) -> (
it: Iterator_Intersect_Leaf(.Ray),
) {
it.bvh = bvh
it.ray = ray
it.min_t = distance
if len(it.bvh.nodes) > 0 {
queue.init(&it.nodes_to_process, queue.DEFAULT_CAPACITY, context.temp_allocator)
queue.push_back(&it.nodes_to_process, 0)
}
return it
}
test_aabb_vs_aabb :: #force_inline proc(a, b: AABB) -> bool {
// Exit with no intersection if separated along an axis
if a.max[0] < b.min[0] || a.min[0] > b.max[0] do return false
if a.max[1] < b.min[1] || a.min[1] > b.max[1] do return false
if a.max[2] < b.min[2] || a.min[2] > b.max[2] do return false
// Overlapping on all axes means AABBs are intersecting
return true
}
iterator_intersect_leaf_next :: proc(
it: ^$T/Iterator_Intersect_Leaf($Intersect_Type),
) -> (
node: Node,
node_idx: int,
ok: bool,
) {
for queue.len(it.nodes_to_process) > 0 {
cur_node_idx := queue.pop_front(&it.nodes_to_process)
assert(cur_node_idx < it.bvh.nodes_used)
cur_node := &it.bvh.nodes[cur_node_idx]
passed :=
test_aabb_vs_aabb(cur_node.aabb, it.bounds) when Intersect_Type ==
.AABB else internal_ray_aabb_test(it.ray, cur_node.aabb, it.min_t)
if passed {
if is_leaf_node(cur_node^) {
node = cur_node^
node_idx = int(cur_node_idx)
ok = true
break
} else {
left_node := cur_node.child_or_prim_start
queue.push_back_elems(&it.nodes_to_process, left_node, left_node + 1)
}
}
}
return
}
traverse_bvh_ray_mesh :: proc(bvh: ^BVH, mesh: Mesh, ray: Ray, out_collision: ^Collision) -> bool {
tracy.Zone()
ray := ray
ray.dir_inv.x = 1.0 / ray.dir.x
ray.dir_inv.y = 1.0 / ray.dir.y
ray.dir_inv.z = 1.0 / ray.dir.z
if !out_collision.hit {
out_collision.t = max(f32)
}
prev_t := out_collision.t
internal_traverse_bvh_ray_triangles(bvh, mesh, ray, out_collision)
return out_collision.hit && out_collision.t < prev_t
}
internal_traverse_bvh_ray_triangles :: proc(
bvh: ^BVH,
mesh: Mesh,
ray: Ray,
out_collision: ^Collision,
) {
temp := runtime.default_temp_allocator_temp_begin()
defer runtime.default_temp_allocator_temp_end(temp)
nodes_to_process: queue.Queue(i32)
queue.init(&nodes_to_process, queue.DEFAULT_CAPACITY, context.temp_allocator)
queue.push_back(&nodes_to_process, 0)
for queue.len(nodes_to_process) > 0 {
node_idx := queue.pop_front(&nodes_to_process)
assert(node_idx < bvh.nodes_used)
node := &bvh.nodes[node_idx]
out_collision.aabb_tests += 1
if !internal_ray_aabb_test(ray, node.aabb, out_collision.t) {
continue
}
rl.DrawBoundingBox(
{min = node.aabb.min, max = node.aabb.max},
debug.int_to_color(node_idx),
)
if is_leaf_node(node^) {
for i in node.child_or_prim_start ..< node.child_or_prim_start + node.prim_len {
internal_ray_tri_test(ray, mesh, bvh.primitives[i], out_collision)
}
} else {
left_node := node.child_or_prim_start
queue.push_back_elems(&nodes_to_process, left_node, left_node + 1)
}
}
}
internal_ray_aabb_test :: proc(ray: Ray, box: AABB, min_t: f32) -> bool {
ts, ok := collision.intersect_ray_aabb(ray.origin, ray.dir, collision.Aabb{box.min, box.max})
return ok && ts[0] < min_t
}
// MöllerTrumbore intersection algorithm
// https://jacco.ompf2.com/2022/04/13/how-to-build-a-bvh-part-1-basics/
internal_ray_tri_test :: proc(ray: Ray, mesh: Mesh, tri: u16, col: ^Collision) {
i1, i2, i3 := mesh.indices[tri * 3], mesh.indices[tri * 3 + 1], mesh.indices[tri * 3 + 2]
v1, v2, v3 := mesh.vertices[i1], mesh.vertices[i2], mesh.vertices[i3]
col.triangle_tests += 1
// rl.DrawTriangle3D(v1, v2, v3, debug.int_to_color(i32(tri) + 1))
t, _, barycentric, ok := collision.intersect_ray_triangle(
{ray.origin, ray.origin + ray.dir},
{v1, v2, v3},
)
if ok && t < col.t {
col.hit = true
col.t = t
col.prim = tri
col.bary = barycentric
}
}