blade/
lib.rs

1#![cfg(not(any(gles, target_arch = "wasm32")))]
2#![allow(
3    irrefutable_let_patterns,
4    clippy::new_without_default,
5    // Conflicts with `pattern_type_mismatch`
6    clippy::needless_borrowed_reference,
7)]
8#![warn(
9    trivial_casts,
10    trivial_numeric_casts,
11    unused_extern_crates,
12    unused_qualifications,
13    // We don't match on a reference, unless required.
14    clippy::pattern_type_mismatch,
15)]
16
17use blade_graphics as gpu;
18use std::{ops, path::Path, sync::Arc};
19
20pub mod config;
21mod trimesh;
22
23const ZERO_V3: mint::Vector3<f32> = mint::Vector3 {
24    x: 0.0,
25    y: 0.0,
26    z: 0.0,
27};
28
29#[derive(Clone, Debug, PartialEq)]
30pub struct Transform {
31    pub position: mint::Vector3<f32>,
32    pub orientation: mint::Quaternion<f32>,
33}
34impl Default for Transform {
35    fn default() -> Self {
36        Self {
37            position: ZERO_V3,
38            orientation: mint::Quaternion { s: 1.0, v: ZERO_V3 },
39        }
40    }
41}
42impl Transform {
43    fn from_isometry(isometry: nalgebra::Isometry3<f32>) -> Self {
44        Self {
45            position: isometry.translation.vector.into(),
46            orientation: isometry.rotation.into(),
47        }
48    }
49    fn into_isometry(self) -> nalgebra::Isometry3<f32> {
50        nalgebra::Isometry3 {
51            translation: nalgebra::Translation {
52                vector: self.position.into(),
53            },
54            rotation: nalgebra::Unit::new_unchecked(self.orientation.into()),
55        }
56    }
57}
58
59#[derive(Clone, Debug, PartialEq)]
60pub struct BoundingBox {
61    pub center: mint::Vector3<f32>,
62    pub half: mint::Vector3<f32>,
63}
64
65/// Type of prediction to be made about the object transformation.
66#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
67pub enum Prediction {
68    /// Report the last known transform. It always makes sense,
69    /// but it could be out of date.
70    #[default]
71    LastKnown,
72    /// Integrate using velocity only.
73    IntegrateVelocity,
74    /// Integrate using velocity and forces affecting the object.
75    IntegrateVelocityAndForces,
76}
77
78#[repr(u8)]
79#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
80pub enum DynamicInput {
81    /// Object is not controllable, it's static.
82    Empty,
83    /// Object is controlled by user setting the position.
84    SetPosition,
85    /// Object is controlled by user setting the velocity.
86    SetVelocity,
87    /// Object is affected by all forces around.
88    #[default]
89    Full,
90}
91impl DynamicInput {
92    fn into_rapier(self) -> rapier3d::dynamics::RigidBodyType {
93        use rapier3d::dynamics::RigidBodyType as Rbt;
94        match self {
95            Self::Empty => Rbt::Fixed,
96            Self::SetPosition => Rbt::KinematicPositionBased,
97            Self::SetVelocity => Rbt::KinematicVelocityBased,
98            Self::Full => Rbt::Dynamic,
99        }
100    }
101}
102
103#[repr(u8)]
104#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
105pub enum JointAxis {
106    LinearX = 0,
107    LinearY = 1,
108    LinearZ = 2,
109    AngularX = 3,
110    AngularY = 4,
111    AngularZ = 5,
112}
113impl JointAxis {
114    fn into_rapier(self) -> rapier3d::dynamics::JointAxis {
115        use rapier3d::dynamics::JointAxis as Ja;
116        match self {
117            Self::LinearX => Ja::LinX,
118            Self::LinearY => Ja::LinY,
119            Self::LinearZ => Ja::LinZ,
120            Self::AngularX => Ja::AngX,
121            Self::AngularY => Ja::AngY,
122            Self::AngularZ => Ja::AngZ,
123        }
124    }
125}
126
127#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
128pub enum JointHandle {
129    Soft(#[doc(hidden)] rapier3d::dynamics::ImpulseJointHandle),
130    Hard(#[doc(hidden)] rapier3d::dynamics::MultibodyJointHandle),
131}
132
133#[derive(Clone, Debug, Default, PartialEq)]
134pub struct FreedomAxis {
135    pub limits: Option<ops::Range<f32>>,
136    pub motor: Option<config::Motor>,
137}
138
139impl FreedomAxis {
140    pub const FREE: Self = Self {
141        limits: None,
142        motor: None,
143    };
144    pub const ALL_FREE: mint::Vector3<Option<Self>> = mint::Vector3 {
145        x: Some(Self::FREE),
146        y: Some(Self::FREE),
147        z: Some(Self::FREE),
148    };
149}
150
151#[derive(Clone, Debug, PartialEq)]
152pub struct JointDesc {
153    pub parent_anchor: Transform,
154    pub child_anchor: Transform,
155    pub linear: mint::Vector3<Option<FreedomAxis>>,
156    pub angular: mint::Vector3<Option<FreedomAxis>>,
157    /// Allow the contacts to happen between A and B
158    pub allow_contacts: bool,
159    /// Hard joints guarantee the releation between
160    /// objects, while soft joints only try to get there.
161    pub is_hard: bool,
162}
163impl Default for JointDesc {
164    fn default() -> Self {
165        Self {
166            parent_anchor: Transform::default(),
167            child_anchor: Transform::default(),
168            linear: mint::Vector3 {
169                x: None,
170                y: None,
171                z: None,
172            },
173            angular: mint::Vector3 {
174                x: None,
175                y: None,
176                z: None,
177            },
178            allow_contacts: false,
179            is_hard: false,
180        }
181    }
182}
183
184const MAX_DEPTH: f32 = 1e9;
185
186#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
187pub struct ObjectHandle(usize);
188
189fn make_quaternion(degrees: mint::Vector3<f32>) -> nalgebra::geometry::UnitQuaternion<f32> {
190    nalgebra::geometry::UnitQuaternion::from_euler_angles(
191        degrees.x.to_radians(),
192        degrees.y.to_radians(),
193        degrees.z.to_radians(),
194    )
195}
196
197trait UiValue {
198    fn value(&mut self, v: f32);
199    fn value_vec3(&mut self, v3: &nalgebra::Vector3<f32>) {
200        for &v in v3.as_slice() {
201            self.value(v);
202        }
203    }
204}
205impl UiValue for egui::Ui {
206    fn value(&mut self, v: f32) {
207        self.colored_label(egui::Color32::WHITE, format!("{v:.1}"));
208    }
209}
210
211#[derive(Default)]
212struct DebugPhysicsRender {
213    lines: Vec<blade_render::DebugLine>,
214}
215impl rapier3d::pipeline::DebugRenderBackend for DebugPhysicsRender {
216    fn draw_line(
217        &mut self,
218        _object: rapier3d::pipeline::DebugRenderObject,
219        a: nalgebra::Point3<f32>,
220        b: nalgebra::Point3<f32>,
221        color: [f32; 4],
222    ) {
223        // Looks like everybody encodes HSL(A) differently...
224        let hsl = colorsys::Hsl::new(
225            color[0] as f64,
226            color[1] as f64 * 100.0,
227            color[2] as f64 * 100.0,
228            None,
229        );
230        let rgb = colorsys::Rgb::from(&hsl);
231        let color = [
232            rgb.red(),
233            rgb.green(),
234            rgb.blue(),
235            color[3].clamp(0.0, 1.0) as f64 * 255.0,
236        ]
237        .iter()
238        .rev()
239        .fold(0u32, |u, &c| (u << 8) | c as u32);
240        self.lines.push(blade_render::DebugLine {
241            a: blade_render::DebugPoint {
242                pos: a.into(),
243                color,
244            },
245            b: blade_render::DebugPoint {
246                pos: b.into(),
247                color,
248            },
249        });
250    }
251}
252
253#[derive(Default)]
254struct Physics {
255    rigid_bodies: rapier3d::dynamics::RigidBodySet,
256    integration_params: rapier3d::dynamics::IntegrationParameters,
257    island_manager: rapier3d::dynamics::IslandManager,
258    impulse_joints: rapier3d::dynamics::ImpulseJointSet,
259    multibody_joints: rapier3d::dynamics::MultibodyJointSet,
260    solver: rapier3d::dynamics::CCDSolver,
261    colliders: rapier3d::geometry::ColliderSet,
262    broad_phase: rapier3d::geometry::DefaultBroadPhase,
263    narrow_phase: rapier3d::geometry::NarrowPhase,
264    gravity: rapier3d::math::Vector<f32>,
265    pipeline: rapier3d::pipeline::PhysicsPipeline,
266    debug_pipeline: rapier3d::pipeline::DebugRenderPipeline,
267    last_time: f32,
268}
269
270impl Physics {
271    fn step(&mut self) {
272        let query_pipeline = None;
273        let physics_hooks = ();
274        let event_handler = ();
275        self.pipeline.step(
276            &self.gravity,
277            &self.integration_params,
278            &mut self.island_manager,
279            &mut self.broad_phase,
280            &mut self.narrow_phase,
281            &mut self.rigid_bodies,
282            &mut self.colliders,
283            &mut self.impulse_joints,
284            &mut self.multibody_joints,
285            &mut self.solver,
286            query_pipeline,
287            &physics_hooks,
288            &event_handler,
289        );
290        self.last_time += self.integration_params.dt;
291    }
292    fn render_debug(&mut self) -> Vec<blade_render::DebugLine> {
293        let mut backend = DebugPhysicsRender::default();
294        self.debug_pipeline.render(
295            &mut backend,
296            &self.rigid_bodies,
297            &self.colliders,
298            &self.impulse_joints,
299            &self.multibody_joints,
300            &self.narrow_phase,
301        );
302        backend.lines
303    }
304}
305
306#[doc(hidden)]
307impl ops::Index<JointHandle> for Physics {
308    type Output = rapier3d::dynamics::GenericJoint;
309    fn index(&self, handle: JointHandle) -> &Self::Output {
310        match handle {
311            JointHandle::Soft(h) => &self.impulse_joints.get(h).unwrap().data,
312            JointHandle::Hard(h) => {
313                let (multibody, link_index) = self.multibody_joints.get(h).unwrap();
314                &multibody.link(link_index).unwrap().joint.data
315            }
316        }
317    }
318}
319impl ops::IndexMut<JointHandle> for Physics {
320    fn index_mut(&mut self, handle: JointHandle) -> &mut Self::Output {
321        match handle {
322            JointHandle::Soft(h) => &mut self.impulse_joints.get_mut(h).unwrap().data,
323            JointHandle::Hard(h) => {
324                let (multibody, link_index) = self.multibody_joints.get_mut(h).unwrap();
325                &mut multibody.link_mut(link_index).unwrap().joint.data
326            }
327        }
328    }
329}
330
331struct Visual {
332    model: blade_asset::Handle<blade_render::Model>,
333    similarity: nalgebra::geometry::Similarity3<f32>,
334}
335
336struct Object {
337    name: String,
338    rigid_body: rapier3d::dynamics::RigidBodyHandle,
339    prev_isometry: nalgebra::Isometry3<f32>,
340    colliders: Vec<rapier3d::geometry::ColliderHandle>,
341    visuals: Vec<Visual>,
342}
343
344#[derive(Clone, Debug, PartialEq)]
345pub struct FrameCamera {
346    pub transform: Transform,
347    pub fov_y: f32,
348}
349
350impl From<blade_render::Camera> for FrameCamera {
351    fn from(cam: blade_render::Camera) -> Self {
352        Self {
353            transform: Transform {
354                position: cam.pos,
355                orientation: cam.rot,
356            },
357            fov_y: cam.fov_y,
358        }
359    }
360}
361
362/// Blade Engine encapsulates all the context for applications,
363/// such as the GPU context, Ray-tracing context, EGUI integration,
364/// asset hub, physics context, task processing, and more.
365pub struct Engine {
366    pacer: blade_render::util::FramePacer,
367    renderer: blade_render::Renderer,
368    physics: Physics,
369    load_tasks: Vec<choir::RunningTask>,
370    gui_painter: blade_egui::GuiPainter,
371    asset_hub: blade_render::AssetHub,
372    gpu_surface: gpu::Surface,
373    gpu_context: Arc<gpu::Context>,
374    environment_map: Option<blade_asset::Handle<blade_render::Texture>>,
375    objects: slab::Slab<Object>,
376    selected_object_handle: Option<ObjectHandle>,
377    selected_collider: Option<rapier3d::geometry::ColliderHandle>,
378    render_objects: Vec<blade_render::Object>,
379    debug: blade_render::DebugConfig,
380    pub frame_config: blade_render::FrameConfig,
381    pub ray_config: blade_render::RayConfig,
382    pub denoiser_enabled: bool,
383    pub denoiser_config: blade_render::DenoiserConfig,
384    pub post_proc_config: blade_render::PostProcConfig,
385    track_hot_reloads: bool,
386    workers: Vec<choir::WorkerHandle>,
387    choir: Arc<choir::Choir>,
388    data_path: String,
389    time_ahead: f32,
390}
391
392impl Engine {
393    fn make_surface_config(physical_size: winit::dpi::PhysicalSize<u32>) -> gpu::SurfaceConfig {
394        gpu::SurfaceConfig {
395            size: gpu::Extent {
396                width: physical_size.width,
397                height: physical_size.height,
398                depth: 1,
399            },
400            usage: gpu::TextureUsage::TARGET,
401            //TODO: make it `Recent`
402            display_sync: gpu::DisplaySync::Block,
403            color_space: gpu::ColorSpace::Linear,
404            transparent: false,
405            allow_exclusive_full_screen: true,
406        }
407    }
408
409    /// Create a new context based on a given window.
410    #[profiling::function]
411    pub fn new(window: &winit::window::Window, config: &config::Engine) -> Self {
412        log::info!("Initializing the engine");
413
414        let gpu_context = Arc::new(unsafe {
415            gpu::Context::init(gpu::ContextDesc {
416                presentation: true,
417                validation: cfg!(debug_assertions),
418                timing: true,
419                capture: false,
420                overlay: false,
421                device_id: 0,
422            })
423            .unwrap()
424        });
425
426        let surface_config = Self::make_surface_config(window.inner_size());
427        let surface_size = surface_config.size;
428        let gpu_surface = gpu_context
429            .create_surface_configured(window, surface_config)
430            .unwrap();
431        let surface_info = gpu_surface.info();
432
433        let num_workers = num_cpus::get_physical().max((num_cpus::get() * 3 + 2) / 4);
434        log::info!("Initializing Choir with {} workers", num_workers);
435        let choir = choir::Choir::new();
436        let workers = (0..num_workers)
437            .map(|i| choir.add_worker(&format!("Worker-{}", i)))
438            .collect();
439
440        let asset_hub = blade_render::AssetHub::new(Path::new("asset-cache"), &choir, &gpu_context);
441        let (shaders, shader_task) =
442            blade_render::Shaders::load(config.shader_path.as_ref(), &asset_hub);
443
444        log::info!("Spinning up the renderer");
445        shader_task.join();
446        let mut pacer = blade_render::util::FramePacer::new(&gpu_context);
447        let (command_encoder, _) = pacer.begin_frame();
448
449        let render_config = blade_render::RenderConfig {
450            surface_size,
451            surface_info,
452            max_debug_lines: 1 << 14,
453        };
454        let renderer = blade_render::Renderer::new(
455            command_encoder,
456            &gpu_context,
457            shaders,
458            &asset_hub.shaders,
459            &render_config,
460        );
461
462        pacer.end_frame(&gpu_context);
463
464        let gui_painter = blade_egui::GuiPainter::new(surface_info, &gpu_context);
465        let mut physics = Physics::default();
466        physics.debug_pipeline.mode = rapier3d::pipeline::DebugRenderMode::empty();
467        physics.integration_params.dt = config.time_step;
468
469        Self {
470            pacer,
471            renderer,
472            physics,
473            load_tasks: Vec::new(),
474            gui_painter,
475            asset_hub,
476            gpu_surface,
477            gpu_context,
478            environment_map: None,
479            objects: slab::Slab::new(),
480            selected_object_handle: None,
481            selected_collider: None,
482            render_objects: Vec::new(),
483            debug: blade_render::DebugConfig::default(),
484            frame_config: blade_render::FrameConfig {
485                frozen: false,
486                debug_draw: true,
487                reset_variance: false,
488                reset_reservoirs: true,
489            },
490            ray_config: blade_helpers::default_ray_config(),
491            denoiser_enabled: true,
492            denoiser_config: blade_render::DenoiserConfig {
493                num_passes: 4,
494                temporal_weight: 0.1,
495            },
496            post_proc_config: blade_render::PostProcConfig {
497                average_luminocity: 0.5,
498                exposure_key_value: 1.0 / 9.6,
499                white_level: 1.0,
500            },
501            track_hot_reloads: false,
502            workers,
503            choir,
504            data_path: config.data_path.clone(),
505            time_ahead: 0.0,
506        }
507    }
508
509    pub fn destroy(&mut self) {
510        self.workers.clear();
511        self.pacer.destroy(&self.gpu_context);
512        self.gui_painter.destroy(&self.gpu_context);
513        self.gpu_context.destroy_surface(&mut self.gpu_surface);
514        self.renderer.destroy(&self.gpu_context);
515        self.asset_hub.destroy();
516    }
517
518    #[profiling::function]
519    pub fn update(&mut self, dt: f32) {
520        self.choir.check_panic();
521        self.time_ahead += dt;
522        while self.time_ahead >= self.physics.integration_params.dt {
523            self.physics.step();
524            self.time_ahead -= self.physics.integration_params.dt;
525        }
526    }
527
528    #[profiling::function]
529    pub fn render(
530        &mut self,
531        camera: &FrameCamera,
532        gui_primitives: &[egui::ClippedPrimitive],
533        gui_textures: &egui::TexturesDelta,
534        physical_size: winit::dpi::PhysicalSize<u32>,
535        scale_factor: f32,
536    ) {
537        if self.track_hot_reloads {
538            self.renderer.hot_reload(
539                &self.asset_hub,
540                &self.gpu_context,
541                self.pacer.last_sync_point().unwrap(),
542            );
543        }
544
545        // Note: the resize is split in 2 parts because `wait_for_previous_frame`
546        // wants to borrow `self` mutably, and `command_encoder` blocks that.
547        let surface_config = Self::make_surface_config(physical_size);
548        let new_render_size = surface_config.size;
549        if new_render_size != self.renderer.get_surface_size() {
550            log::info!("Resizing to {}", new_render_size);
551            self.pacer.wait_for_previous_frame(&self.gpu_context);
552            self.gpu_context
553                .reconfigure_surface(&mut self.gpu_surface, surface_config);
554        }
555
556        let (command_encoder, temp) = self.pacer.begin_frame();
557        if new_render_size != self.renderer.get_surface_size() {
558            self.renderer
559                .resize_screen(new_render_size, command_encoder, &self.gpu_context);
560            self.frame_config.reset_reservoirs = true;
561        }
562        self.frame_config.reset_variance = self.debug.mouse_pos.is_none();
563
564        self.gui_painter
565            .update_textures(command_encoder, gui_textures, &self.gpu_context);
566
567        self.asset_hub.flush(command_encoder, &mut temp.buffers);
568
569        self.load_tasks.retain(|task| !task.is_done());
570
571        // We should be able to update TLAS and render content
572        // even while it's still being loaded.
573        if self.load_tasks.is_empty() {
574            self.render_objects.clear();
575            for (_, object) in self.objects.iter_mut() {
576                let isometry = self
577                    .physics
578                    .rigid_bodies
579                    .get(object.rigid_body)
580                    .unwrap()
581                    .predict_position_using_velocity_and_forces(self.time_ahead);
582
583                for visual in object.visuals.iter() {
584                    let mc = (isometry * visual.similarity).to_homogeneous().transpose();
585                    let mp = (object.prev_isometry * visual.similarity)
586                        .to_homogeneous()
587                        .transpose();
588                    self.render_objects.push(blade_render::Object {
589                        transform: gpu::Transform {
590                            x: mc.column(0).into(),
591                            y: mc.column(1).into(),
592                            z: mc.column(2).into(),
593                        },
594                        prev_transform: gpu::Transform {
595                            x: mp.column(0).into(),
596                            y: mp.column(1).into(),
597                            z: mp.column(2).into(),
598                        },
599                        model: visual.model,
600                    });
601                }
602                object.prev_isometry = isometry;
603            }
604
605            // Rebuilding every frame
606            if !self.frame_config.frozen {
607                self.renderer.build_scene(
608                    command_encoder,
609                    &self.render_objects,
610                    self.environment_map,
611                    &self.asset_hub,
612                    &self.gpu_context,
613                    temp,
614                );
615            }
616
617            self.renderer.prepare(
618                command_encoder,
619                &blade_render::Camera {
620                    pos: camera.transform.position,
621                    rot: camera.transform.orientation,
622                    fov_y: camera.fov_y,
623                    depth: MAX_DEPTH,
624                },
625                self.frame_config,
626            );
627            self.frame_config.reset_reservoirs = false;
628
629            if !self.render_objects.is_empty() {
630                self.renderer
631                    .ray_trace(command_encoder, self.debug, self.ray_config);
632                if self.denoiser_enabled {
633                    self.renderer.denoise(command_encoder, self.denoiser_config);
634                }
635            }
636        }
637
638        let mut debug_lines = self.physics.render_debug();
639        if let Some(handle) = self.selected_object_handle {
640            let object = &self.objects[handle.0];
641            let rb = self.physics.rigid_bodies.get(object.rigid_body).unwrap();
642            for (_, joint) in self.physics.impulse_joints.iter() {
643                let local_frame = if joint.body1 == object.rigid_body {
644                    joint.data.local_frame1
645                } else if joint.body2 == object.rigid_body {
646                    joint.data.local_frame2
647                } else {
648                    continue;
649                };
650                let position = rb.position() * local_frame;
651                let length = 1.0;
652                let base = blade_render::DebugPoint {
653                    pos: position.translation.into(),
654                    color: 0xFFFFFF,
655                };
656                debug_lines.push(blade_render::DebugLine {
657                    a: base,
658                    b: blade_render::DebugPoint {
659                        pos: position
660                            .transform_point(&nalgebra::Point3::new(length, 0.0, 0.0))
661                            .into(),
662                        color: 0x0000FF,
663                    },
664                });
665                debug_lines.push(blade_render::DebugLine {
666                    a: base,
667                    b: blade_render::DebugPoint {
668                        pos: position
669                            .transform_point(&nalgebra::Point3::new(0.0, length, 0.0))
670                            .into(),
671                        color: 0x00FF00,
672                    },
673                });
674                debug_lines.push(blade_render::DebugLine {
675                    a: base,
676                    b: blade_render::DebugPoint {
677                        pos: position
678                            .transform_point(&nalgebra::Point3::new(0.0, 0.0, length))
679                            .into(),
680                        color: 0xFF0000,
681                    },
682                });
683            }
684        }
685
686        let frame = self.gpu_surface.acquire_frame();
687        command_encoder.init_texture(frame.texture());
688
689        if let mut pass = command_encoder.render(
690            "draw",
691            gpu::RenderTargetSet {
692                colors: &[gpu::RenderTarget {
693                    view: frame.texture_view(),
694                    init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack),
695                    finish_op: gpu::FinishOp::Store,
696                }],
697                depth_stencil: None,
698            },
699        ) {
700            let screen_desc = blade_egui::ScreenDescriptor {
701                physical_size: (physical_size.width, physical_size.height),
702                scale_factor,
703            };
704            if self.load_tasks.is_empty() {
705                self.renderer.post_proc(
706                    &mut pass,
707                    self.debug,
708                    self.post_proc_config,
709                    &debug_lines,
710                    &[],
711                );
712            }
713            self.gui_painter
714                .paint(&mut pass, gui_primitives, &screen_desc, &self.gpu_context);
715        }
716
717        command_encoder.present(frame);
718        let sync_point = self.pacer.end_frame(&self.gpu_context);
719        self.gui_painter.after_submit(sync_point);
720
721        profiling::finish_frame!();
722    }
723
724    #[profiling::function]
725    pub fn populate_hud(&mut self, ui: &mut egui::Ui) {
726        use blade_helpers::ExposeHud as _;
727
728        let mut selection = blade_render::SelectionInfo::default();
729        if self.debug.mouse_pos.is_some() {
730            selection = self.renderer.read_debug_selection_info();
731            self.selected_object_handle = self.find_object(selection.custom_index);
732        }
733
734        ui.checkbox(&mut self.track_hot_reloads, "Hot reloading");
735
736        egui::CollapsingHeader::new("Rendering")
737            .default_open(false)
738            .show(ui, |ui| {
739                self.ray_config.populate_hud(ui);
740                self.frame_config.reset_reservoirs |= ui.button("Reset Accumulation").clicked();
741                ui.checkbox(&mut self.denoiser_enabled, "Enable Denoiser");
742                self.denoiser_config.populate_hud(ui);
743                self.post_proc_config.populate_hud(ui);
744            });
745        egui::CollapsingHeader::new("Debug")
746            .default_open(true)
747            .show(ui, |ui| {
748                self.debug.populate_hud(ui);
749                blade_helpers::populate_debug_selection(
750                    &mut self.debug.mouse_pos,
751                    &selection,
752                    &self.asset_hub,
753                    ui,
754                );
755            });
756
757        egui::CollapsingHeader::new("Visualize")
758            .default_open(false)
759            .show(ui, |ui| {
760                let all_bits = rapier3d::pipeline::DebugRenderMode::all().bits();
761                for bit_pos in 0..=all_bits.ilog2() {
762                    let flag = match rapier3d::pipeline::DebugRenderMode::from_bits(1 << bit_pos) {
763                        Some(flag) => flag,
764                        None => continue,
765                    };
766                    let mut enabled = self.physics.debug_pipeline.mode.contains(flag);
767                    ui.checkbox(&mut enabled, format!("{flag:?}"));
768                    self.physics.debug_pipeline.mode.set(flag, enabled);
769                }
770            });
771
772        egui::CollapsingHeader::new("Performance").show(ui, |ui| {
773            for (name, time) in self.pacer.timings() {
774                let millis = time.as_secs_f32() * 1000.0;
775                ui.horizontal(|ui| {
776                    ui.label(name);
777                    ui.colored_label(egui::Color32::WHITE, format!("{:.2} ms", millis));
778                });
779            }
780        });
781
782        egui::CollapsingHeader::new("Objects")
783            .default_open(true)
784            .show(ui, |ui| {
785                for (handle, object) in self.objects.iter() {
786                    ui.selectable_value(
787                        &mut self.selected_object_handle,
788                        Some(ObjectHandle(handle)),
789                        &object.name,
790                    );
791                }
792
793                if let Some(handle) = self.selected_object_handle {
794                    let object = &self.objects[handle.0];
795                    let rigid_body = &mut self.physics.rigid_bodies[object.rigid_body];
796                    if ui.button("Unselect").clicked() {
797                        self.selected_object_handle = None;
798                        self.selected_collider = None;
799                    }
800                    egui::CollapsingHeader::new("Stats")
801                        .default_open(false)
802                        .show(ui, |ui| {
803                            ui.horizontal(|ui| {
804                                ui.label(format!("Position:"));
805                                ui.value_vec3(&rigid_body.translation());
806                            });
807                            ui.horizontal(|ui| {
808                                ui.label(format!("Linear velocity:"));
809                                ui.value_vec3(&rigid_body.linvel());
810                            });
811                            ui.horizontal(|ui| {
812                                ui.label(format!("Linear Damping:"));
813                                ui.value(rigid_body.linear_damping());
814                            });
815                            ui.horizontal(|ui| {
816                                ui.label(format!("Angular velocity:"));
817                                ui.value_vec3(&rigid_body.angvel());
818                            });
819                            ui.horizontal(|ui| {
820                                ui.label(format!("Angular Damping:"));
821                                ui.value(rigid_body.angular_damping());
822                            });
823                            ui.horizontal(|ui| {
824                                ui.label(format!("Kinematic energy:"));
825                                ui.value(rigid_body.kinetic_energy());
826                            });
827                        });
828                    ui.heading("Colliders");
829                    for &collider_handle in rigid_body.colliders() {
830                        let collider = &self.physics.colliders[collider_handle];
831                        let name = format!("{:?}", collider.shape().shape_type());
832                        ui.selectable_value(
833                            &mut self.selected_collider,
834                            Some(collider_handle),
835                            name,
836                        );
837                    }
838                }
839
840                if let Some(collider_handle) = self.selected_collider {
841                    ui.heading("Properties");
842                    let collider = self.physics.colliders.get_mut(collider_handle).unwrap();
843                    let mut density = collider.density();
844                    if ui
845                        .add(
846                            egui::DragValue::new(&mut density)
847                                .prefix("Density: ")
848                                .range(0.1..=1e6),
849                        )
850                        .changed()
851                    {
852                        collider.set_density(density);
853                    }
854                    let mut friction = collider.friction();
855                    if ui
856                        .add(
857                            egui::DragValue::new(&mut friction)
858                                .prefix("Friction: ")
859                                .range(0.0..=5.0)
860                                .speed(0.01),
861                        )
862                        .changed()
863                    {
864                        collider.set_friction(friction);
865                    }
866                    let mut restitution = collider.restitution();
867                    if ui
868                        .add(
869                            egui::DragValue::new(&mut restitution)
870                                .prefix("Restituion: ")
871                                .range(0.0..=1.0)
872                                .speed(0.01),
873                        )
874                        .changed()
875                    {
876                        collider.set_restitution(restitution);
877                    }
878                }
879            });
880    }
881
882    pub fn screen_aspect(&self) -> f32 {
883        let size = self.renderer.get_surface_size();
884        size.width as f32 / size.height.max(1) as f32
885    }
886
887    fn find_object(&self, geometry_index: u32) -> Option<ObjectHandle> {
888        let mut index = geometry_index as usize;
889        for (obj_handle, object) in self.objects.iter() {
890            for visual in object.visuals.iter() {
891                let model = &self.asset_hub.models[visual.model];
892                match index.checked_sub(model.geometries.len()) {
893                    Some(i) => index = i,
894                    None => return Some(ObjectHandle(obj_handle)),
895                }
896            }
897        }
898        None
899    }
900
901    pub fn add_object(
902        &mut self,
903        config: &config::Object,
904        transform: Transform,
905        dynamic_input: DynamicInput,
906    ) -> ObjectHandle {
907        use rapier3d::{
908            dynamics::MassProperties,
909            geometry::{ColliderBuilder, TriMeshFlags},
910        };
911
912        let mut visuals = Vec::new();
913        for visual in config.visuals.iter() {
914            let (model, task) = self.asset_hub.models.load(
915                format!("{}/{}", self.data_path, visual.model),
916                blade_render::model::Meta {
917                    generate_tangents: true,
918                    front_face: match visual.front_face {
919                        config::FrontFace::Cw => blade_render::model::FrontFace::Clockwise,
920                        config::FrontFace::Ccw => blade_render::model::FrontFace::CounterClockwise,
921                    },
922                },
923            );
924            visuals.push(Visual {
925                model,
926                similarity: nalgebra::geometry::Similarity3::from_parts(
927                    nalgebra::Vector3::from(visual.pos).into(),
928                    make_quaternion(visual.rot),
929                    visual.scale,
930                ),
931            });
932            self.load_tasks.push(task.clone());
933        }
934
935        let add_mass_properties = match config.additional_mass {
936            Some(ref am) => match am.shape {
937                config::Shape::Ball { radius } => MassProperties::from_ball(am.density, radius),
938                config::Shape::Cylinder {
939                    half_height,
940                    radius,
941                } => MassProperties::from_cylinder(am.density, half_height, radius),
942                config::Shape::Cuboid { half } => {
943                    MassProperties::from_cuboid(am.density, half.into())
944                }
945                config::Shape::ConvexHull { .. } | config::Shape::TriMesh { .. } => {
946                    unimplemented!()
947                }
948            },
949            None => Default::default(),
950        };
951
952        let rigid_body = rapier3d::dynamics::RigidBodyBuilder::new(dynamic_input.into_rapier())
953            .position(transform.into_isometry())
954            .additional_mass_properties(add_mass_properties)
955            .build();
956        let rb_handle = self.physics.rigid_bodies.insert(rigid_body);
957
958        let mut colliders = Vec::new();
959        for cc in config.colliders.iter() {
960            let isometry = nalgebra::geometry::Isometry3::from_parts(
961                nalgebra::Vector3::from(cc.pos).into(),
962                make_quaternion(cc.rot),
963            );
964            let builder = match cc.shape {
965                config::Shape::Ball { radius } => ColliderBuilder::ball(radius),
966                config::Shape::Cylinder {
967                    half_height,
968                    radius,
969                } => ColliderBuilder::cylinder(half_height, radius),
970                config::Shape::Cuboid { half } => ColliderBuilder::cuboid(half.x, half.y, half.z),
971                config::Shape::ConvexHull {
972                    ref points,
973                    border_radius,
974                } => {
975                    let pv = points
976                        .iter()
977                        .map(|p| nalgebra::Vector3::from(*p).into())
978                        .collect::<Vec<_>>();
979                    let result = if border_radius != 0.0 {
980                        ColliderBuilder::round_convex_hull(&pv, border_radius)
981                    } else {
982                        ColliderBuilder::convex_hull(&pv)
983                    };
984                    result.expect("Unable to build convex hull shape")
985                }
986                config::Shape::TriMesh {
987                    ref model,
988                    convex,
989                    border_radius,
990                } => {
991                    let trimesh = trimesh::load(&format!("{}/{}", self.data_path, model));
992                    if convex && border_radius != 0.0 {
993                        ColliderBuilder::round_convex_mesh(
994                            trimesh.points,
995                            &trimesh.triangles,
996                            border_radius,
997                        )
998                        .expect("Unable to build rounded convex mesh")
999                    } else if convex {
1000                        ColliderBuilder::convex_mesh(trimesh.points, &trimesh.triangles)
1001                            .expect("Unable to build convex mesh")
1002                    } else {
1003                        assert_eq!(border_radius, 0.0);
1004                        let flags = TriMeshFlags::empty();
1005                        ColliderBuilder::trimesh_with_flags(
1006                            trimesh.points,
1007                            trimesh.triangles,
1008                            flags,
1009                        )
1010                    }
1011                }
1012            };
1013            let collider = builder
1014                .density(cc.density)
1015                .friction(cc.friction)
1016                .restitution(cc.restitution)
1017                .position(isometry)
1018                .build();
1019            let c_handle = self.physics.colliders.insert_with_parent(
1020                collider,
1021                rb_handle,
1022                &mut self.physics.rigid_bodies,
1023            );
1024            colliders.push(c_handle);
1025        }
1026
1027        let raw_handle = self.objects.insert(Object {
1028            name: config.name.clone(),
1029            rigid_body: rb_handle,
1030            prev_isometry: nalgebra::Isometry3::default(),
1031            colliders,
1032            visuals,
1033        });
1034        ObjectHandle(raw_handle)
1035    }
1036
1037    pub fn wake_up(&mut self, object: ObjectHandle) {
1038        let rb_handle = self.objects[object.0].rigid_body;
1039        let rb = self.physics.rigid_bodies.get_mut(rb_handle).unwrap();
1040        rb.wake_up(true);
1041    }
1042
1043    pub fn add_joint(
1044        &mut self,
1045        parent: ObjectHandle,
1046        child: ObjectHandle,
1047        desc: JointDesc,
1048    ) -> JointHandle {
1049        let data = {
1050            let mut locked_axes = rapier3d::dynamics::JointAxesMask::empty();
1051            let freedoms = [
1052                (JointAxis::LinearX, &desc.linear.x),
1053                (JointAxis::LinearY, &desc.linear.y),
1054                (JointAxis::LinearZ, &desc.linear.z),
1055                (JointAxis::AngularX, &desc.angular.x),
1056                (JointAxis::AngularY, &desc.angular.y),
1057                (JointAxis::AngularZ, &desc.angular.z),
1058            ];
1059            let mut joint_builder =
1060                rapier3d::dynamics::GenericJointBuilder::new(Default::default())
1061                    .local_frame1(desc.parent_anchor.into_isometry())
1062                    .local_frame2(desc.child_anchor.into_isometry())
1063                    .contacts_enabled(desc.allow_contacts);
1064            for &(axis, ref maybe_freedom) in freedoms.iter() {
1065                let rapier_axis = axis.into_rapier();
1066                match *maybe_freedom {
1067                    Some(freedom) => {
1068                        if let Some(ref limits) = freedom.limits {
1069                            joint_builder =
1070                                joint_builder.limits(rapier_axis, [limits.start, limits.end]);
1071                        }
1072                        if let Some(ref motor) = freedom.motor {
1073                            joint_builder = joint_builder
1074                                .motor_position(rapier_axis, 0.0, motor.stiffness, motor.damping)
1075                                .motor_max_force(rapier_axis, motor.max_force);
1076                        }
1077                    }
1078                    None => {
1079                        locked_axes |= rapier3d::dynamics::JointAxesMask::from(rapier_axis);
1080                    }
1081                }
1082            }
1083            joint_builder.locked_axes(locked_axes).0
1084        };
1085
1086        let body1 = self.objects[parent.0].rigid_body;
1087        let body2 = self.objects[child.0].rigid_body;
1088        if desc.is_hard {
1089            JointHandle::Hard(
1090                self.physics
1091                    .multibody_joints
1092                    .insert(body1, body2, data, true)
1093                    .unwrap(),
1094            )
1095        } else {
1096            JointHandle::Soft(self.physics.impulse_joints.insert(body1, body2, data, true))
1097        }
1098    }
1099
1100    /// Get the current object transform.
1101    ///
1102    /// Since the simulation is done at fixed key frames, the position specifically
1103    /// at the current time needs to be predicted.
1104    pub fn get_object_transform(&self, handle: ObjectHandle, prediction: Prediction) -> Transform {
1105        let object = &self.objects[handle.0];
1106        let body = &self.physics.rigid_bodies[object.rigid_body];
1107        let isometry = match prediction {
1108            Prediction::LastKnown => *body.position(),
1109            Prediction::IntegrateVelocity => unimplemented!(),
1110            Prediction::IntegrateVelocityAndForces => {
1111                body.predict_position_using_velocity_and_forces(self.time_ahead)
1112            }
1113        };
1114        Transform::from_isometry(isometry)
1115    }
1116
1117    pub fn get_object_bounds(&self, handle: ObjectHandle) -> BoundingBox {
1118        let object = &self.objects[handle.0];
1119        let mut aabb = rapier3d::geometry::Aabb::new_invalid();
1120        for &collider_handle in object.colliders.iter() {
1121            let collider = &self.physics.colliders[collider_handle];
1122            rapier3d::geometry::BoundingVolume::merge(&mut aabb, &collider.compute_aabb());
1123        }
1124        BoundingBox {
1125            //TODO: proper Point3 -> Mint conversion?
1126            center: (aabb.center() - nalgebra::Point3::default()).into(),
1127            half: aabb.half_extents().into(),
1128        }
1129    }
1130
1131    pub fn apply_linear_impulse(&mut self, handle: ObjectHandle, impulse: mint::Vector3<f32>) {
1132        let object = &self.objects[handle.0];
1133        let body = &mut self.physics.rigid_bodies[object.rigid_body];
1134        body.apply_impulse(impulse.into(), false)
1135    }
1136
1137    pub fn apply_angular_impulse(&mut self, handle: ObjectHandle, impulse: mint::Vector3<f32>) {
1138        let object = &self.objects[handle.0];
1139        let body = &mut self.physics.rigid_bodies[object.rigid_body];
1140        body.apply_torque_impulse(impulse.into(), false)
1141    }
1142
1143    pub fn teleport_object(&mut self, handle: ObjectHandle, transform: Transform) {
1144        let object = &self.objects[handle.0];
1145        let body = &mut self.physics.rigid_bodies[object.rigid_body];
1146        body.set_linvel(Default::default(), false);
1147        body.set_angvel(Default::default(), false);
1148        body.set_position(transform.into_isometry(), true);
1149    }
1150
1151    pub fn set_joint_motor(
1152        &mut self,
1153        handle: JointHandle,
1154        axis: JointAxis,
1155        target_pos: f32,
1156        target_vel: f32,
1157    ) {
1158        let joint = &mut self.physics[handle];
1159        let rapier_axis = axis.into_rapier();
1160        match joint.motor(rapier_axis) {
1161            Some(&rapier3d::dynamics::JointMotor {
1162                damping, stiffness, ..
1163            }) => {
1164                joint.set_motor(rapier_axis, target_pos, target_vel, stiffness, damping);
1165            }
1166            None => panic!("Axis {:?} of {:?} is not motorized", axis, handle),
1167        }
1168    }
1169
1170    pub fn set_environment_map(&mut self, path: &str) {
1171        if path.is_empty() {
1172            self.environment_map = None;
1173        } else {
1174            let full = format!("{}/{}", self.data_path, path);
1175            let (handle, task) = self.asset_hub.textures.load(
1176                full,
1177                blade_render::texture::Meta {
1178                    format: gpu::TextureFormat::Rgba32Float,
1179                    generate_mips: false,
1180                    y_flip: false,
1181                },
1182            );
1183            self.environment_map = Some(handle);
1184            self.load_tasks.push(task.clone());
1185        }
1186    }
1187
1188    pub fn set_gravity(&mut self, force: f32) {
1189        self.physics.gravity.y = -force;
1190    }
1191
1192    pub fn set_average_luminosity(&mut self, avg_lum: f32) {
1193        self.post_proc_config.average_luminocity = avg_lum;
1194    }
1195
1196    pub fn set_debug_pixel(&mut self, mouse_pos: Option<[i32; 2]>) {
1197        self.debug.mouse_pos = mouse_pos;
1198    }
1199}