diff options
| author | Crozet Sébastien <developer@crozet.re> | 2020-12-17 10:24:36 +0100 |
|---|---|---|
| committer | Crozet Sébastien <developer@crozet.re> | 2020-12-29 11:31:00 +0100 |
| commit | e231bacec608fa5efd24f7a876572927dbd6c9c4 (patch) | |
| tree | 596f0b6a1fc666586ffcd71d07a39a7c182c6ef8 | |
| parent | cc6d1b973002b4d366bc81ec6bf9e8240ad7b404 (diff) | |
| download | rapier-e231bacec608fa5efd24f7a876572927dbd6c9c4.tar.gz rapier-e231bacec608fa5efd24f7a876572927dbd6c9c4.tar.bz2 rapier-e231bacec608fa5efd24f7a876572927dbd6c9c4.zip | |
Move all the contact manifold computations out of Rapier.
49 files changed, 103 insertions, 2511 deletions
@@ -10,8 +10,8 @@ members = [ "build/rapier2d", "build/rapier_testbed2d", "examples2d", "benchmark #nphysics2d = { path = "../nphysics/build/nphysics2d" } #nphysics3d = { path = "../nphysics/build/nphysics3d" } #kiss3d = { path = "../kiss3d" } -buckler2d = { path = "../buckler/build/buckler2d" } -buckler3d = { path = "../buckler/build/buckler3d" } +eagl2d = { path = "../eagl/build/eagl2d" } +eagl3d = { path = "../eagl/build/eagl3d" } [profile.release] #debug = true diff --git a/build/rapier2d/Cargo.toml b/build/rapier2d/Cargo.toml index 987fa2f..c20833c 100644 --- a/build/rapier2d/Cargo.toml +++ b/build/rapier2d/Cargo.toml @@ -21,8 +21,8 @@ simd-nightly = [ "simba/packed_simd", "simd-is-enabled" ] # enabled with the "simd-stable" or "simd-nightly" feature. simd-is-enabled = [ ] wasm-bindgen = [ "instant/wasm-bindgen" ] -serde-serialize = [ "erased-serde", "nalgebra/serde-serialize", "buckler2d/serde-serialize", "serde", "generational-arena/serde", "bit-vec/serde", "arrayvec/serde" ] -enhanced-determinism = [ "simba/libm_force", "indexmap" ] +serde-serialize = [ "erased-serde", "nalgebra/serde-serialize", "eagl2d/serde-serialize", "serde", "generational-arena/serde", "bit-vec/serde", "arrayvec/serde" ] +enhanced-determinism = [ "simba/libm_force", "eagl2d/enhanced-determinism", "indexmap" ] [lib] name = "rapier2d" @@ -35,7 +35,7 @@ vec_map = "0.8" instant = { version = "0.1", features = [ "now" ]} num-traits = "0.2" nalgebra = "0.23" -buckler2d = "0.1" +eagl2d = "0.1" simba = "0.3" approx = "0.4" rayon = { version = "1", optional = true } diff --git a/build/rapier3d/Cargo.toml b/build/rapier3d/Cargo.toml index 7d5673b..a9abef8 100644 --- a/build/rapier3d/Cargo.toml +++ b/build/rapier3d/Cargo.toml @@ -15,14 +15,14 @@ edition = "2018" default = [ "dim3" ] dim3 = [ ] parallel = [ "rayon" ] -simd-stable = [ "buckler3d/simd-stable", "simba/wide", "simd-is-enabled" ] -simd-nightly = [ "buckler3d/simd-nightly", "simba/packed_simd", "simd-is-enabled" ] +simd-stable = [ "eagl3d/simd-stable", "simba/wide", "simd-is-enabled" ] +simd-nightly = [ "eagl3d/simd-nightly", "simba/packed_simd", "simd-is-enabled" ] # Do not enable this feature directly. It is automatically # enabled with the "simd-stable" or "simd-nightly" feature. simd-is-enabled = [ ] wasm-bindgen = [ "instant/wasm-bindgen" ] -serde-serialize = [ "erased-serde", "nalgebra/serde-serialize", "buckler3d/serde-serialize", "serde", "generational-arena/serde", "bit-vec/serde" ] -enhanced-determinism = [ "simba/libm_force", "indexmap" ] +serde-serialize = [ "erased-serde", "nalgebra/serde-serialize", "eagl3d/serde-serialize", "serde", "generational-arena/serde", "bit-vec/serde" ] +enhanced-determinism = [ "simba/libm_force", "eagl3d/enhanced-determinism" ] [lib] name = "rapier3d" @@ -35,7 +35,7 @@ vec_map = "0.8" instant = { version = "0.1", features = [ "now" ]} num-traits = "0.2" nalgebra = "0.23" -buckler3d = "0.1" +eagl3d = "0.1" simba = "0.3" approx = "0.4" rayon = { version = "1", optional = true } @@ -46,7 +46,6 @@ bit-vec = "0.6" rustc-hash = "1" serde = { version = "1", features = [ "derive" ], optional = true } erased-serde = { version = "0.3", optional = true } -indexmap = { version = "1", features = [ "serde-1" ], optional = true } downcast-rs = "1.2" num-derive = "0.3" bitflags = "1" diff --git a/build/rapier_testbed2d/Cargo.toml b/build/rapier_testbed2d/Cargo.toml index 72ead41..23be92e 100644 --- a/build/rapier_testbed2d/Cargo.toml +++ b/build/rapier_testbed2d/Cargo.toml @@ -31,7 +31,7 @@ instant = { version = "0.1", features = [ "web-sys", "now" ]} bitflags = "1" num_cpus = { version = "1", optional = true } wrapped2d = { version = "0.4", optional = true } -buckler2d = "0.1" +eagl2d = "0.1" ncollide2d = "0.26" nphysics2d = { version = "0.18", optional = true } crossbeam = "0.8" diff --git a/build/rapier_testbed3d/Cargo.toml b/build/rapier_testbed3d/Cargo.toml index d4ad764..2820a7c 100644 --- a/build/rapier_testbed3d/Cargo.toml +++ b/build/rapier_testbed3d/Cargo.toml @@ -30,7 +30,7 @@ instant = { version = "0.1", features = [ "web-sys", "now" ]} bitflags = "1" glam = { version = "0.10", optional = true } num_cpus = { version = "1", optional = true } -buckler3d = "0.1" +eagl3d = "0.1" ncollide3d = "0.26" nphysics3d = { version = "0.18", optional = true } physx = { version = "0.8", optional = true } diff --git a/src/data/arena.rs b/src/data/arena.rs index a3af45c..3a24cd1 100644 --- a/src/data/arena.rs +++ b/src/data/arena.rs @@ -3,7 +3,7 @@ //! See https://github.com/fitzgen/generational-arena/blob/master/src/lib.rs. //! This has been modified to have a fully deterministic deserialization (including for the order of //! Index attribution after a deserialization of the arena. -use buckler::partitioning::IndexedData; +use eagl::partitioning::IndexedData; use std::cmp; use std::iter::{self, Extend, FromIterator, FusedIterator}; use std::mem; diff --git a/src/data/hashmap.rs b/src/data/hashmap.rs deleted file mode 100644 index d2ea980..0000000 --- a/src/data/hashmap.rs +++ /dev/null @@ -1,137 +0,0 @@ -//! A hash-map that behaves deterministically when the -//! `enhanced-determinism` feature is enabled. - -#[cfg(all(feature = "enhanced-determinism", feature = "serde-serialize"))] -use indexmap::IndexMap as StdHashMap; -#[cfg(all(not(feature = "enhanced-determinism"), feature = "serde-serialize"))] -use std::collections::HashMap as StdHashMap; - -/// Serializes only the capacity of a hash-map instead of its actual content. -#[cfg(feature = "serde-serialize")] -pub fn serialize_hashmap_capacity<S: serde::Serializer, K, V, H: std::hash::BuildHasher>( - map: &StdHashMap<K, V, H>, - s: S, -) -> Result<S::Ok, S::Error> { - s.serialize_u64(map.capacity() as u64) -} - -/// Creates a new hash-map with its capacity deserialized from `d`. -#[cfg(feature = "serde-serialize")] -pub fn deserialize_hashmap_capacity< - 'de, - D: serde::Deserializer<'de>, - K, - V, - H: std::hash::BuildHasher + Default, ->( - d: D, -) -> Result<StdHashMap<K, V, H>, D::Error> { - struct CapacityVisitor; - impl<'de> serde::de::Visitor<'de> for CapacityVisitor { - type Value = u64; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(formatter, "an integer between 0 and 2^64") - } - - fn visit_u64<E: serde::de::Error>(self, val: u64) -> Result<Self::Value, E> { - Ok(val) - } - } - - let capacity = d.deserialize_u64(CapacityVisitor)? as usize; - Ok(StdHashMap::with_capacity_and_hasher( - capacity, - Default::default(), - )) -} - -/* - * FxHasher taken from rustc_hash, except that it does not depend on the pointer size. - */ -#[cfg(feature = "enhanced-determinism")] -pub type FxHashMap32<K, V> = indexmap::IndexMap<K, V, std::hash::BuildHasherDefault<FxHasher32>>; -#[cfg(feature = "enhanced-determinism")] -pub use {self::FxHashMap32 as HashMap, indexmap::map::Entry}; -#[cfg(not(feature = "enhanced-determinism"))] -pub use {rustc_hash::FxHashMap as HashMap, std::collections::hash_map::Entry}; - -const K: u32 = 0x9e3779b9; - -// Same as FxHasher, but with the guarantee that the internal hash is -// an u32 instead of something that depends on the platform. -pub struct FxHasher32 { - hash: u32, -} - -impl Default for FxHasher32 { - #[inline] - fn default() -> FxHasher32 { - FxHasher32 { hash: 0 } - } -} - -impl FxHasher32 { - #[inline] - fn add_to_hash(&mut self, i: u32) { - use std::ops::BitXor; - self.hash = self.hash.rotate_left(5).bitxor(i).wrapping_mul(K); - } -} - -impl std::hash::Hasher for FxHasher32 { - #[inline] - fn write(&mut self, mut bytes: &[u8]) { - use std::convert::TryInto; - let read_u32 = |bytes: &[u8]| u32::from_ne_bytes(bytes[..4].try_into().unwrap()); - let mut hash = FxHasher32 { hash: self.hash }; - assert!(std::mem::size_of::<u32>() <= 8); - while bytes.len() >= std::mem::size_of::<u32>() { - hash.add_to_hash(read_u32(bytes) as u32); - bytes = &bytes[std::mem::size_of::<u32>()..]; - } - if (std::mem::size_of::<u32>() > 4) && (bytes.len() >= 4) { - hash.add_to_hash(u32::from_ne_bytes(bytes[..4].try_into().unwrap()) as u32); - bytes = &bytes[4..]; - } - if (std::mem::size_of::<u32>() > 2) && bytes.len() >= 2 { - hash.add_to_hash(u16::from_ne_bytes(bytes[..2].try_into().unwrap()) as u32); - bytes = &bytes[2..]; - } - if (std::mem::size_of::<u32>() > 1) && bytes.len() >= 1 { - hash.add_to_hash(bytes[0] as u32); - } - self.hash = hash.hash; - } - - #[inline] - fn write_u8(&mut self, i: u8) { - self.add_to_hash(i as u32); - } - - #[inline] - fn write_u16(&mut self, i: u16) { - self.add_to_hash(i as u32); - } - - #[inline] - fn write_u32(&mut self, i: u32) { - self.add_to_hash(i as u32); - } - - #[inline] - fn write_u64(&mut self, i: u64) { - self.add_to_hash(i as u32); - self.add_to_hash((i >> 32) as u32); - } - - #[inline] - fn write_usize(&mut self, i: usize) { - self.add_to_hash(i as u32); - } - - #[inline] - fn finish(&self) -> u64 { - self.hash as u64 - } -} diff --git a/src/data/maybe_serializable_data.rs b/src/data/maybe_serializable_data.rs deleted file mode 100644 index 8b14e1a..0000000 --- a/src/data/maybe_serializable_data.rs +++ /dev/null @@ -1,17 +0,0 @@ -use downcast_rs::{impl_downcast, DowncastSync}; -#[cfg(feature = "serde-serialize")] -use erased_serde::Serialize; - -/// Piece of data that may be serializable. -pub trait MaybeSerializableData: DowncastSync { - /// Convert this shape as a serializable entity. - #[cfg(feature = "serde-serialize")] - fn as_serialize(&self) -> Option<(u32, &dyn Serialize)> { - None - } - - /// Clones `self`. - fn clone_dyn(&self) -> Box<dyn MaybeSerializableData>; -} - -impl_downcast!(sync MaybeSerializableData); diff --git a/src/data/mod.rs b/src/data/mod.rs index 672bf94..eb0a229 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -1,11 +1,9 @@ //! Data structures modified with guaranteed deterministic behavior after deserialization. pub use self::coarena::Coarena; -pub use self::maybe_serializable_data::MaybeSerializableData; +pub use eagl::utils::MaybeSerializableData; pub mod arena; mod coarena; pub(crate) mod graph; -pub(crate) mod hashmap; -mod maybe_serializable_data; pub mod pubsub; diff --git a/src/dynamics/mod.rs b/src/dynamics/mod.rs index 28f149a..76e9de2 100644 --- a/src/dynamics/mod.rs +++ b/src/dynamics/mod.rs @@ -9,7 +9,7 @@ pub use self::joint::{ }; pub use self::rigid_body::{ActivationStatus, BodyStatus, RigidBody, RigidBodyBuilder}; pub use self::rigid_body_set::{BodyPair, RigidBodyHandle, RigidBodySet}; -pub use buckler::shape::MassProperties; +pub use eagl::shape::MassProperties; // #[cfg(not(feature = "parallel"))] pub(crate) use self::joint::JointGraphEdge; pub(crate) use self::rigid_body::RigidBodyChanges; diff --git a/src/dynamics/solver/joint_constraint/fixed_velocity_constraint_wide.rs b/src/dynamics/solver/joint_constraint/fixed_velocity_constraint_wide.rs index 79c69c6..ce42da8 100644 --- a/src/dynamics/solver/joint_constraint/fixed_velocity_constraint_wide.rs +++ b/src/dynamics/solver/joint_constraint/fixed_velocity_constraint_wide.rs @@ -13,7 +13,7 @@ use crate::utils::{WAngularInertia, WCross, WCrossMatrix}; use na::{Cholesky, Matrix6, Vector6, U3}; #[cfg(feature = "dim2")] use { - crate::utils::SdpMatrix3, + eagl::utils::SdpMatrix3, na::{Matrix3, Vector3}, }; diff --git a/src/dynamics/solver/joint_constraint/prismatic_velocity_constraint.rs b/src/dynamics/solver/joint_constraint/prismatic_velocity_constraint.rs index 49cfc7a..a2c7c2c 100644 --- a/src/dynamics/solver/joint_constraint/prismatic_velocity_constraint.rs +++ b/src/dynamics/solver/joint_constraint/prismatic_velocity_constraint.rs @@ -8,7 +8,7 @@ use crate::utils::{WAngularInertia, WCross, WCrossMatrix}; use na::{Cholesky, Matrix3x2, Matrix5, Vector5, U2, U3}; #[cfg(feature = "dim2")] use { - crate::utils::SdpMatrix2, + eagl::utils::SdpMatrix2, na::{Matrix2, Vector2}, }; diff --git a/src/dynamics/solver/joint_constraint/prismatic_velocity_constraint_wide.rs b/src/dynamics/solver/joint_constraint/prismatic_velocity_constraint_wide.rs index c05c08e..86e0c78 100644 --- a/src/dynamics/solver/joint_constraint/prismatic_velocity_constraint_wide.rs +++ b/src/dynamics/solver/joint_constraint/prismatic_velocity_constraint_wide.rs @@ -12,7 +12,7 @@ use crate::utils::{WAngularInertia, WCross, WCrossMatrix}; use na::{Cholesky, Matrix3x2, Matrix5, Vector5, U2, U3}; #[cfg(feature = "dim2")] use { - crate::utils::SdpMatrix2, + eagl::utils::SdpMatrix2, na::{Matrix2, Vector2}, }; diff --git a/src/geometry/broad_phase_multi_sap.rs b/src/geometry/broad_phase_multi_sap.rs index 56c05df..4242d77 100644 --- a/src/geometry/broad_phase_multi_sap.rs +++ b/src/geometry/broad_phase_multi_sap.rs @@ -1,10 +1,10 @@ -use crate::data::hashmap::HashMap; use crate::data::pubsub::Subscription; use crate::dynamics::RigidBodySet; use crate::geometry::{ColliderHandle, ColliderSet, RemovedCollider}; use crate::math::{Point, Vector, DIM}; use bit_vec::BitVec; -use buckler::bounding_volume::{BoundingVolume, AABB}; +use eagl::bounding_volume::{BoundingVolume, AABB}; +use eagl::utils::hashmap::HashMap; use std::cmp::Ordering; use std::ops::{Index, IndexMut}; @@ -477,8 +477,8 @@ pub struct BroadPhase { #[cfg_attr( feature = "serde-serialize", serde( - serialize_with = "crate::data::hashmap::serialize_hashmap_capacity", - deserialize_with = "crate::data::hashmap::deserialize_hashmap_capacity" + serialize_with = "eagl::utils::hashmap::serialize_hashmap_capacity", + deserialize_with = "eagl::utils::hashmap::deserialize_hashmap_capacity" ) )] reporting: HashMap<(u32, u32), bool>, // Workspace diff --git a/src/geometry/collider.rs b/src/geometry/collider.rs index 1275358..db418a3 100644 --- a/src/geometry/collider.rs +++ b/src/geometry/collider.rs @@ -1,13 +1,13 @@ -use crate::buckler::shape::HalfSpace; use crate::dynamics::{MassProperties, RigidBodyHandle, RigidBodySet}; +use crate::eagl::shape::HalfSpace; use crate::geometry::InteractionGroups; use crate::math::{AngVector, Isometry, Point, Rotation, Vector}; -use buckler::bounding_volume::AABB; -use buckler::shape::{ +use eagl::bounding_volume::AABB; +use eagl::shape::{ Ball, Capsule, Cuboid, HeightField, Segment, Shape, ShapeType, TriMesh, Triangle, }; #[cfg(feature = "dim3")] -use buckler::shape::{Cone, Cylinder, RoundCylinder}; +use eagl::shape::{Cone, Cylinder, RoundCylinder}; use na::Point3; use std::ops::Deref; use std::sync::Arc; diff --git a/src/geometry/contact_generator/ball_ball_contact_generator.rs b/src/geometry/contact_generator/ball_ball_contact_generator.rs deleted file mode 100644 index f2ca7af..0000000 --- a/src/geometry/contact_generator/ball_ball_contact_generator.rs +++ /dev/null @@ -1,103 +0,0 @@ -use crate::geometry::contact_generator::PrimitiveContactGenerationContext; -use crate::geometry::{Contact, ContactManifoldData, KinematicsCategory}; -use crate::math::{Point, Vector}; -#[cfg(feature = "simd-is-enabled")] -use { - crate::geometry::contact_generator::PrimitiveContactGenerationContextSimd, - crate::geometry::{WBall, WContact}, - crate::math::{Isometry, SimdReal, SIMD_WIDTH}, - simba::simd::SimdValue, -}; - -#[cfg(feature = "simd-is-enabled")] -fn generate_contacts_simd(ball1: &WBall, ball2: &WBall, pos21: &Isometry<SimdReal>) -> WContact { - let dcenter = ball2.center - ball1.center; - let center_dist = dcenter.magnitude(); - let normal = dcenter / center_dist; - - WContact { - local_p1: ball1.center + normal * ball1.radius, - local_p2: pos21 * (ball2.center - normal * ball2.radius), - local_n1: normal, - local_n2: pos21 * -normal, - fid1: [0; SIMD_WIDTH], - fid2: [0; SIMD_WIDTH], - dist: center_dist - ball1.radius - ball2.radius, - } -} - -#[cfg(feature = "simd-is-enabled")] -pub fn generate_contacts_ball_ball_simd(ctxt: &mut PrimitiveContactGenerationContextSimd) { - let pos_ba = ctxt.positions2.inverse() * ctxt.positions1; - let radii_a = - SimdReal::from(array![|ii| ctxt.shapes1[ii].as_ball().unwrap().radius; SIMD_WIDTH]); - let radii_b = - SimdReal::from(array![|ii| ctxt.shapes2[ii].as_ball().unwrap().radius; SIMD_WIDTH]); - - let wball_a = WBall::new(Point::origin(), radii_a); - let wball_b = WBall::new(pos_ba.inverse_transform_point(&Point::origin()), radii_b); - let contacts = generate_contacts_simd(&wball_a, &wball_b, &pos_ba); - - for (i, manifold) in ctxt.manifolds.iter_mut().enumerate() { - // FIXME: compare the dist before extracting the contact. - let (contact, local_n1, local_n2) = contacts.extract(i); - if contact.dist <= ctxt.prediction_distance { - if manifold.points.len() != 0 { - manifold.points[0].copy_geometry_from(contact); - } else { - manifold.points.push(contact); - } - - manifold.local_n1 = local_n1; - manifold.local_n2 = local_n2; - manifold.kinematics.category = KinematicsCategory::PointPoint; - manifold.kinematics.radius1 = radii_a.extract(i); - manifold.kinematics.radius2 = radii_b.extract(i); - ContactManifoldData::update_warmstart_multiplier(manifold); - } else { - manifold.points.clear(); - } - - manifold.sort_contacts(ctxt.prediction_distance); - } -} - -pub fn generate_contacts_ball_ball(ctxt: &mut PrimitiveContactGenerationContext) { - let pos_ba = ctxt.position2.inverse() * ctxt.position1; - let radius_a = ctxt.shape1.as_ball().unwrap().radius; - let radius_b = ctxt.shape2.as_ball().unwrap().radius; - - let dcenter = pos_ba.inverse_transform_point(&Point::origin()).coords; - let center_dist = dcenter.magnitude(); - let dist = center_dist - radius_a - radius_b; - - if dist < ctxt.prediction_distance { - let local_n1 = if center_dist != 0.0 { - dcenter / center_dist - } else { - Vector::y() - }; - - let local_n2 = pos_ba.inverse_transform_vector(&-local_n1); - let local_p1 = local_n1 * radius_a; - let local_p2 = local_n2 * radius_b; - let contact = Contact::new(local_p1.into(), local_p2.into(), 0, 0, dist); - - if ctxt.manifold.points.len() != 0 { - ctxt.manifold.points[0].copy_geometry_from(contact); - } else { - ctxt.manifold.points.push(contact); - } - - ctxt.manifold.local_n1 = local_n1; - ctxt.manifold.local_n2 = local_n2; - ctxt.manifold.kinematics.category = KinematicsCategory::PointPoint; - ctxt.manifold.kinematics.radius1 = radius_a; - ctxt.manifold.kinematics.radius2 = radius_b; - ContactManifoldData::update_warmstart_multiplier(ctxt.manifold); - } else { - ctxt.manifold.points.clear(); - } - - ctxt.manifold.sort_contacts(ctxt.prediction_distance); -} diff --git a/src/geometry/contact_generator/ball_convex_contact_generator.rs b/src/geometry/contact_generator/ball_convex_contact_generator.rs deleted file mode 100644 index 88f1912..0000000 --- a/src/geometry/contact_generator/ball_convex_contact_generator.rs +++ /dev/null @@ -1,70 +0,0 @@ -use crate::geometry::contact_generator::PrimitiveContactGenerationContext; -use crate::geometry::{Ball, Contact, ContactManifoldData, KinematicsCategory}; -use crate::math::Isometry; -use buckler::query::PointQuery; -use na::Unit; - -pub fn generate_contacts_ball_convex(ctxt: &mut PrimitiveContactGenerationContext) { - if let Some(ball1) = ctxt.shape1.as_ball() { - ctxt.manifold.swap_identifiers(); - do_generate_contacts(ctxt.shape2, ball1, ctxt, true); - } else if let Some(ball2) = ctxt.shape2.as_ball() { - do_generate_contacts(ctxt.shape1, ball2, ctxt, false); - } - - ctxt.manifold.sort_contacts(ctxt.prediction_distance); -} - -fn do_generate_contacts<P: ?Sized + PointQuery>( - point_query1: &P, - ball2: &Ball, - ctxt: &mut PrimitiveContactGenerationContext, - swapped: bool, -) { - let position1; - let position2; - - if swapped { - position1 = ctxt.position2; - position2 = ctxt.position1; - } else { - position1 = ctxt.position1; - position2 = ctxt.position2; - } - - let local_p2_1 = position1.inverse_transform_point(&position2.translation.vector.into()); - let proj = point_query1.project_local_point(&local_p2_1, cfg!(feature = "dim3")); - let dpos = local_p2_1 - proj.local_point; - - #[allow(unused_mut)] // Because `mut local_n1, mut dist` is needed in 2D but not in 3D. |
