|
| 1 | +//! This module sets up a simple way to spawn a view gizmo that indicates the 3 main axis |
| 2 | +//! It's currently hard coded to the top left of the parent `UiNode` it's spawned in. |
| 3 | +//! It currently doesn't support any input event to move the camera based on a click. |
| 4 | +
|
| 5 | +use bevy::{ |
| 6 | + asset::RenderAssetUsages, |
| 7 | + ecs::relationship::RelatedSpawnerCommands, |
| 8 | + prelude::*, |
| 9 | + render::{ |
| 10 | + render_resource::{Extent3d, Face, TextureDimension, TextureFormat, TextureUsages}, |
| 11 | + view::RenderLayers, |
| 12 | + }, |
| 13 | +}; |
| 14 | +use bevy_editor_cam::prelude::EditorCam; |
| 15 | + |
| 16 | +// That value was picked arbitrarily |
| 17 | +pub const VIEW_GIZMO_TEXTURE_SIZE: u32 = 125; |
| 18 | +// TODO we really shouldn't just hardcode view layers like that |
| 19 | +pub const VIEW_GIZMO_LAYER: usize = 22; |
| 20 | + |
| 21 | +const GIZMO_CAMERA_ZOOM: f32 = 3.5; |
| 22 | + |
| 23 | +pub struct ViewGizmoPlugin; |
| 24 | +impl Plugin for ViewGizmoPlugin { |
| 25 | + fn build(&self, app: &mut App) { |
| 26 | + app.add_systems(Startup, setup_view_gizmo) |
| 27 | + .add_systems(Update, (spawn_view_gizmo_camera, update_view_gizmo)); |
| 28 | + } |
| 29 | +} |
| 30 | + |
| 31 | +#[derive(Component)] |
| 32 | +pub struct ViewGizmoCamera; |
| 33 | + |
| 34 | +#[derive(Component)] |
| 35 | +pub struct ViewGizmoCameraTarget(pub Handle<Image>); |
| 36 | + |
| 37 | +pub fn spawn_view_gizmo_target_texture( |
| 38 | + mut images: ResMut<'_, Assets<Image>>, |
| 39 | + parent: &mut RelatedSpawnerCommands<ChildOf>, |
| 40 | +) { |
| 41 | + let size = Extent3d { |
| 42 | + width: VIEW_GIZMO_TEXTURE_SIZE, |
| 43 | + height: VIEW_GIZMO_TEXTURE_SIZE, |
| 44 | + ..default() |
| 45 | + }; |
| 46 | + |
| 47 | + let mut target_texture = Image::new_fill( |
| 48 | + size, |
| 49 | + TextureDimension::D2, |
| 50 | + &[0, 0, 0, 0], |
| 51 | + TextureFormat::Bgra8UnormSrgb, |
| 52 | + RenderAssetUsages::default(), |
| 53 | + ); |
| 54 | + target_texture.texture_descriptor.usage = |
| 55 | + TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST | TextureUsages::RENDER_ATTACHMENT; |
| 56 | + |
| 57 | + let image = images.add(target_texture); |
| 58 | + |
| 59 | + // TODO don't hardcode it to top left |
| 60 | + // TODO send input events to the image target |
| 61 | + parent.spawn(( |
| 62 | + ImageNode::new(image.clone()), |
| 63 | + Node { |
| 64 | + position_type: PositionType::Absolute, |
| 65 | + top: Val::ZERO, |
| 66 | + bottom: Val::ZERO, |
| 67 | + left: Val::ZERO, |
| 68 | + right: Val::ZERO, |
| 69 | + width: Val::Px(VIEW_GIZMO_TEXTURE_SIZE as f32), |
| 70 | + height: Val::Px(VIEW_GIZMO_TEXTURE_SIZE as f32), |
| 71 | + ..default() |
| 72 | + }, |
| 73 | + ViewGizmoCameraTarget(image.clone()), |
| 74 | + )); |
| 75 | +} |
| 76 | + |
| 77 | +fn setup_view_gizmo( |
| 78 | + mut commands: Commands, |
| 79 | + mut meshes: ResMut<Assets<Mesh>>, |
| 80 | + mut materials: ResMut<Assets<StandardMaterial>>, |
| 81 | + mut gizmo_assets: ResMut<Assets<GizmoAsset>>, |
| 82 | +) { |
| 83 | + let view_gizmo_pass_layer = RenderLayers::layer(VIEW_GIZMO_LAYER); |
| 84 | + let sphere = meshes.add(Sphere::new(0.2).mesh().uv(32, 18)); |
| 85 | + |
| 86 | + for axis in [ |
| 87 | + Vec3::new(1.0, 0.0, 0.0), |
| 88 | + Vec3::new(0.0, 1.0, 0.0), |
| 89 | + Vec3::new(0.0, 0.0, 1.0), |
| 90 | + ] { |
| 91 | + let mut gizmo = GizmoAsset::new(); |
| 92 | + let color = LinearRgba::from_vec3(axis); |
| 93 | + gizmo.line(Vec3::ZERO, axis, color); |
| 94 | + commands.spawn(( |
| 95 | + Gizmo { |
| 96 | + handle: gizmo_assets.add(gizmo), |
| 97 | + line_config: GizmoLineConfig { |
| 98 | + width: 2.5, |
| 99 | + ..default() |
| 100 | + }, |
| 101 | + ..default() |
| 102 | + }, |
| 103 | + Transform::from_xyz(0., 0., 0.), |
| 104 | + view_gizmo_pass_layer.clone(), |
| 105 | + )); |
| 106 | + // TODO react to click on the spheres to snap camera to axis |
| 107 | + commands.spawn(( |
| 108 | + Mesh3d(sphere.clone()), |
| 109 | + MeshMaterial3d(materials.add(StandardMaterial { |
| 110 | + base_color: color.into(), |
| 111 | + unlit: true, |
| 112 | + ..Default::default() |
| 113 | + })), |
| 114 | + Transform::from_translation(axis), |
| 115 | + view_gizmo_pass_layer.clone(), |
| 116 | + )); |
| 117 | + } |
| 118 | + // Use a sphere for the background |
| 119 | + let sphere = meshes.add(Sphere::new(1.3).mesh().uv(32, 18)); |
| 120 | + commands.spawn(( |
| 121 | + Mesh3d(sphere.clone()), |
| 122 | + MeshMaterial3d(materials.add(StandardMaterial { |
| 123 | + base_color: LinearRgba::new(0.0, 0.0, 0.0, 0.5).into(), |
| 124 | + unlit: true, |
| 125 | + // reverse cull mode so it appears behind |
| 126 | + cull_mode: Some(Face::Front), |
| 127 | + alpha_mode: AlphaMode::Blend, |
| 128 | + ..Default::default() |
| 129 | + })), |
| 130 | + Transform::from_xyz(0.0, 0.0, 0.0), |
| 131 | + view_gizmo_pass_layer.clone(), |
| 132 | + )); |
| 133 | +} |
| 134 | + |
| 135 | +fn spawn_view_gizmo_camera( |
| 136 | + mut commands: Commands, |
| 137 | + q: Query<&ViewGizmoCameraTarget, Added<ViewGizmoCameraTarget>>, |
| 138 | +) { |
| 139 | + let view_gizmo_pass_layer = RenderLayers::layer(VIEW_GIZMO_LAYER); |
| 140 | + for target in &q { |
| 141 | + commands.spawn(( |
| 142 | + Camera3d::default(), |
| 143 | + Camera { |
| 144 | + target: target.0.clone().into(), |
| 145 | + clear_color: ClearColorConfig::Custom(Color::srgba(0.0, 0.0, 0.0, 0.0)), |
| 146 | + ..default() |
| 147 | + }, |
| 148 | + Transform::from_translation(Vec3::new(0.0, 0.0, 0.0)).looking_at(Vec3::ZERO, Vec3::Y), |
| 149 | + view_gizmo_pass_layer.clone(), |
| 150 | + ViewGizmoCamera, |
| 151 | + )); |
| 152 | + } |
| 153 | +} |
| 154 | + |
| 155 | +fn update_view_gizmo( |
| 156 | + mut view_cube_camera: Query<&mut Transform, (With<ViewGizmoCamera>, With<Camera3d>)>, |
| 157 | + viewport_camera: Query<&Transform, (Without<ViewGizmoCamera>, With<Camera3d>, With<EditorCam>)>, |
| 158 | +) { |
| 159 | + for mut transform in &mut view_cube_camera { |
| 160 | + if let Ok(viewport_camera_transform) = viewport_camera.get_single() { |
| 161 | + transform.translation = viewport_camera_transform.back() * GIZMO_CAMERA_ZOOM; |
| 162 | + transform.rotation = viewport_camera_transform.rotation; |
| 163 | + } |
| 164 | + } |
| 165 | +} |
0 commit comments