use crate::{actor, camera}; use bevy::prelude::*; use bevy::core_pipeline::Skybox; use bevy::asset::LoadState; use bevy::render::render_resource::{TextureViewDescriptor, TextureViewDimension}; use bevy::pbr::CascadeShadowConfigBuilder; use bevy::core_pipeline::bloom::{BloomCompositeMode, BloomSettings}; use std::f32::consts::PI; const ASTEROID_SIZE: f32 = 100.0; const MOON_SIZE: f32 = 50.0; const MARS_SIZE: f32 = 10.0; const SUN_SIZE: f32 = 5000.0; const ASTRONAUT_SIZE: f32 = 5.0; const SUN_BRIGHTNESS: f32 = 1e5; const SKYBOX_BRIGHTNESS: f32 = 300.0; const ASSET_CUBEMAP: &str = "textures/stars_cubemap.png"; const ASSET_ASTRONAUT: &str = "tmp/alien.glb#Scene0"; pub struct WorldPlugin; impl Plugin for WorldPlugin { fn build(&self, app: &mut App) { app.add_systems(Startup, setup); app.add_systems(Update, asset_loaded.after(load_cubemap_asset)); } } #[derive(Resource)] pub struct Cubemap { is_loaded: bool, image_handle: Handle, } pub fn setup( mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, mut ambient_light: ResMut, asset_server: Res, ) { // Add player commands.spawn(( actor::Player, actor::Actor::default(), actor::LifeForm::default(), actor::Suit { oxygen: actor::OXY_M, ..default() } )); // Add skybox let skybox_handle = asset_server.load(ASSET_CUBEMAP); commands.spawn(( Camera3dBundle { camera: Camera { hdr: true, // HDR is required for bloom ..default() }, transform: Transform::from_xyz(0.0, 0.0, 8.0).looking_at(Vec3::ZERO, Vec3::Y), ..default() }, camera::CameraController::default(), Skybox { image: skybox_handle.clone(), brightness: SKYBOX_BRIGHTNESS, }, BloomSettings { composite_mode: BloomCompositeMode::EnergyConserving, ..default() }, )); commands.insert_resource(Cubemap { is_loaded: false, image_handle: skybox_handle, }); // Add some hand-placed asteroids let sphere_handle = meshes.add(Sphere::new(1.0)); let gray_handle = materials.add(StandardMaterial { base_color: Color::GRAY, perceptual_roughness: 1.0, ..default() }); let brown_handle = materials.add(StandardMaterial { base_color: Color::Rgba { alpha: 1.0, red: 0.8, green: 0.5, blue: 0.1 }, perceptual_roughness: 1.0, ..default() }); commands.spawn(( actor::Actor::default(), PbrBundle { mesh: sphere_handle.clone(), material: gray_handle.clone(), transform: Transform::from_xyz( 0.0, 0.0, -500.0, ).with_scale(Vec3::splat(MOON_SIZE)), ..default() }, )); commands.spawn(( actor::Actor::default(), PbrBundle { mesh: sphere_handle.clone(), material: brown_handle.clone(), transform: Transform::from_xyz( 300.0, 40.0, 250.0, ).with_scale(Vec3::splat(MARS_SIZE)), ..default() }, )); // Generate a bunch of asteriods let asteroid_color_handle = materials.add(StandardMaterial { base_color: Color::rgb(0.25, 0.2, 0.2), perceptual_roughness: 1.0, ..default() }); for i in -12..12 { for j in -13..13 { for k in -14..14 { let offset = 500.0; let dist = 18000.0; let wobble = dist/2.0; let (i, j, k) = (i as f32, j as f32, k as f32); commands.spawn(( actor::Actor::default(), PbrBundle { mesh: sphere_handle.clone(), material: asteroid_color_handle.clone(), transform: Transform::from_xyz( offset + dist * i + wobble * (j+k/PI).sin() * (k+j/PI).cos(), offset + dist * j + wobble * (k+i/PI).sin() * (i+k/PI).cos(), offset + dist * k + wobble * (i+j/PI).sin() * (j+i/PI).cos(), ).with_scale(Vec3::splat(ASTEROID_SIZE)), ..default() } )); } } } // Add THE SUN let hydrogenfusion_handle = materials.add(StandardMaterial { emissive: Color::rgb_linear(SUN_BRIGHTNESS, 0.9 * SUN_BRIGHTNESS, SUN_BRIGHTNESS), ..default() }); commands.spawn(PbrBundle { mesh: sphere_handle.clone(), material: hydrogenfusion_handle.clone(), transform: Transform::from_xyz( 0.0, 30000.0, -500000.0, ).with_scale(Vec3::splat(SUN_SIZE)), ..default() }); // Add alien commands.spawn(SceneBundle { transform: Transform { translation: Vec3::new( 0.0, 0.0, 100.0, ), rotation: Quat::from_rotation_y(-PI / 3.), scale: Vec3::splat(ASTRONAUT_SIZE), }, scene: asset_server.load(ASSET_ASTRONAUT), ..default() }); // Space is DARK ambient_light.brightness = 0.0; // Add Light commands.spawn(DirectionalLightBundle { directional_light: DirectionalLight { illuminance: 1000.0, shadows_enabled: false, ..default() }, transform: Transform::from_rotation(Quat::from_euler( EulerRot::ZYX, 0.0, PI / 1.0, -PI / 7.0, )), cascade_shadow_config: CascadeShadowConfigBuilder { first_cascade_far_bound: 7.0, maximum_distance: 25.0, ..default() } .into(), ..default() }); } pub fn load_cubemap_asset( mut cubemap: ResMut, asset_server: Res, ) { cubemap.image_handle = asset_server.load(ASSET_CUBEMAP); cubemap.is_loaded = false; } pub fn asset_loaded( asset_server: Res, mut images: ResMut>, mut cubemap: ResMut, mut skyboxes: Query<&mut Skybox>, ) { if !cubemap.is_loaded && asset_server.load_state(&cubemap.image_handle) == LoadState::Loaded { let image = images.get_mut(&cubemap.image_handle).unwrap(); if image.texture_descriptor.array_layer_count() == 1 { image.reinterpret_stacked_2d_as_array(image.height() / image.width()); image.texture_view_descriptor = Some(TextureViewDescriptor { dimension: Some(TextureViewDimension::Cube), ..default() }); } for mut skybox in &mut skyboxes { skybox.image = cubemap.image_handle.clone(); } cubemap.is_loaded = true; } }