1use std::f32::consts::PI;
21
22use bevy::{
23 color::palettes::css::{BLUE, GOLD, WHITE},
24 core_pipeline::{tonemapping::Tonemapping::AcesFitted, Skybox},
25 image::ImageLoaderSettings,
26 math::vec3,
27 prelude::*,
28 render::view::Hdr,
29};
30
31const SPHERE_SCALE: f32 = 0.9;
33
34const SPHERE_ROTATION_SPEED: f32 = 0.8;
36
37#[derive(Clone, Copy, PartialEq, Resource, Default)]
39enum LightMode {
40 #[default]
41 Point,
42 Directional,
43}
44
45#[derive(Component)]
47struct ExampleSphere;
48
49pub fn main() {
51 App::new()
52 .init_resource::<LightMode>()
53 .add_plugins(DefaultPlugins)
54 .add_systems(Startup, setup)
55 .add_systems(Update, animate_light)
56 .add_systems(Update, animate_spheres)
57 .add_systems(Update, (handle_input, update_help_text).chain())
58 .run();
59}
60
61fn setup(
63 mut commands: Commands,
64 mut meshes: ResMut<Assets<Mesh>>,
65 mut materials: ResMut<Assets<StandardMaterial>>,
66 asset_server: Res<AssetServer>,
67 light_mode: Res<LightMode>,
68) {
69 let sphere = create_sphere_mesh(&mut meshes);
70 spawn_car_paint_sphere(&mut commands, &mut materials, &asset_server, &sphere);
71 spawn_coated_glass_bubble_sphere(&mut commands, &mut materials, &sphere);
72 spawn_golf_ball(&mut commands, &asset_server);
73 spawn_scratched_gold_ball(&mut commands, &mut materials, &asset_server, &sphere);
74
75 spawn_light(&mut commands);
76 spawn_camera(&mut commands, &asset_server);
77 spawn_text(&mut commands, &light_mode);
78}
79
80fn create_sphere_mesh(meshes: &mut Assets<Mesh>) -> Handle<Mesh> {
82 let mut sphere_mesh = Sphere::new(1.0).mesh().build();
86 sphere_mesh
87 .generate_tangents()
88 .expect("Failed to generate tangents");
89 meshes.add(sphere_mesh)
90}
91
92fn spawn_car_paint_sphere(
94 commands: &mut Commands,
95 materials: &mut Assets<StandardMaterial>,
96 asset_server: &AssetServer,
97 sphere: &Handle<Mesh>,
98) {
99 commands
100 .spawn((
101 Mesh3d(sphere.clone()),
102 MeshMaterial3d(materials.add(StandardMaterial {
103 clearcoat: 1.0,
104 clearcoat_perceptual_roughness: 0.1,
105 normal_map_texture: Some(asset_server.load_with_settings(
106 "textures/BlueNoise-Normal.png",
107 |settings: &mut ImageLoaderSettings| settings.is_srgb = false,
108 )),
109 metallic: 0.9,
110 perceptual_roughness: 0.5,
111 base_color: BLUE.into(),
112 ..default()
113 })),
114 Transform::from_xyz(-1.0, 1.0, 0.0).with_scale(Vec3::splat(SPHERE_SCALE)),
115 ))
116 .insert(ExampleSphere);
117}
118
119fn spawn_coated_glass_bubble_sphere(
121 commands: &mut Commands,
122 materials: &mut Assets<StandardMaterial>,
123 sphere: &Handle<Mesh>,
124) {
125 commands
126 .spawn((
127 Mesh3d(sphere.clone()),
128 MeshMaterial3d(materials.add(StandardMaterial {
129 clearcoat: 1.0,
130 clearcoat_perceptual_roughness: 0.1,
131 metallic: 0.5,
132 perceptual_roughness: 0.1,
133 base_color: Color::srgba(0.9, 0.9, 0.9, 0.3),
134 alpha_mode: AlphaMode::Blend,
135 ..default()
136 })),
137 Transform::from_xyz(-1.0, -1.0, 0.0).with_scale(Vec3::splat(SPHERE_SCALE)),
138 ))
139 .insert(ExampleSphere);
140}
141
142fn spawn_golf_ball(commands: &mut Commands, asset_server: &AssetServer) {
148 commands.spawn((
149 SceneRoot(
150 asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/GolfBall/GolfBall.glb")),
151 ),
152 Transform::from_xyz(1.0, 1.0, 0.0).with_scale(Vec3::splat(SPHERE_SCALE)),
153 ExampleSphere,
154 ));
155}
156
157fn spawn_scratched_gold_ball(
160 commands: &mut Commands,
161 materials: &mut Assets<StandardMaterial>,
162 asset_server: &AssetServer,
163 sphere: &Handle<Mesh>,
164) {
165 commands
166 .spawn((
167 Mesh3d(sphere.clone()),
168 MeshMaterial3d(materials.add(StandardMaterial {
169 clearcoat: 1.0,
170 clearcoat_perceptual_roughness: 0.3,
171 clearcoat_normal_texture: Some(asset_server.load_with_settings(
172 "textures/ScratchedGold-Normal.png",
173 |settings: &mut ImageLoaderSettings| settings.is_srgb = false,
174 )),
175 metallic: 0.9,
176 perceptual_roughness: 0.1,
177 base_color: GOLD.into(),
178 ..default()
179 })),
180 Transform::from_xyz(1.0, -1.0, 0.0).with_scale(Vec3::splat(SPHERE_SCALE)),
181 ))
182 .insert(ExampleSphere);
183}
184
185fn spawn_light(commands: &mut Commands) {
187 commands.spawn(create_point_light());
188}
189
190fn spawn_camera(commands: &mut Commands, asset_server: &AssetServer) {
192 commands
193 .spawn((
194 Camera3d::default(),
195 Hdr,
196 Projection::Perspective(PerspectiveProjection {
197 fov: 27.0 / 180.0 * PI,
198 ..default()
199 }),
200 Transform::from_xyz(0.0, 0.0, 10.0),
201 AcesFitted,
202 ))
203 .insert(Skybox {
204 brightness: 5000.0,
205 image: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
206 ..default()
207 })
208 .insert(EnvironmentMapLight {
209 diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"),
210 specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
211 intensity: 2000.0,
212 ..default()
213 });
214}
215
216fn spawn_text(commands: &mut Commands, light_mode: &LightMode) {
218 commands.spawn((
219 light_mode.create_help_text(),
220 Node {
221 position_type: PositionType::Absolute,
222 bottom: px(12),
223 left: px(12),
224 ..default()
225 },
226 ));
227}
228
229fn animate_light(
231 mut lights: Query<&mut Transform, Or<(With<PointLight>, With<DirectionalLight>)>>,
232 time: Res<Time>,
233) {
234 let now = time.elapsed_secs();
235 for mut transform in lights.iter_mut() {
236 transform.translation = vec3(
237 ops::sin(now * 1.4),
238 ops::cos(now * 1.0),
239 ops::cos(now * 0.6),
240 ) * vec3(3.0, 4.0, 3.0);
241 transform.look_at(Vec3::ZERO, Vec3::Y);
242 }
243}
244
245fn animate_spheres(mut spheres: Query<&mut Transform, With<ExampleSphere>>, time: Res<Time>) {
247 let now = time.elapsed_secs();
248 for mut transform in spheres.iter_mut() {
249 transform.rotation = Quat::from_rotation_y(SPHERE_ROTATION_SPEED * now);
250 }
251}
252
253fn handle_input(
256 mut commands: Commands,
257 mut light_query: Query<Entity, Or<(With<PointLight>, With<DirectionalLight>)>>,
258 keyboard: Res<ButtonInput<KeyCode>>,
259 mut light_mode: ResMut<LightMode>,
260) {
261 if !keyboard.just_pressed(KeyCode::Space) {
262 return;
263 }
264
265 for light in light_query.iter_mut() {
266 match *light_mode {
267 LightMode::Point => {
268 *light_mode = LightMode::Directional;
269 commands
270 .entity(light)
271 .remove::<PointLight>()
272 .insert(create_directional_light());
273 }
274 LightMode::Directional => {
275 *light_mode = LightMode::Point;
276 commands
277 .entity(light)
278 .remove::<DirectionalLight>()
279 .insert(create_point_light());
280 }
281 }
282 }
283}
284
285fn update_help_text(mut text_query: Query<&mut Text>, light_mode: Res<LightMode>) {
287 for mut text in text_query.iter_mut() {
288 *text = light_mode.create_help_text();
289 }
290}
291
292fn create_point_light() -> PointLight {
294 PointLight {
295 color: WHITE.into(),
296 intensity: 100000.0,
297 ..default()
298 }
299}
300
301fn create_directional_light() -> DirectionalLight {
303 DirectionalLight {
304 color: WHITE.into(),
305 illuminance: 1000.0,
306 ..default()
307 }
308}
309
310impl LightMode {
311 fn create_help_text(&self) -> Text {
313 let help_text = match *self {
314 LightMode::Point => "Press Space to switch to a directional light",
315 LightMode::Directional => "Press Space to switch to a point light",
316 };
317
318 Text::new(help_text)
319 }
320}