use bevy::prelude::*; use std::collections::HashMap; #[derive(Component)] pub struct PlanetMeshFace { pub normal: Vec3, } #[derive(Clone)] pub struct QuadtreeChunk { pub bounds_pos: Vec3, pub bounds_size: f32, pub depth: usize, pub max_chunk_depth: usize, pub identifier: String, pub children: Vec, } impl QuadtreeChunk { pub fn new(bounds_pos: Vec3, bounds_size: f32, depth: usize, max_depth: usize) -> Self { let identifier = format!("{:?}_{:?}_{}", bounds_pos, bounds_size, depth); Self { bounds_pos, bounds_size, depth, max_chunk_depth: max_depth, identifier, children: Vec::new(), } } pub fn subdivide( &mut self, focus_point: Vec3, face_origin: Vec3, axis_a: Vec3, axis_b: Vec3, planet_data: &PlanetData, ) { let half = self.bounds_size * 0.5; let quarter = self.bounds_size * 0.25; let offsets = [ Vec2::new(-quarter, -quarter), Vec2::new( quarter, -quarter), Vec2::new(-quarter, quarter), Vec2::new( quarter, quarter), ]; for offset in offsets { let child_2d = Vec2::new(self.bounds_pos.x, self.bounds_pos.z) + offset; let center_3d = face_origin + child_2d.x * axis_a + child_2d.y * axis_b; let distance = planet_data .point_on_planet(center_3d.normalize()) .distance(focus_point); let next_depth = self.depth + 1; let should_split = self.depth < self.max_chunk_depth && distance <= planet_data.lod_levels[self.depth].distance; let mut child = QuadtreeChunk::new( Vec3::new(child_2d.x, 0.0, child_2d.y), half, next_depth, self.max_chunk_depth, ); if should_split { child.subdivide( focus_point, face_origin, axis_a, axis_b, planet_data, ); } self.children.push(child); } } } #[derive(Component, Default)] pub struct PlanetFaceRuntime { pub chunks: HashMap, pub chunks_current: HashMap, } pub fn regenerate_planet_face( mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, planet_data: Res, mut query: Query<(Entity, &PlanetMeshFace, &mut PlanetFaceRuntime)>, ) { if !planet_data.is_changed() { return; } for (entity, face, mut runtime) in &mut query { runtime.chunks_current.clear(); let focus_point = planet_data.lod_focus; let axis_a = Vec3::new(face.normal.y, face.normal.z, face.normal.x).normalize(); let axis_b = face.normal.cross(axis_a).normalize(); let mut root = QuadtreeChunk::new( Vec3::ZERO, 2.0, 0, planet_data.max_lod, ); root.subdivide( focus_point, face.normal, axis_a, axis_b, &planet_data, ); visualize_quadtree( &mut commands, &mut meshes, &mut materials, entity, &mut runtime, &root, face.normal, axis_a, axis_b, &planet_data, ); // Remove unused chunks let old_chunks: Vec = runtime .chunks .keys() .filter(|id| !runtime.chunks_current.contains_key(*id)) .cloned() .collect(); for id in old_chunks { if let Some(e) = runtime.chunks.remove(&id) { commands.entity(e).despawn_recursive(); } } } } fn visualize_quadtree( commands: &mut Commands, meshes: &mut Assets, materials: &mut Assets, parent: Entity, runtime: &mut PlanetFaceRuntime, chunk: &QuadtreeChunk, face_origin: Vec3, axis_a: Vec3, axis_b: Vec3, planet_data: &PlanetData, ) { if chunk.children.is_empty() { runtime.chunks_current.insert(chunk.identifier.clone(), true); if runtime.chunks.contains_key(&chunk.identifier) { return; } let res = planet_data.lod_levels[chunk.depth - 1].resolution as usize; let size = chunk.bounds_size; let offset = chunk.bounds_pos; let mut positions = Vec::with_capacity(res * res); let mut normals = vec![Vec3::ZERO; res * res]; let mut indices = Vec::new(); for y in 0..res { for x in 0..res { let percent = Vec2::new(x as f32, y as f32) / (res as f32 - 1.0); let local = Vec2::new(offset.x, offset.z) + percent * size; let plane = face_origin + local.x * axis_a + local.y * axis_b; let sphere = planet_data.point_on_planet(plane.normalize()); positions.push(sphere); if x < res - 1 && y < res - 1 { let i = (x + y * res) as u32; indices.extend_from_slice(&[ i, i + res as u32, i + res as u32 + 1, i, i + res as u32 + 1, i + 1, ]); } } } // Normals for tri in indices.chunks_exact(3) { let a = tri[0] as usize; let b = tri[1] as usize; let c = tri[2] as usize; let n = (positions[b] - positions[a]) .cross(positions[c] - positions[a]) .normalize(); normals[a] += n; normals[b] += n; normals[c] += n; } for n in &mut normals { *n = n.normalize(); } let mut mesh = Mesh::new(PrimitiveTopology::TriangleList); mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions); mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals); mesh.set_indices(Some(Indices::U32(indices))); let mesh_handle = meshes.add(mesh); let material = materials.add(StandardMaterial { base_color_texture: planet_data.planet_color.clone(), ..default() }); let chunk_entity = commands .spawn(( PbrBundle { mesh: mesh_handle, material, ..default() }, )) .set_parent(parent) .id(); runtime.chunks.insert(chunk.identifier.clone(), chunk_entity); } for child in &chunk.children { visualize_quadtree( commands, meshes, materials, parent, runtime, child, face_origin, axis_a, axis_b, planet_data, ); } }