From b34370209fcc9af9432283a14a984f0011dad20f Mon Sep 17 00:00:00 2001 From: Dragos Daian Date: Mon, 14 Oct 2024 11:41:45 +0200 Subject: [PATCH 01/22] update update to version 0.x use master/latest for parry and rapier fix typo typo --- .gitignore | 1 + Cargo.toml | 4 +- build/salva2d-f64/Cargo.toml | 63 ++++++++++++++++++++++ build/salva2d/Cargo.toml | 33 ++++++------ build/salva3d-f64/Cargo.toml | 59 ++++++++++++++++++++ build/salva3d/Cargo.toml | 33 ++++++------ examples2d/Cargo.toml | 12 ++--- examples3d/Cargo.toml | 8 +-- src/integrations/rapier/fluids_pipeline.rs | 26 ++++----- src/integrations/rapier/testbed_plugin.rs | 36 ++++++------- src/lib.rs | 38 +++++++++---- 11 files changed, 230 insertions(+), 83 deletions(-) create mode 100644 build/salva2d-f64/Cargo.toml create mode 100644 build/salva3d-f64/Cargo.toml diff --git a/.gitignore b/.gitignore index 35e928c..25a5911 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ STYLE.md Cargo.lock .idea .vscode +.DS_Store \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index dc9065a..7cbc036 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["build/salva2d", "build/salva3d", "examples2d", "examples3d"] +members = ["build/salva2d", "build/salva2d-f64", "build/salva3d", "build/salva3d-f64", "examples2d", "examples3d"] resolver = "2" [profile.release] @@ -12,7 +12,9 @@ opt-level = 1 [patch.crates-io] salva2d = { path = "./build/salva2d" } +salva2d-f64 = { path = "./build/salva2d-f64" } salva3d = { path = "./build/salva3d" } +salva3d-f64 = { path = "./build/salva3d-f64" } #parry2d = { git = "https://github.com/sebcrozet/parry" } #parry3d = { git = "https://github.com/sebcrozet/parry" } diff --git a/build/salva2d-f64/Cargo.toml b/build/salva2d-f64/Cargo.toml new file mode 100644 index 0000000..a11513a --- /dev/null +++ b/build/salva2d-f64/Cargo.toml @@ -0,0 +1,63 @@ +[package] +name = "salva2d-f64" +version = "0.9.0" +authors = [ "Sébastien Crozet " ] +description = "2-dimensional particle-based fluid dynamics in Rust." +documentation = "https://salva.rs/docs" +homepage = "https://salva.rs" +repository = "https://github.com/dimforge/salva" +readme = "README.md" +categories = [ "science", "game-development", "mathematics", "simulation", "wasm"] +keywords = [ "physics", "dynamics", "particles", "fluids", "SPH" ] +license = "Apache-2.0" +edition = "2021" + +[badges] +maintenance = { status = "actively-developed" } + +[features] +default = [ "dim2", "f64" ] +dim2 = [ ] +f64 = [ ] +parallel = [ "rayon" ] +sampling = [ "rapier" ] +rapier = [ "parry", "rapier2d-f64" ] +rapier-testbed = [ "rapier", "rapier_testbed2d", "graphics" ] +rapier-harness = [ "rapier-testbed" ] +parry = [ "parry2d-f64" ] +wasm-bindgen = [ "rapier2d-f64/wasm-bindgen" ] +graphics = [ "bevy", "bevy_egui" ] + +[lints] +rust.unexpected_cfgs = { level = "warn", check-cfg = [ + 'cfg(feature, values("dim3", "f32"))', +] } + +[lib] +name = "salva2d_f64" +path = "../../src/lib.rs" +required-features = [ "dim2", "f64" ] + +[dependencies] +approx = "0.5" +num-traits = "0.2" +fnv = "1.0" +itertools = "0.13" +generational-arena = "0.2" +instant = { version = "0.1", features = [ "now" ] } +rayon = { version = "1.8", optional = true } + +nalgebra = "0" +parry2d-f64 = { version = "0", optional = true } +rapier2d-f64 = { version = "0", optional = true } +# TODO update it to f64 +rapier_testbed2d = { version = "0", optional = true } + +bevy_egui = { version = "0.26", features = ["immutable_ctx"], optional = true } + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +bevy = { version = "0.13.2", default-features = false, features = ["bevy_winit", "bevy_render", "x11"], optional = true } + +# Dependencies for WASM only. +[target.'cfg(target_arch = "wasm32")'.dependencies] +bevy = { version = "0.13", default-features = false, features = ["bevy_winit", "bevy_render"], optional = true } diff --git a/build/salva2d/Cargo.toml b/build/salva2d/Cargo.toml index 425538c..82485a7 100644 --- a/build/salva2d/Cargo.toml +++ b/build/salva2d/Cargo.toml @@ -22,26 +22,27 @@ edition = "2021" maintenance = { status = "actively-developed" } [features] -default = ["dim2"] -dim2 = [] -parallel = ["rayon"] -sampling = ["rapier"] -rapier = ["parry", "rapier2d"] -rapier-testbed = ["rapier", "rapier_testbed2d", "graphics"] -rapier-harness = ["rapier-testbed"] -parry = ["parry2d"] -wasm-bindgen = ["rapier2d/wasm-bindgen"] -graphics = ["bevy", "bevy_egui"] +default = [ "dim2", "f32" ] +dim2 = [ ] +f32 = [ ] +parallel = [ "rayon" ] +sampling = [ "rapier" ] +rapier = [ "parry", "rapier2d" ] +rapier-testbed = [ "rapier", "rapier_testbed2d", "graphics" ] +rapier-harness = [ "rapier-testbed" ] +parry = [ "parry2d" ] +wasm-bindgen = [ "rapier2d/wasm-bindgen" ] +graphics = [ "bevy", "bevy_egui" ] [lints] rust.unexpected_cfgs = { level = "warn", check-cfg = [ - 'cfg(feature, values("dim3"))', + 'cfg(feature, values("dim3", "f64"))', ] } [lib] name = "salva2d" path = "../../src/lib.rs" -required-features = ["dim2"] +required-features = [ "dim2", "f32" ] [dependencies] approx = "0.5" @@ -52,10 +53,10 @@ generational-arena = "0.2" instant = { version = "0.1", features = ["now"] } rayon = { version = "1.8", optional = true } -nalgebra = "0.33" -parry2d = { version = "0.16", optional = true } -rapier2d = { version = "0.21", optional = true } -rapier_testbed2d = { version = "0.21", optional = true } +nalgebra = "0" +parry2d = { version = "0", optional = true } +rapier2d = { version = "0", optional = true } +rapier_testbed2d = { version = "0", optional = true } bevy_egui = { version = "0.26", features = ["immutable_ctx"], optional = true } bitflags = "2" diff --git a/build/salva3d-f64/Cargo.toml b/build/salva3d-f64/Cargo.toml new file mode 100644 index 0000000..fef63d8 --- /dev/null +++ b/build/salva3d-f64/Cargo.toml @@ -0,0 +1,59 @@ +[package] +name = "salva3d-f64" +version = "0.9.0" +authors = [ "Sébastien Crozet " ] +description = "3-dimensional particle-based fluid dynamics in Rust." +documentation = "https://salva.rs/rustdoc/salva3d/index.html" +homepage = "https://salva.rs" +repository = "https://github.com/dimforge/salva" +readme = "README.md" +keywords = [ "physics", "dynamics", "particles", "fluids", "SPH" ] +license = "Apache-2.0" +edition = "2021" + +[features] +default = [ "dim3", "f64" ] +dim3 = [ ] +f64 = [ ] +parallel = [ "rayon" ] +rapier = [ "parry", "rapier3d-f64" ] +sampling = [ "rapier" ] +rapier-testbed = [ "rapier", "rapier_testbed3d", "graphics" ] +rapier-harness = [ "rapier-testbed" ] +parry = [ "parry3d-f64" ] +wasm-bindgen = [ "rapier3d-f64/wasm-bindgen" ] +graphics = [ "bevy", "bevy_egui" ] + +[lints] +rust.unexpected_cfgs = { level = "warn", check-cfg = [ + 'cfg(feature, values("dim2", "f32"))', +] } + +[lib] +name = "salva3d_f64" +path = "../../src/lib.rs" +required-features = [ "dim3", "f64" ] + +[dependencies] +approx = "0.5" +num-traits = "0.2" +fnv = "1.0" +itertools = "0.13" +generational-arena = "0.2" +instant = { version = "0.1", features = [ "now" ] } +rayon = { version = "1.8", optional = true } + +nalgebra = "0" +parry3d-f64 = { version = "0", optional = true } +rapier3d-f64 = { version = "0", optional = true } +# TODO update it to f64 +rapier_testbed3d = { version = "0", optional = true } + +bevy_egui = { version = "0.26", features = ["immutable_ctx"], optional = true } + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +bevy = { version = "0.13", default-features = false, features = ["bevy_winit", "bevy_render", "x11"], optional = true } + +# Dependencies for WASM only. +[target.'cfg(target_arch = "wasm32")'.dependencies] +bevy = { version = "0.13", default-features = false, features = ["bevy_winit", "bevy_render"], optional = true } diff --git a/build/salva3d/Cargo.toml b/build/salva3d/Cargo.toml index 75e2cf0..3226464 100644 --- a/build/salva3d/Cargo.toml +++ b/build/salva3d/Cargo.toml @@ -13,25 +13,26 @@ edition = "2021" [lints] rust.unexpected_cfgs = { level = "warn", check-cfg = [ - 'cfg(feature, values("dim2"))', + 'cfg(feature, values("dim2", "f64"))', ] } [features] -default = ["dim3"] -dim3 = [] -parallel = ["rayon"] -rapier = ["parry", "rapier3d"] -sampling = ["rapier"] -rapier-testbed = ["rapier", "rapier_testbed3d", "graphics"] -rapier-harness = ["rapier-testbed"] -parry = ["parry3d"] -wasm-bindgen = ["rapier3d/wasm-bindgen"] -graphics = ["bevy", "bevy_egui"] +default = [ "dim3", "f32" ] +dim3 = [ ] +f32 = [ ] +parallel = [ "rayon" ] +rapier = [ "parry", "rapier3d" ] +sampling = [ "rapier" ] +rapier-testbed = [ "rapier", "rapier_testbed3d", "graphics" ] +rapier-harness = [ "rapier-testbed" ] +parry = [ "parry3d" ] +wasm-bindgen = [ "rapier3d/wasm-bindgen" ] +graphics = [ "bevy", "bevy_egui" ] [lib] name = "salva3d" path = "../../src/lib.rs" -required-features = ["dim3"] +required-features = [ "dim3", "f32" ] [dependencies] approx = "0.5" @@ -42,10 +43,10 @@ generational-arena = "0.2" instant = { version = "0.1", features = ["now"] } rayon = { version = "1.8", optional = true } -nalgebra = "0.33" -parry3d = { version = "0.16", optional = true } -rapier3d = { version = "0.21", optional = true } -rapier_testbed3d = { version = "0.21", optional = true } +nalgebra = "0" +parry3d = { version = "0", optional = true } +rapier3d = { version = "0", optional = true } +rapier_testbed3d = { version = "0", optional = true } bevy_egui = { version = "0.26", features = ["immutable_ctx"], optional = true } bitflags = "2.6.0" diff --git a/examples2d/Cargo.toml b/examples2d/Cargo.toml index ec07788..9944f43 100644 --- a/examples2d/Cargo.toml +++ b/examples2d/Cargo.toml @@ -5,16 +5,16 @@ authors = [ "Sébastien Crozet " ] edition = "2018" [features] -default = [] +default = ["parallel"] parallel = [ "rapier_testbed2d/parallel"] [dependencies] Inflector = "0.11" -nalgebra = "0.33" -parry2d = "0.16" -rapier2d = "0.21" -rapier_testbed2d = "0.21" -parry3d = "0.16" +nalgebra = "0" +parry2d = "0" +rapier2d = "0" +rapier_testbed2d = "0" +parry3d = "0" bevy = "0.13.2" [dependencies.salva2d] diff --git a/examples3d/Cargo.toml b/examples3d/Cargo.toml index cd71aeb..83f03aa 100644 --- a/examples3d/Cargo.toml +++ b/examples3d/Cargo.toml @@ -11,10 +11,10 @@ parallel = [ "rapier_testbed3d/parallel", "salva3d/parallel"] [dependencies] num-traits = "0.2" Inflector = "0.11" -nalgebra = "0.33" -rapier3d = "0.21" -rapier_testbed3d = "0.21" -parry3d = "0.16" +nalgebra = "0" +rapier3d = "0" +rapier_testbed3d = "0" +parry3d = "0" bevy = "0.13.2" [dependencies.salva3d] diff --git a/src/integrations/rapier/fluids_pipeline.rs b/src/integrations/rapier/fluids_pipeline.rs index 0881c86..4965cb5 100644 --- a/src/integrations/rapier/fluids_pipeline.rs +++ b/src/integrations/rapier/fluids_pipeline.rs @@ -2,7 +2,7 @@ use crate::coupling::CouplingManager; use crate::geometry::{HGrid, HGridEntry}; use crate::object::{BoundaryHandle, BoundarySet, Fluid}; use crate::solver::DFSPHSolver; -use crate::LiquidWorld; +use crate::{math, LiquidWorld}; use crate::TimestepManager; use approx::AbsDiffEq; use na::Unit; @@ -31,7 +31,7 @@ impl FluidsPipeline { /// - `particle_radius`: the radius of every particle for the fluid simulation. /// - `smoothing_factor`: the smoothing factor used to compute the SPH kernel radius. /// The kernel radius will be computed as `particle_radius * smoothing_factor * 2.0. - pub fn new(particle_radius: f32, smoothing_factor: f32) -> Self { + pub fn new(particle_radius: math::Real, smoothing_factor: math::Real) -> Self { let dfsph: DFSPHSolver = DFSPHSolver::new(); Self { @@ -47,8 +47,8 @@ impl FluidsPipeline { /// However, it will not integrate these forces. Use the `PhysicsPipeline` for this integration. pub fn step( &mut self, - gravity: &Vector, - dt: f32, + gravity: &Vector, + dt: math::Real, colliders: &ColliderSet, bodies: &mut RigidBodySet, ) { @@ -66,7 +66,7 @@ pub enum ColliderSampling { /// /// It is recommended that those points are separated by a distance smaller or equal to twice /// the particle radius used to initialize the LiquidWorld. - StaticSampling(Vec>), + StaticSampling(Vec>), /// The collider shape is approximated by a dynamic set of points automatically computed based on contacts with fluid particles. DynamicContactSampling, } @@ -147,8 +147,8 @@ impl<'a> CouplingManager for ColliderCouplingManager<'a> { fn update_boundaries( &mut self, timestep: &TimestepManager, - h: f32, - particle_radius: f32, + h: math::Real, + particle_radius: math::Real, hgrid: &HGrid, fluids: &mut [Fluid], boundaries: &mut BoundarySet, @@ -187,11 +187,11 @@ impl<'a> CouplingManager for ColliderCouplingManager<'a> { .push(velocity.unwrap_or(Vector::zeros())); } - boundary.volumes.resize(points.len(), na::zero::()); + boundary.volumes.resize(points.len(), na::zero::()); } ColliderSampling::DynamicContactSampling => { - let prediction = h * na::convert::<_, f32>(0.5); - let margin = particle_radius * na::convert::<_, f32>(0.1); + let prediction = h * na::convert::<_, math::Real>(0.5); + let margin = particle_radius * na::convert::<_, math::Real>(0.1); let collider_pos = collider.position(); let aabb = collider .shape() @@ -218,7 +218,7 @@ impl<'a> CouplingManager for ColliderCouplingManager<'a> { let dpt = particle_pos - proj.point; if let Some((normal, depth)) = - Unit::try_new_and_get(dpt, f32::default_epsilon()) + Unit::try_new_and_get(dpt, math::Real::default_epsilon()) { if proj.is_inside { fluid.positions[*particle_id] -= @@ -227,7 +227,7 @@ impl<'a> CouplingManager for ColliderCouplingManager<'a> { let vel_err = normal.dot(&fluid.velocities[*particle_id]); - if vel_err > na::zero::() { + if vel_err > na::zero::() { fluid.velocities[*particle_id] -= *normal * vel_err; } @@ -243,7 +243,7 @@ impl<'a> CouplingManager for ColliderCouplingManager<'a> { .velocities .push(velocity.unwrap_or(Vector::zeros())); boundary.positions.push(proj.point); - boundary.volumes.push(na::zero::()); + boundary.volumes.push(na::zero::()); coupling.features.push(feature); } } diff --git a/src/integrations/rapier/testbed_plugin.rs b/src/integrations/rapier/testbed_plugin.rs index 8699dd1..0e7be72 100644 --- a/src/integrations/rapier/testbed_plugin.rs +++ b/src/integrations/rapier/testbed_plugin.rs @@ -50,23 +50,23 @@ pub enum FluidsRenderingMode { /// Use a red taint the closer to `max` the velocity is. VelocityColor { /// Fluids with a velocity smaller than this will not have any red taint. - min: f32, + min: Real, /// Fluids with a velocity greater than this will be completely red. - max: f32, + max: Real, }, // /// Use a red taint the closer to `max` the velocity is, with opacity, low velocity is more transparent // VelocityColorOpacity { // /// Fluids with a velocity smaller than this will not have any red taint. - // min: f32, + // min: Real, // /// Fluids with a velocity greater than this will be completely red. - // max: f32, + // max: Real, // }, /// Show particles as arrows indicating the velocity VelocityArrows { /// Fluids with a velocity smaller than this will not have any red taint. - min: f32, + min: Real, /// Fluids with a velocity greater than this will be completely red. - max: f32, + max: Real, }, } @@ -84,9 +84,9 @@ pub struct FluidsTestbedPlugin { fluids_pipeline: FluidsPipeline, f2sn: HashMap>, boundary2sn: HashMap>, - f2color: HashMap>, - ground_color: Point3, - default_fluid_color: Point3, + f2color: HashMap>, + ground_color: Point3, + default_fluid_color: Point3, queue_graphics_reset: bool, } @@ -120,7 +120,7 @@ impl FluidsTestbedPlugin { } /// Sets the color used to render the specified fluid. - pub fn set_fluid_color(&mut self, fluid: FluidHandle, color: Point3) { + pub fn set_fluid_color(&mut self, fluid: FluidHandle, color: Point3) { let _ = self.f2color.insert(fluid, color); } @@ -138,14 +138,14 @@ impl FluidsTestbedPlugin { fn add_particle_graphics( &self, particle: &Point, - particle_radius: f32, + particle_radius: Real, graphics: &mut GraphicsManager, commands: &mut Commands, meshes: &mut Assets, materials: &mut Assets, _components: &mut Query<&mut Transform>, _harness: &mut Harness, - color: &Point3, + color: &Point3, force_shape: Option, ) -> Vec { let shape = if let Some(shape) = force_shape { @@ -186,11 +186,11 @@ impl FluidsTestbedPlugin { } fn lerp_velocity( - velocity: Vector, - start: Vector3, - min: f32, - max: f32, - ) -> Vector3 { + velocity: Vector, + start: Vector3, + min: Real, + max: Real, + ) -> Vector3 { let end = Vector3::new(1.0, 0.0, 0.0); let vel: Vector = na::convert_unchecked(velocity); let vel: Vector = na::convert(vel); @@ -357,7 +357,7 @@ impl TestbedPlugin for FluidsTestbedPlugin { self.queue_graphics_reset = false; } - let (mut min, mut max) = (f32::MAX, f32::MIN); + let (mut min, mut max) = (Real::MAX, Real::MIN); for (handle, fluid) in self.fluids_pipeline.liquid_world.fluids().iter() { if let Some(entities) = self.f2sn.get_mut(&handle) { for (idx, particle) in fluid.positions.iter().enumerate() { diff --git a/src/lib.rs b/src/lib.rs index 650ee92..9855383 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,7 +26,7 @@ The name of this library is inspired from the famous surrealist artist `Salvador #![deny(non_camel_case_types)] #![deny(unused_parens)] #![deny(non_upper_case_globals)] -#![deny(unused_qualifications)] +#![allow(unused_qualifications)] #![warn(missing_docs)] // FIXME: deny this #![deny(unused_results)] #![allow(type_alias_bounds)] @@ -37,18 +37,30 @@ The name of this library is inspired from the famous surrealist artist `Salvador extern crate nalgebra as na; extern crate num_traits as num; -#[cfg(all(feature = "dim2", feature = "parry"))] +#[cfg(all(feature = "dim2", feature = "f32", feature = "parry"))] pub extern crate parry2d as parry; -#[cfg(all(feature = "dim3", feature = "parry"))] +#[cfg(all(feature = "dim2", feature = "f64", feature = "parry"))] +pub extern crate parry2d_f64 as parry; +#[cfg(all(feature = "dim3", feature = "f32", feature = "parry"))] pub extern crate parry3d as parry; -#[cfg(all(feature = "dim2", feature = "rapier"))] +#[cfg(all(feature = "dim3", feature = "f64", feature = "parry"))] +pub extern crate parry3d_f64 as parry; +#[cfg(all(feature = "dim2", feature = "f32", feature = "rapier"))] pub extern crate rapier2d as rapier; -#[cfg(all(feature = "dim3", feature = "rapier"))] +#[cfg(all(feature = "dim2", feature = "f64", feature = "rapier"))] +pub extern crate rapier2d_f64 as rapier; +#[cfg(all(feature = "dim3", feature = "f32", feature = "rapier"))] pub extern crate rapier3d as rapier; -#[cfg(all(feature = "dim2", feature = "rapier-testbed"))] -extern crate rapier_testbed2d as rapier_testbed; -#[cfg(all(feature = "dim3", feature = "rapier-testbed"))] -extern crate rapier_testbed3d as rapier_testbed; +#[cfg(all(feature = "dim3", feature = "f64", feature = "rapier"))] +pub extern crate rapier3d_f64 as rapier; +#[cfg(all(feature = "dim2", feature = "f32", feature = "rapier-testbed"))] +pub extern crate rapier_testbed2d as rapier_testbed; +#[cfg(all(feature = "dim2", feature = "f64", feature = "rapier-testbed"))] +pub extern crate rapier_testbed2d_f64 as rapier_testbed; +#[cfg(all(feature = "dim3", feature = "f32", feature = "rapier-testbed"))] +pub extern crate rapier_testbed3d as rapier_testbed; +#[cfg(all(feature = "dim3", feature = "f64", feature = "rapier-testbed"))] +pub extern crate rapier_testbed3d_f64 as rapier_testbed; macro_rules! par_iter { ($t: expr) => {{ @@ -114,8 +126,12 @@ pub mod math { /// The maximum number of possible translations of a rigid body. pub const DIM: usize = 3; + #[cfg(all(feature = "f32"))] /// The scalar type. pub type Real = f32; + #[cfg(all(feature = "f64"))] + /// The scalar type. + pub type Real = f64; /// The dimension of the ambient space. pub type Dim = U3; @@ -195,8 +211,12 @@ pub mod math { /// The maximum number of possible translations of a rigid body. pub const DIM: usize = 2; + #[cfg(all(feature = "f32"))] /// The scalar type. pub type Real = f32; + #[cfg(all(feature = "f64"))] + /// The scalar type. + pub type Real = f64; /// The dimension of the ambient space. pub type Dim = U2; From 7ffe0d16d03b9afd3865f583298f9a218fe603f2 Mon Sep 17 00:00:00 2001 From: Dragos Daian Date: Wed, 1 Oct 2025 09:09:03 +0200 Subject: [PATCH 02/22] fix parry error (api change) --- src/object/fluid.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/object/fluid.rs b/src/object/fluid.rs index 69bb356..4dd8f19 100644 --- a/src/object/fluid.rs +++ b/src/object/fluid.rs @@ -176,7 +176,7 @@ impl Fluid { #[cfg(feature = "parry")] pub fn compute_aabb(&self, particle_radius: Real) -> parry::bounding_volume::Aabb { use parry::bounding_volume::{details::local_point_cloud_aabb, BoundingVolume}; - local_point_cloud_aabb(&self.positions).loosened(particle_radius) + local_point_cloud_aabb(self.positions.iter().copied()).loosened(particle_radius) } /// The mass of the `i`-th particle of this fluid. From 13b2b4ce3db1fdf0a325f308afe00b89df772ddb Mon Sep 17 00:00:00 2001 From: Dragos Daian Date: Sun, 5 Oct 2025 10:04:42 +0200 Subject: [PATCH 03/22] make interaction group be or. --- src/object/interaction_groups.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/object/interaction_groups.rs b/src/object/interaction_groups.rs index 0873168..7b0b26d 100644 --- a/src/object/interaction_groups.rs +++ b/src/object/interaction_groups.rs @@ -58,14 +58,14 @@ impl InteractionGroups { /// 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 + /// An interaction is allowed or. 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 + || (rhs.memberships.bits() & self.filter.bits()) != 0 } } From 5dc0e34843908d4189a0ea52971fdb4310e637f1 Mon Sep 17 00:00:00 2001 From: Dragos Daian Date: Thu, 30 Oct 2025 13:51:36 +0100 Subject: [PATCH 04/22] use exactly 0.34 --- build/salva2d/Cargo.toml | 2 +- build/salva3d/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build/salva2d/Cargo.toml b/build/salva2d/Cargo.toml index 133b769..010bacb 100644 --- a/build/salva2d/Cargo.toml +++ b/build/salva2d/Cargo.toml @@ -52,7 +52,7 @@ generational-arena = "0.2" instant = { version = "0.1", features = ["now"] } rayon = { version = "1.8", optional = true } -nalgebra = "0" +nalgebra = "0.34" parry2d = { version = "0", optional = true } rapier2d = { version = "0", optional = true } rapier_testbed2d = { version = "0", optional = true } diff --git a/build/salva3d/Cargo.toml b/build/salva3d/Cargo.toml index ce8f268..f77e65e 100644 --- a/build/salva3d/Cargo.toml +++ b/build/salva3d/Cargo.toml @@ -42,7 +42,7 @@ generational-arena = "0.2" instant = { version = "0.1", features = ["now"] } rayon = { version = "1.8", optional = true } -nalgebra = "0" +nalgebra = "0.34" parry3d = { version = "0", optional = true } rapier3d = { version = "0", optional = true } rapier_testbed3d = { version = "0", optional = true } From 5240bca54e9fc833c837d89eabfb0d84c060fa82 Mon Sep 17 00:00:00 2001 From: Dragos Daian Date: Tue, 4 Nov 2025 20:21:41 +0100 Subject: [PATCH 05/22] fix interaction issue between boundary and fluid. Small update of docs --- src/integrations/rapier/fluids_pipeline.rs | 6 ++++++ src/object/interaction_groups.rs | 11 ++++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/integrations/rapier/fluids_pipeline.rs b/src/integrations/rapier/fluids_pipeline.rs index fd8c0dd..3b437f6 100644 --- a/src/integrations/rapier/fluids_pipeline.rs +++ b/src/integrations/rapier/fluids_pipeline.rs @@ -205,6 +205,12 @@ impl<'a> CouplingManager for ColliderCouplingManager<'a> { match particle { HGridEntry::FluidParticle(fluid_id, particle_id) => { let fluid = &mut fluids[*fluid_id]; + + // Check interaction groups + if !boundary.interaction_groups.test(fluid.interaction_groups) { + continue; + } + let particle_pos = fluid.positions[*particle_id] + fluid.velocities[*particle_id] * timestep.dt(); diff --git a/src/object/interaction_groups.rs b/src/object/interaction_groups.rs index 7b0b26d..ab3e68e 100644 --- a/src/object/interaction_groups.rs +++ b/src/object/interaction_groups.rs @@ -7,14 +7,14 @@ /// - The interaction groups memberships. /// - The interaction groups filter. /// -/// An interaction is allowed between two filters `a` and `b` when two conditions -/// are met simultaneously: +/// An interaction is allowed between two filters `a` and `b` when at least one of +/// these conditions is met: /// - 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 +/// (self.memberships & rhs.filter) != 0 || (rhs.memberships & self.filter) != 0 /// ``` #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] #[repr(C)] @@ -58,8 +58,9 @@ impl InteractionGroups { /// Check if interactions should be allowed based on the interaction memberships and filter. /// - /// An interaction is allowed or. the memberships of `self` contain at least one bit set to 1 in common - /// with the filter of `rhs`, and vice-versa. + /// An interaction is allowed if the memberships of `self` contain at least one bit set to 1 in common + /// with the filter of `rhs`, OR if the memberships of `rhs` contain at least one bit set to 1 in common + /// with the filter of `self`. #[inline] pub const fn test(self, rhs: Self) -> bool { // NOTE: since const ops is not stable, we have to convert `Group` into u32 From 13f4e691a9617b934cb6227af9cd2a5fcb5344e3 Mon Sep 17 00:00:00 2001 From: Dragos Daian Date: Tue, 4 Nov 2025 21:42:52 +0100 Subject: [PATCH 06/22] add logs --- src/integrations/rapier/fluids_pipeline.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/integrations/rapier/fluids_pipeline.rs b/src/integrations/rapier/fluids_pipeline.rs index 3b437f6..6044998 100644 --- a/src/integrations/rapier/fluids_pipeline.rs +++ b/src/integrations/rapier/fluids_pipeline.rs @@ -202,12 +202,20 @@ impl<'a> CouplingManager for ColliderCouplingManager<'a> { .cells_intersecting_aabb(&aabb.mins, &aabb.maxs) .flat_map(|e| e.1) { - match particle { + match particle { HGridEntry::FluidParticle(fluid_id, particle_id) => { let fluid = &mut fluids[*fluid_id]; + + // Check interaction groups between this fluid and the boundary. + // Use the fluid's interaction groups explicitly to mirror checks + // performed elsewhere in the codebase. + let fluid_groups = fluid.interaction_groups; + let boundary_groups = boundary.interaction_groups; + + println!("DynamicContactSampling: fluid_groups={:?}, boundary_groups={:?}, test result={}", + fluid_groups, boundary_groups, fluid_groups.test(boundary_groups)); - // Check interaction groups - if !boundary.interaction_groups.test(fluid.interaction_groups) { + if !fluid_groups.test(boundary_groups) { continue; } From f78494a9aa00f39dd6114e0270839ceccf5b944e Mon Sep 17 00:00:00 2001 From: Dragos Daian Date: Tue, 4 Nov 2025 21:53:23 +0100 Subject: [PATCH 07/22] comment --- src/integrations/rapier/fluids_pipeline.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/integrations/rapier/fluids_pipeline.rs b/src/integrations/rapier/fluids_pipeline.rs index 6044998..7a4c5d1 100644 --- a/src/integrations/rapier/fluids_pipeline.rs +++ b/src/integrations/rapier/fluids_pipeline.rs @@ -209,15 +209,15 @@ impl<'a> CouplingManager for ColliderCouplingManager<'a> { // Check interaction groups between this fluid and the boundary. // Use the fluid's interaction groups explicitly to mirror checks // performed elsewhere in the codebase. - let fluid_groups = fluid.interaction_groups; - let boundary_groups = boundary.interaction_groups; + //let fluid_groups = fluid.interaction_groups; + //let boundary_groups = boundary.interaction_groups; - println!("DynamicContactSampling: fluid_groups={:?}, boundary_groups={:?}, test result={}", - fluid_groups, boundary_groups, fluid_groups.test(boundary_groups)); + //println!("DynamicContactSampling: fluid_groups={:?}, boundary_groups={:?}, test result={}", + // fluid_groups, boundary_groups, fluid_groups.test(boundary_groups)); - if !fluid_groups.test(boundary_groups) { - continue; - } + //if !fluid_groups.test(boundary_groups) { + // continue; + //} let particle_pos = fluid.positions[*particle_id] + fluid.velocities[*particle_id] * timestep.dt(); From 17987f4f5698a598f1defbdd9070b00ec7b4cd6d Mon Sep 17 00:00:00 2001 From: Dragos Daian Date: Tue, 4 Nov 2025 22:05:04 +0100 Subject: [PATCH 08/22] Update fluids_pipeline.rs --- src/integrations/rapier/fluids_pipeline.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/integrations/rapier/fluids_pipeline.rs b/src/integrations/rapier/fluids_pipeline.rs index 7a4c5d1..208837e 100644 --- a/src/integrations/rapier/fluids_pipeline.rs +++ b/src/integrations/rapier/fluids_pipeline.rs @@ -209,15 +209,12 @@ impl<'a> CouplingManager for ColliderCouplingManager<'a> { // Check interaction groups between this fluid and the boundary. // Use the fluid's interaction groups explicitly to mirror checks // performed elsewhere in the codebase. - //let fluid_groups = fluid.interaction_groups; - //let boundary_groups = boundary.interaction_groups; + let fluid_groups = fluid.interaction_groups; + let boundary_groups = boundary.interaction_groups; - //println!("DynamicContactSampling: fluid_groups={:?}, boundary_groups={:?}, test result={}", - // fluid_groups, boundary_groups, fluid_groups.test(boundary_groups)); - - //if !fluid_groups.test(boundary_groups) { - // continue; - //} + if !fluid_groups.test(boundary_groups) { + continue; + } let particle_pos = fluid.positions[*particle_id] + fluid.velocities[*particle_id] * timestep.dt(); From c217212219e1a4c45f540f396b0c43f1fda96376 Mon Sep 17 00:00:00 2001 From: Dragos Daian Date: Tue, 4 Nov 2025 22:13:58 +0100 Subject: [PATCH 09/22] add boundary force coefficient --- src/coupling/coupling_manager.rs | 4 ++-- src/integrations/rapier/fluids_pipeline.rs | 19 ++++++++++++++++--- src/liquid_world.rs | 10 +++++++++- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/coupling/coupling_manager.rs b/src/coupling/coupling_manager.rs index 3eeea2c..ceae716 100644 --- a/src/coupling/coupling_manager.rs +++ b/src/coupling/coupling_manager.rs @@ -24,7 +24,7 @@ pub trait CouplingManager { ); /// Transmit forces from salva's boundary objects to the coupled bodies. - fn transmit_forces(&mut self, timestep: &TimestepManager, boundaries: &BoundarySet); + fn transmit_forces(&mut self, timestep: &TimestepManager, boundaries: &BoundarySet, force_coefficient: Real); } impl CouplingManager for () { @@ -39,5 +39,5 @@ impl CouplingManager for () { ) { } - fn transmit_forces(&mut self, _: &TimestepManager, _: &BoundarySet) {} + fn transmit_forces(&mut self, _: &TimestepManager, _: &BoundarySet, _: Real) {} } diff --git a/src/integrations/rapier/fluids_pipeline.rs b/src/integrations/rapier/fluids_pipeline.rs index 208837e..8ba7316 100644 --- a/src/integrations/rapier/fluids_pipeline.rs +++ b/src/integrations/rapier/fluids_pipeline.rs @@ -32,10 +32,23 @@ impl FluidsPipeline { /// - `smoothing_factor`: the smoothing factor used to compute the SPH kernel radius. /// The kernel radius will be computed as `particle_radius * smoothing_factor * 2.0. pub fn new(particle_radius: math::Real, smoothing_factor: math::Real) -> Self { + Self::new_with_boundary_coef(particle_radius, smoothing_factor, na::one::()) + } + + /// Initialize a new pipeline for fluids simulation with a custom boundary force coefficient. + /// + /// # Parameters + /// + /// - `particle_radius`: the radius of every particle for the fluid simulation. + /// - `smoothing_factor`: the smoothing factor used to compute the SPH kernel radius. + /// The kernel radius will be computed as `particle_radius * smoothing_factor * 2.0. + /// - `boundary_force_coefficient`: coefficient applied when transmitting forces from fluids to boundaries. + /// Use 1.0 for full force, 0.5 for half force, etc. + pub fn new_with_boundary_coef(particle_radius: math::Real, smoothing_factor: math::Real, boundary_force_coefficient: math::Real) -> Self { let dfsph: DFSPHSolver = DFSPHSolver::new(); Self { - liquid_world: LiquidWorld::new(dfsph, particle_radius, smoothing_factor), + liquid_world: LiquidWorld::new(dfsph, particle_radius, smoothing_factor, boundary_force_coefficient), coupling: ColliderCouplingSet::new(), } } @@ -271,7 +284,7 @@ impl<'a> CouplingManager for ColliderCouplingManager<'a> { } } - fn transmit_forces(&mut self, timestep: &TimestepManager, boundaries: &BoundarySet) { + fn transmit_forces(&mut self, timestep: &TimestepManager, boundaries: &BoundarySet, force_coefficient: math::Real) { for (collider, coupling) in &self.coupling.entries { if let (Some(collider), Some(boundary)) = ( self.colliders.get(*collider), @@ -288,7 +301,7 @@ impl<'a> CouplingManager for ColliderCouplingManager<'a> { for (pos, force) in boundary.positions.iter().zip(forces.iter().cloned()) { - body.apply_impulse_at_point(force * timestep.dt(), *pos, true) + body.apply_impulse_at_point(force * timestep.dt() * force_coefficient, *pos, true) } } } diff --git a/src/liquid_world.rs b/src/liquid_world.rs index 7ebe81a..b3da460 100644 --- a/src/liquid_world.rs +++ b/src/liquid_world.rs @@ -26,6 +26,10 @@ pub struct LiquidWorld { contact_manager: ContactManager, timestep_manager: TimestepManager, hgrid: HGrid, + /// Coefficient applied when transmitting forces from fluids to coupled rigid bodies. + /// A value of 1.0 applies full force, 0.5 applies half force, etc. + /// Default is 1.0. + pub boundary_force_coefficient: Real, } impl LiquidWorld { @@ -36,10 +40,13 @@ impl LiquidWorld { /// - `particle_radius`: the radius of every particle on this world. /// - `smoothing_factor`: the smoothing factor used to compute the SPH kernel radius. /// The kernel radius will be computed as `particle_radius * smoothing_factor * 2.0. + /// - `boundary_force_coefficient`: coefficient applied when transmitting forces from fluids to boundaries. + /// Default is 1.0 (full force). Use lower values (e.g., 0.5) to reduce fluid influence on rigid bodies. pub fn new( solver: impl PressureSolver + Send + Sync + 'static, particle_radius: Real, smoothing_factor: Real, + boundary_force_coefficient: Real, ) -> Self { let h = particle_radius * smoothing_factor * na::convert::<_, Real>(2.0); Self { @@ -53,6 +60,7 @@ impl LiquidWorld { contact_manager: ContactManager::new(), timestep_manager: TimestepManager::new(particle_radius), hgrid: HGrid::new(h), + boundary_force_coefficient, } } @@ -143,7 +151,7 @@ impl LiquidWorld { self.boundaries.as_slice(), ); - coupling.transmit_forces(&self.timestep_manager, &self.boundaries); + coupling.transmit_forces(&self.timestep_manager, &self.boundaries, self.boundary_force_coefficient); self.counters.stages.solver_time.pause(); } From 2a869fd516464af56c9a4d717cc79cc2ef906819 Mon Sep 17 00:00:00 2001 From: Dragos Daian Date: Thu, 6 Nov 2025 21:44:02 +0100 Subject: [PATCH 10/22] Remove instant dependency --- build/salva2d-f64/Cargo.toml | 1 - build/salva2d/Cargo.toml | 1 - build/salva3d-f64/Cargo.toml | 1 - build/salva3d/Cargo.toml | 1 - src/counters/timer.rs | 6 +++--- src/integrations/rapier/harness_plugin.rs | 4 ++-- src/integrations/rapier/testbed_plugin.rs | 4 ++-- 7 files changed, 7 insertions(+), 11 deletions(-) diff --git a/build/salva2d-f64/Cargo.toml b/build/salva2d-f64/Cargo.toml index a11513a..3999aac 100644 --- a/build/salva2d-f64/Cargo.toml +++ b/build/salva2d-f64/Cargo.toml @@ -44,7 +44,6 @@ num-traits = "0.2" fnv = "1.0" itertools = "0.13" generational-arena = "0.2" -instant = { version = "0.1", features = [ "now" ] } rayon = { version = "1.8", optional = true } nalgebra = "0" diff --git a/build/salva2d/Cargo.toml b/build/salva2d/Cargo.toml index 010bacb..b8903e2 100644 --- a/build/salva2d/Cargo.toml +++ b/build/salva2d/Cargo.toml @@ -49,7 +49,6 @@ num-traits = "0.2" fnv = "1.0" itertools = "0.14" generational-arena = "0.2" -instant = { version = "0.1", features = ["now"] } rayon = { version = "1.8", optional = true } nalgebra = "0.34" diff --git a/build/salva3d-f64/Cargo.toml b/build/salva3d-f64/Cargo.toml index fef63d8..4b55e0e 100644 --- a/build/salva3d-f64/Cargo.toml +++ b/build/salva3d-f64/Cargo.toml @@ -40,7 +40,6 @@ num-traits = "0.2" fnv = "1.0" itertools = "0.13" generational-arena = "0.2" -instant = { version = "0.1", features = [ "now" ] } rayon = { version = "1.8", optional = true } nalgebra = "0" diff --git a/build/salva3d/Cargo.toml b/build/salva3d/Cargo.toml index f77e65e..ae1e2bc 100644 --- a/build/salva3d/Cargo.toml +++ b/build/salva3d/Cargo.toml @@ -39,7 +39,6 @@ num-traits = "0.2" fnv = "1.0" itertools = "0.14" generational-arena = "0.2" -instant = { version = "0.1", features = ["now"] } rayon = { version = "1.8", optional = true } nalgebra = "0.34" diff --git a/src/counters/timer.rs b/src/counters/timer.rs index 9add9d3..8f744ca 100644 --- a/src/counters/timer.rs +++ b/src/counters/timer.rs @@ -37,7 +37,7 @@ impl Timer { pub fn start(&mut self) { if self.enabled { self.time = 0.0; - self.start = Some(instant::now()); + //self.start = Some(instant::now()); } } @@ -45,7 +45,7 @@ impl Timer { pub fn pause(&mut self) { if self.enabled { if let Some(start) = self.start { - self.time += instant::now() - start; + //self.time += instant::now() - start; } self.start = None; } @@ -54,7 +54,7 @@ impl Timer { /// Resume the timer. pub fn resume(&mut self) { if self.enabled { - self.start = Some(instant::now()); + //self.start = Some(instant::now()); } } diff --git a/src/integrations/rapier/harness_plugin.rs b/src/integrations/rapier/harness_plugin.rs index 8f6c5a4..353e640 100644 --- a/src/integrations/rapier/harness_plugin.rs +++ b/src/integrations/rapier/harness_plugin.rs @@ -57,7 +57,7 @@ impl HarnessPlugin for FluidsHarnessPlugin { } fn step(&mut self, physics: &mut PhysicsState, _run_state: &RunState) { - let step_time = instant::now(); + //let step_time = instant::now(); let dt = physics.integration_parameters.dt; self.fluids_pipeline.step( &physics.gravity, @@ -66,7 +66,7 @@ impl HarnessPlugin for FluidsHarnessPlugin { &mut physics.bodies, ); - self.step_time = instant::now() - step_time; + //self.step_time = instant::now() - step_time; } fn profiling_string(&self) -> String { diff --git a/src/integrations/rapier/testbed_plugin.rs b/src/integrations/rapier/testbed_plugin.rs index 0e7be72..2543d31 100644 --- a/src/integrations/rapier/testbed_plugin.rs +++ b/src/integrations/rapier/testbed_plugin.rs @@ -330,7 +330,7 @@ impl TestbedPlugin for FluidsTestbedPlugin { } fn step(&mut self, physics: &mut PhysicsState) { - let step_time = instant::now(); + //let step_time = instant::now(); let dt = physics.integration_parameters.dt; self.fluids_pipeline.step( &physics.gravity, @@ -339,7 +339,7 @@ impl TestbedPlugin for FluidsTestbedPlugin { &mut physics.bodies, ); - self.step_time = instant::now() - step_time; + //self.step_time = instant::now() - step_time; } fn draw( From 37b8a6a3ce2ea88aa9172dcbd936ec005a2a2cab Mon Sep 17 00:00:00 2001 From: Dragos Daian Date: Sun, 9 Nov 2025 18:36:12 +0100 Subject: [PATCH 11/22] make deleted_particles public --- src/object/fluid.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/object/fluid.rs b/src/object/fluid.rs index 4dd8f19..2140c30 100644 --- a/src/object/fluid.rs +++ b/src/object/fluid.rs @@ -23,7 +23,7 @@ pub struct Fluid { /// The rest density of this fluid. pub density0: Real, /// Mask indicating what particles have been deleted. - deleted_particles: Vec, + pub deleted_particles: Vec, /// Indicates if a bit of the `deleted_particles` mask has been set. num_deleted_particles: usize, /// The particles radius. From 3c0a04b6bd444ce0516cbfe9ec6faa738a6d3866 Mon Sep 17 00:00:00 2001 From: iLLsen Date: Mon, 10 Nov 2025 08:57:26 +0100 Subject: [PATCH 12/22] Custom changes (#3) * fix panic stale hgrid * curly typo fix --- src/liquid_world.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/liquid_world.rs b/src/liquid_world.rs index b3da460..28854bf 100644 --- a/src/liquid_world.rs +++ b/src/liquid_world.rs @@ -227,6 +227,12 @@ impl LiquidWorld { .filter_map(move |entry| match entry { HGridEntry::FluidParticle(fid, pid) => { let (fluid, handle) = self.fluids.get_from_contiguous_index(*fid)?; + + // Defensive bounds check for fluids + if *pid >= fluid.positions.len() { + return None; + } + let pt = fluid.positions[*pid]; // FIXME: use `distance_to_local_point` once it's supported. @@ -239,6 +245,16 @@ impl LiquidWorld { } HGridEntry::BoundaryParticle(bid, pid) => { let (boundary, handle) = self.boundaries.get_from_contiguous_index(*bid)?; + + // --- DEFENSIVE FIX --- + // Add a bounds check. This handles the race condition where + // a boundary's particles are cleared or the boundary is freed + // but the hgrid is not yet updated. + if *pid >= boundary.positions.len() { + return None; + } + // --- END FIX --- + let pt = boundary.positions[*pid]; // FIXME: use `distance_to_local_point` once it's supported. let id = &Isometry::identity(); if aabb.distance_to_point(id, &pt, true) < self.particle_radius { @@ -267,6 +283,12 @@ impl LiquidWorld { .filter_map(move |entry| match entry { HGridEntry::FluidParticle(fid, pid) => { let (fluid, handle) = self.fluids.get_from_contiguous_index(*fid)?; + + // Defensive bounds check for fluids + if *pid >= fluid.positions.len() { + return None; + } + let pt = fluid.positions[*pid]; if shape.distance_to_point(pos, &pt, true) <= self.particle_radius { @@ -277,6 +299,13 @@ impl LiquidWorld { } HGridEntry::BoundaryParticle(bid, pid) => { let (boundary, handle) = self.boundaries.get_from_contiguous_index(*bid)?; + + // --- DEFENSIVE FIX --- + if *pid >= boundary.positions.len() { + return None; + } + // --- END FIX --- + let pt = boundary.positions[*pid]; // FIXME: use `distance_to_local_point` once it's supported. if shape.distance_to_point(pos, &pt, true) <= self.particle_radius { Some(ParticleId::BoundaryParticle(handle, *pid)) From fd8e8120ca8d9443928c66cabc188a7432cf0109 Mon Sep 17 00:00:00 2001 From: Dragos Daian Date: Wed, 15 Apr 2026 16:13:53 +0200 Subject: [PATCH 13/22] Update to latest parry and rapier API --- examples2d/basic2.rs | 14 +-- examples2d/custom_forces2.rs | 12 +-- examples2d/elasticity2.rs | 8 +- examples2d/helper.rs | 4 +- examples2d/layers2.rs | 14 +-- examples2d/surface_tension2.rs | 6 +- examples3d/basic3.rs | 6 +- examples3d/custom_forces3.rs | 12 +-- examples3d/elasticity3.rs | 8 +- examples3d/faucet3.rs | 8 +- examples3d/heightfield3.rs | 6 +- examples3d/helper.rs | 4 +- examples3d/surface_tension3.rs | 6 +- src/geometry/contacts.rs | 6 +- src/geometry/hgrid.rs | 89 +++++++++---------- src/integrations/rapier/fluids_pipeline.rs | 4 +- src/integrations/rapier/testbed_plugin.rs | 32 +++---- src/kernel/kernel.rs | 8 +- src/lib.rs | 8 +- src/liquid_world.rs | 2 +- src/object/boundary.rs | 6 +- src/object/fluid.rs | 8 +- src/sampling/ray_sampling.rs | 28 +++--- .../elasticity/becker2009_elasticity.rs | 4 +- src/z_order.rs | 6 +- 25 files changed, 154 insertions(+), 155 deletions(-) diff --git a/examples2d/basic2.rs b/examples2d/basic2.rs index cda0203..80d29a4 100644 --- a/examples2d/basic2.rs +++ b/examples2d/basic2.rs @@ -1,6 +1,6 @@ extern crate nalgebra as na; -use na::{DVector, Point2, Point3, Vector2}; +use na::{DVector, Vector2, Vector3, Vector2}; use rapier2d::dynamics::{ImpulseJointSet, MultibodyJointSet, RigidBodyBuilder, RigidBodySet}; use rapier2d::geometry::{Collider, ColliderBuilder, ColliderSet}; use rapier_testbed2d::Testbed; @@ -38,8 +38,8 @@ pub fn init_world(testbed: &mut Testbed) { 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)); + points1.push(Vector2::new(x, y)); + points2.push(Vector2::new(x + ni as f32 * PARTICLE_RADIUS, y)); } } @@ -47,7 +47,7 @@ pub fn init_world(testbed: &mut Testbed) { 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)); + points3.push(Vector2::new(x, y + shift2)); } } @@ -57,7 +57,7 @@ pub fn init_world(testbed: &mut Testbed) { 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)); + plugin.set_fluid_color(fluid_handle, Vector3::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); @@ -65,13 +65,13 @@ pub fn init_world(testbed: &mut Testbed) { 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)); + plugin.set_fluid_color(fluid_handle, Vector3::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::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)); + plugin.set_fluid_color(fluid_handle, Vector3::new(0.6, 0.8, 0.5)); /* * Ground diff --git a/examples2d/custom_forces2.rs b/examples2d/custom_forces2.rs index a97e5ab..ebe3221 100644 --- a/examples2d/custom_forces2.rs +++ b/examples2d/custom_forces2.rs @@ -1,6 +1,6 @@ extern crate nalgebra as na; -use na::{Point2, Point3, Unit, Vector2}; +use na::{Vector2, Vector3, Unit, Vector2}; use rapier2d::dynamics::{ImpulseJointSet, MultibodyJointSet, RigidBodySet}; use rapier2d::geometry::ColliderSet; use rapier_testbed2d::Testbed; @@ -30,16 +30,16 @@ pub fn init_world(testbed: &mut Testbed) { // Liquid. let nparticles = 30; let custom_force1 = CustomForceField { - origin: Point2::new(1.0, 0.0), + origin: Vector2::new(1.0, 0.0), }; let custom_force2 = CustomForceField { - origin: Point2::new(-1.0, 0.0), + origin: Vector2::new(-1.0, 0.0), }; let mut fluid = helper::cube_fluid(nparticles, nparticles, PARTICLE_RADIUS, 1000.0); fluid.nonpressure_forces.push(Box::new(custom_force1)); fluid.nonpressure_forces.push(Box::new(custom_force2)); let fluid_handle = fluids_pipeline.liquid_world.add_fluid(fluid); - plugin.set_fluid_color(fluid_handle, Point3::new(0.8, 0.7, 1.0)); + plugin.set_fluid_color(fluid_handle, Vector3::new(0.8, 0.7, 1.0)); /* * Set up the testbed. @@ -56,11 +56,11 @@ pub fn init_world(testbed: &mut Testbed) { (), ); testbed.integration_parameters_mut().dt = 1.0 / 200.0; - testbed.look_at(Point2::origin(), 300.0); + testbed.look_at(Vector2::origin(), 300.0); } struct CustomForceField { - origin: Point2, + origin: Vector2, } impl NonPressureForce for CustomForceField { diff --git a/examples2d/elasticity2.rs b/examples2d/elasticity2.rs index 147df00..a6952fd 100644 --- a/examples2d/elasticity2.rs +++ b/examples2d/elasticity2.rs @@ -1,6 +1,6 @@ extern crate nalgebra as na; -use na::{Isometry2, Point2, Point3, Vector2}; +use na::{Isometry2, Vector2, Vector3, Vector2}; use rapier2d::dynamics::{ImpulseJointSet, MultibodyJointSet, RigidBodyBuilder, RigidBodySet}; use rapier2d::geometry::{ColliderBuilder, ColliderSet}; use rapier_testbed2d::Testbed; @@ -49,7 +49,7 @@ pub fn init_world(testbed: &mut Testbed) { 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)); + plugin.set_fluid_color(fluid_handle, Vector3::new(0.8, 0.7, 1.0)); // Second fluid with smaller young modulus. let elasticity: Becker2009Elasticity = Becker2009Elasticity::new(100_000.0, 0.3, true); @@ -61,7 +61,7 @@ pub fn init_world(testbed: &mut Testbed) { fluid.nonpressure_forces.push(Box::new(elasticity)); fluid.nonpressure_forces.push(Box::new(viscosity)); let fluid_handle = fluids_pipeline.liquid_world.add_fluid(fluid); - plugin.set_fluid_color(fluid_handle, Point3::new(0.6, 0.8, 0.5)); + plugin.set_fluid_color(fluid_handle, Vector3::new(0.6, 0.8, 0.5)); // Setup the ground. let ground_handle = bodies.insert(RigidBodyBuilder::fixed().build()); @@ -91,5 +91,5 @@ pub fn init_world(testbed: &mut Testbed) { (), ); testbed.integration_parameters_mut().dt = 1.0 / 200.0; - testbed.look_at(Point2::new(0.0, 1.0), 100.0); + testbed.look_at(Vector2::new(0.0, 1.0), 100.0); } diff --git a/examples2d/helper.rs b/examples2d/helper.rs index 192c1ed..e141b9b 100644 --- a/examples2d/helper.rs +++ b/examples2d/helper.rs @@ -1,4 +1,4 @@ -use na::{Point2, Vector2}; +use na::{Vector2, Vector2}; use salva2d::object::{interaction_groups::InteractionGroups, Fluid}; pub fn cube_fluid(ni: usize, nj: usize, particle_rad: f32, density: f32) -> Fluid { @@ -9,7 +9,7 @@ pub fn cube_fluid(ni: usize, nj: usize, particle_rad: f32, density: f32) -> Flui for j in 0..nj { let x = (i as f32) * particle_rad * 2.0; let y = (j as f32) * particle_rad * 2.0; - points.push(Point2::new(x, y) + Vector2::repeat(particle_rad) - half_extents); + points.push(Vector2::new(x, y) + Vector2::repeat(particle_rad) - half_extents); } } diff --git a/examples2d/layers2.rs b/examples2d/layers2.rs index 20155aa..979b2fb 100644 --- a/examples2d/layers2.rs +++ b/examples2d/layers2.rs @@ -1,6 +1,6 @@ extern crate nalgebra as na; -use na::{DVector, Point2, Point3, Vector2}; +use na::{DVector, Vector2, Vector3, Vector2}; use rapier2d::dynamics::{ImpulseJointSet, MultibodyJointSet, RigidBodyBuilder, RigidBodySet}; use rapier2d::geometry::{Collider, ColliderBuilder, ColliderSet}; use rapier_testbed2d::Testbed; @@ -38,8 +38,8 @@ pub fn init_world(testbed: &mut Testbed) { 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)); + points1.push(Vector2::new(x, y)); + points2.push(Vector2::new(x + ni as f32 * PARTICLE_RADIUS, y)); } } @@ -47,7 +47,7 @@ pub fn init_world(testbed: &mut Testbed) { 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)); + points3.push(Vector2::new(x, y + shift2)); } } @@ -62,7 +62,7 @@ pub fn init_world(testbed: &mut Testbed) { 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)); + plugin.set_fluid_color(fluid_handle, Vector3::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); @@ -75,7 +75,7 @@ pub fn init_world(testbed: &mut Testbed) { 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)); + plugin.set_fluid_color(fluid_handle, Vector3::new(1.0, 0.4, 0.6)); let viscosity = ArtificialViscosity::new(0.5, 0.0); let mut fluid = Fluid::new( @@ -86,7 +86,7 @@ pub fn init_world(testbed: &mut Testbed) { ); 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)); + plugin.set_fluid_color(fluid_handle, Vector3::new(0.6, 0.8, 0.5)); /* * Ground diff --git a/examples2d/surface_tension2.rs b/examples2d/surface_tension2.rs index 3d2549c..7ba5481 100644 --- a/examples2d/surface_tension2.rs +++ b/examples2d/surface_tension2.rs @@ -1,6 +1,6 @@ extern crate nalgebra as na; -use na::{Isometry2, Point2, Point3, Vector2}; +use na::{Isometry2, Vector2, Vector3, Vector2}; use rapier2d::dynamics::{ImpulseJointSet, MultibodyJointSet, RigidBodyBuilder, RigidBodySet}; use rapier2d::geometry::{ColliderBuilder, ColliderSet}; use rapier_testbed2d::{Testbed, TestbedApp}; @@ -40,7 +40,7 @@ pub fn init_world(testbed: &mut Testbed) { fluid.nonpressure_forces.push(Box::new(surface_tension)); fluid.nonpressure_forces.push(Box::new(viscosity)); let fluid_handle = fluids_pipeline.liquid_world.add_fluid(fluid); - plugin.set_fluid_color(fluid_handle, Point3::new(0.8, 0.7, 1.0)); + plugin.set_fluid_color(fluid_handle, Vector3::new(0.8, 0.7, 1.0)); // Setup the ground. let ground_thickness = 0.02; @@ -73,7 +73,7 @@ pub fn init_world(testbed: &mut Testbed) { (), ); testbed.integration_parameters_mut().dt = 1.0 / 200.0; - testbed.look_at(Point2::origin(), 1500.0); + testbed.look_at(Vector2::origin(), 1500.0); // testbed.enable_boundary_particles_rendering(true); } diff --git a/examples3d/basic3.rs b/examples3d/basic3.rs index 5423195..d5ae684 100644 --- a/examples3d/basic3.rs +++ b/examples3d/basic3.rs @@ -1,6 +1,6 @@ extern crate nalgebra as na; -use na::{Isometry3, Point3, Vector3}; +use na::{Isometry3, Vector3, Vector3}; use rapier3d::dynamics::{ImpulseJointSet, MultibodyJointSet, RigidBodyBuilder, RigidBodySet}; use rapier3d::geometry::{ColliderBuilder, ColliderSet, SharedShape}; use rapier_testbed3d::{Testbed, TestbedApp}; @@ -103,7 +103,7 @@ pub fn init_world(testbed: &mut Testbed) { */ let mut plugin = FluidsTestbedPlugin::new(); plugin.set_pipeline(fluids_pipeline); - plugin.set_fluid_color(fluid_handle, Point3::new(0.8, 0.7, 1.0)); + plugin.set_fluid_color(fluid_handle, Vector3::new(0.8, 0.7, 1.0)); plugin.render_boundary_particles = true; testbed.add_plugin(plugin); // testbed.set_body_wireframe(ground_handle, true); @@ -116,7 +116,7 @@ pub fn init_world(testbed: &mut Testbed) { (), ); testbed.integration_parameters_mut().dt = 1.0 / 200.0; - testbed.look_at(Point3::new(3.0, 3.0, 3.0), Point3::origin()); + testbed.look_at(Vector3::new(3.0, 3.0, 3.0), Vector3::origin()); } fn main() { diff --git a/examples3d/custom_forces3.rs b/examples3d/custom_forces3.rs index 2158d38..9dc22cf 100644 --- a/examples3d/custom_forces3.rs +++ b/examples3d/custom_forces3.rs @@ -1,6 +1,6 @@ extern crate nalgebra as na; -use na::{Point3, Unit, Vector3}; +use na::{Vector3, Unit, Vector3}; use rapier3d::dynamics::{ImpulseJointSet, MultibodyJointSet, RigidBodySet}; use rapier3d::geometry::ColliderSet; use rapier_testbed3d::{Testbed, TestbedApp}; @@ -30,16 +30,16 @@ pub fn init_world(testbed: &mut Testbed) { // fluids. let nparticles = 10; let custom_force1 = CustomForceField { - origin: Point3::new(1.0, 0.0, 0.0), + origin: Vector3::new(1.0, 0.0, 0.0), }; let custom_force2 = CustomForceField { - origin: Point3::new(-1.0, 0.0, 0.0), + origin: Vector3::new(-1.0, 0.0, 0.0), }; let mut fluid = helper::cube_fluid(nparticles, nparticles, nparticles, PARTICLE_RADIUS, 1000.0); fluid.nonpressure_forces.push(Box::new(custom_force1)); fluid.nonpressure_forces.push(Box::new(custom_force2)); let fluid_handle = fluids_pipeline.liquid_world.add_fluid(fluid); - plugin.set_fluid_color(fluid_handle, Point3::new(0.8, 0.7, 1.0)); + plugin.set_fluid_color(fluid_handle, Vector3::new(0.8, 0.7, 1.0)); /* * Set up the testbed. @@ -56,7 +56,7 @@ pub fn init_world(testbed: &mut Testbed) { (), ); testbed.integration_parameters_mut().dt = 1.0 / 200.0; - testbed.look_at(Point3::new(3.0, 3.0, 3.0), Point3::origin()); + testbed.look_at(Vector3::new(3.0, 3.0, 3.0), Vector3::origin()); } fn main() { @@ -65,7 +65,7 @@ fn main() { } struct CustomForceField { - origin: Point3, + origin: Vector3, } impl NonPressureForce for CustomForceField { diff --git a/examples3d/elasticity3.rs b/examples3d/elasticity3.rs index 1ad8198..704bcb4 100644 --- a/examples3d/elasticity3.rs +++ b/examples3d/elasticity3.rs @@ -1,6 +1,6 @@ extern crate nalgebra as na; -use na::{Isometry3, Point3, Vector3}; +use na::{Isometry3, Vector3, Vector3}; use rapier3d::dynamics::{ImpulseJointSet, MultibodyJointSet, RigidBodyBuilder, RigidBodySet}; use rapier3d::geometry::{ColliderBuilder, ColliderSet}; use rapier_testbed3d::{Testbed, TestbedApp}; @@ -56,7 +56,7 @@ pub fn init_world(testbed: &mut Testbed) { 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)); + plugin.set_fluid_color(fluid_handle, Vector3::new(0.8, 0.7, 1.0)); // Second fluid with smaller young modulus. let elasticity: Becker2009Elasticity = Becker2009Elasticity::new(100_000.0, 0.3, true); @@ -75,7 +75,7 @@ pub fn init_world(testbed: &mut Testbed) { fluid.nonpressure_forces.push(Box::new(elasticity)); fluid.nonpressure_forces.push(Box::new(viscosity)); let fluid_handle = fluids_pipeline.liquid_world.add_fluid(fluid); - plugin.set_fluid_color(fluid_handle, Point3::new(0.6, 0.8, 0.5)); + plugin.set_fluid_color(fluid_handle, Vector3::new(0.6, 0.8, 0.5)); // Setup the ground. let ground_handle = bodies.insert(RigidBodyBuilder::fixed().build()); @@ -107,7 +107,7 @@ pub fn init_world(testbed: &mut Testbed) { (), ); testbed.integration_parameters_mut().dt = 1.0 / 200.0; - testbed.look_at(Point3::new(1.5, 1.5, 1.5), Point3::origin()); + testbed.look_at(Vector3::new(1.5, 1.5, 1.5), Vector3::origin()); } fn main() { diff --git a/examples3d/faucet3.rs b/examples3d/faucet3.rs index dc2e5ec..a88659e 100644 --- a/examples3d/faucet3.rs +++ b/examples3d/faucet3.rs @@ -1,6 +1,6 @@ extern crate nalgebra as na; -use na::{Point3, Vector3}; +use na::{Vector3, Vector3}; use rapier3d::geometry::{ColliderBuilder, ColliderSet}; use rapier3d::{ dynamics::{RigidBodyBuilder, RigidBodySet}, @@ -45,7 +45,7 @@ pub fn init_world(testbed: &mut Testbed) { fluid.nonpressure_forces.push(Box::new(viscosity)); fluid.nonpressure_forces.push(Box::new(tension)); let fluid_handle = fluids_pipeline.liquid_world.add_fluid(fluid); - plugin.set_fluid_color(fluid_handle, Point3::new(0.5, 1.0, 1.0)); + plugin.set_fluid_color(fluid_handle, Vector3::new(0.5, 1.0, 1.0)); // Setup the ground. let ground_handle = bodies.insert(RigidBodyBuilder::fixed().build()); @@ -95,7 +95,7 @@ pub fn init_world(testbed: &mut Testbed) { for i in 0..nparticles { for j in 0..nparticles { - let pos = Point3::new(i as f32 * diam, height, j as f32 * diam); + let pos = Vector3::new(i as f32 * diam, height, j as f32 * diam); particles.push(pos + Vector3::new(shift, 0.0, shift)); velocities.push(Vector3::y() * vel); } @@ -120,7 +120,7 @@ pub fn init_world(testbed: &mut Testbed) { ); testbed.integration_parameters_mut().dt = 1.0 / 200.0; // testbed.enable_boundary_particles_rendering(true); - testbed.look_at(Point3::new(1.5, 0.0, 1.5), Point3::new(0.0, 0.0, 0.0)); + testbed.look_at(Vector3::new(1.5, 0.0, 1.5), Vector3::new(0.0, 0.0, 0.0)); } fn main() { diff --git a/examples3d/heightfield3.rs b/examples3d/heightfield3.rs index e09b56c..777b76d 100644 --- a/examples3d/heightfield3.rs +++ b/examples3d/heightfield3.rs @@ -81,15 +81,15 @@ pub fn init_world(testbed: &mut Testbed) { let mut plugin = FluidsTestbedPlugin::new(); plugin.set_pipeline(fluids_pipeline); - plugin.set_fluid_color(fluid_handle, Point::new(0.8, 0.7, 1.0)); + plugin.set_fluid_color(fluid_handle, Vector::new(0.8, 0.7, 1.0)); // plugin.render_boundary_particles = true; testbed.add_plugin(plugin); testbed.set_body_wireframe(handle, true); testbed.integration_parameters_mut().dt = 1.0 / 200.0; - // testbed.look_at(Point3::new(3.0, 3.0, 3.0), Point3::origin()); + // testbed.look_at(Vector3::new(3.0, 3.0, 3.0), Vector3::origin()); /* * Set up the testbed. */ testbed.set_world(bodies, colliders, impulse_joints, multibody_joints); - testbed.look_at(point![100.0, 100.0, 100.0], Point::origin()); + testbed.look_at(point![100.0, 100.0, 100.0], Vector::origin()); } diff --git a/examples3d/helper.rs b/examples3d/helper.rs index 5735661..48a071c 100644 --- a/examples3d/helper.rs +++ b/examples3d/helper.rs @@ -1,4 +1,4 @@ -use super::na::{Point3, Vector3}; +use super::na::{Vector3, Vector3}; use salva3d::object::{interaction_groups::InteractionGroups, Fluid}; pub fn cube_fluid(ni: usize, nj: usize, nk: usize, particle_rad: f32, density: f32) -> Fluid { @@ -11,7 +11,7 @@ pub fn cube_fluid(ni: usize, nj: usize, nk: usize, particle_rad: f32, density: f let x = (i as f32) * particle_rad * 2.0; let y = (j as f32) * particle_rad * 2.0; let z = (k as f32) * particle_rad * 2.0; - points.push(Point3::new(x, y, z) + Vector3::repeat(particle_rad) - half_extents); + points.push(Vector3::new(x, y, z) + Vector3::repeat(particle_rad) - half_extents); } } } diff --git a/examples3d/surface_tension3.rs b/examples3d/surface_tension3.rs index 7ad5413..25f5f5c 100644 --- a/examples3d/surface_tension3.rs +++ b/examples3d/surface_tension3.rs @@ -1,6 +1,6 @@ extern crate nalgebra as na; -use na::{Isometry3, Point3, Vector3}; +use na::{Isometry3, Vector3, Vector3}; use rapier3d::dynamics::{ImpulseJointSet, MultibodyJointSet, RigidBodyBuilder, RigidBodySet}; use rapier3d::geometry::{ColliderBuilder, ColliderSet}; use rapier_testbed3d::{Testbed, TestbedApp}; @@ -43,7 +43,7 @@ pub fn init_world(testbed: &mut Testbed) { fluid.nonpressure_forces.push(Box::new(surface_tension)); fluid.nonpressure_forces.push(Box::new(viscosity)); let fluid_handle = fluids_pipeline.liquid_world.add_fluid(fluid); - plugin.set_fluid_color(fluid_handle, Point3::new(0.8, 0.7, 1.0)); + plugin.set_fluid_color(fluid_handle, Vector3::new(0.8, 0.7, 1.0)); // Setup the ground. let ground_handle = bodies.insert(RigidBodyBuilder::fixed().build()); @@ -80,7 +80,7 @@ pub fn init_world(testbed: &mut Testbed) { (), ); testbed.integration_parameters_mut().dt = 1.0 / 200.0; - testbed.look_at(Point3::new(0.25, 0.25, 0.25), Point3::origin()); + testbed.look_at(Vector3::new(0.25, 0.25, 0.25), Vector3::origin()); } fn main() { diff --git a/src/geometry/contacts.rs b/src/geometry/contacts.rs index a4d0964..cb4ec7b 100644 --- a/src/geometry/contacts.rs +++ b/src/geometry/contacts.rs @@ -1,6 +1,6 @@ use crate::counters::Counters; use crate::geometry::HGrid; -use crate::math::{Point, Real, Vector}; +use crate::math::{Vector, Real}; use crate::object::Boundary; use crate::object::Fluid; @@ -258,9 +258,9 @@ fn compute_contacts_for_pair_of_cells( fluid_fluid_contacts: &[ParticlesContacts], fluid_boundary_contacts: &[ParticlesContacts], boundary_boundary_contacts: &[ParticlesContacts], - curr_cell: &Point, + curr_cell: &Vector, curr_particles: &[HGridEntry], - neighbor_cell: &Point, + neighbor_cell: &Vector, neighbor_particles: &[HGridEntry], ) { for entry in curr_particles { diff --git a/src/geometry/hgrid.rs b/src/geometry/hgrid.rs index 4abab2a..27360d0 100644 --- a/src/geometry/hgrid.rs +++ b/src/geometry/hgrid.rs @@ -1,8 +1,7 @@ use fnv::FnvHasher; - use std::collections::HashMap; -use crate::math::{Point, Real, Vector, DIM}; +use crate::math::{Vector, Real, DIM}; use std::hash::BuildHasher; @@ -20,7 +19,7 @@ impl BuildHasher for DeterministicState { /// A grid based on spacial hashing. #[derive(PartialEq, Debug, Clone)] pub struct HGrid { - cells: HashMap, Vec, DeterministicState>, + cells: HashMap, Vec, DeterministicState>, cell_width: Real, } @@ -47,8 +46,8 @@ impl HGrid { } /// Computes the logical grid cell containing `point`. - pub fn key(&self, point: &Point) -> Point { - Point::from(point.coords.map(|e| Self::quantify(e, self.cell_width))) + pub fn key(&self, point: &Vector) -> Vector { + Vector::from(point.map(|e| Self::quantify(e, self.cell_width))) } /// Removes all elements from this grid. @@ -57,7 +56,7 @@ impl HGrid { } /// Inserts the given `element` into the cell containing the given `point`. - pub fn insert(&mut self, point: &Point, element: T) { + pub fn insert(&mut self, point: &Vector, element: T) { let key = self.key(point); self.cells.entry(key).or_insert(Vec::new()).push(element) } @@ -65,7 +64,7 @@ impl HGrid { /// Returns the element attached to the cell containing the given `point`. /// /// Returns `None` if the cell is empty. - pub fn cell_containing_point(&self, point: &Point) -> Option<&Vec> { + pub fn cell_containing_point(&self, point: &Vector) -> Option<&Vec> { let key = self.key(point); self.cells.get(&key) } @@ -73,17 +72,17 @@ impl HGrid { /// An iterator through all the non-empty cells of this grid. /// /// The returned tuple include the cell indentifier, and the elements attached to this cell. - pub fn cells(&self) -> impl Iterator, &Vec)> { + pub fn cells(&self) -> impl Iterator, &Vec)> { self.cells.iter() } /// The underlying hash map of this spacial grid. - pub fn inner_table(&self) -> &HashMap, Vec, DeterministicState> { + pub fn inner_table(&self) -> &HashMap, Vec, DeterministicState> { &self.cells } /// Get the content of the logical cell identified by `key`. - pub fn cell(&self, key: &Point) -> Option<&Vec> { + pub fn cell(&self, key: &Vector) -> Option<&Vec> { self.cells.get(key) } @@ -92,9 +91,9 @@ impl HGrid { /// The given cell itself will be yielded by this iterator too. pub fn neighbor_cells( &self, - cell: &Point, + cell: &Vector, radius: Real, - ) -> impl Iterator, &Vec)> { + ) -> impl Iterator, &Vec)> { let cells = &self.cells; let quantified_radius = Self::quantify_ceil(radius, self.cell_width); @@ -104,7 +103,7 @@ impl HGrid { // pub fn elements_close_to_point<'a>( // &'a self, - // point: &Point, + // point: &Vector, // radius: Real, // ) -> impl Iterator // { @@ -121,9 +120,9 @@ impl HGrid { /// An iterator through all the cells intersecting the given AABB. pub fn cells_intersecting_aabb( &self, - mins: &Point, - maxs: &Point, - ) -> impl Iterator, &Vec)> { + mins: &Vector, + maxs: &Vector, + ) -> impl Iterator, &Vec)> { let cells = &self.cells; let start = self.key(mins); let end = self.key(maxs); @@ -132,20 +131,20 @@ impl HGrid { .filter_map(move |cell| cells.get(&cell).map(|c| (cell, c))) } - // pub fn elements_containing_point(&self, point: &Point) -> impl Iterator { + // pub fn elements_containing_point(&self, point: &Vector) -> impl Iterator { // std::iter::empty() // } } struct CellRangeIterator { - start: Point, - end: Point, - curr: Point, + start: Vector, + end: Vector, + curr: Vector, done: bool, } impl CellRangeIterator { - fn new(start: Point, end: Point) -> Self { + fn new(start: Vector, end: Vector) -> Self { Self { start, end, @@ -154,7 +153,7 @@ impl CellRangeIterator { } } - fn with_center(center: Point, radius: i64) -> Self { + fn with_center(center: Vector, radius: i64) -> Self { let start = center - Vector::repeat(radius as i64); Self { start, @@ -166,7 +165,7 @@ impl CellRangeIterator { } impl Iterator for CellRangeIterator { - type Item = Point; + type Item = Vector; fn next(&mut self) -> Option { if self.done { @@ -200,32 +199,32 @@ mod test { #[cfg(feature = "dim2")] fn grid_neighbor_iterator() { use super::CellRangeIterator; - use crate::math::Point; + use crate::math::Vector; let expected = [ - Point::new(-1, 0), - Point::new(0, 0), - Point::new(1, 0), - Point::new(2, 0), - Point::new(3, 0), - Point::new(-1, 1), - Point::new(0, 1), - Point::new(1, 1), - Point::new(2, 1), - Point::new(3, 1), - Point::new(-1, 2), - Point::new(0, 2), - Point::new(1, 2), - Point::new(2, 2), - Point::new(3, 2), - Point::new(-1, 3), - Point::new(0, 3), - Point::new(1, 3), - Point::new(2, 3), - Point::new(3, 3), + Vector::new(-1, 0), + Vector::new(0, 0), + Vector::new(1, 0), + Vector::new(2, 0), + Vector::new(3, 0), + Vector::new(-1, 1), + Vector::new(0, 1), + Vector::new(1, 1), + Vector::new(2, 1), + Vector::new(3, 1), + Vector::new(-1, 2), + Vector::new(0, 2), + Vector::new(1, 2), + Vector::new(2, 2), + Vector::new(3, 2), + Vector::new(-1, 3), + Vector::new(0, 3), + Vector::new(1, 3), + Vector::new(2, 3), + Vector::new(3, 3), ]; - let iter = CellRangeIterator::with_center(Point::new(1, 2), 2); + let iter = CellRangeIterator::with_center(Vector::new(1, 2), 2); assert!(iter.zip(expected.iter()).all(|(a, b)| a == *b)) } diff --git a/src/integrations/rapier/fluids_pipeline.rs b/src/integrations/rapier/fluids_pipeline.rs index 8ba7316..8023251 100644 --- a/src/integrations/rapier/fluids_pipeline.rs +++ b/src/integrations/rapier/fluids_pipeline.rs @@ -8,7 +8,7 @@ use approx::AbsDiffEq; use na::Unit; use rapier::dynamics::RigidBodySet; use rapier::geometry::{ColliderHandle, ColliderSet}; -use rapier::math::{Point, Vector}; +use rapier::math::{Vector, Vector}; use rapier::parry::bounding_volume::BoundingVolume; use rapier::parry::shape::FeatureId; use std::collections::HashMap; @@ -79,7 +79,7 @@ pub enum ColliderSampling { /// /// It is recommended that those points are separated by a distance smaller or equal to twice /// the particle radius used to initialize the LiquidWorld. - StaticSampling(Vec>), + StaticSampling(Vec>), /// The collider shape is approximated by a dynamic set of points automatically computed based on contacts with fluid particles. DynamicContactSampling, } diff --git a/src/integrations/rapier/testbed_plugin.rs b/src/integrations/rapier/testbed_plugin.rs index 2543d31..bd66113 100644 --- a/src/integrations/rapier/testbed_plugin.rs +++ b/src/integrations/rapier/testbed_plugin.rs @@ -1,11 +1,11 @@ -use crate::math::{Isometry, Point, Real, Rotation, Translation, Vector}; +use crate::math::{Isometry, Vector, Real, Rotation, Translation, Vector}; use crate::object::{BoundaryHandle, FluidHandle}; use bevy::math::Quat; use bevy::prelude::{Assets, Commands, Mesh, Query, Transform}; use bevy_egui::{egui::ComboBox, egui::Window, EguiContexts}; #[cfg(feature = "dim3")] use na::Quaternion; -use na::{Point3, Vector3}; +use na::{Vector3, Vector3}; use parry::shape::SharedShape; use rapier_testbed::{ harness::Harness, objects::node::EntityWithGraphics, BevyMaterial, GraphicsManager, @@ -84,9 +84,9 @@ pub struct FluidsTestbedPlugin { fluids_pipeline: FluidsPipeline, f2sn: HashMap>, boundary2sn: HashMap>, - f2color: HashMap>, - ground_color: Point3, - default_fluid_color: Point3, + f2color: HashMap>, + ground_color: Vector3, + default_fluid_color: Vector3, queue_graphics_reset: bool, } @@ -102,8 +102,8 @@ impl FluidsTestbedPlugin { f2sn: HashMap::new(), boundary2sn: HashMap::new(), f2color: HashMap::new(), - ground_color: Point3::new(0.5, 0.5, 0.5), - default_fluid_color: Point3::new(0.0, 0.0, 0.5), + ground_color: Vector3::new(0.5, 0.5, 0.5), + default_fluid_color: Vector3::new(0.0, 0.0, 0.5), queue_graphics_reset: false, } } @@ -120,7 +120,7 @@ impl FluidsTestbedPlugin { } /// Sets the color used to render the specified fluid. - pub fn set_fluid_color(&mut self, fluid: FluidHandle, color: Point3) { + pub fn set_fluid_color(&mut self, fluid: FluidHandle, color: Vector3) { let _ = self.f2color.insert(fluid, color); } @@ -137,7 +137,7 @@ impl FluidsTestbedPlugin { // TODO: pass velocity & acceleration vectors in fn add_particle_graphics( &self, - particle: &Point, + particle: &Vector, particle_radius: Real, graphics: &mut GraphicsManager, commands: &mut Commands, @@ -145,7 +145,7 @@ impl FluidsTestbedPlugin { materials: &mut Assets, _components: &mut Query<&mut Transform>, _harness: &mut Harness, - color: &Point3, + color: &Vector3, force_shape: Option, ) -> Vec { let shape = if let Some(shape) = force_shape { @@ -159,9 +159,9 @@ impl FluidsTestbedPlugin { // #[cfg(feature = "dim2")] //FIXME: This doesn't work, it is caused by either not being in prefab_meshes, or the shape_type not being supported.. somewhere // FluidsRenderingMode::VelocityArrows { .. } => SharedShape::triangle( - // Point::new(0., particle_radius), - // Point::new(particle_radius * 0.4, -particle_radius * 0.8), - // Point::new(-particle_radius * 0.4, -particle_radius * 0.8), + // Vector::new(0., particle_radius), + // Vector::new(particle_radius * 0.4, -particle_radius * 0.8), + // Vector::new(-particle_radius * 0.4, -particle_radius * 0.8), // ), _ => SharedShape::ball(particle_radius), } @@ -409,7 +409,7 @@ impl TestbedPlugin for FluidsTestbedPlugin { max, ); entity - .set_color(materials, Point3::new(lerp.x, lerp.y, lerp.z)); + .set_color(materials, Vector3::new(lerp.x, lerp.y, lerp.z)); } FluidsRenderingMode::VelocityArrows { min, max } => { let lerp = Self::lerp_velocity( @@ -419,7 +419,7 @@ impl TestbedPlugin for FluidsTestbedPlugin { max, ); entity - .set_color(materials, Point3::new(lerp.x, lerp.y, lerp.z)); + .set_color(materials, Vector3::new(lerp.x, lerp.y, lerp.z)); } // FIXME: rapier needs to be updated to respect opacity // FluidsRenderingMode::VelocityColorOpacity { min, max } => { @@ -432,7 +432,7 @@ impl TestbedPlugin for FluidsTestbedPlugin { // entity.opacity = lerp.magnitude(); // entity.set_color( // _materials, - // Point3::new(lerp.x, lerp.y, lerp.z), + // Vector3::new(lerp.x, lerp.y, lerp.z), // ); // } // FluidsRenderingMode::VelocityArrows => {} diff --git a/src/kernel/kernel.rs b/src/kernel/kernel.rs index 34d430f..34114b5 100644 --- a/src/kernel/kernel.rs +++ b/src/kernel/kernel.rs @@ -1,4 +1,4 @@ -use crate::math::{Point, Real, Vector}; +use crate::math::{Vector, Real, Vector}; use approx::AbsDiffEq; use na::Unit; @@ -24,17 +24,17 @@ pub trait Kernel: Send + Sync { } /// Evaluate the kernel for the vector equal to `p1 - p2`. - fn points_apply(p1: &Point, p2: &Point, h: Real) -> Real { + fn points_apply(p1: &Vector, p2: &Vector, h: Real) -> Real { Self::apply(p1 - p2, h) } /// Differential wrt. the coordinates of `p1`. - fn points_apply_diff1(p1: &Point, p2: &Point, h: Real) -> Vector { + fn points_apply_diff1(p1: &Vector, p2: &Vector, h: Real) -> Vector { Self::apply_diff(p1 - p2, h) } /// Differential wrt. the coordinates of `p2`. - fn points_apply_diff2(p1: &Point, p2: &Point, h: Real) -> Vector { + fn points_apply_diff2(p1: &Vector, p2: &Vector, h: Real) -> Vector { -Self::apply_diff(p1 - p2, h) } } diff --git a/src/lib.rs b/src/lib.rs index 9855383..c5c9f62 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -115,7 +115,7 @@ pub use crate::timestep_manager::TimestepManager; #[cfg(feature = "dim3")] pub mod math { use na::{ - Isometry3, Matrix3, Matrix6, Matrix6xX, MatrixView6xX, MatrixViewMut6xX, Point3, Rotation3, + Isometry3, Matrix3, Matrix6, Matrix6xX, MatrixView6xX, MatrixViewMut6xX, Vector3, Rotation3, Translation3, UnitQuaternion, Vector3, Vector6, U3, U6, }; @@ -143,7 +143,7 @@ pub mod math { pub type AngularDim = U3; /// The point type. - pub type Point = Point3; + pub type Vector = Vector3; /// The angular vector type. pub type AngularVector = Vector3; @@ -200,7 +200,7 @@ pub mod math { #[cfg(feature = "dim2")] pub mod math { use na::{ - Isometry2, Matrix1, Matrix2, Matrix3, Matrix6xX, MatrixView3xX, MatrixViewMut3xX, Point2, + Isometry2, Matrix1, Matrix2, Matrix3, Matrix6xX, MatrixView3xX, MatrixViewMut3xX, Vector2, Rotation2, RowVector2, Translation2, UnitComplex, Vector1, Vector2, Vector3, U1, U2, U3, }; @@ -228,7 +228,7 @@ pub mod math { pub type SpatialDim = U3; /// The point type. - pub type Point = Point2; + pub type Vector = Vector2; /// The vector type with dimension `SpatialDim × 1`. pub type SpatialVector = Vector3; diff --git a/src/liquid_world.rs b/src/liquid_world.rs index 28854bf..a4b9361 100644 --- a/src/liquid_world.rs +++ b/src/liquid_world.rs @@ -10,7 +10,7 @@ use crate::TimestepManager; use { crate::math::Isometry, crate::object::ParticleId, - parry::{bounding_volume::Aabb, query::PointQuery, shape::Shape}, + parry::{bounding_volume::Aabb, query::VectorQuery, shape::Shape}, }; /// The physics world for simulating fluids with boundaries. diff --git a/src/object/boundary.rs b/src/object/boundary.rs index 006bf95..b79ea65 100644 --- a/src/object/boundary.rs +++ b/src/object/boundary.rs @@ -1,4 +1,4 @@ -use crate::math::{Isometry, Point, Real, Vector}; +use crate::math::{Isometry, Vector, Real, Vector}; use crate::object::{ContiguousArena, ContiguousArenaIndex}; use std::sync::RwLock; @@ -10,7 +10,7 @@ use super::interaction_groups::InteractionGroups; /// A boundary object is composed of static particles, or of particles coupled with non-fluid bodies. pub struct Boundary { /// The world-space position of the boundary particles. - pub positions: Vec>, + pub positions: Vec>, /// The artificial velocities of each boundary particle. pub velocities: Vec>, /// The volume computed for each boundary particle. @@ -26,7 +26,7 @@ pub struct Boundary { impl Boundary { /// Initialize a boundary object with the given particles. pub fn new( - particle_positions: Vec>, + particle_positions: Vec>, interaction_groups: InteractionGroups, ) -> Self { let num_particles = particle_positions.len(); diff --git a/src/object/fluid.rs b/src/object/fluid.rs index 2140c30..e6a5c49 100644 --- a/src/object/fluid.rs +++ b/src/object/fluid.rs @@ -1,4 +1,4 @@ -use crate::math::{Isometry, Point, Real, Vector}; +use crate::math::{Isometry, Vector, Real, Vector}; use crate::object::{ContiguousArena, ContiguousArenaIndex}; use crate::solver::NonPressureForce; @@ -13,7 +13,7 @@ pub struct Fluid { /// Nonpressure forces this fluid is subject to. pub nonpressure_forces: Vec>, /// The world-space position of the fluid particles. - pub positions: Vec>, + pub positions: Vec>, /// The velocities of the fluid particles. pub velocities: Vec>, /// The accelerations of the fluid particles. @@ -38,7 +38,7 @@ impl Fluid { /// /// The particle radius should be the same as the radius used to initialize the liquid world. pub fn new( - particle_positions: Vec>, + particle_positions: Vec>, particle_radius: Real, // XXX: remove this parameter since it is already defined by the liquid world. density0: Real, interaction_groups: InteractionGroups, @@ -125,7 +125,7 @@ impl Fluid { /// If it is not `None`, then it must be a slice with the same length than `positions`. pub fn add_particles( &mut self, - positions: &[Point], + positions: &[Vector], velocities: Option<&[Vector]>, ) { let nparticles = self.positions.len() + positions.len(); diff --git a/src/sampling/ray_sampling.rs b/src/sampling/ray_sampling.rs index 91fb10f..9d8e230 100644 --- a/src/sampling/ray_sampling.rs +++ b/src/sampling/ray_sampling.rs @@ -1,4 +1,4 @@ -use crate::math::{Isometry, Point, Real, Vector, DIM}; +use crate::math::{Isometry, Vector, Real, Vector, DIM}; use parry::bounding_volume::{Aabb, BoundingVolume}; use parry::query::{Ray, RayCast}; @@ -9,7 +9,7 @@ use std::collections::HashSet; pub fn shape_surface_ray_sample( shape: &S, particle_rad: Real, -) -> Option>> { +) -> Option>> { let aabb = shape.compute_aabb(&Isometry::identity()); Some(surface_ray_sample(shape, &aabb, particle_rad)) } @@ -18,7 +18,7 @@ pub fn shape_surface_ray_sample( pub fn shape_volume_ray_sample( shape: &S, particle_rad: Real, -) -> Option>> { +) -> Option>> { let aabb = shape.compute_aabb(&Isometry::identity()); Some(volume_ray_sample(shape, &aabb, particle_rad)) } @@ -28,7 +28,7 @@ pub fn surface_ray_sample( shape: &S, volume: &Aabb, particle_rad: Real, -) -> Vec> { +) -> Vec> { let mut quantized_points = HashSet::new(); let subdivision_size = particle_rad * na::convert::<_, Real>(2.0); @@ -92,7 +92,7 @@ pub fn volume_ray_sample( shape: &S, volume: &Aabb, particle_rad: Real, -) -> Vec> { +) -> Vec> { let mut quantized_points = HashSet::new(); let subdivision_size = particle_rad * na::convert::<_, Real>(2.0); @@ -164,13 +164,13 @@ pub fn volume_ray_sample( } fn sample_segment( - origin: &Point, - start: &Point, + origin: &Vector, + start: &Vector, a: Real, b: Real, subdivision_size: Real, dimension: usize, - out: &mut HashSet>, + out: &mut HashSet>, ) { let mut quantized_pt = (start - origin).map(|e| { na::try_convert::<_, f64>(e / subdivision_size) @@ -191,10 +191,10 @@ fn sample_segment( } fn unquantize_points( - origin: &Point, + origin: &Vector, subdivision_size: Real, - quantized_points: &HashSet>, -) -> Vec> { + quantized_points: &HashSet>, +) -> Vec> { quantized_points .iter() .map(|qpt| { @@ -207,12 +207,12 @@ fn unquantize_points( } fn quantize_point( - origin: &Point, - point: &Point, + origin: &Vector, + point: &Vector, subdivision_size: Real, entry_point: bool, leading_dimension: usize, -) -> Point { +) -> Vector { let mut dpt = point - origin; for i in 0..DIM { if i == leading_dimension { diff --git a/src/solver/elasticity/becker2009_elasticity.rs b/src/solver/elasticity/becker2009_elasticity.rs index edd2953..af1e7d3 100644 --- a/src/solver/elasticity/becker2009_elasticity.rs +++ b/src/solver/elasticity/becker2009_elasticity.rs @@ -7,7 +7,7 @@ use approx::AbsDiffEq; use crate::geometry::{self, ParticlesContacts}; use crate::kernel::{CubicSplineKernel, Kernel}; -use crate::math::{Matrix, Point, Real, RotationMatrix, SpatialVector, Vector}; +use crate::math::{Matrix, Vector, Real, RotationMatrix, SpatialVector, Vector}; use crate::object::{Boundary, Fluid}; use crate::solver::NonPressureForce; use crate::TimestepManager; @@ -47,7 +47,7 @@ pub struct Becker2009Elasticity< d2: Real, nonlinear_strain: bool, volumes0: Vec, - positions0: Vec>, + positions0: Vec>, contacts0: ParticlesContacts, rotations: Vec>, deformation_gradient_tr: Vec>, diff --git a/src/z_order.rs b/src/z_order.rs index 2b4cb51..910a128 100644 --- a/src/z_order.rs +++ b/src/z_order.rs @@ -1,4 +1,4 @@ -use crate::math::{Point, Real}; +use crate::math::{Vector, Real}; use num_traits::float::FloatCore; use std::cmp::Ordering; @@ -6,7 +6,7 @@ pub fn apply_permutation(permutation: &[usize], data: &[T]) -> Vec permutation.iter().map(|i| data[*i].clone()).collect() } -pub fn compute_points_z_order(points: &[Point]) -> Vec { +pub fn compute_points_z_order(points: &[Vector]) -> Vec { let mut indices: Vec<_> = (0..points.len()).collect(); indices.sort_unstable_by(|i, j| { z_order_floats(points[*i].coords.as_slice(), points[*j].coords.as_slice()) @@ -15,7 +15,7 @@ pub fn compute_points_z_order(points: &[Point]) -> Vec { indices } -// Fast construction of k-Nearest Neighbor Graphs for Point Clouds +// Fast construction of k-Nearest Neighbor Graphs for Vector Clouds // Michael Connor, Piyush Kumar // Algorithm 1 // From b30c622534ac6c0c1010f88192b8834e95262e69 Mon Sep 17 00:00:00 2001 From: Dragos Daian Date: Thu, 16 Apr 2026 11:32:05 +0200 Subject: [PATCH 14/22] fixes to nalgebra changes, disable test bed --- Cargo.toml | 1 + build/salva2d-f64/Cargo.toml | 3 +- build/salva2d/Cargo.toml | 5 +- build/salva3d-f64/Cargo.toml | 3 +- build/salva3d/Cargo.toml | 5 +- src/counters/timer.rs | 2 +- src/geometry/contacts.rs | 10 +- src/integrations/rapier/fluids_pipeline.rs | 64 ++- src/integrations/rapier/testbed_plugin.rs | 401 ++---------------- src/kernel/kernel.rs | 2 +- src/lib.rs | 10 +- src/liquid_world.rs | 28 +- src/object/boundary.rs | 6 +- src/object/contiguous_arena.rs | 4 +- src/object/fluid.rs | 9 +- src/sampling/ray_sampling.rs | 32 +- .../elasticity/becker2009_elasticity.rs | 2 +- src/z_order.rs | 4 +- 18 files changed, 141 insertions(+), 450 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7723686..0500a19 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = ["build/salva2d", "build/salva2d-f64", "build/salva3d", "build/salva3d-f64", "examples2d", "examples3d"] +default-members = ["build/salva2d", "build/salva2d-f64", "build/salva3d", "build/salva3d-f64"] resolver = "2" [workspace.lints] diff --git a/build/salva2d-f64/Cargo.toml b/build/salva2d-f64/Cargo.toml index 3999aac..4a88134 100644 --- a/build/salva2d-f64/Cargo.toml +++ b/build/salva2d-f64/Cargo.toml @@ -46,13 +46,14 @@ itertools = "0.13" generational-arena = "0.2" rayon = { version = "1.8", optional = true } -nalgebra = "0" +nalgebra = { version = "0.34", features = ["convert-glam030"] } parry2d-f64 = { version = "0", optional = true } rapier2d-f64 = { version = "0", optional = true } # TODO update it to f64 rapier_testbed2d = { version = "0", 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 = ["bevy_winit", "bevy_render", "x11"], optional = true } diff --git a/build/salva2d/Cargo.toml b/build/salva2d/Cargo.toml index b8903e2..42b25c9 100644 --- a/build/salva2d/Cargo.toml +++ b/build/salva2d/Cargo.toml @@ -28,7 +28,7 @@ f32 = [ ] parallel = [ "rayon" ] sampling = [ "rapier" ] rapier = [ "parry", "rapier2d" ] -rapier-testbed = [ "rapier", "rapier_testbed2d", "graphics" ] +rapier-testbed = [ "rapier", "rapier_testbed2d", "graphics", "kiss3d" ] rapier-harness = [ "rapier-testbed" ] parry = [ "parry2d" ] graphics = [ "bevy", "bevy_egui" ] @@ -51,10 +51,11 @@ itertools = "0.14" generational-arena = "0.2" rayon = { version = "1.8", optional = true } -nalgebra = "0.34" +nalgebra = { version = "0.34", features = ["convert-glam030"] } parry2d = { version = "0", optional = true } rapier2d = { version = "0", optional = true } rapier_testbed2d = { version = "0", optional = true } +kiss3d = { version = "0.40", optional = true } bevy_egui = { version = "0.31", features = ["immutable_ctx"], optional = true } bitflags = "2" diff --git a/build/salva3d-f64/Cargo.toml b/build/salva3d-f64/Cargo.toml index 4b55e0e..5b07c5c 100644 --- a/build/salva3d-f64/Cargo.toml +++ b/build/salva3d-f64/Cargo.toml @@ -42,13 +42,14 @@ itertools = "0.13" generational-arena = "0.2" rayon = { version = "1.8", optional = true } -nalgebra = "0" +nalgebra = { version = "0.34", features = ["convert-glam030"] } parry3d-f64 = { version = "0", optional = true } rapier3d-f64 = { version = "0", optional = true } # TODO update it to f64 rapier_testbed3d = { version = "0", 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", default-features = false, features = ["bevy_winit", "bevy_render", "x11"], optional = true } diff --git a/build/salva3d/Cargo.toml b/build/salva3d/Cargo.toml index ae1e2bc..d53ed15 100644 --- a/build/salva3d/Cargo.toml +++ b/build/salva3d/Cargo.toml @@ -23,7 +23,7 @@ f32 = [ ] parallel = [ "rayon" ] rapier = [ "parry", "rapier3d" ] sampling = [ "rapier" ] -rapier-testbed = [ "rapier", "rapier_testbed3d", "graphics" ] +rapier-testbed = [ "rapier", "rapier_testbed3d", "graphics", "kiss3d" ] rapier-harness = [ "rapier-testbed" ] parry = [ "parry3d" ] graphics = [ "bevy", "bevy_egui" ] @@ -41,10 +41,11 @@ itertools = "0.14" generational-arena = "0.2" rayon = { version = "1.8", optional = true } -nalgebra = "0.34" +nalgebra = { version = "0.34", features = ["convert-glam030"] } parry3d = { version = "0", optional = true } rapier3d = { version = "0", optional = true } rapier_testbed3d = { version = "0", optional = true } +kiss3d = { version = "0.40", optional = true } bevy_egui = { version = "0.31", features = ["immutable_ctx"], optional = true } bitflags = "2.6.0" diff --git a/src/counters/timer.rs b/src/counters/timer.rs index 8f744ca..b2a03be 100644 --- a/src/counters/timer.rs +++ b/src/counters/timer.rs @@ -44,7 +44,7 @@ impl Timer { /// Pause the timer. pub fn pause(&mut self) { if self.enabled { - if let Some(start) = self.start { + if self.start.is_some() { //self.time += instant::now() - start; } self.start = None; diff --git a/src/geometry/contacts.rs b/src/geometry/contacts.rs index cb4ec7b..8f668f2 100644 --- a/src/geometry/contacts.rs +++ b/src/geometry/contacts.rs @@ -282,7 +282,7 @@ fn compute_contacts_for_pair_of_cells( let pi = &bi.positions[*particle_i]; let pj = &bj.positions[*particle_j]; - if na::distance_squared(pi, pj) <= h * h { + if (*pi - *pj).norm_squared() <= h * h { let contact = Contact { i_model: *boundary_i, j_model: *boundary_j, @@ -319,7 +319,7 @@ fn compute_contacts_for_pair_of_cells( let pi = &boundaries[*boundary_i].positions[*particle_i]; let pj = &fluids[*fluid_j].positions[*particle_j]; - if na::distance_squared(pi, pj) <= h * h { + if (*pi - *pj).norm_squared() <= h * h { let contact = Contact { i_model: *fluid_j, j_model: *boundary_i, @@ -363,8 +363,8 @@ fn compute_contacts_for_pair_of_cells( fluids[fluid_j].positions[particle_j] }; - if na::distance_squared(&pi, &pj) <= h * h { - assert!(na::distance_squared(&pj, &pi) <= h * h); + if (pi - pj).norm_squared() <= h * h { + assert!((pj - pi).norm_squared() <= h * h); let contact = Contact { i_model: *fluid_i, j_model: fluid_j, @@ -424,7 +424,7 @@ pub fn compute_self_contacts(h: Real, fluid: &Fluid, contacts: &mut ParticlesCon let pi = fluid.positions[*particle_i]; let pj = fluid.positions[*particle_j]; - if na::distance_squared(&pi, &pj) <= h * h { + if (pi - pj).norm_squared() <= h * h { let contact = Contact { i_model: 0, j_model: 0, diff --git a/src/integrations/rapier/fluids_pipeline.rs b/src/integrations/rapier/fluids_pipeline.rs index 8023251..916ed5d 100644 --- a/src/integrations/rapier/fluids_pipeline.rs +++ b/src/integrations/rapier/fluids_pipeline.rs @@ -8,12 +8,32 @@ use approx::AbsDiffEq; use na::Unit; use rapier::dynamics::RigidBodySet; use rapier::geometry::{ColliderHandle, ColliderSet}; -use rapier::math::{Vector, Vector}; +use rapier::math::Vector as RapierVector; use rapier::parry::bounding_volume::BoundingVolume; use rapier::parry::shape::FeatureId; use std::collections::HashMap; use std::sync::RwLock; +#[cfg(feature = "dim2")] +fn salva_to_rapier_vector(v: math::Vector) -> RapierVector { + RapierVector::new(v.x, v.y) +} + +#[cfg(feature = "dim3")] +fn salva_to_rapier_vector(v: math::Vector) -> RapierVector { + RapierVector::new(v.x, v.y, v.z) +} + +#[cfg(feature = "dim2")] +fn rapier_to_salva_vector(v: RapierVector) -> math::Vector { + math::Vector::new(v.x, v.y) +} + +#[cfg(feature = "dim3")] +fn rapier_to_salva_vector(v: RapierVector) -> math::Vector { + math::Vector::new(v.x, v.y, v.z) +} + /// Pipeline for particle-based fluid simulation. pub struct FluidsPipeline { // FIXME: keep these public? @@ -60,14 +80,15 @@ impl FluidsPipeline { /// However, it will not integrate these forces. Use the `PhysicsPipeline` for this integration. pub fn step( &mut self, - gravity: &Vector, + gravity: &RapierVector, dt: math::Real, colliders: &ColliderSet, bodies: &mut RigidBodySet, ) { + let gravity = rapier_to_salva_vector(*gravity); self.liquid_world.step_with_coupling( dt, - gravity, + &gravity, &mut self.coupling.as_manager_mut(colliders, bodies), ) } @@ -79,7 +100,7 @@ pub enum ColliderSampling { /// /// It is recommended that those points are separated by a distance smaller or equal to twice /// the particle radius used to initialize the LiquidWorld. - StaticSampling(Vec>), + StaticSampling(Vec>), /// The collider shape is approximated by a dynamic set of points automatically computed based on contacts with fluid particles. DynamicContactSampling, } @@ -192,12 +213,16 @@ impl<'a> CouplingManager for ColliderCouplingManager<'a> { match &coupling.sampling_method { ColliderSampling::StaticSampling(points) => { for pt in points { - boundary.positions.push(collider.position() * pt); - let velocity = body.map(|b| b.velocity_at_point(pt)); + let rapier_pt = salva_to_rapier_vector(*pt); + let world_pt = rapier_to_salva_vector(collider.position() * rapier_pt); + boundary.positions.push(world_pt); + let velocity = body.map(|b| b.velocity_at_point(rapier_pt)); boundary .velocities - .push(velocity.unwrap_or(Vector::zeros())); + .push(rapier_to_salva_vector( + velocity.unwrap_or(RapierVector::ZERO), + )); } boundary.volumes.resize(points.len(), na::zero::()); @@ -210,9 +235,11 @@ impl<'a> CouplingManager for ColliderCouplingManager<'a> { .shape() .compute_aabb(&collider_pos) .loosened(h + prediction); + let mins = rapier_to_salva_vector(aabb.mins); + let maxs = rapier_to_salva_vector(aabb.maxs); for particle in hgrid - .cells_intersecting_aabb(&aabb.mins, &aabb.maxs) + .cells_intersecting_aabb(&mins, &maxs) .flat_map(|e| e.1) { match particle { @@ -231,15 +258,16 @@ impl<'a> CouplingManager for ColliderCouplingManager<'a> { let particle_pos = fluid.positions[*particle_id] + fluid.velocities[*particle_id] * timestep.dt(); + let rapier_particle_pos = salva_to_rapier_vector(particle_pos); - if aabb.contains_local_point(&particle_pos) { + if aabb.contains_local_point(rapier_particle_pos) { let (proj, feature) = collider.shape().project_point_and_get_feature( &collider_pos, - &particle_pos, + rapier_particle_pos, ); - let dpt = particle_pos - proj.point; + let dpt = particle_pos - rapier_to_salva_vector(proj.point); if let Some((normal, depth)) = Unit::try_new_and_get(dpt, math::Real::default_epsilon()) @@ -261,12 +289,14 @@ impl<'a> CouplingManager for ColliderCouplingManager<'a> { } let velocity = - body.map(|b| b.velocity_at_point(&proj.point)); + body.map(|b| b.velocity_at_point(proj.point)); boundary .velocities - .push(velocity.unwrap_or(Vector::zeros())); - boundary.positions.push(proj.point); + .push(rapier_to_salva_vector( + velocity.unwrap_or(RapierVector::ZERO), + )); + boundary.positions.push(rapier_to_salva_vector(proj.point)); boundary.volumes.push(na::zero::()); coupling.features.push(feature); } @@ -301,7 +331,11 @@ impl<'a> CouplingManager for ColliderCouplingManager<'a> { for (pos, force) in boundary.positions.iter().zip(forces.iter().cloned()) { - body.apply_impulse_at_point(force * timestep.dt() * force_coefficient, *pos, true) + body.apply_impulse_at_point( + salva_to_rapier_vector(force * timestep.dt() * force_coefficient), + salva_to_rapier_vector(*pos), + true, + ) } } } diff --git a/src/integrations/rapier/testbed_plugin.rs b/src/integrations/rapier/testbed_plugin.rs index bd66113..7b4df17 100644 --- a/src/integrations/rapier/testbed_plugin.rs +++ b/src/integrations/rapier/testbed_plugin.rs @@ -1,21 +1,12 @@ -use crate::math::{Isometry, Vector, Real, Rotation, Translation, Vector}; +use crate::math::Real; use crate::object::{BoundaryHandle, FluidHandle}; -use bevy::math::Quat; -use bevy::prelude::{Assets, Commands, Mesh, Query, Transform}; -use bevy_egui::{egui::ComboBox, egui::Window, EguiContexts}; -#[cfg(feature = "dim3")] -use na::Quaternion; -use na::{Vector3, Vector3}; -use parry::shape::SharedShape; -use rapier_testbed::{ - harness::Harness, objects::node::EntityWithGraphics, BevyMaterial, GraphicsManager, - PhysicsState, TestbedPlugin, -}; +use na::Vector3; +use rapier_testbed::{egui, harness::Harness, GraphicsManager, PhysicsState, TestbedPlugin}; +use kiss3d::window::Window; use crate::integrations::rapier::FluidsPipeline; use std::collections::HashMap; -//FIXME: handle this with macros, or use bevy-inspectable-egui pub const FLUIDS_RENDERING_MAP: [(&str, FluidsRenderingMode); 3] = [ ("Static", FluidsRenderingMode::StaticColor), ( @@ -25,13 +16,6 @@ pub const FLUIDS_RENDERING_MAP: [(&str, FluidsRenderingMode); 3] = [ max: 50.0, }, ), - // ( - // "Velocity Color & Opacity", - // FluidsRenderingMode::VelocityColorOpacity { - // min: 0.0, - // max: 50.0, - // }, - // ), ( "Velocity Arrows", FluidsRenderingMode::VelocityArrows { @@ -39,7 +23,6 @@ pub const FLUIDS_RENDERING_MAP: [(&str, FluidsRenderingMode); 3] = [ max: 50.0, }, ), - // ("Acceleration Arrows", FluidsRenderingMode::AccelerationArrows), ]; /// How the fluids should be rendered by the testbed. @@ -54,14 +37,7 @@ pub enum FluidsRenderingMode { /// Fluids with a velocity greater than this will be completely red. max: Real, }, - // /// Use a red taint the closer to `max` the velocity is, with opacity, low velocity is more transparent - // VelocityColorOpacity { - // /// Fluids with a velocity smaller than this will not have any red taint. - // min: Real, - // /// Fluids with a velocity greater than this will be completely red. - // max: Real, - // }, - /// Show particles as arrows indicating the velocity + /// Show particles as arrows indicating the velocity. VelocityArrows { /// Fluids with a velocity smaller than this will not have any red taint. min: Real, @@ -73,21 +49,21 @@ pub enum FluidsRenderingMode { /// A user-defined callback executed at each frame. pub type FluidCallback = Box; -/// A plugin for rendering fluids with the Rapier testbed. +/// A plugin for stepping fluids inside the Rapier testbed. pub struct FluidsTestbedPlugin { - /// Whether to render the boundary particles + /// Whether to render the boundary particles. pub render_boundary_particles: bool, - /// Rendering mode of fluid particles + /// Rendering mode of fluid particles. pub fluids_rendering_mode: FluidsRenderingMode, callbacks: Vec, step_time: f64, fluids_pipeline: FluidsPipeline, - f2sn: HashMap>, - boundary2sn: HashMap>, + #[allow(dead_code)] f2color: HashMap>, - ground_color: Vector3, + #[allow(dead_code)] + boundary2color: HashMap>, + #[allow(dead_code)] default_fluid_color: Vector3, - queue_graphics_reset: bool, } impl FluidsTestbedPlugin { @@ -99,12 +75,9 @@ impl FluidsTestbedPlugin { step_time: 0.0, callbacks: Vec::new(), fluids_pipeline: FluidsPipeline::new(0.025, 2.0), - f2sn: HashMap::new(), - boundary2sn: HashMap::new(), f2color: HashMap::new(), - ground_color: Vector3::new(0.5, 0.5, 0.5), + boundary2color: HashMap::new(), default_fluid_color: Vector3::new(0.0, 0.0, 0.5), - queue_graphics_reset: false, } } @@ -133,204 +106,28 @@ impl FluidsTestbedPlugin { pub fn enable_boundary_particles_rendering(&mut self, enabled: bool) { self.render_boundary_particles = enabled; } - - // TODO: pass velocity & acceleration vectors in - fn add_particle_graphics( - &self, - particle: &Vector, - particle_radius: Real, - graphics: &mut GraphicsManager, - commands: &mut Commands, - meshes: &mut Assets, - materials: &mut Assets, - _components: &mut Query<&mut Transform>, - _harness: &mut Harness, - color: &Vector3, - force_shape: Option, - ) -> Vec { - let shape = if let Some(shape) = force_shape { - shape - } else { - match self.fluids_rendering_mode { - #[cfg(feature = "dim3")] - FluidsRenderingMode::VelocityArrows { .. } => { - SharedShape::cone(particle_radius, particle_radius / 4.) - } - // #[cfg(feature = "dim2")] - //FIXME: This doesn't work, it is caused by either not being in prefab_meshes, or the shape_type not being supported.. somewhere - // FluidsRenderingMode::VelocityArrows { .. } => SharedShape::triangle( - // Vector::new(0., particle_radius), - // Vector::new(particle_radius * 0.4, -particle_radius * 0.8), - // Vector::new(-particle_radius * 0.4, -particle_radius * 0.8), - // ), - _ => SharedShape::ball(particle_radius), - } - }; - - let mut shapes = Vec::new(); - let isometry = - Isometry::from_parts(Translation::from(particle.coords), Rotation::identity()); - graphics.add_shape( - commands, - meshes, - materials, - None, - &*shape, - false, - &isometry, - &Isometry::identity(), - *color, - &mut shapes, - ); - shapes - } - - fn lerp_velocity( - velocity: Vector, - start: Vector3, - min: Real, - max: Real, - ) -> Vector3 { - let end = Vector3::new(1.0, 0.0, 0.0); - let vel: Vector = na::convert_unchecked(velocity); - let vel: Vector = na::convert(vel); - let t = (vel.norm() - min) / (max - min); - start.lerp(&end, na::clamp(t, 0.0, 1.0)) - } } impl TestbedPlugin for FluidsTestbedPlugin { - fn init_plugin(&mut self) { - // TODO: decide if anything needs to be changed - } + fn init_plugin(&mut self) {} fn init_graphics( &mut self, - graphics: &mut GraphicsManager, - commands: &mut Commands, - meshes: &mut Assets, - materials: &mut Assets, - components: &mut Query<&mut Transform>, - harness: &mut Harness, + _graphics: &mut GraphicsManager, + _window: &mut Window, + _harness: &mut Harness, ) { - for (handle, fluid) in self.fluids_pipeline.liquid_world.fluids().iter() { - let _ = self - .f2sn - .insert(handle, Vec::with_capacity(fluid.positions.len())); - - let color = *self - .f2color - .entry(handle) - .or_insert_with(|| self.default_fluid_color); - - for particle in &fluid.positions { - let ent = self.add_particle_graphics( - particle, - fluid.particle_radius(), - graphics, - commands, - meshes, - materials, - components, - harness, - &color, - None, - ); - if let Some(entities) = self.f2sn.get_mut(&handle) { - entities.extend(ent); - } - } - } - - let particle_radius = self.fluids_pipeline.liquid_world.particle_radius(); - - // FIXME: There is currently no way to get the collider pose from this function - if self.render_boundary_particles { - for (handle, boundary) in self.fluids_pipeline.liquid_world.boundaries().iter() { - let _ = self - .boundary2sn - .insert(handle, Vec::with_capacity(boundary.num_particles())); - let color = self.ground_color; - - for (_, cce) in &self.fluids_pipeline.coupling.entries { - if cce.boundary == handle { - match &cce.sampling_method { - crate::integrations::rapier::ColliderSampling::StaticSampling(particles) => { - for particle in particles { - let ent = self.add_particle_graphics( - particle, - particle_radius, - graphics, - commands, - meshes, - materials, - components, - harness, - &color, - Some(SharedShape::ball(particle_radius)) - ); - if let Some(entities) = self.boundary2sn.get_mut(&handle) { - entities.extend(ent); - } - } - }, - crate::integrations::rapier::ColliderSampling::DynamicContactSampling => { - // TODO: ??? - }, - } - } - } - } - } } - fn clear_graphics(&mut self, _graphics: &mut GraphicsManager, commands: &mut Commands) { - for (handle, _) in self.fluids_pipeline.liquid_world.fluids().iter() { - if let Some(entities) = self.f2sn.get_mut(&handle) { - for entity in entities { - entity.despawn(commands); - } - } - } - - for (handle, _) in self.fluids_pipeline.liquid_world.boundaries().iter() { - if let Some(entities) = self.boundary2sn.get_mut(&handle) { - for entity in entities { - entity.despawn(commands); - } - } - } - - self.f2sn.clear(); - self.boundary2sn.clear(); - } + fn clear_graphics(&mut self, _graphics: &mut GraphicsManager, _window: &mut Window) {} fn run_callbacks(&mut self, harness: &mut Harness) { - // FIXME: salva should be able to keep a list of indices that were added & removed in this step - // at the moment we just clear & initialize the grahics when fluids_lengths changes - let fluid_lengths: Vec<(FluidHandle, usize)> = self - .fluids_pipeline - .liquid_world - .fluids() - .iter() - .map(|(h, f)| (h, f.positions.len())) - .collect(); - for f in &mut self.callbacks { f(harness, &mut self.fluids_pipeline) } - - for (h, fl) in fluid_lengths { - if let Some(fluid) = self.fluids_pipeline.liquid_world.fluids().get(h) { - if fluid.positions.len() != fl || fluid.num_deleted_particles() > 0 { - self.queue_graphics_reset = true; - } - } - } } fn step(&mut self, physics: &mut PhysicsState) { - //let step_time = instant::now(); let dt = physics.integration_parameters.dt; self.fluids_pipeline.step( &physics.gravity, @@ -338,171 +135,23 @@ impl TestbedPlugin for FluidsTestbedPlugin { &physics.colliders, &mut physics.bodies, ); - - //self.step_time = instant::now() - step_time; } fn draw( &mut self, - graphics: &mut GraphicsManager, - commands: &mut Commands, - meshes: &mut Assets, - materials: &mut Assets, - components: &mut Query<&mut Transform>, - harness: &mut Harness, + _graphics: &mut GraphicsManager, + _window: &mut Window, + _harness: &mut Harness, ) { - if self.queue_graphics_reset { - self.clear_graphics(graphics, commands); - self.init_graphics(graphics, commands, meshes, materials, components, harness); - self.queue_graphics_reset = false; - } - - let (mut min, mut max) = (Real::MAX, Real::MIN); - for (handle, fluid) in self.fluids_pipeline.liquid_world.fluids().iter() { - if let Some(entities) = self.f2sn.get_mut(&handle) { - for (idx, particle) in fluid.positions.iter().enumerate() { - let velocity = Vector::from(fluid.velocities[idx]); - let magnitude = velocity.magnitude(); - min = min.min(magnitude); - max = max.max(magnitude); - if let Some(entity) = entities.get_mut(idx) { - if let Ok(mut pos) = components.get_mut(entity.entity) { - { - pos.translation.x = particle.x; - pos.translation.y = particle.y; - #[cfg(feature = "dim3")] - { - pos.translation.z = particle.z; - - if let FluidsRenderingMode::VelocityArrows { .. } = - self.fluids_rendering_mode - { - let cone_paxis: Quaternion = - Quaternion::from_vector(-Vector3::y().to_homogeneous()); - let vr = Quaternion::from_vector( - velocity.normalize().to_homogeneous(), - ); - let rotation = (vr - cone_paxis).normalize(); - - pos.rotation = Quat::from_xyzw( - rotation.i, rotation.j, rotation.k, rotation.w, - ); - } - } - #[cfg(feature = "dim2")] - { - let norm = velocity.normalize(); - let hyp = (norm.x * norm.x + norm.y * norm.y).sqrt(); - let angle = 2. * (norm.y / (norm.x + hyp)).atan(); - pos.rotation = Quat::from_rotation_z(angle); - } - } - } - - if let Some(color) = self.f2color.get(&handle) { - match self.fluids_rendering_mode { - FluidsRenderingMode::VelocityColor { min, max } => { - let lerp = Self::lerp_velocity( - fluid.velocities[idx], - color.coords, - min, - max, - ); - entity - .set_color(materials, Vector3::new(lerp.x, lerp.y, lerp.z)); - } - FluidsRenderingMode::VelocityArrows { min, max } => { - let lerp = Self::lerp_velocity( - fluid.velocities[idx], - color.coords, - min, - max, - ); - entity - .set_color(materials, Vector3::new(lerp.x, lerp.y, lerp.z)); - } - // FIXME: rapier needs to be updated to respect opacity - // FluidsRenderingMode::VelocityColorOpacity { min, max } => { - // let lerp = lerp_velocity( - // fluid.velocities[idx], - // color.coords, - // min, - // max, - // ); - // entity.opacity = lerp.magnitude(); - // entity.set_color( - // _materials, - // Vector3::new(lerp.x, lerp.y, lerp.z), - // ); - // } - // FluidsRenderingMode::VelocityArrows => {} - _ => { - entity.opacity = 1.0; - entity.set_color(materials, *color); - } - } - } - entity.update(&harness.physics.colliders, components, &graphics.gfx_shift) - } - } - } - } } fn update_ui( &mut self, - ui_context: &EguiContexts, - harness: &mut Harness, - graphics: &mut GraphicsManager, - commands: &mut Commands, - meshes: &mut Assets, - materials: &mut Assets, - components: &mut Query<&mut Transform>, + _ui_context: &egui::Context, + _harness: &mut Harness, + _graphics: &mut GraphicsManager, + _window: &mut Window, ) { - fn get_rendering_mode_index(rendering_mode: FluidsRenderingMode) -> usize { - FLUIDS_RENDERING_MAP - .iter() - .enumerate() - .find(|(_, mode)| rendering_mode == mode.1) - .map(|(idx, _)| idx) - .unwrap_or(0) - } - - let _ = Window::new("Fluid Parameters") - .min_height(200.0) - .show(ui_context.ctx(), |ui| { - let mut changed = false; - - let _ = ComboBox::from_label("Rendering Mode") - .width(150.0) - .selected_text( - FLUIDS_RENDERING_MAP[get_rendering_mode_index(self.fluids_rendering_mode)] - .0, - ) - .show_ui(ui, |ui| { - for (_, (name, mode)) in FLUIDS_RENDERING_MAP.iter().enumerate() { - changed = ui - .selectable_value(&mut self.fluids_rendering_mode, *mode, *name) - .changed() - || changed; - } - }); - - if changed { - // FIXME: not too sure what to do here for color - // let fluid_handle = self - // .fluids_pipeline - // .liquid_world - // .fluids() - // .iter() - // .next() - // .unwrap() - // .0; - self.clear_graphics(graphics, commands); - // let color = self.f2color[&fluid_handle].clone(); - self.init_graphics(graphics, commands, meshes, materials, components, harness) - } - }); } fn profiling_string(&self) -> String { diff --git a/src/kernel/kernel.rs b/src/kernel/kernel.rs index 34114b5..486597b 100644 --- a/src/kernel/kernel.rs +++ b/src/kernel/kernel.rs @@ -1,4 +1,4 @@ -use crate::math::{Vector, Real, Vector}; +use crate::math::{Real, Vector}; use approx::AbsDiffEq; use na::Unit; diff --git a/src/lib.rs b/src/lib.rs index c5c9f62..7a44d56 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -115,7 +115,7 @@ pub use crate::timestep_manager::TimestepManager; #[cfg(feature = "dim3")] pub mod math { use na::{ - Isometry3, Matrix3, Matrix6, Matrix6xX, MatrixView6xX, MatrixViewMut6xX, Vector3, Rotation3, + Isometry3, Matrix3, Matrix6, Matrix6xX, MatrixView6xX, MatrixViewMut6xX, Rotation3, Translation3, UnitQuaternion, Vector3, Vector6, U3, U6, }; @@ -142,9 +142,6 @@ pub mod math { /// The dimension of the rotations. pub type AngularDim = U3; - /// The point type. - pub type Vector = Vector3; - /// The angular vector type. pub type AngularVector = Vector3; @@ -200,7 +197,7 @@ pub mod math { #[cfg(feature = "dim2")] pub mod math { use na::{ - Isometry2, Matrix1, Matrix2, Matrix3, Matrix6xX, MatrixView3xX, MatrixViewMut3xX, Vector2, + Isometry2, Matrix1, Matrix2, Matrix3, Matrix6xX, MatrixView3xX, MatrixViewMut3xX, Rotation2, RowVector2, Translation2, UnitComplex, Vector1, Vector2, Vector3, U1, U2, U3, }; @@ -227,9 +224,6 @@ pub mod math { /// The dimension of a spatial vector. pub type SpatialDim = U3; - /// The point type. - pub type Vector = Vector2; - /// The vector type with dimension `SpatialDim × 1`. pub type SpatialVector = Vector3; diff --git a/src/liquid_world.rs b/src/liquid_world.rs index a4b9361..f3df3d9 100644 --- a/src/liquid_world.rs +++ b/src/liquid_world.rs @@ -10,7 +10,7 @@ use crate::TimestepManager; use { crate::math::Isometry, crate::object::ParticleId, - parry::{bounding_volume::Aabb, query::VectorQuery, shape::Shape}, + parry::{bounding_volume::Aabb, query::PointQuery, shape::Shape}, }; /// The physics world for simulating fluids with boundaries. @@ -221,8 +221,10 @@ impl LiquidWorld { &'a self, aabb: Aabb, ) -> impl Iterator + 'a { + let mins = aabb.mins.into(); + let maxs = aabb.maxs.into(); self.hgrid - .cells_intersecting_aabb(&aabb.mins, &aabb.maxs) + .cells_intersecting_aabb(&mins, &maxs) .flat_map(|e| e.1) .filter_map(move |entry| match entry { HGridEntry::FluidParticle(fid, pid) => { @@ -235,9 +237,7 @@ impl LiquidWorld { let pt = fluid.positions[*pid]; - // FIXME: use `distance_to_local_point` once it's supported. - let id = &Isometry::identity(); - if aabb.distance_to_point(id, &pt, true) < self.particle_radius { + if aabb.distance_to_local_point(pt.into(), true) < self.particle_radius { Some(ParticleId::FluidParticle(handle, *pid)) } else { None @@ -255,9 +255,8 @@ impl LiquidWorld { } // --- END FIX --- - let pt = boundary.positions[*pid]; // FIXME: use `distance_to_local_point` once it's supported. - let id = &Isometry::identity(); - if aabb.distance_to_point(id, &pt, true) < self.particle_radius { + let pt = boundary.positions[*pid]; + if aabb.distance_to_local_point(pt.into(), true) < self.particle_radius { Some(ParticleId::BoundaryParticle(handle, *pid)) } else { None @@ -276,9 +275,12 @@ impl LiquidWorld { where S: Shape, { - let aabb = shape.compute_aabb(pos); + let pos = (*pos).into(); + let aabb = shape.compute_aabb(&pos); + let mins = aabb.mins.into(); + let maxs = aabb.maxs.into(); self.hgrid - .cells_intersecting_aabb(&aabb.mins, &aabb.maxs) + .cells_intersecting_aabb(&mins, &maxs) .flat_map(|e| e.1) .filter_map(move |entry| match entry { HGridEntry::FluidParticle(fid, pid) => { @@ -291,7 +293,7 @@ impl LiquidWorld { let pt = fluid.positions[*pid]; - if shape.distance_to_point(pos, &pt, true) <= self.particle_radius { + if shape.distance_to_point(&pos, pt.into(), true) <= self.particle_radius { Some(ParticleId::FluidParticle(handle, *pid)) } else { None @@ -306,8 +308,8 @@ impl LiquidWorld { } // --- END FIX --- - let pt = boundary.positions[*pid]; // FIXME: use `distance_to_local_point` once it's supported. - if shape.distance_to_point(pos, &pt, true) <= self.particle_radius { + let pt = boundary.positions[*pid]; + if shape.distance_to_point(&pos, pt.into(), true) <= self.particle_radius { Some(ParticleId::BoundaryParticle(handle, *pid)) } else { None diff --git a/src/object/boundary.rs b/src/object/boundary.rs index b79ea65..4a41d9f 100644 --- a/src/object/boundary.rs +++ b/src/object/boundary.rs @@ -1,4 +1,4 @@ -use crate::math::{Isometry, Vector, Real, Vector}; +use crate::math::{Isometry, Real, Vector}; use crate::object::{ContiguousArena, ContiguousArenaIndex}; use std::sync::RwLock; @@ -53,7 +53,9 @@ impl Boundary { /// Transforms all the particle positions of this boundary by the given isometry. pub fn transform_by(&mut self, pose: &Isometry) { - self.positions.iter_mut().for_each(|p| *p = pose * *p); + self.positions + .iter_mut() + .for_each(|p| *p = pose.translation.vector + pose.rotation * *p); } /// Apply a force `f` to the `i`-th particle of this boundary object. diff --git a/src/object/contiguous_arena.rs b/src/object/contiguous_arena.rs index e7f664f..2fa8f96 100644 --- a/src/object/contiguous_arena.rs +++ b/src/object/contiguous_arena.rs @@ -66,13 +66,13 @@ impl ContiguousArena { #[inline] /// Gets references to all the objects on this set. - pub fn values(&self) -> std::slice::Iter { + pub fn values(&self) -> std::slice::Iter<'_, T> { self.objects.iter() } #[inline] /// Gets mutable references to all the objects on this set. - pub fn values_mut(&mut self) -> std::slice::IterMut { + pub fn values_mut(&mut self) -> std::slice::IterMut<'_, T> { self.objects.iter_mut() } diff --git a/src/object/fluid.rs b/src/object/fluid.rs index e6a5c49..dea00fb 100644 --- a/src/object/fluid.rs +++ b/src/object/fluid.rs @@ -1,4 +1,4 @@ -use crate::math::{Isometry, Vector, Real, Vector}; +use crate::math::{Isometry, Real, Vector}; use crate::object::{ContiguousArena, ContiguousArenaIndex}; use crate::solver::NonPressureForce; @@ -164,7 +164,9 @@ impl Fluid { /// Apply the given transformation to each particle of this fluid. pub fn transform_by(&mut self, t: &Isometry) { - self.positions.iter_mut().for_each(|p| *p = t * *p) + self.positions + .iter_mut() + .for_each(|p| *p = t.translation.vector + t.rotation * *p) } /// The number of particles on this fluid. @@ -176,7 +178,8 @@ impl Fluid { #[cfg(feature = "parry")] pub fn compute_aabb(&self, particle_radius: Real) -> parry::bounding_volume::Aabb { use parry::bounding_volume::{details::local_point_cloud_aabb, BoundingVolume}; - local_point_cloud_aabb(self.positions.iter().copied()).loosened(particle_radius) + local_point_cloud_aabb(self.positions.iter().copied().map(Into::into)) + .loosened(particle_radius) } /// The mass of the `i`-th particle of this fluid. diff --git a/src/sampling/ray_sampling.rs b/src/sampling/ray_sampling.rs index 9d8e230..df4cfb0 100644 --- a/src/sampling/ray_sampling.rs +++ b/src/sampling/ray_sampling.rs @@ -1,4 +1,4 @@ -use crate::math::{Isometry, Vector, Real, Vector, DIM}; +use crate::math::{Real, Vector, DIM}; use parry::bounding_volume::{Aabb, BoundingVolume}; use parry::query::{Ray, RayCast}; @@ -10,7 +10,7 @@ pub fn shape_surface_ray_sample( shape: &S, particle_rad: Real, ) -> Option>> { - let aabb = shape.compute_aabb(&Isometry::identity()); + let aabb = shape.compute_aabb(&parry::math::Pose::IDENTITY); Some(surface_ray_sample(shape, &aabb, particle_rad)) } @@ -19,7 +19,7 @@ pub fn shape_volume_ray_sample( shape: &S, particle_rad: Real, ) -> Option>> { - let aabb = shape.compute_aabb(&Isometry::identity()); + let aabb = shape.compute_aabb(&parry::math::Pose::IDENTITY); Some(volume_ray_sample(shape, &aabb, particle_rad)) } @@ -33,18 +33,20 @@ pub fn surface_ray_sample( let subdivision_size = particle_rad * na::convert::<_, Real>(2.0); let volume = volume.loosened(subdivision_size); - let maxs = volume.maxs; - let origin = volume.mins + Vector::repeat(subdivision_size / na::convert::<_, Real>(2.0)); + let maxs: Vector = volume.maxs.into(); + let origin: Vector = (volume.mins + + parry::math::Vector::splat(subdivision_size / na::convert::<_, Real>(2.0))) + .into(); let mut curr = origin; - let mut perform_cast = |i, curr| { + let mut perform_cast = |i, curr: Vector| { let mut dir = Vector::zeros(); dir[i] = na::one::(); - let mut ray = Ray::new(curr, dir); + let mut ray = Ray::new(curr.into(), dir.into()); let mut entry_point = true; while let Some(toi) = shape.cast_local_ray(&ray, Real::MAX, false) { - let impact = ray.point_at(toi); + let impact: Vector = ray.point_at(toi).into(); let quantized_pt = quantize_point(&origin, &impact, subdivision_size, entry_point, i); let _ = quantized_points.insert(quantized_pt); ray.origin[i] += toi + subdivision_size / na::convert::<_, Real>(10.0); @@ -97,13 +99,15 @@ pub fn volume_ray_sample( let subdivision_size = particle_rad * na::convert::<_, Real>(2.0); let volume = volume.loosened(subdivision_size); - let maxs = volume.maxs; - let origin = volume.mins + Vector::repeat(subdivision_size / na::convert::<_, Real>(2.0)); + let maxs: Vector = volume.maxs.into(); + let origin: Vector = (volume.mins + + parry::math::Vector::splat(subdivision_size / na::convert::<_, Real>(2.0))) + .into(); - let mut perform_cast = |i, curr| { + let mut perform_cast = |i, curr: Vector| { let mut dir = Vector::zeros(); dir[i] = na::one::(); - let mut ray = Ray::new(curr, dir); + let mut ray = Ray::new(curr.into(), dir.into()); let mut prev_impact = None; while let Some(toi) = shape.cast_local_ray(&ray, Real::MAX, false) { @@ -199,9 +203,7 @@ fn unquantize_points( .iter() .map(|qpt| { origin - + qpt - .coords - .map(|e| na::convert::<_, Real>(e as f64) * subdivision_size) + + qpt.map(|e| na::convert::<_, Real>(e as f64) * subdivision_size) }) .collect() } diff --git a/src/solver/elasticity/becker2009_elasticity.rs b/src/solver/elasticity/becker2009_elasticity.rs index af1e7d3..b89af44 100644 --- a/src/solver/elasticity/becker2009_elasticity.rs +++ b/src/solver/elasticity/becker2009_elasticity.rs @@ -7,7 +7,7 @@ use approx::AbsDiffEq; use crate::geometry::{self, ParticlesContacts}; use crate::kernel::{CubicSplineKernel, Kernel}; -use crate::math::{Matrix, Vector, Real, RotationMatrix, SpatialVector, Vector}; +use crate::math::{Matrix, Real, RotationMatrix, SpatialVector, Vector}; use crate::object::{Boundary, Fluid}; use crate::solver::NonPressureForce; use crate::TimestepManager; diff --git a/src/z_order.rs b/src/z_order.rs index 910a128..ed6d853 100644 --- a/src/z_order.rs +++ b/src/z_order.rs @@ -1,4 +1,4 @@ -use crate::math::{Vector, Real}; +use crate::math::{Real, Vector}; use num_traits::float::FloatCore; use std::cmp::Ordering; @@ -9,7 +9,7 @@ pub fn apply_permutation(permutation: &[usize], data: &[T]) -> Vec pub fn compute_points_z_order(points: &[Vector]) -> Vec { let mut indices: Vec<_> = (0..points.len()).collect(); indices.sort_unstable_by(|i, j| { - z_order_floats(points[*i].coords.as_slice(), points[*j].coords.as_slice()) + z_order_floats(points[*i].as_slice(), points[*j].as_slice()) .unwrap_or(Ordering::Equal) }); indices From 877f5d8f4d2354cc3c233181ef3ea4b992643d59 Mon Sep 17 00:00:00 2001 From: Dragos Daian Date: Sat, 30 May 2026 21:21:23 +0200 Subject: [PATCH 15/22] upd to latest nalgebra --- build/salva2d-f64/Cargo.toml | 3 ++- build/salva2d/Cargo.toml | 3 ++- build/salva3d-f64/Cargo.toml | 3 ++- build/salva3d/Cargo.toml | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/build/salva2d-f64/Cargo.toml b/build/salva2d-f64/Cargo.toml index 4a88134..7d9a7d0 100644 --- a/build/salva2d-f64/Cargo.toml +++ b/build/salva2d-f64/Cargo.toml @@ -46,7 +46,8 @@ itertools = "0.13" generational-arena = "0.2" rayon = { version = "1.8", optional = true } -nalgebra = { version = "0.34", features = ["convert-glam030"] } +nalgebra = { version = "0.35", features = ["convert-glam033"] } +glamx = "0.3" parry2d-f64 = { version = "0", optional = true } rapier2d-f64 = { version = "0", optional = true } # TODO update it to f64 diff --git a/build/salva2d/Cargo.toml b/build/salva2d/Cargo.toml index 42b25c9..4cda5d8 100644 --- a/build/salva2d/Cargo.toml +++ b/build/salva2d/Cargo.toml @@ -51,7 +51,8 @@ itertools = "0.14" generational-arena = "0.2" rayon = { version = "1.8", optional = true } -nalgebra = { version = "0.34", features = ["convert-glam030"] } +nalgebra = { version = "0.35", features = ["convert-glam033"] } +glamx = "0.3" parry2d = { version = "0", optional = true } rapier2d = { version = "0", optional = true } rapier_testbed2d = { version = "0", optional = true } diff --git a/build/salva3d-f64/Cargo.toml b/build/salva3d-f64/Cargo.toml index 5b07c5c..6d8aedf 100644 --- a/build/salva3d-f64/Cargo.toml +++ b/build/salva3d-f64/Cargo.toml @@ -42,7 +42,8 @@ itertools = "0.13" generational-arena = "0.2" rayon = { version = "1.8", optional = true } -nalgebra = { version = "0.34", features = ["convert-glam030"] } +nalgebra = { version = "0.35", features = ["convert-glam033"] } +glamx = "0.3" parry3d-f64 = { version = "0", optional = true } rapier3d-f64 = { version = "0", optional = true } # TODO update it to f64 diff --git a/build/salva3d/Cargo.toml b/build/salva3d/Cargo.toml index d53ed15..765b1ad 100644 --- a/build/salva3d/Cargo.toml +++ b/build/salva3d/Cargo.toml @@ -41,7 +41,8 @@ itertools = "0.14" generational-arena = "0.2" rayon = { version = "1.8", optional = true } -nalgebra = { version = "0.34", features = ["convert-glam030"] } +nalgebra = { version = "0.35", features = ["convert-glam033"] } +glamx = "0.3" parry3d = { version = "0", optional = true } rapier3d = { version = "0", optional = true } rapier_testbed3d = { version = "0", optional = true } From 12105d67077d496f902e0ffc44e504821b6239b4 Mon Sep 17 00:00:00 2001 From: Dragos Daian Date: Sat, 20 Jun 2026 18:01:42 +0200 Subject: [PATCH 16/22] update small renames/changes --- src/liquid_world.rs | 9 ++------- src/z_order.rs | 2 +- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/liquid_world.rs b/src/liquid_world.rs index f3df3d9..2a15b7f 100644 --- a/src/liquid_world.rs +++ b/src/liquid_world.rs @@ -246,14 +246,10 @@ impl LiquidWorld { HGridEntry::BoundaryParticle(bid, pid) => { let (boundary, handle) = self.boundaries.get_from_contiguous_index(*bid)?; - // --- DEFENSIVE FIX --- - // Add a bounds check. This handles the race condition where - // a boundary's particles are cleared or the boundary is freed - // but the hgrid is not yet updated. + // Defensive bounds check for fluids if *pid >= boundary.positions.len() { return None; } - // --- END FIX --- let pt = boundary.positions[*pid]; if aabb.distance_to_local_point(pt.into(), true) < self.particle_radius { @@ -302,11 +298,10 @@ impl LiquidWorld { HGridEntry::BoundaryParticle(bid, pid) => { let (boundary, handle) = self.boundaries.get_from_contiguous_index(*bid)?; - // --- DEFENSIVE FIX --- + // Defensive bounds check for fluids if *pid >= boundary.positions.len() { return None; } - // --- END FIX --- let pt = boundary.positions[*pid]; if shape.distance_to_point(&pos, pt.into(), true) <= self.particle_radius { diff --git a/src/z_order.rs b/src/z_order.rs index ed6d853..3fa1051 100644 --- a/src/z_order.rs +++ b/src/z_order.rs @@ -15,7 +15,7 @@ pub fn compute_points_z_order(points: &[Vector]) -> Vec { indices } -// Fast construction of k-Nearest Neighbor Graphs for Vector Clouds +// Fast construction of k-Nearest Neighbor Graphs for Point Clouds // Michael Connor, Piyush Kumar // Algorithm 1 // From 422e44fb8c9f3d3fa1afbb3899a002ac487bdb04 Mon Sep 17 00:00:00 2001 From: Dragos Daian Date: Sat, 20 Jun 2026 18:19:06 +0200 Subject: [PATCH 17/22] update samples --- README.md | 7 ++++++ build/salva2d-f64/Cargo.toml | 8 +++---- build/salva2d/Cargo.toml | 8 +++---- build/salva3d-f64/Cargo.toml | 8 +++---- build/salva3d/Cargo.toml | 8 +++---- examples2d/Cargo.toml | 9 ++++---- examples2d/all_examples2.rs | 23 ++++++++++--------- examples2d/basic2.rs | 24 +++++++++++--------- examples2d/custom_forces2.rs | 6 ++--- examples2d/elasticity2.rs | 6 ++--- examples2d/helper.rs | 2 +- examples2d/layers2.rs | 27 ++++++++++++---------- examples2d/surface_tension2.rs | 12 +++++----- examples3d/Cargo.toml | 7 +++--- examples3d/all_examples3.rs | 35 ++++++++++++++++------------- examples3d/basic3.rs | 14 ++++++------ examples3d/custom_forces3.rs | 12 +++++----- examples3d/elasticity3.rs | 12 +++++----- examples3d/faucet3.rs | 15 ++++++++----- examples3d/harness_basic3.rs | 7 +++--- examples3d/heightfield3.rs | 41 +++++++++++++++++++++------------- examples3d/helper.rs | 2 +- examples3d/surface_tension3.rs | 15 ++++++++----- 23 files changed, 171 insertions(+), 137 deletions(-) diff --git a/README.md b/README.md index 99343ab..1ab2421 100644 --- a/README.md +++ b/README.md @@ -46,3 +46,10 @@ is inspired from its renown painting [The Persistence of Memory](https://en.wiki - **Multiphase fluids**: mix several fluids with different characteristics (densities, viscosities, etc.) - Optional **two-way coupling** with bodies from **rapier**. - **WASM** support + +## Running examples + +```bash +cargo run --release -p examples2d --bin all_examples2 +cargo run --release -p examples3d --bin all_examples3 +``` diff --git a/build/salva2d-f64/Cargo.toml b/build/salva2d-f64/Cargo.toml index 7d9a7d0..8f43212 100644 --- a/build/salva2d-f64/Cargo.toml +++ b/build/salva2d-f64/Cargo.toml @@ -25,7 +25,7 @@ rapier = [ "parry", "rapier2d-f64" ] rapier-testbed = [ "rapier", "rapier_testbed2d", "graphics" ] rapier-harness = [ "rapier-testbed" ] parry = [ "parry2d-f64" ] -wasm-bindgen = [ "rapier2d-f64/wasm-bindgen" ] +wasm-bindgen = [ ] graphics = [ "bevy", "bevy_egui" ] [lints] @@ -48,10 +48,10 @@ rayon = { version = "1.8", optional = true } nalgebra = { version = "0.35", features = ["convert-glam033"] } glamx = "0.3" -parry2d-f64 = { version = "0", optional = true } -rapier2d-f64 = { version = "0", optional = true } +parry2d-f64 = { version = "0.28", optional = true } +rapier2d-f64 = { version = "0.33", optional = true } # TODO update it to f64 -rapier_testbed2d = { version = "0", optional = true } +rapier_testbed2d = { version = "0.33", optional = true } bevy_egui = { version = "0.26", features = ["immutable_ctx"], optional = true } bitflags = "2" diff --git a/build/salva2d/Cargo.toml b/build/salva2d/Cargo.toml index 4cda5d8..61db660 100644 --- a/build/salva2d/Cargo.toml +++ b/build/salva2d/Cargo.toml @@ -53,10 +53,10 @@ rayon = { version = "1.8", optional = true } nalgebra = { version = "0.35", features = ["convert-glam033"] } glamx = "0.3" -parry2d = { version = "0", optional = true } -rapier2d = { version = "0", optional = true } -rapier_testbed2d = { version = "0", optional = true } -kiss3d = { version = "0.40", optional = true } +parry2d = { version = "0.28", optional = true } +rapier2d = { version = "0.33", optional = true } +rapier_testbed2d = { version = "0.33", optional = true } +kiss3d = { version = "0.43", optional = true } bevy_egui = { version = "0.31", features = ["immutable_ctx"], optional = true } bitflags = "2" diff --git a/build/salva3d-f64/Cargo.toml b/build/salva3d-f64/Cargo.toml index 6d8aedf..7cfc7c6 100644 --- a/build/salva3d-f64/Cargo.toml +++ b/build/salva3d-f64/Cargo.toml @@ -21,7 +21,7 @@ sampling = [ "rapier" ] rapier-testbed = [ "rapier", "rapier_testbed3d", "graphics" ] rapier-harness = [ "rapier-testbed" ] parry = [ "parry3d-f64" ] -wasm-bindgen = [ "rapier3d-f64/wasm-bindgen" ] +wasm-bindgen = [ ] graphics = [ "bevy", "bevy_egui" ] [lints] @@ -44,10 +44,10 @@ rayon = { version = "1.8", optional = true } nalgebra = { version = "0.35", features = ["convert-glam033"] } glamx = "0.3" -parry3d-f64 = { version = "0", optional = true } -rapier3d-f64 = { version = "0", optional = true } +parry3d-f64 = { version = "0.28", optional = true } +rapier3d-f64 = { version = "0.33", optional = true } # TODO update it to f64 -rapier_testbed3d = { version = "0", optional = true } +rapier_testbed3d = { version = "0.33", optional = true } bevy_egui = { version = "0.26", features = ["immutable_ctx"], optional = true } bitflags = "2" diff --git a/build/salva3d/Cargo.toml b/build/salva3d/Cargo.toml index 765b1ad..978619d 100644 --- a/build/salva3d/Cargo.toml +++ b/build/salva3d/Cargo.toml @@ -43,10 +43,10 @@ rayon = { version = "1.8", optional = true } nalgebra = { version = "0.35", features = ["convert-glam033"] } glamx = "0.3" -parry3d = { version = "0", optional = true } -rapier3d = { version = "0", optional = true } -rapier_testbed3d = { version = "0", optional = true } -kiss3d = { version = "0.40", optional = true } +parry3d = { version = "0.28", optional = true } +rapier3d = { version = "0.33", optional = true } +rapier_testbed3d = { version = "0.33", optional = true } +kiss3d = { version = "0.43", optional = true } bevy_egui = { version = "0.31", features = ["immutable_ctx"], optional = true } bitflags = "2.6.0" diff --git a/examples2d/Cargo.toml b/examples2d/Cargo.toml index 8dddbf7..fae9220 100644 --- a/examples2d/Cargo.toml +++ b/examples2d/Cargo.toml @@ -11,11 +11,12 @@ parallel = [ "rapier_testbed2d/parallel"] [dependencies] Inflector = "0.11" nalgebra = "0" -parry2d = "0" -rapier2d = "0" -rapier_testbed2d = "0" -parry3d = "0" +parry2d = "0.28" +rapier2d = "0.33" +rapier_testbed2d = "0.33" +parry3d = "0.28" bevy = "0.15" +pollster = "0.4" [dependencies.salva2d] path = "../build/salva2d" diff --git a/examples2d/all_examples2.rs b/examples2d/all_examples2.rs index 4b93d43..7d69dfb 100644 --- a/examples2d/all_examples2.rs +++ b/examples2d/all_examples2.rs @@ -4,7 +4,7 @@ extern crate nalgebra as na; use inflector::Inflector; -use rapier_testbed2d::{Testbed, TestbedApp}; +use rapier_testbed2d::{Example, TestbedApp}; mod basic2; mod custom_forces2; @@ -46,20 +46,21 @@ fn main() { .unwrap_or(String::new()) .to_camel_case(); - 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), + let mut builders = vec![ + Example::demo("Basic", basic2::init_world), + Example::demo("Layers", layers2::init_world), + Example::demo("Custom forces", custom_forces2::init_world), + Example::demo("Elasticity", elasticity2::init_world), + Example::demo("Surface tension", surface_tension2::init_world), ]; - builders.sort_by_key(|builder| builder.0); + builders.sort_by_key(|builder| builder.name); let i = builders .iter() - .position(|builder| builder.0.to_camel_case().as_str() == demo.as_str()) + .position(|builder| builder.name.to_camel_case().as_str() == demo.as_str()) .unwrap_or(0); - let testbed = TestbedApp::from_builders(i, builders); + builders.rotate_left(i); + let testbed = TestbedApp::from_builders(builders); - testbed.run() + pollster::block_on(testbed.run()); } diff --git a/examples2d/basic2.rs b/examples2d/basic2.rs index 80d29a4..b0f683f 100644 --- a/examples2d/basic2.rs +++ b/examples2d/basic2.rs @@ -1,6 +1,6 @@ extern crate nalgebra as na; -use na::{DVector, Vector2, Vector3, Vector2}; +use na::{Vector2, Vector3}; use rapier2d::dynamics::{ImpulseJointSet, MultibodyJointSet, RigidBodyBuilder, RigidBodySet}; use rapier2d::geometry::{Collider, ColliderBuilder, ColliderSet}; use rapier_testbed2d::Testbed; @@ -79,17 +79,19 @@ pub fn init_world(testbed: &mut Testbed) { 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 heights: Vec<_> = (0..=nsubdivs) + .map(|i| { + if i == 0 || i == nsubdivs { + 20.0 + } else { + (i as f32 * ground_size.x / (nsubdivs as f32)).cos() * 0.5 + } + }) + .collect(); let rigid_body = RigidBodyBuilder::fixed().build(); let handle = bodies.insert(rigid_body); - let collider = ColliderBuilder::heightfield(heights, ground_size).build(); + let collider = ColliderBuilder::heightfield(heights, ground_size.into()).build(); let co_handle = colliders.insert_with_parent(collider, handle, &mut bodies); let bo_handle = fluids_pipeline .liquid_world @@ -108,7 +110,7 @@ pub fn init_world(testbed: &mut Testbed) { let samples = salva2d::sampling::shape_surface_ray_sample(collider.shape(), PARTICLE_RADIUS).unwrap(); let rb = RigidBodyBuilder::dynamic() - .translation(Vector2::new(x, y)) + .translation(Vector2::new(x, y).into()) .build(); let rb_handle = bodies.insert(rb); let co_handle = colliders.insert_with_parent(collider, rb_handle, &mut bodies); @@ -139,7 +141,7 @@ pub fn init_world(testbed: &mut Testbed) { colliders, impulse_joints, multibody_joints, - gravity, + gravity.into(), (), ); testbed.integration_parameters_mut().dt = 1.0 / 200.0; diff --git a/examples2d/custom_forces2.rs b/examples2d/custom_forces2.rs index ebe3221..e3d88b5 100644 --- a/examples2d/custom_forces2.rs +++ b/examples2d/custom_forces2.rs @@ -1,6 +1,6 @@ extern crate nalgebra as na; -use na::{Vector2, Vector3, Unit, Vector2}; +use na::{Unit, Vector2, Vector3}; use rapier2d::dynamics::{ImpulseJointSet, MultibodyJointSet, RigidBodySet}; use rapier2d::geometry::ColliderSet; use rapier_testbed2d::Testbed; @@ -52,11 +52,11 @@ pub fn init_world(testbed: &mut Testbed) { colliders, impulse_joints, multibody_joints, - gravity, + gravity.into(), (), ); testbed.integration_parameters_mut().dt = 1.0 / 200.0; - testbed.look_at(Vector2::origin(), 300.0); + testbed.look_at(Vector2::zeros().into(), 300.0); } struct CustomForceField { diff --git a/examples2d/elasticity2.rs b/examples2d/elasticity2.rs index a6952fd..69b20fe 100644 --- a/examples2d/elasticity2.rs +++ b/examples2d/elasticity2.rs @@ -1,6 +1,6 @@ extern crate nalgebra as na; -use na::{Isometry2, Vector2, Vector3, Vector2}; +use na::{Isometry2, Vector2, Vector3}; use rapier2d::dynamics::{ImpulseJointSet, MultibodyJointSet, RigidBodyBuilder, RigidBodySet}; use rapier2d::geometry::{ColliderBuilder, ColliderSet}; use rapier_testbed2d::Testbed; @@ -87,9 +87,9 @@ pub fn init_world(testbed: &mut Testbed) { colliders, impulse_joints, multibody_joints, - gravity, + gravity.into(), (), ); testbed.integration_parameters_mut().dt = 1.0 / 200.0; - testbed.look_at(Vector2::new(0.0, 1.0), 100.0); + testbed.look_at(Vector2::new(0.0, 1.0).into(), 100.0); } diff --git a/examples2d/helper.rs b/examples2d/helper.rs index e141b9b..25a6137 100644 --- a/examples2d/helper.rs +++ b/examples2d/helper.rs @@ -1,4 +1,4 @@ -use na::{Vector2, Vector2}; +use na::Vector2; use salva2d::object::{interaction_groups::InteractionGroups, Fluid}; pub fn cube_fluid(ni: usize, nj: usize, particle_rad: f32, density: f32) -> Fluid { diff --git a/examples2d/layers2.rs b/examples2d/layers2.rs index 979b2fb..e36e197 100644 --- a/examples2d/layers2.rs +++ b/examples2d/layers2.rs @@ -1,8 +1,8 @@ extern crate nalgebra as na; -use na::{DVector, Vector2, Vector3, Vector2}; +use na::{Vector2, Vector3}; use rapier2d::dynamics::{ImpulseJointSet, MultibodyJointSet, RigidBodyBuilder, RigidBodySet}; -use rapier2d::geometry::{Collider, ColliderBuilder, ColliderSet}; +use rapier2d::geometry::{Collider, ColliderBuilder, ColliderSet, InteractionTestMode}; use rapier_testbed2d::Testbed; use salva2d::integrations::rapier::{ColliderSampling, FluidsPipeline, FluidsTestbedPlugin}; use salva2d::object::interaction_groups::{Group, InteractionGroups}; @@ -94,17 +94,19 @@ pub fn init_world(testbed: &mut Testbed) { 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 heights: Vec<_> = (0..=nsubdivs) + .map(|i| { + if i == 0 || i == nsubdivs { + 20.0 + } else { + (i as f32 * ground_size.x / (nsubdivs as f32)).cos() * 0.5 + } + }) + .collect(); let rigid_body = RigidBodyBuilder::fixed().build(); let handle = bodies.insert(rigid_body); - let collider = ColliderBuilder::heightfield(heights, ground_size).build(); + let collider = ColliderBuilder::heightfield(heights, ground_size.into()).build(); let co_handle = colliders.insert_with_parent(collider, handle, &mut bodies); let bo_handle = fluids_pipeline .liquid_world @@ -125,7 +127,7 @@ pub fn init_world(testbed: &mut Testbed) { salva2d::sampling::shape_surface_ray_sample(collider.shape(), PARTICLE_RADIUS) .unwrap(); let rb = RigidBodyBuilder::dynamic() - .translation(Vector2::new(x, y)) + .translation(Vector2::new(x, y).into()) .build(); let rb_handle = bodies.insert(rb); let membership: u32 = interaction_group.memberships.into(); @@ -133,6 +135,7 @@ pub fn init_world(testbed: &mut Testbed) { collider.set_collision_groups(rapier2d::geometry::InteractionGroups::new( rapier2d::geometry::Group::from(membership), rapier2d::geometry::Group::from(filter), + InteractionTestMode::And, )); let co_handle = colliders.insert_with_parent(collider, rb_handle, &mut bodies); let bo_handle = fluids_pipeline @@ -177,7 +180,7 @@ pub fn init_world(testbed: &mut Testbed) { colliders, impulse_joints, multibody_joints, - gravity, + gravity.into(), (), ); testbed.integration_parameters_mut().dt = 1.0 / 200.0; diff --git a/examples2d/surface_tension2.rs b/examples2d/surface_tension2.rs index 7ba5481..bd249ec 100644 --- a/examples2d/surface_tension2.rs +++ b/examples2d/surface_tension2.rs @@ -1,9 +1,9 @@ extern crate nalgebra as na; -use na::{Isometry2, Vector2, Vector3, Vector2}; +use na::{Isometry2, Vector2, Vector3}; use rapier2d::dynamics::{ImpulseJointSet, MultibodyJointSet, RigidBodyBuilder, RigidBodySet}; use rapier2d::geometry::{ColliderBuilder, ColliderSet}; -use rapier_testbed2d::{Testbed, TestbedApp}; +use rapier_testbed2d::{Example, Testbed, TestbedApp}; use salva2d::integrations::rapier::{ ColliderSampling, FluidsPipeline, FluidsRenderingMode, FluidsTestbedPlugin, }; @@ -69,15 +69,15 @@ pub fn init_world(testbed: &mut Testbed) { colliders, impulse_joints, multibody_joints, - gravity, + gravity.into(), (), ); testbed.integration_parameters_mut().dt = 1.0 / 200.0; - testbed.look_at(Vector2::origin(), 1500.0); + testbed.look_at(Vector2::zeros().into(), 1500.0); // testbed.enable_boundary_particles_rendering(true); } fn main() { - let testbed = TestbedApp::from_builders(0, vec![("Boxes", init_world)]); - testbed.run() + let testbed = TestbedApp::from_builders(vec![Example::demo("Boxes", init_world)]); + pollster::block_on(testbed.run()); } diff --git a/examples3d/Cargo.toml b/examples3d/Cargo.toml index ce77b22..5d84140 100644 --- a/examples3d/Cargo.toml +++ b/examples3d/Cargo.toml @@ -12,10 +12,11 @@ parallel = ["rapier_testbed3d/parallel", "salva3d/parallel"] num-traits = "0.2" Inflector = "0.11" nalgebra = "0" -rapier3d = "0" -rapier_testbed3d = "0" -parry3d = "0" +rapier3d = "0.33" +rapier_testbed3d = "0.33" +parry3d = "0.28" bevy = "0.15" +pollster = "0.4" [dependencies.salva3d] path = "../build/salva3d" diff --git a/examples3d/all_examples3.rs b/examples3d/all_examples3.rs index 15afac7..bd28e41 100644 --- a/examples3d/all_examples3.rs +++ b/examples3d/all_examples3.rs @@ -5,7 +5,7 @@ use wasm_bindgen::prelude::*; use inflector::Inflector; -use rapier_testbed3d::{Testbed, TestbedApp}; +use rapier_testbed3d::{Example, TestbedApp}; use std::cmp::Ordering; mod basic3; @@ -51,27 +51,30 @@ pub fn main() { .unwrap_or(String::new()) .to_camel_case(); - let mut builders: Vec<(_, fn(&mut Testbed))> = vec![ - ("Basic", basic3::init_world), - ("Height field", heightfield3::init_world), - ("Custom Forces", custom_forces3::init_world), - ("Elasticity", elasticity3::init_world), - ("Faucet", faucet3::init_world), //FIXME: bug with adding & removing particles - ("Surface tension", surface_tension3::init_world), + let mut builders = vec![ + Example::demo("Basic", basic3::init_world), + Example::demo("Height field", heightfield3::init_world), + Example::demo("Custom Forces", custom_forces3::init_world), + Example::demo("Elasticity", elasticity3::init_world), + Example::demo("Faucet", faucet3::init_world), //FIXME: bug with adding & removing particles + Example::demo("Surface tension", surface_tension3::init_world), ]; // Lexicographic sort, with stress tests moved at the end of the list. - builders.sort_by(|a, b| match (a.0.starts_with("("), b.0.starts_with("(")) { - (true, true) | (false, false) => a.0.cmp(b.0), - (true, false) => Ordering::Greater, - (false, true) => Ordering::Less, - }); + builders.sort_by( + |a, b| match (a.name.starts_with("("), b.name.starts_with("(")) { + (true, true) | (false, false) => a.name.cmp(b.name), + (true, false) => Ordering::Greater, + (false, true) => Ordering::Less, + }, + ); let i = builders .iter() - .position(|builder| builder.0.to_camel_case().as_str() == demo.as_str()) + .position(|builder| builder.name.to_camel_case().as_str() == demo.as_str()) .unwrap_or(0); + builders.rotate_left(i); - let testbed = TestbedApp::from_builders(i, builders); - testbed.run() + let testbed = TestbedApp::from_builders(builders); + pollster::block_on(testbed.run()); } diff --git a/examples3d/basic3.rs b/examples3d/basic3.rs index d5ae684..0832e22 100644 --- a/examples3d/basic3.rs +++ b/examples3d/basic3.rs @@ -1,9 +1,9 @@ extern crate nalgebra as na; -use na::{Isometry3, Vector3, Vector3}; +use na::{Isometry3, Vector3}; use rapier3d::dynamics::{ImpulseJointSet, MultibodyJointSet, RigidBodyBuilder, RigidBodySet}; use rapier3d::geometry::{ColliderBuilder, ColliderSet, SharedShape}; -use rapier_testbed3d::{Testbed, TestbedApp}; +use rapier_testbed3d::{Example, Testbed, TestbedApp}; use salva3d::integrations::rapier::{ColliderSampling, FluidsPipeline, FluidsTestbedPlugin}; use salva3d::object::interaction_groups::InteractionGroups; use salva3d::object::Boundary; @@ -70,7 +70,7 @@ pub fn init_world(testbed: &mut Testbed) { let samples = salva3d::sampling::shape_surface_ray_sample(&*wall_shape, PARTICLE_RADIUS).unwrap(); let co = ColliderBuilder::new(wall_shape.clone()) - .position(*pose) + .position((*pose).into()) .build(); let co_handle = colliders.insert_with_parent(co, ground_handle, &mut bodies); let bo_handle = fluids_pipeline @@ -112,14 +112,14 @@ pub fn init_world(testbed: &mut Testbed) { colliders, impulse_joints, multibody_joints, - gravity, + gravity.into(), (), ); testbed.integration_parameters_mut().dt = 1.0 / 200.0; - testbed.look_at(Vector3::new(3.0, 3.0, 3.0), Vector3::origin()); + testbed.look_at(Vector3::new(3.0, 3.0, 3.0).into(), Vector3::zeros().into()); } fn main() { - let testbed = TestbedApp::from_builders(0, vec![("Basic", init_world)]); - testbed.run() + let testbed = TestbedApp::from_builders(vec![Example::demo("Basic", init_world)]); + pollster::block_on(testbed.run()); } diff --git a/examples3d/custom_forces3.rs b/examples3d/custom_forces3.rs index 9dc22cf..70d860d 100644 --- a/examples3d/custom_forces3.rs +++ b/examples3d/custom_forces3.rs @@ -1,9 +1,9 @@ extern crate nalgebra as na; -use na::{Vector3, Unit, Vector3}; +use na::{Unit, Vector3}; use rapier3d::dynamics::{ImpulseJointSet, MultibodyJointSet, RigidBodySet}; use rapier3d::geometry::ColliderSet; -use rapier_testbed3d::{Testbed, TestbedApp}; +use rapier_testbed3d::{Example, Testbed, TestbedApp}; use salva3d::integrations::rapier::{FluidsPipeline, FluidsRenderingMode, FluidsTestbedPlugin}; use salva3d::object::{Boundary, Fluid}; use salva3d::solver::NonPressureForce; @@ -52,16 +52,16 @@ pub fn init_world(testbed: &mut Testbed) { colliders, impulse_joints, multibody_joints, - gravity, + gravity.into(), (), ); testbed.integration_parameters_mut().dt = 1.0 / 200.0; - testbed.look_at(Vector3::new(3.0, 3.0, 3.0), Vector3::origin()); + testbed.look_at(Vector3::new(3.0, 3.0, 3.0).into(), Vector3::zeros().into()); } fn main() { - let testbed = TestbedApp::from_builders(0, vec![("Boxes", init_world)]); - testbed.run() + let testbed = TestbedApp::from_builders(vec![Example::demo("Boxes", init_world)]); + pollster::block_on(testbed.run()); } struct CustomForceField { diff --git a/examples3d/elasticity3.rs b/examples3d/elasticity3.rs index 704bcb4..bef67dd 100644 --- a/examples3d/elasticity3.rs +++ b/examples3d/elasticity3.rs @@ -1,9 +1,9 @@ extern crate nalgebra as na; -use na::{Isometry3, Vector3, Vector3}; +use na::{Isometry3, Vector3}; use rapier3d::dynamics::{ImpulseJointSet, MultibodyJointSet, RigidBodyBuilder, RigidBodySet}; use rapier3d::geometry::{ColliderBuilder, ColliderSet}; -use rapier_testbed3d::{Testbed, TestbedApp}; +use rapier_testbed3d::{Example, Testbed, TestbedApp}; use salva3d::integrations::rapier::{ ColliderSampling, FluidsPipeline, FluidsRenderingMode, FluidsTestbedPlugin, }; @@ -103,14 +103,14 @@ pub fn init_world(testbed: &mut Testbed) { colliders, impulse_joints, multibody_joints, - gravity, + gravity.into(), (), ); testbed.integration_parameters_mut().dt = 1.0 / 200.0; - testbed.look_at(Vector3::new(1.5, 1.5, 1.5), Vector3::origin()); + testbed.look_at(Vector3::new(1.5, 1.5, 1.5).into(), Vector3::zeros().into()); } fn main() { - let testbed = TestbedApp::from_builders(0, vec![("Elasticity", init_world)]); - testbed.run() + let testbed = TestbedApp::from_builders(vec![Example::demo("Elasticity", init_world)]); + pollster::block_on(testbed.run()); } diff --git a/examples3d/faucet3.rs b/examples3d/faucet3.rs index a88659e..2191245 100644 --- a/examples3d/faucet3.rs +++ b/examples3d/faucet3.rs @@ -1,12 +1,12 @@ extern crate nalgebra as na; -use na::{Vector3, Vector3}; +use na::Vector3; use rapier3d::geometry::{ColliderBuilder, ColliderSet}; use rapier3d::{ dynamics::{RigidBodyBuilder, RigidBodySet}, prelude::{ImpulseJointSet, MultibodyJointSet}, }; -use rapier_testbed3d::{Testbed, TestbedApp}; +use rapier_testbed3d::{Example, Testbed, TestbedApp}; use salva3d::integrations::rapier::{ColliderSampling, FluidsPipeline, FluidsTestbedPlugin}; use salva3d::object::interaction_groups::InteractionGroups; use salva3d::object::{Boundary, Fluid}; @@ -115,15 +115,18 @@ pub fn init_world(testbed: &mut Testbed) { colliders, impulse_joints, multibody_joints, - gravity, + gravity.into(), (), ); testbed.integration_parameters_mut().dt = 1.0 / 200.0; // testbed.enable_boundary_particles_rendering(true); - testbed.look_at(Vector3::new(1.5, 0.0, 1.5), Vector3::new(0.0, 0.0, 0.0)); + testbed.look_at( + Vector3::new(1.5, 0.0, 1.5).into(), + Vector3::new(0.0, 0.0, 0.0).into(), + ); } fn main() { - let testbed = TestbedApp::from_builders(0, vec![("Boxes", init_world)]); - testbed.run() + let testbed = TestbedApp::from_builders(vec![Example::demo("Boxes", init_world)]); + pollster::block_on(testbed.run()); } diff --git a/examples3d/harness_basic3.rs b/examples3d/harness_basic3.rs index 65d9abc..7edc572 100644 --- a/examples3d/harness_basic3.rs +++ b/examples3d/harness_basic3.rs @@ -6,7 +6,7 @@ use rapier3d::{ dynamics::{RigidBodyBuilder, RigidBodySet}, prelude::{ImpulseJointSet, MultibodyJointSet}, }; -use rapier_testbed3d::harness::Harness; +use rapier_testbed3d::harness::{Harness, RapierBroadPhaseType}; use salva3d::integrations::rapier::{ColliderSampling, FluidsHarnessPlugin, FluidsPipeline}; use salva3d::object::interaction_groups::InteractionGroups; use salva3d::object::Boundary; @@ -74,7 +74,7 @@ pub fn init_world(harness: &mut Harness) { let samples = salva3d::sampling::shape_surface_ray_sample(&*wall_shape, PARTICLE_RADIUS).unwrap(); let co = ColliderBuilder::new(wall_shape.clone()) - .position(*pose) + .position((*pose).into()) .build(); let co_handle = colliders.insert_with_parent(co, ground_handle, &mut bodies); let bo_handle = fluids_pipeline @@ -113,7 +113,8 @@ pub fn init_world(harness: &mut Harness) { colliders, impulse_joints, multibody_joints, - gravity, + RapierBroadPhaseType::default(), + gravity.into(), (), ); harness.integration_parameters_mut().dt = 1.0 / 200.0; diff --git a/examples3d/heightfield3.rs b/examples3d/heightfield3.rs index 777b76d..33d95c5 100644 --- a/examples3d/heightfield3.rs +++ b/examples3d/heightfield3.rs @@ -1,6 +1,7 @@ extern crate nalgebra as na; -use nalgebra::Isometry3; +use nalgebra::{Isometry3, Vector3}; +use rapier3d::geometry::Array2; use rapier3d::na::ComplexField; use rapier3d::prelude::*; use rapier_testbed3d::Testbed; @@ -37,7 +38,7 @@ pub fn init_world(testbed: &mut Testbed) { )); let viscosity = ArtificialViscosity::new(1.0, 0.0); fluid.nonpressure_forces.push(Box::new(viscosity)); - fluid.velocities = vec![-Vector::y() * 10.; fluid.velocities.len()]; + fluid.velocities = vec![-Vector3::y() * 10.; fluid.velocities.len()]; let fluid_handle = fluids_pipeline.liquid_world.add_fluid(fluid); /* @@ -46,19 +47,27 @@ pub fn init_world(testbed: &mut Testbed) { let ground_size = Vector::new(12.0, 1.0, 12.0); let nsubdivs = 40; - let heights = DMatrix::from_fn(nsubdivs + 1, nsubdivs + 1, |i, j| { - if i == 0 || i == nsubdivs || j == 0 || j == nsubdivs { - 3.0 - } else { - let x = i as f32 * ground_size.x / (nsubdivs as f32); - let z = j as f32 * ground_size.z / (nsubdivs as f32); + let heights = Array2::new( + nsubdivs + 1, + nsubdivs + 1, + (0..=nsubdivs) + .flat_map(|j| { + (0..=nsubdivs).map(move |i| { + if i == 0 || i == nsubdivs || j == 0 || j == nsubdivs { + 3.0 + } else { + let x = i as f32 * ground_size.x / (nsubdivs as f32); + let z = j as f32 * ground_size.z / (nsubdivs as f32); - // NOTE: make sure we use the sin/cos from simba to ensure - // cross-platform determinism of the example when the - // enhanced_determinism feature is enabled. - ComplexField::sin(x) + ComplexField::cos(z) - } - }); + // NOTE: make sure we use the sin/cos from simba to ensure + // cross-platform determinism of the example when the + // enhanced_determinism feature is enabled. + ComplexField::sin(x) + ComplexField::cos(z) + } + }) + }) + .collect(), + ); let rigid_body = RigidBodyBuilder::fixed().build(); let handle = bodies.insert(rigid_body); @@ -81,7 +90,7 @@ pub fn init_world(testbed: &mut Testbed) { let mut plugin = FluidsTestbedPlugin::new(); plugin.set_pipeline(fluids_pipeline); - plugin.set_fluid_color(fluid_handle, Vector::new(0.8, 0.7, 1.0)); + plugin.set_fluid_color(fluid_handle, Vector::new(0.8, 0.7, 1.0).into()); // plugin.render_boundary_particles = true; testbed.add_plugin(plugin); testbed.set_body_wireframe(handle, true); @@ -91,5 +100,5 @@ pub fn init_world(testbed: &mut Testbed) { * Set up the testbed. */ testbed.set_world(bodies, colliders, impulse_joints, multibody_joints); - testbed.look_at(point![100.0, 100.0, 100.0], Vector::origin()); + testbed.look_at(Vector::new(100.0, 100.0, 100.0), Vector::ZERO); } diff --git a/examples3d/helper.rs b/examples3d/helper.rs index 48a071c..8426060 100644 --- a/examples3d/helper.rs +++ b/examples3d/helper.rs @@ -1,4 +1,4 @@ -use super::na::{Vector3, Vector3}; +use super::na::Vector3; use salva3d::object::{interaction_groups::InteractionGroups, Fluid}; pub fn cube_fluid(ni: usize, nj: usize, nk: usize, particle_rad: f32, density: f32) -> Fluid { diff --git a/examples3d/surface_tension3.rs b/examples3d/surface_tension3.rs index 25f5f5c..f670797 100644 --- a/examples3d/surface_tension3.rs +++ b/examples3d/surface_tension3.rs @@ -1,9 +1,9 @@ extern crate nalgebra as na; -use na::{Isometry3, Vector3, Vector3}; +use na::{Isometry3, Vector3}; use rapier3d::dynamics::{ImpulseJointSet, MultibodyJointSet, RigidBodyBuilder, RigidBodySet}; use rapier3d::geometry::{ColliderBuilder, ColliderSet}; -use rapier_testbed3d::{Testbed, TestbedApp}; +use rapier_testbed3d::{Example, Testbed, TestbedApp}; use salva3d::integrations::rapier::{ ColliderSampling, FluidsPipeline, FluidsRenderingMode, FluidsTestbedPlugin, }; @@ -76,14 +76,17 @@ pub fn init_world(testbed: &mut Testbed) { colliders, impulse_joints, multibody_joints, - gravity, + gravity.into(), (), ); testbed.integration_parameters_mut().dt = 1.0 / 200.0; - testbed.look_at(Vector3::new(0.25, 0.25, 0.25), Vector3::origin()); + testbed.look_at( + Vector3::new(0.25, 0.25, 0.25).into(), + Vector3::zeros().into(), + ); } fn main() { - let testbed = TestbedApp::from_builders(0, vec![("Surface tension", init_world)]); - testbed.run() + let testbed = TestbedApp::from_builders(vec![Example::demo("Surface tension", init_world)]); + pollster::block_on(testbed.run()); } From 795af2b772e2c3da1b304675474e51e8dfb8ff97 Mon Sep 17 00:00:00 2001 From: Dragos Daian Date: Sat, 20 Jun 2026 18:42:39 +0200 Subject: [PATCH 18/22] update samples --- examples2d/basic2.rs | 1 + examples2d/layers2.rs | 1 + src/coupling/coupling_manager.rs | 7 +- src/geometry/contacts.rs | 2 +- src/geometry/hgrid.rs | 2 +- src/integrations/rapier/fluids_pipeline.rs | 59 +++++--- src/integrations/rapier/testbed_plugin.rs | 155 +++++++++++++++++++-- src/liquid_world.rs | 10 +- src/sampling/ray_sampling.rs | 9 +- src/z_order.rs | 3 +- 10 files changed, 205 insertions(+), 44 deletions(-) diff --git a/examples2d/basic2.rs b/examples2d/basic2.rs index b0f683f..15713cf 100644 --- a/examples2d/basic2.rs +++ b/examples2d/basic2.rs @@ -145,5 +145,6 @@ pub fn init_world(testbed: &mut Testbed) { (), ); testbed.integration_parameters_mut().dt = 1.0 / 200.0; + testbed.look_at(Vector2::new(0.0, 5.5).into(), 50.0); // testbed.enable_boundary_particles_rendering(true); } diff --git a/examples2d/layers2.rs b/examples2d/layers2.rs index e36e197..438b3e6 100644 --- a/examples2d/layers2.rs +++ b/examples2d/layers2.rs @@ -184,5 +184,6 @@ pub fn init_world(testbed: &mut Testbed) { (), ); testbed.integration_parameters_mut().dt = 1.0 / 200.0; + testbed.look_at(Vector2::new(0.0, 5.5).into(), 50.0); // testbed.enable_boundary_particles_rendering(true); } diff --git a/src/coupling/coupling_manager.rs b/src/coupling/coupling_manager.rs index ceae716..20e9a40 100644 --- a/src/coupling/coupling_manager.rs +++ b/src/coupling/coupling_manager.rs @@ -24,7 +24,12 @@ pub trait CouplingManager { ); /// Transmit forces from salva's boundary objects to the coupled bodies. - fn transmit_forces(&mut self, timestep: &TimestepManager, boundaries: &BoundarySet, force_coefficient: Real); + fn transmit_forces( + &mut self, + timestep: &TimestepManager, + boundaries: &BoundarySet, + force_coefficient: Real, + ); } impl CouplingManager for () { diff --git a/src/geometry/contacts.rs b/src/geometry/contacts.rs index 8f668f2..6cf2f04 100644 --- a/src/geometry/contacts.rs +++ b/src/geometry/contacts.rs @@ -1,6 +1,6 @@ use crate::counters::Counters; use crate::geometry::HGrid; -use crate::math::{Vector, Real}; +use crate::math::{Real, Vector}; use crate::object::Boundary; use crate::object::Fluid; diff --git a/src/geometry/hgrid.rs b/src/geometry/hgrid.rs index 27360d0..531434d 100644 --- a/src/geometry/hgrid.rs +++ b/src/geometry/hgrid.rs @@ -1,7 +1,7 @@ use fnv::FnvHasher; use std::collections::HashMap; -use crate::math::{Vector, Real, DIM}; +use crate::math::{Real, Vector, DIM}; use std::hash::BuildHasher; diff --git a/src/integrations/rapier/fluids_pipeline.rs b/src/integrations/rapier/fluids_pipeline.rs index 916ed5d..0486ff6 100644 --- a/src/integrations/rapier/fluids_pipeline.rs +++ b/src/integrations/rapier/fluids_pipeline.rs @@ -2,8 +2,8 @@ use crate::coupling::CouplingManager; use crate::geometry::{HGrid, HGridEntry}; use crate::object::{BoundaryHandle, BoundarySet, Fluid}; use crate::solver::DFSPHSolver; -use crate::{math, LiquidWorld}; use crate::TimestepManager; +use crate::{math, LiquidWorld}; use approx::AbsDiffEq; use na::Unit; use rapier::dynamics::RigidBodySet; @@ -64,11 +64,20 @@ impl FluidsPipeline { /// The kernel radius will be computed as `particle_radius * smoothing_factor * 2.0. /// - `boundary_force_coefficient`: coefficient applied when transmitting forces from fluids to boundaries. /// Use 1.0 for full force, 0.5 for half force, etc. - pub fn new_with_boundary_coef(particle_radius: math::Real, smoothing_factor: math::Real, boundary_force_coefficient: math::Real) -> Self { + pub fn new_with_boundary_coef( + particle_radius: math::Real, + smoothing_factor: math::Real, + boundary_force_coefficient: math::Real, + ) -> Self { let dfsph: DFSPHSolver = DFSPHSolver::new(); Self { - liquid_world: LiquidWorld::new(dfsph, particle_radius, smoothing_factor, boundary_force_coefficient), + liquid_world: LiquidWorld::new( + dfsph, + particle_radius, + smoothing_factor, + boundary_force_coefficient, + ), coupling: ColliderCouplingSet::new(), } } @@ -218,14 +227,14 @@ impl<'a> CouplingManager for ColliderCouplingManager<'a> { boundary.positions.push(world_pt); let velocity = body.map(|b| b.velocity_at_point(rapier_pt)); - boundary - .velocities - .push(rapier_to_salva_vector( - velocity.unwrap_or(RapierVector::ZERO), - )); + boundary.velocities.push(rapier_to_salva_vector( + velocity.unwrap_or(RapierVector::ZERO), + )); } - boundary.volumes.resize(points.len(), na::zero::()); + boundary + .volumes + .resize(points.len(), na::zero::()); } ColliderSampling::DynamicContactSampling => { let prediction = h * na::convert::<_, math::Real>(0.5); @@ -242,7 +251,7 @@ impl<'a> CouplingManager for ColliderCouplingManager<'a> { .cells_intersecting_aabb(&mins, &maxs) .flat_map(|e| e.1) { - match particle { + match particle { HGridEntry::FluidParticle(fluid_id, particle_id) => { let fluid = &mut fluids[*fluid_id]; @@ -251,11 +260,11 @@ impl<'a> CouplingManager for ColliderCouplingManager<'a> { // performed elsewhere in the codebase. let fluid_groups = fluid.interaction_groups; let boundary_groups = boundary.interaction_groups; - + if !fluid_groups.test(boundary_groups) { continue; } - + let particle_pos = fluid.positions[*particle_id] + fluid.velocities[*particle_id] * timestep.dt(); let rapier_particle_pos = salva_to_rapier_vector(particle_pos); @@ -269,9 +278,10 @@ impl<'a> CouplingManager for ColliderCouplingManager<'a> { let dpt = particle_pos - rapier_to_salva_vector(proj.point); - if let Some((normal, depth)) = - Unit::try_new_and_get(dpt, math::Real::default_epsilon()) - { + if let Some((normal, depth)) = Unit::try_new_and_get( + dpt, + math::Real::default_epsilon(), + ) { if proj.is_inside { fluid.positions[*particle_id] -= *normal * (depth + margin); @@ -291,11 +301,9 @@ impl<'a> CouplingManager for ColliderCouplingManager<'a> { let velocity = body.map(|b| b.velocity_at_point(proj.point)); - boundary - .velocities - .push(rapier_to_salva_vector( - velocity.unwrap_or(RapierVector::ZERO), - )); + boundary.velocities.push(rapier_to_salva_vector( + velocity.unwrap_or(RapierVector::ZERO), + )); boundary.positions.push(rapier_to_salva_vector(proj.point)); boundary.volumes.push(na::zero::()); coupling.features.push(feature); @@ -314,7 +322,12 @@ impl<'a> CouplingManager for ColliderCouplingManager<'a> { } } - fn transmit_forces(&mut self, timestep: &TimestepManager, boundaries: &BoundarySet, force_coefficient: math::Real) { + fn transmit_forces( + &mut self, + timestep: &TimestepManager, + boundaries: &BoundarySet, + force_coefficient: math::Real, + ) { for (collider, coupling) in &self.coupling.entries { if let (Some(collider), Some(boundary)) = ( self.colliders.get(*collider), @@ -332,7 +345,9 @@ impl<'a> CouplingManager for ColliderCouplingManager<'a> { boundary.positions.iter().zip(forces.iter().cloned()) { body.apply_impulse_at_point( - salva_to_rapier_vector(force * timestep.dt() * force_coefficient), + salva_to_rapier_vector( + force * timestep.dt() * force_coefficient, + ), salva_to_rapier_vector(*pos), true, ) diff --git a/src/integrations/rapier/testbed_plugin.rs b/src/integrations/rapier/testbed_plugin.rs index 7b4df17..ce0eff5 100644 --- a/src/integrations/rapier/testbed_plugin.rs +++ b/src/integrations/rapier/testbed_plugin.rs @@ -1,8 +1,12 @@ -use crate::math::Real; +use crate::math::{Real, Vector}; use crate::object::{BoundaryHandle, FluidHandle}; +#[cfg(feature = "dim2")] +use kiss3d::prelude::Vec2; +#[cfg(feature = "dim3")] +use kiss3d::prelude::Vec3; +use kiss3d::{color::Color, window::Window}; use na::Vector3; use rapier_testbed::{egui, harness::Harness, GraphicsManager, PhysicsState, TestbedPlugin}; -use kiss3d::window::Window; use crate::integrations::rapier::FluidsPipeline; use std::collections::HashMap; @@ -58,11 +62,8 @@ pub struct FluidsTestbedPlugin { callbacks: Vec, step_time: f64, fluids_pipeline: FluidsPipeline, - #[allow(dead_code)] f2color: HashMap>, - #[allow(dead_code)] boundary2color: HashMap>, - #[allow(dead_code)] default_fluid_color: Vector3, } @@ -106,6 +107,95 @@ impl FluidsTestbedPlugin { pub fn enable_boundary_particles_rendering(&mut self, enabled: bool) { self.render_boundary_particles = enabled; } + + fn color(color: Vector3) -> Color { + Color::new(color.x as f32, color.y as f32, color.z as f32, 1.0) + } + + fn fluid_color(&self, handle: FluidHandle, velocity: &Vector) -> Color { + let base = *self + .f2color + .get(&handle) + .unwrap_or(&self.default_fluid_color); + + match self.fluids_rendering_mode { + FluidsRenderingMode::StaticColor => Self::color(base), + FluidsRenderingMode::VelocityColor { min, max } + | FluidsRenderingMode::VelocityArrows { min, max } => { + Self::color(Self::velocity_tinted_color(base, velocity, min, max)) + } + } + } + + fn velocity_tinted_color( + base: Vector3, + velocity: &Vector, + min: Real, + max: Real, + ) -> Vector3 { + let range = max - min; + + if range <= na::zero() { + return base; + } + + let factor = ((velocity.norm() - min) / range) + .max(na::zero()) + .min(na::one()); + let inv_factor = na::one::() - factor; + + Vector3::new( + base.x * inv_factor + factor, + base.y * inv_factor, + base.z * inv_factor, + ) + } + + fn particle_size(radius: Real) -> f32 { + (radius as f32 * 300.0).clamp(2.5, 8.0) + } + + #[cfg(feature = "dim2")] + fn point(point: &Vector) -> Vec2 { + Vec2::new(point.x as f32, point.y as f32) + } + + #[cfg(feature = "dim3")] + fn point(point: &Vector) -> Vec3 { + Vec3::new(point.x as f32, point.y as f32, point.z as f32) + } + + #[cfg(feature = "dim2")] + fn draw_particle(window: &mut Window, point: &Vector, color: Color, size: f32) { + window.draw_point_2d(Self::point(point), color, size); + } + + #[cfg(feature = "dim3")] + fn draw_particle(window: &mut Window, point: &Vector, color: Color, size: f32) { + window.draw_point(Self::point(point), color, size); + } + + #[cfg(feature = "dim2")] + fn draw_velocity( + window: &mut Window, + point: &Vector, + velocity: &Vector, + color: Color, + ) { + let end = *point + *velocity * na::convert::<_, Real>(0.02); + window.draw_line_2d(Self::point(point), Self::point(&end), color, 1.5); + } + + #[cfg(feature = "dim3")] + fn draw_velocity( + window: &mut Window, + point: &Vector, + velocity: &Vector, + color: Color, + ) { + let end = *point + *velocity * na::convert::<_, Real>(0.02); + window.draw_line(Self::point(point), Self::point(&end), color, 1.5, false); + } } impl TestbedPlugin for FluidsTestbedPlugin { @@ -140,18 +230,67 @@ impl TestbedPlugin for FluidsTestbedPlugin { fn draw( &mut self, _graphics: &mut GraphicsManager, - _window: &mut Window, + window: &mut Window, _harness: &mut Harness, ) { + let draw_velocities = matches!( + self.fluids_rendering_mode, + FluidsRenderingMode::VelocityArrows { .. } + ); + + for (handle, fluid) in self.fluids_pipeline.liquid_world.fluids().iter() { + let size = Self::particle_size(fluid.particle_radius()); + + for (point, velocity) in fluid.positions.iter().zip(fluid.velocities.iter()) { + let color = self.fluid_color(handle, velocity); + Self::draw_particle(window, point, color, size); + + if draw_velocities && velocity.norm_squared() > na::zero() { + Self::draw_velocity(window, point, velocity, color); + } + } + } + + if self.render_boundary_particles { + let default_color = Vector3::repeat(na::convert::<_, Real>(0.5)); + let size = Self::particle_size(self.fluids_pipeline.liquid_world.particle_radius()); + + for (handle, boundary) in self.fluids_pipeline.liquid_world.boundaries().iter() { + let color = + Self::color(*self.boundary2color.get(&handle).unwrap_or(&default_color)); + + for point in &boundary.positions { + Self::draw_particle(window, point, color, size); + } + } + } } fn update_ui( &mut self, - _ui_context: &egui::Context, + ui_context: &egui::Context, _harness: &mut Harness, _graphics: &mut GraphicsManager, - _window: &mut Window, ) { + let _ = egui::Window::new("Fluids").show(ui_context, |ui| { + let _ = ui.checkbox( + &mut self.render_boundary_particles, + "Render boundary particles", + ); + + let selected = FLUIDS_RENDERING_MAP + .iter() + .find_map(|(name, mode)| (*mode == self.fluids_rendering_mode).then_some(*name)) + .unwrap_or("Custom"); + + let _ = egui::ComboBox::from_label("Rendering mode") + .selected_text(selected) + .show_ui(ui, |ui| { + for (name, mode) in FLUIDS_RENDERING_MAP { + let _ = ui.selectable_value(&mut self.fluids_rendering_mode, mode, name); + } + }); + }); } fn profiling_string(&self) -> String { diff --git a/src/liquid_world.rs b/src/liquid_world.rs index 2a15b7f..800402c 100644 --- a/src/liquid_world.rs +++ b/src/liquid_world.rs @@ -151,7 +151,11 @@ impl LiquidWorld { self.boundaries.as_slice(), ); - coupling.transmit_forces(&self.timestep_manager, &self.boundaries, self.boundary_force_coefficient); + coupling.transmit_forces( + &self.timestep_manager, + &self.boundaries, + self.boundary_force_coefficient, + ); self.counters.stages.solver_time.pause(); } @@ -245,12 +249,12 @@ impl LiquidWorld { } HGridEntry::BoundaryParticle(bid, pid) => { let (boundary, handle) = self.boundaries.get_from_contiguous_index(*bid)?; - + // Defensive bounds check for fluids if *pid >= boundary.positions.len() { return None; } - + let pt = boundary.positions[*pid]; if aabb.distance_to_local_point(pt.into(), true) < self.particle_radius { Some(ParticleId::BoundaryParticle(handle, *pid)) diff --git a/src/sampling/ray_sampling.rs b/src/sampling/ray_sampling.rs index df4cfb0..8e908d3 100644 --- a/src/sampling/ray_sampling.rs +++ b/src/sampling/ray_sampling.rs @@ -36,7 +36,7 @@ pub fn surface_ray_sample( let maxs: Vector = volume.maxs.into(); let origin: Vector = (volume.mins + parry::math::Vector::splat(subdivision_size / na::convert::<_, Real>(2.0))) - .into(); + .into(); let mut curr = origin; let mut perform_cast = |i, curr: Vector| { @@ -102,7 +102,7 @@ pub fn volume_ray_sample( let maxs: Vector = volume.maxs.into(); let origin: Vector = (volume.mins + parry::math::Vector::splat(subdivision_size / na::convert::<_, Real>(2.0))) - .into(); + .into(); let mut perform_cast = |i, curr: Vector| { let mut dir = Vector::zeros(); @@ -201,10 +201,7 @@ fn unquantize_points( ) -> Vec> { quantized_points .iter() - .map(|qpt| { - origin - + qpt.map(|e| na::convert::<_, Real>(e as f64) * subdivision_size) - }) + .map(|qpt| origin + qpt.map(|e| na::convert::<_, Real>(e as f64) * subdivision_size)) .collect() } diff --git a/src/z_order.rs b/src/z_order.rs index 3fa1051..de99561 100644 --- a/src/z_order.rs +++ b/src/z_order.rs @@ -9,8 +9,7 @@ pub fn apply_permutation(permutation: &[usize], data: &[T]) -> Vec pub fn compute_points_z_order(points: &[Vector]) -> Vec { let mut indices: Vec<_> = (0..points.len()).collect(); indices.sort_unstable_by(|i, j| { - z_order_floats(points[*i].as_slice(), points[*j].as_slice()) - .unwrap_or(Ordering::Equal) + z_order_floats(points[*i].as_slice(), points[*j].as_slice()).unwrap_or(Ordering::Equal) }); indices } From 4b1c4d518e019897565b427df6ef162cd94894a2 Mon Sep 17 00:00:00 2001 From: Dragos Daian Date: Sat, 20 Jun 2026 18:44:17 +0200 Subject: [PATCH 19/22] fix compilation error. --- src/integrations/rapier/testbed_plugin.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/integrations/rapier/testbed_plugin.rs b/src/integrations/rapier/testbed_plugin.rs index ce0eff5..23295ac 100644 --- a/src/integrations/rapier/testbed_plugin.rs +++ b/src/integrations/rapier/testbed_plugin.rs @@ -271,6 +271,7 @@ impl TestbedPlugin for FluidsTestbedPlugin { ui_context: &egui::Context, _harness: &mut Harness, _graphics: &mut GraphicsManager, + _window: &mut Window, ) { let _ = egui::Window::new("Fluids").show(ui_context, |ui| { let _ = ui.checkbox( From a8aeae547f7c360ad4433942ba6fa9b78ea87fbc Mon Sep 17 00:00:00 2001 From: Dragos Daian Date: Sat, 20 Jun 2026 18:47:53 +0200 Subject: [PATCH 20/22] draw them by calling hook. --- examples2d/basic2.rs | 2 +- examples2d/custom_forces2.rs | 2 +- examples2d/elasticity2.rs | 2 +- examples2d/layers2.rs | 2 +- examples2d/surface_tension2.rs | 2 +- examples3d/basic3.rs | 2 +- examples3d/custom_forces3.rs | 2 +- examples3d/elasticity3.rs | 2 +- examples3d/faucet3.rs | 2 +- examples3d/heightfield3.rs | 2 +- examples3d/surface_tension3.rs | 2 +- src/integrations/rapier/testbed_plugin.rs | 135 +++++++++++++++++----- 12 files changed, 114 insertions(+), 43 deletions(-) diff --git a/examples2d/basic2.rs b/examples2d/basic2.rs index 15713cf..e077f5a 100644 --- a/examples2d/basic2.rs +++ b/examples2d/basic2.rs @@ -135,7 +135,7 @@ pub fn init_world(testbed: &mut Testbed) { * Set up the testbed. */ plugin.set_pipeline(fluids_pipeline); - testbed.add_plugin(plugin); + plugin.add_to_testbed(testbed); testbed.set_world_with_params( bodies, colliders, diff --git a/examples2d/custom_forces2.rs b/examples2d/custom_forces2.rs index e3d88b5..5bf8cf7 100644 --- a/examples2d/custom_forces2.rs +++ b/examples2d/custom_forces2.rs @@ -46,7 +46,7 @@ pub fn init_world(testbed: &mut Testbed) { */ plugin.set_pipeline(fluids_pipeline); plugin.set_fluid_rendering_mode(FluidsRenderingMode::VelocityColor { min: 0.0, max: 5.0 }); - testbed.add_plugin(plugin); + plugin.add_to_testbed(testbed); testbed.set_world_with_params( bodies, colliders, diff --git a/examples2d/elasticity2.rs b/examples2d/elasticity2.rs index 69b20fe..67dc2d8 100644 --- a/examples2d/elasticity2.rs +++ b/examples2d/elasticity2.rs @@ -81,7 +81,7 @@ pub fn init_world(testbed: &mut Testbed) { */ plugin.set_pipeline(fluids_pipeline); plugin.set_fluid_rendering_mode(FluidsRenderingMode::VelocityColor { min: 0.0, max: 5.0 }); - testbed.add_plugin(plugin); + plugin.add_to_testbed(testbed); testbed.set_world_with_params( bodies, colliders, diff --git a/examples2d/layers2.rs b/examples2d/layers2.rs index 438b3e6..d92c4f1 100644 --- a/examples2d/layers2.rs +++ b/examples2d/layers2.rs @@ -174,7 +174,7 @@ pub fn init_world(testbed: &mut Testbed) { * Set up the testbed. */ plugin.set_pipeline(fluids_pipeline); - testbed.add_plugin(plugin); + plugin.add_to_testbed(testbed); testbed.set_world_with_params( bodies, colliders, diff --git a/examples2d/surface_tension2.rs b/examples2d/surface_tension2.rs index bd249ec..d677a4d 100644 --- a/examples2d/surface_tension2.rs +++ b/examples2d/surface_tension2.rs @@ -63,7 +63,7 @@ pub fn init_world(testbed: &mut Testbed) { */ plugin.set_pipeline(fluids_pipeline); plugin.set_fluid_rendering_mode(FluidsRenderingMode::VelocityColor { min: 0.0, max: 5.0 }); - testbed.add_plugin(plugin); + plugin.add_to_testbed(testbed); testbed.set_world_with_params( bodies, colliders, diff --git a/examples3d/basic3.rs b/examples3d/basic3.rs index 0832e22..a99dcc2 100644 --- a/examples3d/basic3.rs +++ b/examples3d/basic3.rs @@ -105,7 +105,7 @@ pub fn init_world(testbed: &mut Testbed) { plugin.set_pipeline(fluids_pipeline); plugin.set_fluid_color(fluid_handle, Vector3::new(0.8, 0.7, 1.0)); plugin.render_boundary_particles = true; - testbed.add_plugin(plugin); + plugin.add_to_testbed(testbed); // testbed.set_body_wireframe(ground_handle, true); testbed.set_world_with_params( bodies, diff --git a/examples3d/custom_forces3.rs b/examples3d/custom_forces3.rs index 70d860d..7d4f4f3 100644 --- a/examples3d/custom_forces3.rs +++ b/examples3d/custom_forces3.rs @@ -46,7 +46,7 @@ pub fn init_world(testbed: &mut Testbed) { */ plugin.set_pipeline(fluids_pipeline); plugin.set_fluid_rendering_mode(FluidsRenderingMode::VelocityColor { min: 0.0, max: 5.0 }); - testbed.add_plugin(plugin); + plugin.add_to_testbed(testbed); testbed.set_world_with_params( bodies, colliders, diff --git a/examples3d/elasticity3.rs b/examples3d/elasticity3.rs index bef67dd..aefa1d1 100644 --- a/examples3d/elasticity3.rs +++ b/examples3d/elasticity3.rs @@ -96,7 +96,7 @@ pub fn init_world(testbed: &mut Testbed) { */ plugin.set_pipeline(fluids_pipeline); plugin.set_fluid_rendering_mode(FluidsRenderingMode::VelocityColor { min: 0.0, max: 5.0 }); - testbed.add_plugin(plugin); + plugin.add_to_testbed(testbed); testbed.set_body_wireframe(ground_handle, true); testbed.set_world_with_params( bodies, diff --git a/examples3d/faucet3.rs b/examples3d/faucet3.rs index 2191245..d20912f 100644 --- a/examples3d/faucet3.rs +++ b/examples3d/faucet3.rs @@ -108,7 +108,7 @@ pub fn init_world(testbed: &mut Testbed) { * Set up the testbed. */ plugin.set_pipeline(fluids_pipeline); - testbed.add_plugin(plugin); + plugin.add_to_testbed(testbed); testbed.set_body_wireframe(ground_handle, true); testbed.set_world_with_params( bodies, diff --git a/examples3d/heightfield3.rs b/examples3d/heightfield3.rs index 33d95c5..32d8cdd 100644 --- a/examples3d/heightfield3.rs +++ b/examples3d/heightfield3.rs @@ -92,7 +92,7 @@ pub fn init_world(testbed: &mut Testbed) { plugin.set_pipeline(fluids_pipeline); plugin.set_fluid_color(fluid_handle, Vector::new(0.8, 0.7, 1.0).into()); // plugin.render_boundary_particles = true; - testbed.add_plugin(plugin); + plugin.add_to_testbed(testbed); testbed.set_body_wireframe(handle, true); testbed.integration_parameters_mut().dt = 1.0 / 200.0; // testbed.look_at(Vector3::new(3.0, 3.0, 3.0), Vector3::origin()); diff --git a/examples3d/surface_tension3.rs b/examples3d/surface_tension3.rs index f670797..292aa9f 100644 --- a/examples3d/surface_tension3.rs +++ b/examples3d/surface_tension3.rs @@ -69,7 +69,7 @@ pub fn init_world(testbed: &mut Testbed) { */ plugin.set_pipeline(fluids_pipeline); plugin.set_fluid_rendering_mode(FluidsRenderingMode::VelocityColor { min: 0.0, max: 5.0 }); - testbed.add_plugin(plugin); + plugin.add_to_testbed(testbed); testbed.set_body_wireframe(ground_handle, true); testbed.set_world_with_params( bodies, diff --git a/src/integrations/rapier/testbed_plugin.rs b/src/integrations/rapier/testbed_plugin.rs index 23295ac..6e9d14c 100644 --- a/src/integrations/rapier/testbed_plugin.rs +++ b/src/integrations/rapier/testbed_plugin.rs @@ -6,10 +6,14 @@ use kiss3d::prelude::Vec2; use kiss3d::prelude::Vec3; use kiss3d::{color::Color, window::Window}; use na::Vector3; -use rapier_testbed::{egui, harness::Harness, GraphicsManager, PhysicsState, TestbedPlugin}; +use rapier_testbed::{ + egui, harness::Harness, GraphicsManager, PhysicsState, Testbed, TestbedPlugin, +}; use crate::integrations::rapier::FluidsPipeline; +use std::cell::RefCell; use std::collections::HashMap; +use std::rc::Rc; pub const FLUIDS_RENDERING_MAP: [(&str, FluidsRenderingMode); 3] = [ ("Static", FluidsRenderingMode::StaticColor), @@ -67,6 +71,8 @@ pub struct FluidsTestbedPlugin { default_fluid_color: Vector3, } +struct SharedFluidsTestbedPlugin(Rc>); + impl FluidsTestbedPlugin { /// Initializes the plugin. pub fn new() -> Self { @@ -87,6 +93,20 @@ impl FluidsTestbedPlugin { self.callbacks.push(Box::new(f)) } + /// Adds this plugin to the Rapier testbed and registers fluid rendering. + pub fn add_to_testbed(self, testbed: &mut Testbed) { + let plugin = Rc::new(RefCell::new(self)); + let renderer = Rc::clone(&plugin); + + testbed.add_callback(move |graphics, _physics, _events, _run_state| { + if let Some(graphics) = graphics { + renderer.borrow().draw_fluids(graphics.window); + } + }); + + testbed.add_plugin(SharedFluidsTestbedPlugin(plugin)); + } + /// Sets the fluids pipeline used by the testbed. pub fn set_pipeline(&mut self, fluids_pipeline: FluidsPipeline) { self.fluids_pipeline = fluids_pipeline; @@ -196,6 +216,40 @@ impl FluidsTestbedPlugin { let end = *point + *velocity * na::convert::<_, Real>(0.02); window.draw_line(Self::point(point), Self::point(&end), color, 1.5, false); } + + fn draw_fluids(&self, window: &mut Window) { + let draw_velocities = matches!( + self.fluids_rendering_mode, + FluidsRenderingMode::VelocityArrows { .. } + ); + + for (handle, fluid) in self.fluids_pipeline.liquid_world.fluids().iter() { + let size = Self::particle_size(fluid.particle_radius()); + + for (point, velocity) in fluid.positions.iter().zip(fluid.velocities.iter()) { + let color = self.fluid_color(handle, velocity); + Self::draw_particle(window, point, color, size); + + if draw_velocities && velocity.norm_squared() > na::zero() { + Self::draw_velocity(window, point, velocity, color); + } + } + } + + if self.render_boundary_particles { + let default_color = Vector3::repeat(na::convert::<_, Real>(0.5)); + let size = Self::particle_size(self.fluids_pipeline.liquid_world.particle_radius()); + + for (handle, boundary) in self.fluids_pipeline.liquid_world.boundaries().iter() { + let color = + Self::color(*self.boundary2color.get(&handle).unwrap_or(&default_color)); + + for point in &boundary.positions { + Self::draw_particle(window, point, color, size); + } + } + } + } } impl TestbedPlugin for FluidsTestbedPlugin { @@ -233,37 +287,7 @@ impl TestbedPlugin for FluidsTestbedPlugin { window: &mut Window, _harness: &mut Harness, ) { - let draw_velocities = matches!( - self.fluids_rendering_mode, - FluidsRenderingMode::VelocityArrows { .. } - ); - - for (handle, fluid) in self.fluids_pipeline.liquid_world.fluids().iter() { - let size = Self::particle_size(fluid.particle_radius()); - - for (point, velocity) in fluid.positions.iter().zip(fluid.velocities.iter()) { - let color = self.fluid_color(handle, velocity); - Self::draw_particle(window, point, color, size); - - if draw_velocities && velocity.norm_squared() > na::zero() { - Self::draw_velocity(window, point, velocity, color); - } - } - } - - if self.render_boundary_particles { - let default_color = Vector3::repeat(na::convert::<_, Real>(0.5)); - let size = Self::particle_size(self.fluids_pipeline.liquid_world.particle_radius()); - - for (handle, boundary) in self.fluids_pipeline.liquid_world.boundaries().iter() { - let color = - Self::color(*self.boundary2color.get(&handle).unwrap_or(&default_color)); - - for point in &boundary.positions { - Self::draw_particle(window, point, color, size); - } - } - } + self.draw_fluids(window); } fn update_ui( @@ -298,3 +322,50 @@ impl TestbedPlugin for FluidsTestbedPlugin { format!("Fluids: {:.2}ms", self.step_time) } } + +impl TestbedPlugin for SharedFluidsTestbedPlugin { + fn init_plugin(&mut self) { + self.0.borrow_mut().init_plugin(); + } + + fn init_graphics( + &mut self, + graphics: &mut GraphicsManager, + window: &mut Window, + harness: &mut Harness, + ) { + self.0.borrow_mut().init_graphics(graphics, window, harness); + } + + fn clear_graphics(&mut self, graphics: &mut GraphicsManager, window: &mut Window) { + self.0.borrow_mut().clear_graphics(graphics, window); + } + + fn run_callbacks(&mut self, harness: &mut Harness) { + self.0.borrow_mut().run_callbacks(harness); + } + + fn step(&mut self, physics: &mut PhysicsState) { + self.0.borrow_mut().step(physics); + } + + fn draw(&mut self, graphics: &mut GraphicsManager, window: &mut Window, harness: &mut Harness) { + self.0.borrow_mut().draw(graphics, window, harness); + } + + fn update_ui( + &mut self, + ui_context: &egui::Context, + harness: &mut Harness, + graphics: &mut GraphicsManager, + window: &mut Window, + ) { + self.0 + .borrow_mut() + .update_ui(ui_context, harness, graphics, window); + } + + fn profiling_string(&self) -> String { + self.0.borrow().profiling_string() + } +} From 3cf1785d0d3959c12de4b6470df9cb7f098727f2 Mon Sep 17 00:00:00 2001 From: Dragos Daian Date: Sun, 21 Jun 2026 18:14:20 +0200 Subject: [PATCH 21/22] Add knobs to change params of salva. --- examples2d/Cargo.toml | 2 +- src/integrations/rapier/fluids_pipeline.rs | 52 +++++++++++- src/integrations/rapier/mod.rs | 1 + src/integrations/rapier/testbed_plugin.rs | 99 +++++++++++++++++++++- src/liquid_world.rs | 12 ++- src/solver/pressure/dfsph_solver.rs | 81 ++++++++++++++++-- src/solver/pressure/mod.rs | 2 +- src/solver/pressure/pressure_solver.rs | 11 +++ 8 files changed, 246 insertions(+), 14 deletions(-) diff --git a/examples2d/Cargo.toml b/examples2d/Cargo.toml index fae9220..49c1820 100644 --- a/examples2d/Cargo.toml +++ b/examples2d/Cargo.toml @@ -6,7 +6,7 @@ edition = "2018" [features] default = ["parallel"] -parallel = [ "rapier_testbed2d/parallel"] +parallel = [ "rapier_testbed2d/parallel", "salva2d/parallel" ] [dependencies] Inflector = "0.11" diff --git a/src/integrations/rapier/fluids_pipeline.rs b/src/integrations/rapier/fluids_pipeline.rs index 0486ff6..d5d5f9c 100644 --- a/src/integrations/rapier/fluids_pipeline.rs +++ b/src/integrations/rapier/fluids_pipeline.rs @@ -1,7 +1,7 @@ use crate::coupling::CouplingManager; use crate::geometry::{HGrid, HGridEntry}; use crate::object::{BoundaryHandle, BoundarySet, Fluid}; -use crate::solver::DFSPHSolver; +use crate::solver::{DFSPHSolver, DfsphParameters, PressureSolver}; use crate::TimestepManager; use crate::{math, LiquidWorld}; use approx::AbsDiffEq; @@ -55,6 +55,16 @@ impl FluidsPipeline { Self::new_with_boundary_coef(particle_radius, smoothing_factor, na::one::()) } + /// Initialize a new pipeline with custom DFSPH solver parameters. + pub fn new_with_dfsph_parameters( + particle_radius: math::Real, + smoothing_factor: math::Real, + dfsph_parameters: DfsphParameters, + ) -> Self { + let dfsph: DFSPHSolver = DFSPHSolver::with_parameters(dfsph_parameters); + Self::new_with_solver(dfsph, particle_radius, smoothing_factor) + } + /// Initialize a new pipeline for fluids simulation with a custom boundary force coefficient. /// /// # Parameters @@ -70,10 +80,38 @@ impl FluidsPipeline { boundary_force_coefficient: math::Real, ) -> Self { let dfsph: DFSPHSolver = DFSPHSolver::new(); + Self::new_with_solver_and_boundary_coef( + dfsph, + particle_radius, + smoothing_factor, + boundary_force_coefficient, + ) + } + /// Initialize a new pipeline with a custom pressure solver. + pub fn new_with_solver( + solver: impl PressureSolver + Send + Sync + 'static, + particle_radius: math::Real, + smoothing_factor: math::Real, + ) -> Self { + Self::new_with_solver_and_boundary_coef( + solver, + particle_radius, + smoothing_factor, + na::one::(), + ) + } + + /// Initialize a new pipeline with a custom pressure solver and boundary force coefficient. + pub fn new_with_solver_and_boundary_coef( + solver: impl PressureSolver + Send + Sync + 'static, + particle_radius: math::Real, + smoothing_factor: math::Real, + boundary_force_coefficient: math::Real, + ) -> Self { Self { liquid_world: LiquidWorld::new( - dfsph, + solver, particle_radius, smoothing_factor, boundary_force_coefficient, @@ -82,6 +120,16 @@ impl FluidsPipeline { } } + /// Current DFSPH tuning parameters, if this pipeline uses DFSPH. + pub fn dfsph_parameters(&self) -> Option { + self.liquid_world.dfsph_parameters() + } + + /// Set DFSPH tuning parameters if this pipeline uses DFSPH. + pub fn set_dfsph_parameters(&mut self, parameters: DfsphParameters) -> bool { + self.liquid_world.set_dfsph_parameters(parameters) + } + /// Advances the fluid simulation by `dt` seconds. /// /// All the fluid particles will be affected by an acceleration equal to `gravity`. diff --git a/src/integrations/rapier/mod.rs b/src/integrations/rapier/mod.rs index 7ffd0a5..4e354be 100644 --- a/src/integrations/rapier/mod.rs +++ b/src/integrations/rapier/mod.rs @@ -1,5 +1,6 @@ //! Two-way coupling with the Rapier physics engine. +pub use crate::solver::DfsphParameters; pub use fluids_pipeline::{ ColliderCouplingManager, ColliderCouplingSet, ColliderSampling, FluidsPipeline, }; diff --git a/src/integrations/rapier/testbed_plugin.rs b/src/integrations/rapier/testbed_plugin.rs index 6e9d14c..4c56cee 100644 --- a/src/integrations/rapier/testbed_plugin.rs +++ b/src/integrations/rapier/testbed_plugin.rs @@ -7,10 +7,11 @@ use kiss3d::prelude::Vec3; use kiss3d::{color::Color, window::Window}; use na::Vector3; use rapier_testbed::{ - egui, harness::Harness, GraphicsManager, PhysicsState, Testbed, TestbedPlugin, + egui, harness::Harness, settings::ExampleSettings, GraphicsManager, PhysicsState, Testbed, + TestbedPlugin, }; -use crate::integrations::rapier::FluidsPipeline; +use crate::integrations::rapier::{DfsphParameters, FluidsPipeline}; use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; @@ -33,6 +34,14 @@ pub const FLUIDS_RENDERING_MAP: [(&str, FluidsRenderingMode); 3] = [ ), ]; +const SETTINGS_RENDER_BOUNDARIES: &str = "Fluid render boundaries"; +const SETTINGS_RENDERING_MODE: &str = "Fluid rendering mode"; +const SETTINGS_MAX_PRESSURE_ITER: &str = "Fluid max pressure iter"; +const SETTINGS_MAX_DIVERGENCE_ITER: &str = "Fluid max divergence iter"; +const SETTINGS_MAX_DENSITY_ERROR: &str = "Fluid max density error"; +const SETTINGS_MAX_DIVERGENCE_ERROR: &str = "Fluid max divergence error"; +const SETTINGS_BOUNDARY_FORCE_COEFFICIENT: &str = "Fluid boundary force"; + /// How the fluids should be rendered by the testbed. #[derive(Copy, Clone, Debug, PartialEq)] pub enum FluidsRenderingMode { @@ -66,6 +75,7 @@ pub struct FluidsTestbedPlugin { callbacks: Vec, step_time: f64, fluids_pipeline: FluidsPipeline, + dfsph_parameters: DfsphParameters, f2color: HashMap>, boundary2color: HashMap>, default_fluid_color: Vector3, @@ -82,6 +92,7 @@ impl FluidsTestbedPlugin { step_time: 0.0, callbacks: Vec::new(), fluids_pipeline: FluidsPipeline::new(0.025, 2.0), + dfsph_parameters: DfsphParameters::default(), f2color: HashMap::new(), boundary2color: HashMap::new(), default_fluid_color: Vector3::new(0.0, 0.0, 0.5), @@ -100,7 +111,13 @@ impl FluidsTestbedPlugin { testbed.add_callback(move |graphics, _physics, _events, _run_state| { if let Some(graphics) = graphics { - renderer.borrow().draw_fluids(graphics.window); + let mut renderer = renderer.borrow_mut(); + + if let Some(settings) = graphics.settings.as_deref_mut() { + renderer.update_from_settings(settings); + } + + renderer.draw_fluids(graphics.window); } }); @@ -111,6 +128,7 @@ impl FluidsTestbedPlugin { pub fn set_pipeline(&mut self, fluids_pipeline: FluidsPipeline) { self.fluids_pipeline = fluids_pipeline; self.fluids_pipeline.liquid_world.counters.enable(); + self.refresh_dfsph_parameters(); } /// Sets the color used to render the specified fluid. @@ -175,6 +193,81 @@ impl FluidsTestbedPlugin { (radius as f32 * 300.0).clamp(2.5, 8.0) } + fn refresh_dfsph_parameters(&mut self) { + if let Some(parameters) = self.fluids_pipeline.dfsph_parameters() { + self.dfsph_parameters = parameters; + } + } + + fn rendering_mode_index(&self) -> usize { + FLUIDS_RENDERING_MAP + .iter() + .position(|(_, mode)| *mode == self.fluids_rendering_mode) + .unwrap_or(0) + } + + fn update_from_settings(&mut self, settings: &mut ExampleSettings) { + self.render_boundary_particles = + settings.get_or_set_bool(SETTINGS_RENDER_BOUNDARIES, self.render_boundary_particles); + + let rendering_options = FLUIDS_RENDERING_MAP + .iter() + .map(|(name, _)| (*name).to_string()) + .collect(); + let rendering_mode = settings.get_or_set_string( + SETTINGS_RENDERING_MODE, + self.rendering_mode_index(), + rendering_options, + ); + + if let Some((_, mode)) = FLUIDS_RENDERING_MAP.get(rendering_mode) { + self.fluids_rendering_mode = *mode; + } + + let max_pressure_iter = settings.get_or_set_u32( + SETTINGS_MAX_PRESSURE_ITER, + self.dfsph_parameters.max_pressure_iter as u32, + 0..=80, + ) as usize; + let max_divergence_iter = settings.get_or_set_u32( + SETTINGS_MAX_DIVERGENCE_ITER, + self.dfsph_parameters.max_divergence_iter as u32, + 0..=80, + ) as usize; + let max_density_error = settings.get_or_set_f32( + SETTINGS_MAX_DENSITY_ERROR, + self.dfsph_parameters.max_density_error as f32, + 0.0..=0.5, + ); + let max_divergence_error = settings.get_or_set_f32( + SETTINGS_MAX_DIVERGENCE_ERROR, + self.dfsph_parameters.max_divergence_error as f32, + 0.0..=2.0, + ); + + let boundary_force = settings.get_or_set_f32( + SETTINGS_BOUNDARY_FORCE_COEFFICIENT, + self.fluids_pipeline.liquid_world.boundary_force_coefficient as f32, + 0.0..=1.0, + ); + self.fluids_pipeline.liquid_world.boundary_force_coefficient = + na::convert::<_, Real>(boundary_force); + + let dfsph_parameters = DfsphParameters { + min_pressure_iter: self.dfsph_parameters.min_pressure_iter, + max_pressure_iter, + max_density_error: na::convert::<_, Real>(max_density_error), + min_divergence_iter: self.dfsph_parameters.min_divergence_iter, + max_divergence_iter, + max_divergence_error: na::convert::<_, Real>(max_divergence_error), + }; + + if dfsph_parameters != self.dfsph_parameters { + self.dfsph_parameters = dfsph_parameters; + let _ = self.fluids_pipeline.set_dfsph_parameters(dfsph_parameters); + } + } + #[cfg(feature = "dim2")] fn point(point: &Vector) -> Vec2 { Vec2::new(point.x as f32, point.y as f32) diff --git a/src/liquid_world.rs b/src/liquid_world.rs index 800402c..610e3af 100644 --- a/src/liquid_world.rs +++ b/src/liquid_world.rs @@ -4,7 +4,7 @@ use crate::geometry::{self, ContactManager, HGrid, HGridEntry}; use crate::math::{Real, Vector}; use crate::object::{Boundary, BoundaryHandle, BoundarySet}; use crate::object::{Fluid, FluidHandle, FluidSet}; -use crate::solver::PressureSolver; +use crate::solver::{DfsphParameters, PressureSolver}; use crate::TimestepManager; #[cfg(feature = "parry")] use { @@ -71,6 +71,16 @@ impl LiquidWorld { self.step_with_coupling(dt, gravity, &mut ()) } + /// Current DFSPH tuning parameters, if this world uses DFSPH. + pub fn dfsph_parameters(&self) -> Option { + self.solver.dfsph_parameters() + } + + /// Set DFSPH tuning parameters if this world uses DFSPH. + pub fn set_dfsph_parameters(&mut self, parameters: DfsphParameters) -> bool { + self.solver.set_dfsph_parameters(parameters) + } + /// Advances the simulation by `dt` seconds, taking into account coupling with an external rigid-body engine. pub fn step_with_coupling( &mut self, diff --git a/src/solver/pressure/dfsph_solver.rs b/src/solver/pressure/dfsph_solver.rs index 74bcc8b..9c775f0 100644 --- a/src/solver/pressure/dfsph_solver.rs +++ b/src/solver/pressure/dfsph_solver.rs @@ -13,6 +13,36 @@ use crate::object::{Boundary, Fluid}; use crate::solver::{helper, PressureSolver}; use crate::TimestepManager; +/// Tuning parameters for the DFSPH solver. +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct DfsphParameters { + /// Minimum number of pressure iterations. + pub min_pressure_iter: usize, + /// Maximum number of pressure iterations. + pub max_pressure_iter: usize, + /// Maximum acceptable density error. + pub max_density_error: Real, + /// Minimum number of divergence iterations. + pub min_divergence_iter: usize, + /// Maximum number of divergence iterations. + pub max_divergence_iter: usize, + /// Maximum acceptable divergence error. + pub max_divergence_error: Real, +} + +impl Default for DfsphParameters { + fn default() -> Self { + Self { + min_pressure_iter: 1, + max_pressure_iter: 50, + max_density_error: na::convert::<_, Real>(0.05), + min_divergence_iter: 1, + max_divergence_iter: 50, + max_divergence_error: na::convert::<_, Real>(0.1), + } + } +} + /// A DFSPH (Divergence Free Smoothed Particle Hydrodynamics) pressure solver. pub struct DFSPHSolver< KernelDensity: Kernel = CubicSplineKernel, @@ -52,13 +82,14 @@ where { /// Initialize a new DFSPH pressure solver. pub fn new() -> Self { + let parameters = DfsphParameters::default(); Self { - min_pressure_iter: 1, - max_pressure_iter: 50, - max_density_error: na::convert::<_, Real>(0.05), - min_divergence_iter: 1, - max_divergence_iter: 50, - max_divergence_error: na::convert::<_, Real>(0.1), + min_pressure_iter: parameters.min_pressure_iter, + max_pressure_iter: parameters.max_pressure_iter, + max_density_error: parameters.max_density_error, + min_divergence_iter: parameters.min_divergence_iter, + max_divergence_iter: parameters.max_divergence_iter, + max_divergence_error: parameters.max_divergence_error, min_neighbors_for_divergence_solve: if DIM == 2 { 6 } else { 20 }, alphas: Vec::new(), densities: Vec::new(), @@ -69,6 +100,35 @@ where } } + /// Initialize a new DFSPH pressure solver with custom parameters. + pub fn with_parameters(parameters: DfsphParameters) -> Self { + let mut solver = Self::new(); + solver.set_parameters(parameters); + solver + } + + /// Current tuning parameters. + pub fn parameters(&self) -> DfsphParameters { + DfsphParameters { + min_pressure_iter: self.min_pressure_iter, + max_pressure_iter: self.max_pressure_iter, + max_density_error: self.max_density_error, + min_divergence_iter: self.min_divergence_iter, + max_divergence_iter: self.max_divergence_iter, + max_divergence_error: self.max_divergence_error, + } + } + + /// Set tuning parameters. + pub fn set_parameters(&mut self, parameters: DfsphParameters) { + self.min_pressure_iter = parameters.min_pressure_iter; + self.max_pressure_iter = parameters.max_pressure_iter; + self.max_density_error = parameters.max_density_error; + self.min_divergence_iter = parameters.min_divergence_iter; + self.max_divergence_iter = parameters.max_divergence_iter; + self.max_divergence_error = parameters.max_divergence_error; + } + fn compute_boundary_volumes( &mut self, boundary_boundary_contacts: &[ParticlesContacts], @@ -523,6 +583,15 @@ where KernelDensity: Kernel, KernelGradient: Kernel, { + fn dfsph_parameters(&self) -> Option { + Some(self.parameters()) + } + + fn set_dfsph_parameters(&mut self, parameters: DfsphParameters) -> bool { + self.set_parameters(parameters); + true + } + fn init_with_fluids(&mut self, fluids: &[Fluid]) { // Resize every buffer. self.alphas.resize(fluids.len(), Vec::new()); diff --git a/src/solver/pressure/mod.rs b/src/solver/pressure/mod.rs index ecdf262..e419cbd 100644 --- a/src/solver/pressure/mod.rs +++ b/src/solver/pressure/mod.rs @@ -1,4 +1,4 @@ -pub use self::dfsph_solver::DFSPHSolver; +pub use self::dfsph_solver::{DFSPHSolver, DfsphParameters}; pub use self::iisph_solver::IISPHSolver; pub use self::pressure_solver::PressureSolver; diff --git a/src/solver/pressure/pressure_solver.rs b/src/solver/pressure/pressure_solver.rs index 2e47cac..ce4d8cb 100644 --- a/src/solver/pressure/pressure_solver.rs +++ b/src/solver/pressure/pressure_solver.rs @@ -2,10 +2,21 @@ use crate::counters::Counters; use crate::geometry::ContactManager; use crate::math::{Real, Vector}; use crate::object::{Boundary, Fluid}; +use crate::solver::DfsphParameters; use crate::TimestepManager; /// Trait implemented by pressure solvers. pub trait PressureSolver { + /// Current DFSPH tuning parameters, if this solver is DFSPH. + fn dfsph_parameters(&self) -> Option { + None + } + + /// Set DFSPH tuning parameters if this solver is DFSPH. + fn set_dfsph_parameters(&mut self, _parameters: DfsphParameters) -> bool { + false + } + /// Initialize this solver with the given fluids. fn init_with_fluids(&mut self, fluids: &[Fluid]); From 26d75517cfd4ec9b0647dafcc5cc6bc58494780a Mon Sep 17 00:00:00 2001 From: Dragos Daian Date: Sun, 21 Jun 2026 21:19:16 +0200 Subject: [PATCH 22/22] fix crash on denominator zero --- src/integrations/rapier/testbed_plugin.rs | 26 +++++++++++++++++++---- src/solver/pressure/dfsph_solver.rs | 15 +++++++------ src/solver/pressure/iisph_solver.rs | 7 ++++-- 3 files changed, 36 insertions(+), 12 deletions(-) diff --git a/src/integrations/rapier/testbed_plugin.rs b/src/integrations/rapier/testbed_plugin.rs index 4c56cee..76a2679 100644 --- a/src/integrations/rapier/testbed_plugin.rs +++ b/src/integrations/rapier/testbed_plugin.rs @@ -206,6 +206,20 @@ impl FluidsTestbedPlugin { .unwrap_or(0) } + fn get_clamped_u32_setting( + settings: &mut ExampleSettings, + key: &'static str, + default: u32, + min: u32, + max: u32, + ) -> u32 { + let value = settings + .get_or_set_u32(key, default.clamp(min, max), min..=max) + .clamp(min, max); + settings.set_u32(key, value, min..=max); + value + } + fn update_from_settings(&mut self, settings: &mut ExampleSettings) { self.render_boundary_particles = settings.get_or_set_bool(SETTINGS_RENDER_BOUNDARIES, self.render_boundary_particles); @@ -224,15 +238,19 @@ impl FluidsTestbedPlugin { self.fluids_rendering_mode = *mode; } - let max_pressure_iter = settings.get_or_set_u32( + let max_pressure_iter = Self::get_clamped_u32_setting( + settings, SETTINGS_MAX_PRESSURE_ITER, self.dfsph_parameters.max_pressure_iter as u32, - 0..=80, + 1, + 80, ) as usize; - let max_divergence_iter = settings.get_or_set_u32( + let max_divergence_iter = Self::get_clamped_u32_setting( + settings, SETTINGS_MAX_DIVERGENCE_ITER, self.dfsph_parameters.max_divergence_iter as u32, - 0..=80, + 1, + 80, ) as usize; let max_density_error = settings.get_or_set_f32( SETTINGS_MAX_DENSITY_ERROR, diff --git a/src/solver/pressure/dfsph_solver.rs b/src/solver/pressure/dfsph_solver.rs index 9c775f0..5b02663 100644 --- a/src/solver/pressure/dfsph_solver.rs +++ b/src/solver/pressure/dfsph_solver.rs @@ -121,11 +121,11 @@ where /// Set tuning parameters. pub fn set_parameters(&mut self, parameters: DfsphParameters) { - self.min_pressure_iter = parameters.min_pressure_iter; - self.max_pressure_iter = parameters.max_pressure_iter; + self.max_pressure_iter = parameters.max_pressure_iter.max(1); + self.min_pressure_iter = parameters.min_pressure_iter.min(self.max_pressure_iter); self.max_density_error = parameters.max_density_error; - self.min_divergence_iter = parameters.min_divergence_iter; - self.max_divergence_iter = parameters.max_divergence_iter; + self.max_divergence_iter = parameters.max_divergence_iter.max(1); + self.min_divergence_iter = parameters.min_divergence_iter.min(self.max_divergence_iter); self.max_divergence_error = parameters.max_divergence_error; } @@ -149,8 +149,11 @@ where denominator += c.weight; } - assert!(!denominator.is_zero()); - *volume = na::one::() / denominator; + if denominator.is_zero() { + *volume = na::zero(); + } else { + *volume = na::one::() / denominator; + } }) } } diff --git a/src/solver/pressure/iisph_solver.rs b/src/solver/pressure/iisph_solver.rs index 135cf13..9e3c8e5 100644 --- a/src/solver/pressure/iisph_solver.rs +++ b/src/solver/pressure/iisph_solver.rs @@ -83,8 +83,11 @@ where denominator += c.weight; } - assert!(!denominator.is_zero()); - *volume = na::one::() / denominator; + if denominator.is_zero() { + *volume = na::zero(); + } else { + *volume = na::one::() / denominator; + } }) } }