nphysics2d/object/
mass_spring_system.rs

1use either::Either;
2use std::any::Any;
3use std::collections::{HashMap, HashSet};
4use std::iter;
5use std::marker::PhantomData;
6use std::ops::{AddAssign, SubAssign};
7
8#[cfg(feature = "dim3")]
9use na::Vector2;
10use na::{
11    self, Cholesky, DMatrix, DVector, DVectorSlice, DVectorSliceMut, Dynamic, RealField, Unit,
12};
13#[cfg(feature = "dim3")]
14use ncollide::procedural;
15#[cfg(feature = "dim3")]
16use ncollide::shape::TriMesh;
17use ncollide::shape::{DeformationsType, Polyline};
18use ncollide::utils::DeterministicState;
19
20use crate::math::{
21    Force, ForceType, Inertia, Isometry, Point, Translation, Vector, Velocity, DIM,
22};
23use crate::object::fem_helper;
24use crate::object::{
25    ActivationStatus, Body, BodyPart, BodyStatus, BodyUpdateStatus, FiniteElementIndices,
26};
27use crate::solver::{ForceDirection, IntegrationParameters};
28
29use crate::utils::{UserData, UserDataBox};
30
31/// An element of the mass-spring system.
32#[derive(Clone)]
33pub struct MassSpringElement<N: RealField + Copy> {
34    indices: FiniteElementIndices,
35    phantom: PhantomData<N>,
36}
37
38#[derive(Clone)]
39struct Spring<N: RealField + Copy> {
40    nodes: (usize, usize),
41    // Should be Unit<Vector<N>>, but can be zero.
42    dir: Unit<Vector<N>>,
43    length: N,
44    rest_length: N,
45    stiffness: N,
46    damping_ratio: N,
47    plastic_strain: N,
48}
49
50impl<N: RealField + Copy> Spring<N> {
51    fn from_positions(
52        nodes: (usize, usize),
53        positions: &[N],
54        stiffness: N,
55        damping_ratio: N,
56    ) -> Self {
57        let p0 = Point::from_slice(&positions[nodes.0..nodes.0 + DIM]);
58        let p1 = Point::from_slice(&positions[nodes.1..nodes.1 + DIM]);
59        let rest_length = na::distance(&p0, &p1);
60
61        Spring {
62            nodes,
63            dir: Unit::try_new(p1 - p0, N::zero()).unwrap_or_else(Vector::y_axis),
64            length: rest_length,
65            rest_length,
66            stiffness,
67            damping_ratio,
68            plastic_strain: N::zero(),
69        }
70    }
71}
72
73/// A deformable surface using a mass-spring model with triangular elements.
74pub struct MassSpringSystem<N: RealField + Copy> {
75    springs: Vec<Spring<N>>,
76    elements: Vec<MassSpringElement<N>>,
77    kinematic_nodes: DVector<bool>,
78    positions: DVector<N>,
79    velocities: DVector<N>,
80    accelerations: DVector<N>,
81    forces: DVector<N>,
82    augmented_mass: DMatrix<N>,
83    inv_augmented_mass: Cholesky<N, Dynamic>,
84
85    workspace: DVector<N>,
86
87    companion_id: usize,
88    gravity_enabled: bool,
89    activation: ActivationStatus<N>,
90    status: BodyStatus,
91    update_status: BodyUpdateStatus,
92    mass: N,
93    node_mass: N,
94
95    plasticity_threshold: N,
96    plasticity_creep: N,
97    plasticity_max_force: N,
98
99    user_data: Option<Box<dyn Any + Send + Sync>>,
100}
101
102fn key(i: usize, j: usize) -> (usize, usize) {
103    if i <= j {
104        (i, j)
105    } else {
106        (j, i)
107    }
108}
109
110impl<N: RealField + Copy> MassSpringSystem<N> {
111    /// Creates a new deformable surface following the mass-spring model.
112    ///
113    /// The surface is initialized with a set of links corresponding to each trimesh edges.
114    #[cfg(feature = "dim3")]
115    fn from_trimesh(mesh: &TriMesh<N>, mass: N, stiffness: N, damping_ratio: N) -> Self {
116        let ndofs = mesh.points().len() * DIM;
117        let mut springs = HashMap::with_hasher(DeterministicState::new());
118        let mut elements = Vec::with_capacity(mesh.faces().len());
119        let mut positions = DVector::zeros(ndofs);
120
121        for (i, pos) in positions.as_mut_slice().chunks_mut(DIM).enumerate() {
122            pos.copy_from_slice(mesh.points()[i].coords.as_slice())
123        }
124
125        for face in mesh.faces() {
126            let idx = face.indices * DIM;
127            let elt = MassSpringElement {
128                indices: FiniteElementIndices::Triangle(idx),
129                phantom: PhantomData,
130            };
131
132            elements.push(elt);
133
134            let _ = springs.entry(key(idx.x, idx.y)).or_insert_with(|| {
135                Spring::from_positions(
136                    (idx.x, idx.y),
137                    positions.as_slice(),
138                    stiffness,
139                    damping_ratio,
140                )
141            });
142            let _ = springs.entry(key(idx.y, idx.z)).or_insert_with(|| {
143                Spring::from_positions(
144                    (idx.y, idx.z),
145                    positions.as_slice(),
146                    stiffness,
147                    damping_ratio,
148                )
149            });
150            let _ = springs.entry(key(idx.z, idx.x)).or_insert_with(|| {
151                Spring::from_positions(
152                    (idx.z, idx.x),
153                    positions.as_slice(),
154                    stiffness,
155                    damping_ratio,
156                )
157            });
158        }
159
160        let node_mass = mass / na::convert((ndofs / DIM) as f64);
161
162        MassSpringSystem {
163            springs: springs.values().cloned().collect(),
164            elements,
165            kinematic_nodes: DVector::repeat(ndofs / DIM, false),
166            positions,
167            velocities: DVector::zeros(ndofs),
168            accelerations: DVector::zeros(ndofs),
169            forces: DVector::zeros(ndofs),
170            workspace: DVector::zeros(ndofs),
171            augmented_mass: DMatrix::zeros(ndofs, ndofs),
172            inv_augmented_mass: Cholesky::new(DMatrix::zeros(0, 0)).unwrap(),
173            companion_id: 0,
174            activation: ActivationStatus::new_active(),
175            status: BodyStatus::Dynamic,
176            update_status: BodyUpdateStatus::all(),
177            mass,
178            node_mass,
179            plasticity_max_force: N::zero(),
180            plasticity_creep: N::zero(),
181            plasticity_threshold: N::zero(),
182            gravity_enabled: true,
183            user_data: None,
184        }
185    }
186
187    user_data_accessors!();
188
189    /// Builds a mass-spring system from a polyline.
190    fn from_polyline(polyline: &Polyline<N>, mass: N, stiffness: N, damping_ratio: N) -> Self {
191        let ndofs = polyline.points().len() * DIM;
192        let mut springs = HashMap::with_hasher(DeterministicState::new());
193        let mut elements = Vec::with_capacity(polyline.edges().len());
194        let mut positions = DVector::zeros(ndofs);
195
196        for (i, pos) in positions.as_mut_slice().chunks_mut(DIM).enumerate() {
197            pos.copy_from_slice(polyline.points()[i].coords.as_slice())
198        }
199
200        for (_i, edge) in polyline.edges().iter().enumerate() {
201            let idx = edge.indices * DIM;
202            let elt = MassSpringElement {
203                indices: FiniteElementIndices::Segment(idx),
204                phantom: PhantomData,
205            };
206
207            elements.push(elt);
208
209            let _ = springs.entry(key(idx.x, idx.y)).or_insert_with(|| {
210                Spring::from_positions(
211                    (idx.x, idx.y),
212                    positions.as_slice(),
213                    stiffness,
214                    damping_ratio,
215                )
216            });
217        }
218
219        let node_mass = mass / na::convert((ndofs / DIM) as f64);
220        //        println!("Number of nodes: {}, of springs: {}", positions.len() / DIM, springs.len());
221
222        MassSpringSystem {
223            springs: springs.values().cloned().collect(),
224            kinematic_nodes: DVector::repeat(ndofs / DIM, false),
225            elements,
226            positions,
227            velocities: DVector::zeros(ndofs),
228            accelerations: DVector::zeros(ndofs),
229            forces: DVector::zeros(ndofs),
230            workspace: DVector::zeros(ndofs),
231            augmented_mass: DMatrix::zeros(ndofs, ndofs),
232            inv_augmented_mass: Cholesky::new(DMatrix::zeros(0, 0)).unwrap(),
233            companion_id: 0,
234            activation: ActivationStatus::new_active(),
235            status: BodyStatus::Dynamic,
236            update_status: BodyUpdateStatus::all(),
237            gravity_enabled: true,
238            mass,
239            node_mass,
240            plasticity_max_force: N::zero(),
241            plasticity_creep: N::zero(),
242            plasticity_threshold: N::zero(),
243            user_data: None,
244        }
245    }
246
247    /// Creates a rectangular quad.
248    #[cfg(feature = "dim3")]
249    pub fn quad(
250        transform: &Isometry<N>,
251        extents: &Vector2<N>,
252        nx: usize,
253        ny: usize,
254        mass: N,
255        stiffness: N,
256        damping_ratio: N,
257    ) -> Self {
258        let mesh = procedural::quad(extents.x, extents.y, nx, ny);
259        let vertices = mesh.coords.iter().map(|pt| transform * pt).collect();
260        let indices = mesh
261            .indices
262            .unwrap_unified()
263            .into_iter()
264            .map(|tri| na::convert(tri))
265            .collect();
266        let trimesh = TriMesh::new(vertices, indices, None);
267        Self::from_trimesh(&trimesh, mass, stiffness, damping_ratio)
268    }
269
270    /// The total mass of this mass-spring system.
271    pub fn mass(&self) -> N {
272        self.mass
273    }
274
275    /// The number of nodes of this mass-spring system.
276    pub fn num_nodes(&self) -> usize {
277        self.positions.len() / DIM
278    }
279
280    /// Generate additional springs between nodes that are transitively neighbors.
281    ///
282    /// Given three nodes `a, b, c`, if a spring exists between `a` and `b`, and between `b` and `c`,
283    /// then a spring between `a` and `c` is created if it does not already exists.
284    pub fn generate_neighbor_springs(&mut self, stiffness: N, damping_ratio: N) {
285        self.update_status.set_local_inertia_changed(true);
286
287        let mut neighbor_list: Vec<_> = iter::repeat(Vec::new())
288            .take(self.positions.len() / DIM)
289            .collect();
290        let mut existing_springs = HashSet::with_hasher(DeterministicState::new());
291
292        // Build neighborhood list.
293        for spring in &self.springs {
294            let key = key(spring.nodes.0, spring.nodes.1);
295            neighbor_list[key.0 / DIM].push(key.1 / DIM);
296            let _ = existing_springs.insert(key);
297        }
298
299        // Build springs.
300        for (i, nbhs) in neighbor_list.iter().enumerate() {
301            for nbh in nbhs {
302                for transitive_nbh in &neighbor_list[*nbh] {
303                    let key = key(i * DIM, *transitive_nbh * DIM);
304
305                    if existing_springs.insert(key) {
306                        let spring = Spring::from_positions(
307                            key,
308                            self.positions.as_slice(),
309                            stiffness,
310                            damping_ratio,
311                        );
312                        self.springs.push(spring);
313                    }
314                }
315            }
316        }
317    }
318
319    /// Add one spring to this mass-spring system.
320    pub fn add_spring(&mut self, node1: usize, node2: usize, stiffness: N, damping_ratio: N) {
321        assert!(
322            node1 < self.positions.len() / DIM,
323            "Node index out of bounds."
324        );
325        assert!(
326            node2 < self.positions.len() / DIM,
327            "Node index out of bounds."
328        );
329        self.update_status.set_local_inertia_changed(true);
330        let key = key(node1 * DIM, node2 * DIM);
331        let spring =
332            Spring::from_positions(key, self.positions.as_slice(), stiffness, damping_ratio);
333        self.springs.push(spring);
334    }
335
336    /// Restrict the specified node acceleration to always be zero so
337    /// it can be controlled manually by the user at the velocity level.
338    pub fn set_node_kinematic(&mut self, i: usize, is_kinematic: bool) {
339        assert!(i < self.positions.len() / DIM, "Node index out of bounds.");
340        self.update_status.set_status_changed(true);
341        self.update_status.set_local_inertia_changed(true);
342        self.kinematic_nodes[i] = is_kinematic;
343    }
344
345    /// Mark all nodes as non-kinematic.
346    pub fn clear_kinematic_nodes(&mut self) {
347        self.update_status.set_status_changed(true);
348        self.update_status.set_local_inertia_changed(true);
349        self.kinematic_nodes.fill(false)
350    }
351
352    /// Sets the plastic properties of this mass-spring system.
353    pub fn set_plasticity(&mut self, strain_threshold: N, creep: N, max_force: N) {
354        self.plasticity_threshold = strain_threshold;
355        self.plasticity_creep = creep;
356        self.plasticity_max_force = max_force;
357    }
358
359    fn update_augmented_mass(&mut self, dt: N) {
360        self.augmented_mass.fill(N::zero());
361        self.augmented_mass.fill_diagonal(self.node_mass);
362
363        for spring in &mut self.springs {
364            let kinematic1 = self.kinematic_nodes[spring.nodes.0 / DIM];
365            let kinematic2 = self.kinematic_nodes[spring.nodes.1 / DIM];
366
367            if kinematic1 && kinematic2 {
368                continue;
369            }
370
371            /*
372             * Elastic strain.
373             */
374            //            let damping = spring.damping_ratio * (spring.stiffness * self.node_mass).sqrt() * na::convert(2.0);
375            let l = *spring.dir;
376
377            if spring.length != N::zero() {
378                /*
379                 *
380                 * Stiffness matrix contribution.
381                 *
382                 */
383                let ll = l * l.transpose();
384                let stiffness = ll * spring.stiffness;
385                // NOTE: for now, we only treat the (spring.length() - spring.rest_length) term
386                // in a linearly-implicit way. It appears the other terms would introduce
387                // instabilities.
388                // To treat other terms in a linearly-implicit way, the following terms would
389                // be added to the stiffness matrix:
390                //
391                // let one_minus_ll = MatrixN::<N, DIM>::identity() - ll;
392                // let v0 = self.velocities.fixed_rows::<DIM>(spring.nodes.0);
393                // let v1 = self.velocities.fixed_rows::<DIM>(spring.nodes.1);
394                // let ldot = v1 - v0;
395                // let lldot = l.dot(&ldot);
396                // let coeff = spring.stiffness * (spring.length - spring.rest_length) + damping * lldot;
397                // stiffness += one_minus_ll * coeff / spring.length + (l * ldot.transpose()) * one_minus_ll * damping / spring.length;
398                //
399                // Also the damping matrix would be non-zero:
400                //
401                // let damping_matrix = ll * damping;
402                // let damping_stiffness = -damping_matrix * parameters.dt() + stiffness * (parameters.dt() * parameters.dt()).
403
404                // Add to the mass matrix.
405                let damping_stiffness = stiffness * (dt * dt);
406
407                if !kinematic1 {
408                    self.augmented_mass
409                        .fixed_slice_mut::<DIM, DIM>(spring.nodes.0, spring.nodes.0)
410                        .add_assign(&damping_stiffness);
411                }
412                if !kinematic2 {
413                    self.augmented_mass
414                        .fixed_slice_mut::<DIM, DIM>(spring.nodes.1, spring.nodes.1)
415                        .add_assign(&damping_stiffness);
416                }
417                if !kinematic1 && !kinematic2 {
418                    // FIXME: we don't need to fill both because Cholesky won't read tho upper triangle.
419                    self.augmented_mass
420                        .fixed_slice_mut::<DIM, DIM>(spring.nodes.0, spring.nodes.1)
421                        .sub_assign(&damping_stiffness);
422                    self.augmented_mass
423                        .fixed_slice_mut::<DIM, DIM>(spring.nodes.1, spring.nodes.0)
424                        .sub_assign(&damping_stiffness);
425                }
426            }
427        }
428
429        /*
430         * Set the mass matrix diagonal to the identity for kinematic nodes.
431         */
432        for i in 0..self.positions.len() / DIM {
433            if self.kinematic_nodes[i] {
434                let idof = i * DIM;
435                self.augmented_mass
436                    .fixed_slice_mut::<DIM, DIM>(idof, idof)
437                    .fill_diagonal(N::one());
438            }
439        }
440
441        self.inv_augmented_mass = Cholesky::new(self.augmented_mass.clone()).unwrap();
442    }
443
444    fn update_forces(&mut self, gravity: &Vector<N>, parameters: &IntegrationParameters<N>) {
445        self.accelerations.copy_from(&self.forces);
446
447        for spring in &mut self.springs {
448            let kinematic1 = self.kinematic_nodes[spring.nodes.0 / DIM];
449            let kinematic2 = self.kinematic_nodes[spring.nodes.1 / DIM];
450
451            if kinematic1 && kinematic2 {
452                continue;
453            }
454
455            /*
456             *
457             * Plastic strain.
458             *
459             */
460            // Compute plastic strain.
461            let total_strain = spring.stiffness * (spring.length - spring.rest_length);
462            let strain = total_strain - spring.plastic_strain;
463
464            if strain.abs() > self.plasticity_threshold {
465                let coeff = parameters.dt() * parameters.inv_dt().min(self.plasticity_creep);
466                spring.plastic_strain += strain * coeff;
467            }
468
469            if spring.plastic_strain.abs() > self.plasticity_max_force {
470                spring.plastic_strain = spring.plastic_strain.signum() * self.plasticity_max_force;
471            }
472
473            /*
474             * Elastic strain.
475             */
476            // FIXME: precompute this and store it on the spring struct?
477            let damping = spring.damping_ratio
478                * (spring.stiffness * self.node_mass).sqrt()
479                * na::convert(2.0);
480            let v0 = self.velocities.fixed_rows::<DIM>(spring.nodes.0);
481            let v1 = self.velocities.fixed_rows::<DIM>(spring.nodes.1);
482
483            let ldot = v1 - v0;
484            let l = *spring.dir;
485
486            // Explicit elastic term - plastic term.
487            let coeff =
488                spring.stiffness * (spring.length - spring.rest_length) + damping * l.dot(&ldot);
489            let f0 = l * (coeff - spring.plastic_strain);
490
491            // NOTE: we don't add the additional terms due to the linearly-implicit
492            // integration because they seem to introduce instabilities.
493
494            if !kinematic1 {
495                self.accelerations
496                    .fixed_rows_mut::<DIM>(spring.nodes.0)
497                    .add_assign(&f0);
498            }
499            if !kinematic2 {
500                self.accelerations
501                    .fixed_rows_mut::<DIM>(spring.nodes.1)
502                    .sub_assign(&f0);
503            }
504        }
505
506        /*
507         * Add forces due to gravity.
508         */
509        if self.gravity_enabled {
510            let gravity_force = gravity * self.node_mass;
511
512            for i in 0..self.positions.len() / DIM {
513                let idof = i * DIM;
514
515                if !self.kinematic_nodes[i] {
516                    let mut acc = self.accelerations.fixed_rows_mut::<DIM>(idof);
517                    acc += gravity_force
518                }
519            }
520        }
521
522        self.inv_augmented_mass.solve_mut(&mut self.accelerations);
523    }
524}
525
526impl<N: RealField + Copy> Body<N> for MassSpringSystem<N> {
527    #[inline]
528    fn gravity_enabled(&self) -> bool {
529        self.gravity_enabled
530    }
531
532    #[inline]
533    fn enable_gravity(&mut self, enabled: bool) {
534        self.gravity_enabled = enabled
535    }
536
537    fn update_kinematics(&mut self) {
538        if self.update_status.position_changed() {
539            for spring in &mut self.springs {
540                let p0 = self.positions.fixed_rows::<DIM>(spring.nodes.0);
541                let p1 = self.positions.fixed_rows::<DIM>(spring.nodes.1);
542                let l = p1 - p0;
543
544                if let Some((dir, length)) = Unit::try_new_and_get(l, N::zero()) {
545                    spring.dir = dir;
546                    spring.length = length;
547                } else {
548                    spring.dir = Vector::y_axis();
549                    spring.length = N::zero();
550                }
551            }
552        }
553    }
554
555    fn update_dynamics(&mut self, dt: N) {
556        if self.update_status.inertia_needs_update() && self.status == BodyStatus::Dynamic {
557            if !self.is_active() {
558                self.activate();
559            }
560
561            self.update_augmented_mass(dt);
562        }
563    }
564
565    fn update_acceleration(&mut self, gravity: &Vector<N>, parameters: &IntegrationParameters<N>) {
566        self.update_forces(gravity, parameters);
567    }
568
569    fn clear_forces(&mut self) {
570        self.forces.fill(N::zero())
571    }
572
573    fn apply_displacement(&mut self, disp: &[N]) {
574        self.update_status.set_position_changed(true);
575        let disp = DVectorSlice::from_slice(disp, self.positions.len());
576        self.positions += disp;
577    }
578
579    fn status(&self) -> BodyStatus {
580        self.status
581    }
582
583    fn set_status(&mut self, status: BodyStatus) {
584        self.update_status.set_status_changed(true);
585        self.status = status
586    }
587
588    fn clear_update_flags(&mut self) {
589        self.update_status.clear()
590    }
591
592    fn update_status(&self) -> BodyUpdateStatus {
593        self.update_status
594    }
595
596    fn activation_status(&self) -> &ActivationStatus<N> {
597        &self.activation
598    }
599
600    fn set_deactivation_threshold(&mut self, threshold: Option<N>) {
601        self.activation.set_deactivation_threshold(threshold)
602    }
603
604    fn ndofs(&self) -> usize {
605        self.positions.len()
606    }
607
608    fn generalized_acceleration(&self) -> DVectorSlice<N> {
609        DVectorSlice::from_slice(self.accelerations.as_slice(), self.accelerations.len())
610    }
611
612    fn generalized_velocity(&self) -> DVectorSlice<N> {
613        DVectorSlice::from_slice(self.velocities.as_slice(), self.velocities.len())
614    }
615
616    fn companion_id(&self) -> usize {
617        self.companion_id
618    }
619
620    fn set_companion_id(&mut self, id: usize) {
621        self.companion_id = id
622    }
623
624    fn generalized_velocity_mut(&mut self) -> DVectorSliceMut<N> {
625        self.update_status.set_velocity_changed(true);
626        let len = self.velocities.len();
627        DVectorSliceMut::from_slice(self.velocities.as_mut_slice(), len)
628    }
629
630    fn integrate(&mut self, parameters: &IntegrationParameters<N>) {
631        self.update_status.set_position_changed(true);
632        self.positions
633            .axpy(parameters.dt(), &self.velocities, N::one());
634    }
635
636    fn activate_with_energy(&mut self, energy: N) {
637        self.activation.set_energy(energy)
638    }
639
640    fn deactivate(&mut self) {
641        self.update_status.clear();
642        self.activation.set_energy(N::zero());
643        self.velocities.fill(N::zero());
644    }
645
646    fn num_parts(&self) -> usize {
647        self.elements.len()
648    }
649
650    fn part(&self, id: usize) -> Option<&dyn BodyPart<N>> {
651        self.elements.get(id).map(|e| e as &dyn BodyPart<N>)
652    }
653
654    fn deformed_positions(&self) -> Option<(DeformationsType, &[N])> {
655        Some((DeformationsType::Vectors, self.positions.as_slice()))
656    }
657
658    fn deformed_positions_mut(&mut self) -> Option<(DeformationsType, &mut [N])> {
659        self.update_status.set_position_changed(true);
660        Some((DeformationsType::Vectors, self.positions.as_mut_slice()))
661    }
662
663    fn world_point_at_material_point(&self, part: &dyn BodyPart<N>, point: &Point<N>) -> Point<N> {
664        let elt = part
665            .downcast_ref::<MassSpringElement<N>>()
666            .expect("The provided body part must be a mass-spring element");
667        fem_helper::world_point_at_material_point(elt.indices, &self.positions, point)
668    }
669
670    fn position_at_material_point(&self, part: &dyn BodyPart<N>, point: &Point<N>) -> Isometry<N> {
671        let elt = part
672            .downcast_ref::<MassSpringElement<N>>()
673            .expect("The provided body part must be a mass-spring element");
674        let pt = fem_helper::world_point_at_material_point(elt.indices, &self.positions, point);
675        Isometry::from_parts(Translation::from(pt.coords), na::one())
676    }
677
678    fn material_point_at_world_point(&self, part: &dyn BodyPart<N>, point: &Point<N>) -> Point<N> {
679        let elt = part
680            .downcast_ref::<MassSpringElement<N>>()
681            .expect("The provided body part must be a mass-spring element");
682        fem_helper::material_point_at_world_point(elt.indices, &self.positions, point)
683    }
684
685    fn fill_constraint_geometry(
686        &self,
687        part: &dyn BodyPart<N>,
688        _: usize, // FIXME: keep this parameter?
689        center: &Point<N>,
690        force_dir: &ForceDirection<N>,
691        j_id: usize,
692        wj_id: usize,
693        jacobians: &mut [N],
694        inv_r: &mut N,
695        ext_vels: Option<&DVectorSlice<N>>,
696        out_vel: Option<&mut N>,
697    ) {
698        let elt = part
699            .downcast_ref::<MassSpringElement<N>>()
700            .expect("The provided body part must be a mass-spring element");
701        fem_helper::fill_contact_geometry_fem(
702            self.ndofs(),
703            self.status,
704            elt.indices,
705            &self.positions,
706            &self.velocities,
707            &self.kinematic_nodes,
708            Either::Right(&self.inv_augmented_mass),
709            center,
710            force_dir,
711            j_id,
712            wj_id,
713            jacobians,
714            inv_r,
715            ext_vels,
716            out_vel,
717        );
718    }
719
720    #[inline]
721    fn has_active_internal_constraints(&mut self) -> bool {
722        false
723    }
724
725    #[inline]
726    fn setup_internal_velocity_constraints(
727        &mut self,
728        _: &DVectorSlice<N>,
729        _: &IntegrationParameters<N>,
730    ) {
731    }
732
733    #[inline]
734    fn warmstart_internal_velocity_constraints(&mut self, _: &mut DVectorSliceMut<N>) {}
735
736    #[inline]
737    fn step_solve_internal_velocity_constraints(&mut self, _: &mut DVectorSliceMut<N>) {}
738
739    #[inline]
740    fn step_solve_internal_position_constraints(&mut self, _: &IntegrationParameters<N>) {}
741
742    #[inline]
743    fn velocity_at_point(&self, part_id: usize, point: &Point<N>) -> Velocity<N> {
744        let element = &self.elements[part_id];
745        fem_helper::velocity_at_point(element.indices, &self.positions, &self.velocities, point)
746    }
747
748    fn apply_force_at_local_point(
749        &mut self,
750        part_id: usize,
751        force: &Vector<N>,
752        point: &Point<N>,
753        force_type: ForceType,
754        auto_wake_up: bool,
755    ) {
756        if self.status != BodyStatus::Dynamic {
757            return;
758        }
759
760        if auto_wake_up {
761            self.activate()
762        }
763
764        let element = &self.elements[part_id];
765        let indices = element.indices.as_slice();
766
767        #[cfg(feature = "dim2")]
768        let forces = [
769            force * (N::one() - point.x - point.y),
770            force * point.x,
771            force * point.y,
772        ];
773        #[cfg(feature = "dim3")]
774        let forces = [
775            force * (N::one() - point.x - point.y - point.z),
776            force * point.x,
777            force * point.y,
778            force * point.z,
779        ];
780
781        match force_type {
782            ForceType::Force => {
783                for i in 0..indices.len() {
784                    if !self.kinematic_nodes[indices[i] / DIM] {
785                        self.forces
786                            .fixed_rows_mut::<DIM>(indices[i])
787                            .add_assign(&forces[i]);
788                    }
789                }
790            }
791            ForceType::Impulse => {
792                let dvel = &mut self.workspace;
793                dvel.fill(N::zero());
794                for i in 0..indices.len() {
795                    if !self.kinematic_nodes[indices[i] / DIM] {
796                        dvel.fixed_rows_mut::<DIM>(indices[i]).copy_from(&forces[i]);
797                    }
798                }
799                self.inv_augmented_mass.solve_mut(dvel);
800                self.velocities += &*dvel;
801            }
802            ForceType::AccelerationChange => {
803                for i in 0..indices.len() {
804                    if !self.kinematic_nodes[indices[i] / DIM] {
805                        self.forces
806                            .fixed_rows_mut::<DIM>(indices[i])
807                            .add_assign(forces[i] * self.node_mass);
808                    }
809                }
810            }
811            ForceType::VelocityChange => {
812                for i in 0..indices.len() {
813                    if !self.kinematic_nodes[indices[i] / DIM] {
814                        self.velocities
815                            .fixed_rows_mut::<DIM>(indices[i])
816                            .add_assign(forces[i]);
817                    }
818                }
819            }
820        }
821    }
822
823    fn apply_force(
824        &mut self,
825        part_id: usize,
826        force: &Force<N>,
827        force_type: ForceType,
828        auto_wake_up: bool,
829    ) {
830        let dim = self.elements[part_id].indices.as_slice().len();
831        let inv_dim: N = na::convert(1.0 / dim as f64);
832        let barycenter = Point::from(Vector::repeat(inv_dim));
833        self.apply_force_at_local_point(
834            part_id,
835            &force.linear,
836            &barycenter,
837            force_type,
838            auto_wake_up,
839        )
840    }
841
842    fn apply_local_force(
843        &mut self,
844        part_id: usize,
845        force: &Force<N>,
846        force_type: ForceType,
847        auto_wake_up: bool,
848    ) {
849        // FIXME: compute an approximate rotation for the conserned element (just like the FEM bodies)?
850        self.apply_force(part_id, &force, force_type, auto_wake_up);
851    }
852
853    fn apply_force_at_point(
854        &mut self,
855        part_id: usize,
856        force: &Vector<N>,
857        point: &Point<N>,
858        force_type: ForceType,
859        auto_wake_up: bool,
860    ) {
861        let local_point = self.material_point_at_world_point(&self.elements[part_id], point);
862        self.apply_force_at_local_point(part_id, &force, &local_point, force_type, auto_wake_up)
863    }
864
865    fn apply_local_force_at_point(
866        &mut self,
867        part_id: usize,
868        force: &Vector<N>,
869        point: &Point<N>,
870        force_type: ForceType,
871        auto_wake_up: bool,
872    ) {
873        // FIXME: compute an approximate rotation for the conserned element (just like the FEM bodies)?
874        let local_point = self.material_point_at_world_point(&self.elements[part_id], point);
875        self.apply_force_at_local_point(part_id, &force, &local_point, force_type, auto_wake_up);
876    }
877
878    fn apply_local_force_at_local_point(
879        &mut self,
880        part_id: usize,
881        force: &Vector<N>,
882        point: &Point<N>,
883        force_type: ForceType,
884        auto_wake_up: bool,
885    ) {
886        // FIXME: compute an approximate rotation for the conserned element (just like the FEM bodies)?
887        self.apply_force_at_local_point(part_id, &force, &point, force_type, auto_wake_up);
888    }
889}
890
891impl<N: RealField + Copy> BodyPart<N> for MassSpringElement<N> {
892    fn center_of_mass(&self) -> Point<N> {
893        unimplemented!()
894    }
895    fn local_center_of_mass(&self) -> Point<N> {
896        unimplemented!()
897    }
898
899    fn position(&self) -> Isometry<N> {
900        // XXX
901        Isometry::new(Vector::<N>::y() * na::convert::<_, N>(100.0f64), na::zero())
902    }
903
904    fn velocity(&self) -> Velocity<N> {
905        unimplemented!()
906    }
907
908    fn inertia(&self) -> Inertia<N> {
909        unimplemented!()
910    }
911
912    fn local_inertia(&self) -> Inertia<N> {
913        unimplemented!()
914    }
915}
916
917enum MassSpringSystemDescGeometry<'a, N: RealField + Copy> {
918    Quad(usize, usize),
919    Polyline(&'a Polyline<N>),
920    #[cfg(feature = "dim3")]
921    TriMesh(&'a TriMesh<N>),
922}
923
924/// A builder for mass-spring systems.
925pub struct MassSpringSystemDesc<'a, N: RealField + Copy> {
926    user_data: Option<UserDataBox>,
927    geom: MassSpringSystemDescGeometry<'a, N>,
928    stiffness: N,
929    sleep_threshold: Option<N>,
930    damping_ratio: N,
931    mass: N,
932    plasticity: (N, N, N),
933    kinematic_nodes: Vec<usize>,
934    status: BodyStatus,
935    gravity_enabled: bool,
936}
937
938impl<'a, N: RealField + Copy> MassSpringSystemDesc<'a, N> {
939    fn with_geometry(geom: MassSpringSystemDescGeometry<'a, N>) -> Self {
940        MassSpringSystemDesc {
941            user_data: None,
942            gravity_enabled: true,
943            geom,
944            stiffness: na::convert(1.0e3),
945            sleep_threshold: Some(ActivationStatus::default_threshold()),
946            damping_ratio: na::convert(0.2),
947            mass: N::one(),
948            plasticity: (N::zero(), N::zero(), N::zero()),
949            kinematic_nodes: Vec::new(),
950            status: BodyStatus::Dynamic,
951        }
952    }
953
954    /// Create a mass-constraints system form a triangle mesh.
955    #[cfg(feature = "dim3")]
956    pub fn from_trimesh(mesh: &'a TriMesh<N>) -> Self {
957        Self::with_geometry(MassSpringSystemDescGeometry::TriMesh(mesh))
958    }
959
960    /// Create a mass-constraints system form a polygonal line.
961    pub fn from_polyline(polyline: &'a Polyline<N>) -> Self {
962        Self::with_geometry(MassSpringSystemDescGeometry::Polyline(polyline))
963    }
964
965    /// Create a quad-shaped body.
966    pub fn quad(subdiv_x: usize, subdiv_y: usize) -> Self {
967        Self::with_geometry(MassSpringSystemDescGeometry::Quad(subdiv_x, subdiv_y))
968    }
969
970    user_data_desc_accessors!();
971
972    /// Mark all nodes as non-kinematic.
973    pub fn clear_kinematic_nodes(&mut self) -> &mut Self {
974        self.kinematic_nodes.clear();
975        self
976    }
977
978    desc_custom_setters!(
979        self.plasticity, set_plasticity, strain_threshold: N, creep: N, max_force: N | { self.plasticity = (strain_threshold, creep, max_force) }
980        self.kinematic_nodes, set_nodes_kinematic, nodes: &[usize] | { self.kinematic_nodes.extend_from_slice(nodes) }
981    );
982
983    desc_setters!(
984        gravity_enabled, enable_gravity, gravity_enabled: bool
985        stiffness, set_stiffness, stiffness: N
986        sleep_threshold, set_sleep_threshold, sleep_threshold: Option<N>
987        damping_ratio, set_damping_ratio, damping_ratio: N
988        mass, set_mass, mass: N
989        status, set_status, status: BodyStatus
990    );
991
992    desc_custom_getters!(
993        self.get_plasticity_strain_threshold: N | { self.plasticity.0 }
994        self.get_plasticity_creep: N | { self.plasticity.1 }
995        self.get_plasticity_max_force: N | { self.plasticity.2 }
996        self.get_kinematic_nodes: &[usize] | { &self.kinematic_nodes[..] }
997    );
998
999    desc_getters!(
1000        [val] is_gravity_enabled -> gravity_enabled: bool
1001        [val] get_stiffness -> stiffness: N
1002        [val] get_sleep_threshold -> sleep_threshold: Option<N>
1003        [val] get_damping_ratio -> damping_ratio: N
1004        [val] get_mass -> mass: N
1005        [val] get_status -> status: BodyStatus
1006    );
1007
1008    /// Builds a mass-spring based deformable body from this description.
1009    pub fn build(&self) -> MassSpringSystem<N> {
1010        let mut vol = match self.geom {
1011            MassSpringSystemDescGeometry::Quad(nx, ny) => {
1012                let polyline = Polyline::quad(nx, ny);
1013                MassSpringSystem::from_polyline(
1014                    &polyline,
1015                    self.mass,
1016                    self.stiffness,
1017                    self.damping_ratio,
1018                )
1019            }
1020            MassSpringSystemDescGeometry::Polyline(polyline) => MassSpringSystem::from_polyline(
1021                polyline,
1022                self.mass,
1023                self.stiffness,
1024                self.damping_ratio,
1025            ),
1026            #[cfg(feature = "dim3")]
1027            MassSpringSystemDescGeometry::TriMesh(trimesh) => MassSpringSystem::from_trimesh(
1028                trimesh,
1029                self.mass,
1030                self.stiffness,
1031                self.damping_ratio,
1032            ),
1033        };
1034
1035        vol.set_deactivation_threshold(self.sleep_threshold);
1036        vol.set_plasticity(self.plasticity.0, self.plasticity.1, self.plasticity.2);
1037        vol.enable_gravity(self.gravity_enabled);
1038        vol.set_status(self.status);
1039        let _ = vol.set_user_data(self.user_data.as_ref().map(|data| data.0.to_any()));
1040
1041        for i in &self.kinematic_nodes {
1042            vol.set_node_kinematic(*i, true)
1043        }
1044
1045        vol
1046    }
1047}