use std::f32::consts::PI;
use bevy::{color::palettes::css::WHITE, core_pipeline::Skybox, prelude::*};
const ROTATION_SPEED: f32 = 0.005;
const HUE_SHIFT_SPEED: f32 = 0.2;
static SWITCH_TO_MAP_HELP_TEXT: &str = "Press Space to switch to a specular map";
static SWITCH_TO_SOLID_TINT_HELP_TEXT: &str = "Press Space to switch to a solid specular tint";
#[derive(Resource, Default)]
struct AppStatus {
tint_type: TintType,
hue: f32,
}
#[derive(Resource)]
struct AppAssets {
noise_texture: Handle<Image>,
}
impl FromWorld for AppAssets {
fn from_world(world: &mut World) -> Self {
let asset_server = world.resource::<AssetServer>();
Self {
noise_texture: asset_server.load("textures/AlphaNoise.png"),
}
}
}
#[derive(Clone, Copy, PartialEq, Default)]
enum TintType {
#[default]
Solid,
Map,
}
fn main() {
App::new()
.add_plugins(DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
title: "Bevy Specular Tint Example".into(),
..default()
}),
..default()
}))
.init_resource::<AppAssets>()
.init_resource::<AppStatus>()
.insert_resource(AmbientLight {
color: Color::BLACK,
brightness: 0.0,
..default()
})
.add_systems(Startup, setup)
.add_systems(Update, rotate_camera)
.add_systems(Update, (toggle_specular_map, update_text).chain())
.add_systems(Update, shift_hue.after(toggle_specular_map))
.run();
}
fn setup(
mut commands: Commands,
asset_server: Res<AssetServer>,
app_status: Res<AppStatus>,
mut meshes: ResMut<Assets<Mesh>>,
mut standard_materials: ResMut<Assets<StandardMaterial>>,
) {
commands.spawn((
Transform::from_xyz(-2.0, 0.0, 3.5).looking_at(Vec3::ZERO, Vec3::Y),
Camera {
hdr: true,
..default()
},
Camera3d::default(),
Skybox {
image: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
brightness: 3000.0,
..default()
},
EnvironmentMapLight {
diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"),
specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
intensity: 25000.0,
..default()
},
));
commands.spawn((
Transform::from_rotation(Quat::from_rotation_x(PI * 0.5)),
Mesh3d(meshes.add(Sphere::default().mesh().uv(32, 18))),
MeshMaterial3d(standard_materials.add(StandardMaterial {
base_color: Color::BLACK,
reflectance: 1.0,
specular_tint: Color::hsva(app_status.hue, 1.0, 1.0, 1.0),
metallic: 0.0,
perceptual_roughness: 0.0,
..default()
})),
));
commands.spawn((
Node {
position_type: PositionType::Absolute,
bottom: Val::Px(12.0),
left: Val::Px(12.0),
..default()
},
app_status.create_text(),
));
}
fn rotate_camera(mut cameras: Query<&mut Transform, With<Camera3d>>) {
for mut camera_transform in cameras.iter_mut() {
camera_transform.translation =
Quat::from_rotation_y(ROTATION_SPEED) * camera_transform.translation;
camera_transform.look_at(Vec3::ZERO, Vec3::Y);
}
}
fn shift_hue(
mut app_status: ResMut<AppStatus>,
objects_with_materials: Query<&MeshMaterial3d<StandardMaterial>>,
mut standard_materials: ResMut<Assets<StandardMaterial>>,
) {
if app_status.tint_type != TintType::Solid {
return;
}
app_status.hue += HUE_SHIFT_SPEED;
for material_handle in objects_with_materials.iter() {
let Some(material) = standard_materials.get_mut(material_handle) else {
continue;
};
material.specular_tint = Color::hsva(app_status.hue, 1.0, 1.0, 1.0);
}
}
impl AppStatus {
fn create_text(&self) -> Text {
let tint_map_help_text = match self.tint_type {
TintType::Solid => SWITCH_TO_MAP_HELP_TEXT,
TintType::Map => SWITCH_TO_SOLID_TINT_HELP_TEXT,
};
Text::new(tint_map_help_text)
}
}
fn toggle_specular_map(
keyboard: Res<ButtonInput<KeyCode>>,
mut app_status: ResMut<AppStatus>,
app_assets: Res<AppAssets>,
objects_with_materials: Query<&MeshMaterial3d<StandardMaterial>>,
mut standard_materials: ResMut<Assets<StandardMaterial>>,
) {
if !keyboard.just_pressed(KeyCode::Space) {
return;
}
app_status.tint_type = match app_status.tint_type {
TintType::Solid => TintType::Map,
TintType::Map => TintType::Solid,
};
for material_handle in objects_with_materials.iter() {
let Some(material) = standard_materials.get_mut(material_handle) else {
continue;
};
match app_status.tint_type {
TintType::Solid => {
material.reflectance = 1.0;
material.specular_tint_texture = None;
}
TintType::Map => {
material.reflectance = 2.0;
material.specular_tint = WHITE.into();
material.specular_tint_texture = Some(app_assets.noise_texture.clone());
}
};
}
}
fn update_text(mut text_query: Query<&mut Text>, app_status: Res<AppStatus>) {
for mut text in text_query.iter_mut() {
*text = app_status.create_text();
}
}