// ▄████████▄ + ███ + ▄█████████ ███ + // ███▀ ▀███ + + ███ ███▀ + ███ + + // ███ + ███ ███ ███ █████████ ███ ███ ███ ███ // ███ +███ ███ ███ ███ ███ ██████ ███ ███ ███ // ███ + ███ ███+ ███ +███ ███ + ███ ███ + ███ // ███▄ ▄███ ███▄ ███ ███ + ███ + ███ ███▄ ███ // ▀████████▀ + ▀███████ ███▄ ███▄ ▀████ ▀███████ // + + + ███ // + ▀████████████████████████████████████████████████████▀ use crate::{actor, audio, hud, nature, shading, var}; use bevy::prelude::*; use bevy::math::{DVec3, I64Vec3}; use bevy::scene::{InstanceId, SceneInstance}; use bevy::render::mesh::Indices; use bevy_xpbd_3d::prelude::*; use bevy_xpbd_3d::plugins::sync; use std::collections::HashMap; use std::f32::consts::PI; use fastrand; 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 CENTER_WORLD_ON_PLAYER: bool = true; const SKYBOX: bool = false; const ASTEROID_SPAWN_STEP: f64 = 500.0; const ASTEROID_VIEW_RADIUS: f64 = 3000.0; const ASSET_ASTEROID1: &str = "models/asteroid.glb#Scene0"; const ASSET_ASTEROID2: &str = "models/asteroid2.glb#Scene0"; pub fn asset_name_to_path(name: &str) -> &'static str { match name { "suit" => "models/suit_with_collider.glb#Scene0", "suit_ar_chefhat" => "models/suit_ar_chefhat.glb#Scene0", "asteroid1" => ASSET_ASTEROID1, "asteroid2" => ASSET_ASTEROID2, "asteroid_lum" => "models/asteroid_lum.glb#Scene0", "moonlet" => "models/moonlet.glb#Scene0", "monolith" => "models/monolith_neon.glb#Scene0", "lightorb" => "models/lightorb.glb#Scene0", "orb_busstop" => "models/orb_busstop.glb#Scene0", "orb_busstop_dim" => "models/orb_busstop_dim.glb#Scene0", "MeteorAceGT" => "models/MeteorAceGT.glb#Scene0", "satellite" => "models/satellite.glb#Scene0", "pizzeria" => "models/pizzeria2.glb#Scene0", "pizzasign" => "models/pizzasign.glb#Scene0", "selectagon" => "models/selectagon.glb#Scene0", "orbitring" => "models/orbitring.glb#Scene0", "clippy" => "models/clippy.glb#Scene0", "clippy_ar" => "models/clippy_ar.glb#Scene0", "whale" => "models/whale.glb#Scene0", _ => "models/error.glb#Scene0", } } pub struct WorldPlugin; impl Plugin for WorldPlugin { fn build(&self, app: &mut App) { app.add_systems(Startup, setup); app.add_systems(Update, handle_cheats); app.add_systems(PostUpdate, handle_despawn); app.add_systems(Update, spawn_despawn_asteroids); 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::(); if CENTER_WORLD_ON_PLAYER { // Disable bevy_xpbd's position->transform sync function app.insert_resource(sync::SyncConfig { position_to_transform: true, transform_to_position: false, }); // Add own position->transform sync function app.add_systems(PostUpdate, position_to_transform .after(sync::position_to_transform) .in_set(sync::SyncSet::PositionToTransform)); } } } #[derive(Resource)] struct AsteroidUpdateTimer(Timer); #[derive(Resource)] pub struct ActiveAsteroids(pub HashMap); #[derive(Resource)] struct AsteroidModel1(Handle); #[derive(Resource)] struct AsteroidModel2(Handle); #[derive(Component)] struct Asteroid; #[derive(Component)] pub struct DespawnOnPlayerDeath; pub struct AsteroidData { entity: Entity, //viewdistance: f64, } #[derive(Event)] pub struct DespawnEvent { entity: Entity, sceneinstance: InstanceId, origin: I64Vec3, } #[derive(Component)] pub struct Star; pub fn setup( mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, mut materials_skybox: ResMut>, asset_server: Res, ) { // Load assets commands.insert_resource(AsteroidModel1(asset_server.load(ASSET_ASTEROID1))); commands.insert_resource(AsteroidModel2(asset_server.load(ASSET_ASTEROID2))); // 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() }, )); 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(shading::SkyBox {}), transform: Transform::from_translation(Vec3::new(0.0, 0.0, 0.0)), ..default() }); } } fn spawn_despawn_asteroids( time: Res