Skip to content

Commit 0052c78

Browse files
authored
Add a basic view gizmo (#179)
* add basic view gizmo * fix typo * spawn camera in separate system
1 parent f41e224 commit 0052c78

File tree

2 files changed

+173
-1
lines changed

2 files changed

+173
-1
lines changed

bevy_editor_panes/bevy_3d_viewport/src/lib.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ use bevy_editor_cam::prelude::{DefaultEditorCamPlugins, EditorCam};
1616
use bevy_editor_styles::Theme;
1717
use bevy_infinite_grid::{InfiniteGrid, InfiniteGridPlugin, InfiniteGridSettings};
1818
use bevy_pane_layout::prelude::*;
19+
use view_gizmo::{spawn_view_gizmo_target_texture, ViewGizmoPlugin};
20+
21+
mod view_gizmo;
1922

2023
/// The identifier for the 3D Viewport.
2124
/// This is present on any pane that is a 3D Viewport.
@@ -40,7 +43,8 @@ impl Plugin for Viewport3dPanePlugin {
4043
if !app.is_plugin_added::<InfiniteGridPlugin>() {
4144
app.add_plugins(InfiniteGridPlugin);
4245
}
43-
app.add_plugins(DefaultEditorCamPlugins)
46+
47+
app.add_plugins((DefaultEditorCamPlugins, ViewGizmoPlugin))
4448
.add_systems(Startup, setup)
4549
.add_systems(
4650
PreUpdate,
@@ -162,6 +166,9 @@ fn on_pane_creation(
162166
},
163167
ChildOf(structure.content),
164168
))
169+
.with_children(|parent| {
170+
spawn_view_gizmo_target_texture(images, parent);
171+
})
165172
.observe(|trigger: Trigger<Pointer<Over>>, mut commands: Commands| {
166173
commands.entity(trigger.target()).insert(Active);
167174
})
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
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

Comments
 (0)