diff options
Diffstat (limited to 'src_testbed/testbed.rs')
| -rw-r--r-- | src_testbed/testbed.rs | 1652 |
1 files changed, 1652 insertions, 0 deletions
diff --git a/src_testbed/testbed.rs b/src_testbed/testbed.rs new file mode 100644 index 0000000..ab709ed --- /dev/null +++ b/src_testbed/testbed.rs @@ -0,0 +1,1652 @@ +use std::env; +use std::mem; +use std::path::Path; +use std::rc::Rc; + +use crate::engine::GraphicsManager; +#[cfg(feature = "fluids")] +use crate::objects::FluidRenderingMode; +use crate::ui::TestbedUi; +use crossbeam::channel::Receiver; +use kiss3d::camera::Camera; +use kiss3d::event::Event; +use kiss3d::event::{Action, Key, WindowEvent}; +use kiss3d::light::Light; +use kiss3d::loader::obj; +use kiss3d::planar_camera::PlanarCamera; +use kiss3d::post_processing::PostProcessingEffect; +use kiss3d::text::Font; +use kiss3d::window::{State, Window}; +use na::{self, Point2, Point3, Vector3}; +use rapier::dynamics::{ + ActivationStatus, IntegrationParameters, JointSet, RigidBodyHandle, RigidBodySet, +}; +use rapier::geometry::{BroadPhase, ColliderSet, ContactEvent, NarrowPhase, ProximityEvent}; +use rapier::math::Vector; +use rapier::pipeline::{ChannelEventCollector, PhysicsPipeline}; +#[cfg(feature = "fluids")] +use salva::{coupling::ColliderCouplingSet, object::FluidHandle, LiquidWorld}; + +#[cfg(all(feature = "dim2", feature = "other-backends"))] +use crate::box2d_backend::Box2dWorld; +#[cfg(feature = "other-backends")] +use crate::nphysics_backend::NPhysicsWorld; +#[cfg(all(feature = "dim3", feature = "other-backends"))] +use crate::physx_backend::PhysxWorld; + +const RAPIER_BACKEND: usize = 0; +const NPHYSICS_BACKEND: usize = 1; +#[cfg(feature = "dim2")] +const BOX2D_BACKEND: usize = 2; +pub(crate) const PHYSX_BACKEND_PATCH_FRICTION: usize = 2; +pub(crate) const PHYSX_BACKEND_TWO_FRICTION_DIR: usize = 3; + +#[derive(PartialEq)] +pub enum RunMode { + Running, + Stop, + Step, + Quit, +} + +#[cfg(not(feature = "log"))] +fn usage(exe_name: &str) { + println!("Usage: {} [OPTION] ", exe_name); + println!(); + println!("Options:"); + println!(" --help - prints this help message and exits."); + println!(" --pause - do not start the simulation right away."); +} + +#[cfg(feature = "log")] +fn usage(exe_name: &str) { + info!("Usage: {} [OPTION] ", exe_name); + info!(""); + info!("Options:"); + info!(" --help - prints this help message and exits."); + info!(" --pause - do not start the simulation right away."); +} + +bitflags! { + #[derive(Default)] + pub struct TestbedStateFlags: u32 { + const NONE = 0; + const SLEEP = 1 << 0; + const SUB_STEPPING = 1 << 1; + const SHAPES = 1 << 2; + const JOINTS = 1 << 3; + const AABBS = 1 << 4; + const CONTACT_POINTS = 1 << 5; + const CONTACT_NORMALS = 1 << 6; + const CENTER_OF_MASSES = 1 << 7; + const WIREFRAME = 1 << 8; + const STATISTICS = 1 << 9; + const PROFILE = 1 << 10; + const DEBUG = 1 << 11; + } +} + +bitflags! { + pub struct TestbedActionFlags: u32 { + const RESET_WORLD_GRAPHICS = 1 << 0; + const EXAMPLE_CHANGED = 1 << 1; + const RESTART = 1 << 2; + const BACKEND_CHANGED = 1 << 3; + const TAKE_SNAPSHOT = 1 << 4; + const RESTORE_SNAPSHOT = 1 << 5; + } +} + +pub struct PhysicsSnapshot { + timestep_id: usize, + broad_phase: Vec<u8>, + narrow_phase: Vec<u8>, + bodies: Vec<u8>, + colliders: Vec<u8>, + joints: Vec<u8>, +} + +impl PhysicsSnapshot { + fn new( + timestep_id: usize, + broad_phase: &BroadPhase, + narrow_phase: &NarrowPhase, + bodies: &RigidBodySet, + colliders: &ColliderSet, + joints: &JointSet, + ) -> bincode::Result<Self> { + Ok(Self { + timestep_id, + broad_phase: bincode::serialize(broad_phase)?, + narrow_phase: bincode::serialize(narrow_phase)?, + bodies: bincode::serialize(bodies)?, + colliders: bincode::serialize(colliders)?, + joints: bincode::serialize(joints)?, + }) + } + + fn restore( + &self, + ) -> bincode::Result<( + usize, + BroadPhase, + NarrowPhase, + RigidBodySet, + ColliderSet, + JointSet, + )> { + Ok(( + self.timestep_id, + bincode::deserialize(&self.broad_phase)?, + bincode::deserialize(&self.narrow_phase)?, + bincode::deserialize(&self.bodies)?, + bincode::deserialize(&self.colliders)?, + bincode::deserialize(&self.joints)?, + )) + } + + fn print_snapshot_len(&self) { + let total = self.broad_phase.len() + + self.narrow_phase.len() + + self.bodies.len() + + self.colliders.len() + + self.joints.len(); + println!("Snapshot length: {}B", total); + println!("|_ broad_phase: {}B", self.broad_phase.len()); + println!("|_ narrow_phase: {}B", self.narrow_phase.len()); + println!("|_ bodies: {}B", self.bodies.len()); + println!("|_ colliders: {}B", self.colliders.len()); + println!("|_ joints: {}B", self.joints.len()); + } +} + +pub struct PhysicsEvents { + pub contact_events: Receiver<ContactEvent>, + pub proximity_events: Receiver<ProximityEvent>, +} + +impl PhysicsEvents { + fn poll_all(&self) { + while let Ok(_) = self.contact_events.try_recv() {} + while let Ok(_) = self.proximity_events.try_recv() {} + } +} + +pub struct TestbedState { + pub running: RunMode, + pub draw_colls: bool, + // pub grabbed_object: Option<DefaultBodyPartHandle>, + // pub grabbed_object_constraint: Option<DefaultJointConstraintHandle>, + pub grabbed_object_plane: (Point3<f32>, Vector3<f32>), + pub can_grab_behind_ground: bool, + pub drawing_ray: Option<Point2<f32>>, + pub prev_flags: TestbedStateFlags, + pub flags: TestbedStateFlags, + pub action_flags: TestbedActionFlags, + pub backend_names: Vec<&'static str>, + pub example_names: Vec<&'static str>, + pub selected_example: usize, + pub selected_backend: usize, + pub physx_use_two_friction_directions: bool, + pub num_threads: usize, + pub snapshot: Option<PhysicsSnapshot>, + #[cfg(feature = "parallel")] + pub thread_pool: rapier::rayon::ThreadPool, + pub timestep_id: usize, +} + +#[cfg(feature = "fluids")] +struct FluidsState { + world: LiquidWorld<f32>, + coupling: ColliderCouplingSet<f32, RigidBodyHandle>, +} + +pub struct Testbed { + builders: Vec<(&'static str, fn(&mut Testbed))>, + #[cfg(feature = "fluids")] + fluids: Option<FluidsState>, + gravity: Vector<f32>, + integration_parameters: IntegrationParameters, + broad_phase: BroadPhase, + narrow_phase: NarrowPhase, + bodies: RigidBodySet, + colliders: ColliderSet, + joints: JointSet, + physics_pipeline: PhysicsPipeline, + window: Option<Box<Window>>, + graphics: GraphicsManager, + nsteps: usize, + camera_locked: bool, // Used so that the camera can remain the same before and after we change backend or press the restart button. + callbacks: Callbacks, + #[cfg(feature = "fluids")] + callbacks_fluids: CallbacksFluids, + time: f32, + hide_counters: bool, + // persistant_contacts: HashMap<ContactId, bool>, + font: Rc<Font>, + // cursor_pos: Point2<f32>, + events: PhysicsEvents, + event_handler: ChannelEventCollector, + ui: TestbedUi, + state: TestbedState, + #[cfg(all(feature = "dim2", feature = "other-backends"))] + box2d: Option<Box2dWorld>, + #[cfg(all(feature = "dim3", feature = "other-backends"))] + physx: Option<PhysxWorld>, + #[cfg(feature = "other-backends")] + nphysics: Option<NPhysicsWorld>, +} + +type Callbacks = Vec< + Box<dyn FnMut(&mut RigidBodySet, &mut ColliderSet, &PhysicsEvents, &mut GraphicsManager, f32)>, +>; + +#[cfg(feature = "fluids")] +type CallbacksFluids = Vec< + Box< + dyn FnMut( + &mut LiquidWorld<f32>, + &mut ColliderCouplingSet<f32, RigidBodyHandle>, + &mut RigidBodySet, + &mut GraphicsManager, + f32, + ), + >, +>; + +impl Testbed { + pub fn new_empty() -> Testbed { + let graphics = GraphicsManager::new(); + + #[cfg(feature = "dim3")] + let mut window = Box::new(Window::new("rapier: 3d demo")); + #[cfg(feature = "dim2")] + let mut window = Box::new(Window::new("rapier: 2d demo")); + window.set_background_color(0.85, 0.85, 0.85); + window.set_framerate_limit(Some(60)); + window.set_light(Light::StickToCamera); + + let flags = TestbedStateFlags::SLEEP; + let ui = TestbedUi::new(&mut window); + + #[allow(unused_mut)] + let mut backend_names = vec!["rapier"]; + #[cfg(feature = "other-backends")] + backend_names.push("nphysics"); + #[cfg(all(feature = "dim2", feature = "other-backends"))] + backend_names.push("box2d"); + #[cfg(all(feature = "dim3", feature = "other-backends"))] + backend_names.push("physx (patch friction)"); + #[cfg(all(feature = "dim3", feature = "other-backends"))] + backend_names.push("physx (two friction dir)"); + + #[cfg(feature = "parallel")] + let num_threads = num_cpus::get_physical(); + #[cfg(not(feature = "parallel"))] + let num_threads = 1; + + #[cfg(feature = "parallel")] + let thread_pool = rapier::rayon::ThreadPoolBuilder::new() + .num_threads(num_threads) + .build() + .unwrap(); + + let state = TestbedState { + running: RunMode::Running, + draw_colls: false, + // grabbed_object: None, + // grabbed_object_constraint: None, + grabbed_object_plane: (Point3::origin(), na::zero()), + can_grab_behind_ground: false, + drawing_ray: None, + snapshot: None, + prev_flags: flags, + flags, + action_flags: TestbedActionFlags::empty(), + backend_names, + example_names: Vec::new(), + selected_example: 0, + timestep_id: 0, + selected_backend: RAPIER_BACKEND, + physx_use_two_friction_directions: true, + num_threads, + #[cfg(feature = "parallel")] + thread_pool, + }; + + let gravity = Vector::y() * -9.81; + let integration_parameters = IntegrationParameters::default(); + let broad_phase = BroadPhase::new(); + let narrow_phase = NarrowPhase::new(); + let bodies = RigidBodySet::new(); + let colliders = ColliderSet::new(); + let joints = JointSet::new(); + let contact_channel = crossbeam::channel::unbounded(); + let proximity_channel = crossbeam::channel::unbounded(); + let event_handler = ChannelEventCollector::new(proximity_channel.0, contact_channel.0); + let events = PhysicsEvents { + contact_events: contact_channel.1, + proximity_events: proximity_channel.1, + }; + + Testbed { + builders: Vec::new(), + #[cfg(feature = "fluids")] + fluids: None, + gravity, + integration_parameters, + broad_phase, + narrow_phase, + bodies, + colliders, + joints, + physics_pipeline: PhysicsPipeline::new(), + callbacks: Vec::new(), + #[cfg(feature = "fluids")] + callbacks_fluids: Vec::new(), + window: Some(window), + graphics, + nsteps: 1, + camera_locked: false, + time: 0.0, + hide_counters: true, + // persistant_contacts: HashMap::new(), + font: Font::default(), + // cursor_pos: Point2::new(0.0f32, 0.0), + ui, + event_handler, + events, + state, + #[cfg(all(feature = "dim2", feature = "other-backends"))] + box2d: None, + #[cfg(all(feature = "dim3", feature = "other-backends"))] + physx: None, + #[cfg(feature = "other-backends")] + nphysics: None, + } + } + + pub fn new(bodies: RigidBodySet, colliders: ColliderSet, joints: JointSet) -> Self { + let mut res = Self::new_empty(); + res.set_world(bodies, colliders, joints); + res + } + + pub fn from_builders(default: usize, builders: Vec<(&'static str, fn(&mut Self))>) -> Self { + let mut res = Testbed::new_empty(); + res.state + .action_flags + .set(TestbedActionFlags::EXAMPLE_CHANGED, true); + res.state.selected_example = default; + res.set_builders(builders); + res + } + + pub fn set_number_of_steps_per_frame(&mut self, nsteps: usize) { + self.nsteps = nsteps + } + + pub fn allow_grabbing_behind_ground(&mut self, allow: bool) { + self.state.can_grab_behind_ground = allow; + } + + pub fn hide_performance_counters(&mut self) { + self.hide_counters = true; + } + + pub fn show_performance_counters(&mut self) { + self.hide_counters = false; + } + + pub fn set_world(&mut self, bodies: RigidBodySet, colliders: ColliderSet, joints: JointSet) { + println!("Num bodies: {}", bodies.len()); + println!("Num joints: {}", joints.len()); + self.bodies = bodies; + self.colliders = colliders; + self.joints = joints; + self.broad_phase = BroadPhase::new(); + self.narrow_phase = NarrowPhase::new(); + self.state + .action_flags + .set(TestbedActionFlags::RESET_WORLD_GRAPHICS, true); + self.time = 0.0; + self.state.timestep_id = 0; + self.physics_pipeline.counters.enable(); + + #[cfg(all(feature = "dim2", feature = "other-backends"))] + { + if self.state.selected_backend == BOX2D_BACKEND { + self.box2d = Some(Box2dWorld::from_rapier( + self.gravity, + &self.bodies, + &self.colliders, + &self.joints, + )); + } + } + + #[cfg(all(feature = "dim3", feature = "other-backends"))] + { + if self.state.selected_backend == PHYSX_BACKEND_PATCH_FRICTION + || self.state.selected_backend == PHYSX_BACKEND_TWO_FRICTION_DIR + { + self.physx = Some(PhysxWorld::from_rapier( + self.gravity, + &self.integration_parameters, + &self.bodies, + &self.colliders, + &self.joints, + self.state.selected_backend == PHYSX_BACKEND_TWO_FRICTION_DIR, + self.state.num_threads, + )); + } + } + + #[cfg(feature = "other-backends")] + { + if self.state.selected_backend == NPHYSICS_BACKEND { + self.nphysics = Some(NPhysicsWorld::from_rapier( + self.gravity, + &self.bodies, + &self.colliders, + &self.joints, + )); + } + } + } + + #[cfg(feature = "fluids")] + pub fn set_liquid_world( + &mut self, + mut liquid_world: LiquidWorld<f32>, + coupling: ColliderCouplingSet<f32, RigidBodyHandle>, + ) { + liquid_world.counters.enable(); + self.fluids = Some(FluidsState { + world: liquid_world, + coupling, + }); + } + + pub fn set_builders(&mut self, builders: Vec<(&'static str, fn(&mut Self))>) { + self.state.example_names = builders.iter().map(|e| e.0).collect(); + self.builders = builders + } + + #[cfg(feature = "dim2")] + pub fn look_at(&mut self, at: Point2<f32>, zoom: f32) { + if !self.camera_locked { + self.graphics.look_at(at, zoom); + } + } + + #[cfg(feature = "dim3")] + pub fn look_at(&mut self, eye: Point3<f32>, at: Point3<f32>) { + if !self.camera_locked { + self.graphics.look_at(eye, at); + } + } + + pub fn set_body_color(&mut self, body: RigidBodyHandle, color: Point3<f32>) { + self.graphics.set_body_color(body, color); + } + + #[cfg(feature = "fluids")] + pub fn set_fluid_color(&mut self, fluid: FluidHandle, color: Point3<f32>) { + self.graphics.set_fluid_color(fluid, color); + } + + pub fn set_body_wireframe(&mut self, body: RigidBodyHandle, wireframe_enabled: bool) { + self.graphics.set_body_wireframe(body, wireframe_enabled); + } + + #[cfg(feature = "fluids")] + pub fn set_fluid_rendering_mode(&mut self, mode: FluidRenderingMode) { + self.graphics.set_fluid_rendering_mode(mode) + } + + #[cfg(feature = "fluids")] + pub fn enable_boundary_particles_rendering(&mut self, enabled: bool) { + self.graphics.enable_boundary_particles_rendering(enabled) + } + + // pub fn world(&self) -> &Box<WorldOwner> { + // &self.world + // } + + pub fn graphics_mut(&mut self) -> &mut GraphicsManager { + &mut self.graphics + } + + #[cfg(feature = "dim3")] + pub fn set_up_axis(&mut self, up_axis: Vector3<f32>) { + self.graphics.set_up_axis(up_axis); + } + + pub fn load_obj(path: &str) -> Vec<(Vec<Point3<f32>>, Vec<usize>)> { + let path = Path::new(path); + let empty = Path::new("_some_non_existant_folder"); // dont bother loading mtl files correctly + let objects = obj::parse_file(&path, &empty, "").expect("Unable to open the obj file."); + + let mut res = Vec::new(); + + for (_, m, _) in objects.into_iter() { + let vertices = m.coords().read().unwrap().to_owned().unwrap(); + let indices = m.faces().read().unwrap().to_owned().unwrap(); + + let mut flat_indices = Vec::new(); + + for i in indices.into_iter() { + flat_indices.push(i.x as usize); + flat_indices.push(i.y as usize); + flat_indices.push(i.z as usize); + } + + let m = (vertices, flat_indices); + + res.push(m); + } + + res + } + + fn clear(&mut self, window: &mut Window) { + self.callbacks.clear(); + #[cfg(feature = "fluids")] + self.callbacks_fluids.clear(); + // self.persistant_contacts.clear(); + // self.state.grabbed_object = None; + // self.state.grabbed_object_constraint = None; + self.state.can_grab_behind_ground = false; + self.graphics.clear(window); + } + + pub fn add_callback< + F: FnMut(&mut RigidBodySet, &mut ColliderSet, &PhysicsEvents, &mut GraphicsManager, f32) + + 'static, + >( + &mut self, + callback: F, + ) { + self.callbacks.push(Box::new(callback)); + } + + #[cfg(feature = "fluids")] + pub fn add_callback_with_fluids< + F: FnMut( + &mut LiquidWorld<f32>, + &mut ColliderCouplingSet<f32, RigidBodyHandle>, + &mut RigidBodySet, + &mut GraphicsManager, + f32, + ) + 'static, + >( + &mut self, + callback: F, + ) { + self.callbacks_fluids.push(Box::new(callback)); + } + + pub fn run(mut self) { + let mut args = env::args(); + let mut benchmark_mode = false; + + if args.len() > 1 { + let exname = args.next().unwrap(); + for arg in args { + if &arg[..] == "--help" || &arg[..] == "-h" { + usage(&exname[..]); + return; + } else if &arg[..] == "--pause" { + self.state.running = RunMode::Stop; + } else if &arg[..] == "--bench" { + benchmark_mode = true; + } + } + } + + if benchmark_mode { + use std::fs::File; + use std::io::{BufWriter, Write}; + // Don't enter the main loop. We will just step the simulation here. + let mut results = Vec::new(); + let builders = mem::replace(&mut self.builders, Vec::new()); + let backend_names = self.state.backend_names.clone(); + + for builder in builders { + results.clear(); + println!("Running benchmark for {}", builder.0); + const NUM_ITERS: usize = 1000; + + for (backend_id, backend) in backend_names.iter().enumerate() { + println!("|_ using backend {}", backend); + self.state.selected_backend = backend_id; + if cfg!(feature = "dim3") + && (backend_id == PHYSX_BACKEND_PATCH_FRICTION + || backend_id == PHYSX_BACKEND_TWO_FRICTION_DIR) + { + self.integration_parameters.max_velocity_iterations = 1; + self.integration_parameters.max_position_iterations = 4; + } else { + self.integration_parameters.max_velocity_iterations = 4; + self.integration_parameters.max_position_iterations = 1; + } + // Init world. + (builder.1)(&mut self); + // Run the simulation. + let mut timings = Vec::new(); + for k in 0..=NUM_ITERS { + { + // FIXME: code duplicated from self.step() + if self.state.selected_backend == RAPIER_BACKEND { + #[cfg(feature = "parallel")] + { + let gravity = &self.gravity; + let params = &self.integration_parameters; + let pipeline = &mut self.physics_pipeline; + let narrow_phase = &mut self.narrow_phase; + let broad_phase = &mut self.broad_phase; + let bodies = &mut self.bodies; + let colliders = &mut self.colliders; + let joints = &mut self.joints; + let event_handler = &self.event_handler; + self.state.thread_pool.install(|| { + pipeline.step( + gravity, + params, + broad_phase, + narrow_phase, + bodies, + colliders, + joints, + event_handler, + ); + }); + } + + #[cfg(not(feature = "parallel"))] + self.physics_pipeline.step( + &self.gravity, + &self.integration_parameters, + &mut self.broad_phase, + &mut self.narrow_phase, + &mut self.bodies, + &mut self.colliders, + &mut self.joints, + &self.event_handler, + ); + + #[cfg(feature = "fluids")] + { + fluids_time = instant::now(); + if let Some(fluids) = &mut self.fluids { + let dt = self.world.timestep(); + let gravity = &self.world.gravity; + fluids.world.step_with_coupling( + dt, + gravity, + &mut fluids + .coupling + .as_manager_mut(&self.colliders, &mut self.bodies), + ); + } + + fluids_time = instant::now() - fluids_time; + } + } + + #[cfg(all(feature = "dim2", feature = "other-backends"))] + { + if self.state.selected_backend == BOX2D_BACKEND { + self.box2d.as_mut().unwrap().step( + &mut self.physics_pipeline.counters, + &self.integration_parameters, + ); + self.box2d + .as_mut() + .unwrap() + .sync(&mut self.bodies, &mut self.colliders); + } + } + + #[cfg(all(feature = "dim3", feature = "other-backends"))] + { + if self.state.selected_backend == PHYSX_BACKEND_PATCH_FRICTION + || self.state.selected_backend == PHYSX_BACKEND_TWO_FRICTION_DIR + { + // println!("Step"); + self.physx.as_mut().unwrap().step( + &mut self.physics_pipeline.counters, + &self.integration_parameters, + ); + self.physx + .as_mut() + .unwrap() + .sync(&mut self.bodies, &mut self.colliders); + } + } + + #[cfg(feature = "other-backends")] + { + if self.state.selected_backend == NPHYSICS_BACKEND { + self.nphysics.as_mut().unwrap().step( + &mut self.physics_pipeline.counters, + &self.integration_parameters, + ); + self.nphysics + .as_mut() + .unwrap() + .sync(&mut self.bodies, &mut self.colliders); + } + } + } + + // Skip the first update. + if k > 0 { + timings.push(self.physics_pipeline.counters.step_time.time()); + } + } + results.push(timings); + } + + // Write the result as a csv file. + let filename = format!("{}.csv", builder.0); + let mut file = BufWriter::new(File::create(filename).unwrap()); + + write!(file, "{}", backend_names[0]).unwrap(); + for backend in &backend_names[1..] { + write!(file, ",{}", backend).unwrap(); + } + writeln!(file).unwrap(); + + for i in 0..results[0].len() { + write!(file, "{}", results[0][i]).unwrap(); + for result in &results[1..] { + write!(file, ",{}", result[i]).unwrap(); + } + writeln!(file).unwrap(); + } + } + } else { + let window = mem::replace(&mut self.window, None).unwrap(); + window.render_loop(self); + } + } + + fn handle_common_event<'b>(&mut self, event: Event<'b>) -> Event<'b> { + match event.value { + WindowEvent::Key(Key::T, Action::Release, _) => { + if self.state.running == RunMode::Stop { + self.state.running = RunMode::Running; + } else { + self.state.running = RunMode::Stop; + } + } + WindowEvent::Key(Key::S, Action::Release, _) => self.state.running = RunMode::Step, + WindowEvent::Key(Key::R, Action::Release, _) => self + .state + .action_flags + .set(TestbedActionFlags::EXAMPLE_CHANGED, true), + WindowEvent::Key(Key::D, Action::Release, _) => { + // Delete 10% of the remaining dynamic bodies. + let dynamic_bodies: Vec<_> = self + .bodies + .iter() + .filter(|e| e.1.is_dynamic()) + .map(|e| e.0) + .collect(); + let num_to_delete = (dynamic_bodies.len() / 10).max(1); + for to_delete in &dynamic_bodies[..num_to_delete] { + self.physics_pipeline.remove_rigid_body( + *to_delete, + &mut self.broad_phase, + &mut self.narrow_phase, + &mut self.bodies, + &mut self.colliders, + &mut self.joints, + ); + } + } + _ => {} + } + + event + } + + #[cfg(feature = "dim2")] + fn handle_special_event(&mut self, window: &mut Window, _event: Event) { + if window.is_conrod_ui_capturing_mouse() { + return; + } + + /* + match event.value { + WindowEvent::MouseButton(MouseButton::Button1, Action::Press, modifier) => { + let all_groups = &CollisionGroups::new(); + for b in self.geometrical_world.interferences_with_point( + &self.colliders, + &self.cursor_pos, + all_groups, + ) { + if !b.1.query_type().is_proximity_query() + && Some(b.1.body()) != self.ground_handle + { + if let ColliderAnchor::OnBodyPart { body_part, .. } = b.1.anchor() { + self.state.grabbed_object = Some(*body_part); + } else { + continue; + } + } + } + + if modifier.contains(Modifiers::Shift) { + if let Some(body_part) = self.state.grabbed_object { + if Some(body_part.0) != self.ground_handle { + self.graphics.remove_body_nodes(window, body_part.0); + self.bodies.remove(body_part.0); + } + } + + self.state.grabbed_object = None; + } else if modifier.contains(Modifiers::Alt) { + self.state.drawing_ray = Some(self.cursor_pos); + } else if !modifier.contains(Modifiers::Control) { + if let Some(body) = self.state.grabbed_object { + if let Some(joint) = self.state.grabbed_object_constraint { + let _ = self.constraints.remove(joint); + } + + let body_pos = self + .bodies + .get(body.0) + .unwrap() + .part(body.1) + .unwrap() + .position(); + let attach1 = self.cursor_pos; + let attach2 = body_pos.inverse() * attach1; + + if let Some(ground) = self.ground_handle { + let joint = MouseConstraint::new( + BodyPartHandle(ground, 0), + body, + attach1, + attach2, + 1.0, + ); + self.state.grabbed_object_constraint = + Some(self.constraints.insert(joint)); + } + + for node in self.graphics.body_nodes_mut(body.0).unwrap().iter_mut() { + node.select() + } + } + + event.inhibited = true; + } else { + self.state.grabbed_object = None; + } + } + WindowEvent::MouseButton(MouseButton::Button1, Action::Release, _) => { + if let Some(body) = self.state.grabbed_object { + for n in self.graphics.body_nodes_mut(body.0).unwrap().iter_mut() { + n.unselect() + } + } + + if let Some(joint) = self.state.grabbed_object_constraint { + let _ = self.constraints.remove(joint); + } + + if let Some(start) = self.state.drawing_ray { + self.graphics + .add_ray(Ray::new(start, self.cursor_pos - start)); + } + + self.state.drawing_ray = None; + self.state.grabbed_object = None; + self.state.grabbed_object_constraint = None; + } + WindowEvent::CursorPos(x, y, modifiers) => { + self.cursor_pos.x = x as f32; + self.cursor_pos.y = y as f32; + + self.cursor_pos = self + .graphics + .camera() + .unproject(&self.cursor_pos, &na::convert(window.size())); + + let attach2 = self.cursor_pos; + if self.state.grabbed_object.is_some() { + if let Some(constraint) = self + .state + .grabbed_object_constraint + .and_then(|joint| self.constraints.get_mut(joint)) + .and_then(|joint| { + joint.downcast_mut::<MouseConstraint<f32, RigidBodyHandle>>() + }) + { + constraint.set_anchor_1(attach2); + } + } + + event.inhibited = + modifiers.contains(Modifiers::Control) || modifiers.contains(Modifiers::Shift); + } + _ => {} + } + */ + } + + #[cfg(feature = "dim3")] + fn handle_special_event(&mut self, window: &mut Window, _event: Event) { + if window.is_conrod_ui_capturing_mouse() { + return; + } + + /* + match event.value { + WindowEvent::MouseButton(MouseButton::Button1, Action::Press, modifier) => { + if modifier.contains(Modifiers::Alt) { + let size = window.size(); + let (pos, dir) = self + .graphics + .camera() + .unproject(&self.cursor_pos, &na::convert(size)); + let ray = Ray::new(pos, dir); + self.graphics.add_ray(ray); + + event.inhibited = true; + } else if modifier.contains(Modifiers::Shift) { + // XXX: huge and ugly code duplication for the ray cast. + let size = window.size(); + let (pos, dir) = self + .graphics + .camera() + .unproject(&self.cursor_pos, &na::convert(size)); + let ray = Ray::new(pos, dir); + + // cast the ray + let mut mintoi = Bounded::max_value(); + let mut minb = None; + + let all_groups = CollisionGroups::new(); + for (_, b, inter) in self.geometrical_world.interferences_with_ray( + &self.colliders, + &ray, + std::f32::MAX, + &all_groups, + ) { + if !b.query_type().is_proximity_query() && inter.toi < mintoi { + |
