mod global;
mod scene2d;
mod scene3d;
mod widgets;
use std::path::Path;
use glamx::Vec3;
use crate::color::Color;
use crate::post_processing::HdrSettings;
use crate::renderer::{DofSettings, RayTracer, RenderTimings, SsaoSettings, SsrSettings};
use crate::scene::{SceneNode2d, SceneNode3d};
use crate::window::NumSamples;
use self::widgets::{node_is_empty, node_is_empty_2d};
use super::Window;
#[derive(Clone, Copy, Debug)]
struct RtKnobs {
max_bounces: u32,
samples_per_frame: u32,
denoise: bool,
denoise_iterations: u32,
interactive_scale: f32,
env_rotation_deg: f32,
env_intensity: f32,
}
impl Default for RtKnobs {
fn default() -> Self {
RtKnobs {
max_bounces: 8,
samples_per_frame: 1,
denoise: false,
denoise_iterations: 5,
interactive_scale: 0.5,
env_rotation_deg: 0.0,
env_intensity: 1.0,
}
}
}
impl RtKnobs {
fn apply(&self, rt: &mut RayTracer, prev_env: Option<(f32, f32)>) -> Option<(f32, f32)> {
rt.set_max_bounces(self.max_bounces);
rt.set_samples_per_frame(self.samples_per_frame);
rt.set_denoise(self.denoise);
rt.set_denoise_iterations(self.denoise_iterations);
rt.set_interactive_scale(self.interactive_scale);
let env = (self.env_rotation_deg.to_radians(), self.env_intensity);
if prev_env != Some(env) {
rt.set_environment_orientation(env.0, env.1);
}
Some(env)
}
}
struct WinSettings {
background: [f32; 3],
ambient: f32,
samples: NumSamples,
shadows: bool,
shadow_res: u32,
shadow_softness: f32,
hdr: HdrSettings,
vsync: bool,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum InspectorTab {
Global,
Scene3d,
Scene2d,
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum MapTarget {
BaseColor,
Normal,
MetallicRoughness,
Ao,
Emissive,
Height,
}
impl MapTarget {
fn label(self) -> &'static str {
match self {
MapTarget::BaseColor => "Base color",
MapTarget::Normal => "Normal",
MapTarget::MetallicRoughness => "Metallic / roughness",
MapTarget::Ao => "Ambient occlusion",
MapTarget::Emissive => "Emissive",
MapTarget::Height => "Height (relief)",
}
}
const ALL: [MapTarget; 6] = [
MapTarget::BaseColor,
MapTarget::Normal,
MapTarget::MetallicRoughness,
MapTarget::Ao,
MapTarget::Emissive,
MapTarget::Height,
];
}
pub struct Inspector {
open: bool,
tab: InspectorTab,
initialized: bool,
rt: RtKnobs,
applied_env: Option<(f32, f32)>,
env_path: String,
env_status: String,
env_load_requested: bool,
env_clear_requested: bool,
samples: NumSamples,
ssao_enabled: bool,
ssao: SsaoSettings,
ssr_enabled: bool,
ssr: SsrSettings,
dof_enabled: bool,
dof: DofSettings,
skybox_path: String,
skybox_status: String,
skybox_load_requested: bool,
skybox_clear_requested: bool,
skybox_rotation_deg: f32,
skybox_intensity: f32,
skybox_orient_dirty: bool,
tex_path: String,
tex_status: String,
tex_target: MapTarget,
selected: Option<SceneNode3d>,
selected_2d: Option<SceneNode2d>,
edit_rot: Option<(u64, Vec3)>,
}
impl Default for Inspector {
fn default() -> Self {
Inspector {
open: true,
tab: InspectorTab::Global,
initialized: false,
rt: RtKnobs::default(),
applied_env: None,
env_path: String::new(),
env_status: String::new(),
env_load_requested: false,
env_clear_requested: false,
samples: NumSamples::One,
ssao_enabled: false,
ssao: SsaoSettings::default(),
ssr_enabled: false,
ssr: SsrSettings::default(),
dof_enabled: false,
dof: DofSettings::default(),
skybox_path: String::new(),
skybox_status: String::new(),
skybox_load_requested: false,
skybox_clear_requested: false,
skybox_rotation_deg: 0.0,
skybox_intensity: 1.0,
skybox_orient_dirty: false,
tex_path: String::new(),
tex_status: String::new(),
tex_target: MapTarget::BaseColor,
selected: None,
selected_2d: None,
edit_rot: None,
}
}
}
impl Inspector {
pub fn new() -> Inspector {
Inspector::default()
}
pub fn is_open(&self) -> bool {
self.open
}
pub fn set_open(&mut self, open: bool) {
self.open = open;
}
pub fn tab(&self) -> InspectorTab {
self.tab
}
pub fn set_tab(&mut self, tab: InspectorTab) {
self.tab = tab;
}
pub fn selected(&self) -> Option<&SceneNode3d> {
self.selected.as_ref()
}
pub fn select(&mut self, node: Option<SceneNode3d>) {
self.selected = node;
}
pub fn selected_2d(&self) -> Option<&SceneNode2d> {
self.selected_2d.as_ref()
}
pub fn select_2d(&mut self, node: Option<SceneNode2d>) {
self.selected_2d = node;
}
fn ui(
&mut self,
ctx: &egui::Context,
scene: Option<&mut SceneNode3d>,
scene_2d: Option<&mut SceneNode2d>,
win: &mut WinSettings,
mut raytracer: Option<&mut RayTracer>,
timings: Option<&RenderTimings>,
) {
if !self.open {
return;
}
let show_3d = scene.as_deref().is_some_and(|s| !node_is_empty(s));
let show_2d = scene_2d.as_deref().is_some_and(|s| !node_is_empty_2d(s));
if (self.tab == InspectorTab::Scene3d && !show_3d)
|| (self.tab == InspectorTab::Scene2d && !show_2d)
{
self.tab = InspectorTab::Global;
}
let path_tracing = raytracer.as_deref().is_some_and(|rt| rt.enabled());
egui::Window::new("🛠 kiss3d inspector")
.default_width(320.0)
.default_pos([12.0, 12.0])
.show(ctx, |ui| {
ui.horizontal(|ui| {
ui.selectable_value(&mut self.tab, InspectorTab::Global, "Global");
if show_3d {
ui.selectable_value(&mut self.tab, InspectorTab::Scene3d, "3D scene");
}
if show_2d {
ui.selectable_value(&mut self.tab, InspectorTab::Scene2d, "2D scene");
}
});
ui.separator();
egui::ScrollArea::vertical()
.max_height(ui.ctx().content_rect().height() - 60.0)
.show(ui, |ui| match self.tab {
InspectorTab::Global => {
self.renderer_section(ui, raytracer.as_deref_mut());
let pt = raytracer.as_deref().is_some_and(|rt| rt.enabled());
self.common_section(ui, &mut win.hdr, &mut win.vsync);
if pt {
self.path_tracer_section(ui);
} else {
self.rasterizer_section(ui, win);
}
Self::timings_section(ui, timings);
}
InspectorTab::Scene3d => {
if let Some(scene) = scene {
self.scene_tree_section(ui, scene);
ui.separator();
self.selection_section(ui, path_tracing);
}
}
InspectorTab::Scene2d => {
if let Some(scene_2d) = scene_2d {
self.scene_tree_section_2d(ui, scene_2d);
ui.separator();
self.selection_section_2d(ui);
}
}
});
});
}
}
impl Window {
pub fn draw_inspector(
&mut self,
inspector: &mut Inspector,
scene: Option<&mut SceneNode3d>,
scene_2d: Option<&mut SceneNode2d>,
mut raytracer: Option<&mut RayTracer>,
) {
if !inspector.initialized {
inspector.samples = NumSamples::from_u32(self.samples()).unwrap_or(NumSamples::One);
inspector.ssao_enabled = self.ssao_enabled();
inspector.ssr_enabled = self.ssr_enabled();
inspector.dof_enabled = self.dof_enabled();
inspector.initialized = true;
}
let mut settings = WinSettings {
background: [self.background.r, self.background.g, self.background.b],
ambient: self.ambient(),
samples: inspector.samples,
shadows: self.shadows_enabled(),
shadow_res: self.shadow_resolution(),
shadow_softness: self.shadow_softness(),
hdr: *self.hdr_settings(),
vsync: self.vsync(),
};
let timings = self.last_timings.clone();
self.draw_ui(|ctx| {
inspector.ui(
ctx,
scene,
scene_2d,
&mut settings,
raytracer.as_deref_mut(),
timings.as_ref(),
)
});
let bg = Color::new(
settings.background[0],
settings.background[1],
settings.background[2],
self.background.a,
);
if bg != self.background {
self.set_background_color(bg);
}
if settings.ambient != self.ambient() {
self.set_ambient(settings.ambient);
}
if settings.shadows != self.shadows_enabled() {
self.set_shadows_enabled(settings.shadows);
}
if settings.shadow_res != self.shadow_resolution() {
self.set_shadow_resolution(settings.shadow_res);
}
if settings.shadow_softness != self.shadow_softness() {
self.set_shadow_softness(settings.shadow_softness);
}
if settings.vsync != self.vsync() {
self.set_vsync(settings.vsync);
}
if (settings.samples as u32).max(1) != self.samples() {
self.set_samples(settings.samples);
}
inspector.samples = settings.samples;
*self.hdr_settings_mut() = settings.hdr;
self.set_ssao_enabled(inspector.ssao_enabled);
if inspector.ssao_enabled {
*self.ssao_settings_mut() = inspector.ssao;
}
self.set_ssr_enabled(inspector.ssr_enabled);
if inspector.ssr_enabled {
*self.ssr_settings_mut() = inspector.ssr;
}
self.set_dof_enabled(inspector.dof_enabled);
if inspector.dof_enabled {
*self.dof_settings_mut() = inspector.dof;
}
if inspector.skybox_load_requested {
inspector.skybox_load_requested = false;
let path = inspector.skybox_path.trim().to_string();
inspector.skybox_status =
if !path.is_empty() && self.set_skybox_from_file(Path::new(&path)) {
format!("loaded {path}")
} else {
"failed to load".to_string()
};
}
if inspector.skybox_clear_requested {
inspector.skybox_clear_requested = false;
self.clear_skybox();
inspector.skybox_status = "cleared".to_string();
}
if inspector.skybox_orient_dirty && self.has_skybox() {
self.set_skybox_orientation(
inspector.skybox_rotation_deg.to_radians(),
inspector.skybox_intensity,
);
}
if let Some(rt) = raytracer {
if inspector.env_load_requested {
inspector.env_load_requested = false;
inspector.env_status =
if rt.set_environment_from_file(Path::new(&inspector.env_path)) {
"loaded".to_string()
} else {
"failed to load".to_string()
};
}
if inspector.env_clear_requested {
inspector.env_clear_requested = false;
rt.clear_environment();
inspector.env_status = "procedural sky".to_string();
}
let knobs = inspector.rt;
inspector.applied_env = knobs.apply(rt, inspector.applied_env);
}
}
}