bevy-inspector-egui
bevy-inspector-egui copied to clipboard
Game View not rendering
Hello I'm still inexperienced with bevy and bevy-inspector-egui, and I'm having some problems trying to render my game view together with the dock integration, the ui renders normally, and the game renders normally when not using the editor ui, but when i put the two together only the ui renders. Here's my code:
// inspector_tool.rs
use std::any::TypeId;
use bevy::{
asset::{ReflectAsset, UntypedAssetId},
prelude::*,
reflect::TypeRegistry,
render::camera::CameraProjection,
window::PrimaryWindow,
};
use bevy_inspector_egui::{
bevy_egui::{self, egui, EguiContext, EguiPostUpdateSet},
bevy_inspector::{
self,
hierarchy::{hierarchy_ui, SelectedEntities},
ui_for_entities_shared_components, ui_for_entity_with_children,
},
DefaultInspectorConfigPlugin,
};
use egui_dock::{
egui::{CollapsingHeader, Slider, TextureHandle},
DockArea, DockState, NodeIndex, Style, TabViewer,
};
use egui_plot::{Line, Plot, Points};
use crate::{
camera::MainCamera,
noise_tool::{
update_noise_texture, CellReturnType, FractalConfig, ModifierConfig, NoiseSettings,
NoiseType, NoiseUpdateEvent,
},
};
#[cfg(egui_dock_gizmo)]
use transform_gizmo_egui::GizmoMode;
/// Placeholder type if gizmo is disabled.
#[cfg(not(egui_dock_gizmo))]
#[derive(Clone, Copy)]
pub struct GizmoMode;
// Main Plugin
pub struct EditorPlugin;
impl Plugin for EditorPlugin {
fn build(&self, app: &mut App) {
app.add_plugins(DefaultInspectorConfigPlugin)
.add_plugins(bevy_egui::EguiPlugin)
.init_resource::<NoiseSettings>()
.init_resource::<EditorState>()
.add_event::<NoiseUpdateEvent>()
.add_systems(
PostUpdate,
(
draw_editor_ui
.before(EguiPostUpdateSet::ProcessOutput)
.before(bevy_egui::end_pass_system)
.before(bevy::transform::TransformSystem::TransformPropagate),
auto_update_preview.after(draw_editor_ui),
),
)
.add_systems(Update, set_gizmo_mode)
.add_systems(Update, update_noise_texture.before(auto_update_preview));
}
}
#[derive(Resource)]
pub struct EditorTextureHandle {
pub handle: Option<egui::TextureHandle>,
}
impl Default for EditorTextureHandle {
fn default() -> Self {
Self { handle: None }
}
}
// Editor State
#[derive(Resource)]
pub struct EditorState {
pub dock_state: DockState<EditorTab>,
pub selected_entity: SelectedEntities,
pub viewport_rect: egui::Rect,
pub selection: InspectorSelection,
pub gizmo_mode: GizmoMode,
}
pub enum InspectorSelection {
Entities,
Resource(TypeId, String),
Asset(TypeId, String, UntypedAssetId),
}
#[derive(Debug)]
pub enum EditorTab {
GameView,
Hierarchy,
Resources,
Assets,
Inspector,
NoiseTool,
}
impl Default for EditorState {
fn default() -> Self {
let mut state = DockState::new(vec![EditorTab::GameView]);
let tree = state.main_surface_mut();
let [game, _inspector] = tree.split_right(
NodeIndex::root(),
0.75,
vec![EditorTab::Inspector, EditorTab::NoiseTool],
);
let [game, _hierarchy] = tree.split_left(game, 0.2, vec![EditorTab::Hierarchy]);
let [_game, _bottom] =
tree.split_below(game, 0.8, vec![EditorTab::Resources, EditorTab::Assets]);
Self {
dock_state: state,
selected_entity: SelectedEntities::default(),
selection: InspectorSelection::Entities,
viewport_rect: egui::Rect::NOTHING,
gizmo_mode: GizmoMode,
}
}
}
// Systems
pub fn draw_editor_ui(world: &mut World) {
let Ok(egui_context) = world
.query_filtered::<&mut EguiContext, With<PrimaryWindow>>()
.get_single(world)
else {
return;
};
let mut ctx = egui_context.clone();
let ctx = ctx.get_mut();
world.resource_scope::<EditorState, _>(|world, editor_state| {
let state = editor_state.into_inner();
let mut viewport_rect = &mut state.viewport_rect;
let mut selected_entities = &mut state.selected_entity;
let mut selection = &mut state.selection;
world.resource_scope::<NoiseSettings, _>(|world, mut noise_settings| {
world.resource_scope::<EditorTextureHandle, _>(|world, mut texture_handle| {
let mut texture_handle = match &mut texture_handle.handle {
Some(handle) => handle,
None => return,
};
let mut tab_viewer = EditorTabViewer {
world,
viewport_rect: &mut viewport_rect,
noise_settings: &mut noise_settings,
texture_handle: &mut texture_handle,
selected_entities: &mut selected_entities,
selection: &mut selection,
};
DockArea::new(&mut state.dock_state)
.style(Style::from_egui(ctx.style().as_ref()))
.show(ctx, &mut tab_viewer);
});
});
});
}
fn select_resource(
ui: &mut egui::Ui,
type_registry: &TypeRegistry,
selection: &mut InspectorSelection,
) {
let mut resources: Vec<_> = type_registry
.iter()
.filter(|registration| registration.data::<ReflectResource>().is_some())
.map(|registration| {
(
registration.type_info().type_path_table().short_path(),
registration.type_id(),
)
})
.collect();
resources.sort_by(|(name_a, _), (name_b, _)| name_a.cmp(name_b));
for (resource_name, type_id) in resources {
let selected = match *selection {
InspectorSelection::Resource(selected, _) => selected == type_id,
_ => false,
};
if ui.selectable_label(selected, resource_name).clicked() {
*selection = InspectorSelection::Resource(type_id, resource_name.to_string());
}
}
}
fn select_asset(
ui: &mut egui::Ui,
type_registry: &TypeRegistry,
world: &World,
selection: &mut InspectorSelection,
) {
let mut assets: Vec<_> = type_registry
.iter()
.filter_map(|registration| {
let reflect_asset = registration.data::<ReflectAsset>()?;
Some((
registration.type_info().type_path_table().short_path(),
registration.type_id(),
reflect_asset,
))
})
.collect();
assets.sort_by(|(name_a, ..), (name_b, ..)| name_a.cmp(name_b));
for (asset_name, asset_type_id, reflect_asset) in assets {
let handles: Vec<_> = reflect_asset.ids(world).collect();
ui.collapsing(format!("{asset_name} ({})", handles.len()), |ui| {
for handle in handles {
let selected = match *selection {
InspectorSelection::Asset(_, _, selected_id) => selected_id == handle,
_ => false,
};
if ui
.selectable_label(selected, format!("{:?}", handle))
.clicked()
{
*selection =
InspectorSelection::Asset(asset_type_id, asset_name.to_string(), handle);
}
}
});
}
}
// Tab Implementation
struct EditorTabViewer<'a> {
world: &'a mut World,
viewport_rect: &'a mut egui::Rect,
noise_settings: &'a mut NoiseSettings,
texture_handle: &'a mut TextureHandle,
selected_entities: &'a mut SelectedEntities,
selection: &'a mut InspectorSelection,
}
impl TabViewer for EditorTabViewer<'_> {
type Tab = EditorTab;
fn title(&mut self, tab: &mut Self::Tab) -> egui::WidgetText {
format!("{tab:?}").into()
}
fn ui(&mut self, ui: &mut egui::Ui, tab: &mut Self::Tab) {
let type_registry = self.world.resource::<AppTypeRegistry>().0.clone();
let type_registry = type_registry.read();
match tab {
EditorTab::GameView => {
*self.viewport_rect = ui.clip_rect();
debug!(
"GameView tab is being rendered with viewport rect: {:?}",
self.viewport_rect
);
draw_gizmo(ui, self.world, &self.selected_entities);
}
EditorTab::Hierarchy => {
let selected = hierarchy_ui(self.world, ui, self.selected_entities);
if selected {
*self.selection = InspectorSelection::Entities;
}
}
EditorTab::Resources => select_resource(ui, &type_registry, self.selection),
EditorTab::Assets => select_asset(ui, &type_registry, self.world, self.selection),
EditorTab::Inspector => match *self.selection {
InspectorSelection::Entities => match self.selected_entities.as_slice() {
&[entity] => ui_for_entity_with_children(self.world, entity, ui),
entities => ui_for_entities_shared_components(self.world, entities, ui),
},
InspectorSelection::Resource(type_id, ref name) => {
ui.label(name);
bevy_inspector::by_type_id::ui_for_resource(
self.world,
type_id,
ui,
name,
&type_registry,
)
}
InspectorSelection::Asset(type_id, ref name, handle) => {
ui.label(name);
bevy_inspector::by_type_id::ui_for_asset(
self.world,
type_id,
handle,
ui,
&type_registry,
);
}
},
EditorTab::NoiseTool => {
ui.heading("Noise Generation Settings");
CollapsingHeader::new("Base Noise Settings")
.default_open(true)
.show(ui, |ui| {
// Noise type selection
ui.horizontal(|ui| {
ui.label("Noise Type:");
egui::ComboBox::from_id_salt("noise_type")
.selected_text(format!("{:?}", self.noise_settings.config.base))
.show_ui(ui, |ui| {
ui.selectable_value(
&mut self.noise_settings.config.base,
NoiseType::Simplex {
seed: 42,
frequency: 0.0,
},
"Simplex",
);
ui.selectable_value(
&mut self.noise_settings.config.base,
NoiseType::Perlin {
seed: 42,
frequency: 0.0,
},
"Perlin",
);
ui.selectable_value(
&mut self.noise_settings.config.base,
NoiseType::Worley {
seed: 42,
jitter: 1.0,
frequency: 0.0,
return_type: CellReturnType::CellValue,
},
"Worley",
);
ui.selectable_value(
&mut self.noise_settings.config.base,
NoiseType::Value {
seed: 42,
frequency: 0.0,
},
"Value",
);
});
});
// Common parameters
match &mut self.noise_settings.config.base {
NoiseType::Simplex { seed, frequency }
| NoiseType::Perlin { seed, frequency }
| NoiseType::Value { seed, frequency } => {
ui.add(Slider::new(seed, 0..=9999).text("Seed"));
ui.add(Slider::new(frequency, 0.001..=1.0).text("Frequency"));
}
NoiseType::Worley {
seed,
jitter,
frequency,
return_type,
} => {
ui.add(Slider::new(seed, 0..=9999).text("Seed"));
ui.add(Slider::new(frequency, 0.001..=1.0).text("Frequency"));
ui.add(Slider::new(jitter, 0.0..=1.0).text("Jitter"));
ui.horizontal(|ui| {
ui.label("Return Type:");
egui::ComboBox::from_id_salt("worley_return")
.selected_text(format!("{:?}", return_type))
.show_ui(ui, |ui| {
ui.selectable_value(
return_type,
CellReturnType::CellValue,
"Cell Value",
);
ui.selectable_value(
return_type,
CellReturnType::Distance,
"Distance",
);
ui.selectable_value(
return_type,
CellReturnType::Distance2,
"Distance²",
);
});
});
}
_ => {}
}
});
// Fractal Configuration ================================================
CollapsingHeader::new("Fractal Settings").show(ui, |ui| {
ui.horizontal(|ui| {
ui.label("Fractal Type:");
egui::ComboBox::from_id_salt("fractal_type")
.selected_text(format!("{:?}", &self.noise_settings.config.fractal))
.show_ui(ui, |ui| {
ui.selectable_value(
&mut self.noise_settings.config.fractal,
Option::<FractalConfig>::None,
"None",
);
ui.selectable_value(
&mut self.noise_settings.config.fractal,
Some(FractalConfig::Fbm {
octaves: 4,
gain: 0.5,
lacunarity: 2.0,
}),
"FBM",
);
ui.selectable_value(
&mut self.noise_settings.config.fractal,
Some(FractalConfig::Ridged {
octaves: 4,
gain: 0.5,
lacunarity: 2.0,
}),
"Ridged",
);
});
});
if let Some(fractal) = &mut self.noise_settings.config.fractal {
match fractal {
FractalConfig::Fbm {
octaves,
gain,
lacunarity,
}
| FractalConfig::Ridged {
octaves,
gain,
lacunarity,
} => {
ui.add(Slider::new(octaves, 1..=8).text("Octaves"));
ui.add(Slider::new(lacunarity, 1.0..=4.0).text("Lacunarity"));
ui.add(Slider::new(gain, 0.0..=1.0).text("Gain"));
}
}
}
});
// Noise type selection
CollapsingHeader::new("Modifiers").show(ui, |ui| {
// Spline editor
ui.separator();
ui.label("Cutout Spline Remapping:");
// Add spline point button
if ui.button("Add Point").clicked() {
self.noise_settings.spline_points.push([0.5, 0.5]);
}
// Capture input state before plot
let space_pressed = ui.input(|i| i.key_down(egui::Key::Space));
// Spline editor plot
Plot::new("spline_plot")
.view_aspect(1.0)
.allow_drag(false)
.allow_zoom(false)
.allow_scroll(false)
.allow_boxed_zoom(false)
.show(ui, |plot_ui| {
plot_ui.set_plot_bounds(egui_plot::PlotBounds::from_min_max(
[0.0, 0.0],
[1.0, 1.0],
));
// Handle point interaction
let mut to_remove = None;
let response = plot_ui.response();
let pointer = plot_ui.pointer_coordinate();
if response.hovered() {
// Add new points with space+click
if space_pressed && response.clicked() {
if let Some(pos) = pointer {
let clamped_x = pos.x.clamp(0.0, 1.0) as f32;
let clamped_y = pos.y.clamp(0.0, 1.0) as f32;
// Update points
self.noise_settings
.spline_points
.push([clamped_x, clamped_y]);
self.noise_settings.spline_points.sort_by(|a, b| {
a[0].partial_cmp(&b[0])
.unwrap_or(std::cmp::Ordering::Equal)
});
}
}
// Check for nearest point
let mut nearest_dist = f64::MAX;
let mut nearest_index = None;
for (i, &[x, y]) in
self.noise_settings.spline_points.iter().enumerate()
{
if let Some(p) = pointer {
let dist = ((x as f64 - p.x).powi(2)
+ (y as f64 - p.y).powi(2))
.sqrt();
if dist < 0.05 {
if dist < nearest_dist {
nearest_dist = dist;
nearest_index = Some(i);
}
}
}
}
// Handle dragging
if let Some(index) = nearest_index {
if response.dragged() {
if let Some(pos) = pointer {
let clamped_x = pos.x.clamp(0.0, 1.0) as f32;
let clamped_y = pos.y.clamp(0.0, 1.0) as f32;
self.noise_settings.spline_points[index] =
[clamped_x, clamped_y];
}
}
// Double-click to delete
if response.double_clicked() {
to_remove = Some(index);
}
}
}
// Remove point if needed
if let Some(index) = to_remove {
self.noise_settings.spline_points.remove(index);
}
// Draw spline line
if self.noise_settings.spline_points.len() >= 2 {
let mut points: Vec<_> = self
.noise_settings
.spline_points
.iter()
.map(|&[x, y]| [x as f64, y as f64])
.collect();
points.sort_by(|a, b| a[0].partial_cmp(&b[0]).unwrap());
let line = Line::new(points.clone())
.color(egui::Color32::GREEN)
.width(2.0);
plot_ui.line(line);
}
// Draw points
for (i, &[x, y]) in self.noise_settings.spline_points.iter().enumerate()
{
let point = Points::new(vec![[x as f64, y as f64]])
.color(egui::Color32::RED)
.radius(5.0);
if Some(i) == self.noise_settings.selected_point {
plot_ui.points(point.color(egui::Color32::GOLD));
} else {
plot_ui.points(point);
}
}
});
let points_len = self.noise_settings.spline_points.len();
if let Some(index) = self.noise_settings.selected_point {
if index < points_len {
let mut x = self.noise_settings.spline_points[index][0];
let mut y = self.noise_settings.spline_points[index][1];
ui.horizontal(|ui| {
ui.add(
egui::DragValue::new(&mut x)
.range(0.0..=1.0)
.speed(0.01)
.prefix("X: "),
);
ui.add(
egui::DragValue::new(&mut y)
.range(0.0..=1.0)
.speed(0.01)
.prefix("Y: "),
);
if ui.button("Remove").clicked() {
self.noise_settings.spline_points.remove(index);
self.noise_settings.selected_point = None;
} else {
self.noise_settings.spline_points[index] = [x, y];
}
});
}
}
ui.separator();
ui.label("Modifier Stack:");
let mut to_remove = None;
for (i, modifier) in self.noise_settings.config.modifiers.iter_mut().enumerate()
{
ui.horizontal(|ui| {
match modifier {
ModifierConfig::Remap { min, max } => {
ui.label("Remap:");
ui.vertical(|ui| {
ui.add(Slider::new(min, -1.0..=*max).text("Min"));
ui.add(Slider::new(max, *min..=1.0).text("Max"));
});
}
ModifierConfig::Scale { factor } => {
ui.label("Scale:");
ui.add(Slider::new(factor, 0.1..=4.0));
}
ModifierConfig::Bias { value } => {
ui.label("Bias:");
ui.add(Slider::new(value, -1.0..=1.0));
}
}
if ui.button("×").clicked() {
to_remove = Some(i);
}
});
}
if let Some(index) = to_remove {
self.noise_settings.config.modifiers.remove(index);
}
ui.horizontal(|ui| {
if ui.button("Add Remap").clicked() {
self.noise_settings
.config
.modifiers
.push(ModifierConfig::Remap { min: 0.0, max: 1.0 });
}
if ui.button("Add Scale").clicked() {
self.noise_settings
.config
.modifiers
.push(ModifierConfig::Scale { factor: 1.0 });
}
if ui.button("Add Bias").clicked() {
self.noise_settings
.config
.modifiers
.push(ModifierConfig::Bias { value: 0.0 });
}
});
});
ui.separator();
ui.label("Noise Preview");
ui.vertical(|ui| {
ui.label("Preview Size:");
ui.add(Slider::new(&mut self.noise_settings.size[0], 64..=512).text("Size X"));
ui.add(Slider::new(&mut self.noise_settings.size[1], 64..=512).text("Size Y"));
});
let size = self.texture_handle.size_vec2();
ui.image((self.texture_handle.id(), size));
}
}
}
}
fn auto_update_preview(
noise_settings: Res<NoiseSettings>,
mut events: EventWriter<NoiseUpdateEvent>,
) {
if noise_settings.is_changed() {
events.send(NoiseUpdateEvent);
}
}
fn set_gizmo_mode(input: Res<ButtonInput<KeyCode>>, mut ui_state: ResMut<EditorState>) {
#[cfg(egui_dock_gizmo)]
let keybinds = [
(KeyCode::KeyR, GizmoMode::Rotate),
(KeyCode::KeyT, GizmoMode::Translate),
(KeyCode::KeyS, GizmoMode::Scale),
];
#[cfg(not(egui_dock_gizmo))]
let keybinds = [];
for (key, mode) in keybinds {
if input.just_pressed(key) {
ui_state.gizmo_mode = mode;
}
}
}
fn draw_gizmo(ui: &mut egui::Ui, world: &mut World, selected_entities: &SelectedEntities) {
let (cam_transform, projection) = world
.query_filtered::<(&GlobalTransform, &Projection), With<MainCamera>>()
.single(world);
let view_matrix = Mat4::from(cam_transform.affine().inverse());
let projection_matrix = projection.get_clip_from_view();
if selected_entities.len() != 1 {
#[allow(clippy::needless_return)]
return;
}
}
// camera.rs
use crate::inspector_tool::EditorState;
use bevy::{
input::mouse::AccumulatedMouseMotion,
prelude::*,
render::{
camera::{RenderTarget, Viewport},
view::RenderLayers,
},
window::{CursorGrabMode, PrimaryWindow},
};
use bevy_inspector_egui::{bevy_egui::EguiContextSettings, prelude::*};
#[derive(Component)]
pub struct MainCamera;
pub struct CameraPlugin;
#[derive(Reflect, Resource, InspectorOptions)]
#[reflect(Resource, InspectorOptions)]
pub struct MovementSettings {
#[inspector(min = 0.0, max = 100.0)]
pub speed: f32,
#[inspector(min = 0.0, max = 1.0)]
pub sensitivity: f32,
#[inspector(min = 0.0, max = 10.0)]
pub vertical_multiplier: f32,
}
impl Default for MovementSettings {
fn default() -> Self {
Self {
speed: 12.0,
sensitivity: 0.00012,
vertical_multiplier: 1.0,
}
}
}
impl Plugin for CameraPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<MovementSettings>()
.register_type::<MovementSettings>()
.add_systems(Startup, setup_camera)
.add_systems(Update, camera_movement)
.add_systems(Update, handle_mouse_movement)
.add_systems(Update, update_toggle_grab_cursor);
}
}
fn setup_camera(
mut commands: Commands,
mut primary_window: Query<&mut Window, With<PrimaryWindow>>,
) {
commands.spawn((
Camera3d::default(),
Transform::from_xyz(0.0, 5.0, 10.0).looking_at(Vec3::ZERO, Vec3::Y),
MainCamera,
RenderLayers::layer(0),
));
if let Ok(mut window) = primary_window.get_single_mut() {
toggle_grab_cursor(&mut window);
} else {
warn!("Primary window not found for `initial_grab_cursor`!");
}
}
fn toggle_grab_cursor(window: &mut Window) {
match window.cursor_options.grab_mode {
CursorGrabMode::None => {
window.cursor_options.grab_mode = CursorGrabMode::Confined;
window.cursor_options.visible = true;
}
_ => {
window.cursor_options.grab_mode = CursorGrabMode::None;
window.cursor_options.visible = true;
}
}
}
fn update_toggle_grab_cursor(
keys: Res<ButtonInput<KeyCode>>,
mut primary_window: Query<&mut Window, With<PrimaryWindow>>,
) {
if let Ok(mut window) = primary_window.get_single_mut() {
if keys.just_pressed(KeyCode::Escape) {
toggle_grab_cursor(&mut window);
}
} else {
warn!("Primary window not found for `update_toggle_grab_cursor`!");
}
}
fn handle_mouse_movement(
mut query: Query<&mut Transform, With<Camera>>,
primary_window: Query<&Window, With<PrimaryWindow>>,
mouse_motion: Res<AccumulatedMouseMotion>,
settings: Res<MovementSettings>,
) {
let Ok(window) = primary_window.get_single() else {
return;
};
for mut transform in query.iter_mut() {
let (_, mut pitch, _) = transform.rotation.to_euler(EulerRot::YXZ);
if mouse_motion.delta.length_squared() < 0.1 {
return;
}
let (mut yaw, _, _) = transform.rotation.to_euler(EulerRot::YXZ);
match window.cursor_options.grab_mode {
CursorGrabMode::None => (),
_ => {
// Using smallest of height or width ensures equal vertical and horizontal sensitivity
let window_scale = window.height().min(window.width());
pitch -= (settings.sensitivity * mouse_motion.delta.y * window_scale).to_radians();
yaw -= (settings.sensitivity * mouse_motion.delta.x * window_scale).to_radians();
}
}
pitch = pitch.clamp(-1.57, 1.57);
// Order is important to prevent unintended roll
transform.rotation =
Quat::from_axis_angle(Vec3::Y, yaw) * Quat::from_axis_angle(Vec3::X, pitch);
}
}
fn camera_movement(
keys: Res<ButtonInput<KeyCode>>,
mut query: Query<&mut Transform, With<Camera>>,
primary_window: Query<&Window, With<PrimaryWindow>>,
time: Res<Time>,
settings: Res<MovementSettings>,
) {
if let Ok(window) = primary_window.get_single() {
for mut transform in query.iter_mut() {
let mut velocity = Vec3::ZERO;
let local_z = transform.local_z();
let forward = -Vec3::new(local_z.x, 0., local_z.z);
let right = Vec3::new(local_z.z, 0., -local_z.x);
for key in keys.get_pressed() {
match window.cursor_options.grab_mode {
CursorGrabMode::None => (),
_ => {
let key = *key;
if key == KeyCode::KeyW {
velocity += forward;
} else if key == KeyCode::KeyS {
velocity -= forward;
} else if key == KeyCode::KeyA {
velocity -= right;
} else if key == KeyCode::KeyD {
velocity += right;
} else if key == KeyCode::Space {
velocity += Vec3::Y * settings.vertical_multiplier;
} else if key == KeyCode::ShiftLeft {
velocity -= Vec3::Y * settings.vertical_multiplier;
}
}
}
}
transform.translation =
transform.translation + velocity * settings.speed * time.delta_secs();
}
} else {
warn!("Primary window not found for `player_move`!");
}
}
// make camera only render to view not obstructed by UI
pub fn set_camera_viewport(
ui_state: Res<EditorState>,
primary_window: Query<&mut Window, With<PrimaryWindow>>,
egui_settings: Query<&EguiContextSettings>,
mut cameras: Query<&mut Camera, With<MainCamera>>,
) {
let mut cam = cameras.single_mut();
let Ok(window) = primary_window.get_single() else {
return;
};
let scale_factor = window.scale_factor() * egui_settings.single().scale_factor;
let viewport_pos = ui_state.viewport_rect.left_top().to_vec2() * scale_factor;
let viewport_size = ui_state.viewport_rect.size() * scale_factor;
debug!("Viewport Position: {:?}", viewport_pos);
debug!("Viewport Size: {:?}", viewport_size);
let physical_position = UVec2::new(viewport_pos.x as u32, viewport_pos.y as u32);
let physical_size = UVec2::new(viewport_size.x as u32, viewport_size.y as u32);
// The desired viewport rectangle at its offset in "physical pixel space"
let rect = physical_position + physical_size;
let window_size = window.physical_size();
debug!("Window Size: {:?}", window_size);
debug!("Rect: {:?}", rect);
// wgpu will panic if trying to set a viewport rect which has coordinates extending
// past the size of the render target, i.e. the physical window in our case.
// Typically this shouldn't happen- but during init and resizing etc. edge cases might occur.
// Simply do nothing in those cases.
if rect.x <= window_size.x && rect.y <= window_size.y {
cam.viewport = Some(Viewport {
physical_position,
physical_size,
depth: 0.0..1.0,
});
debug!("Camera viewport: {:?}", cam.viewport);
}
}
// main.rs
use bevy::{log::LogPlugin, prelude::*, render::view::RenderLayers, window::PrimaryWindow};
use bevy_inspector_egui::bevy_egui::EguiContext;
use camera::{set_camera_viewport, CameraPlugin};
use common::common::logging::setup_file_logging;
use egui_dock::egui::{self, TextureOptions};
use inspector_tool::{draw_editor_ui, EditorPlugin, EditorTextureHandle};
use noise_tool::{generate_noise_texture, NoiseSettings};
mod camera;
mod inspector_tool;
mod noise_tool;
fn main() {
let _guard = setup_file_logging();
App::new()
.add_plugins(DefaultPlugins.build().disable::<LogPlugin>())
.add_plugins(EditorPlugin)
.add_plugins(CameraPlugin)
.add_systems(Startup, setup)
.add_systems(PostUpdate, set_camera_viewport.after(draw_editor_ui))
.register_type::<Option<Handle<Image>>>()
.register_type::<AlphaMode>()
.run();
}
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
mut contexts: Query<&mut EguiContext, With<PrimaryWindow>>,
noise_settings: Res<NoiseSettings>,
) {
let mut editor_texture_handle = EditorTextureHandle::default();
editor_texture_handle.handle.get_or_insert_with(|| {
let mut ctx = contexts.get_single_mut().expect("EguiContext not found");
let ctx = ctx.get_mut();
let pixels = generate_noise_texture(
&noise_settings,
UVec2 {
x: noise_settings.size[0],
y: noise_settings.size[1],
},
);
let pixels: Vec<_> = pixels
.chunks(4)
.map(|p| egui::Color32::from_rgba_unmultiplied(p[0], p[1], p[2], p[3]))
.collect();
let color_image = egui::ColorImage {
size: [512, 512],
pixels,
};
ctx.load_texture("noise_texture", color_image, TextureOptions::LINEAR)
});
commands.insert_resource(editor_texture_handle);
let box_size = 2.0;
let box_thickness = 0.15;
let box_offset = (box_size + box_thickness) / 2.0;
// left - red
let mut transform = Transform::from_xyz(-box_offset, box_offset, 0.0);
transform.rotate(Quat::from_rotation_z(std::f32::consts::FRAC_PI_2));
commands.spawn((
Mesh3d(meshes.add(Cuboid::new(box_size, box_thickness, box_size))),
MeshMaterial3d(materials.add(StandardMaterial {
base_color: Color::srgb(0.63, 0.065, 0.05),
..Default::default()
})),
transform,
));
// right - green
let mut transform = Transform::from_xyz(box_offset, box_offset, 0.0);
transform.rotate(Quat::from_rotation_z(std::f32::consts::FRAC_PI_2));
commands.spawn((
Mesh3d(meshes.add(Cuboid::new(box_size, box_thickness, box_size))),
transform,
MeshMaterial3d(materials.add(StandardMaterial {
base_color: Color::srgb(0.14, 0.45, 0.091),
..Default::default()
})),
RenderLayers::layer(0),
));
// bottom - white
commands.spawn((
Mesh3d(meshes.add(Cuboid::new(
box_size + 2.0 * box_thickness,
box_thickness,
box_size,
))),
MeshMaterial3d(materials.add(StandardMaterial {
base_color: Color::srgb(0.725, 0.71, 0.68),
..Default::default()
})),
RenderLayers::layer(0),
));
// top - white
let transform = Transform::from_xyz(0.0, 2.0 * box_offset, 0.0);
commands.spawn((
Mesh3d(meshes.add(Cuboid::new(
box_size + 2.0 * box_thickness,
box_thickness,
box_size,
))),
transform,
MeshMaterial3d(materials.add(StandardMaterial {
base_color: Color::srgb(0.725, 0.71, 0.68),
..Default::default()
})),
RenderLayers::layer(0),
));
// back - white
let mut transform = Transform::from_xyz(0.0, box_offset, -box_offset);
transform.rotate(Quat::from_rotation_x(std::f32::consts::FRAC_PI_2));
commands.spawn((
Mesh3d(meshes.add(Cuboid::new(
box_size + 2.0 * box_thickness,
box_thickness,
box_size + 2.0 * box_thickness,
))),
transform,
MeshMaterial3d(materials.add(StandardMaterial {
base_color: Color::srgb(0.725, 0.71, 0.68),
..Default::default()
})),
RenderLayers::layer(0),
));
// ambient light
commands.insert_resource(AmbientLight {
color: Color::WHITE,
brightness: 0.02,
});
// top light
commands
.spawn((
Mesh3d(meshes.add(Plane3d::default().mesh().size(0.4, 0.4))),
Transform::from_matrix(Mat4::from_scale_rotation_translation(
Vec3::ONE,
Quat::from_rotation_x(std::f32::consts::PI),
Vec3::new(0.0, box_size + 0.5 * box_thickness, 0.0),
)),
MeshMaterial3d(materials.add(StandardMaterial {
base_color: Color::WHITE,
emissive: LinearRgba::WHITE * 100.0,
..Default::default()
})),
RenderLayers::layer(0),
))
.with_children(|builder| {
builder.spawn((
PointLight {
color: Color::WHITE,
intensity: 25000.0,
..Default::default()
},
Transform::from_translation((box_thickness + 0.05) * Vec3::Y),
RenderLayers::layer(0),
));
});
// directional light
commands.spawn((
DirectionalLight {
illuminance: 2000.0,
..default()
},
Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::PI / 2.0)),
));
}
if there is need for anything let me know and I will try to provide, thanks in advance for the help.