vec3

Function vec3 

Source
pub const fn vec3(x: f32, y: f32, z: f32) -> Vec3
Expand description

Creates a 3-dimensional vector.

Examples found in repository?
examples/3d/visibility_range.rs (line 15)
15const CAMERA_FOCAL_POINT: Vec3 = vec3(0.0, 0.3, 0.0);
More examples
Hide additional 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: px(12),
142            left: px(12),
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 115)
115const INITIAL_SPHERE_POSITION: Vec3 = vec3(0.0, 0.5233223, 0.0);
116
117fn main() {
118    App::new()
119        .add_plugins(DefaultPlugins.set(WindowPlugin {
120            primary_window: Some(Window {
121                title: "Bevy Mixed Lighting Example".into(),
122                ..default()
123            }),
124            ..default()
125        }))
126        .add_plugins(MeshPickingPlugin)
127        .insert_resource(AmbientLight {
128            color: ClearColor::default().0,
129            brightness: 10000.0,
130            affects_lightmapped_meshes: true,
131        })
132        .init_resource::<AppStatus>()
133        .add_message::<WidgetClickEvent<LightingMode>>()
134        .add_message::<LightingModeChanged>()
135        .add_systems(Startup, setup)
136        .add_systems(Update, update_lightmaps)
137        .add_systems(Update, update_directional_light)
138        .add_systems(Update, make_sphere_nonpickable)
139        .add_systems(Update, update_radio_buttons)
140        .add_systems(Update, handle_lighting_mode_change)
141        .add_systems(Update, widgets::handle_ui_interactions::<LightingMode>)
142        .add_systems(Update, reset_sphere_position)
143        .add_systems(Update, move_sphere)
144        .add_systems(Update, adjust_help_text)
145        .run();
146}
147
148/// Creates the scene.
149fn setup(mut commands: Commands, asset_server: Res<AssetServer>, app_status: Res<AppStatus>) {
150    spawn_camera(&mut commands);
151    spawn_scene(&mut commands, &asset_server);
152    spawn_buttons(&mut commands);
153    spawn_help_text(&mut commands, &app_status);
154}
155
156/// Spawns the 3D camera.
157fn spawn_camera(commands: &mut Commands) {
158    commands
159        .spawn(Camera3d::default())
160        .insert(Transform::from_xyz(-0.7, 0.7, 1.0).looking_at(vec3(0.0, 0.3, 0.0), Vec3::Y));
161}
162
163/// Spawns the scene.
164///
165/// The scene is loaded from a glTF file.
166fn spawn_scene(commands: &mut Commands, asset_server: &AssetServer) {
167    commands
168        .spawn(SceneRoot(
169            asset_server.load(
170                GltfAssetLabel::Scene(0)
171                    .from_asset("models/MixedLightingExample/MixedLightingExample.gltf"),
172            ),
173        ))
174        .observe(
175            |_: On<SceneInstanceReady>,
176             mut lighting_mode_changed_writer: MessageWriter<LightingModeChanged>| {
177                // When the scene loads, send a `LightingModeChanged` event so
178                // that we set up the lightmaps.
179                lighting_mode_changed_writer.write(LightingModeChanged);
180            },
181        );
182}
183
184/// Spawns the buttons that allow the user to change the lighting mode.
185fn spawn_buttons(commands: &mut Commands) {
186    commands.spawn((
187        widgets::main_ui_node(),
188        children![widgets::option_buttons(
189            "Lighting",
190            &[
191                (LightingMode::Baked, "Baked"),
192                (LightingMode::MixedDirect, "Mixed (Direct)"),
193                (LightingMode::MixedIndirect, "Mixed (Indirect)"),
194                (LightingMode::RealTime, "Real-Time"),
195            ],
196        )],
197    ));
198}
199
200/// Spawns the help text at the top of the window.
201fn spawn_help_text(commands: &mut Commands, app_status: &AppStatus) {
202    commands.spawn((
203        create_help_text(app_status),
204        Node {
205            position_type: PositionType::Absolute,
206            top: px(12),
207            left: px(12),
208            ..default()
209        },
210        HelpText,
211    ));
212}
213
214/// Adds lightmaps to and/or removes lightmaps from objects in the scene when
215/// the lighting mode changes.
216///
217/// This is also called right after the scene loads in order to set up the
218/// lightmaps.
219fn update_lightmaps(
220    mut commands: Commands,
221    asset_server: Res<AssetServer>,
222    mut materials: ResMut<Assets<StandardMaterial>>,
223    meshes: Query<(Entity, &GltfMeshName, &MeshMaterial3d<StandardMaterial>), With<Mesh3d>>,
224    mut lighting_mode_changed_reader: MessageReader<LightingModeChanged>,
225    app_status: Res<AppStatus>,
226) {
227    // Only run if the lighting mode changed. (Note that a change event is fired
228    // when the scene first loads.)
229    if lighting_mode_changed_reader.read().next().is_none() {
230        return;
231    }
232
233    // Select the lightmap to use, based on the lighting mode.
234    let lightmap: Option<Handle<Image>> = match app_status.lighting_mode {
235        LightingMode::Baked => {
236            Some(asset_server.load("lightmaps/MixedLightingExample-Baked.zstd.ktx2"))
237        }
238        LightingMode::MixedDirect => {
239            Some(asset_server.load("lightmaps/MixedLightingExample-MixedDirect.zstd.ktx2"))
240        }
241        LightingMode::MixedIndirect => {
242            Some(asset_server.load("lightmaps/MixedLightingExample-MixedIndirect.zstd.ktx2"))
243        }
244        LightingMode::RealTime => None,
245    };
246
247    'outer: for (entity, name, material) in &meshes {
248        // Add lightmaps to or remove lightmaps from the scenery objects in the
249        // scene (all objects but the sphere).
250        //
251        // Note that doing a linear search through the `LIGHTMAPS` array is
252        // inefficient, but we do it anyway in this example to improve clarity.
253        for (lightmap_name, uv_rect) in LIGHTMAPS {
254            if &**name != lightmap_name {
255                continue;
256            }
257
258            // Lightmap exposure defaults to zero, so we need to set it.
259            if let Some(ref mut material) = materials.get_mut(material) {
260                material.lightmap_exposure = LIGHTMAP_EXPOSURE;
261            }
262
263            // Add or remove the lightmap.
264            match lightmap {
265                Some(ref lightmap) => {
266                    commands.entity(entity).insert(Lightmap {
267                        image: (*lightmap).clone(),
268                        uv_rect,
269                        bicubic_sampling: false,
270                    });
271                }
272                None => {
273                    commands.entity(entity).remove::<Lightmap>();
274                }
275            }
276            continue 'outer;
277        }
278
279        // Add lightmaps to or remove lightmaps from the sphere.
280        if &**name == "Sphere" {
281            // Lightmap exposure defaults to zero, so we need to set it.
282            if let Some(ref mut material) = materials.get_mut(material) {
283                material.lightmap_exposure = LIGHTMAP_EXPOSURE;
284            }
285
286            // Add or remove the lightmap from the sphere. We only apply the
287            // lightmap in fully-baked mode.
288            match (&lightmap, app_status.lighting_mode) {
289                (Some(lightmap), LightingMode::Baked) => {
290                    commands.entity(entity).insert(Lightmap {
291                        image: (*lightmap).clone(),
292                        uv_rect: SPHERE_UV_RECT,
293                        bicubic_sampling: false,
294                    });
295                }
296                _ => {
297                    commands.entity(entity).remove::<Lightmap>();
298                }
299            }
300        }
301    }
302}
303
304/// Converts a uv rectangle from the OpenGL coordinate system (origin in the
305/// lower left) to the Vulkan coordinate system (origin in the upper left) that
306/// Bevy uses.
307///
308/// For this particular example, the baking tool happened to use the OpenGL
309/// coordinate system, so it was more convenient to do the conversion at compile
310/// time than to pre-calculate and hard-code the values.
311const fn uv_rect_opengl(gl_min: Vec2, size: Vec2) -> Rect {
312    let min = vec2(gl_min.x, 1.0 - gl_min.y - size.y);
313    Rect {
314        min,
315        max: vec2(min.x + size.x, min.y + size.y),
316    }
317}
318
319/// Ensures that clicking on the scene to move the sphere doesn't result in a
320/// hit on the sphere itself.
321fn make_sphere_nonpickable(
322    mut commands: Commands,
323    mut query: Query<(Entity, &Name), (With<Mesh3d>, Without<Pickable>)>,
324) {
325    for (sphere, name) in &mut query {
326        if &**name == "Sphere" {
327            commands.entity(sphere).insert(Pickable::IGNORE);
328        }
329    }
330}
331
332/// Updates the directional light settings as necessary when the lighting mode
333/// changes.
334fn update_directional_light(
335    mut lights: Query<&mut DirectionalLight>,
336    mut lighting_mode_changed_reader: MessageReader<LightingModeChanged>,
337    app_status: Res<AppStatus>,
338) {
339    // Only run if the lighting mode changed. (Note that a change event is fired
340    // when the scene first loads.)
341    if lighting_mode_changed_reader.read().next().is_none() {
342        return;
343    }
344
345    // Real-time direct light is used on the scenery if we're using mixed
346    // indirect or real-time mode.
347    let scenery_is_lit_in_real_time = matches!(
348        app_status.lighting_mode,
349        LightingMode::MixedIndirect | LightingMode::RealTime
350    );
351
352    for mut light in &mut lights {
353        light.affects_lightmapped_mesh_diffuse = scenery_is_lit_in_real_time;
354        // Don't bother enabling shadows if they won't show up on the scenery.
355        light.shadows_enabled = scenery_is_lit_in_real_time;
356    }
357}
358
359/// Updates the state of the selection widgets at the bottom of the window when
360/// the lighting mode changes.
361fn update_radio_buttons(
362    mut widgets: Query<
363        (
364            Entity,
365            Option<&mut BackgroundColor>,
366            Has<Text>,
367            &WidgetClickSender<LightingMode>,
368        ),
369        Or<(With<RadioButton>, With<RadioButtonText>)>,
370    >,
371    app_status: Res<AppStatus>,
372    mut writer: TextUiWriter,
373) {
374    for (entity, image, has_text, sender) in &mut widgets {
375        let selected = **sender == app_status.lighting_mode;
376
377        if let Some(mut bg_color) = image {
378            widgets::update_ui_radio_button(&mut bg_color, selected);
379        }
380        if has_text {
381            widgets::update_ui_radio_button_text(entity, &mut writer, selected);
382        }
383    }
384}
385
386/// Handles clicks on the widgets at the bottom of the screen and fires
387/// [`LightingModeChanged`] events.
388fn handle_lighting_mode_change(
389    mut widget_click_event_reader: MessageReader<WidgetClickEvent<LightingMode>>,
390    mut lighting_mode_changed_writer: MessageWriter<LightingModeChanged>,
391    mut app_status: ResMut<AppStatus>,
392) {
393    for event in widget_click_event_reader.read() {
394        app_status.lighting_mode = **event;
395        lighting_mode_changed_writer.write(LightingModeChanged);
396    }
397}
398
399/// Moves the sphere to its original position when the user selects the baked
400/// lighting mode.
401///
402/// As the light from the sphere is precomputed and depends on the sphere's
403/// original position, the sphere must be placed there in order for the lighting
404/// to be correct.
405fn reset_sphere_position(
406    mut objects: Query<(&Name, &mut Transform)>,
407    mut lighting_mode_changed_reader: MessageReader<LightingModeChanged>,
408    app_status: Res<AppStatus>,
409) {
410    // Only run if the lighting mode changed and if the lighting mode is
411    // `LightingMode::Baked`. (Note that a change event is fired when the scene
412    // first loads.)
413    if lighting_mode_changed_reader.read().next().is_none()
414        || app_status.lighting_mode != LightingMode::Baked
415    {
416        return;
417    }
418
419    for (name, mut transform) in &mut objects {
420        if &**name == "Sphere" {
421            transform.translation = INITIAL_SPHERE_POSITION;
422            break;
423        }
424    }
425}
426
427/// Updates the position of the sphere when the user clicks on a spot in the
428/// scene.
429///
430/// Note that the position of the sphere is locked in baked lighting mode.
431fn move_sphere(
432    mouse_button_input: Res<ButtonInput<MouseButton>>,
433    pointers: Query<&PointerInteraction>,
434    mut meshes: Query<(&GltfMeshName, &ChildOf), With<Mesh3d>>,
435    mut transforms: Query<&mut Transform>,
436    app_status: Res<AppStatus>,
437) {
438    // Only run when the left button is clicked and we're not in baked lighting
439    // mode.
440    if app_status.lighting_mode == LightingMode::Baked
441        || !mouse_button_input.pressed(MouseButton::Left)
442    {
443        return;
444    }
445
446    // Find the sphere.
447    let Some(child_of) = meshes
448        .iter_mut()
449        .filter_map(|(name, child_of)| {
450            if &**name == "Sphere" {
451                Some(child_of)
452            } else {
453                None
454            }
455        })
456        .next()
457    else {
458        return;
459    };
460
461    // Grab its transform.
462    let Ok(mut transform) = transforms.get_mut(child_of.parent()) else {
463        return;
464    };
465
466    // Set its transform to the appropriate position, as determined by the
467    // picking subsystem.
468    for interaction in pointers.iter() {
469        if let Some(&(
470            _,
471            HitData {
472                position: Some(position),
473                ..
474            },
475        )) = interaction.get_nearest_hit()
476        {
477            transform.translation = position + vec3(0.0, SPHERE_OFFSET, 0.0);
478        }
479    }
480}
examples/stress_tests/many_cubes.rs (line 505)
504fn fast_hue_to_rgb(hue: f32) -> Vec3 {
505    (hue * 6.0 - vec3(3.0, 2.0, 4.0)).abs() * vec3(1.0, -1.0, -1.0) + vec3(-1.0, 2.0, 2.0)
506}
examples/shader_advanced/custom_phase_item.rs (line 158)
157static VERTICES: [Vertex; 3] = [
158    Vertex::new(vec3(-0.866, -0.5, 0.5), vec3(1.0, 0.0, 0.0)),
159    Vertex::new(vec3(0.866, -0.5, 0.5), vec3(0.0, 1.0, 0.0)),
160    Vertex::new(vec3(0.0, 1.0, 0.5), vec3(0.0, 0.0, 1.0)),
161];
examples/shader_advanced/render_depth_to_texture.rs (line 251)
248fn spawn_main_camera(commands: &mut Commands) {
249    commands.spawn((
250        Camera3d::default(),
251        Transform::from_xyz(5.0, 2.0, 30.0).looking_at(vec3(5.0, 2.0, 0.0), Vec3::Y),
252        // Disable antialiasing just for simplicity's sake.
253        Msaa::Off,
254    ));
255}
256
257/// Spawns the instructional text at the top of the screen.
258fn spawn_instructions(commands: &mut Commands) {
259    commands.spawn((
260        Text::new("Use WASD to move the secondary camera"),
261        Node {
262            position_type: PositionType::Absolute,
263            top: Val::Px(12.0),
264            left: Val::Px(12.0),
265            ..Node::default()
266        },
267    ));
268}
269
270/// Spins the cube a bit every frame.
271fn rotate_cube(mut cubes: Query<&mut Transform, With<RotatingCube>>, time: Res<Time>) {
272    for mut transform in &mut cubes {
273        transform.rotate_x(1.5 * time.delta_secs());
274        transform.rotate_y(1.1 * time.delta_secs());
275        transform.rotate_z(-1.3 * time.delta_secs());
276    }
277}
278
279impl Material for ShowDepthTextureMaterial {
280    fn fragment_shader() -> ShaderRef {
281        SHADER_ASSET_PATH.into()
282    }
283}
284
285impl ViewNode for CopyDepthTextureNode {
286    type ViewQuery = (Read<ExtractedCamera>, Read<ViewDepthTexture>);
287
288    fn run<'w>(
289        &self,
290        _: &mut RenderGraphContext,
291        render_context: &mut RenderContext<'w>,
292        (camera, depth_texture): QueryItem<'w, '_, Self::ViewQuery>,
293        world: &'w World,
294    ) -> Result<(), NodeRunError> {
295        // Make sure we only run on the depth-only camera.
296        // We could make a marker component for that camera and extract it to
297        // the render world, but using `order` as a tag to tell the main camera
298        // and the depth-only camera apart works in a pinch.
299        if camera.order >= 0 {
300            return Ok(());
301        }
302
303        // Grab the texture we're going to copy to.
304        let demo_depth_texture = world.resource::<DemoDepthTexture>();
305        let image_assets = world.resource::<RenderAssets<GpuImage>>();
306        let Some(demo_depth_image) = image_assets.get(demo_depth_texture.0.id()) else {
307            return Ok(());
308        };
309
310        // Perform the copy.
311        render_context.add_command_buffer_generation_task(move |render_device| {
312            let mut command_encoder =
313                render_device.create_command_encoder(&CommandEncoderDescriptor {
314                    label: Some("copy depth to demo texture command encoder"),
315                });
316            command_encoder.push_debug_group("copy depth to demo texture");
317
318            // Copy from the view's depth texture to the destination depth
319            // texture.
320            command_encoder.copy_texture_to_texture(
321                TexelCopyTextureInfo {
322                    texture: &depth_texture.texture,
323                    mip_level: 0,
324                    origin: Origin3d::default(),
325                    aspect: TextureAspect::DepthOnly,
326                },
327                TexelCopyTextureInfo {
328                    texture: &demo_depth_image.texture,
329                    mip_level: 0,
330                    origin: Origin3d::default(),
331                    aspect: TextureAspect::DepthOnly,
332                },
333                Extent3d {
334                    width: DEPTH_TEXTURE_SIZE,
335                    height: DEPTH_TEXTURE_SIZE,
336                    depth_or_array_layers: 1,
337                },
338            );
339
340            command_encoder.pop_debug_group();
341            command_encoder.finish()
342        });
343
344        Ok(())
345    }
346}
347
348impl FromWorld for DemoDepthTexture {
349    fn from_world(world: &mut World) -> Self {
350        let mut images = world.resource_mut::<Assets<Image>>();
351
352        // Create a new 32-bit floating point depth texture.
353        let mut depth_image = Image::new_uninit(
354            Extent3d {
355                width: DEPTH_TEXTURE_SIZE,
356                height: DEPTH_TEXTURE_SIZE,
357                depth_or_array_layers: 1,
358            },
359            TextureDimension::D2,
360            TextureFormat::Depth32Float,
361            RenderAssetUsages::default(),
362        );
363
364        // Create a sampler. Note that this needs to specify a `compare`
365        // function in order to be compatible with depth textures.
366        depth_image.sampler = ImageSampler::Descriptor(ImageSamplerDescriptor {
367            label: Some("custom depth image sampler".to_owned()),
368            compare: Some(ImageCompareFunction::Always),
369            ..ImageSamplerDescriptor::default()
370        });
371
372        let depth_image_handle = images.add(depth_image);
373        DemoDepthTexture(depth_image_handle)
374    }
375}
376
377impl ExtractResource for DemoDepthTexture {
378    type Source = Self;
379
380    fn extract_resource(source: &Self::Source) -> Self {
381        // Share the `DemoDepthTexture` resource over to the render world so
382        // that our `CopyDepthTextureNode` can access it.
383        (*source).clone()
384    }
385}
386
387/// Draws an outline of the depth texture on the screen.
388fn draw_camera_gizmo(cameras: Query<(&Camera, &GlobalTransform)>, mut gizmos: Gizmos) {
389    for (camera, transform) in &cameras {
390        // As above, we use the order as a cheap tag to tell the depth texture
391        // apart from the main texture.
392        if camera.order >= 0 {
393            continue;
394        }
395
396        // Draw a cone representing the camera.
397        gizmos.primitive_3d(
398            &Cone {
399                radius: 1.0,
400                height: 3.0,
401            },
402            Isometry3d::new(
403                transform.translation(),
404                // We have to rotate here because `Cone` primitives are oriented
405                // along +Y and cameras point along +Z.
406                transform.rotation() * Quat::from_rotation_x(FRAC_PI_2),
407            ),
408            LIME,
409        );
410    }
411}
412
413/// Orbits the cube when WASD is pressed.
414fn move_camera(
415    mut cameras: Query<(&Camera, &mut Transform)>,
416    keyboard: Res<ButtonInput<KeyCode>>,
417    time: Res<Time>,
418) {
419    for (camera, mut transform) in &mut cameras {
420        // Only affect the depth camera.
421        if camera.order >= 0 {
422            continue;
423        }
424
425        // Convert the camera's position from Cartesian to spherical coordinates.
426        let mut spherical_coords = SphericalCoordinates::from_cartesian(transform.translation);
427
428        // Modify those spherical coordinates as appropriate.
429        let mut changed = false;
430        if keyboard.pressed(KeyCode::KeyW) {
431            spherical_coords.inclination -= time.delta_secs() * CAMERA_MOVEMENT_SPEED;
432            changed = true;
433        }
434        if keyboard.pressed(KeyCode::KeyS) {
435            spherical_coords.inclination += time.delta_secs() * CAMERA_MOVEMENT_SPEED;
436            changed = true;
437        }
438        if keyboard.pressed(KeyCode::KeyA) {
439            spherical_coords.azimuth += time.delta_secs() * CAMERA_MOVEMENT_SPEED;
440            changed = true;
441        }
442        if keyboard.pressed(KeyCode::KeyD) {
443            spherical_coords.azimuth -= time.delta_secs() * CAMERA_MOVEMENT_SPEED;
444            changed = true;
445        }
446
447        // If they were changed, convert from spherical coordinates back to
448        // Cartesian ones, and update the camera's transform.
449        if changed {
450            spherical_coords.inclination = spherical_coords.inclination.clamp(0.01, PI - 0.01);
451            transform.translation = spherical_coords.to_cartesian();
452            transform.look_at(Vec3::ZERO, Vec3::Y);
453        }
454    }
455}
456
457impl SphericalCoordinates {
458    /// [Converts] from Cartesian coordinates to spherical coordinates.
459    ///
460    /// [Converts]: https://en.wikipedia.org/wiki/Spherical_coordinate_system#Cartesian_coordinates
461    fn from_cartesian(p: Vec3) -> SphericalCoordinates {
462        let radius = p.length();
463        SphericalCoordinates {
464            radius,
465            inclination: acos(p.y / radius),
466            azimuth: atan2(p.z, p.x),
467        }
468    }
469
470    /// [Converts] from spherical coordinates to Cartesian coordinates.
471    ///
472    /// [Converts]: https://en.wikipedia.org/wiki/Spherical_coordinate_system#Cartesian_coordinates
473    fn to_cartesian(self) -> Vec3 {
474        let (sin_inclination, cos_inclination) = sin_cos(self.inclination);
475        let (sin_azimuth, cos_azimuth) = sin_cos(self.azimuth);
476        self.radius
477            * vec3(
478                sin_inclination * cos_azimuth,
479                cos_inclination,
480                sin_inclination * sin_azimuth,
481            )
482    }