//! Demonstrates light textures, which modulate light sources. use std::f32::consts::{FRAC_PI_2, FRAC_PI_3, FRAC_PI_4, PI}; use std::fmt::{self, Formatter}; use bevy::{ color::palettes::css::{SILVER, YELLOW}, input::mouse::AccumulatedMouseMotion, pbr::{decal, DirectionalLightTexture, NotShadowCaster, PointLightTexture, SpotLightTexture}, prelude::*, render::renderer::{RenderAdapter, RenderDevice}, window::SystemCursorIcon, winit::cursor::CursorIcon, }; use light_consts::lux::{AMBIENT_DAYLIGHT, CLEAR_SUNRISE}; use ops::{acos, cos, sin}; use widgets::{ WidgetClickEvent, WidgetClickSender, BUTTON_BORDER, BUTTON_BORDER_COLOR, BUTTON_BORDER_RADIUS_SIZE, BUTTON_PADDING, }; #[path = "../helpers/widgets.rs"] mod widgets; /// The speed at which the cube rotates, in radians per second. const CUBE_ROTATION_SPEED: f32 = FRAC_PI_2; /// The speed at which the selection can be moved, in spherical coordinate /// radians per mouse unit. const MOVE_SPEED: f32 = 0.008; /// The speed at which the selection can be scaled, in reciprocal mouse units. const SCALE_SPEED: f32 = 0.05; /// The speed at which the selection can be scaled, in radians per mouse unit. const ROLL_SPEED: f32 = 0.01; /// Various settings for the demo. #[derive(Resource, Default)] struct AppStatus { /// The object that will be moved, scaled, or rotated when the mouse is /// dragged. selection: Selection, /// What happens when the mouse is dragged: one of a move, rotate, or scale /// operation. drag_mode: DragMode, } /// The object that will be moved, scaled, or rotated when the mouse is dragged. #[derive(Clone, Copy, Component, Default, PartialEq)] enum Selection { /// The camera. /// /// The camera can only be moved, not scaled or rotated. #[default] Camera, /// The spotlight, which uses a torch-like light texture SpotLight, /// The point light, which uses a light texture cubemap constructed from the faces mesh PointLight, /// The directional light, which uses a caustic-like texture DirectionalLight, } impl fmt::Display for Selection { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match *self { Selection::Camera => f.write_str("camera"), Selection::SpotLight => f.write_str("spotlight"), Selection::PointLight => f.write_str("point light"), Selection::DirectionalLight => f.write_str("directional light"), } } } /// What happens when the mouse is dragged: one of a move, rotate, or scale /// operation. #[derive(Clone, Copy, Component, Default, PartialEq, Debug)] enum DragMode { /// The mouse moves the current selection. #[default] Move, /// The mouse scales the current selection. /// /// This only applies to decals, not cameras. Scale, /// The mouse rotates the current selection around its local Z axis. /// /// This only applies to decals, not cameras. Roll, } impl fmt::Display for DragMode { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match *self { DragMode::Move => f.write_str("move"), DragMode::Scale => f.write_str("scale"), DragMode::Roll => f.write_str("roll"), } } } /// A marker component for the help text in the top left corner of the window. #[derive(Clone, Copy, Component)] struct HelpText; /// Entry point. fn main() { App::new() .add_plugins(DefaultPlugins.set(WindowPlugin { primary_window: Some(Window { title: "Bevy Light Textures Example".into(), ..default() }), ..default() })) .init_resource::() .add_event::>() .add_event::>() .add_systems(Startup, setup) .add_systems(Update, draw_gizmos) .add_systems(Update, rotate_cube) .add_systems(Update, hide_shadows) .add_systems(Update, widgets::handle_ui_interactions::) .add_systems(Update, widgets::handle_ui_interactions::) .add_systems( Update, (handle_selection_change, update_radio_buttons) .after(widgets::handle_ui_interactions::) .after(widgets::handle_ui_interactions::), ) .add_systems(Update, toggle_visibility) .add_systems(Update, update_directional_light) .add_systems(Update, process_move_input) .add_systems(Update, process_scale_input) .add_systems(Update, process_roll_input) .add_systems(Update, switch_drag_mode) .add_systems(Update, update_help_text) .add_systems(Update, update_button_visibility) .run(); } /// Creates the scene. fn setup( mut commands: Commands, asset_server: Res, app_status: Res, render_device: Res, render_adapter: Res, mut meshes: ResMut>, mut materials: ResMut>, ) { // Error out if clustered decals (and so light textures) aren't supported on the current platform. if !decal::clustered::clustered_decals_are_usable(&render_device, &render_adapter) { error!("Light textures aren't usable on this platform."); commands.write_event(AppExit::error()); } spawn_cubes(&mut commands, &mut meshes, &mut materials); spawn_camera(&mut commands); spawn_light(&mut commands, &asset_server); spawn_buttons(&mut commands); spawn_help_text(&mut commands, &app_status); spawn_light_textures(&mut commands, &asset_server, &mut meshes, &mut materials); } #[derive(Component)] struct Rotate; /// Spawns the cube onto which the decals are projected. fn spawn_cubes( commands: &mut Commands, meshes: &mut Assets, materials: &mut Assets, ) { // Rotate the cube a bit just to make it more interesting. let mut transform = Transform::IDENTITY; transform.rotate_y(FRAC_PI_3); commands.spawn(( Mesh3d(meshes.add(Cuboid::new(3.0, 3.0, 3.0))), MeshMaterial3d(materials.add(StandardMaterial { base_color: SILVER.into(), ..default() })), transform, Rotate, )); commands.spawn(( Mesh3d(meshes.add(Cuboid::new(-13.0, -13.0, -13.0))), MeshMaterial3d(materials.add(StandardMaterial { base_color: SILVER.into(), ..default() })), transform, )); } /// Spawns the directional light. fn spawn_light(commands: &mut Commands, asset_server: &AssetServer) { commands .spawn(( Visibility::Hidden, Transform::from_xyz(8.0, 8.0, 4.0).looking_at(Vec3::ZERO, Vec3::Y), Selection::DirectionalLight, )) .with_child(( DirectionalLight { illuminance: AMBIENT_DAYLIGHT, ..default() }, DirectionalLightTexture { image: asset_server.load("lightmaps/caustic_directional_texture.png"), tiled: true, }, Visibility::Visible, )); } /// Spawns the camera. fn spawn_camera(commands: &mut Commands) { commands .spawn(Camera3d::default()) .insert(Transform::from_xyz(0.0, 2.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y)) // Tag the camera with `Selection::Camera`. .insert(Selection::Camera); } fn spawn_light_textures( commands: &mut Commands, asset_server: &AssetServer, meshes: &mut Assets, materials: &mut Assets, ) { commands.spawn(( SpotLight { color: Color::srgb(1.0, 1.0, 0.8), intensity: 10e6, outer_angle: 0.25, inner_angle: 0.25, shadows_enabled: true, ..default() }, Transform::from_translation(Vec3::new(6.0, 1.0, 2.0)).looking_at(Vec3::ZERO, Vec3::Y), SpotLightTexture { image: asset_server.load("lightmaps/torch_spotlight_texture.png"), }, Visibility::Inherited, Selection::SpotLight, )); commands .spawn(( Visibility::Hidden, Transform::from_translation(Vec3::new(0.0, 1.8, 0.01)).with_scale(Vec3::splat(0.1)), Selection::PointLight, )) .with_children(|parent| { parent.spawn(SceneRoot( asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/Faces/faces.glb")), )); parent.spawn(( Mesh3d(meshes.add(Sphere::new(1.0))), MeshMaterial3d(materials.add(StandardMaterial { emissive: Color::srgb(0.0, 0.0, 300.0).to_linear(), ..default() })), )); parent.spawn(( PointLight { color: Color::srgb(0.0, 0.0, 1.0), intensity: 1e6, shadows_enabled: true, ..default() }, PointLightTexture { image: asset_server.load("lightmaps/faces_pointlight_texture_blurred.png"), cubemap_layout: decal::clustered::CubemapLayout::CrossVertical, }, )); }); } /// Spawns the buttons at the bottom of the screen. fn spawn_buttons(commands: &mut Commands) { // Spawn the radio buttons that allow the user to select an object to // control. commands .spawn(widgets::main_ui_node()) .with_children(|parent| { widgets::spawn_option_buttons( parent, "Drag to Move", &[ (Selection::Camera, "Camera"), (Selection::SpotLight, "Spotlight"), (Selection::PointLight, "Point Light"), (Selection::DirectionalLight, "Directional Light"), ], ); }); // Spawn the drag buttons that allow the user to control the scale and roll // of the selected object. commands .spawn(Node { flex_direction: FlexDirection::Row, position_type: PositionType::Absolute, right: Val::Px(10.0), bottom: Val::Px(10.0), column_gap: Val::Px(6.0), ..default() }) .with_children(|parent| { widgets::spawn_option_buttons( parent, "", &[ (Visibility::Inherited, "Show"), (Visibility::Hidden, "Hide"), ], ); spawn_drag_button(parent, "Scale").insert(DragMode::Scale); spawn_drag_button(parent, "Roll").insert(DragMode::Roll); }); } /// Spawns a button that the user can drag to change a parameter. fn spawn_drag_button<'a>( commands: &'a mut ChildSpawnerCommands, label: &str, ) -> EntityCommands<'a> { let mut kid = commands.spawn(Node { border: BUTTON_BORDER, justify_content: JustifyContent::Center, align_items: AlignItems::Center, padding: BUTTON_PADDING, ..default() }); kid.insert(( Button, BackgroundColor(Color::BLACK), BorderRadius::all(BUTTON_BORDER_RADIUS_SIZE), BUTTON_BORDER_COLOR, )) .with_children(|parent| { widgets::spawn_ui_text(parent, label, Color::WHITE); }); kid } /// Spawns the help text at the top of the screen. fn spawn_help_text(commands: &mut Commands, app_status: &AppStatus) { commands.spawn(( Text::new(create_help_string(app_status)), Node { position_type: PositionType::Absolute, top: Val::Px(12.0), left: Val::Px(12.0), ..default() }, HelpText, )); } /// Draws the outlines that show the bounds of the spotlight. fn draw_gizmos(mut gizmos: Gizmos, spotlight: Query<(&GlobalTransform, &SpotLight, &Visibility)>) { if let Ok((global_transform, spotlight, visibility)) = spotlight.single() { if visibility != Visibility::Hidden { gizmos.primitive_3d( &Cone::new(7.0 * spotlight.outer_angle, 7.0), Isometry3d { rotation: global_transform.rotation() * Quat::from_rotation_x(FRAC_PI_2), translation: global_transform.translation_vec3a() * 0.5, }, YELLOW, ); } } } /// Rotates the cube a bit every frame. fn rotate_cube(mut meshes: Query<&mut Transform, With>, time: Res