Skip to content

3D Viewport #110

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,4 @@ bevy_pane_layout = { path = "crates/bevy_pane_layout" }
bevy_transform_gizmos = { path = "crates/bevy_transform_gizmos" }
bevy_undo = { path = "crates/bevy_undo" }
bevy_infinite_grid = { path = "crates/bevy_infinite_grid" }
bevy_editor_cam = { path = "crates/bevy_editor_cam" }
8 changes: 7 additions & 1 deletion bevy_editor_panes/bevy_2d_viewport/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use bevy::{
render::{
camera::RenderTarget,
render_resource::{Extent3d, TextureFormat, TextureUsages},
view::RenderLayers,
},
ui::ui_layout_system,
};
Expand Down Expand Up @@ -32,7 +33,10 @@ pub struct Viewport2dPanePlugin;

impl Plugin for Viewport2dPanePlugin {
fn build(&self, app: &mut App) {
app.add_plugins((EditorCamera2dPlugin, InfiniteGridPlugin))
if !app.is_plugin_added::<InfiniteGridPlugin>() {
app.add_plugins(InfiniteGridPlugin);
}
app.add_plugins(EditorCamera2dPlugin)
.add_systems(Startup, setup)
.add_systems(
PostUpdate,
Expand Down Expand Up @@ -70,6 +74,7 @@ fn setup(mut commands: Commands, theme: Res<Theme>) {
..default()
},
Transform::from_rotation(Quat::from_rotation_arc(Vec3::Y, Vec3::Z)),
RenderLayers::layer(2),
));
}

Expand Down Expand Up @@ -125,6 +130,7 @@ fn on_pane_creation(
clear_color: ClearColorConfig::Custom(theme.viewport_background_color),
..default()
},
RenderLayers::from_layers(&[0, 2]),
))
.id();

Expand Down
3 changes: 3 additions & 0 deletions bevy_editor_panes/bevy_3d_viewport/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ edition = "2021"
[dependencies]
bevy.workspace = true
bevy_pane_layout.workspace = true
bevy_editor_cam.workspace = true
bevy_editor_styles.workspace = true
bevy_infinite_grid.workspace = true

[lints]
workspace = true
222 changes: 217 additions & 5 deletions bevy_editor_panes/bevy_3d_viewport/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,234 @@
//! 3D Viewport for Bevy

use bevy::prelude::*;
use bevy_pane_layout::PaneRegistry;
use bevy::{
picking::{
pointer::{Location, PointerId, PointerInput, PointerLocation},
PickSet,
},
prelude::*,
render::{
camera::{NormalizedRenderTarget, RenderTarget},
render_resource::{Extent3d, TextureFormat, TextureUsages},
view::RenderLayers,
},
ui::ui_layout_system,
};
use bevy_editor_cam::prelude::{DefaultEditorCamPlugins, EditorCam};
use bevy_editor_styles::Theme;
use bevy_infinite_grid::{InfiniteGrid, InfiniteGridPlugin, InfiniteGridSettings};
use bevy_pane_layout::{PaneContentNode, PaneRegistry};

/// The identifier for the 3D Viewport.
/// This is present on any pane that is a 3D Viewport.
#[derive(Component)]
pub struct Bevy3DViewport;
pub struct Bevy3dViewport {
camera: Entity,
}

impl Default for Bevy3dViewport {
fn default() -> Self {
Bevy3dViewport {
camera: Entity::PLACEHOLDER,
}
}
}

/// Plugin for the 3D Viewport pane.
pub struct Viewport3dPanePlugin;

impl Plugin for Viewport3dPanePlugin {
fn build(&self, app: &mut App) {
if !app.is_plugin_added::<InfiniteGridPlugin>() {
app.add_plugins(InfiniteGridPlugin);
}
app.add_plugins(DefaultEditorCamPlugins)
.add_systems(Startup, setup)
.add_systems(
PreUpdate,
render_target_picking_passthrough.in_set(PickSet::Last),
)
.add_systems(
PostUpdate,
update_render_target_size.after(ui_layout_system),
)
.add_observer(on_pane_creation)
.add_observer(
|trigger: Trigger<OnRemove, Bevy3dViewport>,
mut commands: Commands,
query: Query<&Bevy3dViewport>| {
// Despawn the viewport camera
commands
.entity(query.get(trigger.entity()).unwrap().camera)
.despawn_recursive();
},
);

app.world_mut()
.get_resource_or_init::<PaneRegistry>()
.register("Viewport 3D", |mut commands, pane_root| {
commands.entity(pane_root).insert(Bevy3DViewport);
commands.entity(pane_root).insert(Bevy3dViewport::default());
});
}
}

#[derive(Component)]
struct Active;

// TODO This does not properly handle multiple windows.
/// Copies picking events and moves pointers through render-targets.
fn render_target_picking_passthrough(
mut commands: Commands,
viewports: Query<(Entity, &Bevy3dViewport)>,
content: Query<&PaneContentNode>,
children_query: Query<&Children>,
node_query: Query<(&ComputedNode, &GlobalTransform, &UiImage), With<Active>>,
mut pointers: Query<(&PointerId, &mut PointerLocation)>,
mut pointer_input_reader: EventReader<PointerInput>,
) {
for event in pointer_input_reader.read() {
// Ignore the events we send to the render-targets
if !matches!(event.location.target, NormalizedRenderTarget::Window(..)) {
continue;
}
for (pane_root, _viewport) in &viewports {
let content_node_id = children_query
.iter_descendants(pane_root)
.find(|e| content.contains(*e))
.unwrap();

let image_id = children_query.get(content_node_id).unwrap()[0];

let Ok((computed_node, global_transform, ui_image)) = node_query.get(image_id) else {
// Inactive viewport
continue;
};
let node_rect =
Rect::from_center_size(global_transform.translation().xy(), computed_node.size());

let new_location = Location {
position: event.location.position - node_rect.min,
target: NormalizedRenderTarget::Image(ui_image.texture.clone()),
};

// Duplicate the event
let mut new_event = event.clone();
// Relocate the event to the render-target
new_event.location = new_location.clone();
// Resend the event
commands.send_event(new_event);

if let Some((_id, mut pointer_location)) = pointers
.iter_mut()
.find(|(pointer_id, _)| **pointer_id == event.pointer_id)
{
// Relocate the pointer to the render-target
pointer_location.location = Some(new_location);
}
}
}
}

fn setup(mut commands: Commands, theme: Res<Theme>) {
commands.spawn((
InfiniteGrid,
InfiniteGridSettings {
major_line_color: theme.background_color.0,
minor_line_color: theme.pane_header_background_color.0,
..default()
},
RenderLayers::layer(1),
));
}

fn on_pane_creation(
trigger: Trigger<OnAdd, Bevy3dViewport>,
mut commands: Commands,
children_query: Query<&Children>,
mut query: Query<&mut Bevy3dViewport>,
content: Query<&PaneContentNode>,
mut images: ResMut<Assets<Image>>,
theme: Res<Theme>,
) {
let pane_root = trigger.entity();
let content_node = children_query
.iter_descendants(pane_root)
.find(|e| content.contains(*e))
.unwrap();

let mut image = Image::default();

image.texture_descriptor.usage |= TextureUsages::RENDER_ATTACHMENT;
image.texture_descriptor.format = TextureFormat::Bgra8UnormSrgb;

let image_handle = images.add(image);

commands
.spawn((
UiImage {
texture: image_handle.clone(),
..Default::default()
},
Node {
position_type: PositionType::Absolute,
top: Val::ZERO,
bottom: Val::ZERO,
left: Val::ZERO,
right: Val::ZERO,
..default()
},
))
.set_parent(content_node)
.observe(|trigger: Trigger<Pointer<Over>>, mut commands: Commands| {
commands.entity(trigger.entity()).insert(Active);
})
.observe(|trigger: Trigger<Pointer<Out>>, mut commands: Commands| {
commands.entity(trigger.entity()).remove::<Active>();
});

let camera_id = commands
.spawn((
Camera3d::default(),
Camera {
target: RenderTarget::Image(image_handle),
clear_color: ClearColorConfig::Custom(theme.viewport_background_color),
..default()
},
EditorCam::default(),
Transform::from_translation(Vec3::ONE * 5.).looking_at(Vec3::ZERO, Vec3::Y),
RenderLayers::from_layers(&[0, 1]),
))
.id();

query.get_mut(pane_root).unwrap().camera = camera_id;
}

fn update_render_target_size(
query: Query<(Entity, &Bevy3dViewport)>,
mut camera_query: Query<&Camera>,
content: Query<&PaneContentNode>,
children_query: Query<&Children>,
computed_node_query: Query<&ComputedNode, Changed<ComputedNode>>,
mut images: ResMut<Assets<Image>>,
) {
for (pane_root, viewport) in &query {
let content_node_id = children_query
.iter_descendants(pane_root)
.find(|e| content.contains(*e))
.unwrap();

let Ok(computed_node) = computed_node_query.get(content_node_id) else {
continue;
};
// TODO Convert to physical pixels
let content_node_size = computed_node.size();

let camera = camera_query.get_mut(viewport.camera).unwrap();

let image_handle = camera.target.as_image().unwrap();
let size = Extent3d {
width: u32::max(1, content_node_size.x as u32),
height: u32::max(1, content_node_size.y as u32),
depth_or_array_layers: 1,
};
images.get_mut(image_handle).unwrap().resize(size);
}
}
15 changes: 13 additions & 2 deletions crates/bevy_editor/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,21 @@ fn main() {
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
mut materials_2d: ResMut<Assets<ColorMaterial>>,
mut materials_3d: ResMut<Assets<StandardMaterial>>,
) {
commands.spawn((
Mesh2d(meshes.add(Circle::new(50.0))),
MeshMaterial2d(materials.add(ColorMaterial::from_color(Color::WHITE))),
MeshMaterial2d(materials_2d.add(Color::WHITE)),
));

commands.spawn((
Mesh3d(meshes.add(Cuboid::from_length(1.0))),
MeshMaterial3d(materials_3d.add(Color::WHITE)),
));

commands.spawn((
DirectionalLight::default(),
Transform::default().looking_to(Vec3::NEG_ONE, Vec3::Y),
));
}
2 changes: 1 addition & 1 deletion crates/bevy_editor_cam/src/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ impl EditorCamInputEvent {
.sum();

let zoom_amount = match pointer {
// TODO: add pinch zoom support, probably in mod_picking
// TODO: add pinch zoom support, probably in bevy_picking
PointerId::Mouse => mouse_wheel
.read()
.map(|mw| {
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_pane_layout/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ fn setup(
.set_parent(divider)
.id();

spawn_pane(&mut commands, &theme, 0.70, "Viewport 2D").set_parent(asset_browser_divider);
spawn_pane(&mut commands, &theme, 0.70, "Viewport 3D").set_parent(asset_browser_divider);
spawn_resize_handle(&mut commands, Divider::Vertical).set_parent(asset_browser_divider);
spawn_pane(&mut commands, &theme, 0.30, "Asset Browser").set_parent(asset_browser_divider);
}
Expand Down
Loading