Program Listing for File FluidModel.cpp

Return to documentation for file (SPlisHSPlasH/FluidModel.cpp)

#include "FluidModel.h"
#include "SPHKernels.h"
#include <iostream>
#include "TimeManager.h"
#include "TimeStep.h"
#include "Utilities/Logger.h"
#include "NeighborhoodSearch.h"
#include "Simulation.h"
#include "EmitterSystem.h"
#include "Viscosity/ViscosityBase.h"
#include "SurfaceTension/SurfaceTensionBase.h"
#include "Vorticity/VorticityBase.h"
#include "Drag/DragBase.h"
#include "SurfaceTension/SurfaceTension_Becker2007.h"
#include "SurfaceTension/SurfaceTension_Akinci2013.h"
#include "SurfaceTension/SurfaceTension_He2014.h"
#include "Viscosity/Viscosity_XSPH.h"
#include "Viscosity/Viscosity_Standard.h"
#include "Viscosity/Viscosity_Bender2017.h"
#include "Viscosity/Viscosity_Peer2015.h"
#include "Viscosity/Viscosity_Peer2016.h"
#include "Viscosity/Viscosity_Takahashi2015.h"
#include "Viscosity/Viscosity_Weiler2018.h"
#include "Vorticity/VorticityConfinement.h"
#include "Vorticity/MicropolarModel_Bender2017.h"
#include "Drag/DragForce_Gissler2017.h"
#include "Drag/DragForce_Macklin2014.h"
#include "Elasticity/ElasticityBase.h"
#include "Elasticity/Elasticity_Becker2009.h"
#include "Elasticity/Elasticity_Peer2018.h"

#ifdef USE_THIRD_PARTY_METHODS
#include "SurfaceTension/SurfaceTension_ZorillaRitter2020.h"
#endif

using namespace SPH;
using namespace GenParam;

int FluidModel::NUM_PARTICLES = -1;
int FluidModel::NUM_REUSED_PARTICLES = -1;
int FluidModel::DENSITY0 = -1;
int FluidModel::DRAG_METHOD = -1;
int FluidModel::SURFACE_TENSION_METHOD = -1;
int FluidModel::VISCOSITY_METHOD = -1;
int FluidModel::VORTICITY_METHOD = -1;
int FluidModel::ELASTICITY_METHOD = -1;
int FluidModel::ENUM_DRAG_NONE = -1;
int FluidModel::ENUM_DRAG_MACKLIN2014 = -1;
int FluidModel::ENUM_DRAG_GISSLER2017 = -1;
int FluidModel::ENUM_SURFACETENSION_NONE = -1;
int FluidModel::ENUM_SURFACETENSION_BECKER2007 = -1;
int FluidModel::ENUM_SURFACETENSION_AKINCI2013 = -1;
int FluidModel::ENUM_SURFACETENSION_HE2014 = -1;
#ifdef USE_THIRD_PARTY_METHODS
int FluidModel::ENUM_SURFACETENSION_ZORILLARITTER2020 = -1;
#endif
int FluidModel::ENUM_VISCOSITY_NONE = -1;
int FluidModel::ENUM_VISCOSITY_STANDARD = -1;
int FluidModel::ENUM_VISCOSITY_XSPH = -1;
int FluidModel::ENUM_VISCOSITY_BENDER2017 = -1;
int FluidModel::ENUM_VISCOSITY_PEER2015 = -1;
int FluidModel::ENUM_VISCOSITY_PEER2016 = -1;
int FluidModel::ENUM_VISCOSITY_TAKAHASHI2015 = -1;
int FluidModel::ENUM_VISCOSITY_WEILER2018 = -1;
int FluidModel::ENUM_VORTICITY_NONE = -1;
int FluidModel::ENUM_VORTICITY_MICROPOLAR = -1;
int FluidModel::ENUM_VORTICITY_VC = -1;
int FluidModel::ENUM_ELASTICITY_NONE = -1;
int FluidModel::ENUM_ELASTICITY_BECKER2009 = -1;
int FluidModel::ENUM_ELASTICITY_PEER2018 = -1;


FluidModel::FluidModel() :
    m_masses(),
    m_a(),
    m_v0(),
    m_x0(),
    m_x(),
    m_v(),
    m_density(),
    m_particleId(),
    m_particleState()
{
    m_density0 = 1000.0;
    m_pointSetIndex = 0;

    m_emitterSystem = new EmitterSystem(this);
    m_viscosity = nullptr;
    m_viscosityMethod = ViscosityMethods::None;
    m_surfaceTension = nullptr;
    m_surfaceTensionMethod = SurfaceTensionMethods::None;
    m_vorticityMethod = VorticityMethods::None;
    m_vorticity = nullptr;
    m_dragMethod = DragMethods::None;
    m_drag = nullptr;
    m_dragMethodChanged = nullptr;
    m_surfaceTensionMethodChanged = nullptr;
    m_viscosityMethodChanged = nullptr;
    m_vorticityMethodChanged = nullptr;
    m_elasticityMethod = ElasticityMethods::None;
    m_elasticity = nullptr;
    m_elasticityMethodChanged = nullptr;

    addField({ "id", FieldType::UInt, [&](const unsigned int i) -> unsigned int* { return &getParticleId(i); }, true });
    addField({ "position", FieldType::Vector3, [&](const unsigned int i) -> Real* { return &getPosition(i)[0]; }, true });
    addField({ "velocity", FieldType::Vector3, [&](const unsigned int i) -> Real* { return &getVelocity(i)[0]; }, true });
    addField({ "density", FieldType::Scalar, [&](const unsigned int i) -> Real* { return &getDensity(i); }, false });
}

FluidModel::~FluidModel(void)
{
    removeFieldByName("id");
    removeFieldByName("position");
    removeFieldByName("velocity");
    removeFieldByName("density");

    delete m_emitterSystem;
    delete m_surfaceTension;
    delete m_drag;
    delete m_vorticity;
    delete m_viscosity;
    delete m_elasticity;

    releaseFluidParticles();
}

void FluidModel::init()
{
    initParameters();

    setViscosityMethod(static_cast<int>(ViscosityMethods::Standard));
}

void FluidModel::initParameters()
{
    std::string groupName = "FluidModel";
    ParameterObject::initParameters();

    ParameterBase::GetFunc<Real> getDensity0Fct = std::bind(&FluidModel::getDensity0, this);
    ParameterBase::SetFunc<Real> setDensity0Fct = std::bind(&FluidModel::setDensity0, this, std::placeholders::_1);
    DENSITY0 = createNumericParameter("density0", "Rest density", getDensity0Fct, setDensity0Fct);
    setGroup(DENSITY0, groupName);
    setDescription(DENSITY0, "Rest density of the fluid.");
    getParameter(DENSITY0)->setReadOnly(true);

    NUM_PARTICLES = createNumericParameter("numParticles", "# active particles", &m_numActiveParticles);
    setGroup(NUM_PARTICLES, groupName);
    setDescription(NUM_PARTICLES, "Number of active fluids particles in the simulation.");
    getParameter(NUM_PARTICLES)->setReadOnly(true);

    NUM_REUSED_PARTICLES = createNumericParameter<unsigned int>("numReusedParticles", "# reused particles", [&]() { return m_emitterSystem->numReusedParticles(); });
    setGroup(NUM_REUSED_PARTICLES, groupName);
    setDescription(NUM_REUSED_PARTICLES, "Number of reused fluid particles in the simulation.");
    getParameter(NUM_REUSED_PARTICLES)->setReadOnly(true);

    ParameterBase::GetFunc<int> getDragFct = std::bind(&FluidModel::getDragMethod, this);
    ParameterBase::SetFunc<int> setDragFct = std::bind(&FluidModel::setDragMethod, this, std::placeholders::_1);
    DRAG_METHOD = createEnumParameter("dragMethod", "Drag method", getDragFct, setDragFct);
    setGroup(DRAG_METHOD, "Drag force");
    setDescription(DRAG_METHOD, "Method to compute drag forces.");
    EnumParameter *enumParam = static_cast<EnumParameter*>(getParameter(DRAG_METHOD));
    enumParam->addEnumValue("None", ENUM_DRAG_NONE);
    enumParam->addEnumValue("Macklin et al. 2014", ENUM_DRAG_MACKLIN2014);
    enumParam->addEnumValue("Gissler et al. 2017", ENUM_DRAG_GISSLER2017);


    ParameterBase::GetFunc<int> getSurfaceTensionFct = std::bind(&FluidModel::getSurfaceTensionMethod, this);
    ParameterBase::SetFunc<int> setSurfaceTensionFct = std::bind(&FluidModel::setSurfaceTensionMethod, this, std::placeholders::_1);
    SURFACE_TENSION_METHOD = createEnumParameter("surfaceTensionMethod", "Surface tension", getSurfaceTensionFct, setSurfaceTensionFct);
    setGroup(SURFACE_TENSION_METHOD, "Surface tension");
    setDescription(SURFACE_TENSION_METHOD, "Method to compute surface tension forces.");
    enumParam = static_cast<EnumParameter*>(getParameter(SURFACE_TENSION_METHOD));
    enumParam->addEnumValue("None", ENUM_SURFACETENSION_NONE);
    enumParam->addEnumValue("Becker & Teschner 2007", ENUM_SURFACETENSION_BECKER2007);
    enumParam->addEnumValue("Akinci et al. 2013", ENUM_SURFACETENSION_AKINCI2013);
    enumParam->addEnumValue("He et al. 2014", ENUM_SURFACETENSION_HE2014);
#ifdef USE_THIRD_PARTY_METHODS
    enumParam->addEnumValue("Zorilla, Ritter, et al. 2020", ENUM_SURFACETENSION_ZORILLARITTER2020);
#endif

    ParameterBase::GetFunc<int> getViscosityFct = std::bind(&FluidModel::getViscosityMethod, this);
    ParameterBase::SetFunc<int> setViscosityFct = std::bind(&FluidModel::setViscosityMethod, this, std::placeholders::_1);
    VISCOSITY_METHOD = createEnumParameter("viscosityMethod", "Viscosity", getViscosityFct, setViscosityFct);
    setGroup(VISCOSITY_METHOD, "Viscosity");
    setDescription(VISCOSITY_METHOD, "Method to compute viscosity forces.");
    enumParam = static_cast<EnumParameter*>(getParameter(VISCOSITY_METHOD));
    enumParam->addEnumValue("None", ENUM_VISCOSITY_NONE);
    enumParam->addEnumValue("Standard", ENUM_VISCOSITY_STANDARD);
    enumParam->addEnumValue("XSPH", ENUM_VISCOSITY_XSPH);
    enumParam->addEnumValue("Bender and Koschier 2017", ENUM_VISCOSITY_BENDER2017);
    enumParam->addEnumValue("Peer et al. 2015", ENUM_VISCOSITY_PEER2015);
    enumParam->addEnumValue("Peer et al. 2016", ENUM_VISCOSITY_PEER2016);
    enumParam->addEnumValue("Takahashi et al. 2015 (improved)", ENUM_VISCOSITY_TAKAHASHI2015);
    enumParam->addEnumValue("Weiler et al. 2018", ENUM_VISCOSITY_WEILER2018);

    ParameterBase::GetFunc<int> getVorticityFct = std::bind(&FluidModel::getVorticityMethod, this);
    ParameterBase::SetFunc<int> setVorticityFct = std::bind(&FluidModel::setVorticityMethod, this, std::placeholders::_1);
    VORTICITY_METHOD = createEnumParameter("vorticityMethod", "Vorticity", getVorticityFct, setVorticityFct);
    setGroup(VORTICITY_METHOD, "Vorticity");
    setDescription(VORTICITY_METHOD, "Method to compute vorticity forces.");
    enumParam = static_cast<EnumParameter*>(getParameter(VORTICITY_METHOD));
    enumParam->addEnumValue("None", ENUM_VORTICITY_NONE);
    enumParam->addEnumValue("Micropolar model", ENUM_VORTICITY_MICROPOLAR);
    enumParam->addEnumValue("Vorticity confinement", ENUM_VORTICITY_VC);

    ParameterBase::GetFunc<int> getElasticityFct = std::bind(&FluidModel::getElasticityMethod, this);
    ParameterBase::SetFunc<int> setElasticityFct = std::bind(&FluidModel::setElasticityMethod, this, std::placeholders::_1);
    ELASTICITY_METHOD = createEnumParameter("elasticityMethod", "Elasticity method", getElasticityFct, setElasticityFct);
    setGroup(ELASTICITY_METHOD, "Elasticity");
    setDescription(ELASTICITY_METHOD, "Method to compute elastic forces.");
    enumParam = static_cast<EnumParameter*>(getParameter(ELASTICITY_METHOD));
    enumParam->addEnumValue("None", ENUM_ELASTICITY_NONE);
    enumParam->addEnumValue("Becker et al. 2009", ENUM_ELASTICITY_BECKER2009);
    enumParam->addEnumValue("Peer et al. 2018", ENUM_ELASTICITY_PEER2018);
}


void FluidModel::reset()
{
    setNumActiveParticles(m_numActiveParticles0);
    const unsigned int nPoints = numActiveParticles();

    // Fluid
    for (unsigned int i = 0; i < nPoints; i++)
    {
        const Vector3r& x0 = getPosition0(i);
        getPosition(i) = x0;
        getVelocity(i) = getVelocity0(i);
        getAcceleration(i).setZero();
        m_density[i] = 0.0;
        m_particleId[i] = i;
        m_particleState[i] = ParticleState::Active;
    }
    // emitted particles
    for (unsigned int i = nPoints; i < (unsigned int)m_particleId.size(); i++)
    {
        m_particleId[i] = i;
    }

    NeighborhoodSearch *neighborhoodSearch = Simulation::getCurrent()->getNeighborhoodSearch();
    if (neighborhoodSearch->point_set(m_pointSetIndex).n_points() != nPoints)
        neighborhoodSearch->resize_point_set(m_pointSetIndex, &getPosition(0)[0], nPoints);

    if (m_surfaceTension)
        m_surfaceTension->reset();
    if (m_viscosity)
        m_viscosity->reset();
    if (m_vorticity)
        m_vorticity->reset();
    if (m_drag)
        m_drag->reset();
    if (m_elasticity)
        m_elasticity->reset();

    m_emitterSystem->reset();
}

void FluidModel::initMasses()
{
    const Real particleRadius = Simulation::getCurrent()->getParticleRadius();
    const int nParticles = (int) numParticles();
    const Real diam = static_cast<Real>(2.0)*particleRadius;
    if (Simulation::getCurrent()->is2DSimulation())
        m_V = static_cast<Real>(0.8) * diam*diam;
    else
        m_V = static_cast<Real>(0.8) * diam*diam*diam;

    #pragma omp parallel default(shared)
    {
        #pragma omp for schedule(static)
        for (int i = 0; i < nParticles; i++)
        {
            setMass(i, m_V * m_density0);                       // each particle represents a cube with a side length of r
                                                                // mass is slightly reduced to prevent pressure at the beginning of the simulation
        }
    }
}

void FluidModel::resizeFluidParticles(const unsigned int newSize)
{
    m_x0.resize(newSize);
    m_x.resize(newSize);
    m_v.resize(newSize);
    m_v0.resize(newSize);
    m_a.resize(newSize);
    m_masses.resize(newSize);
    m_density.resize(newSize);
    m_particleId.resize(newSize);
    m_particleState.resize(newSize);
}

void FluidModel::releaseFluidParticles()
{
    m_x0.clear();
    m_x.clear();
    m_v.clear();
    m_v0.clear();
    m_a.clear();
    m_masses.clear();
    m_density.clear();
    m_particleId.clear();
    m_particleState.clear();
}

void FluidModel::initModel(const std::string &id, const unsigned int nFluidParticles, Vector3r* fluidParticles, Vector3r* fluidVelocities, const unsigned int nMaxEmitterParticles)
{
    m_id = id;
    init();
    releaseFluidParticles();
    resizeFluidParticles(nFluidParticles + nMaxEmitterParticles);

    // copy fluid positions
    #pragma omp parallel default(shared)
    {
        #pragma omp for schedule(static)
        for (int i = 0; i < (int)nFluidParticles; i++)
        {
            getPosition0(i) = fluidParticles[i];
            getPosition(i) = fluidParticles[i];
            getVelocity0(i) = fluidVelocities[i];
            getVelocity(i) = fluidVelocities[i];
            getAcceleration(i).setZero();
            m_density[i] = 0.0;
            m_particleId[i] = i;
            m_particleState[i] = ParticleState::Active;
        }
    }
    // set IDs for emitted particles
    for (unsigned int i = nFluidParticles; i < (nFluidParticles + nMaxEmitterParticles); i++)
    {
        m_particleId[i] = i;
    }

    // initialize masses
    initMasses();

    // Fluids
    NeighborhoodSearch *neighborhoodSearch = Simulation::getCurrent()->getNeighborhoodSearch();
    m_pointSetIndex = neighborhoodSearch->add_point_set(&getPosition(0)[0], nFluidParticles, true, true, true, this);

    m_numActiveParticles0 = nFluidParticles;
    m_numActiveParticles = m_numActiveParticles0;
}


void FluidModel::performNeighborhoodSearchSort()
{
    const unsigned int numPart = numActiveParticles();
    if (numPart == 0)
        return;

    NeighborhoodSearch *neighborhoodSearch = Simulation::getCurrent()->getNeighborhoodSearch();

    auto const& d = neighborhoodSearch->point_set(m_pointSetIndex);
    d.sort_field(&m_x[0]);
    d.sort_field(&m_v[0]);
    d.sort_field(&m_a[0]);
    d.sort_field(&m_masses[0]);
    d.sort_field(&m_density[0]);
    d.sort_field(&m_particleId[0]);
    d.sort_field(&m_particleState[0]);

    if (m_viscosity)
        m_viscosity->performNeighborhoodSearchSort();
    if (m_surfaceTension)
        m_surfaceTension->performNeighborhoodSearchSort();
    if (m_vorticity)
        m_vorticity->performNeighborhoodSearchSort();
    if (m_drag)
        m_drag->performNeighborhoodSearchSort();
    if (m_elasticity)
        m_elasticity->performNeighborhoodSearchSort();
}

void SPH::FluidModel::setDensity0(const Real v)
{
    m_density0 = v;
    initMasses();
}

const SPH::FieldDescription & SPH::FluidModel::getField(const std::string &name)
{
    unsigned int index = 0;
    for (auto i = 0; i < m_fields.size(); i++)
    {
        if (m_fields[i].name == name)
        {
            index = i;
            break;
        }
    }
    return m_fields[index];
}

void FluidModel::setNumActiveParticles(const unsigned int num)
{
    m_numActiveParticles = num;
}

unsigned int FluidModel::numActiveParticles() const
{
    return m_numActiveParticles;
}

void FluidModel::setDragMethodChangedCallback(std::function<void()> const& callBackFct)
{
    m_dragMethodChanged = callBackFct;
}

void FluidModel::setSurfaceMethodChangedCallback(std::function<void()> const& callBackFct)
{
    m_surfaceTensionMethodChanged = callBackFct;
}

void FluidModel::setViscosityMethodChangedCallback(std::function<void()> const& callBackFct)
{
    m_viscosityMethodChanged = callBackFct;
}

void FluidModel::setVorticityMethodChangedCallback(std::function<void()> const& callBackFct)
{
    m_vorticityMethodChanged = callBackFct;
}

void FluidModel::setElasticityMethodChangedCallback(std::function<void()> const& callBackFct)
{
    m_elasticityMethodChanged = callBackFct;
}

void FluidModel::computeSurfaceTension()
{
    if (m_surfaceTension)
        m_surfaceTension->step();
}

void FluidModel::computeViscosity()
{
    if (m_viscosity)
        m_viscosity->step();
}

void FluidModel::computeVorticity()
{
    if (m_vorticity)
        m_vorticity->step();
}

void FluidModel::computeDragForce()
{
    if (m_drag)
        m_drag->step();
}

void FluidModel::computeElasticity()
{
    if (m_elasticity)
        m_elasticity->step();
}

void FluidModel::emittedParticles(const unsigned int startIndex)
{
    if (m_viscosity)
        m_viscosity->emittedParticles(startIndex);
    if (m_surfaceTension)
        m_surfaceTension->emittedParticles(startIndex);
    if (m_vorticity)
        m_vorticity->emittedParticles(startIndex);
    if (m_drag)
        m_drag->emittedParticles(startIndex);
    if (m_elasticity)
        m_elasticity->emittedParticles(startIndex);
}

void FluidModel::setSurfaceTensionMethod(const int val)
{
    SurfaceTensionMethods stm = static_cast<SurfaceTensionMethods>(val);
    if ((stm < SurfaceTensionMethods::None) || (stm >= SurfaceTensionMethods::NumSurfaceTensionMethods))
        stm = SurfaceTensionMethods::None;
    if (stm == m_surfaceTensionMethod)
        return;

    delete m_surfaceTension;
    m_surfaceTension = nullptr;

    m_surfaceTensionMethod = stm;
    if (m_surfaceTensionMethod == SurfaceTensionMethods::Becker2007)
        m_surfaceTension = new SurfaceTension_Becker2007(this);
    else if (m_surfaceTensionMethod == SurfaceTensionMethods::Akinci2013)
        m_surfaceTension = new SurfaceTension_Akinci2013(this);
    else if (m_surfaceTensionMethod == SurfaceTensionMethods::He2014)
        m_surfaceTension = new SurfaceTension_He2014(this);
#ifdef USE_THIRD_PARTY_METHODS
    else if (m_surfaceTensionMethod == SurfaceTensionMethods::ZorillaRitter2020)
        m_surfaceTension = new SurfaceTension_ZorillaRitter2020(this);
#endif

    if (m_surfaceTension != nullptr)
        m_surfaceTension->init();

    if (m_surfaceTensionMethodChanged != nullptr)
        m_surfaceTensionMethodChanged();
}

void FluidModel::setViscosityMethod(const int val)
{
    ViscosityMethods vm = static_cast<ViscosityMethods>(val);
    if ((vm < ViscosityMethods::None) || (vm >= ViscosityMethods::NumViscosityMethods))
        vm = ViscosityMethods::XSPH;

    if (vm == m_viscosityMethod)
        return;

    delete m_viscosity;
    m_viscosity = nullptr;

    m_viscosityMethod = vm;

    if (m_viscosityMethod == ViscosityMethods::Standard)
        m_viscosity = new Viscosity_Standard(this);
    else if (m_viscosityMethod == ViscosityMethods::XSPH)
        m_viscosity = new Viscosity_XSPH(this);
    else if (m_viscosityMethod == ViscosityMethods::Bender2017)
        m_viscosity = new Viscosity_Bender2017(this);
    else if (m_viscosityMethod == ViscosityMethods::Peer2015)
        m_viscosity = new Viscosity_Peer2015(this);
    else if (m_viscosityMethod == ViscosityMethods::Peer2016)
        m_viscosity = new Viscosity_Peer2016(this);
    else if (m_viscosityMethod == ViscosityMethods::Takahashi2015)
        m_viscosity = new Viscosity_Takahashi2015(this);
    else if (m_viscosityMethod == ViscosityMethods::Weiler2018)
        m_viscosity = new Viscosity_Weiler2018(this);

    if (m_viscosity != nullptr)
        m_viscosity->init();

    if (m_viscosityMethodChanged != nullptr)
        m_viscosityMethodChanged();
}


void FluidModel::setVorticityMethod(const int val)
{
    VorticityMethods vm = static_cast<VorticityMethods>(val);
    if ((vm < VorticityMethods::None) || (vm >= VorticityMethods::NumVorticityMethods))
        vm = VorticityMethods::None;

    if (vm == m_vorticityMethod)
        return;

    delete m_vorticity;
    m_vorticity = nullptr;

    m_vorticityMethod = vm;

    if (m_vorticityMethod == VorticityMethods::Micropolar)
        m_vorticity = new MicropolarModel_Bender2017(this);
    else if (m_vorticityMethod == VorticityMethods::VorticityConfinement)
        m_vorticity = new VorticityConfinement(this);

    if (m_vorticity != nullptr)
        m_vorticity->init();

    if (m_vorticityMethodChanged != nullptr)
        m_vorticityMethodChanged();
}

void FluidModel::setDragMethod(const int val)
{
    DragMethods dm = static_cast<DragMethods>(val);
    if ((dm < DragMethods::None) || (dm >= DragMethods::NumDragMethods))
        dm = DragMethods::None;

    if (dm == m_dragMethod)
        return;

    delete m_drag;
    m_drag = nullptr;

    m_dragMethod = dm;

    if (m_dragMethod == DragMethods::Gissler2017)
        m_drag = new DragForce_Gissler2017(this);
    else if (m_dragMethod == DragMethods::Macklin2014)
        m_drag = new DragForce_Macklin2014(this);

    if (m_drag != nullptr)
        m_drag->init();

    if (m_dragMethodChanged != nullptr)
        m_dragMethodChanged();
}

void FluidModel::setElasticityMethod(const int val)
{
    ElasticityMethods em = static_cast<ElasticityMethods>(val);
    if ((em < ElasticityMethods::None) || (em >= ElasticityMethods::NumElasticityMethods))
        em = ElasticityMethods::None;

    if (em == m_elasticityMethod)
        return;

    delete m_elasticity;
    m_elasticity = nullptr;

    m_elasticityMethod = em;

    if (m_elasticityMethod == ElasticityMethods::Becker2009)
        m_elasticity = new Elasticity_Becker2009(this);
    else if (m_elasticityMethod == ElasticityMethods::Peer2018)
        m_elasticity = new Elasticity_Peer2018(this);

    if (m_elasticity != nullptr)
        m_elasticity->init();

    if (m_elasticityMethodChanged != nullptr)
        m_elasticityMethodChanged();
}


void FluidModel::addField(const FieldDescription &field)
{
    m_fields.push_back(field);
    std::sort(m_fields.begin(), m_fields.end(), [](FieldDescription &i, FieldDescription &j) -> bool { return (i.name < j.name); });
}

void FluidModel::removeFieldByName(const std::string &fieldName)
{
    for (auto it = m_fields.begin(); it != m_fields.end(); it++)
    {
        if (it->name == fieldName)
        {
            m_fields.erase(it);
            break;
        }
    }
}

void SPH::FluidModel::saveState(BinaryFileWriter &binWriter)
{
    binWriter.write(m_numActiveParticles);
    binWriter.writeBuffer((char*) m_particleState.data(), m_numActiveParticles * sizeof(ParticleState));

    if (m_surfaceTension)
        m_surfaceTension->saveState(binWriter);
    if (m_viscosity)
        m_viscosity->saveState(binWriter);
    if (m_vorticity)
        m_vorticity->saveState(binWriter);
    if (m_drag)
        m_drag->saveState(binWriter);
    if (m_elasticity)
        m_elasticity->saveState(binWriter);
    m_emitterSystem->saveState(binWriter);
}

void SPH::FluidModel::loadState(BinaryFileReader &binReader)
{
    binReader.read(m_numActiveParticles);
    NeighborhoodSearch *neighborhoodSearch = Simulation::getCurrent()->getNeighborhoodSearch();
    neighborhoodSearch->update_point_sets();
    neighborhoodSearch->resize_point_set(m_pointSetIndex, &getPosition(0)[0], m_numActiveParticles);

    binReader.readBuffer((char*)m_particleState.data(), m_numActiveParticles * sizeof(ParticleState));

    if (m_surfaceTension)
        m_surfaceTension->loadState(binReader);
    if (m_viscosity)
        m_viscosity->loadState(binReader);
    if (m_vorticity)
        m_vorticity->loadState(binReader);
    if (m_drag)
        m_drag->loadState(binReader);
    if (m_elasticity)
        m_elasticity->loadState(binReader);

    m_emitterSystem->loadState(binReader);
}