pub const fn vec3(x: f32, y: f32, z: f32) -> Vec3
Expand description
Creates a 3-dimensional vector.
Examples found in repository?
More examples
examples/3d/anisotropy.rs (line 14)
14const CAMERA_INITIAL_POSITION: Vec3 = vec3(-0.4, 0.0, 0.0);
15
16/// The current settings of the app, as chosen by the user.
17#[derive(Resource)]
18struct AppStatus {
19 /// Which type of light is in the scene.
20 light_mode: LightMode,
21 /// Whether anisotropy is enabled.
22 anisotropy_enabled: bool,
23 /// Which mesh is visible
24 visible_scene: Scene,
25}
26
27/// Which type of light we're using: a directional light, a point light, or an
28/// environment map.
29#[derive(Clone, Copy, PartialEq, Default)]
30enum LightMode {
31 /// A rotating directional light.
32 #[default]
33 Directional,
34 /// A rotating point light.
35 Point,
36 /// An environment map (image-based lighting, including skybox).
37 EnvironmentMap,
38}
39
40/// A component that stores the version of the material with anisotropy and the
41/// version of the material without it.
42///
43/// This is placed on each mesh with a material. It exists so that the
44/// appropriate system can replace the materials when the user presses Enter to
45/// turn anisotropy on and off.
46#[derive(Component)]
47struct MaterialVariants {
48 /// The version of the material in the glTF file, with anisotropy.
49 anisotropic: Handle<StandardMaterial>,
50 /// The version of the material with anisotropy removed.
51 isotropic: Handle<StandardMaterial>,
52}
53
54#[derive(Default, Clone, Copy, PartialEq, Eq, Component)]
55enum Scene {
56 #[default]
57 BarnLamp,
58 Sphere,
59}
60
61impl Scene {
62 fn next(&self) -> Self {
63 match self {
64 Self::BarnLamp => Self::Sphere,
65 Self::Sphere => Self::BarnLamp,
66 }
67 }
68}
69
70impl Display for Scene {
71 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72 let scene_name = match self {
73 Self::BarnLamp => "Barn Lamp",
74 Self::Sphere => "Sphere",
75 };
76 write!(f, "{scene_name}")
77 }
78}
79
80/// The application entry point.
81fn main() {
82 App::new()
83 .init_resource::<AppStatus>()
84 .add_plugins(DefaultPlugins.set(WindowPlugin {
85 primary_window: Some(Window {
86 title: "Bevy Anisotropy Example".into(),
87 ..default()
88 }),
89 ..default()
90 }))
91 .add_systems(Startup, setup)
92 .add_systems(Update, create_material_variants)
93 .add_systems(Update, animate_light)
94 .add_systems(Update, rotate_camera)
95 .add_systems(Update, (handle_input, update_help_text).chain())
96 .run();
97}
98
99/// Creates the initial scene.
100fn setup(mut commands: Commands, asset_server: Res<AssetServer>, app_status: Res<AppStatus>) {
101 commands.spawn((
102 Camera3d::default(),
103 Transform::from_translation(CAMERA_INITIAL_POSITION).looking_at(Vec3::ZERO, Vec3::Y),
104 ));
105
106 spawn_directional_light(&mut commands);
107
108 commands.spawn((
109 SceneRoot(asset_server.load("models/AnisotropyBarnLamp/AnisotropyBarnLamp.gltf#Scene0")),
110 Transform::from_xyz(0.0, 0.07, -0.13),
111 Scene::BarnLamp,
112 ));
113
114 commands.spawn((
115 Mesh3d(
116 asset_server.add(
117 Mesh::from(Sphere::new(0.1))
118 .with_generated_tangents()
119 .unwrap(),
120 ),
121 ),
122 MeshMaterial3d(asset_server.add(StandardMaterial {
123 base_color: palettes::tailwind::GRAY_300.into(),
124 anisotropy_rotation: 0.5,
125 anisotropy_strength: 1.,
126 ..default()
127 })),
128 Scene::Sphere,
129 Visibility::Hidden,
130 ));
131
132 spawn_text(&mut commands, &app_status);
133}
134
135/// Spawns the help text.
136fn spawn_text(commands: &mut Commands, app_status: &AppStatus) {
137 commands.spawn((
138 app_status.create_help_text(),
139 Node {
140 position_type: PositionType::Absolute,
141 bottom: Val::Px(12.0),
142 left: Val::Px(12.0),
143 ..default()
144 },
145 ));
146}
147
148/// For each material, creates a version with the anisotropy removed.
149///
150/// This allows the user to press Enter to toggle anisotropy on and off.
151fn create_material_variants(
152 mut commands: Commands,
153 mut materials: ResMut<Assets<StandardMaterial>>,
154 new_meshes: Query<
155 (Entity, &MeshMaterial3d<StandardMaterial>),
156 (
157 Added<MeshMaterial3d<StandardMaterial>>,
158 Without<MaterialVariants>,
159 ),
160 >,
161) {
162 for (entity, anisotropic_material_handle) in new_meshes.iter() {
163 let Some(anisotropic_material) = materials.get(anisotropic_material_handle).cloned() else {
164 continue;
165 };
166
167 commands.entity(entity).insert(MaterialVariants {
168 anisotropic: anisotropic_material_handle.0.clone(),
169 isotropic: materials.add(StandardMaterial {
170 anisotropy_texture: None,
171 anisotropy_strength: 0.0,
172 anisotropy_rotation: 0.0,
173 ..anisotropic_material
174 }),
175 });
176 }
177}
178
179/// A system that animates the light every frame, if there is one.
180fn animate_light(
181 mut lights: Query<&mut Transform, Or<(With<DirectionalLight>, With<PointLight>)>>,
182 time: Res<Time>,
183) {
184 let now = time.elapsed_secs();
185 for mut transform in lights.iter_mut() {
186 transform.translation = vec3(ops::cos(now), 1.0, ops::sin(now)) * vec3(3.0, 4.0, 3.0);
187 transform.look_at(Vec3::ZERO, Vec3::Y);
188 }
189}
examples/3d/mixed_lighting.rs (line 114)
114const INITIAL_SPHERE_POSITION: Vec3 = vec3(0.0, 0.5233223, 0.0);
115
116fn main() {
117 App::new()
118 .add_plugins(DefaultPlugins.set(WindowPlugin {
119 primary_window: Some(Window {
120 title: "Bevy Mixed Lighting Example".into(),
121 ..default()
122 }),
123 ..default()
124 }))
125 .add_plugins(MeshPickingPlugin)
126 .insert_resource(AmbientLight {
127 color: ClearColor::default().0,
128 brightness: 10000.0,
129 affects_lightmapped_meshes: true,
130 })
131 .init_resource::<AppStatus>()
132 .add_event::<WidgetClickEvent<LightingMode>>()
133 .add_event::<LightingModeChanged>()
134 .add_systems(Startup, setup)
135 .add_systems(Update, update_lightmaps)
136 .add_systems(Update, update_directional_light)
137 .add_systems(Update, make_sphere_nonpickable)
138 .add_systems(Update, update_radio_buttons)
139 .add_systems(Update, handle_lighting_mode_change)
140 .add_systems(Update, widgets::handle_ui_interactions::<LightingMode>)
141 .add_systems(Update, reset_sphere_position)
142 .add_systems(Update, move_sphere)
143 .add_systems(Update, adjust_help_text)
144 .run();
145}
146
147/// Creates the scene.
148fn setup(mut commands: Commands, asset_server: Res<AssetServer>, app_status: Res<AppStatus>) {
149 spawn_camera(&mut commands);
150 spawn_scene(&mut commands, &asset_server);
151 spawn_buttons(&mut commands);
152 spawn_help_text(&mut commands, &app_status);
153}
154
155/// Spawns the 3D camera.
156fn spawn_camera(commands: &mut Commands) {
157 commands
158 .spawn(Camera3d::default())
159 .insert(Transform::from_xyz(-0.7, 0.7, 1.0).looking_at(vec3(0.0, 0.3, 0.0), Vec3::Y));
160}
161
162/// Spawns the scene.
163///
164/// The scene is loaded from a glTF file.
165fn spawn_scene(commands: &mut Commands, asset_server: &AssetServer) {
166 commands
167 .spawn(SceneRoot(
168 asset_server.load(
169 GltfAssetLabel::Scene(0)
170 .from_asset("models/MixedLightingExample/MixedLightingExample.gltf"),
171 ),
172 ))
173 .observe(
174 |_: Trigger<SceneInstanceReady>,
175 mut lighting_mode_change_event_writer: EventWriter<LightingModeChanged>| {
176 // When the scene loads, send a `LightingModeChanged` event so
177 // that we set up the lightmaps.
178 lighting_mode_change_event_writer.write(LightingModeChanged);
179 },
180 );
181}
182
183/// Spawns the buttons that allow the user to change the lighting mode.
184fn spawn_buttons(commands: &mut Commands) {
185 commands
186 .spawn(widgets::main_ui_node())
187 .with_children(|parent| {
188 widgets::spawn_option_buttons(
189 parent,
190 "Lighting",
191 &[
192 (LightingMode::Baked, "Baked"),
193 (LightingMode::MixedDirect, "Mixed (Direct)"),
194 (LightingMode::MixedIndirect, "Mixed (Indirect)"),
195 (LightingMode::RealTime, "Real-Time"),
196 ],
197 );
198 });
199}
200
201/// Spawns the help text at the top of the window.
202fn spawn_help_text(commands: &mut Commands, app_status: &AppStatus) {
203 commands.spawn((
204 create_help_text(app_status),
205 Node {
206 position_type: PositionType::Absolute,
207 top: Val::Px(12.0),
208 left: Val::Px(12.0),
209 ..default()
210 },
211 HelpText,
212 ));
213}
214
215/// Adds lightmaps to and/or removes lightmaps from objects in the scene when
216/// the lighting mode changes.
217///
218/// This is also called right after the scene loads in order to set up the
219/// lightmaps.
220fn update_lightmaps(
221 mut commands: Commands,
222 asset_server: Res<AssetServer>,
223 mut materials: ResMut<Assets<StandardMaterial>>,
224 meshes: Query<(Entity, &Name, &MeshMaterial3d<StandardMaterial>), With<Mesh3d>>,
225 mut lighting_mode_change_event_reader: EventReader<LightingModeChanged>,
226 app_status: Res<AppStatus>,
227) {
228 // Only run if the lighting mode changed. (Note that a change event is fired
229 // when the scene first loads.)
230 if lighting_mode_change_event_reader.read().next().is_none() {
231 return;
232 }
233
234 // Select the lightmap to use, based on the lighting mode.
235 let lightmap: Option<Handle<Image>> = match app_status.lighting_mode {
236 LightingMode::Baked => {
237 Some(asset_server.load("lightmaps/MixedLightingExample-Baked.zstd.ktx2"))
238 }
239 LightingMode::MixedDirect => {
240 Some(asset_server.load("lightmaps/MixedLightingExample-MixedDirect.zstd.ktx2"))
241 }
242 LightingMode::MixedIndirect => {
243 Some(asset_server.load("lightmaps/MixedLightingExample-MixedIndirect.zstd.ktx2"))
244 }
245 LightingMode::RealTime => None,
246 };
247
248 'outer: for (entity, name, material) in &meshes {
249 // Add lightmaps to or remove lightmaps from the scenery objects in the
250 // scene (all objects but the sphere).
251 //
252 // Note that doing a linear search through the `LIGHTMAPS` array is
253 // inefficient, but we do it anyway in this example to improve clarity.
254 for (lightmap_name, uv_rect) in LIGHTMAPS {
255 if &**name != lightmap_name {
256 continue;
257 }
258
259 // Lightmap exposure defaults to zero, so we need to set it.
260 if let Some(ref mut material) = materials.get_mut(material) {
261 material.lightmap_exposure = LIGHTMAP_EXPOSURE;
262 }
263
264 // Add or remove the lightmap.
265 match lightmap {
266 Some(ref lightmap) => {
267 commands.entity(entity).insert(Lightmap {
268 image: (*lightmap).clone(),
269 uv_rect,
270 bicubic_sampling: false,
271 });
272 }
273 None => {
274 commands.entity(entity).remove::<Lightmap>();
275 }
276 }
277 continue 'outer;
278 }
279
280 // Add lightmaps to or remove lightmaps from the sphere.
281 if &**name == "Sphere" {
282 // Lightmap exposure defaults to zero, so we need to set it.
283 if let Some(ref mut material) = materials.get_mut(material) {
284 material.lightmap_exposure = LIGHTMAP_EXPOSURE;
285 }
286
287 // Add or remove the lightmap from the sphere. We only apply the
288 // lightmap in fully-baked mode.
289 match (&lightmap, app_status.lighting_mode) {
290 (Some(lightmap), LightingMode::Baked) => {
291 commands.entity(entity).insert(Lightmap {
292 image: (*lightmap).clone(),
293 uv_rect: SPHERE_UV_RECT,
294 bicubic_sampling: false,
295 });
296 }
297 _ => {
298 commands.entity(entity).remove::<Lightmap>();
299 }
300 }
301 }
302 }
303}
304
305/// Converts a uv rectangle from the OpenGL coordinate system (origin in the
306/// lower left) to the Vulkan coordinate system (origin in the upper left) that
307/// Bevy uses.
308///
309/// For this particular example, the baking tool happened to use the OpenGL
310/// coordinate system, so it was more convenient to do the conversion at compile
311/// time than to pre-calculate and hard-code the values.
312const fn uv_rect_opengl(gl_min: Vec2, size: Vec2) -> Rect {
313 let min = vec2(gl_min.x, 1.0 - gl_min.y - size.y);
314 Rect {
315 min,
316 max: vec2(min.x + size.x, min.y + size.y),
317 }
318}
319
320/// Ensures that clicking on the scene to move the sphere doesn't result in a
321/// hit on the sphere itself.
322fn make_sphere_nonpickable(
323 mut commands: Commands,
324 mut query: Query<(Entity, &Name), (With<Mesh3d>, Without<Pickable>)>,
325) {
326 for (sphere, name) in &mut query {
327 if &**name == "Sphere" {
328 commands.entity(sphere).insert(Pickable::IGNORE);
329 }
330 }
331}
332
333/// Updates the directional light settings as necessary when the lighting mode
334/// changes.
335fn update_directional_light(
336 mut lights: Query<&mut DirectionalLight>,
337 mut lighting_mode_change_event_reader: EventReader<LightingModeChanged>,
338 app_status: Res<AppStatus>,
339) {
340 // Only run if the lighting mode changed. (Note that a change event is fired
341 // when the scene first loads.)
342 if lighting_mode_change_event_reader.read().next().is_none() {
343 return;
344 }
345
346 // Real-time direct light is used on the scenery if we're using mixed
347 // indirect or real-time mode.
348 let scenery_is_lit_in_real_time = matches!(
349 app_status.lighting_mode,
350 LightingMode::MixedIndirect | LightingMode::RealTime
351 );
352
353 for mut light in &mut lights {
354 light.affects_lightmapped_mesh_diffuse = scenery_is_lit_in_real_time;
355 // Don't bother enabling shadows if they won't show up on the scenery.
356 light.shadows_enabled = scenery_is_lit_in_real_time;
357 }
358}
359
360/// Updates the state of the selection widgets at the bottom of the window when
361/// the lighting mode changes.
362fn update_radio_buttons(
363 mut widgets: Query<
364 (
365 Entity,
366 Option<&mut BackgroundColor>,
367 Has<Text>,
368 &WidgetClickSender<LightingMode>,
369 ),
370 Or<(With<RadioButton>, With<RadioButtonText>)>,
371 >,
372 app_status: Res<AppStatus>,
373 mut writer: TextUiWriter,
374) {
375 for (entity, image, has_text, sender) in &mut widgets {
376 let selected = **sender == app_status.lighting_mode;
377
378 if let Some(mut bg_color) = image {
379 widgets::update_ui_radio_button(&mut bg_color, selected);
380 }
381 if has_text {
382 widgets::update_ui_radio_button_text(entity, &mut writer, selected);
383 }
384 }
385}
386
387/// Handles clicks on the widgets at the bottom of the screen and fires
388/// [`LightingModeChanged`] events.
389fn handle_lighting_mode_change(
390 mut widget_click_event_reader: EventReader<WidgetClickEvent<LightingMode>>,
391 mut lighting_mode_change_event_writer: EventWriter<LightingModeChanged>,
392 mut app_status: ResMut<AppStatus>,
393) {
394 for event in widget_click_event_reader.read() {
395 app_status.lighting_mode = **event;
396 lighting_mode_change_event_writer.write(LightingModeChanged);
397 }
398}
399
400/// Moves the sphere to its original position when the user selects the baked
401/// lighting mode.
402///
403/// As the light from the sphere is precomputed and depends on the sphere's
404/// original position, the sphere must be placed there in order for the lighting
405/// to be correct.
406fn reset_sphere_position(
407 mut objects: Query<(&Name, &mut Transform)>,
408 mut lighting_mode_change_event_reader: EventReader<LightingModeChanged>,
409 app_status: Res<AppStatus>,
410) {
411 // Only run if the lighting mode changed and if the lighting mode is
412 // `LightingMode::Baked`. (Note that a change event is fired when the scene
413 // first loads.)
414 if lighting_mode_change_event_reader.read().next().is_none()
415 || app_status.lighting_mode != LightingMode::Baked
416 {
417 return;
418 }
419
420 for (name, mut transform) in &mut objects {
421 if &**name == "Sphere" {
422 transform.translation = INITIAL_SPHERE_POSITION;
423 break;
424 }
425 }
426}
427
428/// Updates the position of the sphere when the user clicks on a spot in the
429/// scene.
430///
431/// Note that the position of the sphere is locked in baked lighting mode.
432fn move_sphere(
433 mouse_button_input: Res<ButtonInput<MouseButton>>,
434 pointers: Query<&PointerInteraction>,
435 mut meshes: Query<(&Name, &ChildOf), With<Mesh3d>>,
436 mut transforms: Query<&mut Transform>,
437 app_status: Res<AppStatus>,
438) {
439 // Only run when the left button is clicked and we're not in baked lighting
440 // mode.
441 if app_status.lighting_mode == LightingMode::Baked
442 || !mouse_button_input.pressed(MouseButton::Left)
443 {
444 return;
445 }
446
447 // Find the sphere.
448 let Some(child_of) = meshes
449 .iter_mut()
450 .filter_map(|(name, child_of)| {
451 if &**name == "Sphere" {
452 Some(child_of)
453 } else {
454 None
455 }
456 })
457 .next()
458 else {
459 return;
460 };
461
462 // Grab its transform.
463 let Ok(mut transform) = transforms.get_mut(child_of.parent()) else {
464 return;
465 };
466
467 // Set its transform to the appropriate position, as determined by the
468 // picking subsystem.
469 for interaction in pointers.iter() {
470 if let Some(&(
471 _,
472 HitData {
473 position: Some(position),
474 ..
475 },
476 )) = interaction.get_nearest_hit()
477 {
478 transform.translation = position + vec3(0.0, SPHERE_OFFSET, 0.0);
479 }
480 }
481}
examples/3d/clearcoat.rs (lines 238-242)
232fn animate_light(
233 mut lights: Query<&mut Transform, Or<(With<PointLight>, With<DirectionalLight>)>>,
234 time: Res<Time>,
235) {
236 let now = time.elapsed_secs();
237 for mut transform in lights.iter_mut() {
238 transform.translation = vec3(
239 ops::sin(now * 1.4),
240 ops::cos(now * 1.0),
241 ops::cos(now * 0.6),
242 ) * vec3(3.0, 4.0, 3.0);
243 transform.look_at(Vec3::ZERO, Vec3::Y);
244 }
245}
Additional examples can be found in: