viewport_node/
viewport_node.rs

1//! A simple scene to demonstrate spawning a viewport widget. The example will demonstrate how to
2//! pick entities visible in the widget's view.
3
4use bevy::{
5    asset::RenderAssetUsages,
6    camera::RenderTarget,
7    picking::pointer::PointerInteraction,
8    prelude::*,
9    render::render_resource::{TextureDimension, TextureFormat, TextureUsages},
10    ui::widget::ViewportNode,
11};
12
13fn main() {
14    App::new()
15        .add_plugins((DefaultPlugins, MeshPickingPlugin))
16        .add_systems(Startup, test)
17        .add_systems(Update, draw_mesh_intersections)
18        .run();
19}
20
21#[derive(Component, Reflect, Debug)]
22#[reflect(Component)]
23struct Shape;
24
25fn test(
26    mut commands: Commands,
27    mut images: ResMut<Assets<Image>>,
28    mut meshes: ResMut<Assets<Mesh>>,
29    mut materials: ResMut<Assets<StandardMaterial>>,
30) {
31    // Spawn a UI camera
32    commands.spawn(Camera3d::default());
33
34    // Set up an texture for the 3D camera to render to.
35    // The size of the texture will be based on the viewport's ui size.
36    let mut image = Image::new_uninit(
37        default(),
38        TextureDimension::D2,
39        TextureFormat::Bgra8UnormSrgb,
40        RenderAssetUsages::all(),
41    );
42    image.texture_descriptor.usage =
43        TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST | TextureUsages::RENDER_ATTACHMENT;
44    let image_handle = images.add(image);
45
46    // Spawn the 3D camera
47    let camera = commands
48        .spawn((
49            Camera3d::default(),
50            Camera {
51                // Render this camera before our UI camera
52                order: -1,
53                target: RenderTarget::Image(image_handle.clone().into()),
54                ..default()
55            },
56        ))
57        .id();
58
59    // Spawn something for the 3D camera to look at
60    commands
61        .spawn((
62            Mesh3d(meshes.add(Cuboid::new(5.0, 5.0, 5.0))),
63            MeshMaterial3d(materials.add(Color::WHITE)),
64            Transform::from_xyz(0.0, 0.0, -10.0),
65            Shape,
66        ))
67        // We can observe pointer events on our objects as normal, the
68        // `bevy::ui::widgets::viewport_picking` system will take care of ensuring our viewport
69        // clicks pass through
70        .observe(on_drag_cuboid);
71
72    // Spawn our viewport widget
73    commands
74        .spawn((
75            Node {
76                position_type: PositionType::Absolute,
77                top: px(50),
78                left: px(50),
79                width: px(200),
80                height: px(200),
81                border: UiRect::all(px(5)),
82                ..default()
83            },
84            BorderColor::all(Color::WHITE),
85            ViewportNode::new(camera),
86        ))
87        .observe(on_drag_viewport);
88}
89
90fn on_drag_viewport(drag: On<Pointer<Drag>>, mut node_query: Query<&mut Node>) {
91    if matches!(drag.button, PointerButton::Secondary) {
92        let mut node = node_query.get_mut(drag.entity).unwrap();
93
94        if let (Val::Px(top), Val::Px(left)) = (node.top, node.left) {
95            node.left = px(left + drag.delta.x);
96            node.top = px(top + drag.delta.y);
97        };
98    }
99}
100
101fn on_drag_cuboid(drag: On<Pointer<Drag>>, mut transform_query: Query<&mut Transform>) {
102    if matches!(drag.button, PointerButton::Primary) {
103        let mut transform = transform_query.get_mut(drag.entity).unwrap();
104        transform.rotate_y(drag.delta.x * 0.02);
105        transform.rotate_x(drag.delta.y * 0.02);
106    }
107}
108
109fn draw_mesh_intersections(
110    pointers: Query<&PointerInteraction>,
111    untargetable: Query<Entity, Without<Shape>>,
112    mut gizmos: Gizmos,
113) {
114    for (point, normal) in pointers
115        .iter()
116        .flat_map(|interaction| interaction.iter())
117        .filter_map(|(entity, hit)| {
118            if !untargetable.contains(*entity) {
119                hit.position.zip(hit.normal)
120            } else {
121                None
122            }
123        })
124    {
125        gizmos.arrow(point, point + normal.normalize() * 0.5, Color::WHITE);
126    }
127}