//! A practical example of a spare ship on a planet, in a solar system, surrounded by stars. extern crate alloc; use alloc::collections::VecDeque; use bevy::core_pipeline::Skybox; use bevy::{ color::palettes, core_pipeline::bloom::Bloom, math::DVec3, pbr::{CascadeShadowConfigBuilder, NotShadowCaster}, prelude::*, render::camera::Exposure, transform::TransformSystem, }; use big_space::prelude::*; use big_space::validation::BigSpaceValidationPlugin; use turborand::{rng::Rng, TurboRand}; fn main() { App::new() .add_plugins(( DefaultPlugins.build().disable::(), BigSpaceDefaultPlugins .build() .enable::(), bevy_egui::EguiPlugin { enable_multipass_for_primary_context: true, }, bevy_inspector_egui::quick::WorldInspectorPlugin::default(), )) .insert_resource(AmbientLight { color: Color::WHITE, brightness: 200.0, ..Default::default() }) .add_systems(Startup, spawn_solar_system) .add_systems(Update, configure_skybox_image) .add_systems( PostUpdate, ( rotate, lighting .in_set(TransformSystem::TransformPropagate) .after(BigSpaceSystems::PropagateLowPrecision), cursor_grab_system, springy_ship .after(big_space::camera::default_camera_inputs) .before(big_space::camera::camera_controller), ), ) .register_type::() .register_type::() .run(); } const EARTH_ORBIT_RADIUS_M: f64 = 149.60e9; const EARTH_RADIUS_M: f64 = 6.371e6; const SUN_RADIUS_M: f64 = 695_508_000_f64; const MOON_RADIUS_M: f64 = 1.7375e6; #[derive(Component, Reflect)] struct Sun; #[derive(Component, Reflect)] struct PrimaryLight; #[derive(Component, Reflect)] struct Spaceship; #[derive(Component, Reflect)] struct Rotates(f32); fn rotate(mut rotate_query: Query<(&mut Transform, &Rotates)>) { for (mut transform, rotates) in rotate_query.iter_mut() { transform.rotate_local_y(rotates.0); } } fn lighting( mut light: Query<(&mut Transform, &mut GlobalTransform), With>, sun: Query<&GlobalTransform, (With, Without)>, ) -> Result { let sun_pos = sun.single()?.translation(); let (mut light_tr, mut light_gt) = light.single_mut()?; light_tr.look_at(-sun_pos, Vec3::Y); *light_gt = (*light_tr).into(); Ok(()) } fn springy_ship( cam_input: Res, mut ship: Query<&mut Transform, With>, mut desired_dir: Local<(Vec3, Quat)>, mut smoothed_rot: Local>, ) -> Result { desired_dir.0 = DVec3::new(cam_input.right, cam_input.up, -cam_input.forward).as_vec3() * (1.0 + cam_input.boost as u8 as f32); smoothed_rot.truncate(15); smoothed_rot.push_front(DVec3::new(cam_input.pitch, cam_input.yaw, cam_input.roll).as_vec3()); let avg_rot = smoothed_rot.iter().sum::() / smoothed_rot.len() as f32; use core::f32::consts::*; desired_dir.1 = Quat::IDENTITY.slerp( Quat::from_euler( EulerRot::XYZ, avg_rot.x.clamp(-FRAC_PI_4, FRAC_PI_4), avg_rot.y.clamp(-FRAC_PI_4, FRAC_PI_4), avg_rot.z.clamp(-FRAC_PI_4, FRAC_PI_4), ), 0.2, ) * Quat::from_rotation_y(PI); ship.single_mut()?.translation = ship .single_mut()? .translation .lerp(desired_dir.0 * Vec3::new(0.5, 0.5, -2.0), 0.02); ship.single_mut()?.rotation = ship.single_mut()?.rotation.slerp(desired_dir.1, 0.02); Ok(()) } fn spawn_solar_system( asset_server: Res, mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, ) { let sun_mesh_handle = meshes.add(Sphere::new(SUN_RADIUS_M as f32).mesh().ico(6).unwrap()); let earth_mesh_handle = meshes.add(Sphere::new(1.0).mesh().ico(35).unwrap()); let moon_mesh_handle = meshes.add(Sphere::new(MOON_RADIUS_M as f32).mesh().ico(15).unwrap()); let ball_mesh_handle = meshes.add(Sphere::new(5.0).mesh().ico(5).unwrap()); let plane_mesh_handle = meshes.add(Plane3d::new(Vec3::X, Vec2::splat(0.5))); commands.spawn(( PrimaryLight, DirectionalLight { color: Color::WHITE, illuminance: 120_000., shadows_enabled: true, ..default() }, CascadeShadowConfigBuilder { num_cascades: 4, minimum_distance: 0.1, maximum_distance: 10_000.0, first_cascade_far_bound: 100.0, overlap_proportion: 0.2, } .build(), )); commands.spawn_big_space_default(|root_grid| { root_grid.with_grid_default(|sun| { sun.insert((Sun, Name::new("Sun"))); sun.spawn_spatial(( Mesh3d(sun_mesh_handle), MeshMaterial3d(materials.add(StandardMaterial { base_color: Color::WHITE, emissive: LinearRgba::rgb(10000., 10000., 10000.), ..default() })), NotShadowCaster, )); let earth_pos = DVec3::Z * EARTH_ORBIT_RADIUS_M; let (earth_cell, earth_pos) = sun.grid().translation_to_grid(earth_pos); sun.with_grid_default(|earth| { earth.insert(( Name::new("Earth"), earth_cell, Mesh3d(earth_mesh_handle), MeshMaterial3d(materials.add(StandardMaterial { base_color: Color::Srgba(palettes::css::BLUE), perceptual_roughness: 0.8, reflectance: 1.0, ..default() })), Transform::from_translation(earth_pos) .with_scale(Vec3::splat(EARTH_RADIUS_M as f32)), Rotates(0.000001), )); let moon_orbit_radius_m = 385e6; let moon_pos = DVec3::NEG_Z * moon_orbit_radius_m; let (moon_cell, moon_pos) = earth.grid().translation_to_grid(moon_pos); earth.spawn_spatial(( Name::new("Moon"), Mesh3d(moon_mesh_handle), MeshMaterial3d(materials.add(StandardMaterial { base_color: Color::Srgba(palettes::css::GRAY), perceptual_roughness: 1.0, reflectance: 0.0, ..default() })), Transform::from_translation(moon_pos), moon_cell, )); let ball_pos = DVec3::X * (EARTH_RADIUS_M + 1.0) + DVec3::NEG_Z * 30.0 + DVec3::Y * 10.0; let (ball_cell, ball_pos) = earth.grid().translation_to_grid(ball_pos); earth .spawn_spatial((ball_cell, Transform::from_translation(ball_pos))) .with_children(|children| { children.spawn(( Mesh3d(ball_mesh_handle), MeshMaterial3d(materials.add(StandardMaterial { base_color: Color::WHITE, ..default() })), )); children.spawn(( Mesh3d(plane_mesh_handle), MeshMaterial3d(materials.add(StandardMaterial { base_color: Color::Srgba(palettes::css::DARK_GREEN), perceptual_roughness: 1.0, reflectance: 0.0, ..default() })), Transform::from_scale(Vec3::splat(100.0)) .with_translation(Vec3::X * -5.0), )); }); let cam_pos = DVec3::X * (EARTH_RADIUS_M + 1.0); let (cam_cell, cam_pos) = earth.grid().translation_to_grid(cam_pos); earth.with_grid_default(|floating_origin| { floating_origin.insert(( FloatingOrigin, Transform::from_translation(cam_pos).looking_to(Vec3::NEG_Z, Vec3::X), BigSpaceCameraController::default() // Built-in camera controller .with_speed_bounds([0.1, 10e35]) .with_smoothness(0.98, 0.98) .with_speed(1.0), cam_cell, )); floating_origin.spawn_spatial(( Camera3d::default(), Transform::from_xyz(0.0, 4.0, 22.0), Camera { hdr: true, clear_color: ClearColorConfig::None, ..default() }, Exposure::SUNLIGHT, Bloom::ANAMORPHIC, bevy::core_pipeline::post_process::ChromaticAberration { intensity: 0.01, ..Default::default() }, bevy::core_pipeline::motion_blur::MotionBlur { shutter_angle: 1.0, samples: 8, }, Msaa::Off, )); floating_origin.with_spatial(|ship_scene| { ship_scene.insert(( Spaceship, SceneRoot( asset_server.load("models/low_poly_spaceship/scene.gltf#Scene0"), ), Transform::from_rotation(Quat::from_rotation_y(core::f32::consts::PI)), )); }); }); }); }); let star_mat = materials.add(StandardMaterial { base_color: Color::WHITE, emissive: LinearRgba::rgb(2., 2., 2.), ..default() }); let star_mesh_handle = meshes.add(Sphere::new(1e10).mesh().ico(5).unwrap()); let rng = Rng::new(); (0..1000).for_each(|_| { root_grid.spawn_spatial(( Mesh3d(star_mesh_handle.clone()), MeshMaterial3d(star_mat.clone()), Transform::from_xyz( (rng.f32() - 0.5) * 1e14, (rng.f32() - 0.5) * 1e14, (rng.f32() - 0.5) * 1e14, ), )); }); }); } fn cursor_grab_system( mut windows: Query<&mut Window, With>, mut cam: ResMut, btn: Res>, key: Res>, mut triggered: Local, ) -> Result<()> { let mut window = windows.single_mut()?; if btn.just_pressed(MouseButton::Right) || !*triggered { window.cursor_options.grab_mode = bevy::window::CursorGrabMode::Locked; window.cursor_options.visible = false; // window.mode = WindowMode::BorderlessFullscreen; cam.defaults_disabled = false; *triggered = true; } if key.just_pressed(KeyCode::Escape) { window.cursor_options.grab_mode = bevy::window::CursorGrabMode::None; window.cursor_options.visible = true; // window.mode = WindowMode::Windowed; cam.defaults_disabled = true; } Ok(()) } #[derive(Resource)] struct Cubemap(Handle, bool); fn configure_skybox_image( mut commands: Commands, asset_server: Res, cubemap: Option>, cameras: Query>, mut images: ResMut>, ) { let mut cubemap = match cubemap { None => { commands.insert_resource(Cubemap(asset_server.load("images/cubemap.png"), false)); return; } Some(cubemap) => cubemap, }; if cubemap.1 { return; } if asset_server.load_state(&cubemap.0).is_loaded() { let image = images.get_mut(&cubemap.0).unwrap(); // NOTE: PNGs do not have any metadata that could indicate they contain a cubemap texture, // so they appear as one texture. The following code reconfigures the texture as necessary. if image.texture_descriptor.array_layer_count() == 1 { image.reinterpret_stacked_2d_as_array(image.height() / image.width()); image.texture_view_descriptor = Some(bevy_render::render_resource::TextureViewDescriptor { dimension: Some(bevy_render::render_resource::TextureViewDimension::Cube), ..default() }); } let camera = cameras.single().unwrap(); commands.entity(camera).insert(Skybox { image: cubemap.0.clone(), ..Default::default() }); cubemap.1 = true; } }