From 7bcb661e6a7715cccf912f7afdcf70cc1fc7f8b5 Mon Sep 17 00:00:00 2001 From: annieversary Date: Sun, 7 Aug 2022 17:45:42 +0100 Subject: [PATCH 01/10] Add bevy_color_blindness as a top level crate --- Cargo.toml | 6 + crates/bevy_color_blindness/Cargo.toml | 25 ++ .../src/color_blindness.wgsl | 34 ++ crates/bevy_color_blindness/src/lib.rs | 399 ++++++++++++++++++ crates/bevy_internal/Cargo.toml | 1 + crates/bevy_internal/src/lib.rs | 8 + crates/bevy_internal/src/prelude.rs | 4 + examples/color_blindness_simulation.rs | 90 ++++ 8 files changed, 567 insertions(+) create mode 100644 crates/bevy_color_blindness/Cargo.toml create mode 100644 crates/bevy_color_blindness/src/color_blindness.wgsl create mode 100644 crates/bevy_color_blindness/src/lib.rs create mode 100644 examples/color_blindness_simulation.rs diff --git a/Cargo.toml b/Cargo.toml index b0920d9ecd0fa..b3165fad013ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ default = [ "animation", "bevy_asset", "bevy_audio", + "bevy_color_blindness", "bevy_gilrs", "bevy_scene", "bevy_winit", @@ -57,6 +58,7 @@ render = [ bevy_animation = ["bevy_internal/bevy_animation"] bevy_asset = ["bevy_internal/bevy_asset"] bevy_audio = ["bevy_internal/bevy_audio"] +bevy_color_blindness = ["bevy_internal/bevy_color_blindness"] bevy_core_pipeline = ["bevy_internal/bevy_core_pipeline"] bevy_dynamic_plugin = ["bevy_internal/bevy_dynamic_plugin"] bevy_gilrs = ["bevy_internal/bevy_gilrs"] @@ -141,6 +143,10 @@ path = "examples/hello_world.rs" [package.metadata.example.hello_world] hidden = true +[[example]] +name = "color_blindness_simulation" +path = "examples/color_blindness_simulation.rs" + # 2D Rendering [[example]] name = "move_sprite" diff --git a/crates/bevy_color_blindness/Cargo.toml b/crates/bevy_color_blindness/Cargo.toml new file mode 100644 index 0000000000000..5eac24a9fc17e --- /dev/null +++ b/crates/bevy_color_blindness/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "bevy_color_blindness" +version = "0.9.0-dev" +edition = "2021" +description = "Provides color blindness simulation for Bevy Engine" +homepage = "https://bevyengine.org" +repository = "https://github.com/bevyengine/bevy" +license = "MIT OR Apache-2.0" +keywords = ["bevy"] + +[dependencies] +# bevy +bevy_app = { path = "../bevy_app", version = "0.9.0-dev" } +bevy_asset = { path = "../bevy_asset", version = "0.9.0-dev" } +bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.9.0-dev" } +bevy_ecs = { path = "../bevy_ecs", version = "0.9.0-dev" } +bevy_math = { path = "../bevy_math", version = "0.9.0-dev" } +bevy_reflect = { path = "../bevy_reflect", version = "0.9.0-dev", features = [ + "bevy", +] } +bevy_render = { path = "../bevy_render", version = "0.9.0-dev" } +bevy_sprite = { path = "../bevy_sprite", version = "0.9.0-dev" } +bevy_transform = { path = "../bevy_transform", version = "0.9.0-dev" } +bevy_ui = { path = "../bevy_ui", version = "0.9.0-dev" } +bevy_window = { path = "../bevy_window", version = "0.9.0-dev" } diff --git a/crates/bevy_color_blindness/src/color_blindness.wgsl b/crates/bevy_color_blindness/src/color_blindness.wgsl new file mode 100644 index 0000000000000..73c3074b4b6cf --- /dev/null +++ b/crates/bevy_color_blindness/src/color_blindness.wgsl @@ -0,0 +1,34 @@ +#import bevy_pbr::mesh_view_bindings + +struct Percentages { + red: vec3, + green: vec3, + blue: vec3, +}; + +@group(1) @binding(0) +var texture: texture_2d; + +@group(1) @binding(1) +var our_sampler: sampler; + +@group(1) @binding(2) +var p: Percentages; + +@fragment +fn fragment( + @builtin(position) position: vec4, + #import bevy_sprite::mesh2d_vertex_output +) -> @location(0) vec4 { + // Get screen position with coordinates from 0 to 1 + let uv = position.xy / vec2(view.width, view.height); + + var c = textureSample(texture, our_sampler, uv); + + return vec4( + c.r * p.red.x + c.g * p.red.y + c.b * p.red.z, + c.r * p.green.x + c.g * p.green.y + c.b * p.green.z, + c.r * p.blue.x + c.g * p.blue.y + c.b * p.blue.z, + c.a + ); +} diff --git a/crates/bevy_color_blindness/src/lib.rs b/crates/bevy_color_blindness/src/lib.rs new file mode 100644 index 0000000000000..13e662679894e --- /dev/null +++ b/crates/bevy_color_blindness/src/lib.rs @@ -0,0 +1,399 @@ +//! Plugin to simulate and preview different types of +//! color blindness. + +use bevy_app::{App, Plugin}; +use bevy_asset::{load_internal_asset, AssetServer, Assets, Handle, HandleUntyped}; +use bevy_core_pipeline::core_2d::Camera2dBundle; +use bevy_ecs::{ + entity::Entity, + prelude::{Added, Component}, + system::{Commands, Query, Res, ResMut}, +}; +use bevy_math::{Vec2, Vec3}; +use bevy_reflect::TypeUuid; +use bevy_render::{ + camera::{Camera, RenderTarget}, + mesh::{shape, Mesh}, + prelude::Image, + render_resource::{ + AsBindGroup, Extent3d, Shader, ShaderRef, TextureDescriptor, TextureDimension, + TextureFormat, TextureUsages, + }, + view::RenderLayers, +}; +use bevy_sprite::{Material2d, Material2dPlugin, MaterialMesh2dBundle}; +use bevy_transform::prelude::Transform; +use bevy_ui::entity::UiCameraConfig; +use bevy_window::Windows; + +/// Plugin to simulate and preview different types of +/// color blindness. +/// +/// This lets you ensure that your game is accessible to all players by testing how it +/// will be seen under different conditions. While this is important, +/// please also consider not relying on color alone to convey important information to your players. +/// A common option is to add identifying symbols, like in the game +/// [Hue](https://gameaccessibilityguidelines.com/hue-colorblind-mode/). +/// +/// Based on [Alan Zucconi's post](https://www.alanzucconi.com/2015/12/16/color-blindness/). +/// Supports: Normal, Protanopia, Protanomaly, Deuteranopia, Deuteranomaly, +/// Tritanopia, Tritanomaly, Achromatopsia, and Achromatomaly. +/// +/// First, add the [`ColorBlindnessPlugin`] to your app, and add [`ColorBlindnessCamera`] to +/// your main camera. +/// +/// You can change the selected mode by inserting [`ColorBlindnessParams`] before the plugin. +/// You can also skip this, and change the resource at any time in a system. +/// +/// ```rust,no_run +/// use bevy::prelude::*; +/// +/// fn main() { +/// App::new() +/// .add_plugins(DefaultPlugins) +/// .insert_resource(ColorBlindnessParams { +/// mode: Mode::Deuteranomaly, +/// enable: true, +/// }) +/// // add the plugin +/// .add_plugin(ColorBlindnessPlugin) +/// .add_startup_system(setup) +/// .run(); +/// } +/// +/// fn setup(mut commands: Commands) { +/// // set up your scene... +/// +/// // create the camera +/// commands +/// .spawn_bundle(Camera3dBundle { +/// transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y), +/// ..default() +/// }) +/// // IMPORTANT: add this component to your main camera +/// .insert(ColorBlindnessCamera); +/// } +/// ``` +/// +/// # Important note +/// +/// This plugin only simulates how color blind players will see your game. +/// It does not correct for color blindness to make your game more accessible. +/// This plugin should only be used during development, and removed on final builds. +pub struct ColorBlindnessPlugin; +impl Plugin for ColorBlindnessPlugin { + fn build(&self, app: &mut App) { + let world = &mut app.world; + world.get_resource_or_insert_with(ColorBlindnessParams::default); + + load_internal_asset!( + app, + COLOR_BLINDNESS_SHADER_HANDLE, + "color_blindness.wgsl", + Shader::from_wgsl + ); + + app.add_plugin(Material2dPlugin::::default()) + .add_startup_system(setup) + .add_system(set_camera_target) + .add_system(update_percentages); + } +} + +/// handle to the color blindness simulation shader +const COLOR_BLINDNESS_SHADER_HANDLE: HandleUntyped = + HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 3937837360667146578); + +/// Resource which selects the type of color blindness to simulate +/// +/// # Example +/// +/// This system will only enable the simulation while `Space` is held, and will cycle through +/// the different modes when `N` is pressed. +/// +/// ```rust +/// # use bevy::prelude::*; +/// fn cycle_mode(input: Res>, mut params: ResMut) { +/// if input.just_pressed(KeyCode::N) { +/// params.mode.cycle(); +/// println!("Changed to {:?}", params.mode); +/// } +/// +/// params.enable = input.pressed(KeyCode::Space); +/// } +/// ``` +#[derive(Default, Debug)] +pub struct ColorBlindnessParams { + /// Selects the color blindness mode to use + /// + /// Defaults to `Mode::Normal` + pub mode: Mode, + /// Controls whether color blindness simulation is enabled + /// + /// Defaults to `false` + pub enable: bool, +} + +/// The different modes of color blindness simulation supported. +#[derive(Clone, Default, Debug)] +pub enum Mode { + #[default] + Normal, + Protanopia, + Protanomaly, + Deuteranopia, + Deuteranomaly, + Tritanopia, + Tritanomaly, + Achromatopsia, + Achromatomaly, +} + +impl Mode { + fn percentages(&self) -> (Vec3, Vec3, Vec3) { + // table from https://www.alanzucconi.com/2015/12/16/color-blindness/ + // https://web.archive.org/web/20081014161121/http://www.colorjack.com/labs/colormatrix/ + + match self { + Mode::Normal => (Vec3::X, Vec3::Y, Vec3::Z), + Mode::Protanopia => ( + [0.56667, 0.43333, 0.0].into(), + [0.55833, 0.44167, 0.0].into(), + [0.0, 0.24167, 0.75833].into(), + ), + Mode::Protanomaly => ( + [0.81667, 0.18333, 0.0].into(), + [0.33333, 0.66667, 0.0].into(), + [0.0, 0.125, 0.875].into(), + ), + Mode::Deuteranopia => ( + [0.625, 0.375, 0.0].into(), + [0.70, 0.30, 0.0].into(), + [0.0, 0.30, 0.70].into(), + ), + Mode::Deuteranomaly => ( + [0.80, 0.20, 0.0].into(), + [0.25833, 0.74167, 0.0].into(), + [0.0, 0.14167, 0.85833].into(), + ), + Mode::Tritanopia => ( + [0.95, 0.5, 0.0].into(), + [0.0, 0.43333, 0.56667].into(), + [0.0, 0.475, 0.525].into(), + ), + Mode::Tritanomaly => ( + [0.96667, 0.3333, 0.0].into(), + [0.0, 0.73333, 0.26667].into(), + [0.0, 0.18333, 0.81667].into(), + ), + Mode::Achromatopsia => ( + [0.299, 0.587, 0.114].into(), + [0.299, 0.587, 0.114].into(), + [0.299, 0.587, 0.114].into(), + ), + Mode::Achromatomaly => ( + [0.618, 0.32, 0.62].into(), + [0.163, 0.775, 0.62].into(), + [0.163, 0.320, 0.516].into(), + ), + } + } + + /// Changes `self` to the next Mode. + /// + /// Useful for writing something like the following: + /// + /// ```rust + /// # use bevy::prelude::*; + /// fn cycle_mode(input: Res>, mut params: ResMut) { + /// if input.just_pressed(KeyCode::N) { + /// params.mode.cycle(); + /// println!("Changed to {:?}", params.mode); + /// } + /// } + /// ``` + pub fn cycle(&mut self) { + *self = match self { + Mode::Normal => Mode::Protanopia, + Mode::Protanopia => Mode::Protanomaly, + Mode::Protanomaly => Mode::Deuteranopia, + Mode::Deuteranopia => Mode::Deuteranomaly, + Mode::Deuteranomaly => Mode::Tritanopia, + Mode::Tritanopia => Mode::Tritanomaly, + Mode::Tritanomaly => Mode::Achromatopsia, + Mode::Achromatopsia => Mode::Achromatomaly, + Mode::Achromatomaly => Mode::Normal, + }; + } +} + +/// Our custom post processing material +#[derive(AsBindGroup, TypeUuid, Clone)] +#[uuid = "bc2f08eb-a0fb-43f1-a908-54871ea597d5"] +struct PostProcessingMaterial { + /// In this example, this image will be the result of the main camera. + #[texture(0)] + #[sampler(1)] + source_image: Handle, + + #[uniform(2)] + red: Vec3, + #[uniform(2)] + green: Vec3, + #[uniform(2)] + blue: Vec3, +} + +impl Material2d for PostProcessingMaterial { + fn fragment_shader() -> ShaderRef { + ShaderRef::Handle(COLOR_BLINDNESS_SHADER_HANDLE.typed()) + } +} + +/// Component to identify your main camera +/// +/// Adding this component to a camera will set up the post-processing pipeline +/// which simulates color blindness. This is done by changing the render target +/// to be an image, and then using another camera to render that image. +/// +/// Cameras with `ColorBlindnessCamera` will have [`UiCameraConfig`] inserted with +/// `show_ui` set to `false`. This is to ensure that UI elements are not rendered twice. +/// In most cases, you will want to render UI using the final post-processing camera. +/// If for some reason this behavior is not desired, please open an issue. +/// +/// [`UiCameraConfig`]: bevy_ui::entity::UiCameraConfig +#[derive(Component)] +pub struct ColorBlindnessCamera; + +/// sets the target for newly added `ColorBlindCamera`s +fn set_camera_target( + mut commands: Commands, + mut query: Query<(Entity, &mut Camera), Added>, + inner: Res, +) { + for (entity, mut camera) in query.iter_mut() { + camera.target = RenderTarget::Image(inner.image.clone()); + commands + .entity(entity) + .insert(UiCameraConfig { show_ui: false }); + } +} + +/// updates the percentages in the post processing material when the Mode changes in Params +fn update_percentages( + params: Res, + inner: Res, + mut materials: ResMut>, +) { + if params.is_changed() { + let mut mat = materials.get_mut(&inner.post).unwrap(); + + let mode = if params.enable { + ¶ms.mode + } else { + &Mode::Normal + }; + let (red, green, blue) = mode.percentages(); + + mat.red = red; + mat.green = green; + mat.blue = blue; + } +} + +/// internal resource which holds the handles +struct InternalResource { + image: Handle, + post: Handle, +} + +/// creates the image, the material, the final camera, and the whole post-processing pipeline +/// +/// based on the post-processing example +/// https://github.com/bevyengine/bevy/blob/main/examples/shader/post_processing.rs +fn setup( + mut commands: Commands, + mut windows: ResMut, + mut meshes: ResMut>, + mut post_processing_materials: ResMut>, + mut images: ResMut>, + asset_server: Res, + params: Res, +) { + asset_server.watch_for_changes().unwrap(); + + let window = windows.get_primary_mut().unwrap(); + let size = Extent3d { + width: window.physical_width(), + height: window.physical_height(), + ..Default::default() + }; + + // This is the texture that will be rendered to. + let mut image = Image { + texture_descriptor: TextureDescriptor { + label: None, + size, + dimension: TextureDimension::D2, + format: TextureFormat::Bgra8UnormSrgb, + mip_level_count: 1, + sample_count: 1, + usage: TextureUsages::TEXTURE_BINDING + | TextureUsages::COPY_DST + | TextureUsages::RENDER_ATTACHMENT, + }, + ..Default::default() + }; + + // fill image.data with zeroes + image.resize(size); + + let image_handle = images.add(image); + + // This specifies the layer used for the post processing camera, which will be attached to the post processing camera and 2d quad. + let post_processing_pass_layer = RenderLayers::layer((RenderLayers::TOTAL_LAYERS - 1) as u8); + + let quad_handle = meshes.add(Mesh::from(shape::Quad::new(Vec2::new( + size.width as f32, + size.height as f32, + )))); + + // This material has the texture that has been rendered. + let (red, green, blue) = params.mode.percentages(); + let material_handle = post_processing_materials.add(PostProcessingMaterial { + source_image: image_handle.clone(), + red, + green, + blue, + }); + + commands.insert_resource(InternalResource { + image: image_handle, + post: material_handle.clone(), + }); + + // Post processing 2d quad, with material using the render texture done by the main camera, with a custom shader. + commands + .spawn_bundle(MaterialMesh2dBundle { + mesh: quad_handle.into(), + material: material_handle, + transform: Transform { + translation: Vec3::new(0.0, 0.0, 1.5), + ..Default::default() + }, + ..Default::default() + }) + .insert(post_processing_pass_layer); + + // The post-processing pass camera. + commands + .spawn_bundle(Camera2dBundle { + camera: Camera { + // renders after the first main camera which has default value: 0. + priority: 1, + ..Default::default() + }, + ..Camera2dBundle::default() + }) + .insert(post_processing_pass_layer); +} diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index d4be55fc72878..ae9086b853e56 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -85,6 +85,7 @@ bevy_tasks = { path = "../bevy_tasks", version = "0.9.0-dev" } bevy_animation = { path = "../bevy_animation", optional = true, version = "0.9.0-dev" } bevy_asset = { path = "../bevy_asset", optional = true, version = "0.9.0-dev" } bevy_audio = { path = "../bevy_audio", optional = true, version = "0.9.0-dev" } +bevy_color_blindness = { path = "../bevy_color_blindness", optional = true, version = "0.9.0-dev" } bevy_core_pipeline = { path = "../bevy_core_pipeline", optional = true, version = "0.9.0-dev" } bevy_gltf = { path = "../bevy_gltf", optional = true, version = "0.9.0-dev" } bevy_pbr = { path = "../bevy_pbr", optional = true, version = "0.9.0-dev" } diff --git a/crates/bevy_internal/src/lib.rs b/crates/bevy_internal/src/lib.rs index a9158f0bafacb..eedb45a9b3579 100644 --- a/crates/bevy_internal/src/lib.rs +++ b/crates/bevy_internal/src/lib.rs @@ -109,6 +109,14 @@ pub mod audio { pub use bevy_audio::*; } +#[cfg(feature = "bevy_color_blindness")] +pub mod color_blindness { + //! Plugin to simulate and preview different types of + //! color blindness. + + pub use bevy_color_blindness::*; +} + #[cfg(feature = "bevy_core_pipeline")] pub mod core_pipeline { //! Core render pipeline. diff --git a/crates/bevy_internal/src/prelude.rs b/crates/bevy_internal/src/prelude.rs index cc3614544d0bb..fd053d8bf78ab 100644 --- a/crates/bevy_internal/src/prelude.rs +++ b/crates/bevy_internal/src/prelude.rs @@ -19,6 +19,10 @@ pub use crate::audio::prelude::*; #[cfg(feature = "bevy_animation")] pub use crate::animation::prelude::*; +#[doc(hidden)] +#[cfg(feature = "bevy_color_blindness")] +pub use crate::color_blindness::*; + #[doc(hidden)] #[cfg(feature = "bevy_core_pipeline")] pub use crate::core_pipeline::prelude::*; diff --git a/examples/color_blindness_simulation.rs b/examples/color_blindness_simulation.rs new file mode 100644 index 0000000000000..50b56e8cd2d1f --- /dev/null +++ b/examples/color_blindness_simulation.rs @@ -0,0 +1,90 @@ +//! Small demo of how to use color blindness simulation +//! Shows a small scene, with four different cubes +//! +//! Holding the Space key enables the simulation +//! Pressing N cycles through the modes + +use bevy::{prelude::*, window::close_on_esc}; + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + // you can select the color blindness simulation mode with the `ColorBlindnessParams` resource + .insert_resource(ColorBlindnessParams { + mode: Mode::Deuteranomaly, + ..default() + }) + // add the plugin + .add_plugin(ColorBlindnessPlugin) + .add_startup_system(setup) + .add_system(close_on_esc) + .add_system(change_mode) + .run(); +} + +/// set up a simple 3D scene +fn setup( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + // create a small world + commands.spawn_bundle(PbrBundle { + mesh: meshes.add(Mesh::from(shape::Plane { size: 5.0 })), + material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()), + ..default() + }); + let cube = meshes.add(Mesh::from(shape::Cube { size: 0.5 })); + commands.spawn_bundle(PbrBundle { + mesh: cube.clone(), + material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()), + transform: Transform::from_xyz(0.0, 0.5, 0.0), + ..default() + }); + commands.spawn_bundle(PbrBundle { + mesh: cube.clone(), + material: materials.add(Color::rgb(1.0, 0.0, 0.0).into()), + transform: Transform::from_xyz(2.0, 0.5, 0.0), + ..default() + }); + commands.spawn_bundle(PbrBundle { + mesh: cube.clone(), + material: materials.add(Color::rgb(0.0, 1.0, 0.0).into()), + transform: Transform::from_xyz(3.0, 0.5, 0.0), + ..default() + }); + commands.spawn_bundle(PbrBundle { + mesh: cube, + material: materials.add(Color::rgb(0.0, 0.0, 1.0).into()), + transform: Transform::from_xyz(4.0, 0.5, 0.0), + ..default() + }); + commands.spawn_bundle(PointLightBundle { + point_light: PointLight { + intensity: 1500.0, + shadows_enabled: true, + ..default() + }, + transform: Transform::from_xyz(4.0, 8.0, 4.0), + ..default() + }); + + // create the camera + commands + .spawn_bundle(Camera3dBundle { + transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y), + ..default() + }) + // IMPORTANT: add this component to your main camera + .insert(ColorBlindnessCamera); +} + +fn change_mode(input: Res>, mut params: ResMut) { + // cycle through the modes by pressing N + if input.just_pressed(KeyCode::N) { + params.mode.cycle(); + println!("Changed to {:?}", params.mode); + } + + params.enable = input.pressed(KeyCode::Space); +} From e329e76dc4ad4a58de6157e553132d5215f5c7a2 Mon Sep 17 00:00:00 2001 From: annieversary Date: Sun, 7 Aug 2022 18:10:44 +0100 Subject: [PATCH 02/10] Move example to new Accessibility category --- Cargo.toml | 15 +++++++++++---- examples/README.md | 6 ++++++ .../color_blindness_simulation.rs | 0 3 files changed, 17 insertions(+), 4 deletions(-) rename examples/{ => accessibility}/color_blindness_simulation.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index b3165fad013ea..b1a489ca97042 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -143,10 +143,6 @@ path = "examples/hello_world.rs" [package.metadata.example.hello_world] hidden = true -[[example]] -name = "color_blindness_simulation" -path = "examples/color_blindness_simulation.rs" - # 2D Rendering [[example]] name = "move_sprite" @@ -490,6 +486,17 @@ description = "Showcases wireframe rendering" category = "3D Rendering" wasm = true +# Accessibility +[[example]] +name = "color_blindness_simulation" +path = "examples/accessibility/color_blindness_simulation.rs" + +[package.metadata.example.color_blindness_simulation] +name = "Color Blindness Simulation" +description = "Demonstrates color blindness simulation" +category = "Accessibility" +wasm = true + # Animation [[example]] name = "animated_fox" diff --git a/examples/README.md b/examples/README.md index b23260cdcb5e0..38b19e65bf7dc 100644 --- a/examples/README.md +++ b/examples/README.md @@ -127,6 +127,12 @@ Example | Description [Vertex Colors](../examples/3d/vertex_colors.rs) | Shows the use of vertex colors [Wireframe](../examples/3d/wireframe.rs) | Showcases wireframe rendering +## Accessibility + +Example | Description +--- | --- +[Color Blindness Simulation](../examples/accessibility/color_blindness_simulation.rs) | Demonstrates color blindness simulation + ## Animation Example | Description diff --git a/examples/color_blindness_simulation.rs b/examples/accessibility/color_blindness_simulation.rs similarity index 100% rename from examples/color_blindness_simulation.rs rename to examples/accessibility/color_blindness_simulation.rs From 1160186244b2b064bf92cacb7220595c561955f0 Mon Sep 17 00:00:00 2001 From: annieversary Date: Sun, 7 Aug 2022 18:34:49 +0100 Subject: [PATCH 03/10] fix imports in doc examples --- crates/bevy_color_blindness/Cargo.toml | 3 +++ crates/bevy_color_blindness/src/lib.rs | 27 +++++++++++++++++++------- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/crates/bevy_color_blindness/Cargo.toml b/crates/bevy_color_blindness/Cargo.toml index 5eac24a9fc17e..304ed77a72692 100644 --- a/crates/bevy_color_blindness/Cargo.toml +++ b/crates/bevy_color_blindness/Cargo.toml @@ -23,3 +23,6 @@ bevy_sprite = { path = "../bevy_sprite", version = "0.9.0-dev" } bevy_transform = { path = "../bevy_transform", version = "0.9.0-dev" } bevy_ui = { path = "../bevy_ui", version = "0.9.0-dev" } bevy_window = { path = "../bevy_window", version = "0.9.0-dev" } + +[dev-dependencies] +bevy_input = { path = "../bevy_input", version = "0.9.0-dev" } diff --git a/crates/bevy_color_blindness/src/lib.rs b/crates/bevy_color_blindness/src/lib.rs index 13e662679894e..3712cc0f0dd0d 100644 --- a/crates/bevy_color_blindness/src/lib.rs +++ b/crates/bevy_color_blindness/src/lib.rs @@ -5,8 +5,9 @@ use bevy_app::{App, Plugin}; use bevy_asset::{load_internal_asset, AssetServer, Assets, Handle, HandleUntyped}; use bevy_core_pipeline::core_2d::Camera2dBundle; use bevy_ecs::{ + component::Component, entity::Entity, - prelude::{Added, Component}, + query::Added, system::{Commands, Query, Res, ResMut}, }; use bevy_math::{Vec2, Vec3}; @@ -46,8 +47,12 @@ use bevy_window::Windows; /// You can also skip this, and change the resource at any time in a system. /// /// ```rust,no_run -/// use bevy::prelude::*; -/// +/// # use bevy_app::{App, NoopPluginGroup as DefaultPlugins}; +/// # use bevy_color_blindness::*; +/// # use bevy_core_pipeline::core_3d::Camera3dBundle; +/// # use bevy_ecs::system::Commands; +/// # use bevy_math::prelude::*; +/// # use bevy_transform::prelude::*; /// fn main() { /// App::new() /// .add_plugins(DefaultPlugins) @@ -68,7 +73,7 @@ use bevy_window::Windows; /// commands /// .spawn_bundle(Camera3dBundle { /// transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y), -/// ..default() +/// ..Default::default() /// }) /// // IMPORTANT: add this component to your main camera /// .insert(ColorBlindnessCamera); @@ -112,7 +117,12 @@ const COLOR_BLINDNESS_SHADER_HANDLE: HandleUntyped = /// the different modes when `N` is pressed. /// /// ```rust -/// # use bevy::prelude::*; +/// # use bevy_app::prelude::*; +/// # use bevy_color_blindness::*; +/// # use bevy_ecs::prelude::*; +/// # use bevy_input::prelude::*; +/// # use bevy_math::prelude::*; +/// # use bevy_transform::prelude::*; /// fn cycle_mode(input: Res>, mut params: ResMut) { /// if input.just_pressed(KeyCode::N) { /// params.mode.cycle(); @@ -204,7 +214,10 @@ impl Mode { /// Useful for writing something like the following: /// /// ```rust - /// # use bevy::prelude::*; + /// # use bevy_app::prelude::*; + /// # use bevy_color_blindness::*; + /// # use bevy_ecs::prelude::*; + /// # use bevy_input::prelude::*; /// fn cycle_mode(input: Res>, mut params: ResMut) { /// if input.just_pressed(KeyCode::N) { /// params.mode.cycle(); @@ -310,7 +323,7 @@ struct InternalResource { /// creates the image, the material, the final camera, and the whole post-processing pipeline /// /// based on the post-processing example -/// https://github.com/bevyengine/bevy/blob/main/examples/shader/post_processing.rs +/// `https://github.com/bevyengine/bevy/blob/main/examples/shader/post_processing.rs` fn setup( mut commands: Commands, mut windows: ResMut, From 905f1730e8c61c197aa273f95ee48978e15bec8e Mon Sep 17 00:00:00 2001 From: annieversary Date: Sun, 7 Aug 2022 18:51:47 +0100 Subject: [PATCH 04/10] run `cargo run -p build-example-pages -- update` --- examples/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/README.md b/examples/README.md index 38b19e65bf7dc..dc6c5940ed65f 100644 --- a/examples/README.md +++ b/examples/README.md @@ -39,6 +39,7 @@ git checkout v0.4.0 - [Cross-Platform Examples](#cross-platform-examples) - [2D Rendering](#2d-rendering) - [3D Rendering](#3d-rendering) + - [Accessibility](#accessibility) - [Animation](#animation) - [Application](#application) - [Assets](#assets) From 465cc970249f408f285f4d092bae8bc7da77c153 Mon Sep 17 00:00:00 2001 From: annieversary Date: Sun, 7 Aug 2022 19:43:21 +0100 Subject: [PATCH 05/10] Improve names and documentation. --- Cargo.toml | 2 +- crates/bevy_color_blindness/src/lib.rs | 122 ++++++++++++------ .../color_blindness_simulation.rs | 2 +- 3 files changed, 82 insertions(+), 44 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b1a489ca97042..e8bdbaeaa1df1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -493,7 +493,7 @@ path = "examples/accessibility/color_blindness_simulation.rs" [package.metadata.example.color_blindness_simulation] name = "Color Blindness Simulation" -description = "Demonstrates color blindness simulation" +description = "Modify your app's rendering output to simulate color blindness" category = "Accessibility" wasm = true diff --git a/crates/bevy_color_blindness/src/lib.rs b/crates/bevy_color_blindness/src/lib.rs index 3712cc0f0dd0d..610e03d38844d 100644 --- a/crates/bevy_color_blindness/src/lib.rs +++ b/crates/bevy_color_blindness/src/lib.rs @@ -17,7 +17,7 @@ use bevy_render::{ mesh::{shape, Mesh}, prelude::Image, render_resource::{ - AsBindGroup, Extent3d, Shader, ShaderRef, TextureDescriptor, TextureDimension, + AsBindGroup, Extent3d, Shader, ShaderRef, ShaderType, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, }, view::RenderLayers, @@ -57,7 +57,7 @@ use bevy_window::Windows; /// App::new() /// .add_plugins(DefaultPlugins) /// .insert_resource(ColorBlindnessParams { -/// mode: Mode::Deuteranomaly, +/// mode: ColorBlindnessMode::Deuteranomaly, /// enable: true, /// }) /// // add the plugin @@ -136,8 +136,8 @@ const COLOR_BLINDNESS_SHADER_HANDLE: HandleUntyped = pub struct ColorBlindnessParams { /// Selects the color blindness mode to use /// - /// Defaults to `Mode::Normal` - pub mode: Mode, + /// Defaults to `ColorBlindnessMode::Normal` + pub mode: ColorBlindnessMode, /// Controls whether color blindness simulation is enabled /// /// Defaults to `false` @@ -146,62 +146,110 @@ pub struct ColorBlindnessParams { /// The different modes of color blindness simulation supported. #[derive(Clone, Default, Debug)] -pub enum Mode { +pub enum ColorBlindnessMode { + /// Normal full color vision #[default] Normal, + // Descriptions of the different types of color blindness are sourced from: + // https://www.nei.nih.gov/learn-about-eye-health/eye-conditions-and-diseases/color-blindness/types-color-blindness + /// Inability to differentiate between green and red. Protanopia, + /// Condition where red looks more green. Protanomaly, + /// Inability to differentiate between green and red. Deuteranopia, + /// Condition where green looks more red. Deuteranomaly, + /// Inability to differentiate between blue and green, purple and red, and yellow and pink. Tritanopia, + /// Difficulty differentiating between blue and green, and between yellow and red Tritanomaly, + /// Absence of color discrimination. Achromatopsia, + /// All color cones have some form of deficiency. Achromatomaly, } -impl Mode { - fn percentages(&self) -> (Vec3, Vec3, Vec3) { +/// Indicates how to mix the RGB channels to obtain output colors. +/// +/// Normal vision corresponds to the following: +/// ```rust +/// # use bevy_math::prelude::*; +/// # use bevy_color_blindness::*; +/// # fn _none() -> ColorBlindnessPercentages { +/// ColorBlindnessPercentages { +/// // red channel output is 100% red, 0% green, 0% blue +/// red: Vec3::X, +/// // green channel is 0% red, 100% green, 0% blue +/// green: Vec3::Y, +/// // blue channel is 0% red, 0% green, 100% blue +/// blue: Vec3::Z +/// } +/// # } +/// ``` +#[derive(ShaderType, Clone, Debug)] +pub struct ColorBlindnessPercentages { + /// Percentages of red, green, and blue to mix on the red channel. + pub red: Vec3, + /// Percentages of red, green, and blue to mix on the green channel. + pub green: Vec3, + /// Percentages of red, green, and blue to mix on the blue channel. + pub blue: Vec3, +} + +impl ColorBlindnessPercentages { + /// Creates a new `ColorBlindnessPercentages` + fn new(red: Vec3, green: Vec3, blue: Vec3) -> Self { + Self { red, green, blue } + } +} + +impl ColorBlindnessMode { + /// Returns the percentages of colors to mix corresponding to each type of color blindness. + /// + /// [Source](https://web.archive.org/web/20081014161121/http://www.colorjack.com/labs/colormatrix/) + pub fn percentages(&self) -> ColorBlindnessPercentages { // table from https://www.alanzucconi.com/2015/12/16/color-blindness/ // https://web.archive.org/web/20081014161121/http://www.colorjack.com/labs/colormatrix/ match self { - Mode::Normal => (Vec3::X, Vec3::Y, Vec3::Z), - Mode::Protanopia => ( + ColorBlindnessMode::Normal => ColorBlindnessPercentages::new(Vec3::X, Vec3::Y, Vec3::Z), + ColorBlindnessMode::Protanopia => ColorBlindnessPercentages::new( [0.56667, 0.43333, 0.0].into(), [0.55833, 0.44167, 0.0].into(), [0.0, 0.24167, 0.75833].into(), ), - Mode::Protanomaly => ( + ColorBlindnessMode::Protanomaly => ColorBlindnessPercentages::new( [0.81667, 0.18333, 0.0].into(), [0.33333, 0.66667, 0.0].into(), [0.0, 0.125, 0.875].into(), ), - Mode::Deuteranopia => ( + ColorBlindnessMode::Deuteranopia => ColorBlindnessPercentages::new( [0.625, 0.375, 0.0].into(), [0.70, 0.30, 0.0].into(), [0.0, 0.30, 0.70].into(), ), - Mode::Deuteranomaly => ( + ColorBlindnessMode::Deuteranomaly => ColorBlindnessPercentages::new( [0.80, 0.20, 0.0].into(), [0.25833, 0.74167, 0.0].into(), [0.0, 0.14167, 0.85833].into(), ), - Mode::Tritanopia => ( + ColorBlindnessMode::Tritanopia => ColorBlindnessPercentages::new( [0.95, 0.5, 0.0].into(), [0.0, 0.43333, 0.56667].into(), [0.0, 0.475, 0.525].into(), ), - Mode::Tritanomaly => ( + ColorBlindnessMode::Tritanomaly => ColorBlindnessPercentages::new( [0.96667, 0.3333, 0.0].into(), [0.0, 0.73333, 0.26667].into(), [0.0, 0.18333, 0.81667].into(), ), - Mode::Achromatopsia => ( + ColorBlindnessMode::Achromatopsia => ColorBlindnessPercentages::new( [0.299, 0.587, 0.114].into(), [0.299, 0.587, 0.114].into(), [0.299, 0.587, 0.114].into(), ), - Mode::Achromatomaly => ( + ColorBlindnessMode::Achromatomaly => ColorBlindnessPercentages::new( [0.618, 0.32, 0.62].into(), [0.163, 0.775, 0.62].into(), [0.163, 0.320, 0.516].into(), @@ -209,7 +257,7 @@ impl Mode { } } - /// Changes `self` to the next Mode. + /// Changes `self` to the next `ColorBlindnessMode`. /// /// Useful for writing something like the following: /// @@ -227,20 +275,20 @@ impl Mode { /// ``` pub fn cycle(&mut self) { *self = match self { - Mode::Normal => Mode::Protanopia, - Mode::Protanopia => Mode::Protanomaly, - Mode::Protanomaly => Mode::Deuteranopia, - Mode::Deuteranopia => Mode::Deuteranomaly, - Mode::Deuteranomaly => Mode::Tritanopia, - Mode::Tritanopia => Mode::Tritanomaly, - Mode::Tritanomaly => Mode::Achromatopsia, - Mode::Achromatopsia => Mode::Achromatomaly, - Mode::Achromatomaly => Mode::Normal, + ColorBlindnessMode::Normal => ColorBlindnessMode::Protanopia, + ColorBlindnessMode::Protanopia => ColorBlindnessMode::Protanomaly, + ColorBlindnessMode::Protanomaly => ColorBlindnessMode::Deuteranopia, + ColorBlindnessMode::Deuteranopia => ColorBlindnessMode::Deuteranomaly, + ColorBlindnessMode::Deuteranomaly => ColorBlindnessMode::Tritanopia, + ColorBlindnessMode::Tritanopia => ColorBlindnessMode::Tritanomaly, + ColorBlindnessMode::Tritanomaly => ColorBlindnessMode::Achromatopsia, + ColorBlindnessMode::Achromatopsia => ColorBlindnessMode::Achromatomaly, + ColorBlindnessMode::Achromatomaly => ColorBlindnessMode::Normal, }; } } -/// Our custom post processing material +/// Post processing material that applies color blindness simulation to `image` #[derive(AsBindGroup, TypeUuid, Clone)] #[uuid = "bc2f08eb-a0fb-43f1-a908-54871ea597d5"] struct PostProcessingMaterial { @@ -250,11 +298,7 @@ struct PostProcessingMaterial { source_image: Handle, #[uniform(2)] - red: Vec3, - #[uniform(2)] - green: Vec3, - #[uniform(2)] - blue: Vec3, + percentages: ColorBlindnessPercentages, } impl Material2d for PostProcessingMaterial { @@ -292,7 +336,7 @@ fn set_camera_target( } } -/// updates the percentages in the post processing material when the Mode changes in Params +/// updates the percentages in the post processing material when the `ColorBlindnessMode` changes in Params fn update_percentages( params: Res, inner: Res, @@ -304,13 +348,10 @@ fn update_percentages( let mode = if params.enable { ¶ms.mode } else { - &Mode::Normal + &ColorBlindnessMode::Normal }; - let (red, green, blue) = mode.percentages(); - mat.red = red; - mat.green = green; - mat.blue = blue; + mat.percentages = mode.percentages(); } } @@ -372,12 +413,9 @@ fn setup( )))); // This material has the texture that has been rendered. - let (red, green, blue) = params.mode.percentages(); let material_handle = post_processing_materials.add(PostProcessingMaterial { source_image: image_handle.clone(), - red, - green, - blue, + percentages: params.mode.percentages(), }); commands.insert_resource(InternalResource { diff --git a/examples/accessibility/color_blindness_simulation.rs b/examples/accessibility/color_blindness_simulation.rs index 50b56e8cd2d1f..5e3c8293844b2 100644 --- a/examples/accessibility/color_blindness_simulation.rs +++ b/examples/accessibility/color_blindness_simulation.rs @@ -11,7 +11,7 @@ fn main() { .add_plugins(DefaultPlugins) // you can select the color blindness simulation mode with the `ColorBlindnessParams` resource .insert_resource(ColorBlindnessParams { - mode: Mode::Deuteranomaly, + mode: ColorBlindnessMode::Deuteranomaly, ..default() }) // add the plugin From 4b672f98dbc42fba0ecd541d1588c1576e00dfd8 Mon Sep 17 00:00:00 2001 From: annieversary Date: Sun, 7 Aug 2022 20:03:15 +0100 Subject: [PATCH 06/10] run `cargo run -p build-example-pages -- update` --- examples/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/README.md b/examples/README.md index dc6c5940ed65f..7ecad68559f47 100644 --- a/examples/README.md +++ b/examples/README.md @@ -132,7 +132,7 @@ Example | Description Example | Description --- | --- -[Color Blindness Simulation](../examples/accessibility/color_blindness_simulation.rs) | Demonstrates color blindness simulation +[Color Blindness Simulation](../examples/accessibility/color_blindness_simulation.rs) | Modify your app's rendering output to simulate color blindness ## Animation From f82464d7b99706805dfb840cbe507095ab17194f Mon Sep 17 00:00:00 2001 From: annieversary Date: Mon, 8 Aug 2022 09:17:30 +0100 Subject: [PATCH 07/10] `TextureFormat::bevy_default()` --- crates/bevy_color_blindness/src/lib.rs | 3 ++- docs/cargo_features.md | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/bevy_color_blindness/src/lib.rs b/crates/bevy_color_blindness/src/lib.rs index 610e03d38844d..7f48bafde3e64 100644 --- a/crates/bevy_color_blindness/src/lib.rs +++ b/crates/bevy_color_blindness/src/lib.rs @@ -20,6 +20,7 @@ use bevy_render::{ AsBindGroup, Extent3d, Shader, ShaderRef, ShaderType, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, }, + texture::BevyDefault, view::RenderLayers, }; use bevy_sprite::{Material2d, Material2dPlugin, MaterialMesh2dBundle}; @@ -389,7 +390,7 @@ fn setup( label: None, size, dimension: TextureDimension::D2, - format: TextureFormat::Bgra8UnormSrgb, + format: TextureFormat::bevy_default(), mip_level_count: 1, sample_count: 1, usage: TextureUsages::TEXTURE_BINDING diff --git a/docs/cargo_features.md b/docs/cargo_features.md index 8d0db4604380e..f4ec6abd387b9 100644 --- a/docs/cargo_features.md +++ b/docs/cargo_features.md @@ -7,6 +7,7 @@ |animation|Animation support and glTF animation loading.| |bevy_asset|Provides asset functionality for Bevy Engine.| |bevy_audio|Audio support. Support for all audio formats depends on this.| +|bevy_color_blindness|Provides color blindness simulation features.| |bevy_gilrs|Adds gamepad support.| |bevy_gltf|[glTF](https://www.khronos.org/gltf/) support.| |bevy_scene|Provides scene functionality for Bevy Engine.| From 0a8a176c7342db256febf464398d3478944b4ffa Mon Sep 17 00:00:00 2001 From: annieversary Date: Sun, 14 Aug 2022 17:58:21 +0100 Subject: [PATCH 08/10] add bevy_color_blindness to `tools/publish.sh` --- tools/publish.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/publish.sh b/tools/publish.sh index d5e880c2e7791..9ff7f22493618 100644 --- a/tools/publish.sh +++ b/tools/publish.sh @@ -35,6 +35,7 @@ crates=( bevy_sprite bevy_text bevy_ui + bevy_color_blindness bevy_winit bevy_internal bevy_dylib From aa0fcb07ef711bbbf2011eb835691f6f0544d011 Mon Sep 17 00:00:00 2001 From: annieversary Date: Sun, 14 Aug 2022 18:53:05 +0100 Subject: [PATCH 09/10] remove `ColorBlindnessParams` This commit moves `mode` and `enabled` into `ColorBlindnessCamera`, which allows for multiple cameras with different modes. This should allow multiple windows to work correctly, but it hasn't been tested yet. --- crates/bevy_color_blindness/src/lib.rs | 282 ++++++++---------- .../color_blindness_simulation.rs | 27 +- 2 files changed, 136 insertions(+), 173 deletions(-) diff --git a/crates/bevy_color_blindness/src/lib.rs b/crates/bevy_color_blindness/src/lib.rs index 7f48bafde3e64..5a72c8a675c61 100644 --- a/crates/bevy_color_blindness/src/lib.rs +++ b/crates/bevy_color_blindness/src/lib.rs @@ -2,12 +2,12 @@ //! color blindness. use bevy_app::{App, Plugin}; -use bevy_asset::{load_internal_asset, AssetServer, Assets, Handle, HandleUntyped}; +use bevy_asset::{load_internal_asset, Assets, Handle, HandleUntyped}; use bevy_core_pipeline::core_2d::Camera2dBundle; use bevy_ecs::{ component::Component, entity::Entity, - query::Added, + query::{Added, Changed}, system::{Commands, Query, Res, ResMut}, }; use bevy_math::{Vec2, Vec3}; @@ -44,9 +44,6 @@ use bevy_window::Windows; /// First, add the [`ColorBlindnessPlugin`] to your app, and add [`ColorBlindnessCamera`] to /// your main camera. /// -/// You can change the selected mode by inserting [`ColorBlindnessParams`] before the plugin. -/// You can also skip this, and change the resource at any time in a system. -/// /// ```rust,no_run /// # use bevy_app::{App, NoopPluginGroup as DefaultPlugins}; /// # use bevy_color_blindness::*; @@ -57,10 +54,6 @@ use bevy_window::Windows; /// fn main() { /// App::new() /// .add_plugins(DefaultPlugins) -/// .insert_resource(ColorBlindnessParams { -/// mode: ColorBlindnessMode::Deuteranomaly, -/// enable: true, -/// }) /// // add the plugin /// .add_plugin(ColorBlindnessPlugin) /// .add_startup_system(setup) @@ -73,11 +66,13 @@ use bevy_window::Windows; /// // create the camera /// commands /// .spawn_bundle(Camera3dBundle { -/// transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y), -/// ..Default::default() +/// transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y), +/// ..Default::default() /// }) -/// // IMPORTANT: add this component to your main camera -/// .insert(ColorBlindnessCamera); +/// .insert(ColorBlindnessCamera { +/// mode: ColorBlindnessMode::Deuteranopia, +/// enabled: true, +/// }); /// } /// ``` /// @@ -89,9 +84,6 @@ use bevy_window::Windows; pub struct ColorBlindnessPlugin; impl Plugin for ColorBlindnessPlugin { fn build(&self, app: &mut App) { - let world = &mut app.world; - world.get_resource_or_insert_with(ColorBlindnessParams::default); - load_internal_asset!( app, COLOR_BLINDNESS_SHADER_HANDLE, @@ -99,9 +91,8 @@ impl Plugin for ColorBlindnessPlugin { Shader::from_wgsl ); - app.add_plugin(Material2dPlugin::::default()) - .add_startup_system(setup) - .add_system(set_camera_target) + app.add_plugin(Material2dPlugin::::default()) + .add_system(setup_new_color_blindness_cameras) .add_system(update_percentages); } } @@ -110,41 +101,6 @@ impl Plugin for ColorBlindnessPlugin { const COLOR_BLINDNESS_SHADER_HANDLE: HandleUntyped = HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 3937837360667146578); -/// Resource which selects the type of color blindness to simulate -/// -/// # Example -/// -/// This system will only enable the simulation while `Space` is held, and will cycle through -/// the different modes when `N` is pressed. -/// -/// ```rust -/// # use bevy_app::prelude::*; -/// # use bevy_color_blindness::*; -/// # use bevy_ecs::prelude::*; -/// # use bevy_input::prelude::*; -/// # use bevy_math::prelude::*; -/// # use bevy_transform::prelude::*; -/// fn cycle_mode(input: Res>, mut params: ResMut) { -/// if input.just_pressed(KeyCode::N) { -/// params.mode.cycle(); -/// println!("Changed to {:?}", params.mode); -/// } -/// -/// params.enable = input.pressed(KeyCode::Space); -/// } -/// ``` -#[derive(Default, Debug)] -pub struct ColorBlindnessParams { - /// Selects the color blindness mode to use - /// - /// Defaults to `ColorBlindnessMode::Normal` - pub mode: ColorBlindnessMode, - /// Controls whether color blindness simulation is enabled - /// - /// Defaults to `false` - pub enable: bool, -} - /// The different modes of color blindness simulation supported. #[derive(Clone, Default, Debug)] pub enum ColorBlindnessMode { @@ -267,11 +223,16 @@ impl ColorBlindnessMode { /// # use bevy_color_blindness::*; /// # use bevy_ecs::prelude::*; /// # use bevy_input::prelude::*; - /// fn cycle_mode(input: Res>, mut params: ResMut) { + /// fn change_mode(input: Res>, mut cameras: Query<&mut ColorBlindnessCamera>) { + /// for mut camera in &mut cameras { + /// // cycle through the modes by pressing N /// if input.just_pressed(KeyCode::N) { - /// params.mode.cycle(); - /// println!("Changed to {:?}", params.mode); + /// camera.mode.cycle(); + /// println!("Changed to {:?}", camera.mode); /// } + /// + /// camera.enabled = input.pressed(KeyCode::Space); + /// } /// } /// ``` pub fn cycle(&mut self) { @@ -292,7 +253,7 @@ impl ColorBlindnessMode { /// Post processing material that applies color blindness simulation to `image` #[derive(AsBindGroup, TypeUuid, Clone)] #[uuid = "bc2f08eb-a0fb-43f1-a908-54871ea597d5"] -struct PostProcessingMaterial { +struct ColorBlindnessMaterial { /// In this example, this image will be the result of the main camera. #[texture(0)] #[sampler(1)] @@ -302,7 +263,7 @@ struct PostProcessingMaterial { percentages: ColorBlindnessPercentages, } -impl Material2d for PostProcessingMaterial { +impl Material2d for ColorBlindnessMaterial { fn fragment_shader() -> ShaderRef { ShaderRef::Handle(COLOR_BLINDNESS_SHADER_HANDLE.typed()) } @@ -320,34 +281,31 @@ impl Material2d for PostProcessingMaterial { /// If for some reason this behavior is not desired, please open an issue. /// /// [`UiCameraConfig`]: bevy_ui::entity::UiCameraConfig -#[derive(Component)] -pub struct ColorBlindnessCamera; - -/// sets the target for newly added `ColorBlindCamera`s -fn set_camera_target( - mut commands: Commands, - mut query: Query<(Entity, &mut Camera), Added>, - inner: Res, -) { - for (entity, mut camera) in query.iter_mut() { - camera.target = RenderTarget::Image(inner.image.clone()); - commands - .entity(entity) - .insert(UiCameraConfig { show_ui: false }); - } +#[derive(Component, Default)] +pub struct ColorBlindnessCamera { + /// Selects the color blindness mode to use + /// + /// Defaults to `ColorBlindnessMode::Normal` + pub mode: ColorBlindnessMode, + /// Controls whether color blindness simulation is enabled + /// + /// Defaults to `false` + pub enabled: bool, } -/// updates the percentages in the post processing material when the `ColorBlindnessMode` changes in Params +/// updates the percentages in the post processing material when the values in `ColorBlindnessCamera` change fn update_percentages( - params: Res, - inner: Res, - mut materials: ResMut>, + cameras: Query< + (&Handle, &ColorBlindnessCamera), + Changed, + >, + mut materials: ResMut>, ) { - if params.is_changed() { - let mut mat = materials.get_mut(&inner.post).unwrap(); + for (handle, camera) in &cameras { + let mut mat = materials.get_mut(handle).unwrap(); - let mode = if params.enable { - ¶ms.mode + let mode = if camera.enabled { + &camera.mode } else { &ColorBlindnessMode::Normal }; @@ -356,96 +314,102 @@ fn update_percentages( } } -/// internal resource which holds the handles -struct InternalResource { - image: Handle, - post: Handle, -} - -/// creates the image, the material, the final camera, and the whole post-processing pipeline -/// -/// based on the post-processing example -/// `https://github.com/bevyengine/bevy/blob/main/examples/shader/post_processing.rs` -fn setup( +/// sets up post processing for cameras that have had `ColorBlindnessCamera` added +fn setup_new_color_blindness_cameras( mut commands: Commands, - mut windows: ResMut, + windows: Res, mut meshes: ResMut>, - mut post_processing_materials: ResMut>, + mut post_processing_materials: ResMut>, mut images: ResMut>, - asset_server: Res, - params: Res, + mut cameras: Query<(Entity, &mut Camera, &ColorBlindnessCamera), Added>, ) { - asset_server.watch_for_changes().unwrap(); + for (entity, mut camera, color_blindness_camera) in &mut cameras { + // Get the size the camera is rendering to + let size = match &camera.target { + RenderTarget::Window(window_id) => { + let window = windows.get(*window_id).expect("ColorBlindnessCamera is rendering to a window, but this window could not be found"); + Extent3d { + width: window.physical_width(), + height: window.physical_height(), + ..Default::default() + } + } + RenderTarget::Image(handle) => { + let image = images.get(handle).expect( + "ColorBlindnessCamera is rendering to an Image, but this Image could not be found", + ); + image.texture_descriptor.size + } + }; - let window = windows.get_primary_mut().unwrap(); - let size = Extent3d { - width: window.physical_width(), - height: window.physical_height(), - ..Default::default() - }; + // This is the texture that will be rendered to. + let mut image = Image { + texture_descriptor: TextureDescriptor { + label: None, + size, + dimension: TextureDimension::D2, + format: TextureFormat::bevy_default(), + mip_level_count: 1, + sample_count: 1, + usage: TextureUsages::TEXTURE_BINDING + | TextureUsages::COPY_DST + | TextureUsages::RENDER_ATTACHMENT, + }, + ..Default::default() + }; - // This is the texture that will be rendered to. - let mut image = Image { - texture_descriptor: TextureDescriptor { - label: None, - size, - dimension: TextureDimension::D2, - format: TextureFormat::bevy_default(), - mip_level_count: 1, - sample_count: 1, - usage: TextureUsages::TEXTURE_BINDING - | TextureUsages::COPY_DST - | TextureUsages::RENDER_ATTACHMENT, - }, - ..Default::default() - }; + // fill image.data with zeroes + image.resize(size); - // fill image.data with zeroes - image.resize(size); + let image_handle = images.add(image); - let image_handle = images.add(image); + // This specifies the layer used for the post processing camera, which will be attached to the post processing camera and 2d quad. + let post_processing_pass_layer = + RenderLayers::layer((RenderLayers::TOTAL_LAYERS - 1) as u8); - // This specifies the layer used for the post processing camera, which will be attached to the post processing camera and 2d quad. - let post_processing_pass_layer = RenderLayers::layer((RenderLayers::TOTAL_LAYERS - 1) as u8); + let quad_handle = meshes.add(Mesh::from(shape::Quad::new(Vec2::new( + size.width as f32, + size.height as f32, + )))); - let quad_handle = meshes.add(Mesh::from(shape::Quad::new(Vec2::new( - size.width as f32, - size.height as f32, - )))); + // This material has the texture that has been rendered. + let material_handle = post_processing_materials.add(ColorBlindnessMaterial { + source_image: image_handle.clone(), + percentages: color_blindness_camera.mode.percentages(), + }); - // This material has the texture that has been rendered. - let material_handle = post_processing_materials.add(PostProcessingMaterial { - source_image: image_handle.clone(), - percentages: params.mode.percentages(), - }); + commands + .entity(entity) + // add the handle to the camera so we can access it and change the percentages + .insert(material_handle.clone()) + // also disable show_ui so UI elements don't get rendered twice + .insert(UiCameraConfig { show_ui: false }); - commands.insert_resource(InternalResource { - image: image_handle, - post: material_handle.clone(), - }); + camera.target = RenderTarget::Image(image_handle); - // Post processing 2d quad, with material using the render texture done by the main camera, with a custom shader. - commands - .spawn_bundle(MaterialMesh2dBundle { - mesh: quad_handle.into(), - material: material_handle, - transform: Transform { - translation: Vec3::new(0.0, 0.0, 1.5), + // Post processing 2d quad, with material using the render texture done by the main camera, with a custom shader. + commands + .spawn_bundle(MaterialMesh2dBundle { + mesh: quad_handle.into(), + material: material_handle, + transform: Transform { + translation: Vec3::new(0.0, 0.0, 1.5), + ..Default::default() + }, ..Default::default() - }, - ..Default::default() - }) - .insert(post_processing_pass_layer); + }) + .insert(post_processing_pass_layer); - // The post-processing pass camera. - commands - .spawn_bundle(Camera2dBundle { - camera: Camera { - // renders after the first main camera which has default value: 0. - priority: 1, - ..Default::default() - }, - ..Camera2dBundle::default() - }) - .insert(post_processing_pass_layer); + // The post-processing pass camera. + commands + .spawn_bundle(Camera2dBundle { + camera: Camera { + // renders after the first main camera which has default value: 0. + priority: 1, + ..Default::default() + }, + ..Camera2dBundle::default() + }) + .insert(post_processing_pass_layer); + } } diff --git a/examples/accessibility/color_blindness_simulation.rs b/examples/accessibility/color_blindness_simulation.rs index 5e3c8293844b2..190ac3f8b013c 100644 --- a/examples/accessibility/color_blindness_simulation.rs +++ b/examples/accessibility/color_blindness_simulation.rs @@ -9,11 +9,6 @@ use bevy::{prelude::*, window::close_on_esc}; fn main() { App::new() .add_plugins(DefaultPlugins) - // you can select the color blindness simulation mode with the `ColorBlindnessParams` resource - .insert_resource(ColorBlindnessParams { - mode: ColorBlindnessMode::Deuteranomaly, - ..default() - }) // add the plugin .add_plugin(ColorBlindnessPlugin) .add_startup_system(setup) @@ -75,16 +70,20 @@ fn setup( transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y), ..default() }) - // IMPORTANT: add this component to your main camera - .insert(ColorBlindnessCamera); + .insert(ColorBlindnessCamera { + mode: ColorBlindnessMode::Deuteranopia, + enabled: false, + }); } -fn change_mode(input: Res>, mut params: ResMut) { - // cycle through the modes by pressing N - if input.just_pressed(KeyCode::N) { - params.mode.cycle(); - println!("Changed to {:?}", params.mode); - } +fn change_mode(input: Res>, mut cameras: Query<&mut ColorBlindnessCamera>) { + for mut camera in &mut cameras { + // cycle through the modes by pressing N + if input.just_pressed(KeyCode::N) { + camera.mode.cycle(); + println!("Changed to {:?}", camera.mode); + } - params.enable = input.pressed(KeyCode::Space); + camera.enabled = input.pressed(KeyCode::Space); + } } From 278f5680cbe62f3f9af737f388ed545209eceb7c Mon Sep 17 00:00:00 2001 From: annieversary Date: Sun, 14 Aug 2022 19:06:49 +0100 Subject: [PATCH 10/10] Respect render target --- crates/bevy_color_blindness/src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/bevy_color_blindness/src/lib.rs b/crates/bevy_color_blindness/src/lib.rs index 5a72c8a675c61..42b6c27c9576a 100644 --- a/crates/bevy_color_blindness/src/lib.rs +++ b/crates/bevy_color_blindness/src/lib.rs @@ -324,6 +324,8 @@ fn setup_new_color_blindness_cameras( mut cameras: Query<(Entity, &mut Camera, &ColorBlindnessCamera), Added>, ) { for (entity, mut camera, color_blindness_camera) in &mut cameras { + let original_target = camera.target.clone(); + // Get the size the camera is rendering to let size = match &camera.target { RenderTarget::Window(window_id) => { @@ -406,6 +408,8 @@ fn setup_new_color_blindness_cameras( camera: Camera { // renders after the first main camera which has default value: 0. priority: 1, + // set this new camera to render to where the other camera was rendering + target: original_target, ..Default::default() }, ..Camera2dBundle::default()