#![allow(irrefutable_let_patterns)]
#![cfg(not(target_arch = "wasm32"))]
use blade_graphics as gpu;
use blade_helpers::ControlledCamera;
use std::{
fmt, fs,
path::{Path, PathBuf},
sync::Arc,
time,
};
const RENDER_WHILE_LOADING: bool = true;
const MAX_DEPTH: f32 = 1e9;
#[derive(Clone, Copy, PartialEq, strum::EnumIter)]
enum DebugBlitInput {
None,
SelectedBaseColor,
SelectedNormal,
Environment,
EnvironmentWeight,
}
impl fmt::Display for DebugBlitInput {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let desc = match *self {
Self::None => "-",
Self::SelectedBaseColor => "selected base color",
Self::SelectedNormal => "selected normal",
Self::Environment => "environment map",
Self::EnvironmentWeight => "environment weight",
};
desc.fmt(f)
}
}
struct TransformComponents {
scale: glam::Vec3,
rotation: glam::Quat,
translation: glam::Vec3,
}
impl From<gpu::Transform> for TransformComponents {
fn from(bm: gpu::Transform) -> Self {
let transposed = glam::Mat4 {
x_axis: bm.x.into(),
y_axis: bm.y.into(),
z_axis: bm.z.into(),
w_axis: glam::Vec4::W,
};
let (scale, rotation, translation) = transposed.transpose().to_scale_rotation_translation();
Self {
scale,
rotation,
translation,
}
}
}
impl From<transform_gizmo_egui::math::Transform> for TransformComponents {
fn from(t: transform_gizmo_egui::math::Transform) -> Self {
Self {
scale: glam::DVec3::from(t.scale).as_vec3(),
rotation: glam::DQuat::from(t.rotation).as_quat(),
translation: glam::DVec3::from(t.translation).as_vec3(),
}
}
}
impl TransformComponents {
fn to_blade(&self) -> gpu::Transform {
let m = glam::Mat4::from_scale_rotation_translation(
self.scale,
self.rotation,
self.translation,
)
.transpose();
gpu::Transform {
x: m.x_axis.into(),
y: m.y_axis.into(),
z: m.z_axis.into(),
}
}
fn to_egui(&self) -> transform_gizmo_egui::math::Transform {
transform_gizmo_egui::math::Transform {
scale: self.scale.as_dvec3().into(),
rotation: self.rotation.as_dquat().into(),
translation: self.translation.as_dvec3().into(),
}
}
fn is_inversible(&self) -> bool {
self.scale
.x
.abs()
.min(self.scale.y.abs())
.min(self.scale.z.abs())
> 0.01
}
}
struct ObjectExtra {
path: PathBuf,
}
#[derive(serde::Deserialize, serde::Serialize)]
struct ConfigCamera {
position: mint::Vector3<f32>,
orientation: mint::Quaternion<f32>,
fov_y: f32,
speed: f32,
}
fn default_transform() -> mint::RowMatrix3x4<f32> {
gpu::IDENTITY_TRANSFORM
}
fn default_luminocity() -> f32 {
1.0
}
#[derive(serde::Deserialize, serde::Serialize)]
struct ConfigObject {
path: String,
#[serde(default = "default_transform")]
transform: mint::RowMatrix3x4<f32>,
}
#[derive(serde::Deserialize, serde::Serialize)]
struct ConfigScene {
camera: ConfigCamera,
#[serde(default)]
environment_map: String,
#[serde(default = "default_luminocity")]
average_luminocity: f32,
objects: Vec<ConfigObject>,
}
struct Example {
scene_path: PathBuf,
scene_environment_map: String,
pacer: blade_render::util::FramePacer,
renderer: blade_render::Renderer,
scene_load_task: Option<choir::RunningTask>,
gui_painter: blade_egui::GuiPainter,
asset_hub: blade_render::AssetHub,
context: Arc<gpu::Context>,
surface: gpu::Surface,
environment_map: Option<blade_asset::Handle<blade_render::Texture>>,
objects: Vec<blade_render::Object>,
object_extras: Vec<ObjectExtra>,
selected_object_index: Option<usize>,
need_picked_selection_frames: usize,
have_objects_changed: bool,
gizmo: transform_gizmo_egui::Gizmo,
scene_revision: usize,
camera: ControlledCamera,
debug: blade_render::DebugConfig,
track_hot_reloads: bool,
need_accumulation_reset: bool,
is_point_selected: bool,
is_file_hovered: bool,
ray_config: blade_render::RayConfig,
denoiser_enabled: bool,
denoiser_config: blade_render::DenoiserConfig,
post_proc_config: blade_render::PostProcConfig,
debug_blit: Option<blade_render::DebugBlit>,
debug_blit_input: DebugBlitInput,
workers: Vec<choir::WorkerHandle>,
choir: Arc<choir::Choir>,
}
impl Example {
fn make_surface_config(physical_size: winit::dpi::PhysicalSize<u32>) -> gpu::SurfaceConfig {
gpu::SurfaceConfig {
size: gpu::Extent {
width: physical_size.width,
height: physical_size.height,
depth: 1,
},
usage: gpu::TextureUsage::TARGET,
display_sync: gpu::DisplaySync::Block,
..Default::default()
}
}
#[profiling::function]
fn new(window: &winit::window::Window) -> Self {
log::info!("Initializing");
let context = Arc::new(unsafe {
gpu::Context::init(gpu::ContextDesc {
presentation: true,
validation: cfg!(debug_assertions),
capture: true,
..Default::default()
})
.unwrap()
});
let surface_config = Self::make_surface_config(window.inner_size());
let surface_size = surface_config.size;
let surface = context
.create_surface_configured(window, surface_config)
.unwrap();
let surface_info = surface.info();
let num_workers = num_cpus::get_physical().max((num_cpus::get() * 3 + 2) / 4);
log::info!("Initializing Choir with {} workers", num_workers);
let choir = choir::Choir::new();
let workers = (0..num_workers)
.map(|i| choir.add_worker(&format!("Worker-{}", i)))
.collect();
let asset_hub = blade_render::AssetHub::new(Path::new("asset-cache"), &choir, &context);
let (shaders, shader_task) =
blade_render::Shaders::load("blade-render/code/".as_ref(), &asset_hub);
log::info!("Spinning up the renderer");
shader_task.join();
let mut pacer = blade_render::util::FramePacer::new(&context);
let (command_encoder, _) = pacer.begin_frame();
let render_config = blade_render::RenderConfig {
surface_size,
surface_info,
max_debug_lines: 1000,
};
let renderer = blade_render::Renderer::new(
command_encoder,
&context,
shaders,
&asset_hub.shaders,
&render_config,
);
pacer.end_frame(&context);
let gui_painter = blade_egui::GuiPainter::new(surface_info, &context);
Self {
scene_path: PathBuf::new(),
scene_environment_map: String::new(),
pacer,
renderer,
scene_load_task: None,
gui_painter,
asset_hub,
context,
surface,
environment_map: None,
objects: Vec::new(),
object_extras: Vec::new(),
selected_object_index: None,
need_picked_selection_frames: 0,
have_objects_changed: false,
gizmo: Default::default(),
scene_revision: 0,
camera: ControlledCamera::default(),
debug: blade_render::DebugConfig::default(),
track_hot_reloads: true,
need_accumulation_reset: true,
is_point_selected: false,
is_file_hovered: false,
ray_config: blade_helpers::default_ray_config(),
denoiser_enabled: true,
denoiser_config: blade_render::DenoiserConfig {
num_passes: 3,
temporal_weight: 0.1,
},
post_proc_config: blade_render::PostProcConfig {
average_luminocity: 1.0,
exposure_key_value: 1.0 / 9.6,
white_level: 1.0,
},
debug_blit: None,
debug_blit_input: DebugBlitInput::None,
workers,
choir,
}
}
fn destroy(&mut self) {
self.workers.clear();
self.pacer.destroy(&self.context);
self.gui_painter.destroy(&self.context);
self.renderer.destroy(&self.context);
self.asset_hub.destroy();
self.context.destroy_surface(&mut self.surface);
}
pub fn load_scene(&mut self, scene_path: &Path) {
if self.scene_load_task.is_some() {
log::error!("Unable to reload the scene while something is loading");
return;
}
self.objects.clear();
self.object_extras.clear();
self.selected_object_index = None;
self.have_objects_changed = true;
log::info!("Loading scene from: {}", scene_path.display());
let config_scene: ConfigScene =
ron::de::from_bytes(&fs::read(scene_path).expect("Unable to open the scene file"))
.expect("Unable to parse the scene file");
self.camera.inner = blade_render::Camera {
pos: config_scene.camera.position,
rot: glam::Quat::from(config_scene.camera.orientation)
.normalize()
.into(),
fov_y: config_scene.camera.fov_y,
depth: MAX_DEPTH,
};
self.camera.fly_speed = config_scene.camera.speed;
self.ray_config.environment_importance_sampling = !config_scene.environment_map.is_empty();
self.post_proc_config.average_luminocity = config_scene.average_luminocity;
self.environment_map = None;
let parent = scene_path.parent().unwrap();
let mut load_finish = self.choir.spawn("load finish").init_dummy();
if !config_scene.environment_map.is_empty() {
let meta = blade_render::texture::Meta {
format: gpu::TextureFormat::Rgba32Float,
generate_mips: false,
y_flip: false,
};
let (texture, texture_task) = self
.asset_hub
.textures
.load(parent.join(&config_scene.environment_map), meta);
load_finish.depend_on(texture_task);
self.environment_map = Some(texture);
}
for config_object in config_scene.objects {
let (model, model_task) = self.asset_hub.models.load(
parent.join(&config_object.path),
blade_render::model::Meta {
generate_tangents: true,
..Default::default()
},
);
load_finish.depend_on(model_task);
self.objects.push(blade_render::Object {
model,
transform: config_object.transform,
prev_transform: config_object.transform,
});
self.object_extras.push(ObjectExtra {
path: PathBuf::from(config_object.path),
});
}
self.scene_load_task = Some(load_finish.run());
self.scene_path = scene_path.to_owned();
self.scene_environment_map = config_scene.environment_map;
}
pub fn save_scene(&self, scene_path: &Path) {
let config_scene = ConfigScene {
camera: ConfigCamera {
position: self.camera.inner.pos,
orientation: self.camera.inner.rot,
fov_y: self.camera.inner.fov_y,
speed: self.camera.fly_speed,
},
environment_map: self.scene_environment_map.clone(),
average_luminocity: self.post_proc_config.average_luminocity,
objects: self
.objects
.iter()
.zip(self.object_extras.iter())
.map(|(object, extra)| ConfigObject {
path: extra.path.to_string_lossy().into_owned(),
transform: object.transform,
})
.collect(),
};
let string = ron::ser::to_string_pretty(&config_scene, ron::ser::PrettyConfig::default())
.expect("Unable to form the scene file");
fs::write(scene_path, &string).expect("Unable to write the scene file");
log::info!("Saving scene to: {}", scene_path.display());
}
fn reset_object_motion(&mut self) {
for object in self.objects.iter_mut() {
object.prev_transform = object.transform;
}
}
#[profiling::function]
fn render(
&mut self,
gui_primitives: &[egui::ClippedPrimitive],
gui_textures: &egui::TexturesDelta,
physical_size: winit::dpi::PhysicalSize<u32>,
scale_factor: f32,
) {
if self.track_hot_reloads {
self.need_accumulation_reset |= self.renderer.hot_reload(
&self.asset_hub,
&self.context,
self.pacer.last_sync_point().unwrap(),
);
}
let surface_config = Self::make_surface_config(physical_size);
let new_render_size = surface_config.size;
if new_render_size != self.renderer.get_surface_size() {
log::info!("Resizing to {}", new_render_size);
self.pacer.wait_for_previous_frame(&self.context);
self.context
.reconfigure_surface(&mut self.surface, surface_config);
}
let (command_encoder, temp) = self.pacer.begin_frame();
if new_render_size != self.renderer.get_surface_size() {
self.renderer
.resize_screen(new_render_size, command_encoder, &self.context);
self.need_accumulation_reset = true;
}
self.gui_painter
.update_textures(command_encoder, gui_textures, &self.context);
self.asset_hub.flush(command_encoder, &mut temp.buffers);
if let Some(ref task) = self.scene_load_task {
if task.is_done() {
log::info!("Scene is loaded");
self.scene_load_task = None;
self.have_objects_changed = true;
}
}
if self.scene_load_task.is_none() && self.have_objects_changed {
assert_eq!(self.objects.len(), self.object_extras.len());
self.renderer.build_scene(
command_encoder,
&self.objects,
self.environment_map,
&self.asset_hub,
&self.context,
temp,
);
self.have_objects_changed = false;
self.scene_revision += 1;
}
let do_render =
self.scene_load_task.is_none() || (RENDER_WHILE_LOADING && self.scene_revision != 0);
if do_render {
self.renderer.prepare(
command_encoder,
&self.camera.inner,
blade_render::FrameConfig {
frozen: false,
debug_draw: self.is_point_selected || self.is_file_hovered,
reset_variance: self.debug.mouse_pos.is_none(),
reset_reservoirs: self.need_accumulation_reset,
},
);
self.need_accumulation_reset = false;
if !self.objects.is_empty() {
self.renderer
.ray_trace(command_encoder, self.debug, self.ray_config);
if self.denoiser_enabled {
self.renderer.denoise(command_encoder, self.denoiser_config);
}
}
}
let frame = self.surface.acquire_frame();
command_encoder.init_texture(frame.texture());
if let mut pass = command_encoder.render(
"draw",
gpu::RenderTargetSet {
colors: &[gpu::RenderTarget {
view: frame.texture_view(),
init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack),
finish_op: gpu::FinishOp::Store,
}],
depth_stencil: None,
},
) {
let screen_desc = blade_egui::ScreenDescriptor {
physical_size: (physical_size.width, physical_size.height),
scale_factor,
};
if do_render {
let mut debug_blit_array = [blade_render::DebugBlit::default()];
let debug_blits = match self.debug_blit {
Some(ref blit) => {
debug_blit_array[0] = *blit;
&debug_blit_array[..]
}
None => &[],
};
self.renderer.post_proc(
&mut pass,
self.debug,
self.post_proc_config,
&[],
debug_blits,
);
}
self.gui_painter
.paint(&mut pass, gui_primitives, &screen_desc, &self.context);
}
command_encoder.present(frame);
let sync_point = self.pacer.end_frame(&self.context);
self.gui_painter.after_submit(sync_point);
self.reset_object_motion();
}
fn add_manipulation_gizmo(&mut self, obj_index: usize, ui: &mut egui::Ui) {
use transform_gizmo_egui::GizmoExt as _;
let view_matrix = self.camera.get_view_matrix();
let extent = self.renderer.get_surface_size();
let projection_matrix = self
.camera
.get_projection_matrix(extent.width as f32 / extent.height as f32);
self.gizmo.update_config(transform_gizmo_egui::GizmoConfig {
view_matrix: view_matrix.as_dmat4().into(),
projection_matrix: projection_matrix.as_dmat4().into(),
viewport: transform_gizmo_egui::Rect {
min: transform_gizmo_egui::math::Pos2::ZERO,
max: transform_gizmo_egui::math::Pos2 {
x: extent.width as f32,
y: extent.height as f32,
},
},
orientation: transform_gizmo_egui::GizmoOrientation::Global,
snapping: true,
..Default::default()
});
let object = &mut self.objects[obj_index];
let tc = TransformComponents::from(object.transform);
if let Some((_result, transforms)) = self.gizmo.interact(ui, &[tc.to_egui()]) {
object.transform = TransformComponents::from(transforms[0]).to_blade();
self.have_objects_changed = true;
}
}
#[profiling::function]
fn populate_view(&mut self, ui: &mut egui::Ui) {
use blade_helpers::{populate_debug_selection, ExposeHud as _};
use strum::IntoEnumIterator as _;
if self.scene_load_task.is_some() {
ui.horizontal(|ui| {
ui.label("Loading...");
ui.spinner();
});
for task in self.asset_hub.list_running_tasks() {
ui.label(format!("{}", task.as_ref()));
}
return;
}
let mut selection = blade_render::SelectionInfo::default();
if self.debug.mouse_pos.is_some() {
selection = self.renderer.read_debug_selection_info();
if self.need_picked_selection_frames > 0 {
self.need_picked_selection_frames -= 1;
self.selected_object_index = self.find_object(selection.custom_index);
}
}
egui::CollapsingHeader::new("Camera").show(ui, |ui| {
self.camera.populate_hud(ui);
});
egui::CollapsingHeader::new("Debug")
.default_open(true)
.show(ui, |ui| {
self.debug.populate_hud(ui);
populate_debug_selection(
&mut self.debug.mouse_pos,
&selection,
&self.asset_hub,
ui,
);
ui.label("Debug blit:");
egui::ComboBox::from_label("Input")
.selected_text(format!("{}", self.debug_blit_input))
.show_ui(ui, |ui| {
for value in DebugBlitInput::iter() {
ui.selectable_value(
&mut self.debug_blit_input,
value,
format!("{value}"),
);
}
});
let blit_view = match self.debug_blit_input {
DebugBlitInput::None => None,
DebugBlitInput::SelectedBaseColor => selection
.base_color_texture
.map(|handle| self.asset_hub.textures[handle].view),
DebugBlitInput::SelectedNormal => selection
.normal_texture
.map(|handle| self.asset_hub.textures[handle].view),
DebugBlitInput::Environment => Some(self.renderer.view_environment_main()),
DebugBlitInput::EnvironmentWeight => {
Some(self.renderer.view_environment_weight())
}
};
let min_size = 64u32;
self.debug_blit = if let Some(view) = blit_view {
let mut db = match self.debug_blit.take() {
Some(db) => db,
None => {
let mut db = blade_render::DebugBlit::default();
db.target_size = [min_size, min_size];
db
}
};
db.input = view;
let style = ui.style();
egui::Frame::group(style).show(ui, |ui| {
ui.add(egui::Slider::new(&mut db.mip_level, 0u32..=15u32).text("Mip"));
ui.add(
egui::Slider::new(&mut db.target_size[0], min_size..=1024u32)
.text("Target size"),
);
db.target_size[1] = db.target_size[0];
});
Some(db)
} else {
None
};
});
let old_ray_config = self.ray_config;
egui::CollapsingHeader::new("Ray Trace")
.default_open(false)
.show(ui, |ui| {
self.ray_config.populate_hud(ui);
});
self.need_accumulation_reset |= self.ray_config != old_ray_config;
egui::CollapsingHeader::new("Denoise")
.default_open(false)
.show(ui, |ui| {
ui.checkbox(&mut self.denoiser_enabled, "Enable");
self.denoiser_config.populate_hud(ui);
});
egui::CollapsingHeader::new("Tone Map").show(ui, |ui| {
self.post_proc_config.populate_hud(ui);
});
}
#[profiling::function]
fn populate_content(&mut self, ui: &mut egui::Ui) {
ui.group(|ui| {
ui.colored_label(egui::Color32::WHITE, self.scene_path.display().to_string());
ui.horizontal(|ui| {
if ui.button("Save").clicked() {
self.save_scene(&self.scene_path);
}
if ui.button("Reload").clicked() {
let path = self.scene_path.clone();
self.load_scene(&path);
}
});
});
egui::CollapsingHeader::new("Objects")
.default_open(true)
.show(ui, |ui| {
for (index, extra) in self.object_extras.iter().enumerate() {
let name = extra.path.file_name().unwrap().to_str().unwrap();
ui.selectable_value(&mut self.selected_object_index, Some(index), name);
}
});
if let Some(index) = self.selected_object_index {
self.add_manipulation_gizmo(index, ui);
ui.group(|ui| {
ui.horizontal(|ui| {
if ui.button("Unselect").clicked() {
self.selected_object_index = None;
}
if ui.button("Delete!").clicked() {
self.selected_object_index = None;
self.objects.remove(index);
self.object_extras.remove(index);
self.have_objects_changed = true;
}
})
});
}
if let Some(index) = self.selected_object_index {
egui::CollapsingHeader::new("Transform")
.default_open(true)
.show(ui, |ui| {
use std::f32::consts::PI;
let object = self.objects.get_mut(index).unwrap();
let mut tc = TransformComponents::from(object.transform);
let (mut a1, mut a2, mut a3) = tc.rotation.to_euler(glam::EulerRot::default());
ui.horizontal(|ui| {
ui.label("Translate");
ui.add(egui::DragValue::new(&mut tc.translation.x));
ui.add(egui::DragValue::new(&mut tc.translation.y));
ui.add(egui::DragValue::new(&mut tc.translation.z));
});
ui.add(egui::Slider::new(&mut a1, -PI..=PI).text("Euler Y"));
ui.add(egui::Slider::new(&mut a2, -PI * 0.5..=PI * 0.5).text("Euler X"));
ui.add(egui::Slider::new(&mut a3, -PI..=PI).text("Euler Z"));
ui.horizontal(|ui| {
ui.label("Scale");
ui.add(egui::DragValue::new(&mut tc.scale.x));
ui.add(egui::DragValue::new(&mut tc.scale.y));
ui.add(egui::DragValue::new(&mut tc.scale.z));
});
tc.rotation = glam::Quat::from_euler(glam::EulerRot::default(), a1, a2, a3);
let transform = tc.to_blade();
if object.transform != transform {
if tc.is_inversible() {
object.transform = transform;
self.have_objects_changed = true;
}
}
});
}
}
fn find_object(&self, geometry_index: u32) -> Option<usize> {
let mut index = geometry_index as usize;
for (obj_index, object) in self.objects.iter().enumerate() {
let model = &self.asset_hub.models[object.model];
match index.checked_sub(model.geometries.len()) {
Some(i) => index = i,
None => return Some(obj_index),
}
}
None
}
fn add_object(&mut self, file_path: &Path) -> bool {
if self.scene_load_task.is_some() {
return false;
}
let transform = if self.debug.mouse_pos.is_some() {
let selection = self.renderer.read_debug_selection_info();
let rotation = glam::Quat::from_rotation_arc(glam::Vec3::Y, selection.normal.into());
let m = glam::Mat4::from_rotation_translation(rotation, selection.position.into())
.transpose();
gpu::Transform {
x: m.x_axis.into(),
y: m.y_axis.into(),
z: m.z_axis.into(),
}
} else {
gpu::IDENTITY_TRANSFORM
};
let (model, model_task) = self.asset_hub.models.load(
file_path,
blade_render::model::Meta {
generate_tangents: true,
..Default::default()
},
);
self.scene_load_task = Some(model_task.clone());
self.objects.push(blade_render::Object {
model,
transform,
prev_transform: transform,
});
self.object_extras.push(ObjectExtra {
path: file_path.to_owned(),
});
true
}
}
fn main() {
env_logger::init();
let event_loop = winit::event_loop::EventLoop::new().unwrap();
let window_attributes = winit::window::Window::default_attributes().with_title("blade-scene");
let window = event_loop.create_window(window_attributes).unwrap();
let egui_ctx = egui::Context::default();
let viewport_id = egui_ctx.viewport_id();
let mut egui_winit = egui_winit::State::new(egui_ctx, viewport_id, &window, None, None, None);
let mut args = std::env::args();
let path_to_scene = args
.nth(1)
.unwrap_or("examples/scene/data/scene.ron".to_string());
let mut example = Example::new(&window);
example.load_scene(Path::new(&path_to_scene));
struct Drag {
_screen_pos: glam::IVec2,
_rotation: glam::Quat,
}
let mut drag_start = None::<Drag>;
let mut last_event = time::Instant::now();
let mut last_mouse_pos = [0i32; 2];
event_loop
.run(|event, target| {
example.choir.check_panic();
target.set_control_flow(winit::event_loop::ControlFlow::Poll);
let delta = last_event.elapsed().as_secs_f32();
let drag_speed = 0.01f32;
last_event = time::Instant::now();
match event {
winit::event::Event::AboutToWait => {
window.request_redraw();
}
winit::event::Event::WindowEvent { event, .. } => {
let response = egui_winit.on_window_event(&window, &event);
if response.repaint {
window.request_redraw();
}
if response.consumed {
return;
}
match event {
winit::event::WindowEvent::KeyboardInput {
event:
winit::event::KeyEvent {
physical_key: winit::keyboard::PhysicalKey::Code(key_code),
state: winit::event::ElementState::Pressed,
..
},
..
} => {
if key_code == winit::keyboard::KeyCode::Escape {
target.exit();
} else if drag_start.is_none() && example.camera.on_key(key_code, delta)
{
example.debug.mouse_pos = None;
}
}
winit::event::WindowEvent::CloseRequested => {
target.exit();
}
winit::event::WindowEvent::MouseInput {
state,
button: winit::event::MouseButton::Left,
..
} => {
drag_start = match state {
winit::event::ElementState::Pressed => Some(Drag {
_screen_pos: last_mouse_pos.into(),
_rotation: example.camera.inner.rot.into(),
}),
winit::event::ElementState::Released => None,
};
}
winit::event::WindowEvent::MouseInput {
state: winit::event::ElementState::Pressed,
button: winit::event::MouseButton::Right,
..
} => {
example.is_point_selected = true;
example.need_picked_selection_frames = 3;
}
winit::event::WindowEvent::MouseInput {
state: winit::event::ElementState::Released,
button: winit::event::MouseButton::Right,
..
} => {
example.is_point_selected = false;
}
winit::event::WindowEvent::CursorMoved { position, .. } => {
if let Some(_) = drag_start {
let prev = glam::Quat::from(example.camera.inner.rot);
let rotation_local = glam::Quat::from_rotation_x(
(last_mouse_pos[1] as f32 - position.y as f32) * drag_speed,
);
let rotation_global = glam::Quat::from_rotation_y(
(last_mouse_pos[0] as f32 - position.x as f32) * drag_speed,
);
example.camera.inner.rot =
(rotation_global * prev * rotation_local).into();
example.debug.mouse_pos = None;
}
last_mouse_pos = [position.x as i32, position.y as i32];
}
winit::event::WindowEvent::MouseWheel { delta, .. } => {
example.camera.on_wheel(delta);
}
winit::event::WindowEvent::HoveredFile(_) => {
example.is_file_hovered = true;
example
.debug
.draw_flags
.set(blade_render::DebugDrawFlags::SPACE, true);
}
winit::event::WindowEvent::HoveredFileCancelled => {
example.is_file_hovered = false;
example
.debug
.draw_flags
.set(blade_render::DebugDrawFlags::SPACE, false);
}
winit::event::WindowEvent::DroppedFile(ref file_path) => {
example.is_file_hovered = false;
example
.debug
.draw_flags
.set(blade_render::DebugDrawFlags::SPACE, false);
if !example.add_object(file_path) {
log::warn!(
"Unable to drop {}, loading in progress",
file_path.display()
);
}
}
winit::event::WindowEvent::RedrawRequested => {
let raw_input = egui_winit.take_egui_input(&window);
let egui_output = egui_winit.egui_ctx().run(raw_input, |egui_ctx| {
let frame = {
let mut frame = egui::Frame::side_top_panel(&egui_ctx.style());
let mut fill = frame.fill.to_array();
for f in fill.iter_mut() {
*f = (*f as u32 * 7 / 8) as u8;
}
frame.fill = egui::Color32::from_rgba_premultiplied(
fill[0], fill[1], fill[2], fill[3],
);
frame
};
egui::SidePanel::right("view")
.frame(frame)
.show(egui_ctx, |ui| {
example.populate_view(ui);
});
egui::SidePanel::left("content").frame(frame).show(
egui_ctx,
|ui| {
example.populate_content(ui);
ui.separator();
if ui.button("Quit").clicked() {
target.exit();
}
},
);
});
if example.have_objects_changed
&& egui_winit.egui_ctx().wants_pointer_input()
{
drag_start = None;
}
egui_winit.handle_platform_output(&window, egui_output.platform_output);
let repaint_delay =
egui_output.viewport_output[&viewport_id].repaint_delay;
let primitives = egui_winit
.egui_ctx()
.tessellate(egui_output.shapes, egui_output.pixels_per_point);
let control_flow = if let Some(repaint_after_instant) =
std::time::Instant::now().checked_add(repaint_delay)
{
winit::event_loop::ControlFlow::WaitUntil(repaint_after_instant)
} else {
winit::event_loop::ControlFlow::Wait
};
target.set_control_flow(control_flow);
example.render(
&primitives,
&egui_output.textures_delta,
window.inner_size(),
window.scale_factor() as f32,
);
profiling::finish_frame!();
}
_ => {}
}
if example.is_point_selected || example.is_file_hovered {
example.debug.mouse_pos = Some(last_mouse_pos);
}
}
_ => {}
}
})
.unwrap();
example.destroy();
}