// ▄████████▄ + ███ + ▄█████████ ███ + // ███▀ ▀███ + + ███ ███▀ + ███ + + // ███ + ███ ███ ███ █████████ ███ ███ ███ ███ // ███ +███ ███ ███ ███ ███▐██████ ███ ███ ███ // ███ + ███ ███+ ███ +███ ███ + ███ ███ + ███ // ███▄ ▄███ ███▄ ███ ███ + ███ + ███ ███▄ ███ // ▀████████▀ + ▀███████ ███▄ ███▄ ▀████ ▀███████ // + + + ███ // + ▀████████████████████████████████████████████████████▀ // // This module populates the world with stars and asteroids. use crate::prelude::*; use bevy::math::I64Vec3; use bevy::prelude::*; use bevy::render::mesh::Indices; use bevy::scene::{InstanceId, SceneInstance}; use bevy_xpbd_3d::prelude::*; use fastrand; use std::collections::HashMap; const ASTEROID_UPDATE_INTERVAL: f32 = 0.1; // seconds const ASTEROID_SIZE_FACTOR: f32 = 10.0; const RING_THICKNESS: f64 = 8.0e6; const STARS_MAX_MAGNITUDE: f32 = 5.5; // max 7.0, see generate_starchart.py const SKYBOX: bool = false; const ASTEROID_SPAWN_STEP: f64 = 1000.0; const ASTEROID_VIEW_RADIUS: f64 = 3000.0; const ASSET_NAME_ASTEROID1: &str = "asteroid1"; const ASSET_NAME_ASTEROID2: &str = "asteroid2"; pub struct WorldPlugin; impl Plugin for WorldPlugin { fn build(&self, app: &mut App) { app.add_systems(Startup, setup); app.add_systems(PostUpdate, handle_despawn); app.add_systems(Update, spawn_despawn_asteroids); app.add_systems(Update, handle_respawn.run_if(on_event::())); app.add_plugins(PhysicsPlugins::default()); //app.add_plugins(PhysicsDebugPlugin::default()); app.insert_resource(Gravity(DVec3::splat(0.0))); app.insert_resource(AsteroidUpdateTimer(Timer::from_seconds( ASTEROID_UPDATE_INTERVAL, TimerMode::Repeating, ))); app.insert_resource(ActiveAsteroids(HashMap::new())); app.add_event::(); app.add_event::(); } } #[derive(Resource)] struct AsteroidUpdateTimer(Timer); #[derive(Resource)] pub struct ActiveAsteroids(pub HashMap); #[derive(Component)] struct Asteroid; #[derive(Component)] pub struct Star; #[derive(Component)] pub struct DespawnOnPlayerDeath; #[derive(Event)] pub struct RespawnEvent; pub struct AsteroidData { entity: Entity, //viewdistance: f64, } #[derive(Event)] pub struct DespawnAsteroidEvent { entity: Entity, sceneinstance: InstanceId, origin: I64Vec3, } pub fn setup( mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, mut materials_skybox: ResMut>, ) { // Generate starmap let sphere_handle = meshes.add(Sphere::new(1.0).mesh().uv(16, 16)); let mut starcount = 0; for (index, star) in nature::STARS.iter().enumerate() { let (x, y, z, mag, _absmag, color_index, name) = *star; if mag > STARS_MAX_MAGNITUDE { continue; } let (r, g, b) = nature::star_color_index_to_rgb(color_index); let mut pos = DVec3::new(x as f64, z as f64, -y as f64) * nature::PARSEC2METER; if pos.length() > 1e21 { pos *= 0.002; } let pos_render = pos * 1.0; let scale_factor = 1e-4 * pos_render.length() as f32; // from experimentation let mag = mag.min(6.0); let scale_size = { |mag: f32| scale_factor * (0.230299 * mag * mag - 3.09013 * mag + 15.1782) }; let scale = scale_size(mag); let scale_color = { |color: f32| 1.2 * color * (0.0659663 * mag * mag - 1.09862 * mag + 4.3) }; //let scale = translation.length().powf(0.84); //pos_render.length().powf(0.64) //(radius as f64 * nature::SOL_RADIUS).powf(0.02) as f32 * let star_color_handle = materials.add(StandardMaterial { base_color: Color::rgb(scale_color(r), scale_color(g), scale_color(b)), unlit: true, ..default() }); let name = if name.is_empty() { format!("Uncharted Star #{index}") } else { name.to_string() }; let distance = if pos.length() > 1e21 { None } else { Some(pos.length()) }; commands.spawn(( Star, hud::IsClickable { name: Some(name), distance, ..default() }, PbrBundle { mesh: sphere_handle.clone(), material: star_color_handle, transform: Transform { translation: pos_render.as_vec3(), scale: Vec3::splat(scale as f32), ..default() }, ..default() }, Position::from(pos_render), Rotation::from(Quat::IDENTITY), )); starcount += 1; } info!("Generated {starcount} stars"); // Add shaded skybox if SKYBOX { let mut mesh = Mesh::from(Sphere::new(1e10).mesh().uv(5, 5)); //let mut mesh = Mesh::from(Cuboid::from_size(Vec3::splat(2e10))); if let Some(Indices::U32(indices)) = mesh.indices_mut() { // Reverse the order of each triangle to avoid backface culling for slice in indices.chunks_mut(3) { slice.reverse(); } } commands.spawn(MaterialMeshBundle { mesh: meshes.add(mesh), material: materials_skybox.add(load::SkyBox {}), transform: Transform::from_translation(Vec3::new(0.0, 0.0, 0.0)), ..default() }); } } fn spawn_despawn_asteroids( time: Res