1use std::f32::consts::PI;
4
5use bevy::picking::PickingSystems;
6use bevy::{
7 asset::{uuid::Uuid, RenderAssetUsages},
8 camera::RenderTarget,
9 color::palettes::css::{BLUE, GRAY, RED},
10 input::ButtonState,
11 picking::{
12 backend::ray::RayMap,
13 pointer::{Location, PointerAction, PointerId, PointerInput},
14 },
15 prelude::*,
16 render::render_resource::{Extent3d, TextureDimension, TextureFormat, TextureUsages},
17 window::{PrimaryWindow, WindowEvent},
18};
19
20const CUBE_POINTER_ID: PointerId = PointerId::Custom(Uuid::from_u128(90870987));
21
22fn main() {
23 App::new()
24 .add_plugins(DefaultPlugins)
25 .add_systems(Startup, setup)
26 .add_systems(Update, rotator_system)
27 .add_systems(First, drive_diegetic_pointer.in_set(PickingSystems::Input))
28 .run();
29}
30
31#[derive(Component)]
33struct Cube;
34
35fn setup(
36 mut commands: Commands,
37 mut meshes: ResMut<Assets<Mesh>>,
38 mut materials: ResMut<Assets<StandardMaterial>>,
39 mut images: ResMut<Assets<Image>>,
40) {
41 let size = Extent3d {
42 width: 512,
43 height: 512,
44 ..default()
45 };
46
47 let mut image = Image::new_fill(
49 size,
50 TextureDimension::D2,
51 &[0, 0, 0, 0],
52 TextureFormat::Bgra8UnormSrgb,
53 RenderAssetUsages::default(),
54 );
55 image.texture_descriptor.usage =
57 TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST | TextureUsages::RENDER_ATTACHMENT;
58
59 let image_handle = images.add(image);
60
61 commands.spawn(DirectionalLight::default());
63
64 let texture_camera = commands
65 .spawn((
66 Camera2d,
67 Camera {
68 order: -1,
70 target: RenderTarget::Image(image_handle.clone().into()),
71 ..default()
72 },
73 ))
74 .id();
75
76 commands
77 .spawn((
78 Node {
79 width: percent(100),
81 height: percent(100),
82 flex_direction: FlexDirection::Column,
83 justify_content: JustifyContent::Center,
84 align_items: AlignItems::Center,
85 ..default()
86 },
87 BackgroundColor(GRAY.into()),
88 UiTargetCamera(texture_camera),
89 ))
90 .with_children(|parent| {
91 parent
92 .spawn((
93 Node {
94 position_type: PositionType::Absolute,
95 width: Val::Auto,
96 height: Val::Auto,
97 align_items: AlignItems::Center,
98 padding: UiRect::all(Val::Px(20.)),
99 ..default()
100 },
101 BorderRadius::all(Val::Px(10.)),
102 BackgroundColor(BLUE.into()),
103 ))
104 .observe(
105 |drag: On<Pointer<Drag>>, mut nodes: Query<(&mut Node, &ComputedNode)>| {
106 let (mut node, computed) = nodes.get_mut(drag.entity).unwrap();
107 node.left =
108 Val::Px(drag.pointer_location.position.x - computed.size.x / 2.0);
109 node.top = Val::Px(drag.pointer_location.position.y - 50.0);
110 },
111 )
112 .observe(
113 |over: On<Pointer<Over>>, mut colors: Query<&mut BackgroundColor>| {
114 colors.get_mut(over.entity).unwrap().0 = RED.into();
115 },
116 )
117 .observe(
118 |out: On<Pointer<Out>>, mut colors: Query<&mut BackgroundColor>| {
119 colors.get_mut(out.entity).unwrap().0 = BLUE.into();
120 },
121 )
122 .with_children(|parent| {
123 parent.spawn((
124 Text::new("Drag Me!"),
125 TextFont {
126 font_size: 40.0,
127 ..default()
128 },
129 TextColor::WHITE,
130 ));
131 });
132 });
133
134 let mesh_handle = meshes.add(Cuboid::default());
135
136 let material_handle = materials.add(StandardMaterial {
138 base_color_texture: Some(image_handle),
139 reflectance: 0.02,
140 unlit: false,
141 ..default()
142 });
143
144 commands.spawn((
146 Mesh3d(mesh_handle),
147 MeshMaterial3d(material_handle),
148 Transform::from_xyz(0.0, 0.0, 1.5).with_rotation(Quat::from_rotation_x(PI)),
149 Cube,
150 ));
151
152 commands.spawn((
154 Camera3d::default(),
155 Transform::from_xyz(0.0, 0.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
156 ));
157
158 commands.spawn(CUBE_POINTER_ID);
159}
160
161const ROTATION_SPEED: f32 = 0.1;
162
163fn rotator_system(time: Res<Time>, mut query: Query<&mut Transform, With<Cube>>) {
164 for mut transform in &mut query {
165 transform.rotate_x(1.0 * time.delta_secs() * ROTATION_SPEED);
166 transform.rotate_y(0.7 * time.delta_secs() * ROTATION_SPEED);
167 }
168}
169
170fn drive_diegetic_pointer(
175 mut cursor_last: Local<Vec2>,
176 mut raycast: MeshRayCast,
177 rays: Res<RayMap>,
178 cubes: Query<&Mesh3d, With<Cube>>,
179 ui_camera: Query<&Camera, With<Camera2d>>,
180 primary_window: Query<Entity, With<PrimaryWindow>>,
181 windows: Query<(Entity, &Window)>,
182 images: Res<Assets<Image>>,
183 manual_texture_views: Res<ManualTextureViews>,
184 mut window_events: MessageReader<WindowEvent>,
185 mut pointer_inputs: MessageWriter<PointerInput>,
186) -> Result {
187 let target = ui_camera
190 .single()?
191 .target
192 .normalize(primary_window.single().ok())
193 .unwrap();
194 let target_info = target
195 .get_render_target_info(windows, &images, &manual_texture_views)
196 .unwrap();
197 let size = target_info.physical_size.as_vec2();
198
199 let raycast_settings = MeshRayCastSettings {
201 visibility: RayCastVisibility::VisibleInView,
202 filter: &|entity| cubes.contains(entity),
203 early_exit_test: &|_| false,
204 };
205 for (_id, ray) in rays.iter() {
206 for (_cube, hit) in raycast.cast_ray(*ray, &raycast_settings) {
207 let position = size * hit.uv.unwrap();
208 if position != *cursor_last {
209 pointer_inputs.write(PointerInput::new(
210 CUBE_POINTER_ID,
211 Location {
212 target: target.clone(),
213 position,
214 },
215 PointerAction::Move {
216 delta: position - *cursor_last,
217 },
218 ));
219 *cursor_last = position;
220 }
221 }
222 }
223
224 for window_event in window_events.read() {
226 if let WindowEvent::MouseButtonInput(input) = window_event {
227 let button = match input.button {
228 MouseButton::Left => PointerButton::Primary,
229 MouseButton::Right => PointerButton::Secondary,
230 MouseButton::Middle => PointerButton::Middle,
231 _ => continue,
232 };
233 let action = match input.state {
234 ButtonState::Pressed => PointerAction::Press(button),
235 ButtonState::Released => PointerAction::Release(button),
236 };
237 pointer_inputs.write(PointerInput::new(
238 CUBE_POINTER_ID,
239 Location {
240 target: target.clone(),
241 position: *cursor_last,
242 },
243 action,
244 ));
245 }
246 }
247
248 Ok(())
249}