use bevy::prelude::*; use bevy::render::mesh::{Indices, PrimitiveTopology}; fn main() { App::new() .add_plugins(DefaultPlugins) .add_startup_system(setup) .add_system(update_lod) .run(); } #[derive(Resource)] struct LodSettings { distances: Vec<(f32, u32)>, // (distance, subdivisions) } #[derive(Component)] struct Planet; #[derive(Component)] struct LodLevel(u32); #[derive(Component)] struct FocusPoint; /// ───────────────────────────────────────────── /// Setup /// ───────────────────────────────────────────── fn setup( mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, ) { commands.insert_resource(LodSettings { distances: vec![ (200.0, 0), (100.0, 1), (50.0, 2), (20.0, 3), ], }); // Camera (pod-scroll compatible) commands.spawn(Camera3dBundle { transform: Transform::from_xyz(0.0, 0.0, 150.0), ..default() }) .insert(FocusPoint); // Light commands.spawn(PointLightBundle { transform: Transform::from_xyz(100.0, 100.0, 100.0), ..default() }); // Planet let mesh = generate_hex_sphere(1, 50.0); let mesh_handle = meshes.add(mesh); let material = materials.add(StandardMaterial { base_color: Color::rgb(0.4, 0.6, 1.0), perceptual_roughness: 0.8, ..default() }); commands.spawn(( PbrBundle { mesh: mesh_handle, material, transform: Transform::default(), ..default() }, Planet, LodLevel(1), )); } fn generate_hex_sphere(subdivisions: u32, radius: f32) -> Mesh { // Start from an icosahedron let mut vertices = icosahedron_vertices(); let mut indices = icosahedron_indices(); for _ in 0..subdivisions { let (v, i) = subdivide(&vertices, &indices); vertices = v; indices = i; } // Project to sphere for v in &mut vertices { *v = v.normalize() * radius; } // Build mesh let mut mesh = Mesh::new(PrimitiveTopology::TriangleList); mesh.insert_attribute( Mesh::ATTRIBUTE_POSITION, vertices.iter().map(|v| [v.x, v.y, v.z]).collect::>(), ); mesh.set_indices(Some(Indices::U32(indices))); mesh.compute_normals(); mesh } fn icosahedron_vertices() -> Vec { let t = (1.0 + 5.0_f32.sqrt()) / 2.0; vec![ Vec3::new(-1.0, t, 0.0), Vec3::new( 1.0, t, 0.0), Vec3::new(-1.0, -t, 0.0), Vec3::new( 1.0, -t, 0.0), Vec3::new( 0.0, -1.0, t), Vec3::new( 0.0, 1.0, t), Vec3::new( 0.0, -1.0, -t), Vec3::new( 0.0, 1.0, -t), Vec3::new( t, 0.0, -1.0), Vec3::new( t, 0.0, 1.0), Vec3::new(-t, 0.0, -1.0), Vec3::new(-t, 0.0, 1.0), ] } fn icosahedron_indices() -> Vec { vec![ 0,11,5, 0,5,1, 0,1,7, 0,7,10, 0,10,11, 1,5,9, 5,11,4, 11,10,2, 10,7,6, 7,1,8, 3,9,4, 3,4,2, 3,2,6, 3,6,8, 3,8,9, 4,9,5, 2,4,11, 6,2,10, 8,6,7, 9,8,1, ] } fn subdivide(vertices: &Vec, indices: &Vec) -> (Vec, Vec) { let mut new_vertices = vertices.clone(); let mut new_indices = Vec::new(); let mut midpoint_cache = std::collections::HashMap::new(); let mut midpoint = |a: u32, b: u32| -> u32 { let key = if a < b { (a, b) } else { (b, a) }; if let Some(&i) = midpoint_cache.get(&key) { return i; } let v = (vertices[a as usize] + vertices[b as usize]) * 0.5; let index = new_vertices.len() as u32; new_vertices.push(v); midpoint_cache.insert(key, index); index }; for tri in indices.chunks(3) { let a = tri[0]; let b = tri[1]; let c = tri[2]; let ab = midpoint(a, b); let bc = midpoint(b, c); let ca = midpoint(c, a); new_indices.extend([ a, ab, ca, b, bc, ab, c, ca, bc, ab, bc, ca, ]); } (new_vertices, new_indices) } fn update_lod( mut commands: Commands, camera: Query<&Transform, With>, mut planets: Query<(Entity, &Transform, &mut LodLevel), With>, settings: Res, mut meshes: ResMut>, ) { let cam_pos = camera.single().translation; for (entity, transform, mut lod) in &mut planets { let distance = cam_pos.distance(transform.translation); let target = settings .distances .iter() .find(|(d, _)| distance < *d) .map(|(_, l)| *l) .unwrap_or(settings.distances.last().unwrap().1); if lod.0 != target { lod.0 = target; let mesh = generate_hex_sphere(target, 50.0); let handle = meshes.add(mesh); commands.entity(entity).insert(handle); } } }