mirror of
https://github.com/bevyengine/bevy.git
synced 2026-06-30 07:45:50 -04:00
3f401d1507
# Objective Fix #19101. (Kinda, this is not the exact solution proposed in that issue, but it still shortens most of the examples) ## Solution Most of the images created in examples are for use in render targets. `Image::new_target_texture` is made for exactly that and significantly shortens the image creation process. ## Testing I tested all the examples I changed and they seem to work fine. --- btw for some reason most of the examples use `TextureFormat::Bgra8UnormSrgb` while the documentation for `Image::new_target_texture` recommends `TextureFormat::Rgba8UnormSrgb` for SDR images. What's up with that? --------- Co-authored-by: Carter Anderson <mcanders1@gmail.com>
233 lines
8.0 KiB
Rust
233 lines
8.0 KiB
Rust
//! Shows how to render UI to a texture. Useful for displaying UI in 3D space.
|
|
|
|
use std::f32::consts::PI;
|
|
|
|
use bevy::picking::PickingSystems;
|
|
use bevy::{
|
|
asset::uuid::Uuid,
|
|
camera::RenderTarget,
|
|
color::palettes::css::{BLUE, GRAY, RED},
|
|
input::ButtonState,
|
|
picking::{
|
|
backend::ray::RayMap,
|
|
pointer::{Location, PointerAction, PointerId, PointerInput},
|
|
},
|
|
prelude::*,
|
|
render::render_resource::TextureFormat,
|
|
window::{PrimaryWindow, WindowEvent},
|
|
};
|
|
|
|
const CUBE_POINTER_ID: PointerId = PointerId::Custom(Uuid::from_u128(90870987));
|
|
|
|
fn main() {
|
|
App::new()
|
|
.add_plugins(DefaultPlugins)
|
|
.add_systems(Startup, setup)
|
|
.add_systems(Update, rotator_system)
|
|
.add_systems(First, drive_diegetic_pointer.in_set(PickingSystems::Input))
|
|
.run();
|
|
}
|
|
|
|
// Marks the cube, to which the UI texture is applied.
|
|
#[derive(Component)]
|
|
struct Cube;
|
|
|
|
fn setup(
|
|
mut commands: Commands,
|
|
mut meshes: ResMut<Assets<Mesh>>,
|
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
|
mut images: ResMut<Assets<Image>>,
|
|
) {
|
|
// This is the texture that will be rendered to.
|
|
let image = Image::new_target_texture(512, 512, TextureFormat::Bgra8UnormSrgb, None);
|
|
|
|
let image_handle = images.add(image);
|
|
|
|
// Light
|
|
commands.spawn(DirectionalLight::default());
|
|
|
|
let texture_camera = commands
|
|
.spawn((
|
|
Camera2d,
|
|
Camera {
|
|
// render before the "main pass" camera
|
|
order: -1,
|
|
..default()
|
|
},
|
|
RenderTarget::Image(image_handle.clone().into()),
|
|
))
|
|
.id();
|
|
|
|
commands
|
|
.spawn((
|
|
Node {
|
|
// Cover the whole image
|
|
width: percent(100),
|
|
height: percent(100),
|
|
flex_direction: FlexDirection::Column,
|
|
justify_content: JustifyContent::Center,
|
|
align_items: AlignItems::Center,
|
|
..default()
|
|
},
|
|
BackgroundColor(GRAY.into()),
|
|
UiTargetCamera(texture_camera),
|
|
))
|
|
.with_children(|parent| {
|
|
parent
|
|
.spawn((
|
|
Node {
|
|
position_type: PositionType::Absolute,
|
|
width: auto(),
|
|
height: auto(),
|
|
align_items: AlignItems::Center,
|
|
padding: UiRect::all(px(20.)),
|
|
border_radius: BorderRadius::all(px(10.)),
|
|
..default()
|
|
},
|
|
BackgroundColor(BLUE.into()),
|
|
))
|
|
.observe(
|
|
|drag: On<Pointer<Drag>>, mut nodes: Query<(&mut Node, &ComputedNode)>| {
|
|
let (mut node, computed) = nodes.get_mut(drag.entity).unwrap();
|
|
node.left = px(drag.pointer_location.position.x - computed.size.x / 2.0);
|
|
node.top = px(drag.pointer_location.position.y - 50.0);
|
|
},
|
|
)
|
|
.observe(
|
|
|over: On<Pointer<Over>>, mut colors: Query<&mut BackgroundColor>| {
|
|
colors.get_mut(over.entity).unwrap().0 = RED.into();
|
|
},
|
|
)
|
|
.observe(
|
|
|out: On<Pointer<Out>>, mut colors: Query<&mut BackgroundColor>| {
|
|
colors.get_mut(out.entity).unwrap().0 = BLUE.into();
|
|
},
|
|
)
|
|
.with_children(|parent| {
|
|
parent.spawn((
|
|
Text::new("Drag Me!"),
|
|
TextFont {
|
|
font_size: FontSize::Px(40.0),
|
|
..default()
|
|
},
|
|
TextColor::WHITE,
|
|
));
|
|
});
|
|
});
|
|
|
|
let mesh_handle = meshes.add(Cuboid::default());
|
|
|
|
// This material has the texture that has been rendered.
|
|
let material_handle = materials.add(StandardMaterial {
|
|
base_color_texture: Some(image_handle),
|
|
reflectance: 0.02,
|
|
unlit: false,
|
|
..default()
|
|
});
|
|
|
|
// Cube with material containing the rendered UI texture.
|
|
commands.spawn((
|
|
Mesh3d(mesh_handle),
|
|
MeshMaterial3d(material_handle),
|
|
Transform::from_xyz(0.0, 0.0, 1.5).with_rotation(Quat::from_rotation_x(PI)),
|
|
Cube,
|
|
));
|
|
|
|
// The main pass camera.
|
|
commands.spawn((
|
|
Camera3d::default(),
|
|
Transform::from_xyz(0.0, 0.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
|
|
));
|
|
|
|
commands.spawn(CUBE_POINTER_ID);
|
|
}
|
|
|
|
const ROTATION_SPEED: f32 = 0.1;
|
|
|
|
fn rotator_system(time: Res<Time>, mut query: Query<&mut Transform, With<Cube>>) {
|
|
for mut transform in &mut query {
|
|
transform.rotate_x(1.0 * time.delta_secs() * ROTATION_SPEED);
|
|
transform.rotate_y(0.7 * time.delta_secs() * ROTATION_SPEED);
|
|
}
|
|
}
|
|
|
|
/// Because bevy has no way to know how to map a mouse input to the UI texture, we need to write a
|
|
/// system that tells it there is a pointer on the UI texture. We cast a ray into the scene and find
|
|
/// the UV (2D texture) coordinates of the raycast hit. This UV coordinate is effectively the same
|
|
/// as a pointer coordinate on a 2D UI rect.
|
|
fn drive_diegetic_pointer(
|
|
mut cursor_last: Local<Vec2>,
|
|
mut raycast: MeshRayCast,
|
|
rays: Res<RayMap>,
|
|
cubes: Query<&Mesh3d, With<Cube>>,
|
|
ui_camera: Query<&RenderTarget, With<Camera2d>>,
|
|
primary_window: Query<Entity, With<PrimaryWindow>>,
|
|
windows: Query<(Entity, &Window)>,
|
|
images: Res<Assets<Image>>,
|
|
manual_texture_views: Res<ManualTextureViews>,
|
|
mut window_events: MessageReader<WindowEvent>,
|
|
mut pointer_inputs: MessageWriter<PointerInput>,
|
|
) -> Result {
|
|
// Get the size of the texture, so we can convert from dimensionless UV coordinates that span
|
|
// from 0 to 1, to pixel coordinates.
|
|
let target = ui_camera
|
|
.single()?
|
|
.normalize(primary_window.single().ok())
|
|
.unwrap();
|
|
let target_info = target
|
|
.get_render_target_info(windows, &images, &manual_texture_views)
|
|
.unwrap();
|
|
let size = target_info.physical_size.as_vec2();
|
|
|
|
// Find raycast hits and update the virtual pointer.
|
|
let raycast_settings = MeshRayCastSettings {
|
|
visibility: RayCastVisibility::VisibleInView,
|
|
filter: &|entity| cubes.contains(entity),
|
|
early_exit_test: &|_| false,
|
|
};
|
|
for (_id, ray) in rays.iter() {
|
|
for (_cube, hit) in raycast.cast_ray(*ray, &raycast_settings) {
|
|
let position = size * hit.uv.unwrap();
|
|
if position != *cursor_last {
|
|
pointer_inputs.write(PointerInput::new(
|
|
CUBE_POINTER_ID,
|
|
Location {
|
|
target: target.clone(),
|
|
position,
|
|
},
|
|
PointerAction::Move {
|
|
delta: position - *cursor_last,
|
|
},
|
|
));
|
|
*cursor_last = position;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Pipe pointer button presses to the virtual pointer on the UI texture.
|
|
for window_event in window_events.read() {
|
|
if let WindowEvent::MouseButtonInput(input) = window_event {
|
|
let button = match input.button {
|
|
MouseButton::Left => PointerButton::Primary,
|
|
MouseButton::Right => PointerButton::Secondary,
|
|
MouseButton::Middle => PointerButton::Middle,
|
|
_ => continue,
|
|
};
|
|
let action = match input.state {
|
|
ButtonState::Pressed => PointerAction::Press(button),
|
|
ButtonState::Released => PointerAction::Release(button),
|
|
};
|
|
pointer_inputs.write(PointerInput::new(
|
|
CUBE_POINTER_ID,
|
|
Location {
|
|
target: target.clone(),
|
|
position: *cursor_last,
|
|
},
|
|
action,
|
|
));
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|