1use core::f32;
4
5use bevy::{
6 camera::Hdr,
7 camera_controller::free_camera::{FreeCamera, FreeCameraPlugin},
8 light::ParallaxCorrection,
9 prelude::*,
10};
11
12use crate::widgets::{WidgetClickEvent, WidgetClickSender};
13
14#[path = "../helpers/widgets.rs"]
15mod widgets;
16
17#[derive(Clone, Component)]
19struct InnerCube;
20
21const ENVIRONMENT_MAP_INTENSITY: f32 = 100.0;
27
28const OUTER_CUBE_URL: &str =
29 "https://github.com/bevyengine/bevy_asset_files/raw/main/pccm_example/outer_cube.glb#Scene0";
30const ENV_DIFFUSE_URL: &str =
31 "https://github.com/bevyengine/bevy_asset_files/raw/main/pccm_example/env_diffuse.ktx2";
32const ENV_SPECULAR_URL: &str =
33 "https://github.com/bevyengine/bevy_asset_files/raw/main/pccm_example/env_specular.ktx2";
34
35#[derive(Resource, Default)]
37struct AppStatus {
38 pccm_enabled: PccmEnableStatus,
40}
41
42#[derive(Clone, Copy, PartialEq, Default)]
44enum PccmEnableStatus {
45 #[default]
47 Enabled,
48 Disabled,
50}
51
52fn main() {
54 App::new()
55 .add_plugins((
56 DefaultPlugins.set(WindowPlugin {
57 primary_window: Some(Window {
58 title: "Bevy Parallax-Corrected Cubemaps Example".into(),
59 ..default()
60 }),
61 ..default()
62 }),
63 FreeCameraPlugin,
64 ))
65 .init_resource::<AppStatus>()
66 .add_message::<WidgetClickEvent<PccmEnableStatus>>()
67 .add_systems(Startup, setup)
68 .add_systems(Update, widgets::handle_ui_interactions::<PccmEnableStatus>)
69 .add_systems(
70 Update,
71 (handle_pccm_enable_change, update_radio_buttons)
72 .after(widgets::handle_ui_interactions::<PccmEnableStatus>),
73 )
74 .run();
75}
76
77fn setup(
79 mut commands: Commands,
80 asset_server: Res<AssetServer>,
81 mut meshes: ResMut<Assets<Mesh>>,
82 mut materials: ResMut<Assets<StandardMaterial>>,
83) {
84 commands.spawn(WorldAssetRoot(asset_server.load(OUTER_CUBE_URL)));
86
87 spawn_camera(&mut commands);
88 spawn_inner_cube(&mut commands, &mut meshes, &mut materials);
89 spawn_reflection_probe(&mut commands, &asset_server);
90 spawn_buttons(&mut commands);
91}
92
93fn spawn_camera(commands: &mut Commands) {
95 commands.spawn((
96 Camera3d::default(),
97 FreeCamera::default(),
98 Transform::from_xyz(0.0, 0.0, 4.0).looking_at(Vec3::new(0.0, -2.5, 0.0), Dir3::Y),
99 Hdr,
100 ));
101}
102
103fn spawn_inner_cube(
105 commands: &mut Commands,
106 meshes: &mut Assets<Mesh>,
107 materials: &mut Assets<StandardMaterial>,
108) {
109 let cube_mesh = meshes.add(
110 Cuboid {
111 half_size: Vec3::new(5.0, 1.0, 2.0),
112 }
113 .mesh()
114 .build()
115 .with_duplicated_vertices()
116 .with_computed_flat_normals(),
117 );
118 let cube_material = materials.add(StandardMaterial {
119 base_color: Color::WHITE,
120 metallic: 1.0,
121 reflectance: 1.0,
122 perceptual_roughness: 0.0,
123 ..default()
124 });
125
126 commands.spawn((
127 Mesh3d(cube_mesh),
128 MeshMaterial3d(cube_material),
129 Transform::from_xyz(0.0, -4.0, -2.5),
130 InnerCube,
131 ));
132}
133
134fn spawn_reflection_probe(commands: &mut Commands, asset_server: &AssetServer) {
136 let diffuse_map = asset_server.load(ENV_DIFFUSE_URL);
137 let specular_map = asset_server.load(ENV_SPECULAR_URL);
138 commands.spawn((
139 LightProbe::default(),
140 EnvironmentMapLight {
141 diffuse_map,
142 specular_map,
143 intensity: ENVIRONMENT_MAP_INTENSITY,
144 ..default()
145 },
146 Transform::from_scale(Vec3::splat(10.01)),
150 ));
151}
152
153fn spawn_buttons(commands: &mut Commands) {
155 commands.spawn((
156 widgets::main_ui_node(),
157 children![widgets::option_buttons(
158 "Parallax Correction",
159 &[
160 (PccmEnableStatus::Enabled, "On"),
161 (PccmEnableStatus::Disabled, "Off"),
162 ],
163 )],
164 ));
165}
166
167fn handle_pccm_enable_change(
169 mut commands: Commands,
170 light_probe_query: Query<Entity, With<LightProbe>>,
171 mut app_status: ResMut<AppStatus>,
172 mut messages: MessageReader<WidgetClickEvent<PccmEnableStatus>>,
173) {
174 let Some(light_probe_entity) = light_probe_query.iter().next() else {
175 return;
176 };
177
178 for message in messages.read() {
179 app_status.pccm_enabled = **message;
182
183 match **message {
185 PccmEnableStatus::Enabled => {
186 commands
187 .entity(light_probe_entity)
188 .insert(ParallaxCorrection::Auto);
189 }
190 PccmEnableStatus::Disabled => {
191 commands
192 .entity(light_probe_entity)
193 .insert(ParallaxCorrection::None);
194 }
195 }
196 }
197}
198
199fn update_radio_buttons(
202 mut widgets_query: Query<(
203 Entity,
204 Option<&mut BackgroundColor>,
205 Has<Text>,
206 &WidgetClickSender<PccmEnableStatus>,
207 )>,
208 app_status: Res<AppStatus>,
209 mut text_ui_writer: TextUiWriter,
210) {
211 for (entity, maybe_bg_color, has_text, sender) in &mut widgets_query {
212 let selected = app_status.pccm_enabled == **sender;
215
216 if let Some(mut bg_color) = maybe_bg_color {
217 widgets::update_ui_radio_button(&mut bg_color, selected);
218 }
219 if has_text {
220 widgets::update_ui_radio_button_text(entity, &mut text_ui_writer, selected);
221 }
222 }
223}