2025-01-06 03:40:40 +04:00

383 lines
9.1 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 rl "vendor:raylib"
_ :: log
_ :: rl
_ :: lg
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) {
vertices, indices := mesh.vertices, mesh.indices
assert(len(indices) % 3 == 0)
bvh := &mesh_bvh.bvh
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
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) * 0.33333333333
aabbs[i].min = Vec3{min(v1.x, v2.x, v3.x), min(v1.y, v2.y, v3.y), min(v1.z, v2.z, v3.z)}
aabbs[i].max = Vec3{max(v1.x, v2.x, v3.x), max(v1.y, v2.y, v3.y), max(v1.z, v2.z, v3.z)}
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, allocator := context.allocator) -> (bvh: BVH) {
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] = u16(i)
}
bvh.nodes_used = 1
root := &bvh.nodes[0]
root.prim_len = i32(len(aabbs))
update_node_bounds(&bvh, 0, aabbs)
subdivide(&bvh, 0, centroids, aabbs)
return
}
update_node_bounds :: proc(bvh: ^BVH, node_idx: i32, prim_aabbs: []AABB) {
node := &bvh.nodes[node_idx]
node.aabb.min = math.F32_MAX
node.aabb.max = math.F32_MIN
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.x = min(node.aabb.min.x, prim_aabb.min.x)
node.aabb.min.y = min(node.aabb.min.y, prim_aabb.min.y)
node.aabb.min.z = min(node.aabb.min.z, prim_aabb.min.z)
node.aabb.max.x = max(node.aabb.max.x, prim_aabb.max.x)
node.aabb.max.y = max(node.aabb.max.y, prim_aabb.max.y)
node.aabb.max.z = max(node.aabb.max.z, prim_aabb.max.z)
}
size := node.aabb.max - node.aabb.min
assert(size.x >= 0)
assert(size.y >= 0)
assert(size.z >= 0)
}
subdivide :: proc(bvh: ^BVH, node_idx: i32, centroids: []Vec3, aabbs: []AABB) {
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,
}
traverse_bvh_ray_mesh :: proc(bvh: ^BVH, mesh: Mesh, ray: Ray, out_collision: ^Collision) -> bool {
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 = math.F32_MAX
}
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]
if !internal_ray_aabb_test(ray, node.aabb, out_collision.t) {
return
}
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)
}
}
}
// https://tavianator.com/2022/ray_box_boundary.html
internal_ray_aabb_test :: proc(ray: Ray, box: AABB, min_t: f32) -> bool {
_, ok := collision.intersect_ray_aabb(ray.origin, ray.dir, collision.Aabb{box.min, box.max})
return ok
// t1 := (box.min[0] - ray.origin[0]) * ray.dir_inv[0]
// t2 := (box.max[0] - ray.origin[0]) * ray.dir_inv[0]
// tmin := min(t1, t2)
// tmax := max(t1, t2)
// for i in 1 ..< 3 {
// t1 = (box.min[i] - ray.origin[i]) * ray.dir_inv[i]
// t2 = (box.max[i] - ray.origin[i]) * ray.dir_inv[i]
// tmin = max(tmin, min(t1, t2))
// tmax = min(tmax, max(t1, t2))
// }
// return tmax > max(tmin, 0.0)
}
// 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]
t, _, barycentric, ok := collision.intersect_segment_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
}
// rl.DrawTriangle3D(v1, v2, v3, debug_int_to_color(i32(tri)))
// rl_col := rl.GetRayCollisionTriangle(rl.Ray{ray.origin, ray.dir}, v1, v2, v3)
// if rl_col.hit && rl_col.distance < col.t {
// col.hit = true
// col.t = lg.distance(ray.origin, rl_col.point)
// }
return
//e1, e2 := v2 - v1, v3 - v1
//h := lg.cross(ray.dir, e2)
//a := lg.dot(e1, h)
//// ray parallel to triangle
//if a > -0.0001 || a < 0.0001 {
// return
//}
//f: f32 = 1.0 / a
//s := ray.origin - v1
//u := f * lg.dot(s, h)
//if u < 0 || u > 1 {
// return
//}
//q := lg.cross(s, e1)
//v := f * lg.dot(ray.dir, q)
//if v < 0 || u + v > 1 {
// return
//}
//t := f * lg.dot(e2, q)
//if t > 0.0001 && t < col.t {
// col.hit = true
// col.t = t
// col.prim = tri
// col.bary = Vec3{u, v, 0} // TODO: calc W
//}
}