use std::collections::HashSet;
use nalgebra_glm::Vec3;
use super::bvh::SdfEditBvh;
use super::clipmap::{BASE_VOXEL_SIZE, DEFAULT_LEVEL_COUNT, SdfClipmap};
use super::evaluation::{GpuBrickDispatch, prepare_gpu_brick_dispatches};
use super::primitives::{Aabb, SdfEdit, SdfPrimitive};
pub fn terrain_hash(ix: i32, iz: i32, seed: u32) -> f32 {
let mut n =
(ix.wrapping_mul(1619)) ^ (iz.wrapping_mul(6971)) ^ ((seed as i32).wrapping_mul(1013));
n = n.wrapping_mul(n).wrapping_mul(n);
n = n ^ (n >> 13);
(n & 0x7fffffff) as f32 / 2147483647.0
}
pub fn noised_2d(px: f32, pz: f32, seed: u32) -> (f32, f32, f32) {
let cell_x = px.floor() as i32;
let cell_z = pz.floor() as i32;
let wx = px - cell_x as f32;
let wz = pz - cell_z as f32;
let ux = wx * wx * wx * (wx * (wx * 6.0 - 15.0) + 10.0);
let uz = wz * wz * wz * (wz * (wz * 6.0 - 15.0) + 10.0);
let dux = 30.0 * wx * wx * (wx * (wx - 2.0) + 1.0);
let duz = 30.0 * wz * wz * (wz * (wz - 2.0) + 1.0);
let h00 = terrain_hash(cell_x, cell_z, seed);
let h10 = terrain_hash(cell_x + 1, cell_z, seed);
let h01 = terrain_hash(cell_x, cell_z + 1, seed);
let h11 = terrain_hash(cell_x + 1, cell_z + 1, seed);
let k0 = h00;
let k1 = h10 - h00;
let k2 = h01 - h00;
let k3 = h00 - h10 - h01 + h11;
let value = -1.0 + 2.0 * (k0 + k1 * ux + k2 * uz + k3 * ux * uz);
let dvdx = 2.0 * dux * (k1 + k3 * uz);
let dvdz = 2.0 * duz * (k2 + k3 * ux);
(value, dvdx, dvdz)
}
pub fn terrain_fbm(config: &TerrainConfig, point: Vec3) -> f32 {
let mut px = point.x * config.frequency;
let mut pz = point.z * config.frequency;
let mut total = 0.0_f32;
let mut amplitude = config.amplitude;
let mut dx_sum = 0.0_f32;
let mut dz_sum = 0.0_f32;
for _octave in 0..config.octaves {
let (value, dvdx, dvdz) = noised_2d(px, pz, config.seed);
dx_sum += dvdx * 0.5;
dz_sum += dvdz * 0.5;
let dampening = 1.0 / (1.0 + dx_sum * dx_sum + dz_sum * dz_sum);
total += amplitude * value * dampening;
let new_px = 1.6 * px - 1.2 * pz;
let new_pz = 1.2 * px + 1.6 * pz;
px = new_px;
pz = new_pz;
amplitude *= config.gain;
}
point.y - config.base_height - total
}
#[derive(Clone, Copy)]
pub struct TerrainConfig {
pub enabled: bool,
pub base_height: f32,
pub material_id: u32,
pub seed: u32,
pub frequency: f32,
pub amplitude: f32,
pub octaves: u32,
pub lacunarity: f32,
pub gain: f32,
}
impl Default for TerrainConfig {
fn default() -> Self {
Self {
enabled: false,
base_height: 0.0,
material_id: 0,
seed: 0,
frequency: 0.01,
amplitude: 30.0,
octaves: 11,
lacunarity: 2.0,
gain: 0.5,
}
}
}
impl TerrainConfig {
pub fn evaluate(&self, point: Vec3) -> f32 {
terrain_fbm(self, point)
}
pub fn height_at(&self, x: f32, z: f32) -> f32 {
let mut px = x * self.frequency;
let mut pz = z * self.frequency;
let mut total = 0.0_f32;
let mut amplitude = self.amplitude;
let mut dx_sum = 0.0_f32;
let mut dz_sum = 0.0_f32;
for _octave in 0..self.octaves {
let (value, dvdx, dvdz) = noised_2d(px, pz, self.seed);
dx_sum += dvdx * 0.5;
dz_sum += dvdz * 0.5;
let dampening = 1.0 / (1.0 + dx_sum * dx_sum + dz_sum * dz_sum);
total += amplitude * value * dampening;
let new_px = 1.6 * px - 1.2 * pz;
let new_pz = 1.2 * px + 1.6 * pz;
px = new_px;
pz = new_pz;
amplitude *= self.gain;
}
self.base_height + total
}
pub fn max_surface_extent(&self) -> f32 {
self.amplitude * 2.5
}
}
#[derive(Clone)]
pub enum UndoAction {
AddEdit { index: usize },
RemoveEdit { edit: SdfEdit, index: usize },
ModifyEdit { old_edit: SdfEdit, index: usize },
}
pub struct SdfWorld {
pub edits: Vec<SdfEdit>,
pub clipmap: SdfClipmap,
pub bvh: SdfEditBvh,
pub dirty: bool,
pub pending_gpu_dispatches: Vec<GpuBrickDispatch>,
pub max_updates_per_frame: usize,
pub debug_brick_coloring: bool,
pub terrain: TerrainConfig,
pub smoothness_scale: f32,
undo_stack: Vec<UndoAction>,
redo_stack: Vec<UndoAction>,
max_undo_history: usize,
}
impl Default for SdfWorld {
fn default() -> Self {
Self::new()
}
}
impl SdfWorld {
pub fn new() -> Self {
Self::with_config(DEFAULT_LEVEL_COUNT, BASE_VOXEL_SIZE)
}
pub fn with_config(level_count: usize, base_voxel_size: f32) -> Self {
use super::brick_map::MAX_BRICKS;
Self {
edits: Vec::new(),
clipmap: SdfClipmap::new(level_count, base_voxel_size, MAX_BRICKS as u32),
bvh: SdfEditBvh::new(),
dirty: false,
pending_gpu_dispatches: Vec::new(),
max_updates_per_frame: 4000,
debug_brick_coloring: false,
terrain: TerrainConfig::default(),
smoothness_scale: 1.0,
undo_stack: Vec::new(),
redo_stack: Vec::new(),
max_undo_history: 100,
}
}
pub fn add_edit(&mut self, edit: SdfEdit) -> usize {
let index = self.edits.len();
let bounds = edit.effective_bounds();
self.bvh.add_edit(index, bounds);
self.edits.push(edit);
self.clipmap.mark_dirty_in_bounds(&bounds);
self.dirty = true;
self.undo_stack.push(UndoAction::AddEdit { index });
self.redo_stack.clear();
self.trim_undo_history();
index
}
pub fn add_edit_no_undo(&mut self, edit: SdfEdit) -> usize {
let index = self.edits.len();
let bounds = edit.effective_bounds();
self.bvh.add_edit(index, bounds);
self.edits.push(edit);
self.clipmap.mark_dirty_in_bounds(&bounds);
self.dirty = true;
index
}
pub fn add_sphere(&mut self, center: Vec3, radius: f32, material_id: u32) -> usize {
let transform = nalgebra_glm::translation(¢er);
let edit = SdfEdit::union(SdfPrimitive::Sphere { radius }, transform, material_id);
self.add_edit(edit)
}
pub fn add_box(&mut self, center: Vec3, half_extents: Vec3, material_id: u32) -> usize {
let transform = nalgebra_glm::translation(¢er);
let edit = SdfEdit::union(SdfPrimitive::Box { half_extents }, transform, material_id);
self.add_edit(edit)
}
pub fn add_ground_plane(&mut self, height: f32, material_id: u32) -> usize {
let transform = nalgebra_glm::translation(&Vec3::new(0.0, height, 0.0));
let edit = SdfEdit::union(
SdfPrimitive::Plane {
normal: Vec3::new(0.0, 1.0, 0.0),
offset: 0.0,
},
transform,
material_id,
);
self.add_edit(edit)
}
pub fn subtract_sphere(&mut self, center: Vec3, radius: f32, smoothness: f32) -> usize {
let transform = nalgebra_glm::translation(¢er);
let edit = if smoothness > 0.0 {
SdfEdit::smooth_subtraction(SdfPrimitive::Sphere { radius }, transform, 0, smoothness)
} else {
SdfEdit::subtraction(SdfPrimitive::Sphere { radius }, transform, 0)
};
self.add_edit(edit)
}
pub fn modify_edit<F>(&mut self, index: usize, modifier: F)
where
F: FnOnce(&mut SdfEdit),
{
if let Some(edit) = self.edits.get_mut(index) {
let old_edit = edit.clone();
let old_bounds = edit.effective_bounds();
modifier(edit);
let new_bounds = edit.effective_bounds();
self.bvh.update_edit(index, new_bounds);
self.clipmap.mark_dirty_in_bounds(&old_bounds);
self.clipmap.mark_dirty_in_bounds(&new_bounds);
self.dirty = true;
self.undo_stack
.push(UndoAction::ModifyEdit { old_edit, index });
self.redo_stack.clear();
self.trim_undo_history();
}
}
pub fn modify_edit_no_undo<F>(&mut self, index: usize, modifier: F)
where
F: FnOnce(&mut SdfEdit),
{
if let Some(edit) = self.edits.get_mut(index) {
let old_bounds = edit.effective_bounds();
modifier(edit);
let new_bounds = edit.effective_bounds();
self.bvh.update_edit(index, new_bounds);
self.clipmap.mark_dirty_in_bounds(&old_bounds);
self.clipmap.mark_dirty_in_bounds(&new_bounds);
self.dirty = true;
}
}
pub fn remove_edit(&mut self, index: usize) {
if index < self.edits.len() {
let edit = self.edits.remove(index);
let bounds = edit.effective_bounds();
self.bvh.remove_edit(index);
for following_index in index..self.edits.len() {
let following_bounds = self.edits[following_index].effective_bounds();
self.bvh.remove_edit(following_index + 1);
self.bvh.add_edit(following_index, following_bounds);
}
self.clipmap.mark_dirty_in_bounds(&bounds);
self.dirty = true;
self.undo_stack.push(UndoAction::RemoveEdit { edit, index });
self.redo_stack.clear();
self.trim_undo_history();
}
}
pub fn undo(&mut self) -> bool {
if let Some(action) = self.undo_stack.pop() {
match action.clone() {
UndoAction::AddEdit { index } => {
if index < self.edits.len() {
let edit = self.edits.remove(index);
let bounds = edit.effective_bounds();
self.bvh.remove_edit(index);
for following_index in index..self.edits.len() {
let following_bounds = self.edits[following_index].effective_bounds();
self.bvh.remove_edit(following_index + 1);
self.bvh.add_edit(following_index, following_bounds);
}
self.clipmap.mark_dirty_in_bounds(&bounds);
self.dirty = true;
self.redo_stack.push(UndoAction::RemoveEdit { edit, index });
}
}
UndoAction::RemoveEdit { edit, index } => {
let bounds = edit.effective_bounds();
for following_index in (index..self.edits.len()).rev() {
let following_bounds = self.edits[following_index].effective_bounds();
self.bvh.remove_edit(following_index);
self.bvh.add_edit(following_index + 1, following_bounds);
}
self.edits.insert(index, edit);
self.bvh.add_edit(index, bounds);
self.clipmap.mark_dirty_in_bounds(&bounds);
self.dirty = true;
self.redo_stack.push(UndoAction::AddEdit { index });
}
UndoAction::ModifyEdit { old_edit, index } => {
if let Some(current_edit) = self.edits.get_mut(index) {
let current_clone = current_edit.clone();
let old_bounds = current_edit.effective_bounds();
*current_edit = old_edit;
let new_bounds = current_edit.effective_bounds();
self.bvh.update_edit(index, new_bounds);
self.clipmap.mark_dirty_in_bounds(&old_bounds);
self.clipmap.mark_dirty_in_bounds(&new_bounds);
self.dirty = true;
self.redo_stack.push(UndoAction::ModifyEdit {
old_edit: current_clone,
index,
});
}
}
}
true
} else {
false
}
}
pub fn redo(&mut self) -> bool {
if let Some(action) = self.redo_stack.pop() {
match action.clone() {
UndoAction::AddEdit { index } => {
if index < self.edits.len() {
let edit = self.edits.remove(index);
let bounds = edit.effective_bounds();
self.bvh.remove_edit(index);
for following_index in index..self.edits.len() {
let following_bounds = self.edits[following_index].effective_bounds();
self.bvh.remove_edit(following_index + 1);
self.bvh.add_edit(following_index, following_bounds);
}
self.clipmap.mark_dirty_in_bounds(&bounds);
self.dirty = true;
self.undo_stack.push(UndoAction::RemoveEdit { edit, index });
}
}
UndoAction::RemoveEdit { edit, index } => {
let bounds = edit.effective_bounds();
for following_index in (index..self.edits.len()).rev() {
let following_bounds = self.edits[following_index].effective_bounds();
self.bvh.remove_edit(following_index);
self.bvh.add_edit(following_index + 1, following_bounds);
}
self.edits.insert(index, edit);
self.bvh.add_edit(index, bounds);
self.clipmap.mark_dirty_in_bounds(&bounds);
self.dirty = true;
self.undo_stack.push(UndoAction::AddEdit { index });
}
UndoAction::ModifyEdit { old_edit, index } => {
if let Some(current_edit) = self.edits.get_mut(index) {
let current_clone = current_edit.clone();
let old_bounds = current_edit.effective_bounds();
*current_edit = old_edit;
let new_bounds = current_edit.effective_bounds();
self.bvh.update_edit(index, new_bounds);
self.clipmap.mark_dirty_in_bounds(&old_bounds);
self.clipmap.mark_dirty_in_bounds(&new_bounds);
self.dirty = true;
self.undo_stack.push(UndoAction::ModifyEdit {
old_edit: current_clone,
index,
});
}
}
}
true
} else {
false
}
}
pub fn can_undo(&self) -> bool {
!self.undo_stack.is_empty()
}
pub fn can_redo(&self) -> bool {
!self.redo_stack.is_empty()
}
pub fn clear_undo_history(&mut self) {
self.undo_stack.clear();
self.redo_stack.clear();
}
fn trim_undo_history(&mut self) {
while self.undo_stack.len() > self.max_undo_history {
self.undo_stack.remove(0);
}
}
pub fn clear(&mut self) {
for edit in &self.edits {
let bounds = edit.effective_bounds();
self.clipmap.mark_dirty_in_bounds(&bounds);
}
self.edits.clear();
self.bvh = SdfEditBvh::new();
self.dirty = true;
self.clear_undo_history();
}
pub fn update(&mut self, camera_position: Vec3) {
self.pending_gpu_dispatches.clear();
for level in &mut self.clipmap.levels {
level.pointer_dirty = false;
}
let bounds: Vec<Aabb> = self.edits.iter().map(|e| e.effective_bounds()).collect();
self.bvh.rebuild_if_needed(&bounds);
let edits_aabb = if !self.edits.is_empty() {
let mut aabb_min = Vec3::new(f32::MAX, f32::MAX, f32::MAX);
let mut aabb_max = Vec3::new(f32::MIN, f32::MIN, f32::MIN);
for bound in &bounds {
aabb_min = nalgebra_glm::min2(&aabb_min, &bound.min);
aabb_max = nalgebra_glm::max2(&aabb_max, &bound.max);
}
Some(Aabb {
min: aabb_min,
max: aabb_max,
})
} else {
None
};
self.clipmap.update_center_with_bvh(
camera_position,
Some(&self.bvh),
edits_aabb.as_ref(),
&self.terrain,
);
let dispatches = prepare_gpu_brick_dispatches(
&mut self.clipmap,
&self.bvh,
&self.terrain,
self.max_updates_per_frame,
camera_position,
);
if !dispatches.is_empty() {
self.dirty = true;
}
self.pending_gpu_dispatches = dispatches;
}
pub fn mark_edit_dirty(&mut self, index: usize) {
if let Some(edit) = self.edits.get(index) {
let bounds = edit.effective_bounds();
self.clipmap.mark_dirty_in_bounds(&bounds);
self.dirty = true;
}
}
pub fn edit_count(&self) -> usize {
self.edits.len()
}
pub fn allocated_brick_count(&self) -> u32 {
self.clipmap.allocator.allocated_count()
}
pub fn max_brick_count(&self) -> u32 {
self.clipmap.allocator.capacity()
}
pub fn level_count(&self) -> usize {
self.clipmap.level_count()
}
pub fn voxel_sizes(&self) -> Vec<f32> {
self.clipmap.voxel_sizes()
}
pub fn evaluate_at(&self, point: Vec3) -> f32 {
let mut distance = if self.terrain.enabled {
self.terrain.evaluate(point)
} else {
f32::MAX
};
for edit in &self.edits {
let edit_distance = edit.evaluate(point);
distance = edit.operation().apply(distance, edit_distance);
}
distance
}
pub fn evaluate_at_lod(&self, point: Vec3, max_octaves: u32) -> f32 {
let mut distance = if self.terrain.enabled {
let lod_config = TerrainConfig {
octaves: max_octaves.min(self.terrain.octaves),
..self.terrain
};
lod_config.evaluate(point)
} else {
f32::MAX
};
for edit in &self.edits {
let edit_distance = edit.evaluate(point);
distance = edit.operation().apply(distance, edit_distance);
}
distance
}
pub fn evaluate_at_lod_excluding(
&self,
point: Vec3,
max_octaves: u32,
excluded_indices: &HashSet<usize>,
) -> f32 {
let mut distance = if self.terrain.enabled {
let lod_config = TerrainConfig {
octaves: max_octaves.min(self.terrain.octaves),
..self.terrain
};
lod_config.evaluate(point)
} else {
f32::MAX
};
for (index, edit) in self.edits.iter().enumerate() {
if excluded_indices.contains(&index) {
continue;
}
let edit_distance = edit.evaluate(point);
distance = edit.operation().apply(distance, edit_distance);
}
distance
}
pub fn set_smoothness_scale(&mut self, scale: f32) {
if (self.smoothness_scale - scale).abs() > f32::EPSILON {
self.smoothness_scale = scale;
self.clipmap.force_reinitialize();
self.dirty = true;
}
}
pub fn set_terrain_config(&mut self, config: TerrainConfig) {
let changed = self.terrain.enabled != config.enabled
|| self.terrain.base_height != config.base_height
|| self.terrain.seed != config.seed
|| self.terrain.frequency != config.frequency
|| self.terrain.amplitude != config.amplitude
|| self.terrain.octaves != config.octaves
|| self.terrain.gain != config.gain;
self.terrain = config;
if changed {
self.clipmap.force_reinitialize();
self.dirty = true;
}
}
pub fn raycast(&self, origin: Vec3, direction: Vec3, max_distance: f32) -> Option<Vec3> {
if self.edits.is_empty() && !self.terrain.enabled {
return None;
}
let direction = nalgebra_glm::normalize(&direction);
let mut total_distance = 0.0;
let base_voxel_size = BASE_VOXEL_SIZE;
let min_step = base_voxel_size * 0.5;
let hit_threshold = base_voxel_size * 0.1;
for _ in 0..512 {
let current_point = origin + direction * total_distance;
let distance = self.evaluate_at(current_point);
if distance.abs() < hit_threshold {
return Some(current_point);
}
if distance < 0.0 {
total_distance += min_step;
} else if distance == f32::MAX {
total_distance += base_voxel_size * 4.0;
} else {
total_distance += distance.max(min_step);
}
if total_distance > max_distance {
return None;
}
}
None
}
pub fn evaluate_normal_at(&self, point: Vec3) -> Vec3 {
let epsilon = BASE_VOXEL_SIZE * 0.5;
let dx = self.evaluate_at(point + Vec3::new(epsilon, 0.0, 0.0))
- self.evaluate_at(point - Vec3::new(epsilon, 0.0, 0.0));
let dy = self.evaluate_at(point + Vec3::new(0.0, epsilon, 0.0))
- self.evaluate_at(point - Vec3::new(0.0, epsilon, 0.0));
let dz = self.evaluate_at(point + Vec3::new(0.0, 0.0, epsilon))
- self.evaluate_at(point - Vec3::new(0.0, 0.0, epsilon));
nalgebra_glm::normalize(&Vec3::new(dx, dy, dz))
}
pub fn snap_to_voxel_grid(&self, point: Vec3, level: usize) -> Vec3 {
let voxel_size = if level < self.clipmap.levels.len() {
self.clipmap.levels[level].voxel_size
} else {
BASE_VOXEL_SIZE
};
Vec3::new(
(point.x / voxel_size).round() * voxel_size,
(point.y / voxel_size).round() * voxel_size,
(point.z / voxel_size).round() * voxel_size,
)
}
pub fn base_voxel_size(&self) -> f32 {
BASE_VOXEL_SIZE
}
pub fn get_brick_pointer_at(&self, world_pos: Vec3, level: usize) -> i32 {
if level >= self.clipmap.levels.len() {
return -1;
}
let grid = &self.clipmap.levels[level];
let brick_coord = grid.world_to_brick_coord(world_pos);
grid.get_pointer(brick_coord)
}
pub fn get_allocated_bricks_in_range(
&self,
level: usize,
center: Vec3,
radius: i32,
) -> Vec<(nalgebra_glm::IVec3, Vec3)> {
self.get_allocated_bricks_in_range_with_pointers(level, center, radius)
.into_iter()
.map(|(coord, origin, _pointer)| (coord, origin))
.collect()
}
pub fn get_allocated_bricks_in_range_with_pointers(
&self,
level: usize,
center: Vec3,
radius: i32,
) -> Vec<(nalgebra_glm::IVec3, Vec3, i32)> {
let mut result = Vec::new();
if level >= self.clipmap.levels.len() {
return result;
}
let grid = &self.clipmap.levels[level];
let brick_world_size = grid.voxel_size * 8.0;
let center_brick = grid.world_to_brick_coord(center);
for bz in -radius..=radius {
for by in -radius..=radius {
for bx in -radius..=radius {
let brick_coord = nalgebra_glm::IVec3::new(
center_brick.x + bx,
center_brick.y + by,
center_brick.z + bz,
);
let pointer = grid.get_pointer(brick_coord);
if pointer >= 0 {
let world_origin = Vec3::new(
brick_coord.x as f32 * brick_world_size,
brick_coord.y as f32 * brick_world_size,
brick_coord.z as f32 * brick_world_size,
);
result.push((brick_coord, world_origin, pointer));
}
}
}
}
result
}
}
#[derive(Clone, Copy)]
pub struct SdfMaterial {
pub base_color: Vec3,
pub roughness: f32,
pub metallic: f32,
pub emissive: Vec3,
}
impl Default for SdfMaterial {
fn default() -> Self {
Self {
base_color: Vec3::new(0.8, 0.8, 0.8),
roughness: 0.5,
metallic: 0.0,
emissive: Vec3::zeros(),
}
}
}
impl SdfMaterial {
pub fn new(base_color: Vec3) -> Self {
Self {
base_color,
..Default::default()
}
}
pub fn with_roughness(mut self, roughness: f32) -> Self {
self.roughness = roughness;
self
}
pub fn with_metallic(mut self, metallic: f32) -> Self {
self.metallic = metallic;
self
}
pub fn with_emissive(mut self, emissive: Vec3) -> Self {
self.emissive = emissive;
self
}
}
pub struct SdfMaterialRegistry {
pub materials: Vec<SdfMaterial>,
}
impl Default for SdfMaterialRegistry {
fn default() -> Self {
Self::new()
}
}
impl SdfMaterialRegistry {
pub fn new() -> Self {
let mut registry = Self {
materials: Vec::new(),
};
registry.add_material(SdfMaterial::default());
registry
}
pub fn add_material(&mut self, material: SdfMaterial) -> u32 {
let id = self.materials.len() as u32;
self.materials.push(material);
id
}
pub fn get_material(&self, id: u32) -> Option<&SdfMaterial> {
self.materials.get(id as usize)
}
pub fn set_material(&mut self, id: u32, material: SdfMaterial) {
if let Some(existing) = self.materials.get_mut(id as usize) {
*existing = material;
}
}
}