use nalgebra_glm::{IVec3, Vec3};
use super::brick_map::{BRICK_CORNER_COUNT, BRICK_SIZE, BrickData, EMPTY_BRICK};
use super::bvh::SdfEditBvh;
use super::clipmap::SdfClipmap;
use super::primitives::{Aabb, SdfEdit};
use super::resources::TerrainConfig;
pub fn evaluate_sdf(edits: &[SdfEdit], point: Vec3) -> (f32, u32) {
evaluate_sdf_dithered(edits, point, 0.5)
}
pub fn evaluate_sdf_dithered(edits: &[SdfEdit], point: Vec3, dither_threshold: f32) -> (f32, u32) {
if edits.is_empty() {
return (f32::MAX, 0);
}
let mut distance = f32::MAX;
let mut material = 0u32;
for edit in edits {
let edit_distance = edit.evaluate(point);
(distance, material) = edit.operation().apply_with_material_dithered(
distance,
material,
edit_distance,
edit.material_id(),
dither_threshold,
);
}
(distance, material)
}
pub fn evaluate_sdf_with_terrain_dithered(
edits: &[SdfEdit],
terrain: &TerrainConfig,
point: Vec3,
dither_threshold: f32,
) -> (f32, u32) {
let (mut distance, mut material) = if terrain.enabled {
(terrain.evaluate(point), terrain.material_id)
} else {
(f32::MAX, 0)
};
for edit in edits {
let edit_distance = edit.evaluate(point);
(distance, material) = edit.operation().apply_with_material_dithered(
distance,
material,
edit_distance,
edit.material_id(),
dither_threshold,
);
}
(distance, material)
}
struct PendingBrick {
coord: IVec3,
level: usize,
priority: f32,
voxel_size: f32,
brick_world_origin: Vec3,
old_pointer: i32,
}
pub struct GpuBrickDispatch {
pub brick_coord: IVec3,
pub level_index: usize,
pub atlas_slot: u32,
pub old_pointer: i32,
pub world_origin: Vec3,
pub voxel_size: f32,
}
pub fn prepare_gpu_brick_dispatches(
clipmap: &mut SdfClipmap,
bvh: &SdfEditBvh,
terrain: &TerrainConfig,
max_updates_per_frame: usize,
camera_position: Vec3,
) -> Vec<GpuBrickDispatch> {
let mut pending = Vec::new();
for (level_index, level) in clipmap.levels.iter_mut().enumerate() {
let dirty = level.take_dirty_bricks();
let voxel_size = level.voxel_size;
for coord in dirty {
let brick_world_origin = level.brick_to_world_origin(coord);
let distance = nalgebra_glm::length(&(brick_world_origin - camera_position));
let old_pointer = level.get_pointer(coord);
pending.push(PendingBrick {
coord,
level: level_index,
priority: distance,
voxel_size,
brick_world_origin,
old_pointer,
});
}
}
pending.sort_by(|a, b| a.priority.partial_cmp(&b.priority).unwrap());
let to_process: Vec<_> = pending
.drain(..max_updates_per_frame.min(pending.len()))
.collect();
for remaining in pending {
if clipmap.levels[remaining.level].is_in_bounds(remaining.coord) {
clipmap.levels[remaining.level].mark_dirty(remaining.coord);
}
}
let mut dispatches = Vec::with_capacity(to_process.len());
let mut scratch = Vec::new();
for brick in to_process {
if !clipmap.levels[brick.level].is_in_bounds(brick.coord) {
if brick.old_pointer >= 0 {
clipmap.allocator.deallocate(brick.old_pointer as u32);
}
continue;
}
let brick_half_size = brick.voxel_size * BRICK_CORNER_COUNT as f32 * 0.5;
let brick_center =
brick.brick_world_origin + Vec3::new(brick_half_size, brick_half_size, brick_half_size);
let brick_bounds = Aabb::from_center_half_extents(
brick_center,
Vec3::new(brick_half_size, brick_half_size, brick_half_size),
);
scratch.clear();
bvh.query_bounds(&brick_bounds.expand(brick.voxel_size * 4.0), &mut scratch);
let has_terrain_surface = terrain.enabled && {
let brick_min_y = brick.brick_world_origin.y;
let brick_max_y = brick_min_y + brick.voxel_size * BRICK_CORNER_COUNT as f32;
let terrain_min_y = terrain.base_height - terrain.max_surface_extent();
let terrain_max_y = terrain.base_height + terrain.max_surface_extent();
terrain_max_y >= brick_min_y && terrain_min_y <= brick_max_y
};
if scratch.is_empty() && !has_terrain_surface {
if brick.old_pointer >= 0 {
clipmap.allocator.deallocate(brick.old_pointer as u32);
clipmap.levels[brick.level].set_pointer(brick.coord, EMPTY_BRICK);
}
continue;
}
let atlas_slot = if brick.old_pointer >= 0 {
brick.old_pointer as u32
} else {
match clipmap.allocator.allocate() {
Some(slot) => slot,
None => {
clipmap.levels[brick.level].mark_dirty(brick.coord);
continue;
}
}
};
let new_pointer = atlas_slot as i32;
clipmap.levels[brick.level].set_pointer(brick.coord, new_pointer);
dispatches.push(GpuBrickDispatch {
brick_coord: brick.coord,
level_index: brick.level,
atlas_slot,
old_pointer: brick.old_pointer,
world_origin: brick.brick_world_origin,
voxel_size: brick.voxel_size,
});
}
dispatches
}
pub fn trilinear_interpolate(brick: &BrickData, local_pos: Vec3) -> f32 {
let x = local_pos.x.clamp(0.0, BRICK_SIZE as f32);
let y = local_pos.y.clamp(0.0, BRICK_SIZE as f32);
let z = local_pos.z.clamp(0.0, BRICK_SIZE as f32);
let x0 = x.floor() as usize;
let y0 = y.floor() as usize;
let z0 = z.floor() as usize;
let x1 = (x0 + 1).min(BRICK_CORNER_COUNT - 1);
let y1 = (y0 + 1).min(BRICK_CORNER_COUNT - 1);
let z1 = (z0 + 1).min(BRICK_CORNER_COUNT - 1);
let fx = x - x0 as f32;
let fy = y - y0 as f32;
let fz = z - z0 as f32;
let d000 = brick.get_corner_distance(x0, y0, z0);
let d100 = brick.get_corner_distance(x1, y0, z0);
let d010 = brick.get_corner_distance(x0, y1, z0);
let d110 = brick.get_corner_distance(x1, y1, z0);
let d001 = brick.get_corner_distance(x0, y0, z1);
let d101 = brick.get_corner_distance(x1, y0, z1);
let d011 = brick.get_corner_distance(x0, y1, z1);
let d111 = brick.get_corner_distance(x1, y1, z1);
let c00 = d000 * (1.0 - fx) + d100 * fx;
let c10 = d010 * (1.0 - fx) + d110 * fx;
let c01 = d001 * (1.0 - fx) + d101 * fx;
let c11 = d011 * (1.0 - fx) + d111 * fx;
let c0 = c00 * (1.0 - fy) + c10 * fy;
let c1 = c01 * (1.0 - fy) + c11 * fy;
c0 * (1.0 - fz) + c1 * fz
}
pub fn trilinear_gradient(brick: &BrickData, local_pos: Vec3, voxel_size: f32) -> Vec3 {
let x = local_pos.x.clamp(0.0, BRICK_SIZE as f32);
let y = local_pos.y.clamp(0.0, BRICK_SIZE as f32);
let z = local_pos.z.clamp(0.0, BRICK_SIZE as f32);
let x0 = x.floor() as usize;
let y0 = y.floor() as usize;
let z0 = z.floor() as usize;
let x1 = (x0 + 1).min(BRICK_CORNER_COUNT - 1);
let y1 = (y0 + 1).min(BRICK_CORNER_COUNT - 1);
let z1 = (z0 + 1).min(BRICK_CORNER_COUNT - 1);
let fx = x - x0 as f32;
let fy = y - y0 as f32;
let fz = z - z0 as f32;
let d000 = brick.get_corner_distance(x0, y0, z0);
let d100 = brick.get_corner_distance(x1, y0, z0);
let d010 = brick.get_corner_distance(x0, y1, z0);
let d110 = brick.get_corner_distance(x1, y1, z0);
let d001 = brick.get_corner_distance(x0, y0, z1);
let d101 = brick.get_corner_distance(x1, y0, z1);
let d011 = brick.get_corner_distance(x0, y1, z1);
let d111 = brick.get_corner_distance(x1, y1, z1);
let y0_x = lerp(d100 - d000, d110 - d010, fy);
let y1_x = lerp(d101 - d001, d111 - d011, fy);
let grad_x = lerp(y0_x, y1_x, fz);
let x0_y = lerp(d010 - d000, d110 - d100, fx);
let x1_y = lerp(d011 - d001, d111 - d101, fx);
let grad_y = lerp(x0_y, x1_y, fz);
let x0_z = lerp(d001 - d000, d101 - d100, fx);
let x1_z = lerp(d011 - d010, d111 - d110, fx);
let grad_z = lerp(x0_z, x1_z, fy);
let gradient = Vec3::new(grad_x, grad_y, grad_z) / voxel_size;
let len = nalgebra_glm::length(&gradient);
if len > 0.0001 {
gradient / len
} else {
Vec3::new(0.0, 1.0, 0.0)
}
}
fn lerp(a: f32, b: f32, t: f32) -> f32 {
a + (b - a) * t
}