1#![cfg(not(any(gles, target_arch = "wasm32")))]
2#![allow(
3 irrefutable_let_patterns,
4 clippy::new_without_default,
5 clippy::needless_borrowed_reference,
7)]
8#![warn(
9 trivial_casts,
10 trivial_numeric_casts,
11 unused_extern_crates,
12 unused_qualifications,
13 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#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
67pub enum Prediction {
68 #[default]
71 LastKnown,
72 IntegrateVelocity,
74 IntegrateVelocityAndForces,
76}
77
78#[repr(u8)]
79#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
80pub enum DynamicInput {
81 Empty,
83 SetPosition,
85 SetVelocity,
87 #[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 pub allow_contacts: bool,
159 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 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
362pub 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 display_sync: gpu::DisplaySync::Block,
403 color_space: gpu::ColorSpace::Linear,
404 transparent: false,
405 allow_exclusive_full_screen: true,
406 }
407 }
408
409 #[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 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 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 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 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 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}