From 4bae6810f27378995604f419f0358dac2cc7ed18 Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Tue, 2 Jul 2024 13:59:57 -0700 Subject: [PATCH] Add emmissive example. --- bevy_nannou/src/lib.rs | 1 + bevy_nannou_draw/src/draw/background.rs | 4 +- bevy_nannou_draw/src/draw/drawing.rs | 8 +- bevy_nannou_draw/src/draw/properties/color.rs | 4 +- bevy_nannou_draw/src/render.rs | 20 +- examples/Cargo.toml | 3 + examples/draw/draw.rs | 35 ++- examples/draw/draw_material_bloom.rs | 58 ++++ generative_design/color/p_1_0_01.rs | 2 +- nannou/src/app.rs | 11 +- nannou/src/camera.rs | 277 ++++++++++++++++++ nannou/src/lib.rs | 2 + nannou/src/light.rs | 1 + nannou/src/prelude.rs | 2 + nannou/src/window.rs | 99 +++---- 15 files changed, 450 insertions(+), 77 deletions(-) create mode 100644 examples/draw/draw_material_bloom.rs create mode 100644 nannou/src/camera.rs create mode 100644 nannou/src/light.rs diff --git a/bevy_nannou/src/lib.rs b/bevy_nannou/src/lib.rs index cf2085468..acf7d9ec1 100644 --- a/bevy_nannou/src/lib.rs +++ b/bevy_nannou/src/lib.rs @@ -8,6 +8,7 @@ pub mod prelude { pub use bevy::render::render_asset::*; pub use bevy::render::render_resource::*; pub use bevy::winit::UpdateMode; + pub use bevy::core_pipeline::bloom::*; pub use bevy_nannou_draw::color::*; pub use bevy_nannou_draw::draw::*; diff --git a/bevy_nannou_draw/src/draw/background.rs b/bevy_nannou_draw/src/draw/background.rs index f93010bfc..fe0b2ab3c 100644 --- a/bevy_nannou_draw/src/draw/background.rs +++ b/bevy_nannou_draw/src/draw/background.rs @@ -157,11 +157,11 @@ where self.color(Color::oklcha(l, c, h, alpha)) } - pub fn xyz(self, x: f32, y: f32, z: f32) -> Self { + pub fn cie_xyz(self, x: f32, y: f32, z: f32) -> Self { self.color(Color::xyz(x, y, z)) } - pub fn xyza(self, x: f32, y: f32, z: f32, w: f32) -> Self { + pub fn cie_xyza(self, x: f32, y: f32, z: f32, w: f32) -> Self { self.color(Color::xyza(x, y, z, w)) } } diff --git a/bevy_nannou_draw/src/draw/drawing.rs b/bevy_nannou_draw/src/draw/drawing.rs index 185ccdd42..ba080a7ab 100644 --- a/bevy_nannou_draw/src/draw/drawing.rs +++ b/bevy_nannou_draw/src/draw/drawing.rs @@ -478,12 +478,12 @@ where self.map_ty(|ty| SetColor::oklcha(ty, l, c, h, alpha)) } - pub fn xyz(self, x: f32, y: f32, z: f32) -> Self { - self.map_ty(|ty| SetColor::xyz(ty, x, y, z)) + pub fn cie_xyz(self, x: f32, y: f32, z: f32) -> Self { + self.map_ty(|ty| SetColor::cie_xyz(ty, x, y, z)) } - pub fn xyza(self, x: f32, y: f32, z: f32, alpha: f32) -> Self { - self.map_ty(|ty| SetColor::xyza(ty, x, y, z, alpha)) + pub fn cie_xyza(self, x: f32, y: f32, z: f32, alpha: f32) -> Self { + self.map_ty(|ty| SetColor::cie_xyza(ty, x, y, z, alpha)) } /// Specify the color as gray scale diff --git a/bevy_nannou_draw/src/draw/properties/color.rs b/bevy_nannou_draw/src/draw/properties/color.rs index d1c80ce57..d84ce2c48 100644 --- a/bevy_nannou_draw/src/draw/properties/color.rs +++ b/bevy_nannou_draw/src/draw/properties/color.rs @@ -144,11 +144,11 @@ pub trait SetColor: Sized { self.color(Color::oklcha(l, c, h, alpha)) } - fn xyz(self, x: f32, y: f32, z: f32) -> Self { + fn cie_xyz(self, x: f32, y: f32, z: f32) -> Self { self.color(Color::xyz(x, y, z)) } - fn xyza(self, x: f32, y: f32, z: f32, alpha: f32) -> Self { + fn cie_xyza(self, x: f32, y: f32, z: f32, alpha: f32) -> Self { self.color(Color::xyza(x, y, z, alpha)) } diff --git a/bevy_nannou_draw/src/render.rs b/bevy_nannou_draw/src/render.rs index cb4aa8fa1..b83f6331a 100644 --- a/bevy_nannou_draw/src/render.rs +++ b/bevy_nannou_draw/src/render.rs @@ -289,18 +289,18 @@ fn update_draw_mesh( mut meshes: ResMut>, ) { for draw in draw_q.iter() { - let (mut window_camera, window_layers) = cameras_q - .iter_mut() - .find(|(camera, _)| { - if let RenderTarget::Window(WindowRef::Entity(window)) = camera.target { - if window == draw.window { - return true; - } + let Some((mut window_camera, window_layers)) = cameras_q.iter_mut().find(|(camera, _)| { + if let RenderTarget::Window(WindowRef::Entity(window)) = camera.target { + if window == draw.window { + return true; } + } - false - }) - .expect("No camera found for window"); + false + }) else { + warn!("No camera found for window {:?}", draw.window); + continue; + }; // Reset the clear color each frame. window_camera.clear_color = ClearColorConfig::None; diff --git a/examples/Cargo.toml b/examples/Cargo.toml index e5076dcf8..05fdaa3ae 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -119,6 +119,9 @@ path = "draw/draw_textured_polygon.rs" [[example]] name = "draw_transform" path = "draw/draw_transform.rs" +[[example]] +name = "draw_material_bloom" +path = "draw/draw_material_bloom.rs" # Laser [[example]] diff --git a/examples/draw/draw.rs b/examples/draw/draw.rs index 1a03f910c..079e7b478 100644 --- a/examples/draw/draw.rs +++ b/examples/draw/draw.rs @@ -8,5 +8,38 @@ fn view(app: &App) { // Begin drawing let draw = app.draw(); - draw.tri().width(100.0).color(RED); + // Clear the background to blue. + draw.background().color(CORNFLOWER_BLUE); + + // Draw a purple triangle in the top left half of the window. + let win = app.window_rect(); + draw.tri() + .points(win.bottom_left(), win.top_left(), win.top_right()) + .color(VIOLET); + + // Draw an ellipse to follow the mouse. + let t = app.elapsed_seconds(); + draw.ellipse() + .x_y(app.mouse().x * t.cos(), app.mouse().y) + .radius(win.w() * 0.125 * t.sin()) + .color(RED); + + // Draw a line! + draw.line() + .weight(10.0 + (t.sin() * 0.5 + 0.5) * 90.0) + .caps_round() + .color(PALE_GOLDENROD) + .points(win.top_left() * t.sin(), win.bottom_right() * t.cos()); + + // Draw a quad that follows the inverse of the ellipse. + draw.quad() + .x_y(-app.mouse().x, app.mouse().y) + .color(DARK_GREEN) + .rotate(t); + + // Draw a rect that follows a different inverse of the ellipse. + draw.rect() + .x_y(app.mouse().y, app.mouse().x) + .w(app.mouse().x * 0.25) + .hsv(t, 1.0, 1.0); } diff --git a/examples/draw/draw_material_bloom.rs b/examples/draw/draw_material_bloom.rs new file mode 100644 index 000000000..eef3c735e --- /dev/null +++ b/examples/draw/draw_material_bloom.rs @@ -0,0 +1,58 @@ +use nannou::prelude::*; + +fn main() { + nannou::app(model).update(update).run(); +} + +struct Model { + window: Entity, + camera: Entity, +} + +fn model(app: &App) -> Model { + let camera = app + .new_camera() + // HDR is required for bloom to work. + .hdr(true) + // Pick a default bloom settings. This also can be configured manually. + .bloom_settings(BloomSettings::OLD_SCHOOL) + .build(); + + let window = app + .new_window() + .camera(camera) + .size_pixels(1024, 1024) + .view(view) + .build(); + + Model { camera, window } +} + +fn update(app: &App, model: &mut Model) { + let camera = app.camera(model.camera); + let window_rect = app.window_rect(); + let norm_mouse_y = (app.mouse().y / window_rect.w()) + 0.5; + + camera.bloom_intensity(norm_mouse_y.clamp(0.0, 0.8)); +} + +fn view(app: &App, model: &Model) { + // Begin drawing + let draw = app.draw(); + let window_rect = app.window_rect(); + let norm_mouse_x = (app.mouse().x / window_rect.w()) + 0.5; + + // Use the normalized mouse coordinate to create an initial color. + let color_hsl = Color::hsl((1.0 - norm_mouse_x) * 360.0, 1.0, 0.5); + + // Convert the color to linear RGBA and multiply (for emissives, values outside of 1.0 are used). + let mut color_linear_rgb: LinearRgba = color_hsl.into(); + color_linear_rgb = color_linear_rgb * 5.0; + + let t = app.elapsed_seconds(); + + draw.tri() + .width(100.0) + .emissive(color_linear_rgb.into()) + .color(WHITE); +} diff --git a/generative_design/color/p_1_0_01.rs b/generative_design/color/p_1_0_01.rs index a335aa649..c8e1dc463 100644 --- a/generative_design/color/p_1_0_01.rs +++ b/generative_design/color/p_1_0_01.rs @@ -38,7 +38,7 @@ fn view(app: &App) { // Prepare to draw. let draw = app.draw(); - let norm_mouse_y = (app.mouse().x / app.window_rect().h()) + 0.5; + let norm_mouse_y = (app.mouse().y / app.window_rect().h()) + 0.5; draw.background().hsl(norm_mouse_y, 1.0, 0.5); draw.rect() diff --git a/nannou/src/app.rs b/nannou/src/app.rs index 49afcd4d2..dc9f3f366 100644 --- a/nannou/src/app.rs +++ b/nannou/src/app.rs @@ -49,7 +49,7 @@ use crate::prelude::bevy_ecs::system::SystemState; use crate::prelude::render::{NannouMesh, NannouPersistentMesh}; use crate::prelude::NannouMaterialPlugin; use crate::window::WindowUserFunctions; -use crate::{geom, window}; +use crate::{camera, geom, window}; /// The user function type for initialising their model. pub type ModelFn = fn(&App) -> Model; @@ -654,6 +654,11 @@ impl<'w> App<'w> { todo!() } + /// Begin building a new camera. + pub fn new_camera<'a>(&'a self) -> camera::Builder<'a, 'w> { + camera::Builder::new(self) + } + /// Begin building a new window. pub fn new_window<'a, M>(&'a self) -> window::Builder<'a, 'w, M> where @@ -683,6 +688,10 @@ impl<'w> App<'w> { window::Window::new(self, id) } + pub fn camera<'a>(&'a self, id: Entity) -> camera::Camera<'a, 'w> { + camera::Camera::new(self, id) + } + /// Return the [Entity] of the currently focused window. /// /// **Panics** if there are no windows or if no window is in focus. diff --git a/nannou/src/camera.rs b/nannou/src/camera.rs new file mode 100644 index 000000000..453d9d383 --- /dev/null +++ b/nannou/src/camera.rs @@ -0,0 +1,277 @@ +use crate::prelude::bevy_render::camera::RenderTarget; +use crate::prelude::Camera3dBundle; +use crate::App; +use bevy::core_pipeline::bloom::BloomSettings; +use bevy::core_pipeline::tonemapping::Tonemapping; +use bevy::math::UVec2; +use bevy::prelude::{Camera3d, Projection, Transform}; +use bevy::render::camera; +use bevy::render::view::RenderLayers; +use bevy::window::WindowRef; +use bevy_nannou::prelude::{default, ClearColorConfig, Entity, OrthographicProjection, Vec3}; +use bevy_nannou::prelude::render::NannouCamera; + +pub struct Camera<'a, 'w> { + entity: Entity, + app: &'a App<'w>, +} + +pub struct Builder<'a, 'w> { + app: &'a App<'w>, + camera: Camera3dBundle, + bloom_settings: Option, + layer: Option, +} + +pub trait SetCamera: Sized { + fn layer(mut self, layer: RenderLayers) -> Self { + self.map_layer(|_| layer) + } + + fn order(mut self, order: isize) -> Self { + self.map_camera(|mut camera| { + camera.camera.order = order; + camera + }) + } + + fn translation(mut self, x: f32, y: f32) -> Self { + self.map_camera(|mut camera| { + camera.transform.translation = bevy::math::Vec3::new(x, y, 10.0); + camera + }) + } + + fn hdr(mut self, hdr: bool) -> Self { + self.map_camera(|mut camera| { + camera.camera.hdr = hdr; + camera + }) + } + + fn viewport(mut self, position: UVec2, size: UVec2) -> Self { + self.map_camera(|mut camera| { + camera.camera.viewport = Some(camera::Viewport { + physical_position: position, + physical_size: size, + depth: Default::default(), + }); + camera + }) + } + + fn window(mut self, window: Entity) -> Self { + self.map_camera(|mut camera| { + camera.camera.target = RenderTarget::Window(WindowRef::Entity(window)); + camera + }) + } + + fn tonemapping(mut self, tonemapping: Tonemapping) -> Self { + self.map_camera(|mut camera| { + camera.tonemapping = tonemapping; + camera + }) + } + + fn clear_color(mut self, color: ClearColorConfig) -> Self { + self.map_camera(|mut camera| { + camera.camera.clear_color = color; + camera + }) + } + + fn bloom_settings(mut self, settings: BloomSettings) -> Self { + self.map_bloom_settings(|_| settings) + } + + fn bloom_intensity(mut self, intensity: f32) -> Self { + self.map_bloom_settings(|mut settings| { + settings.intensity = intensity; + settings + }) + } + + fn bloom_low_frequency_boost(mut self, low_frequency_boost: f32) -> Self { + self.map_bloom_settings(|mut settings| { + settings.low_frequency_boost = low_frequency_boost; + settings + }) + } + + fn bloom_low_frequency_boost_curvature(mut self, low_frequency_boost_curvature: f32) -> Self { + self.map_bloom_settings(|mut settings| { + settings.low_frequency_boost_curvature = low_frequency_boost_curvature; + settings + }) + } + + fn bloom_high_pass_frequency(mut self, high_pass_frequency: f32) -> Self { + self.map_bloom_settings(|mut settings| { + settings.high_pass_frequency = high_pass_frequency; + settings + }) + } + + fn bloom_prefilter_settings(mut self, threshold: f32, threshold_softness: f32) -> Self { + self.map_bloom_settings(|mut settings| { + settings.prefilter_settings = bevy::core_pipeline::bloom::BloomPrefilterSettings { + threshold, + threshold_softness, + }; + settings + }) + } + + fn bloom_composite_mode( + mut self, + composite_mode: bevy::core_pipeline::bloom::BloomCompositeMode, + ) -> Self { + self.map_bloom_settings(|mut settings| { + settings.composite_mode = composite_mode; + settings + }) + } + + fn map_layer(self, f: F) -> Self + where + F: FnOnce(RenderLayers) -> RenderLayers; + + fn map_bloom_settings(self, f: F) -> Self + where + F: FnOnce(BloomSettings) -> BloomSettings; + + fn map_camera(self, f: F) -> Self + where + F: FnOnce(Camera3dBundle) -> Camera3dBundle; +} + +impl<'a, 'w> Builder<'a, 'w> { + pub fn new(app: &'a App<'w>) -> Self { + Self { + app, + camera: Camera3dBundle { + transform: Transform::from_xyz(0.0, 0.0, 10.0).looking_at(Vec3::ZERO, Vec3::Y), + projection: OrthographicProjection::default().into(), + ..default() + }, + bloom_settings: None, + layer: None, + } + } + + pub fn build(self) -> Entity { + let entity = self.app.world_mut().spawn((self.camera, NannouCamera)).id(); + if let Some(layer) = self.layer { + self.app.world_mut().entity_mut(entity).insert(layer); + } else { + self.app.world_mut().entity_mut(entity).insert(RenderLayers::default()); + } + entity + } +} + +impl<'a, 'w> SetCamera for Builder<'a, 'w> { + fn map_layer(self, f: F) -> Self + where + F: FnOnce(RenderLayers) -> RenderLayers, + { + Self { + layer: Some(f(self.layer.unwrap_or(RenderLayers::default()))), + ..self + } + } + + fn map_bloom_settings(self, f: F) -> Self + where + F: FnOnce(BloomSettings) -> BloomSettings, + { + Self { + bloom_settings: Some(f(self.bloom_settings.unwrap_or(BloomSettings::default()))), + ..self + } + } + + fn map_camera(self, f: F) -> Self + where + F: FnOnce(Camera3dBundle) -> Camera3dBundle, + { + Self { + camera: f(self.camera), + ..self + } + } +} + +impl<'a, 'w> Camera<'a, 'w> { + pub fn new(app: &'a App<'w>, entity: Entity) -> Self { + Self { + entity, + app, + } + } +} + +impl<'a, 'w> SetCamera for Camera<'a, 'w> { + fn map_layer(self, f: F) -> Self + where + F: FnOnce(RenderLayers) -> RenderLayers, + { + let world = self.app.world_mut(); + let mut layer_q = world.query::>(); + if let Ok(mut layer) = layer_q.get_mut(world, self.entity) { + if let Some(mut layer) = layer { + *layer = f(layer.clone()); + } else { + world + .entity_mut(self.entity) + .insert(f(RenderLayers::default())); + } + } + self + } + + fn map_bloom_settings(self, f: F) -> Self + where + F: FnOnce(BloomSettings) -> BloomSettings, + { + let world = self.app.world_mut(); + let mut bloom_q = world.query::>(); + if let Ok(mut bloom) = bloom_q.get_mut(world, self.entity) { + if let Some(mut bloom) = bloom { + *bloom = f(bloom.clone()); + } else { + world + .entity_mut(self.entity) + .insert(f(BloomSettings::default())); + } + } + self + } + + fn map_camera(self, f: F) -> Self + where + F: FnOnce(Camera3dBundle) -> Camera3dBundle, + { + let world = self.app.world_mut(); + let mut camera_q = world.query::<( + &mut Transform, + &mut camera::Camera, + &mut Camera3d, + &mut Projection, + )>(); + let (transform, camera, camera_3d, projection) = + camera_q.get_mut(world, self.entity).unwrap(); + let bundle = Camera3dBundle { + transform: transform.clone(), + camera: camera.clone(), + camera_3d: camera_3d.clone(), + projection: projection.clone(), + ..default() + }; + + let bundle = f(bundle); + world.entity_mut(self.entity).insert(bundle); + self + } +} diff --git a/nannou/src/lib.rs b/nannou/src/lib.rs index fb4703671..c8efcdab9 100644 --- a/nannou/src/lib.rs +++ b/nannou/src/lib.rs @@ -23,9 +23,11 @@ pub use nannou_core::{glam, math, rand}; pub use self::app::App; pub mod app; +mod camera; pub mod geom; pub mod image; pub mod io; +mod light; pub mod noise; pub mod prelude; pub mod time; diff --git a/nannou/src/light.rs b/nannou/src/light.rs new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/nannou/src/light.rs @@ -0,0 +1 @@ + diff --git a/nannou/src/prelude.rs b/nannou/src/prelude.rs index eebdd60ad..7c01af275 100644 --- a/nannou/src/prelude.rs +++ b/nannou/src/prelude.rs @@ -10,6 +10,8 @@ pub use bevy_egui::egui; pub use bevy_nannou::prelude::*; pub use nannou_core::prelude::*; + pub use crate::app::{self, App, RunMode, UpdateModeExt}; +pub use crate::camera::SetCamera; pub use crate::io::{load_from_json, load_from_toml, safe_file_save, save_to_json, save_to_toml}; pub use crate::time::DurationF64; diff --git a/nannou/src/window.rs b/nannou/src/window.rs index 32f33620e..c300d9af8 100644 --- a/nannou/src/window.rs +++ b/nannou/src/window.rs @@ -6,8 +6,6 @@ use std::fmt; use std::path::{Path, PathBuf}; -use bevy::core_pipeline::bloom::BloomSettings; -use bevy::core_pipeline::tonemapping::Tonemapping; use bevy::input::mouse::MouseWheel; use bevy::prelude::*; use bevy::render::camera::RenderTarget; @@ -37,6 +35,7 @@ pub struct Window<'a, 'w> { pub struct Builder<'a, 'w, M = ()> { app: &'a App<'w>, window: bevy::window::Window, + camera: Option, primary: bool, title_was_set: bool, user_functions: UserFunctions, @@ -236,6 +235,7 @@ where Builder { app, window: bevy::window::Window::default(), + camera: None, primary: false, title_was_set: false, user_functions: UserFunctions::::default(), @@ -419,45 +419,39 @@ where .insert(PrimaryWindow); } - self.app.world_mut().spawn(( - Camera3dBundle { - camera: Camera { - hdr: self.hdr, - target: RenderTarget::Window(WindowRef::Entity(entity)), - clear_color: self - .clear_color - .map(ClearColorConfig::Custom) - .unwrap_or(ClearColorConfig::None), - ..default() - }, - transform: Transform::from_xyz(0.0, 0.0, 10.0).looking_at(Vec3::ZERO, Vec3::Y), - projection: OrthographicProjection::default().into(), - ..default() - }, - RenderLayers::layer(self.app.window_count() * 2usize), - NannouCamera, - )); - - self.app.world_mut().spawn(( - Camera3dBundle { - camera: Camera { - hdr: self.hdr, - target: RenderTarget::Window(WindowRef::Entity(entity)), - clear_color: ClearColorConfig::None, // We render on top of the previous camera - order: 2, + if let Some(camera) = self.camera { + // Update the camera's render target to be the window. + let mut q = self.app.world_mut().query::<(&mut Camera, Option<&mut RenderLayers>)>(); + if let Ok((mut camera, layers)) = q.get_mut(self.app.world_mut(), camera) { + camera.target = RenderTarget::Window(WindowRef::Entity(entity)); + if let None = layers { + self.app + .world_mut() + .entity_mut(self.camera.unwrap()) + .insert(RenderLayers::layer(self.app.window_count() * 2usize)); + } + } + } else { + info!("No camera provided for window, creating a default camera"); + self.app.world_mut().spawn(( + Camera3dBundle { + camera: Camera { + hdr: self.hdr, + target: RenderTarget::Window(WindowRef::Entity(entity)), + clear_color: self + .clear_color + .map(ClearColorConfig::Custom) + .unwrap_or(ClearColorConfig::None), + ..default() + }, + transform: Transform::from_xyz(0.0, 0.0, 10.0).looking_at(Vec3::ZERO, Vec3::Y), + projection: OrthographicProjection::default().into(), ..default() }, - tonemapping: Tonemapping::TonyMcMapface, - transform: Transform::from_xyz(0.0, 0.0, 10.0).looking_at(Vec3::ZERO, Vec3::Y), - projection: OrthographicProjection::default().into(), - ..default() - }, - BloomSettings::OLD_SCHOOL, - // The emissive layer is N*2+1, this helps to ensure that bloom effect is only - // applied to meshes that are rendered on the emissive layer. - RenderLayers::layer(self.app.window_count() * 2usize + 1), - NannouCamera, - )); + RenderLayers::layer(self.app.window_count() * 2usize), + NannouCamera, + )); + } entity } @@ -466,27 +460,20 @@ where where F: FnOnce(bevy::window::Window) -> bevy::window::Window, { - let Builder { - app, - window, - primary, - title_was_set, - user_functions, - clear_color, - hdr, - } = self; - let window = map(window); Builder { - app, - window, - primary, - title_was_set, - user_functions, - clear_color, - hdr, + window: map(self.window), + ..self } } + /// The camera to use for rendering to this window. Setting the camera will override the + /// default camera that would be created for this window. The camera's render target + /// will be set to the window when the window is built. + pub fn camera(mut self, camera: Entity) -> Self { + self.camera = Some(camera); + self + } + /// Requests the window to be a specific size in points. /// /// This describes to the "inner" part of the window, not including desktop decorations like the