1use core::f32;
4
5use bevy::{
6 camera::Hdr,
7 camera_controller::free_camera::{FreeCamera, FreeCameraPlugin},
8 feathers::{theme::UiTheme, FeathersPlugins},
9 light::ParallaxCorrection,
10 prelude::*,
11 ui_widgets::{radio_self_update, ValueChange},
12};
13
14#[path = "../helpers/radio.rs"]
15mod radio;
16
17#[path = "../helpers/theme.rs"]
18mod theme;
19
20use crate::radio::{feathers_option_buttons, main_ui_node_scene, RadioButtonOptionValue};
21use crate::theme::basic_example_theme;
22
23#[derive(Clone, Component)]
25struct InnerCube;
26
27const ENVIRONMENT_MAP_INTENSITY: f32 = 100.0;
33
34const OUTER_CUBE_URL: &str =
35 "https://raw.githubusercontent.com/bevyengine/bevy_asset_files/main/pccm_example/outer_cube.glb#Scene0";
36const ENV_DIFFUSE_URL: &str =
37 "https://raw.githubusercontent.com/bevyengine/bevy_asset_files/main/pccm_example/env_diffuse.ktx2";
38const ENV_SPECULAR_URL: &str =
39 "https://raw.githubusercontent.com/bevyengine/bevy_asset_files/main/pccm_example/env_specular.ktx2";
40
41#[derive(Resource, Default)]
43struct AppStatus {
44 pccm_enabled: PccmEnableStatus,
46}
47
48#[derive(Clone, Copy, PartialEq, Default)]
50enum PccmEnableStatus {
51 #[default]
53 Enabled,
54 Disabled,
56}
57
58fn main() {
60 App::new()
61 .insert_resource(UiTheme(basic_example_theme(Color::WHITE)))
62 .add_plugins((
63 DefaultPlugins.set(WindowPlugin {
64 primary_window: Some(Window {
65 title: "Bevy Parallax-Corrected Cubemaps Example".into(),
66 ..default()
67 }),
68 ..default()
69 }),
70 FreeCameraPlugin,
71 FeathersPlugins,
72 ))
73 .init_resource::<AppStatus>()
74 .add_systems(Startup, setup)
75 .add_observer(handle_pccm_enable_change)
76 .add_observer(radio_self_update)
77 .run();
78}
79
80fn setup(
82 mut commands: Commands,
83 asset_server: Res<AssetServer>,
84 mut meshes: ResMut<Assets<Mesh>>,
85 mut materials: ResMut<Assets<StandardMaterial>>,
86) {
87 commands.spawn(WorldAssetRoot(asset_server.load(OUTER_CUBE_URL)));
89
90 spawn_camera(&mut commands);
91 spawn_inner_cube(&mut commands, &mut meshes, &mut materials);
92 spawn_reflection_probe(&mut commands, &asset_server);
93 spawn_buttons(&mut commands);
94}
95
96fn spawn_camera(commands: &mut Commands) {
98 commands.spawn((
99 Camera3d::default(),
100 FreeCamera::default(),
101 Transform::from_xyz(0.0, 0.0, 4.0).looking_at(Vec3::new(0.0, -2.5, 0.0), Dir3::Y),
102 Hdr,
103 ));
104}
105
106fn spawn_inner_cube(
108 commands: &mut Commands,
109 meshes: &mut Assets<Mesh>,
110 materials: &mut Assets<StandardMaterial>,
111) {
112 let cube_mesh = meshes.add(
113 Cuboid {
114 half_size: Vec3::new(5.0, 1.0, 2.0),
115 }
116 .mesh()
117 .build()
118 .with_duplicated_vertices()
119 .with_computed_flat_normals(),
120 );
121 let cube_material = materials.add(StandardMaterial {
122 base_color: Color::WHITE,
123 metallic: 1.0,
124 reflectance: 1.0,
125 perceptual_roughness: 0.0,
126 ..default()
127 });
128
129 commands.spawn((
130 Mesh3d(cube_mesh),
131 MeshMaterial3d(cube_material),
132 Transform::from_xyz(0.0, -4.0, -2.5),
133 InnerCube,
134 ));
135}
136
137fn spawn_reflection_probe(commands: &mut Commands, asset_server: &AssetServer) {
139 let diffuse_map = asset_server.load(ENV_DIFFUSE_URL);
140 let specular_map = asset_server.load(ENV_SPECULAR_URL);
141 commands.spawn((
142 LightProbe::default(),
143 EnvironmentMapLight {
144 diffuse_map,
145 specular_map,
146 intensity: ENVIRONMENT_MAP_INTENSITY,
147 ..default()
148 },
149 Transform::from_scale(Vec3::splat(10.01)),
153 ));
154}
155
156fn spawn_buttons(commands: &mut Commands) {
158 commands.spawn_scene(bsn! {
159 @main_ui_node_scene()
160 Children [
161 @feathers_option_buttons(
162 "Parallax Correction",
163 &[
164 (PccmEnableStatus::Enabled, "On"),
165 (PccmEnableStatus::Disabled, "Off"),
166 ],
167 0,
168 )
169 ]
170 });
171}
172
173fn handle_pccm_enable_change(
175 event: On<ValueChange<Entity>>,
176 new_value_query: Query<&RadioButtonOptionValue<PccmEnableStatus>>,
177 mut commands: Commands,
178 light_probe_query: Query<Entity, With<LightProbe>>,
179 mut app_status: ResMut<AppStatus>,
180) {
181 let Ok(RadioButtonOptionValue(pccm_enabled)) = new_value_query.get(event.value) else {
182 return;
183 };
184 app_status.pccm_enabled = *pccm_enabled;
185
186 let Some(light_probe_entity) = light_probe_query.iter().next() else {
187 return;
188 };
189 match app_status.pccm_enabled {
191 PccmEnableStatus::Enabled => {
192 commands
193 .entity(light_probe_entity)
194 .insert(ParallaxCorrection::Auto);
195 }
196 PccmEnableStatus::Disabled => {
197 commands
198 .entity(light_probe_entity)
199 .insert(ParallaxCorrection::None);
200 }
201 }
202}