//#####################################################################
// Copyright 2006-2009, Jon Gretarsson, Geoffrey Irving, Nipun Kwatra, Craig Schroeder, Andrew Selle, Tamar Shinar, Eftychios Sifakis, Jonathan Su, Wen Zheng.
// This file is part of PhysBAM whose distribution is governed by the license contained in the accompanying file PHYSBAM_COPYRIGHT.txt.
//#####################################################################
// Class DEFORMABLES_STANDARD_TESTS
//#####################################################################
#include <Deformables/Standard_Tests/DEFORMABLES_STANDARD_TESTS.h>
#include <Core/Basic_Geometry/CYLINDER.h>
#include <Core/Basic_Geometry/RANGE_TESSELLATION.h>
#include <Core/Basic_Geometry/SPHERE.h>
#include <Core/Basic_Geometry/TETRAHEDRON.h>
#include <Core/Basic_Geometry/TORUS.h>
#include <Core/Grids_Uniform_Computations/LEVELSET_MAKER_UNIFORM.h>
#include <Core/Grids_Uniform_Computations/SEGMENTED_CURVE_2D_SIGNED_DISTANCE.h>
#include <Core/Implicit_Objects/ANALYTIC_IMPLICIT_OBJECT.h>
#include <Core/Implicit_Objects/IMPLICIT_OBJECT_TRANSFORMED.h>
#include <Core/Implicit_Objects/IMPLICIT_OBJECT_UTILITIES.h>
#include <Core/Implicit_Objects/LEVELSET_IMPLICIT_OBJECT.h>
#include <Core/Intersections/SEGMENT_3D_TRIANGLE_3D_INTERSECTION.h>
#include <Core/Meshes/SEGMENTED_CURVE.h>
#include <Core/Meshes/TETRAHEDRALIZED_VOLUME.h>
#include <Core/Read_Write/FILE_UTILITIES.h>
#include <Core/Spatial_Acceleration/MESH_HIERARCHY.h>
#include <Core/Utilities/VIEWER_DIR.h>
#include <Rigids/Collisions/COLLISION_BODY_COLLECTION.h>
#include <Deformables/Bindings/BINDING_LIST.h>
#include <Deformables/Bindings/LINEAR_BINDING.h>
#include <Deformables/Bindings/SOFT_BINDINGS.h>
#include <Deformables/Collisions_And_Interactions/DEFORMABLE_OBJECT_COLLISIONS.h>
#include <Deformables/Collisions_And_Interactions/TETRAHEDRON_COLLISION_BODY.h>
#include <Deformables/Collisions_And_Interactions/TRIANGLE_COLLISION_PARAMETERS.h>
#include <Deformables/Deformable_Objects/DEFORMABLE_BODY_COLLECTION.h>
#include <Deformables/Forces/DEFORMABLE_GRAVITY.h>
#include <Deformables/Fracture/EMBEDDED_TETRAHEDRALIZED_VOLUME.h>
#include <Deformables/Fracture/EMBEDDED_TETRAHEDRALIZED_VOLUME_BOUNDARY_SURFACE.h>
#include <Deformables/Particles/FREE_PARTICLES.h>
using namespace PhysBAM;
//#####################################################################
// Constructor
//#####################################################################
template<class TV> DEFORMABLES_STANDARD_TESTS<TV>::
DEFORMABLES_STANDARD_TESTS(STREAM_TYPE stream_type,DEFORMABLE_BODY_COLLECTION<TV>& deformable_body_collection_input)
    :stream_type(stream_type),deformable_body_collection(deformable_body_collection_input)
{
}
//#####################################################################
// Destructor
//#####################################################################
template<class TV> DEFORMABLES_STANDARD_TESTS<TV>::
~DEFORMABLES_STANDARD_TESTS()
{
}
//#####################################################################
// Function Add_Gravity
//#####################################################################
template<class TV> void DEFORMABLES_STANDARD_TESTS<TV>::
Add_Gravity()
{
    // add gravity on all deformable particles
    deformable_body_collection.Add_Force(new DEFORMABLE_GRAVITY<TV>(deformable_body_collection.particles,true));
}
//#####################################################################
// Function Copy_And_Add_Structure
//#####################################################################
template<class TV> template<class T_STRUCTURE> T_STRUCTURE& DEFORMABLES_STANDARD_TESTS<TV>::
Copy_And_Add_Structure(T_STRUCTURE& structure,ARRAY<int>* particle_indices,bool delete_structure)
{
    deformable_body_collection.Add_Structure(structure.Append_Particles_And_Create_Copy(deformable_body_collection.particles,particle_indices));
    deformable_body_collection.structures.Last()->Update_Number_Nodes();
    if(delete_structure) delete &structure;
    return dynamic_cast<T_STRUCTURE&>(*deformable_body_collection.structures.Last());
}
//#####################################################################
// Function Set_Initial_Particle_Configuration
//#####################################################################
template<class TV> void DEFORMABLES_STANDARD_TESTS<TV>::
Set_Initial_Particle_Configuration(GEOMETRY_PARTICLES<TV>& particles,const RIGID_BODY_STATE<TV>& state,const bool relative_to_box_center)
{
    LOG::cout<<"Deformable body - Total Particles : "<<particles.Size()<<std::endl;
    particles.Store_Velocity();
    if(relative_to_box_center){
        particles.X-=RANGE<TV>::Bounding_Box(particles.X).Center();}
    for(int p=0;p<particles.Size();p++){
        particles.X(p)=state.frame*(particles.X(p));
        particles.V(p)=state.Pointwise_Object_Velocity(particles.X(p));}
}
//#####################################################################
// Function Create_Segmented_Curve
//#####################################################################
template<class TV> MESH_OBJECT<TV,1>& DEFORMABLES_STANDARD_TESTS<TV>::
Create_Segmented_Curve(const GRID<VECTOR<T,1> >& square_grid,const RIGID_BODY_STATE<TV>& initial_state,const T density)
{
    DEFORMABLE_PARTICLES<TV>& particles=*new DEFORMABLE_PARTICLES<TV>;
    T_SEGMENTED_CURVE& segmented_curve=*T_SEGMENTED_CURVE::Create(particles);
    segmented_curve.Initialize_Straight_Mesh_And_Particles(square_grid);
    LOG::cout<<"Adding Segmented Curve - Segments = "<<segmented_curve.mesh.elements.m<<std::endl;
    Set_Mass_Of_Particles(segmented_curve,density,false);
    Set_Initial_Particle_Configuration(particles,initial_state,true);
    MESH_OBJECT<TV,1>& copy=Copy_And_Add_Structure(segmented_curve);
    delete &particles;
    return copy;
}
//#####################################################################
// Function Create_Segmented_Curve
//#####################################################################
template<class TV> MESH_OBJECT<TV,1>& DEFORMABLES_STANDARD_TESTS<TV>::
Create_Segmented_Curve(const int m,const RIGID_BODY_STATE<TV>& initial_state,const T initial_radius,const T density)
{
    DEFORMABLE_PARTICLES<TV>& particles=*new DEFORMABLE_PARTICLES<TV>;
    T_SEGMENTED_CURVE& segmented_curve=*T_SEGMENTED_CURVE::Create(particles);
    segmented_curve.Initialize_Circle_Mesh_And_Particles(m,initial_radius);
    LOG::cout<<"Adding Segmented Curve - Segments = "<<segmented_curve.mesh.elements.m<<std::endl;
    Set_Mass_Of_Particles(segmented_curve,density,false);
    Set_Initial_Particle_Configuration(particles,initial_state,true);
    MESH_OBJECT<TV,1>& copy=Copy_And_Add_Structure(segmented_curve);
    delete &particles;
    return copy;
}
//#####################################################################
// Function Create_Tetrahedralized_Volume
//#####################################################################
template<class TV> TETRAHEDRALIZED_VOLUME<typename TV::SCALAR>& DEFORMABLES_STANDARD_TESTS<TV>::
Create_Tetrahedralized_Volume(const std::string& filename,const RIGID_BODY_STATE<TV>& initial_state,const bool relative_to_box_center,const bool use_constant_mass,const T density,const T scale)
{
    PHYSBAM_ASSERT(scale>0);
    PHYSBAM_ASSERT(density>0);
    DEFORMABLE_PARTICLES<TV>& particles=*new DEFORMABLE_PARTICLES<TV>;
    TETRAHEDRALIZED_VOLUME<T>& tetrahedralized_volume=*TETRAHEDRALIZED_VOLUME<T>::Create(particles);
    Read_From_File(filename,tetrahedralized_volume);
    tetrahedralized_volume.Rescale(scale);
    LOG::cout<<"Adding Tetrahedralized Volume - Tetrahedra = "<<tetrahedralized_volume.mesh.elements.m<<std::endl;
    Set_Mass_Of_Particles(tetrahedralized_volume,density,use_constant_mass);
    Set_Initial_Particle_Configuration(particles,initial_state,relative_to_box_center);
    TETRAHEDRALIZED_VOLUME<T>& copy=Copy_And_Add_Structure(tetrahedralized_volume);
    delete &particles;
    return copy;
}
//#####################################################################
// Function Create_Triangulated_Object
//#####################################################################
template<class TV> MESH_OBJECT<TV,2>& DEFORMABLES_STANDARD_TESTS<TV>::
Create_Triangulated_Object(const GRID<TV>& square_grid,const RIGID_BODY_STATE<TV>& initial_state,const T density)
{
    DEFORMABLE_PARTICLES<TV>& particles=*new DEFORMABLE_PARTICLES<TV>;
    T_TRIANGULATED_OBJECT& triangulated_object=*T_TRIANGULATED_OBJECT::Create(particles);
    triangulated_object.Initialize_Square_Mesh_And_Particles(square_grid);
    triangulated_object.Check_Signed_Areas_And_Make_Consistent(true);
    LOG::cout<<"Adding Triangulated Object - Triangles = "<<triangulated_object.mesh.elements.m<<std::endl;
    Set_Mass_Of_Particles(triangulated_object,density,false);
    Set_Initial_Particle_Configuration(particles,initial_state,true);
    MESH_OBJECT<TV,2>& copy=Copy_And_Add_Structure(triangulated_object);
    delete &particles;
    return copy;
}
//#####################################################################
// Function Create_Triangulated_Object
//#####################################################################
template<class TV> MESH_OBJECT<TV,2>& DEFORMABLES_STANDARD_TESTS<TV>::
Create_Triangulated_Object(const std::string& filename,const RIGID_BODY_STATE<TV>& initial_state,const bool relative_to_box_center,const bool use_constant_mass,const T scale)
{
    PHYSBAM_ASSERT(scale>0);
    DEFORMABLE_PARTICLES<TV>& particles=*new DEFORMABLE_PARTICLES<TV>;
    T_TRIANGULATED_OBJECT& triangulated_object=*T_TRIANGULATED_OBJECT::Create(particles);
    Read_From_File(filename,triangulated_object);
    triangulated_object.Rescale(scale);
    LOG::cout<<"Adding Triangulated Object - Triangles = "<<triangulated_object.mesh.elements.m<<std::endl;
    T density=TV::m==1?1:TV::m==2?100:1000;
    Set_Mass_Of_Particles(triangulated_object,density,use_constant_mass);
    Set_Initial_Particle_Configuration(particles,initial_state,relative_to_box_center);
    MESH_OBJECT<TV,2>& copy=Copy_And_Add_Structure(triangulated_object);
    delete &particles;
    return copy;
}
//#####################################################################
// Function Create_Segmented_Curve
//#####################################################################
template<class TV> MESH_OBJECT<TV,1>& DEFORMABLES_STANDARD_TESTS<TV>::
Create_Segmented_Curve(const std::string& filename,const RIGID_BODY_STATE<TV>& initial_state,const bool relative_to_box_center,const bool use_constant_mass)
{
    DEFORMABLE_PARTICLES<TV>& particles=*new DEFORMABLE_PARTICLES<TV>;
    T_SEGMENTED_CURVE& segmented_curve=*T_SEGMENTED_CURVE::Create(particles);
    Read_From_File(filename,segmented_curve);
    LOG::cout<<"Adding Segmented Curve - Segments = "<<segmented_curve.mesh.elements.m<<std::endl;
    T density=TV::m==1?1:TV::m==2?100:1000;
    Set_Mass_Of_Particles(segmented_curve,density,use_constant_mass);
    Set_Initial_Particle_Configuration(particles,initial_state,relative_to_box_center);
    MESH_OBJECT<TV,1>& copy=Copy_And_Add_Structure(segmented_curve);
    delete &particles;
    return copy;
}
//#####################################################################
// Function Create_Mattress
//#####################################################################
template<class TV> TRIANGULATED_AREA<typename TV::SCALAR>& DEFORMABLES_STANDARD_TESTS<TV>::
Create_Mattress(const GRID<VECTOR<T,2> >& mattress_grid,const bool use_constant_mass,const RIGID_BODY_STATE<TV>* initial_state,const T density,const bool reverse_triangles)
{
    PHYSBAM_ASSERT(density>0);
    DEFORMABLE_PARTICLES<TV>& particles=*new DEFORMABLE_PARTICLES<TV>;
    TRIANGULATED_AREA<T>& triangulated_area=*TRIANGULATED_AREA<T>::Create(particles);
    triangulated_area.Initialize_Square_Mesh_And_Particles(mattress_grid,reverse_triangles);
    triangulated_area.Check_Signed_Areas_And_Make_Consistent(true);
    LOG::cout<<"Adding Mattress - Total Triangles : "<<triangulated_area.mesh.elements.m<<std::endl;
    particles.Store_Velocity();
    Set_Mass_Of_Particles(triangulated_area,density,use_constant_mass);
    if(initial_state) Set_Initial_Particle_Configuration(particles,*initial_state,true);
    TRIANGULATED_AREA<T>& copy=Copy_And_Add_Structure(triangulated_area);
    delete &particles;
    return copy;
}
//#####################################################################
// Function Create_Mattress
//#####################################################################
template<class TV> TETRAHEDRALIZED_VOLUME<typename TV::SCALAR>& DEFORMABLES_STANDARD_TESTS<TV>::
Create_Mattress(const GRID<VECTOR<T,3> >& mattress_grid,const bool use_constant_mass,const RIGID_BODY_STATE<TV>* initial_state,const T density)
{
    PHYSBAM_ASSERT(density>0);
    DEFORMABLE_PARTICLES<TV>& particles=*new DEFORMABLE_PARTICLES<TV>;
    TETRAHEDRALIZED_VOLUME<T>& tetrahedralized_volume=*TETRAHEDRALIZED_VOLUME<T>::Create(particles);
    tetrahedralized_volume.Initialize_Cube_Mesh_And_Particles(mattress_grid);
    LOG::cout<<"Adding Mattress - Total Tetrahedra : "<<tetrahedralized_volume.mesh.elements.m<<std::endl;
    particles.Store_Velocity();
    Set_Mass_Of_Particles(tetrahedralized_volume,density,use_constant_mass);
    if(initial_state) Set_Initial_Particle_Configuration(particles,*initial_state,true);
    TETRAHEDRALIZED_VOLUME<T>& copy=Copy_And_Add_Structure(tetrahedralized_volume);
    delete &particles;
    return copy;
}
//#####################################################################
// Function Create_Cylinder
//#####################################################################
template<class TV> TETRAHEDRALIZED_VOLUME<typename TV::SCALAR>& DEFORMABLES_STANDARD_TESTS<TV>::
Create_Cylinder(const CYLINDER<T>& cylinder,int num_elements_height,int num_elements_radius,const bool use_constant_mass,const RIGID_BODY_STATE<TV>* initial_state,const T density)
{
    PHYSBAM_ASSERT(density>0);
    DEFORMABLE_PARTICLES<TV>& particles=*new DEFORMABLE_PARTICLES<TV>;
    TETRAHEDRALIZED_VOLUME<T>& tetrahedralized_volume=*TETRAHEDRALIZED_VOLUME<T>::Create(particles);
    tetrahedralized_volume.Initialize_Cylinder_Mesh_And_Particles(cylinder,num_elements_height,num_elements_radius);
    LOG::cout<<"Adding Cylinder - Total Tetrahedra : "<<tetrahedralized_volume.mesh.elements.m<<std::endl;
    particles.Store_Velocity();
    Set_Mass_Of_Particles(tetrahedralized_volume,density,use_constant_mass);
    if(initial_state) Set_Initial_Particle_Configuration(particles,*initial_state,true);
    TETRAHEDRALIZED_VOLUME<T>& copy=Copy_And_Add_Structure(tetrahedralized_volume);
    delete &particles;
    return copy;
}
//#####################################################################
// Function Create_Embedded_Tetrahedralized_Volume
//#####################################################################
template<class TV> template<class T_SHAPE> EMBEDDED_TETRAHEDRALIZED_VOLUME_BOUNDARY_SURFACE<typename TV::SCALAR>& DEFORMABLES_STANDARD_TESTS<TV>::
Create_Embedded_Tetrahedralized_Volume(const T_SHAPE& shape,const RIGID_BODY_STATE<TV>& initial_state,const bool relative_to_box_center)
{
    DEFORMABLE_PARTICLES<TV>& particles=*(new DEFORMABLE_PARTICLES<TV>());
    EMBEDDED_TETRAHEDRALIZED_VOLUME_BOUNDARY_SURFACE<T>& embedding=*EMBEDDED_TETRAHEDRALIZED_VOLUME_BOUNDARY_SURFACE<T>::Create(particles);
    EMBEDDED_TETRAHEDRALIZED_VOLUME<T>& embedded_tetrahedralized_volume=embedding.embedded_object;
    embedded_tetrahedralized_volume.Set_Interpolation_Fraction_Threshold((T)1e-2);
    TETRAHEDRALIZED_VOLUME<T>& tetrahedralized_volume=embedded_tetrahedralized_volume.simplicial_object;
    GRID<TV> grid(IV()+12,RANGE<TV>::Centered_Box());tetrahedralized_volume.Initialize_Octahedron_Mesh_And_Particles(grid);
    ARRAY<T> phi(particles.Size());
    for(int p=0;p<phi.m;p++) phi(p)=shape.Signed_Distance(particles.X(p));
    // compute embedded surface
    particles.Store_Velocity();
    T density=TV::m==1?1:TV::m==2?100:1000;
    Set_Mass_Of_Particles(tetrahedralized_volume,density,true);
    embedded_tetrahedralized_volume.Calculate_Boundary_From_Levelset_On_Nodes(phi);
    tetrahedralized_volume.mesh.number_nodes=particles.Size();
    embedding.Create_Material_Surface_From_Manifold_Embedded_Surface();
    Set_Initial_Particle_Configuration(particles,initial_state,relative_to_box_center);
    EMBEDDED_TETRAHEDRALIZED_VOLUME_BOUNDARY_SURFACE<T>& copy=Copy_And_Add_Structure(embedding);
    delete &particles;
    return copy;
}
//#####################################################################
// Function Substitute_Soft_Bindings_For_Nodes
//#####################################################################
template<class TV> template<class T_OBJECT> void DEFORMABLES_STANDARD_TESTS<TV>::
Substitute_Soft_Bindings_For_Nodes(T_OBJECT& object,SOFT_BINDINGS<TV>& soft_bindings,HASHTABLE<int,int>* persistent_soft_bindings,const bool embedded_only,
    const bool use_impulses_for_collisions)
{
    DEFORMABLE_PARTICLES<TV>& particles=soft_bindings.particles;
    ARRAY<int> nodes=Get_Unique(object.mesh.elements.Flattened());
    ARRAY<int> map_to_new_particles(particles.Size());
    Set_Counting(map_to_new_particles);
    for(int i=0;i<nodes.m;i++) if(!embedded_only || soft_bindings.binding_list.Binding_Index_From_Particle_Index(nodes(i))>=0){
        int node=nodes(i),bound_node;
        if(persistent_soft_bindings && persistent_soft_bindings->Get(node,bound_node))
            persistent_soft_bindings->Delete(node);
        else{
            bound_node=particles.Add_Element_From_Deletion_List();
            particles.Copy_Element(particles,node,bound_node);}
        map_to_new_particles(node)=bound_node;
        soft_bindings.Add_Binding(VECTOR<int,2>(bound_node,node),use_impulses_for_collisions);}
    for(int i=0;i<object.mesh.elements.m;i++) object.mesh.elements(i)=map_to_new_particles.Subset(object.mesh.elements(i));
}
//#####################################################################
// Function Read_Or_Initialize_Implicit_Surface
//#####################################################################
template<class TV> LEVELSET_IMPLICIT_OBJECT<TV>* DEFORMABLES_STANDARD_TESTS<TV>::
Read_Or_Initialize_Implicit_Surface(const std::string& levelset_filename,const VIEWER_DIR& viewer_dir,TRIANGULATED_SURFACE<T>& undeformed_triangulated_surface) const
{
    if(File_Exists(levelset_filename)){
        LEVELSET_IMPLICIT_OBJECT<TV>& undeformed_levelset=*LEVELSET_IMPLICIT_OBJECT<TV>::Create();
        Read_From_File(levelset_filename,undeformed_levelset);
        return &undeformed_levelset;}
    LEVELSET_IMPLICIT_OBJECT<TV>& undeformed_levelset=*Initialize_Implicit_Surface(undeformed_triangulated_surface,100);
    fs::create_directories(viewer_dir.output_directory);
    Write_To_File(stream_type,levelset_filename,undeformed_levelset);
    return &undeformed_levelset;
}
//#####################################################################
// Function Initialize_Tetrahedron_Collisions
//#####################################################################
template<class TV> void DEFORMABLES_STANDARD_TESTS<TV>::
Initialize_Tetrahedron_Collisions(const int id_number,VIEWER_DIR& viewer_dir,TETRAHEDRALIZED_VOLUME<T>& tetrahedralized_volume,TRIANGLE_COLLISION_PARAMETERS<TV>& triangle_collision_parameters,
    TRIANGULATED_SURFACE<T>* triangulated_surface)
{
    triangle_collision_parameters.perform_self_collision=false;
    tetrahedralized_volume.Update_Number_Nodes();
    if(!triangulated_surface){
        if(!tetrahedralized_volume.triangulated_surface) tetrahedralized_volume.Initialize_Triangulated_Surface();
        triangulated_surface=tetrahedralized_volume.triangulated_surface;}
    DEFORMABLE_PARTICLES<TV>& undeformed_particles=*new DEFORMABLE_PARTICLES<TV>(deformable_body_collection.particles);
    TRIANGULATED_SURFACE<T>& undeformed_triangulated_surface=*(new TRIANGULATED_SURFACE<T>(triangulated_surface->mesh,undeformed_particles));
    undeformed_triangulated_surface.Update_Triangle_List();
    undeformed_triangulated_surface.Initialize_Hierarchy();
    viewer_dir.Make_Common_Directory();
    std::string levelset_filename=viewer_dir.Common_File(
        LOG::sprintf("deformable_body_undeformed_levelset_%d.phi",id_number));
    LEVELSET_IMPLICIT_OBJECT<TV>& undeformed_levelset=*Read_Or_Initialize_Implicit_Surface(levelset_filename,viewer_dir,undeformed_triangulated_surface);
    deformable_body_collection.collisions.collision_body_list.Add_Body(new TETRAHEDRON_COLLISION_BODY<T>(tetrahedralized_volume,undeformed_triangulated_surface,undeformed_levelset,triangulated_surface),0,true);
}
//#####################################################################
// Function Create_Drifted_Surface
//#####################################################################
template<class TV> TRIANGULATED_SURFACE<typename TV::SCALAR>& DEFORMABLES_STANDARD_TESTS<TV>::
Create_Drifted_Surface(const TRIANGULATED_SURFACE<T>& triangulated_surface,SOFT_BINDINGS<TV>& soft_bindings,const bool use_impulses_for_collisions) const
{
    assert(&triangulated_surface.particles==&soft_bindings.particles);
    DEFORMABLE_PARTICLES<TV>& particles=soft_bindings.particles;
    ARRAY<int> triangulated_surface_nodes=Get_Unique(triangulated_surface.mesh.elements.Flattened());
    ARRAY<int> child_particles(particles.Size());
    particles.Preallocate(particles.Size()+triangulated_surface_nodes.m);
    for(int i=0;i<triangulated_surface_nodes.m;i++){int p=triangulated_surface_nodes(i);
        child_particles(p)=particles.Append(particles,p);
        soft_bindings.Add_Binding(VECTOR<int,2>(child_particles(p),p),use_impulses_for_collisions);}
    TRIANGULATED_SURFACE<T>& drifted_surface=*TRIANGULATED_SURFACE<T>::Create(particles);
    for(int i=0;i<triangulated_surface.mesh.elements.m;i++)
        drifted_surface.mesh.elements.Append(VECTOR<int,3>(child_particles.Subset(triangulated_surface.mesh.elements(i))));
    return drifted_surface;
}
//#####################################################################
// Function Set_Mass_Of_Particles 
//#####################################################################
template<class TV> template<class T_OBJECT> void DEFORMABLES_STANDARD_TESTS<TV>::
Set_Mass_Of_Particles(const T_OBJECT& object,const T density,const bool use_constant_mass)
{
    PHYSBAM_ASSERT(density>0);
    if(!object.mesh.elements.m) return;
    DEFORMABLE_PARTICLES<TV>& particles=dynamic_cast<DEFORMABLE_PARTICLES<TV>&>(object.particles);
    particles.Store_Mass();
    ARRAY<int> nodes=Get_Unique(object.mesh.elements.Flattened());
    if(use_constant_mass&&nodes.m){
        T mass_per_node=density*object.Total_Size()/nodes.m;
        particles.mass.Subset(nodes).Fill(mass_per_node);}
    else{
        particles.mass.Subset(nodes).Fill((T)0);
        int nodes_per_element=object.mesh.elements(0).m;
        T density_scaled=density/(T)nodes_per_element;
        for(int t=0;t<object.mesh.elements.m;t++){
            T mass_scaled=density_scaled*object.Signed_Size(t);
            assert(mass_scaled>0);
            particles.mass.Subset(object.mesh.elements(t))+=mass_scaled;}}
}
//#####################################################################
// Function Create_Cloth_Panel
//#####################################################################
template<class TV> TRIANGULATED_SURFACE<typename TV::SCALAR>& DEFORMABLES_STANDARD_TESTS<TV>::
Create_Cloth_Panel(const int number_side_panels,const T side_length,const T aspect_ratio,const RIGID_BODY_STATE<TV>* initial_state,
    std::function<void (TRIANGULATED_SURFACE<T>&)> clipping_function,ARRAY<int>* particle_indices)
{
    DEFORMABLE_PARTICLES<TV>& particles=*(new DEFORMABLE_PARTICLES<TV>());
    TRIANGULATED_SURFACE<T>& triangulated_surface=*TRIANGULATED_SURFACE<T>::Create(particles);
    TRIANGLE_MESH& mesh=triangulated_surface.mesh;
    particles.Store_Mass();
    int m=(int)(aspect_ratio*number_side_panels)+1,n=number_side_panels+1;
    mesh.Initialize_Herring_Bone_Mesh(m,n);particles.Add_Elements(mesh.number_nodes);
    T mass_node=aspect_ratio*sqr(side_length)/(m*n);particles.mass.Fill(mass_node); // TODO: make this consistent with the density attribute
    T dx=aspect_ratio*side_length/(m-1),dy=side_length/(n-1);
    for(int i=0;i<m;i++) for(int j=0;j<n;j++) particles.X(i+m*j)=TV(i*dx,(T).5,j*dy);
    if(initial_state) Set_Initial_Particle_Configuration(particles,*initial_state,true);
    if(clipping_function) clipping_function(triangulated_surface);
    TRIANGULATED_SURFACE<T>& copy=Copy_And_Add_Structure(triangulated_surface,particle_indices);
    delete &particles;
    return copy;
}
//#####################################################################
// Function Embed_Particles_In_Tetrahedralized_Volume
//#####################################################################
template<class TV> void DEFORMABLES_STANDARD_TESTS<TV>::
Embed_Particles_In_Tetrahedralized_Volume(BINDING_LIST<VECTOR<T,3> >& binding_list,const PARTICLES_SUBSET<VECTOR<T,3>,DEFORMABLE_PARTICLES<VECTOR<T,3> > >& particles_to_embed,
    TETRAHEDRALIZED_VOLUME<T>& tetrahedralized_volume,const T thickness_over_two)
{
    bool tetrahedron_list_initialized=tetrahedralized_volume.tetrahedron_list!=0;if(!tetrahedron_list_initialized) tetrahedralized_volume.Update_Tetrahedron_List();
    bool hierarchy_initialized=tetrahedralized_volume.hierarchy!=0;if(!hierarchy_initialized) tetrahedralized_volume.Initialize_Hierarchy();
    const ARRAY<TETRAHEDRON<T> >& tetrahedron_list=*tetrahedralized_volume.tetrahedron_list;
    ARRAY<int> candidate_tets;
    for(int p=0;p<particles_to_embed.Size();p++){
        VECTOR<T,3> X=particles_to_embed.X(p);
        T current_thickness_over_two=thickness_over_two;
        candidate_tets.Remove_All();
        // find tetrahedron
        int embedding_tet=0;
        while(!candidate_tets.m){
            tetrahedralized_volume.hierarchy->Intersection_List(X,candidate_tets,current_thickness_over_two);
            if(candidate_tets.m || thickness_over_two==0) break;
            current_thickness_over_two*=2;}
        for(int t=0;t<candidate_tets.m;t++) if(tetrahedron_list(candidate_tets(t)).Inside(X)){embedding_tet=candidate_tets(t);break;}
        if(!embedding_tet && candidate_tets.m){ // find closest tet
            T rho_min=FLT_MAX;
            for(int t=0;t<candidate_tets.m;t++){
                T rho=(tetrahedron_list(candidate_tets(t)).Surface(X)-X).Magnitude_Squared();if(rho<rho_min){rho_min=rho;embedding_tet=candidate_tets(t);}}}
        // add binding
        VECTOR<int,4> vertices=tetrahedralized_volume.mesh.elements(embedding_tet);
        VECTOR<T,3> weights=tetrahedron_list(embedding_tet).Barycentric_Coordinates(X);
        binding_list.Add_Binding(new LINEAR_BINDING<VECTOR<T,3>,4>(binding_list.particles,particles_to_embed.active_indices(p),vertices,weights));}
    // clean up
    if(!tetrahedron_list_initialized){delete tetrahedralized_volume.tetrahedron_list;tetrahedralized_volume.tetrahedron_list=0;}
    if(!hierarchy_initialized){delete tetrahedralized_volume.hierarchy;tetrahedralized_volume.hierarchy=0;}
}
//#####################################################################
// Function Find_Intersected_Segments_Triangles
//#####################################################################
template<class TV> void DEFORMABLES_STANDARD_TESTS<TV>::
Find_Intersected_Segments_Triangles(SEGMENTED_CURVE<TV>& segments,TRIANGULATED_SURFACE<T>& surface,ARRAY<bool>* segments_intersected,ARRAY<bool>* triangles_intersected,T thickness_over_two,ARRAY<T>* segment_weights,ARRAY<VECTOR<T,3> >* triangle_weights)
{
    bool hierarchy_initialized=surface.hierarchy!=0;if(!hierarchy_initialized) surface.Initialize_Hierarchy();
    ARRAY<int> candidates;
    if(segments_intersected){segments_intersected->Remove_All();segments_intersected->Resize(segments.mesh.elements.m);}
    if(triangles_intersected){triangles_intersected->Remove_All();triangles_intersected->Resize(surface.mesh.elements.m);}
    if(segment_weights){segment_weights->Remove_All();segment_weights->Resize(segments.mesh.elements.m);}
    if(triangle_weights){triangle_weights->Remove_All();triangle_weights->Resize(surface.mesh.elements.m);}
    VECTOR<T,2> segment_weight;
    VECTOR<T,3> triangle_weight;
    for(int i=0;i<segments.mesh.elements.m;i++){
        SEGMENT_3D<T> segment(segments.particles.X.Subset(segments.mesh.elements(i)));
        RANGE<TV> box(segment.X.x);
        box.Enlarge_To_Include_Point(segment.X.y);
        candidates.Remove_All();
        surface.hierarchy->Intersection_List(box,candidates,thickness_over_two);
        for(int j=0;j<candidates.m;j++)
            if(Intersects(segment,surface.Get_Element(candidates(j)),segment_weight,triangle_weight,thickness_over_two)){
                if(segments_intersected) (*segments_intersected)(i)=true;
                if(triangles_intersected) (*triangles_intersected)(candidates(j))=true;
                if(segment_weights) (*segment_weights)(i)=segment_weight.x;
                if(triangle_weights) (*triangle_weights)(candidates(j))=triangle_weight;}}
    if(!hierarchy_initialized){delete surface.hierarchy;surface.hierarchy=0;}
}
//#####################################################################
// Function Embed_Surface_In_Tetrahedralized_Volume
//#####################################################################
template<class TV> void DEFORMABLES_STANDARD_TESTS<TV>::
Embed_Surface_In_Tetrahedralized_Volume(BINDING_LIST<TV>& binding_list,SOFT_BINDINGS<TV>& soft_bindings,TRIANGULATED_SURFACE<T>& surface,TETRAHEDRALIZED_VOLUME<T>& volume,const T thickness_over_two,
    ARRAY<int>& surface_particle_map,ARRAY<int>& volume_particle_map,TRIANGULATED_SURFACE<T>** new_surface,TETRAHEDRALIZED_VOLUME<T>** new_volume,bool bind_edges)
{
    if(!volume.tetrahedron_list) volume.Update_Tetrahedron_List();
    if(!volume.hierarchy) volume.Initialize_Hierarchy();
    if(!volume.mesh.triangle_mesh) volume.mesh.Initialize_Triangle_Mesh();
    if(!volume.mesh.segment_mesh) volume.mesh.Initialize_Segment_Mesh();
    if(!volume.mesh.adjacent_elements) volume.mesh.Initialize_Adjacent_Elements();
    if(!surface.mesh.segment_mesh) surface.mesh.Initialize_Segment_Mesh();
    if(!surface.bounding_box) surface.Update_Bounding_Box();
    if(!surface.hierarchy) surface.Initialize_Hierarchy();
    if(!surface.mesh.adjacent_elements) surface.mesh.Initialize_Adjacent_Elements();
    if(!surface.triangle_list) surface.Update_Triangle_List();
    if(!surface.mesh.incident_elements) surface.mesh.Initialize_Incident_Elements();

    ARRAY<int> candidates,point_to_tet(surface.particles.X.m);
    ARRAY<TV> weights(surface.particles.X.m);
    for(int p=0;p<surface.particles.X.m;p++){
        point_to_tet(p)=volume.Find(surface.particles.X(p),thickness_over_two,candidates);
        weights(p)=(*volume.tetrahedron_list)(point_to_tet(p)).First_Three_Barycentric_Coordinates(surface.particles.X(p));}

    HASHTABLE<VECTOR<int,2> > marked_segments;
    SEGMENTED_CURVE<TV> volume_segments(*volume.mesh.segment_mesh,volume.particles);
    ARRAY<bool> segment_intersected(volume_segments.mesh.elements.m);
    ARRAY<T> binding_weights;
    Find_Intersected_Segments_Triangles(volume_segments,surface,&segment_intersected,0,thickness_over_two,bind_edges?&binding_weights:0,0);
    for(int i=0;i<segment_intersected.m;i++) if(segment_intersected(i)) marked_segments.Set(volume_segments.mesh.elements(i).Sorted());

    ARRAY<pair<VECTOR<int,2>,T> > edge_bindings;
    if(bind_edges){
        for(int i=0;i<segment_intersected.m;i++)
            if(segment_intersected(i))
                edge_bindings.Append(pair<VECTOR<int,2>,T>(volume.mesh.segment_mesh->elements(i),binding_weights(i)));}

    ARRAY<int> tet_color(volume.mesh.elements.m); // 0=unknown, 1=boundary, 2=inside, 3=outside
    for(int i=0;i<point_to_tet.m;i++) tet_color(point_to_tet(i))=1;

    TRIANGULATED_SURFACE<T> volume_triangles(*volume.mesh.triangle_mesh,volume.particles);
    SEGMENTED_CURVE<TV> surface_segments(*surface.mesh.segment_mesh,surface.particles);
    ARRAY<bool> triangle_intersected(volume_triangles.mesh.elements.m);
    Find_Intersected_Segments_Triangles(surface_segments,volume_triangles,0,&triangle_intersected,thickness_over_two,0,0);
    HASHTABLE<VECTOR<int,3> > marked_triangles;
    for(int i=0;i<triangle_intersected.m;i++) if(triangle_intersected(i)) marked_triangles.Set(volume_triangles.mesh.elements(i).Sorted());

    for(int i=0;i<volume.mesh.elements.m;i++){
        int a,b,c,d;volume.mesh.elements(i).Get(a,b,c,d);
        if(marked_triangles.Contains(VECTOR<int,3>(a,b,c).Sorted())){tet_color(i)=1;continue;}
        if(marked_triangles.Contains(VECTOR<int,3>(a,b,d).Sorted())){tet_color(i)=1;continue;}
        if(marked_triangles.Contains(VECTOR<int,3>(a,c,d).Sorted())){tet_color(i)=1;continue;}
        if(marked_triangles.Contains(VECTOR<int,3>(b,c,d).Sorted())){tet_color(i)=1;continue;}
        if(marked_segments.Contains(VECTOR<int,2>(a,b).Sorted())){tet_color(i)=1;continue;}
        if(marked_segments.Contains(VECTOR<int,2>(a,c).Sorted())){tet_color(i)=1;continue;}
        if(marked_segments.Contains(VECTOR<int,2>(a,d).Sorted())){tet_color(i)=1;continue;}
        if(marked_segments.Contains(VECTOR<int,2>(b,c).Sorted())){tet_color(i)=1;continue;}
        if(marked_segments.Contains(VECTOR<int,2>(b,d).Sorted())){tet_color(i)=1;continue;}
        if(marked_segments.Contains(VECTOR<int,2>(c,d).Sorted())){tet_color(i)=1;continue;}}

    ARRAY<int> todo;
    for(int i=0;i<volume.mesh.elements.m;i++){
        if(tet_color(i)) continue;
        int color=surface.Outside(volume.particles.X(volume.mesh.elements(i)(0)),thickness_over_two)?3:2;
        tet_color(i)=color;
        todo.Append(i);
        while(todo.m){
            int j=todo.Pop_Value();
            ARRAY<int>& list=(*volume.mesh.adjacent_elements)(j);
            for(int k=0;k<list.m;k++){
                int e=list(k);
                if(!tet_color(e)){
                    tet_color(e)=color;
                    todo.Append(e);}}}}

    ARRAY<int> tet_map(volume.mesh.elements.m);
    int k=0;
    for(int i=0;i<volume.mesh.elements.m;i++){
        if(tet_color(i)!=3){
            volume.mesh.elements(k)=volume.mesh.elements(i);
            tet_map(i)=k++;}}
    volume.mesh.elements.Resize(k);
    for(int i=0;i<point_to_tet.m;i++) point_to_tet(i)=tet_map(point_to_tet(i));
    volume.Discard_Valence_Zero_Particles_And_Renumber(volume_particle_map);

    volume.Update_Number_Nodes();
    ARRAY<int> particle_indices;
    TETRAHEDRALIZED_VOLUME<T>& new_v=Copy_And_Add_Structure(volume,&particle_indices);
    if(new_volume) *new_volume=&new_v;
    for(int i=0;i<volume_particle_map.m;i++)
        if(volume_particle_map(i)>=0)
            volume_particle_map(i)=particle_indices(volume_particle_map(i));

    if(bind_edges){
        FREE_PARTICLES<TV>* free_particles=new FREE_PARTICLES<TV>;
        for(int i=0;i<edge_bindings.m;i++){
            int hb=binding_list.particles.Add_Element();
            int sb=binding_list.particles.Add_Element();
            VECTOR<int,2> pa(volume_particle_map.Subset(edge_bindings(i).first));
            VECTOR<T,2> we(1-edge_bindings(i).second,edge_bindings(i).second);
            binding_list.Add_Binding(new LINEAR_BINDING<TV,2>(binding_list.particles,hb,pa,we));
            soft_bindings.Add_Binding(VECTOR<int,2>(sb,hb),true);
            free_particles->nodes.Append(sb);}
        deformable_body_collection.Add_Structure(free_particles);}

    TRIANGULATED_SURFACE<T>& new_s=Copy_And_Add_Structure(surface,&surface_particle_map);
    if(new_surface) *new_surface=&new_s;
    for(int i=0;i<point_to_tet.m;i++){
        VECTOR<int,4> vertices=new_v.mesh.elements(point_to_tet(i));
        binding_list.Add_Binding(new LINEAR_BINDING<VECTOR<T,3>,4>(binding_list.particles,surface_particle_map(i),vertices,weights(i)));}
}
//#####################################################################
// Function Create_Embedded_Surface
//#####################################################################
template<class TV> void DEFORMABLES_STANDARD_TESTS<TV>::
Create_Regular_Embedded_Surface(BINDING_LIST<TV>& binding_list,SOFT_BINDINGS<TV>& soft_bindings,TRIANGULATED_SURFACE<T>& surface,T density,int approx_volume,const T thickness_over_two,ARRAY<int>& surface_particle_map,
    TRIANGULATED_SURFACE<T>** new_surface,TETRAHEDRALIZED_VOLUME<T>** new_volume,bool bind_edges)
{
    surface.Update_Bounding_Box();
    RANGE<TV> box(*surface.bounding_box);
    T dx=pow(box.Size()/approx_volume,(T)1./3);
    TV cells=ceil(box.Edge_Lengths()/dx);
    box.Scale_About_Center(cells*dx/box.Edge_Lengths());
    GRID<TV> grid(IV(cells),box);

    DEFORMABLE_PARTICLES<TV>& particles=*new DEFORMABLE_PARTICLES<TV>;
    TETRAHEDRALIZED_VOLUME<T>& volume=*TETRAHEDRALIZED_VOLUME<T>::Create(particles);
    volume.Initialize_Cube_Mesh_And_Particles(grid);
    particles.Store_Velocity();
    Set_Mass_Of_Particles(volume,density,true);
    ARRAY<int> volume_map;
    Embed_Surface_In_Tetrahedralized_Volume(binding_list,soft_bindings,surface,volume,thickness_over_two,surface_particle_map,volume_map,new_surface,new_volume,bind_edges);
    delete &particles;
}
//#####################################################################
// Function Mark_Hard_Bindings_With_Free_Particles
//#####################################################################
template<class TV> void DEFORMABLES_STANDARD_TESTS<TV>::
Mark_Hard_Bindings_With_Free_Particles()
{
    FREE_PARTICLES<TV>* free_particles=deformable_body_collection.template Find_Structure<FREE_PARTICLES<TV>*>();
    if(!free_particles){
        free_particles=FREE_PARTICLES<TV>::Create();
        deformable_body_collection.Add_Structure(free_particles);}
    for(int i=0;i<deformable_body_collection.binding_list.bindings.m;i++)
        free_particles->nodes.Append(deformable_body_collection.binding_list.bindings(i)->particle_index);
}
//#####################################################################
#define INSTANTIATION_HELPER_1D(T) \
    template MESH_OBJECT<VECTOR<T,1>,1>& DEFORMABLES_STANDARD_TESTS<VECTOR<T,1> >::Create_Segmented_Curve(const GRID<VECTOR<T,1> >&,const RIGID_BODY_STATE<VECTOR<T,1> >&,const T); \
    template void DEFORMABLES_STANDARD_TESTS<VECTOR<T,1> >::Add_Gravity();

#define INSTANTIATION_HELPER_2D(T) \
    INSTANTIATION_HELPER_ALL(T,2) \
    template MESH_OBJECT<VECTOR<T,2>,2>& DEFORMABLES_STANDARD_TESTS<VECTOR<T,2> >::Create_Triangulated_Object(const GRID<VECTOR<T,2> >&,const RIGID_BODY_STATE<VECTOR<T,2> >&,const T);

#define INSTANTIATION_HELPER_ALL(T,d) \
    template void DEFORMABLES_STANDARD_TESTS<VECTOR<T,d> >::Set_Initial_Particle_Configuration(GEOMETRY_PARTICLES<VECTOR<T,d> >&,const RIGID_BODY_STATE<VECTOR<T,d> >&,const bool); \
    template MESH_OBJECT<VECTOR<T,d>,1>& DEFORMABLES_STANDARD_TESTS<VECTOR<T,d> >::Create_Segmented_Curve(const GRID<VECTOR<T,1> >&,const RIGID_BODY_STATE<VECTOR<T,d> >&,const T); \
    template MESH_OBJECT<VECTOR<T,d>,1>& DEFORMABLES_STANDARD_TESTS<VECTOR<T,d> >::Create_Segmented_Curve(const int,const RIGID_BODY_STATE<VECTOR<T,d> >&,const T,const T); \
    template MESH_OBJECT<VECTOR<T,d>,1>& DEFORMABLES_STANDARD_TESTS<VECTOR<T,d> >::Create_Segmented_Curve(const std::string&,const RIGID_BODY_STATE<VECTOR<T,d> >&,const bool,const bool); \
    template MESH_OBJECT<VECTOR<T,d>,2>& DEFORMABLES_STANDARD_TESTS<VECTOR<T,d> >::Create_Triangulated_Object(const std::string&,const RIGID_BODY_STATE<VECTOR<T,d> >&,const bool,const bool,const T); \
    template void DEFORMABLES_STANDARD_TESTS<VECTOR<T,d> >::Substitute_Soft_Bindings_For_Nodes(TRIANGULATED_SURFACE<T>&,SOFT_BINDINGS<VECTOR<T,d> >&,HASHTABLE<int,int>*,const bool,const bool); \
    template void DEFORMABLES_STANDARD_TESTS<VECTOR<T,d> >::Add_Gravity();

#define INSTANTIATION_HELPER(T) \
    INSTANTIATION_HELPER_1D(T) \
    INSTANTIATION_HELPER_2D(T) \
    INSTANTIATION_HELPER_ALL(T,3) \
    template TETRAHEDRALIZED_VOLUME<VECTOR<T,3>::SCALAR>& DEFORMABLES_STANDARD_TESTS<VECTOR<T,3> >::Create_Tetrahedralized_Volume(const std::string&,const RIGID_BODY_STATE<VECTOR<T,3> >&,const bool,const bool,const T,const T); \
    template TRIANGULATED_AREA<VECTOR<T,2>::SCALAR>& DEFORMABLES_STANDARD_TESTS<VECTOR<T,2> >::Create_Mattress(const GRID<VECTOR<T,2> >&,const bool,const RIGID_BODY_STATE<VECTOR<T,2> >*,const T,const bool); \
    template TETRAHEDRALIZED_VOLUME<VECTOR<T,3>::SCALAR>& DEFORMABLES_STANDARD_TESTS<VECTOR<T,3> >::Create_Mattress(const GRID<VECTOR<T,3> >&,const bool,const RIGID_BODY_STATE<VECTOR<T,3> >*,const T); \
    template TRIANGULATED_SURFACE<VECTOR<T,3>::SCALAR>& DEFORMABLES_STANDARD_TESTS<VECTOR<T,3> >::Create_Drifted_Surface(const TRIANGULATED_SURFACE<T>&,SOFT_BINDINGS<VECTOR<T,3> >&,const bool) const; \
    template EMBEDDED_TETRAHEDRALIZED_VOLUME_BOUNDARY_SURFACE<VECTOR<T,3>::SCALAR>& DEFORMABLES_STANDARD_TESTS<VECTOR<T,3> >::Create_Embedded_Tetrahedralized_Volume(const SPHERE<VECTOR<T,3> >&,const RIGID_BODY_STATE<VECTOR<T,3> >&,const bool); \
    template EMBEDDED_TETRAHEDRALIZED_VOLUME_BOUNDARY_SURFACE<VECTOR<T,3>::SCALAR>& DEFORMABLES_STANDARD_TESTS<VECTOR<T,3> >::Create_Embedded_Tetrahedralized_Volume(const TORUS<T>&,const RIGID_BODY_STATE<VECTOR<T,3> >&,const bool); \
    template TETRAHEDRALIZED_VOLUME<T>& DEFORMABLES_STANDARD_TESTS<VECTOR<T,3> >::Copy_And_Add_Structure(TETRAHEDRALIZED_VOLUME<T>&,ARRAY<int>*,bool); \
    template TRIANGULATED_SURFACE<T>& DEFORMABLES_STANDARD_TESTS<VECTOR<T,3> >::Copy_And_Add_Structure(TRIANGULATED_SURFACE<T>&,ARRAY<int>*,bool); \
    template TRIANGULATED_AREA<T>& DEFORMABLES_STANDARD_TESTS<VECTOR<T,2> >::Copy_And_Add_Structure(TRIANGULATED_AREA<T>&,ARRAY<int>*,bool); \
    template SEGMENTED_CURVE<VECTOR<T,2> >& DEFORMABLES_STANDARD_TESTS<VECTOR<T,2> >::Copy_And_Add_Structure(SEGMENTED_CURVE<VECTOR<T,2> >&,ARRAY<int>*,bool); \
    template TRIANGULATED_SURFACE<T>& DEFORMABLES_STANDARD_TESTS<VECTOR<T,3> >::Create_Cloth_Panel(const int,const T,const T,const RIGID_BODY_STATE<VECTOR<T,3> >*, \
        std::function<void (TRIANGULATED_SURFACE<T>&)>,ARRAY<int>*);                  \
    template void DEFORMABLES_STANDARD_TESTS<VECTOR<T,2> >::Set_Mass_Of_Particles<SEGMENTED_CURVE<VECTOR<T,2> > >(SEGMENTED_CURVE<VECTOR<T,2> > const&,T,bool); \
    template void DEFORMABLES_STANDARD_TESTS<VECTOR<T,3> >::Set_Mass_Of_Particles<SEGMENTED_CURVE<VECTOR<T,3> > >(SEGMENTED_CURVE<VECTOR<T,3> > const&,T,bool);

INSTANTIATION_HELPER(float);
template void DEFORMABLES_STANDARD_TESTS<VECTOR<float,3> >::Mark_Hard_Bindings_With_Free_Particles();
template void DEFORMABLES_STANDARD_TESTS<VECTOR<float,3> >::Set_Mass_Of_Particles<TRIANGULATED_SURFACE<float> >(TRIANGULATED_SURFACE<float> const&,float,bool);
template void DEFORMABLES_STANDARD_TESTS<VECTOR<float,2> >::Set_Mass_Of_Particles<TRIANGULATED_AREA<float> >(TRIANGULATED_AREA<float> const&,float,bool);
template void DEFORMABLES_STANDARD_TESTS<VECTOR<float,3> >::Set_Mass_Of_Particles<TETRAHEDRALIZED_VOLUME<float> >(TETRAHEDRALIZED_VOLUME<float> const&,float,bool);
template void DEFORMABLES_STANDARD_TESTS<VECTOR<float,1> >::Set_Mass_Of_Particles<SEGMENTED_CURVE<VECTOR<float,1> > >(SEGMENTED_CURVE<VECTOR<float,1> > const&,float,bool);
template void DEFORMABLES_STANDARD_TESTS<VECTOR<float,3> >::Create_Regular_Embedded_Surface(BINDING_LIST<VECTOR<float,3> >&,SOFT_BINDINGS<VECTOR<float,3> >&,
    TRIANGULATED_SURFACE<float>&,float,int,float,ARRAY<int,int>&,TRIANGULATED_SURFACE<float>**,TETRAHEDRALIZED_VOLUME<float>**,bool);
template DEFORMABLES_STANDARD_TESTS<VECTOR<float,1> >::~DEFORMABLES_STANDARD_TESTS();
template DEFORMABLES_STANDARD_TESTS<VECTOR<float,2> >::~DEFORMABLES_STANDARD_TESTS();
template DEFORMABLES_STANDARD_TESTS<VECTOR<float,3> >::~DEFORMABLES_STANDARD_TESTS();
INSTANTIATION_HELPER(double);
template void DEFORMABLES_STANDARD_TESTS<VECTOR<double,3> >::Mark_Hard_Bindings_With_Free_Particles();
template void DEFORMABLES_STANDARD_TESTS<VECTOR<double,3> >::Set_Mass_Of_Particles<TRIANGULATED_SURFACE<double> >(TRIANGULATED_SURFACE<double> const&,double,bool);
template void DEFORMABLES_STANDARD_TESTS<VECTOR<double,2> >::Set_Mass_Of_Particles<TRIANGULATED_AREA<double> >(TRIANGULATED_AREA<double> const&,double,bool);
template void DEFORMABLES_STANDARD_TESTS<VECTOR<double,3> >::Set_Mass_Of_Particles<TETRAHEDRALIZED_VOLUME<double> >(TETRAHEDRALIZED_VOLUME<double> const&,double,bool);
template void DEFORMABLES_STANDARD_TESTS<VECTOR<double,1> >::Set_Mass_Of_Particles<SEGMENTED_CURVE<VECTOR<double,1> > >(SEGMENTED_CURVE<VECTOR<double,1> > const&,double,bool);
template void DEFORMABLES_STANDARD_TESTS<VECTOR<double,3> >::Create_Regular_Embedded_Surface(BINDING_LIST<VECTOR<double,3> >&,SOFT_BINDINGS<VECTOR<double,3> >&,
    TRIANGULATED_SURFACE<double>&,double,int,double,ARRAY<int,int>&,TRIANGULATED_SURFACE<double>**,TETRAHEDRALIZED_VOLUME<double>**,bool);
template DEFORMABLES_STANDARD_TESTS<VECTOR<double,1> >::~DEFORMABLES_STANDARD_TESTS();
template DEFORMABLES_STANDARD_TESTS<VECTOR<double,2> >::~DEFORMABLES_STANDARD_TESTS();
template DEFORMABLES_STANDARD_TESTS<VECTOR<double,3> >::~DEFORMABLES_STANDARD_TESTS();
template DEFORMABLES_STANDARD_TESTS<VECTOR<double,1> >::DEFORMABLES_STANDARD_TESTS(STREAM_TYPE,DEFORMABLE_BODY_COLLECTION<VECTOR<double,1> >&);
template DEFORMABLES_STANDARD_TESTS<VECTOR<double,2> >::DEFORMABLES_STANDARD_TESTS(STREAM_TYPE,DEFORMABLE_BODY_COLLECTION<VECTOR<double,2> >&);
template DEFORMABLES_STANDARD_TESTS<VECTOR<double,3> >::DEFORMABLES_STANDARD_TESTS(STREAM_TYPE,DEFORMABLE_BODY_COLLECTION<VECTOR<double,3> >&);
template DEFORMABLES_STANDARD_TESTS<VECTOR<float,1> >::DEFORMABLES_STANDARD_TESTS(STREAM_TYPE,DEFORMABLE_BODY_COLLECTION<VECTOR<float,1> >&);
template DEFORMABLES_STANDARD_TESTS<VECTOR<float,2> >::DEFORMABLES_STANDARD_TESTS(STREAM_TYPE,DEFORMABLE_BODY_COLLECTION<VECTOR<float,2> >&);
template DEFORMABLES_STANDARD_TESTS<VECTOR<float,3> >::DEFORMABLES_STANDARD_TESTS(STREAM_TYPE,DEFORMABLE_BODY_COLLECTION<VECTOR<float,3> >&);
template TETRAHEDRALIZED_VOLUME<double>& DEFORMABLES_STANDARD_TESTS<VECTOR<double,3> >::Create_Cylinder(CYLINDER<double> const&,int,int,bool,RIGID_BODY_STATE<VECTOR<double,3> > const*,double);
template TETRAHEDRALIZED_VOLUME<float>& DEFORMABLES_STANDARD_TESTS<VECTOR<float,3> >::Create_Cylinder(CYLINDER<float> const&,int,int,bool,RIGID_BODY_STATE<VECTOR<float,3> > const*,float);
template TRIANGULATED_SURFACE<double> const& DEFORMABLES_STANDARD_TESTS<VECTOR<double,3> >::Copy_And_Add_Structure<TRIANGULATED_SURFACE<double> const>(TRIANGULATED_SURFACE<double> const&,ARRAY<int,int>*,bool);
template TRIANGULATED_SURFACE<float> const& DEFORMABLES_STANDARD_TESTS<VECTOR<float,3> >::Copy_And_Add_Structure<TRIANGULATED_SURFACE<float> const>(TRIANGULATED_SURFACE<float> const&,ARRAY<int,int>*,bool);
template void DEFORMABLES_STANDARD_TESTS<VECTOR<float,3> >::Initialize_Tetrahedron_Collisions(int,VIEWER_DIR&,TETRAHEDRALIZED_VOLUME<float>&,TRIANGLE_COLLISION_PARAMETERS<VECTOR<float,3> >&,TRIANGULATED_SURFACE<float>*);
template void DEFORMABLES_STANDARD_TESTS<VECTOR<double,3> >::Initialize_Tetrahedron_Collisions(int,VIEWER_DIR&,TETRAHEDRALIZED_VOLUME<double>&,TRIANGLE_COLLISION_PARAMETERS<VECTOR<double,3> >&,TRIANGULATED_SURFACE<double>*);
template LEVELSET_IMPLICIT_OBJECT<VECTOR<float,3> >* DEFORMABLES_STANDARD_TESTS<VECTOR<float,3> >::Read_Or_Initialize_Implicit_Surface(std::string const&,VIEWER_DIR const&,TRIANGULATED_SURFACE<float>&) const;
template LEVELSET_IMPLICIT_OBJECT<VECTOR<double,3> >* DEFORMABLES_STANDARD_TESTS<VECTOR<double,3> >::Read_Or_Initialize_Implicit_Surface(std::string const&,VIEWER_DIR const&,TRIANGULATED_SURFACE<double>&) const;
