From 7e598e88ef625aef1f48658df7e70a39d98a9105 Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Fri, 15 Nov 2024 16:03:40 +0100 Subject: [PATCH] Add an interaction group to specify fluid/boundary interactions (#62) --- CHANGELOG.md | 5 + build/salva2d/Cargo.toml | 1 + build/salva3d/Cargo.toml | 1 + examples2d/all_examples2.rs | 2 + examples2d/basic2.rs | 11 +- examples2d/elasticity2.rs | 3 +- examples2d/helper.rs | 4 +- examples2d/layers2.rs | 180 +++++++++++++++++++++++++++++++ examples2d/surface_tension2.rs | 3 +- examples3d/basic3.rs | 5 +- examples3d/elasticity3.rs | 3 +- examples3d/faucet3.rs | 10 +- examples3d/harness_basic3.rs | 5 +- examples3d/heightfield3.rs | 4 +- examples3d/helper.rs | 4 +- examples3d/surface_tension3.rs | 3 +- src/geometry/contacts.rs | 23 +++- src/object/boundary.rs | 7 +- src/object/fluid.rs | 6 ++ src/object/interaction_groups.rs | 169 +++++++++++++++++++++++++++++ src/object/mod.rs | 1 + 21 files changed, 425 insertions(+), 25 deletions(-) create mode 100644 examples2d/layers2.rs create mode 100644 src/object/interaction_groups.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d2176e..fcabaa8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## Unreleased +### Added + +- `Fluid` now has a new parameter `InteractionGroup`, + which controls which static particles (`Boundary`s) can interact with it. + ### Changed - Update dependencies: diff --git a/build/salva2d/Cargo.toml b/build/salva2d/Cargo.toml index 7abc7ed..425538c 100644 --- a/build/salva2d/Cargo.toml +++ b/build/salva2d/Cargo.toml @@ -58,6 +58,7 @@ rapier2d = { version = "0.21", optional = true } rapier_testbed2d = { version = "0.21", optional = true } bevy_egui = { version = "0.26", features = ["immutable_ctx"], optional = true } +bitflags = "2" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] bevy = { version = "0.13.2", default-features = false, features = [ diff --git a/build/salva3d/Cargo.toml b/build/salva3d/Cargo.toml index 1cef4b1..75e2cf0 100644 --- a/build/salva3d/Cargo.toml +++ b/build/salva3d/Cargo.toml @@ -48,6 +48,7 @@ rapier3d = { version = "0.21", optional = true } rapier_testbed3d = { version = "0.21", optional = true } bevy_egui = { version = "0.26", features = ["immutable_ctx"], optional = true } +bitflags = "2.6.0" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] bevy = { version = "0.13", default-features = false, features = [ diff --git a/examples2d/all_examples2.rs b/examples2d/all_examples2.rs index b0d3390..4b93d43 100644 --- a/examples2d/all_examples2.rs +++ b/examples2d/all_examples2.rs @@ -9,6 +9,7 @@ use rapier_testbed2d::{Testbed, TestbedApp}; mod basic2; mod custom_forces2; mod elasticity2; +mod layers2; mod surface_tension2; fn demo_name_from_command_line() -> Option { @@ -47,6 +48,7 @@ fn main() { let mut builders: Vec<(_, fn(&mut Testbed))> = vec![ ("Basic", basic2::init_world), + ("Layers", layers2::init_world), ("Custom forces", custom_forces2::init_world), ("Elasticity", elasticity2::init_world), ("Surface tension", surface_tension2::init_world), diff --git a/examples2d/basic2.rs b/examples2d/basic2.rs index 863566e..cda0203 100644 --- a/examples2d/basic2.rs +++ b/examples2d/basic2.rs @@ -5,6 +5,7 @@ use rapier2d::dynamics::{ImpulseJointSet, MultibodyJointSet, RigidBodyBuilder, R use rapier2d::geometry::{Collider, ColliderBuilder, ColliderSet}; use rapier_testbed2d::Testbed; use salva2d::integrations::rapier::{ColliderSampling, FluidsPipeline, FluidsTestbedPlugin}; +use salva2d::object::interaction_groups::InteractionGroups; use salva2d::object::{Boundary, Fluid}; use salva2d::solver::{ArtificialViscosity, Becker2009Elasticity, XSPHViscosity}; use std::f32; @@ -52,7 +53,7 @@ pub fn init_world(testbed: &mut Testbed) { let elasticity: Becker2009Elasticity = Becker2009Elasticity::new(1_000.0, 0.3, true); let viscosity = XSPHViscosity::new(0.5, 1.0); - let mut fluid = Fluid::new(points1, PARTICLE_RADIUS, 1.0); + let mut fluid = Fluid::new(points1, PARTICLE_RADIUS, 1.0, InteractionGroups::default()); fluid.nonpressure_forces.push(Box::new(elasticity)); fluid.nonpressure_forces.push(Box::new(viscosity.clone())); let fluid_handle = fluids_pipeline.liquid_world.add_fluid(fluid); @@ -60,14 +61,14 @@ pub fn init_world(testbed: &mut Testbed) { let elasticity: Becker2009Elasticity = Becker2009Elasticity::new(1_000.0, 0.3, true); let viscosity = XSPHViscosity::new(0.5, 1.0); - let mut fluid = Fluid::new(points2, PARTICLE_RADIUS, 1.0); + let mut fluid = Fluid::new(points2, PARTICLE_RADIUS, 1.0, InteractionGroups::default()); fluid.nonpressure_forces.push(Box::new(elasticity)); fluid.nonpressure_forces.push(Box::new(viscosity.clone())); let fluid_handle = fluids_pipeline.liquid_world.add_fluid(fluid); plugin.set_fluid_color(fluid_handle, Point3::new(1.0, 0.4, 0.6)); let viscosity = ArtificialViscosity::new(0.5, 0.0); - let mut fluid = Fluid::new(points3, PARTICLE_RADIUS, 1.0); + let mut fluid = Fluid::new(points3, PARTICLE_RADIUS, 1.0, InteractionGroups::default()); fluid.nonpressure_forces.push(Box::new(viscosity.clone())); let fluid_handle = fluids_pipeline.liquid_world.add_fluid(fluid); plugin.set_fluid_color(fluid_handle, Point3::new(0.6, 0.8, 0.5)); @@ -92,7 +93,7 @@ pub fn init_world(testbed: &mut Testbed) { let co_handle = colliders.insert_with_parent(collider, handle, &mut bodies); let bo_handle = fluids_pipeline .liquid_world - .add_boundary(Boundary::new(Vec::new())); + .add_boundary(Boundary::new(Vec::new(), InteractionGroups::default())); fluids_pipeline.coupling.register_coupling( bo_handle, co_handle, @@ -113,7 +114,7 @@ pub fn init_world(testbed: &mut Testbed) { let co_handle = colliders.insert_with_parent(collider, rb_handle, &mut bodies); let bo_handle = fluids_pipeline .liquid_world - .add_boundary(Boundary::new(Vec::new())); + .add_boundary(Boundary::new(Vec::new(), InteractionGroups::default())); fluids_pipeline.coupling.register_coupling( bo_handle, co_handle, diff --git a/examples2d/elasticity2.rs b/examples2d/elasticity2.rs index 68bd119..147df00 100644 --- a/examples2d/elasticity2.rs +++ b/examples2d/elasticity2.rs @@ -7,6 +7,7 @@ use rapier_testbed2d::Testbed; use salva2d::integrations::rapier::{ ColliderSampling, FluidsPipeline, FluidsRenderingMode, FluidsTestbedPlugin, }; +use salva2d::object::interaction_groups::InteractionGroups; use salva2d::object::Boundary; use salva2d::solver::{Becker2009Elasticity, XSPHViscosity}; use std::f32; @@ -68,7 +69,7 @@ pub fn init_world(testbed: &mut Testbed) { let co_handle = colliders.insert_with_parent(co, ground_handle, &mut bodies); let bo_handle = fluids_pipeline .liquid_world - .add_boundary(Boundary::new(Vec::new())); + .add_boundary(Boundary::new(Vec::new(), InteractionGroups::default())); fluids_pipeline.coupling.register_coupling( bo_handle, co_handle, diff --git a/examples2d/helper.rs b/examples2d/helper.rs index ea5388c..192c1ed 100644 --- a/examples2d/helper.rs +++ b/examples2d/helper.rs @@ -1,5 +1,5 @@ use na::{Point2, Vector2}; -use salva2d::object::Fluid; +use salva2d::object::{interaction_groups::InteractionGroups, Fluid}; pub fn cube_fluid(ni: usize, nj: usize, particle_rad: f32, density: f32) -> Fluid { let mut points = Vec::new(); @@ -13,5 +13,5 @@ pub fn cube_fluid(ni: usize, nj: usize, particle_rad: f32, density: f32) -> Flui } } - Fluid::new(points, particle_rad, density) + Fluid::new(points, particle_rad, density, InteractionGroups::default()) } diff --git a/examples2d/layers2.rs b/examples2d/layers2.rs new file mode 100644 index 0000000..c7c9d34 --- /dev/null +++ b/examples2d/layers2.rs @@ -0,0 +1,180 @@ +extern crate nalgebra as na; + +use na::{DVector, Point2, Point3, Vector2}; +use rapier2d::dynamics::{ImpulseJointSet, MultibodyJointSet, RigidBodyBuilder, RigidBodySet}; +use rapier2d::geometry::{Collider, ColliderBuilder, ColliderSet}; +use rapier_testbed2d::Testbed; +use salva2d::integrations::rapier::{ColliderSampling, FluidsPipeline, FluidsTestbedPlugin}; +use salva2d::object::interaction_groups::{Group, InteractionGroups}; +use salva2d::object::{Boundary, Fluid}; +use salva2d::solver::{ArtificialViscosity, Becker2009Elasticity, XSPHViscosity}; +use std::f32; + +const PARTICLE_RADIUS: f32 = 0.1; +const SMOOTHING_FACTOR: f32 = 2.0; + +pub fn init_world(testbed: &mut Testbed) { + /* + * World + */ + let gravity = Vector2::y() * -9.81; + let mut plugin = FluidsTestbedPlugin::new(); + let mut bodies = RigidBodySet::new(); + let mut colliders = ColliderSet::new(); + let impulse_joints = ImpulseJointSet::new(); + let multibody_joints = MultibodyJointSet::new(); + let mut fluids_pipeline = FluidsPipeline::new(PARTICLE_RADIUS, SMOOTHING_FACTOR); + + // Liquid. + let mut points1 = Vec::new(); + let mut points2 = Vec::new(); + let mut points3 = Vec::new(); + let ni = 25; + let nj = 15; + + let shift2 = (nj as f32) * PARTICLE_RADIUS * 2.0; + + for i in 0..ni / 2 { + for j in 0..nj { + let x = (i as f32) * PARTICLE_RADIUS * 2.0 - ni as f32 * PARTICLE_RADIUS; + let y = (j as f32 + 1.0) * PARTICLE_RADIUS * 2.0 + 0.5; + points1.push(Point2::new(x, y)); + points2.push(Point2::new(x + ni as f32 * PARTICLE_RADIUS, y)); + } + } + + for i in 0..ni { + for j in 0..nj * 2 { + let x = (i as f32) * PARTICLE_RADIUS * 2.0 - ni as f32 * PARTICLE_RADIUS; + let y = (j as f32 + 1.0) * PARTICLE_RADIUS * 2.0 + 0.5; + points3.push(Point2::new(x, y + shift2)); + } + } + + let elasticity: Becker2009Elasticity = Becker2009Elasticity::new(1_000.0, 0.3, true); + let viscosity = XSPHViscosity::new(0.5, 1.0); + let mut fluid = Fluid::new( + points1, + PARTICLE_RADIUS, + 1.0, + InteractionGroups::new(Group::GROUP_1, Group::GROUP_1), + ); + fluid.nonpressure_forces.push(Box::new(elasticity)); + fluid.nonpressure_forces.push(Box::new(viscosity.clone())); + let fluid_handle = fluids_pipeline.liquid_world.add_fluid(fluid); + plugin.set_fluid_color(fluid_handle, Point3::new(0.8, 0.7, 1.0)); + + let elasticity: Becker2009Elasticity = Becker2009Elasticity::new(1_000.0, 0.3, true); + let viscosity = XSPHViscosity::new(0.5, 1.0); + let mut fluid = Fluid::new( + points2, + PARTICLE_RADIUS, + 1.0, + InteractionGroups::new(Group::GROUP_2, Group::GROUP_2), + ); + fluid.nonpressure_forces.push(Box::new(elasticity)); + fluid.nonpressure_forces.push(Box::new(viscosity.clone())); + let fluid_handle = fluids_pipeline.liquid_world.add_fluid(fluid); + plugin.set_fluid_color(fluid_handle, Point3::new(1.0, 0.4, 0.6)); + + let viscosity = ArtificialViscosity::new(0.5, 0.0); + let mut fluid = Fluid::new(points3, PARTICLE_RADIUS, 1.0, InteractionGroups::none()); + fluid.nonpressure_forces.push(Box::new(viscosity.clone())); + let fluid_handle = fluids_pipeline.liquid_world.add_fluid(fluid); + plugin.set_fluid_color(fluid_handle, Point3::new(0.6, 0.8, 0.5)); + + /* + * Ground + */ + let ground_size = Vector2::new(10.0, 1.0); + let nsubdivs = 50; + + let heights = DVector::from_fn(nsubdivs + 1, |i, _| { + if i == 0 || i == nsubdivs { + 20.0 + } else { + (i as f32 * ground_size.x / (nsubdivs as f32)).cos() * 0.5 + } + }); + + let rigid_body = RigidBodyBuilder::fixed().build(); + let handle = bodies.insert(rigid_body); + let collider = ColliderBuilder::heightfield(heights, ground_size).build(); + let co_handle = colliders.insert_with_parent(collider, handle, &mut bodies); + let bo_handle = fluids_pipeline + .liquid_world + .add_boundary(Boundary::new(Vec::new(), InteractionGroups::all())); + fluids_pipeline.coupling.register_coupling( + bo_handle, + co_handle, + ColliderSampling::DynamicContactSampling, + ); + + /* + * Create a dynamic rigid-bodies. + */ + let rad = 0.4; + let mut build_rigid_body_with_coupling = + |x, y, mut collider: Collider, interaction_group: InteractionGroups| { + let samples = + salva2d::sampling::shape_surface_ray_sample(collider.shape(), PARTICLE_RADIUS) + .unwrap(); + let rb = RigidBodyBuilder::dynamic() + .translation(Vector2::new(x, y)) + .build(); + let rb_handle = bodies.insert(rb); + let membership: u32 = interaction_group.memberships.into(); + let filter: u32 = interaction_group.filter.into(); + collider.set_collision_groups(rapier2d::geometry::InteractionGroups::new( + rapier2d::geometry::Group::from(membership), + rapier2d::geometry::Group::from(filter), + )); + let co_handle = colliders.insert_with_parent(collider, rb_handle, &mut bodies); + let bo_handle = fluids_pipeline + .liquid_world + .add_boundary(Boundary::new(Vec::new(), interaction_group)); + fluids_pipeline.coupling.register_coupling( + bo_handle, + co_handle, + ColliderSampling::StaticSampling(samples.clone()), + ); + }; + + let co1 = ColliderBuilder::cuboid(rad, rad).density(0.8).build(); + let co2 = ColliderBuilder::ball(rad).density(0.8).build(); + let co3 = ColliderBuilder::capsule_y(rad, rad).density(0.8).build(); + build_rigid_body_with_coupling( + 0.0, + 10.0, + co1, + InteractionGroups::new(Group::GROUP_2, Group::GROUP_2), + ); + build_rigid_body_with_coupling( + -2.0, + 10.0, + co2, + InteractionGroups::new(Group::GROUP_1, Group::GROUP_1), + ); + build_rigid_body_with_coupling( + 2.0, + 10.5, + co3, + InteractionGroups::new(Group::GROUP_3, Group::GROUP_3), + ); + + /* + * Set up the testbed. + */ + plugin.set_pipeline(fluids_pipeline); + testbed.add_plugin(plugin); + testbed.set_world_with_params( + bodies, + colliders, + impulse_joints, + multibody_joints, + gravity, + (), + ); + testbed.integration_parameters_mut().dt = 1.0 / 200.0; + // testbed.enable_boundary_particles_rendering(true); +} diff --git a/examples2d/surface_tension2.rs b/examples2d/surface_tension2.rs index 9c15d4d..3d2549c 100644 --- a/examples2d/surface_tension2.rs +++ b/examples2d/surface_tension2.rs @@ -7,6 +7,7 @@ use rapier_testbed2d::{Testbed, TestbedApp}; use salva2d::integrations::rapier::{ ColliderSampling, FluidsPipeline, FluidsRenderingMode, FluidsTestbedPlugin, }; +use salva2d::object::interaction_groups::InteractionGroups; use salva2d::object::Boundary; use salva2d::solver::{Akinci2013SurfaceTension, ArtificialViscosity}; use std::f32; @@ -50,7 +51,7 @@ pub fn init_world(testbed: &mut Testbed) { let co_handle = colliders.insert_with_parent(co, ground_handle, &mut bodies); let bo_handle = fluids_pipeline .liquid_world - .add_boundary(Boundary::new(Vec::new())); + .add_boundary(Boundary::new(Vec::new(), InteractionGroups::default())); fluids_pipeline.coupling.register_coupling( bo_handle, co_handle, diff --git a/examples3d/basic3.rs b/examples3d/basic3.rs index 0739bdf..5423195 100644 --- a/examples3d/basic3.rs +++ b/examples3d/basic3.rs @@ -5,6 +5,7 @@ use rapier3d::dynamics::{ImpulseJointSet, MultibodyJointSet, RigidBodyBuilder, R use rapier3d::geometry::{ColliderBuilder, ColliderSet, SharedShape}; use rapier_testbed3d::{Testbed, TestbedApp}; use salva3d::integrations::rapier::{ColliderSampling, FluidsPipeline, FluidsTestbedPlugin}; +use salva3d::object::interaction_groups::InteractionGroups; use salva3d::object::Boundary; use salva3d::solver::ArtificialViscosity; use std::f32; @@ -74,7 +75,7 @@ pub fn init_world(testbed: &mut Testbed) { let co_handle = colliders.insert_with_parent(co, ground_handle, &mut bodies); let bo_handle = fluids_pipeline .liquid_world - .add_boundary(Boundary::new(Vec::new())); + .add_boundary(Boundary::new(Vec::new(), InteractionGroups::default())); fluids_pipeline.coupling.register_coupling( bo_handle, @@ -89,7 +90,7 @@ pub fn init_world(testbed: &mut Testbed) { let co_handle = colliders.insert_with_parent(co, ground_handle, &mut bodies); let bo_handle = fluids_pipeline .liquid_world - .add_boundary(Boundary::new(Vec::new())); + .add_boundary(Boundary::new(Vec::new(), InteractionGroups::default())); fluids_pipeline.coupling.register_coupling( bo_handle, diff --git a/examples3d/elasticity3.rs b/examples3d/elasticity3.rs index 1a9f77c..1ad8198 100644 --- a/examples3d/elasticity3.rs +++ b/examples3d/elasticity3.rs @@ -7,6 +7,7 @@ use rapier_testbed3d::{Testbed, TestbedApp}; use salva3d::integrations::rapier::{ ColliderSampling, FluidsPipeline, FluidsRenderingMode, FluidsTestbedPlugin, }; +use salva3d::object::interaction_groups::InteractionGroups; use salva3d::object::Boundary; use salva3d::solver::{Becker2009Elasticity, XSPHViscosity}; use std::f32; @@ -83,7 +84,7 @@ pub fn init_world(testbed: &mut Testbed) { let co_handle = colliders.insert_with_parent(co, ground_handle, &mut bodies); let bo_handle = fluids_pipeline .liquid_world - .add_boundary(Boundary::new(Vec::new())); + .add_boundary(Boundary::new(Vec::new(), InteractionGroups::default())); fluids_pipeline.coupling.register_coupling( bo_handle, co_handle, diff --git a/examples3d/faucet3.rs b/examples3d/faucet3.rs index 9b6a994..dc2e5ec 100644 --- a/examples3d/faucet3.rs +++ b/examples3d/faucet3.rs @@ -8,6 +8,7 @@ use rapier3d::{ }; use rapier_testbed3d::{Testbed, TestbedApp}; use salva3d::integrations::rapier::{ColliderSampling, FluidsPipeline, FluidsTestbedPlugin}; +use salva3d::object::interaction_groups::InteractionGroups; use salva3d::object::{Boundary, Fluid}; use salva3d::solver::{Akinci2013SurfaceTension, XSPHViscosity}; use std::f32; @@ -35,7 +36,12 @@ pub fn init_world(testbed: &mut Testbed) { // Initialize the fluid. let viscosity = XSPHViscosity::new(0.5, 0.0); let tension = Akinci2013SurfaceTension::new(1.0, 10.0); - let mut fluid = Fluid::new(Vec::new(), PARTICLE_RADIUS, 1000.0); + let mut fluid = Fluid::new( + Vec::new(), + PARTICLE_RADIUS, + 1000.0, + InteractionGroups::default(), + ); fluid.nonpressure_forces.push(Box::new(viscosity)); fluid.nonpressure_forces.push(Box::new(tension)); let fluid_handle = fluids_pipeline.liquid_world.add_fluid(fluid); @@ -49,7 +55,7 @@ pub fn init_world(testbed: &mut Testbed) { let co_handle = colliders.insert_with_parent(co, ground_handle, &mut bodies); let bo_handle = fluids_pipeline .liquid_world - .add_boundary(Boundary::new(Vec::new())); + .add_boundary(Boundary::new(Vec::new(), InteractionGroups::default())); fluids_pipeline.coupling.register_coupling( bo_handle, diff --git a/examples3d/harness_basic3.rs b/examples3d/harness_basic3.rs index 2eb5931..65d9abc 100644 --- a/examples3d/harness_basic3.rs +++ b/examples3d/harness_basic3.rs @@ -8,6 +8,7 @@ use rapier3d::{ }; use rapier_testbed3d::harness::Harness; use salva3d::integrations::rapier::{ColliderSampling, FluidsHarnessPlugin, FluidsPipeline}; +use salva3d::object::interaction_groups::InteractionGroups; use salva3d::object::Boundary; use salva3d::solver::ArtificialViscosity; use std::f32; @@ -78,7 +79,7 @@ pub fn init_world(harness: &mut Harness) { let co_handle = colliders.insert_with_parent(co, ground_handle, &mut bodies); let bo_handle = fluids_pipeline .liquid_world - .add_boundary(Boundary::new(Vec::new())); + .add_boundary(Boundary::new(Vec::new(), InteractionGroups::default())); fluids_pipeline.coupling.register_coupling( bo_handle, @@ -93,7 +94,7 @@ pub fn init_world(harness: &mut Harness) { let co_handle = colliders.insert_with_parent(co, ground_handle, &mut bodies); let bo_handle = fluids_pipeline .liquid_world - .add_boundary(Boundary::new(Vec::new())); + .add_boundary(Boundary::new(Vec::new(), InteractionGroups::default())); fluids_pipeline.coupling.register_coupling( bo_handle, diff --git a/examples3d/heightfield3.rs b/examples3d/heightfield3.rs index dbd0133..e09b56c 100644 --- a/examples3d/heightfield3.rs +++ b/examples3d/heightfield3.rs @@ -7,7 +7,7 @@ use rapier_testbed3d::Testbed; use salva3d::integrations::rapier::ColliderSampling; use salva3d::integrations::rapier::FluidsPipeline; use salva3d::integrations::rapier::FluidsTestbedPlugin; -use salva3d::object::Boundary; +use salva3d::object::{interaction_groups::InteractionGroups, Boundary}; use salva3d::solver::ArtificialViscosity; #[path = "./helper.rs"] @@ -71,7 +71,7 @@ pub fn init_world(testbed: &mut Testbed) { let bo_handle = fluids_pipeline .liquid_world - .add_boundary(Boundary::new(Vec::new())); + .add_boundary(Boundary::new(Vec::new(), InteractionGroups::default())); fluids_pipeline.coupling.register_coupling( bo_handle, diff --git a/examples3d/helper.rs b/examples3d/helper.rs index 0af1f44..5735661 100644 --- a/examples3d/helper.rs +++ b/examples3d/helper.rs @@ -1,5 +1,5 @@ use super::na::{Point3, Vector3}; -use salva3d::object::Fluid; +use salva3d::object::{interaction_groups::InteractionGroups, Fluid}; pub fn cube_fluid(ni: usize, nj: usize, nk: usize, particle_rad: f32, density: f32) -> Fluid { let mut points = Vec::new(); @@ -16,5 +16,5 @@ pub fn cube_fluid(ni: usize, nj: usize, nk: usize, particle_rad: f32, density: f } } - Fluid::new(points, particle_rad, density) + Fluid::new(points, particle_rad, density, InteractionGroups::default()) } diff --git a/examples3d/surface_tension3.rs b/examples3d/surface_tension3.rs index cf6b2c8..7ad5413 100644 --- a/examples3d/surface_tension3.rs +++ b/examples3d/surface_tension3.rs @@ -7,6 +7,7 @@ use rapier_testbed3d::{Testbed, TestbedApp}; use salva3d::integrations::rapier::{ ColliderSampling, FluidsPipeline, FluidsRenderingMode, FluidsTestbedPlugin, }; +use salva3d::object::interaction_groups::InteractionGroups; use salva3d::object::Boundary; use salva3d::solver::{Akinci2013SurfaceTension, ArtificialViscosity}; use std::f32; @@ -55,7 +56,7 @@ pub fn init_world(testbed: &mut Testbed) { let co_handle = colliders.insert_with_parent(co, ground_handle, &mut bodies); let bo_handle = fluids_pipeline .liquid_world - .add_boundary(Boundary::new(Vec::new())); + .add_boundary(Boundary::new(Vec::new(), InteractionGroups::default())); fluids_pipeline.coupling.register_coupling( bo_handle, diff --git a/src/geometry/contacts.rs b/src/geometry/contacts.rs index a7c5515..ce6b5fa 100644 --- a/src/geometry/contacts.rs +++ b/src/geometry/contacts.rs @@ -271,8 +271,14 @@ fn compute_contacts_for_pair_of_cells( // Those will already be detected as fluid-boundary contacts instead. match entry { HGridEntry::BoundaryParticle(boundary_j, particle_j) => { - let pi = &boundaries[*boundary_i].positions[*particle_i]; - let pj = &boundaries[*boundary_j].positions[*particle_j]; + let bi = &boundaries[*boundary_i]; + let bj = &boundaries[*boundary_j]; + if !bi.fluid_interaction.test(bj.fluid_interaction) { + continue; + } + + let pi = &bi.positions[*particle_i]; + let pj = &bj.positions[*particle_j]; if na::distance_squared(pi, pj) <= h * h { let contact = Contact { @@ -303,7 +309,11 @@ fn compute_contacts_for_pair_of_cells( // fluid particle. continue; } - + let bi = &boundaries[*boundary_i]; + let fj = &fluids[*fluid_j]; + if !bi.fluid_interaction.test(fj.boundary_interaction) { + continue; + } let pi = &boundaries[*boundary_i].positions[*particle_i]; let pj = &fluids[*fluid_j].positions[*particle_j]; @@ -331,6 +341,13 @@ fn compute_contacts_for_pair_of_cells( let (fluid_j, particle_j, is_boundary_j) = entry.into_tuple(); let pi = fluids[*fluid_i].positions[*particle_i]; let pj = if is_boundary_j { + let bj = &boundaries[fluid_j]; + if !fluids[*fluid_i] + .boundary_interaction + .test(bj.fluid_interaction) + { + continue; + } boundaries[fluid_j].positions[particle_j] } else { fluids[fluid_j].positions[particle_j] diff --git a/src/object/boundary.rs b/src/object/boundary.rs index 8015d24..76a5c06 100644 --- a/src/object/boundary.rs +++ b/src/object/boundary.rs @@ -3,6 +3,8 @@ use crate::object::{ContiguousArena, ContiguousArenaIndex}; use std::sync::RwLock; +use super::interaction_groups::InteractionGroups; + /// A boundary object. /// /// A boundary object is composed of static particles, or of particles coupled with non-fluid bodies. @@ -17,11 +19,13 @@ pub struct Boundary { /// If this is set to `None` (which is the default), the boundary won't receive any /// force for fluids. pub forces: Option>>>, + /// Determines which fluids this boundary is allowed to interact with. + pub fluid_interaction: InteractionGroups, } impl Boundary { /// Initialize a boundary object with the given particles. - pub fn new(particle_positions: Vec>) -> Self { + pub fn new(particle_positions: Vec>, fluid_interaction: InteractionGroups) -> Self { let num_particles = particle_positions.len(); let velocities = std::iter::repeat(Vector::zeros()) .take(num_particles) @@ -35,6 +39,7 @@ impl Boundary { velocities, volumes, forces: None, + fluid_interaction, } } diff --git a/src/object/fluid.rs b/src/object/fluid.rs index 755a967..5583547 100644 --- a/src/object/fluid.rs +++ b/src/object/fluid.rs @@ -4,6 +4,8 @@ use crate::solver::NonPressureForce; use num::Zero; +use super::interaction_groups::InteractionGroups; + /// A fluid object. /// /// A fluid object is composed of movable particles with additional properties like viscosity. @@ -26,6 +28,8 @@ pub struct Fluid { num_deleted_particles: usize, /// The particles radius. particle_radius: Real, + /// The groups controlling which static particles ([`Boundary`]) can interact with this fluid. + pub boundary_interaction: InteractionGroups, } impl Fluid { @@ -36,6 +40,7 @@ impl Fluid { particle_positions: Vec>, particle_radius: Real, // XXX: remove this parameter since it is already defined by the liquid world. density0: Real, + boundary_interaction: InteractionGroups, ) -> Self { let num_particles = particle_positions.len(); let velocities: Vec<_> = std::iter::repeat(Vector::zeros()) @@ -53,6 +58,7 @@ impl Fluid { volumes: std::iter::repeat(particle_volume) .take(num_particles) .collect(), + boundary_interaction, deleted_particles: std::iter::repeat(false).take(num_particles).collect(), num_deleted_particles: 0, density0, diff --git a/src/object/interaction_groups.rs b/src/object/interaction_groups.rs new file mode 100644 index 0000000..0873168 --- /dev/null +++ b/src/object/interaction_groups.rs @@ -0,0 +1,169 @@ +#![allow(clippy::bad_bit_mask)] // Clippy will complain about the bitmasks due to Group::NONE being 0. +//! When you want two elements to ignore each other, you'll reach out for `InteractionGroups`. + +/// Pairwise filtering using bit masks. +/// +/// This filtering method is based on two 32-bit values: +/// - The interaction groups memberships. +/// - The interaction groups filter. +/// +/// An interaction is allowed between two filters `a` and `b` when two conditions +/// are met simultaneously: +/// - The groups membership of `a` has at least one bit set to `1` in common with the groups filter of `b`. +/// - The groups membership of `b` has at least one bit set to `1` in common with the groups filter of `a`. +/// +/// In other words, interactions are allowed between two filter iff. the following condition is met: +/// ```ignore +/// (self.memberships & rhs.filter) != 0 && (rhs.memberships & self.filter) != 0 +/// ``` +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +#[repr(C)] +pub struct InteractionGroups { + /// Groups memberships. + pub memberships: Group, + /// Groups filter. + pub filter: Group, +} + +impl InteractionGroups { + /// Initializes with the given interaction groups and interaction mask. + pub const fn new(memberships: Group, filter: Group) -> Self { + Self { + memberships, + filter, + } + } + + /// Allow interaction with everything. + pub const fn all() -> Self { + Self::new(Group::ALL, Group::ALL) + } + + /// Prevent all interactions. + pub const fn none() -> Self { + Self::new(Group::NONE, Group::NONE) + } + + /// Sets the group this filter is part of. + pub const fn with_memberships(mut self, memberships: Group) -> Self { + self.memberships = memberships; + self + } + + /// Sets the interaction mask of this filter. + pub const fn with_filter(mut self, filter: Group) -> Self { + self.filter = filter; + self + } + + /// Check if interactions should be allowed based on the interaction memberships and filter. + /// + /// An interaction is allowed iff. the memberships of `self` contain at least one bit set to 1 in common + /// with the filter of `rhs`, and vice-versa. + #[inline] + pub const fn test(self, rhs: Self) -> bool { + // NOTE: since const ops is not stable, we have to convert `Group` into u32 + // to use & operator in const context. + (self.memberships.bits() & rhs.filter.bits()) != 0 + && (rhs.memberships.bits() & self.filter.bits()) != 0 + } +} + +impl Default for InteractionGroups { + fn default() -> Self { + Self { + memberships: Group::GROUP_1, + filter: Group::ALL, + } + } +} + +bitflags::bitflags! { + /// A bit mask identifying groups for interaction. + #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] + pub struct Group: u32 { + /// The group n°1. + const GROUP_1 = 1 << 0; + /// The group n°2. + const GROUP_2 = 1 << 1; + /// The group n°3. + const GROUP_3 = 1 << 2; + /// The group n°4. + const GROUP_4 = 1 << 3; + /// The group n°5. + const GROUP_5 = 1 << 4; + /// The group n°6. + const GROUP_6 = 1 << 5; + /// The group n°7. + const GROUP_7 = 1 << 6; + /// The group n°8. + const GROUP_8 = 1 << 7; + /// The group n°9. + const GROUP_9 = 1 << 8; + /// The group n°10. + const GROUP_10 = 1 << 9; + /// The group n°11. + const GROUP_11 = 1 << 10; + /// The group n°12. + const GROUP_12 = 1 << 11; + /// The group n°13. + const GROUP_13 = 1 << 12; + /// The group n°14. + const GROUP_14 = 1 << 13; + /// The group n°15. + const GROUP_15 = 1 << 14; + /// The group n°16. + const GROUP_16 = 1 << 15; + /// The group n°17. + const GROUP_17 = 1 << 16; + /// The group n°18. + const GROUP_18 = 1 << 17; + /// The group n°19. + const GROUP_19 = 1 << 18; + /// The group n°20. + const GROUP_20 = 1 << 19; + /// The group n°21. + const GROUP_21 = 1 << 20; + /// The group n°22. + const GROUP_22 = 1 << 21; + /// The group n°23. + const GROUP_23 = 1 << 22; + /// The group n°24. + const GROUP_24 = 1 << 23; + /// The group n°25. + const GROUP_25 = 1 << 24; + /// The group n°26. + const GROUP_26 = 1 << 25; + /// The group n°27. + const GROUP_27 = 1 << 26; + /// The group n°28. + const GROUP_28 = 1 << 27; + /// The group n°29. + const GROUP_29 = 1 << 28; + /// The group n°30. + const GROUP_30 = 1 << 29; + /// The group n°31. + const GROUP_31 = 1 << 30; + /// The group n°32. + const GROUP_32 = 1 << 31; + + /// All of the groups. + const ALL = u32::MAX; + /// None of the groups. + const NONE = 0; + } +} + +impl From for Group { + #[inline] + fn from(val: u32) -> Self { + Self::from_bits_retain(val) + } +} + +impl From for u32 { + #[inline] + fn from(val: Group) -> Self { + val.bits() + } +} diff --git a/src/object/mod.rs b/src/object/mod.rs index 6066f6e..8ce8d5a 100644 --- a/src/object/mod.rs +++ b/src/object/mod.rs @@ -7,6 +7,7 @@ pub use self::fluid::{Fluid, FluidHandle, FluidSet}; mod boundary; mod contiguous_arena; mod fluid; +pub mod interaction_groups; /// The identifier of a single particle. pub enum ParticleId {