Skip to main content

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